diff --git a/docs/soft/maixpy3/zh/sidebar.json b/docs/soft/maixpy3/zh/sidebar.json index 40fd50bc..a5cd7782 100644 --- a/docs/soft/maixpy3/zh/sidebar.json +++ b/docs/soft/maixpy3/zh/sidebar.json @@ -22,6 +22,10 @@ } ] }, + { + "label": "M2DOCK(V831)上手视觉指南", + "file": "/news/MaixPy3/v831_usage/v831_usage.md" + }, { "label":"遇到问题怎么办?", "items": [ diff --git a/news/MaixPy3/v831_usage/assest/6050.jpg b/news/MaixPy3/v831_usage/assest/6050.jpg new file mode 100644 index 00000000..f7564039 Binary files /dev/null and b/news/MaixPy3/v831_usage/assest/6050.jpg differ diff --git a/news/MaixPy3/v831_usage/assest/YOLOv2.jpg b/news/MaixPy3/v831_usage/assest/YOLOv2.jpg new file mode 100644 index 00000000..ae1826ea Binary files /dev/null and b/news/MaixPy3/v831_usage/assest/YOLOv2.jpg differ diff --git a/news/MaixPy3/v831_usage/assest/i2c.jpg b/news/MaixPy3/v831_usage/assest/i2c.jpg new file mode 100644 index 00000000..eaedf7d2 Binary files /dev/null and b/news/MaixPy3/v831_usage/assest/i2c.jpg differ diff --git a/news/MaixPy3/v831_usage/assest/print.jpg b/news/MaixPy3/v831_usage/assest/print.jpg new file mode 100644 index 00000000..46c83fcc Binary files /dev/null and b/news/MaixPy3/v831_usage/assest/print.jpg differ diff --git a/news/MaixPy3/v831_usage/v831_usage.md b/news/MaixPy3/v831_usage/v831_usage.md index 399a35f4..66e873aa 100644 --- a/news/MaixPy3/v831_usage/v831_usage.md +++ b/news/MaixPy3/v831_usage/v831_usage.md @@ -15,7 +15,7 @@ update: > 铛 ~ 新的 2023 年当然要有新的上手指南辣(虽然已经过去5个月)总有事情要被鸽但能填上就是好坑。 > 新的上手指南还是旧的目的,希望大家可以借助这篇文章在使用 M2DOCK 时更加顺利一点吧! -![m2dock](./../../../docs/hardware/assets/maixII/m2dock.jpg) +![m2dock](https://wiki.sipeed.com/hardware/assets/maixII/m2dock.jpg) 本篇文章以 `视觉` 为主题阐述如何在 M2DOCK(V831)从基础的开箱上手再到视觉的衍生(深度开发)的使用全过程,因篇幅的有限这里不再对产品进行详细介绍,想要了解相关产品介绍可点击资料进行查看。 @@ -218,7 +218,7 @@ from maix import camera, display, image display.show(camera.capture()) ``` -
+![hello_world](https://wiki.sipeed.com/hardware/zh/maixII/M2/asserts/hello_world.jpg) > 如果屏幕没有显示内容。那么首先确认一下镜像镜像版本,并且确认一下外设和驱动对的上 @@ -293,24 +293,6 @@ for i in range(120): **第二步**:存储后务必使用电脑系统自带的 U 盘移除相关的功能来断开电脑与 U 盘的通信。 **第三步**:最后进行重启(按下 RST 键或者重新插拔 USB)来使 m2dock 再次重新加载文件系统,否则会因为板卡与电脑非正常断开通信而导致文件系统损坏,保存失败。 -## 学会使用文档(资源) - -无论是在 K210 还是 M2DOCK(V831)上一定要学会使用我们公开的文档资料,去借助资料的力量帮助自己在学习的道路上往前走,学会借助资源完善自身的不足,可以让你少走很多的坑以及弯路。 - -1. 学会搜索并利用官方的 `文档社区` 以及 `github issue` 资源,会让新手小白少走很多弯路雷坑。 -2. 文档资源在 `常见问题 FAQ` 中基本涵盖了所有的坑,使用途中报错可以先查看排错。 -3. 想要实现更多的功能示例或需要更多的脚本源码,可前往 MaixPy3 的源码例程仓库查找。 - -**文档搜索例程**:[点击查看](https://wiki.sipeed.com/soft/maixpy/zh/how_to_read.html) -**BBS 社区教程贴**:[点击前往](https://bbs.sipeed.com/thread/492) -**MaixPy3 源码仓库**:[点击前往](https://github.com/sipeed/maixpy3) -**MaixPy3 issue**:[点击前往](https://github.com/sipeed/MaixPy3/issues) -**MaixPy3 常见问题指南**:[点击前往](https://wiki.sipeed.com/soft/maixpy3/zh/tools/0.MaixII-Dock.html#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%8C%87%E5%8D%97) -**V831 源码**:[点击前往](https://github.com/Tina-Linux/tina-V83x) -**V83 工具链**:[点击前往](https://dl.sipeed.com/shareURL/MaixII/MaixII-Dock/SDK/Toolchain) -**V831 常见问题与解决方法**:[点击前往](https://wiki.sipeed.com/soft/maixpy3/zh/question/maixpy3_faq.html) -**BBS 社区常见问题汇总贴**:[点击查看](https://bbs.sipeed.com/thread/489) - ## 进阶使用 > 到达进阶使用这里后,这里则需要小伙伴们具备一些 Linux 基础了才能继续往下走学习。 @@ -380,20 +362,41 @@ echo "usb_device" > /sys/devices/platform/soc/usbc0/otg_role **Linux 系统下 V831 使用编译链**:[toolchain-sunxi-musl-pack-2021-01-09.tar.xz](https://dl.sipeed.com/shareURL/MaixII/MaixII-Dock/SDK/Toolchain) -## 如何编译与开发 +## 学会使用文档(资源) + +无论是在 K210 还是 M2DOCK(V831)上一定要学会使用我们公开的文档资料,去借助资料的力量帮助自己在学习的道路上往前走,学会借助资源完善自身的不足,可以让你少走很多的坑以及弯路。 + +1. 学会搜索并利用官方的 `文档社区` 以及 `github issue` 资源,会让新手小白少走很多弯路雷坑。 +2. 文档资源在 `常见问题 FAQ` 中基本涵盖了所有的坑,使用途中报错可以先查看排错。 +3. 想要实现更多的功能示例或需要更多的脚本源码,可前往 MaixPy3 的源码例程仓库查找。 + +**文档搜索例程**:[点击查看](https://wiki.sipeed.com/soft/maixpy/zh/how_to_read.html) +**BBS 社区教程贴**:[点击前往](https://bbs.sipeed.com/thread/492) +**MaixPy3 源码仓库**:[点击前往](https://github.com/sipeed/maixpy3) +**MaixPy3 issue**:[点击前往](https://github.com/sipeed/MaixPy3/issues) +**MaixPy3 常见问题指南**:[点击前往](https://wiki.sipeed.com/soft/maixpy3/zh/tools/0.MaixII-Dock.html#%E5%B8%B8%E8%A7%81%E9%97%AE%E9%A2%98%E6%8C%87%E5%8D%97) +**V831 源码**:[点击前往](https://github.com/Tina-Linux/tina-V83x) +**V83 工具链**:[点击前往](https://dl.sipeed.com/shareURL/MaixII/MaixII-Dock/SDK/Toolchain) +**V831 常见问题与解决方法**:[点击前往](https://wiki.sipeed.com/soft/maixpy3/zh/question/maixpy3_faq.html) +**BBS 社区常见问题汇总贴**:[点击查看](https://bbs.sipeed.com/thread/489) + + +## 编译与开发 + +> 当你走到这时已经代表你熟悉(V831)的基础操作准备深层了解,以下的文档是为了开发者写的参考文,希望可以基于此让更多的伙伴进入 Linux 嵌入式开发的领域。 ### 系统环境搭建 - 安装 vscode 和换源:[点击前往](https://fishros.org.cn/forum/topic/20/%E5%B0%8F%E9%B1%BC%E7%9A%84%E4%B8%80%E9%94%AE%E5%AE%89%E8%A3%85%E7%B3%BB%E5%88%97) - 安装输入法:[点击前往](https://shurufa.sogou.com/linux/guide) -- 安装截图工具:[点击前往](https://zhuanlan.zhihu.com/p/88325211),并设置快捷键为 `ctrl + alt + a`. +- 安装截图工具:[点击安装](https://zhuanlan.zhihu.com/p/88325211)并设置快捷键为 `ctrl + alt + a`。 - 安装魔法上网工具:自行安装 -### MaixPY3 架构介绍 +### MaixPy3 架构介绍 -篇幅过多这里不详细介绍,小伙伴可根据指路前往:[MaixPy3 架构介绍](https://wiki.sipeed.com/soft/maixpy3/zh/others/framework.html) +因篇幅过多这里不详细介绍,小伙伴们可根据指路前往:[MaixPy3 架构介绍](https://wiki.sipeed.com/soft/maixpy3/zh/others/framework.html) -### 为什么采用 libmax 开发 +### 为什么采用 libmaix 开发 ```C++ +----------------------------------------+ @@ -437,17 +440,17 @@ echo "usb_device" > /sys/devices/platform/soc/usbc0/otg_role ``` -可以清楚的看到我们平时用的 Python 的接口底层实现也是通过了 libmaix 的开发成果,我们平时需要 Python 语言调参验证原型功能,是为了快速验证我们的开发板可用于不同的场景和功能,方便我们快速验证功能外设是否正常,同时我们也需要 C / C++ 优化性能和减少内存占用用于开发商业项目,那么,直接使用 libmaix 进行开发就显得尤为重要了。 -一开始采用的 python 需要一定时间过渡到 c++ ,特别是成熟的项目,更需要脱离 python 了,为了更成熟精简的项目结构和更好的性能,这就不得不看看 libmaix 了喵。 +上文中我们可以清楚的看到平时用的 `Python` 的接口底层实现也是通过了 `libmaix` 的开发成果,平时需要 `Python` 语言调参验证原型功能,是为了快速验证我们的开发板可用于不同的场景和功能,方便我们快速验证功能外设是否正常,同时我们也需要 `C / C++` 优化性能和减少内存占用用于开发商业项目,那么,直接使用 libmaix 进行开发就显得尤为重要了。 +一开始采用的 `Python` 需要一定时间过渡到 `C++` ,特别是成熟的项目更需要脱离 `Python`,为了更成熟精简的项目结构和更好的性能就得看看 `libmaix` 的表现了。 #### 首先要配置好开发环境 -参考下面连接完成你的开发环境配置 +> 参考下文给出的资料文章链接配置相关开发环境。 1. [V831 如何使用 libmaix SDK C++ 开发](https://wiki.sipeed.com/news/MaixPy3/run_lvgl/run_lvgl.html) 2. [Linux 连接不上 adb 设备](https://wiki.sipeed.com/news/others/linux_adb/linux_adb.html) -完成了上面的环境配置后,这里提供了一个Demo,内容就是如何使用 V831 LINUX C++ 进行图像视觉处理和开发,在此我提供了一个 串口 + mv cv ai 的示例项目,供开发者评估使用,此处不涉及到 ISP 调试,需要进一步 ISP 图像调试则需要联系大佬鼠。 +完成上面的环境配置后,这里提供了可运行的 `Demo` 内容就是如何使用 V831 LINUX C++ 进行图像视觉处理和开发,在此我们提供了 `串口 + mv cv ai` 的示例项目,供开发者评估使用,此处不涉及到 ISP 调试,需要进一步 ISP 图像调试则需要联系大佬鼠。 >相关资料:[libmaix](https://github.com/sipeed/libmaix/blob/6ad1102a0527bd3d394c0b1de82cbf64d6eac40d/components/maix_dls831/src/dls831_uvai.cpp#L435-L663) @@ -455,12 +458,730 @@ echo "usb_device" > /sys/devices/platform/soc/usbc0/otg_role ![libmaix](./assest/libmaix.jpg) -我们可以看到程序是稳定三十帧的运行速率。 +我们可以看到程序是保持稳定三十帧的运行速率。 +## 库 + +接下来我们来认识一下现有的库:分别是 AI、Opencv、Openmv、LVGL。 + +### AI + +它的实现方式与 `Python` 的流程类似,并在 `C++` 中也需配置相关模型的参数。 + +#### 以 YOLOv2 为例 + +>YOLOv2 是一种基于深度学习的目标检测算法,它是 YOLO(you only look once)的改进版本。 + +**YOLOv2 相比于 YOLO 主要有以下几个不同的改进点:** + +1. 使用 `batch normalization` 来提高模型的稳定性和泛化能力,同时去掉了 `dropout` 层。 +2. 使用了高分辨率的图像(448×448)来微调分类网络,然后再用于检测网络并提高了检测的精度。 +3. 引入 `anchor boxes` 的概念,根据数据集的边界框分布进行 `k-means` 聚类,得到最合适的 `anchor boxes` 的尺寸和比例提高了召回率。 +4. 使用了直接位置预测的方法,将边界框的中心坐标相对于网格单元的偏移量用 `logistic` 回归函数进行归一化,使得模型更容易学习。 +5. 使用了多尺度训练的策略动态地改变输入图像的尺寸,使得模型能够适应不同大小的目标。 + +**YOLOv2 的优点主要有以下几个方面:** + +1. 速度快:YOLOv2 相比于基于区域提议的方法,只需对图像进行一次前向传播大大减少计算量和时间。 +2. 精度高:YOLOv2 相比于 YOLO 来说在 `mAP`(平均精度)上提升了约 10 个百分点,在小目标上也有明显的改善。 +3. 通用性强:YOLOv2 可以同时在 `COCO` 和 `ImageNet` 数据集上进行训练,得到一个能够检测 9000 多种物体的模型。 + +![YOLOv2](./assest/YOLOv2.jpg) + +**接下来我们来尝试采用人脸模型为测试:** + +在出厂镜像中我们已内置了相关的人脸检测的模型,无需用户再次下载。 + +```C++ +static struct _nn_yolo_face_ + { + //模型地址,需要提前通过adb传到板子里,下列模型出厂固件已经自带 + const char *model_path_param = "/home/model/face/yolo2_face_awnn.param"; + const char *model_path_bin = "/home/model/face/yolo2_face_awnn.bin"; + const char *inputs_names[1] = {"input0"}; + const char *outputs_names[1] = {"output0"}; + const float opt_param_mean = 127.5; + const float opt_param_norm = 0.0078125; + //网络的输入参数 + libmaix_nn_layer_t input = { + .w = 224, + .h = 224, + .c = 3, + .dtype = LIBMAIX_NN_DTYPE_UINT8, + }; + libmaix_nn_layer_t out_fmap = { + .w = 7, + .h = 7, + .c = 30,//根据上图的网络结构计算公式(sizeof(labels)+5) * 5 + .dtype = LIBMAIX_NN_DTYPE_FLOAT, + }; + libmaix_nn_t *nn; + libmaix_nn_model_path_t model_path; + libmaix_nn_opt_param_t opt_param; + // -------------- yolo2 decode ----------------------- + const char *labels[1] = {"face"};//你有多少个类别,就填多少 + const float anchors[10] = {1.19, 1.98, 2.79, 4.59, 4.53, 8.92, 8.06, 5.29, 10.32, 10.65}; + libmaix_nn_decoder_t *yolo2_decoder; + libmaix_nn_decoder_yolo2_result_t yolo2_result; + libmaix_nn_decoder_yolo2_config_t yolo2_config = { + .classes_num = sizeof(labels) / sizeof(anchors[0]), + .threshold = 0.5, + .nms_value = 0.3, + .anchors_num = (sizeof(anchors) / sizeof(anchors[0])) / 2, + .anchors = (float *)anchors, + .net_in_width = 224, + .net_in_height = 224, + .net_out_width = 7, + .net_out_height = 7, + .input_width = 224, + .input_height = 224 + }; + } nn_yolo_face_app; + +auto self = (_nn_yolo_face_ *)app->userdata; +self->opt_param.awnn.input_names = (char **)self->inputs_names; +self->opt_param.awnn.output_names = (char **)self->outputs_names; +self->opt_param.awnn.input_num = sizeof(self->inputs_names) / sizeof(self->inputs_names[0]); +self->opt_param.awnn.output_num = sizeof(self->outputs_names) / sizeof(self->outputs_names[0]); + +self->opt_param.awnn.mean[0] = self->opt_param_mean; +self->opt_param.awnn.mean[1] = self->opt_param.awnn.mean[0]; +self->opt_param.awnn.mean[2] = self->opt_param.awnn.mean[0]; + +self->opt_param.awnn.norm[0] = self->opt_param_norm; +self->opt_param.awnn.norm[1] = self->opt_param.awnn.norm[0]; +self->opt_param.awnn.norm[2] = self->opt_param.awnn.norm[0]; + +self->model_path.awnn.param_path = (char *)self->model_path_param; +self->model_path.awnn.bin_path = (char *)self->model_path_bin; + +self->input.need_quantization = true; + +self->out_fmap.data = (float *)malloc(self->out_fmap.w * self->out_fmap.h * self->out_fmap.c * sizeof(float)); +if (!self->out_fmap.data) +{ + LIBMAIX_INFO_PRINTF("no memory!!!\n"); + return -1; +} + +self->input.buff_quantization = (uint8_t *)malloc(self->input.w * self->input.h * self->input.c); +if (!self->input.buff_quantization) +{ + LIBMAIX_INFO_PRINTF("no memory!!!\n"); + return -1; +} + +LIBMAIX_INFO_PRINTF("-- nn create\n"); +self->nn = libmaix_nn_create(); +if (!self->nn) +{ + LIBMAIX_INFO_PRINTF("libmaix_nn object create fail\n"); + return -1; +} +LIBMAIX_INFO_PRINTF("-- nn object init\n"); +err = self->nn->init(self->nn); +if (err != LIBMAIX_ERR_NONE) +{ + LIBMAIX_INFO_PRINTF("libmaix_nn init fail: %s\n", libmaix_get_err_msg(err)); + return -1; +} +LIBMAIX_INFO_PRINTF("-- nn object load model\n"); +err = self->nn->load(self->nn, &self->model_path, &self->opt_param); +if (err != LIBMAIX_ERR_NONE) +{ + LIBMAIX_INFO_PRINTF("libmaix_nn load fail: %s\n", libmaix_get_err_msg(err)); + return -1; +} + +LIBMAIX_INFO_PRINTF("-- yolo2 decoder create\n"); +self->yolo2_decoder = libmaix_nn_decoder_yolo2_create(libmaix_nn_decoder_yolo2_init, + libmaix_nn_decoder_yolo2_deinit, + libmaix_nn_decoder_yolo2_decode); +if (!self->yolo2_decoder) +{ + LIBMAIX_INFO_PRINTF("no mem\n"); + return -1; +} +LIBMAIX_INFO_PRINTF("-- yolo2 decoder init\n"); +err = self->yolo2_decoder->init(self->yolo2_decoder, (void *)&self->yolo2_config); +if (err != LIBMAIX_ERR_NONE) +{ + LIBMAIX_INFO_PRINTF("decoder init error:%d\n", err); + return -1; +} + +err = self->nn->forward(self->nn, &self->input, &self->out_fmap); +if (err != LIBMAIX_ERR_NONE) +{ +printf("libmaix_nn forward fail: %s\n", libmaix_get_err_msg(err)); +} + +err = self->yolo2_decoder->decode(self->yolo2_decoder, &self->out_fmap, (void *)&self->yolo2_result); +if (err != LIBMAIX_ERR_NONE) +{ +printf("yolo2 decode fail: %s\n", libmaix_get_err_msg(err)); +} +if (self->yolo2_result.boxes_num > 0) +{ +LIBMAIX_INFO_PRINTF("yolo2_result.boxes_num %d", self->yolo2_result.boxes_num); +} +``` + +### OpenCV + +> 相关的函数可以去官方文档查看:[OpenCV Tutorials](https://docs.opencv.org/4.x/d9/df8/tutorial_root.html) + +为了照顾 C 程序在 `/libmaix/components/maix_cv_image/src/libmaix_cv_image.cpp` 下对 Opencv 的函数又做了一层封装,当时还没有全面用 C++ 但现在已经全部迁移到 C++ 了。 + +可以在提供的 `libmaix/components/maix_dls831/src/dls831_uvai.cpp` 的例子中,看到是如何采用了 Opencv 实现滤波和二值化等操作。 + +```C++ + void AdaptiveThreshold(cv::Mat &src, cv::Mat &dst, double Maxval, int Subsize, double c, adaptiveMethod method = meanFilter) + { + + if (src.channels() > 1) + cv::cvtColor(src, src, cv::COLOR_RGB2GRAY); + + cv::Mat smooth; + switch (method) + { + case meanFilter: + cv::blur(src, smooth, cv::Size(Subsize, Subsize)); // 均值滤波 + break; + case gaaussianFilter: + cv::GaussianBlur(src, smooth, cv::Size(Subsize, Subsize), 0, 0); // 高斯滤波 + break; + case medianFilter: + cv::medianBlur(src, smooth, Subsize); // 中值滤波 + break; + default: + break; + } + smooth = smooth - c; + src.copyTo(dst); + for (int r = 0; r < src.rows; ++r) + { + const uchar *srcptr = src.ptr(r); + const uchar *smoothptr = smooth.ptr(r); + uchar *dstptr = dst.ptr(r); + for (int c = 0; c < src.cols; ++c) + { + if (srcptr[c] > smoothptr[c]) + { + dstptr[c] = Maxval; + } + else + dstptr[c] = 0; + } + } + } +// 自适应阈值 提取边缘 +AdaptiveThreshold(gray, gray, 255, 21, 10, meanFilter);` +``` + +**前文的例子中不仅有 Opencv 的常用算法,也提供了 Opencv 的画图方法。** + +```C++ +// 绘制十字线 +for (size_t i = 0; i < reticle_results.size(); i++) +{ + reticle_result reticle_result = reticle_results[i]; + cv::line(bgra, Point(reticle_result.lines[0][0], reticle_result.lines[0][1]), Point(reticle_result.lines[0][2], reticle_result.lines[0][3]), Scalar(255, 0, 0, 255), 2, LINE_AA); + cv::line(bgra, Point(reticle_result.lines[1][0], reticle_result.lines[1][1]), Point(reticle_result.lines[1][2], reticle_result.lines[1][3]), Scalar(255, 0, 0, 255), 2, LINE_AA); + cv::circle(bgra, reticle_result.crossPoint, 5, Scalar(0, 0, 255, 255), -1, LINE_AA); +} +``` +### Openmv + +当然为了更好的兼容 Openmv 玩家,我们也提供了 Openmv 的函数在 `components/third_party/imlib` 下,函数全部增加了一个 `imlib` 的前缀,其他的功能与Openmv 是一致的。 + +举个栗子:比如 Openmv 的寻找色块函数 [寻找色块 · OpenMV中文入门教程](https://book.openmv.cc/image/blob.html) + +```C++ +image.find_blobs(thresholds, roi=Auto, x_stride=2, y_stride=1, invert=False, + area_threshold=10, pixels_threshold=10, merge=False, margin=0, + threshold_cb=None, merge_cb=None) +``` + +根据上面的链接描述,我们再看看 `imlib` 中的使用几乎一模一样。 + +```C++ +void imlib_find_blobs(list_t *out, image_t *ptr, rectangle_t *roi, unsigned int x_stride, unsigned int y_stride, + list_t *thresholds, bool invert, unsigned int area_threshold, unsigned int pixels_threshold, + bool merge, int margin, + bool (*threshold_cb)(void*,find_blobs_list_lnk_data_t*), void *threshold_cb_arg, + bool (*merge_cb)(void*,find_blobs_list_lnk_data_t*,find_blobs_list_lnk_data_t*), void *merge_cb_arg, + unsigned int x_hist_bins_max, unsigned int y_hist_bins_max) + +imlib_find_blobs(&out, img, &roi, x_stride, y_stride, &thresholds, invert, area_threshold, + pixels_threshold, merge, margin, NULL, NULL, NULL, NULL, x_hist_bins_max, y_hist_bins_max); +``` + +再看看最经典的巡线功能中最重要的识别直线函数:[find_lines 识别直线 · OpenMV中文入门教程](https://book.openmv.cc/example/09-Feature-Detection/find-lines.html) + +```C++ +imlib_find_lines(&out, img, &roi, x_stride, y_stride, threshold, theta_margin, rho_margin); +``` + +在上面提供的例子中就采用了寻找直线的功能。 + +### LVGL + +LVGL(**注意提供的版本为LVGL7**)的相关函数资料:[百问网 LVGL 中文教程手册文档](http://lvgl.100ask.net/7.11/) + +## 外设 + +接下来我们来认识一下外设以及相关的使用方法,例如串口、SPI、PWM、IIC 等。 + +### 串口 + +串口的使用非常的简单,我们先设置好波特率,数据位,停止位等参数,打开串口后挂载到 `select` 中,当串口收到数据后会打印一个字节出来。 + +```C++ +int ret = 0; +uart_t uart_dev_parm = { +.baud = 115200, +.data_bits = 8, +.stop_bits = 1, +.parity = 'n'}; +dls831->dev_ttyS1 = linux_uart_init((char *)"/dev/ttyS1", &uart_dev_parm); +if (dls831->dev_ttyS1 < 0) +{ + perror(" uart /dev/ttyS1 open err!"); + abort(); +} +FD_ZERO(&dls831->readfd); +write(dls831->dev_ttyS1, "dls831!\r\n", sizeof("dls831!\r\n"));I2C +``` + +```C++ +// serial +FD_SET(dls831->dev_ttyS1, &dls831->readfd); +ret = select(dls831->dev_ttyS1 + 1, &dls831->readfd, NULL, NULL, &dls831->timeout); +if (ret != -1 && FD_ISSET(dls831->dev_ttyS1, &dls831->readfd)) +{ + char tmp[2] = {0}; + int readByte = read(dls831->dev_ttyS1, &tmp, 1); + if (readByte != -1) + { + printf("readByte %d %X\n", readByte, tmp); + } +} +``` + +### SPI + +以 WS2812 为例,首先搞清楚设备的 SPI 的四种时序模式:[一文搞懂 spi 协议 4 种模式时序](https://zhuanlan.zhihu.com/p/386787861) + +```C++ +#define WS2812_SPI_DEVICE "/dev/spidev1.0" +unsigned char spi_init() +{ + //初始化 + int i; + _ws2812_fd = open (WS2812_SPI_DEVICE, O_RDWR); + printf("\r\n*****ws2812 open success******\r\n"); /* open spi device */ + if ((_ws2812_fd) < 0) /* check result */ + { + printf("spi: open failed.\n"); /* open failed */ + return 1; /* return error */ + } + else + { + i = SPI_MODE_3; /* set mode */ + if (ioctl(_ws2812_fd, SPI_IOC_WR_MODE, &i) < 0) /* config write mode */ + { + printf("spi: write mode set failed.\n"); /* write mode set failed */ + return 1; /* return error */ + } + if (ioctl(_ws2812_fd, SPI_IOC_RD_MODE, &i) < 0) /* config read mode */ + { + printf("spi: read mode set failed.\n"); /* read mode set failed */ + return 1; /* return error */ + } + i = 20000000; /* set spi frequence */ + if (ioctl(_ws2812_fd, SPI_IOC_WR_MAX_SPEED_HZ, &i) < 0) /* set write max frequence */ + { + printf("spi: set spi write speed failed.\n"); /* set spi write speed failed */ + return 1; /* return error */ + } + if (ioctl(_ws2812_fd, SPI_IOC_RD_MAX_SPEED_HZ, &i) < 0) /* set read max frequence */ + { + printf("spi: set spi read speed failed.\n"); /* set spi read speed failed */ + return 1; /* return error */ + } + i = 0; + if (ioctl(_ws2812_fd, SPI_IOC_WR_LSB_FIRST, &i) < 0) /* set write MSB first */ + { + printf("spi: set spi write msb first failed.\n"); /* set spi write msb first failed */ + return 1; /* return error */ + } + if (ioctl(_ws2812_fd, SPI_IOC_RD_LSB_FIRST, &i) < 0) /* set read MSB first */ + { + printf("spi: set spi read msb first failed.\n"); /* set spi read msb first failed */ + return 1; /* return error */ + } + i = 8; /* set 8 bits */ + if (ioctl(_ws2812_fd, SPI_IOC_WR_BITS_PER_WORD, &i) < 0) /* set write bits */ + { + printf("spi: set spi wirte 8 bit failed.\n"); /* set spi wirte 8 bit failed */ + return 1; /* return error */ + } + if (ioctl(_ws2812_fd, SPI_IOC_RD_BITS_PER_WORD, &i) < 0) /* set read bits */ + { + printf("spi: set spi read 8 bit failed.\n"); /* set spi read 8 bit failed */ + return 1; /* return error */ + } + return 0; /* success return 0 */ + } +} +``` + +```C++ +unsigned char spi_write_data(unsigned char *buf, uint16_t len) +{ + struct spi_ioc_transfer k; + int l; + // ws2812b_interface_debug_print("\r\n***const char *const fmt***\r\n"); + memset(&k, 0, sizeof(k)); /* clear ioc transfer */ + k.tx_buf = (unsigned long) buf; /* set tx buffer */ + k.len = len; /* set tx length */ + k.cs_change = 0; /* set cs change */ + l = ioctl(_ws2812_fd, SPI_IOC_MESSAGE(1), &k); /* send data */ + if (l != len) /* check length */ + { + printf("spi: length check error.\n"); /* length check error */ + + return 1; /* return error */ + } + + return 0; +} + +// spi.xfer(tx, int(4/1.05e-6)) +unsigned char spi_xfer_data(unsigned char *buf, uint16_t len, int clk) +{ + struct spi_ioc_transfer k; + int l; + // ws2812b_interface_debug_print("\r\n***const char *const fmt***\r\n"); + memset(&k, 0, sizeof(k)); /* clear ioc transfer */ + k.tx_buf = (unsigned long) buf; /* set tx buffer */ + k.len = len; /* set tx length */ + k.cs_change = 0; /* set cs change */ + k.speed_hz = clk; /* set spi clk */ + k.bits_per_word = 8; + l = ioctl(_ws2812_fd, SPI_IOC_MESSAGE(1), &k); /* send data */ + if (l != len) /* check length */ + { + printf("spi: length check error.\n"); /* length check error */ + + return 1; /* return error */ + } + + return 0; +} +``` + +读写数据的函数编写好后,就可以愉快的封装 WS2812 的亮灯程序啦! + +```C++ +uint8_t _set_rgbs_color(uint8_t *color_buf) +{ + uint8_t ret = 0; + uint8_t i = 0; + uint8_t j = 0; + uint8_t k = 0; + uint8_t tx[25] = {0}; + uint8_t rgb[3] = {0}; + uint8_t byte = 0; + uint8_t ibit = 0; + uint8_t *p = color_buf; + for (i = 0; i < 2; i++) + { + rgb[0] = p[0]; + rgb[1] = p[1]; + rgb[2] = p[2]; + for (j = 0; j < 3; j++) + { + + uint8_t colorBit[4] = {0}; + byte = rgb[j]; + for (ibit = 0; ibit < 4; ibit++) + { + colorBit[ibit] = (((byte >> (2 * ibit + 1)) & 1) * 0x60 + ((byte >> (2 * ibit + 0)) & 1) * 0x06 + 0x88); + } + + tx[k++] = colorBit[3]; + tx[k++] = colorBit[2]; + tx[k++] = colorBit[1]; + tx[k++] = colorBit[0]; + } + p += 3; + } + tx[0] &=0x0f; + // spi need clear head high level + tx[0] |=0x40; + tx[24] = 0b1000000; // spi need clear end high level + + ret = spi_xfer_data(tx, 25, (int)(4/1.05e-6)); + return ret; +} +``` + +### PWM + +通过写寄存器的方式直接对三个 PWM 进行初始化操作。 + +```C++ + /*pwm*/ + void pwm_init()//刚开始初始化50HZ 2.5%占空比 + { + + _pwm_init("PH6", 100, 2.5/100);//为了初始化引脚 + _pwm_init("PH7", 100, 2.5/100);//为了初始化引脚 + _pwm_init("PH8", 100, 2.5/100);//为了初始化引脚 + + usleep(100); + chdir("/sys/class/sunxi_dump"); + system("echo 0x0300A080 0x00 > write"); //PWM DISABLE + system("echo 0x0300A040 0x00 > write");//PWM CLOCK DISABLE + + system("echo 0x0300A028 0x00 > write");//PWM45 + system("echo 0x0300A02C 0x00 > write");//PWM67 CLOCK CONFIG PCCR67 24M 不分频 + system("echo 0x0300A030 0x00 > write");//PWM89 CLOCK CONFIG PCCR89 24M 不分频 + system("echo 0x0300A040 0x1D0 > write");//PWM Clock Gating 打开PWM4 6,7,8时钟 + system("echo 0x0300A180 0x107 > write");//PWM4 + system("echo 0x0300A184 0xea6005dc > write");//PWM + system("echo 0x0300A188 0x00 > write");//PWM + + system("echo 0x0300A1C0 0x107 > write");//PWM6配置8分频,循环模式 + system("echo 0x0300A1C4 0xea6005dc > write");//PWM6初始波形定义 + system("echo 0x0300A1C8 0x00 > write");//PWM6初始计数值为0 + + system("echo 0x0300A1E0 0x107 > write");//PWM7配置8分频,循环模式 + system("echo 0x0300A1E4 0xea6005dc > write");//PWM7初始波形定义 + system("echo 0x0300A1E8 0x00 > write");//PWM7初始计数值为0 + + system("echo 0x0300A200 0x107 > write");//PWM8配置8分频,循环模式 + system("echo 0x0300A204 0xea6005dc > write");//PWM8初始波形定义 + system("echo 0x0300A208 0x00 > write");//PWM8初始计数值为0 + + system("echo 0x0300A080 0x1D0 > write");//ENABLE PWM4 6,7,8 + } + + void pwm_deinit() + { + chdir("/sys/class/sunxi_dump"); + system("echo 0x0300A080 0x00 > write"); + system("echo 0x0300A040 0x00 > write"); + _pwm_deinit("PH6"); + _pwm_deinit("PH7"); + _pwm_deinit("PH8"); + } + + //设置PWM输出的频率,占空比,最大30KHZ + //0--pwm6 + //1--pwm7 + //2--pwm8 + void pwm_set(uint8_t pwm_id,uint32_t freq,float duty) + { + char data_buf[128]; + unsigned int reg_freq_count = 0; + unsigned int reg_duty_count = 0; + unsigned int reg_data = 0; + memset(data_buf,0,sizeof(data_buf)); + + reg_freq_count = (int)(3000000/freq); + reg_duty_count = (int)(reg_freq_count*duty/100); + reg_data = (reg_freq_count<<16)+reg_duty_count; + switch (pwm_id) + { + case 0: + sprintf(data_buf,"echo 0x0300A1C4 0x%x > write",reg_data); + break; + case 1: + sprintf(data_buf,"echo 0x0300A1E4 0x%x > write",reg_data); + break; + case 2: + sprintf(data_buf,"echo 0x0300A204 0x%x > write",reg_data); + break; + default: + break; + } +``` + +通过封装好的 `pwm_set` 就可以简单的控制三个 `pwm` 的输出频率了。 + +### IIC + +以 MPU6050 为例,首先查询你所用芯片的寄存器手册,配置相关寄存器设置。 + +```C++ +#define WHO_AM_I_REG 0x75 +#define POWR_MGNT_REG 0x6B +#define ACCL_CONF_REG 0x1C +#define GYRO_CONF_REG 0x1B +#define SMPL_DVDR_REG 0x19 +#define CONFIG_REG 0x1A +#define ACCL_XH_REG 0x3B +#define ACCL_XL_REG 0x3C +#define ACCL_YH_REG 0x3D +#define ACCL_YL_REG 0x3E +#define ACCL_ZH_REG 0x3F +#define ACCL_ZL_REG 0x40 +#define GYRO_XH_REG 0x43 +#define GYRO_XL_REG 0x44 +#define GYRO_YH_REG 0x45 +#define GYRO_YL_REG 0x46 +#define GYRO_ZH_REG 0x47 +#define GYRO_ZL_REG 0x48 +#define TEMP_HI_REG 0x41 +#define TEMP_LO_REG 0x42 +#define FIFO EN_REG 0x23 +#define FIFO_CH_REG 0x72 +#define FIFO_CL_REG 0x73 +#define FIFO_REG 0x74 + +char i2c_write(int file, uint8_t address, uint8_t command, uint8_t value) +{ + uint8_t buf[2] = {command, value}; + struct i2c_msg msgs[1] = { + { + .addr = address, + .flags = 0, + .len = 2, + .buf = buf, + }, + }; + struct i2c_rdwr_ioctl_data data = { + .msgs = msgs, + .nmsgs = 1, + }; + if (ioctl(file, I2C_RDWR, &data) < 0) { + return -1; + } + return 0; +} + +char i2c_read(int file, uint8_t address, uint8_t command) +{ + uint8_t buf[1]; + struct i2c_msg msgs[2] = { + { + .addr = address, + .flags = 0, + .len = 1, + .buf = &command, + }, + { + .addr = address, + .flags = I2C_M_RD, + .len = 1, + .buf = buf, + }, + }; + struct i2c_rdwr_ioctl_data data = { + .msgs = msgs, + .nmsgs = 2, + }; + if (ioctl(file, I2C_RDWR, &data) < 0) { + return -1; + } + return buf[0]; +} + +void mpu6050_init(uint8_t i2c_number, uint8_t slave_addr) +{ + file = open("/dev/i2c-2", O_RDWR); + if (file < 0) { + printf("I2C %hhu Not Found\n", i2c_number); + exit(1); + } + i2c_address = slave_addr; + if (ioctl(file, I2C_SLAVE, i2c_address) < 0) { + close(file); + printf("I2C Slave Not Found"); + exit(1); + } + i2c_write(file,slave_addr,POWR_MGNT_REG, 0x00); + i2c_write(file,slave_addr,ACCL_CONF_REG, 0x01); + i2c_write(file,slave_addr,GYRO_CONF_REG, 0x18); + i2c_write(file,slave_addr,SMPL_DVDR_REG, 0x07); + i2c_write(file,slave_addr,CONFIG_REG, 0x06); +} +``` + +在初始化 mpu6050 时需先查阅原理图,根据 `M2DCOK` 的原理图可得知 `IIC` 的 `PH11` 和 `PH12` 分别是 `SCL` 和 `SDA` 两根信号线,且对应的设备是 `/dev/i2c-2`,这样看来 `i2c_number` 就应该为 `/dev/i2c-2`。 + +**第一步**:我们可以通过命令去查找 `mpu6050` 在 `IIC` 上的地址 + +```shell +i2cdetetc -y 2 +``` + +![i2c](./assest/i2c.jpg) + +>我们可从上图得知有两个地址:62 以及 68,62 是板卡上自带的三轴传感器,而 68 则是我们的 mpu6050 挂载在 IIC 上的地址。 + +**第二步**:拿到地址后也可以通过命令直接去查看 mpu6050 中寄存器的值 + +```shell +i2cdump -y 2 0x68 +``` + +![6050](./assest/6050.jpg) + +**第三步**:分别取出高八位和低八位的值后,拼接起来后再通过一层换算才能得到真实的数据,以加速度、角速度和温度为例,打印出换算后的个值。 + +```C++ +int16_t twos_complement(uint8_t higher_byte, uint8_t lower_byte) +{ + uint16_t word = (uint16_t)((higher_byte << 8) | lower_byte); + int16_t signed_word; + if((word & 0x8000) != 0) + { + word = ~word + 0x0001; + signed_word = (int16_t)(word * -1); + return signed_word; + } + else + { + signed_word = (int16_t)word; + return signed_word; + } +} +mpu6050_init(2, 0x68); +axh = i2c_read(file,i2c_address,ACCL_XH_REG); +axl = i2c_read(file,i2c_address,ACCL_XL_REG); +ayh = i2c_read(file,i2c_address,ACCL_YH_REG); +ayl = i2c_read(file,i2c_address,ACCL_YL_REG); +azh = i2c_read(file,i2c_address,ACCL_ZH_REG); +azl = i2c_read(file,i2c_address,ACCL_ZL_REG); +gxh = i2c_read(file,i2c_address,GYRO_XH_REG); +gxl = i2c_read(file,i2c_address,GYRO_XL_REG); +gyh = i2c_read(file,i2c_address,GYRO_YH_REG); +gyl = i2c_read(file,i2c_address,GYRO_YL_REG); +gzh = i2c_read(file,i2c_address,GYRO_ZH_REG); +gzl = i2c_read(file,i2c_address,GYRO_ZL_REG); +th = i2c_read(file,i2c_address,TEMP_HI_REG); +tl = i2c_read(file,i2c_address,TEMP_LO_REG); + +axw = twos_complement(axh, axl); +ayw = twos_complement(ayh, ayl); +azw = twos_complement(azh, azl); +gxw = twos_complement(gxh, gxl); +gyw = twos_complement(gyh, gyl); +gzw = twos_complement(gzh, gzl); +tw = twos_complement(th, tl); +``` + +最后打印结果如下图: + +![print](./assest/print.jpg)