前言:本文为手把手教学 openmv 与 stm32 的数据通信项目教程,本教程使用 stm32f103c8t6 与 openmv 进行操作。 openmv 是非常强大的计算机视觉实现工具,自身提供了非常多的视觉项目案例,编程与使用门槛极低。为了进一步增强作品的功能与创意性,往往需要将 openmv 的视觉与 stm32 的控制融合,本篇博客将为读者朋友教学使用 uart 串口构建两者的快速数据通信。希望本篇博客能给读者朋友的工程项目或科研生活给予些许帮助,敬礼respect!!!(篇末代码开源!)
实验硬件:openmv;stm32f103c8t6;0.96寸oled;杜邦线若干
项目实物图:


项目效果图:


引脚连接:
一、openmv概述
1.1 openmv
openmv 是一个开源、功能强大的机器视觉模块。通常以 stm32f767/stm32h743/stm32f427 为 cpu 核心,集成 ov7725 等欧姆龙系列摄像头芯片,在小巧的硬件模块上,用c语言高效地实现了核心机器视觉算法,并提供 micropython 编程接口,拥有专属的编程平台 openmv ide 程序。创客或电赛选手往往忠爱使用 openmv 为自己的产品和发明增加有特色的竞争力。作者补充:实话实说目前 openmv 在计算机视觉上的帮助确实很大,可是考虑到如今的 openmv 的售价是非常不合适的!
openmv 的整体设计小巧,使用门槛低,使得它拥有很强的 "视觉diy” 属性。 openmv 还拥有丰富的外设资源,例如:uart、i2c、spi、pwm、adc、dac以及gpio等接口,方便扩展外围功能。usb接口用于连接电脑上的集成开发环境 openmv ide,协助完成编程、调试和更新固件等工作。tf卡槽支持大容量的tf卡,可以用于存放程序和保存照片等。

1.2 openmv项目
中国 openmv 官方代理是星瞳科技,星瞳科技在其官网提供了超多详细且丰富的 openmv 使用案例,例如:特征点检测、测距、扫描识别、寻找色块、模板匹配、颜色形状识别与人脸识别等。

上述图片中的案例都是可以借助 openmv 进行实现的,当然考虑到 stm32f7/stm32h7 等系列 cpu 算力的上限,可能输出图像像素以及 fps 并不是特别优秀的。有能力和专研精神的读者朋友可以尝试高级的计算机视觉开发工具,例如:jeston nano、k210、k510、rk3568、rk3588与树莓派4/5b系列等(部分产品的性能与算力非常有竞争力)!
二、博客项目概述
2.1 openmv的mnist数字识别
openmv 提供了超级多的计算机视觉的案例,作者选择常用的 mnist 数字识别项目作为 openmv 终端处理的事件(电赛送药小车题目与之类似),该案例可以直接通过星瞳科技官网进行获取(老旧版本的 openmv 可能需要升级固件才能使用该案例):
作者手上的 openmv 为 openmv3 r1,cpu 的处理性能非常一般。官方在 openmv4 h7 plus上面运行大概每秒 45 帧,在 openmv4 h7上面运行大概每秒 25 帧左右。mnist 数字识别案例使用了 cnn 卷积神经网络进行识别,例程利用 mnis t数字数据集,自行训练神经网络得到手写数字识别神经网络模型,性能和准确率很高(可以直接使用案例的权重文件即可)。
★运行目录前,将官网提供的 mnist 数字识别的 trained.tflite 文件下载到电脑,并复制到 openmv 的存储中。


mnist数字识别代码:
# this code run in openmv4 h7 or openmv4 h7 plus
import sensor, image, time, os, tf
sensor.reset() # reset and initialize the sensor.
sensor.set_pixformat(sensor.grayscale) # set pixel format to rgb565 (or grayscale)
sensor.set_framesize(sensor.qvga) # set frame size to qvga (320x240)
sensor.set_windowing((240, 240)) # set 240x240 window.
sensor.skip_frames(time=2000) # let the camera adjust.
clock = time.clock()
while(true):
clock.tick()
img = sensor.snapshot().binary([(0,64)])
for obj in tf.classify("trained.tflite", img, min_scale=1.0, scale_mul=0.5, x_overlap=0.0, y_overlap=0.0):
output = obj.output()
number = output.index(max(output))
print(number)
print(clock.fps(), "fps")
案例测试:

2.2 项目整体说明
本项目利用 openmv 的数字识别案例进行数字识别,将识别到的数字信息通过 openmv的 uart 串口发送至 stm32f103c8t6。而 stm32f103c8t6 通过 i2c 协议将 openmv 传输过来的数字信息显示在 0.96 寸的 oled 屏幕上。该项目的整体实现还是非常简单,特别是计算机视觉的数字识别部分,openmv 直接封装为案例,极大地方便研发人员的后续使用。当然,本篇博客最核心部分是 openmv 与 stm32 的通信部分的处理,包含数据包的处理编程!

三、传输数据包协议
3.1 数据包通信概述
传输完全体数据包可以包含:帧头、地址信息、数据类型、数据长度、数据块、校验码、帧尾。正常情况下,考虑到传输速率问题不会使用完全体数据包。大多数情况下,工程师仅使用简版数据包:帧头、数据字节长度与帧尾!

传输数据过程中的数据包解析通常有 2 种方式:(1)、中断内部解析;(2)、中断外部解析;
第一种方法:中断服务函数内部直接解析使用,该方法适用于数据帧简单,数据复杂程度低的情况。可以满足中断函数的快进快出,该方法可以使整个项目代码框架简洁,方便后期纠错改正!!!

第二种方法:中断服务函数外部解析使用,该方法适用于数据帧繁杂,数据复杂程度高的情况。该情况下,往往无法满足中断服务函数的快进快出,容易卡死在中断内部。这种情况下,工程师可以在中断中只接收数据,随后通过 extern 全局变量将数据在外部进行解析处理。实际工程中,该方法使用可能性高,希望读者朋友可以完全掌握该技能!!!

3.2 数据包传输(hex方式)
数据包传输方式是机器设备间通信最常见的方法,数据包传输方式一般分为 3 种:(1) 固定包长,含帧头帧尾;(2) 可变包长,含帧头帧尾;(3) 可变包长,含数据字节长度及帧头帧尾;详情如下图所示:

四、cubemx配置
1、rcc配置外部高速晶振(精度更高)——hse;

2、sys配置:debug设置成serial wire(否则可能导致芯片自锁);

3、usart1配置:设置uart1串口;波特率:115200;开启uart串口中断;

4、i2c配置:设置i2c1与 0.96 寸oled进行通信;

5、时钟树配置

6、工程配置

五、代码与解析
5.1 openmv数据发生端代码
5.1.1 openmv的串口数据传输
星瞳科技官网提供了 openmv 的串口 uart 的使用案例,升级到最新版固件就可以直接运行。作者使用 ch340 芯片将串口数据上传至电脑终端进行测试(读者朋友搞工程的时候,也建议按部就班的搭建和完善代码流程)。
openmv 串口通信代码:
# this code run in openmv4 h7 or openmv4 h7 plus
import sensor, image, time, os, tf
from pyb import uart
sensor.reset() # reset and initialize the sensor.
sensor.set_pixformat(sensor.grayscale) # set pixel format to rgb565 (or grayscale)
sensor.set_framesize(sensor.qvga) # set frame size to qvga (320x240)
sensor.set_windowing((240, 240)) # set 240x240 window.
sensor.skip_frames(time=2000) # let the camera adjust.
#openmv串口uart传输数据
uart = uart(3, 115200) # 实例化一个串口3,波特率为115200,必须与stm32接收端保持一致
clock = time.clock()
while(true):
clock.tick()
img = sensor.snapshot().binary([(0,64)])
for obj in tf.classify("trained.tflite", img, min_scale=1.0, scale_mul=0.5, x_overlap=0.0, y_overlap=0.0):
output = obj.output()
number = output.index(max(output))
print(number)
print(clock.fps(), "fps")
uart.write("hello world!\r")

5.1.2 openmv发送端完整代码
在上述官方提供的 openmv 的 2 个例程代码的基础上结合项目实际情况进行编写代码。openmv 只能传输十六进制的数据给 stm32,否则 stm32 将收不到数据,就是单片机和 openmv 都能正常和电脑通信,但是两者结合就不能正常通信。十六进制数据的实现主要通过 bytearray() 这个函数,代码格式如下:out_data =bytearray([0x2c,0x12,cx,cy,cw,ch,0x5b])
mnist.py代码:
# this code run in openmv4 h7 or openmv4 h7 plus
import sensor, image, time, os, tf
from pyb import uart
sensor.reset() # reset and initialize the sensor.
sensor.set_pixformat(sensor.grayscale) # set pixel format to rgb565 (or grayscale)
sensor.set_framesize(sensor.qvga) # set frame size to qvga (320x240)
sensor.set_windowing((240, 240)) # set 240x240 window.
sensor.skip_frames(time=2000) # let the camera adjust.
#openmv串口uart传输数据
uart = uart(3, 115200) # 实例化一个串口3,波特率为115200,必须与stm32接收端保持一致
#定义数据包发送函数
def sending_data(num):
global uart;
outdata = bytearray([0xfe,0xbc,num,0xef]) #构建发送数据的数据包
uart.write(outdata); #必须要传入一个字节数组
clock = time.clock()
while(true):
clock.tick()
img = sensor.snapshot().binary([(0,64)])
for obj in tf.classify("trained.tflite", img, min_scale=1.0, scale_mul=0.5, x_overlap=0.0, y_overlap=0.0):
output = obj.output()
number = output.index(max(output))
sending_data(number)
print(number)
print(clock.fps(), "fps")
mnist数字识别数据传输:

5.2 stm32数据接收端代码
5.2.1 0.96寸oled代码
本篇博客项目中使用 0.96 寸 oled 将 openmv 识别的 mnist 数字结果进行输出,0.96 寸的 oled 驱动代码可以参考作者的另一篇博客。考虑到博客篇幅有限,0.96 寸 oled 驱动就不详细赘述了,希望读者朋友可以自行掌握!
博客地址:http://t.csdnimg.cn/gdcev
5.2.2 stm32接收端完整代码
代码解析:本篇项目代码中 stm32 接收端关键操作都是依赖于 hal_uart_rxcpltcallback() 函数实现的。openmv 与 stm32 数据传输过程中的解码在中断回调函数中直接通过 openmv_data_receive() 函数实现。usart1_rxbuff 变量为 usart1 开启后持续传输的数据,将该变量放入 openmv_data_receive() 进行解码。
★核心函数 openmv_data_receive() 解析:
openmv 与 stm32 数据传输稍微复杂点的其实就是 stm32 接收端的解码过程,常规情况下 openmv 发送端的数据是一组数据包。这组数据包的组成是程序员自己定义的,比如作者 openmv 端的数据包格式为:0xfe,0xbc,num,0xef。其中,0xfe,0xbc 为帧头,num 为需要解码出的真正数据,0xef 为帧尾。

stm32 接收端需要根据 openmv 发送端的数据包格式进行解码,hal_uart_receive_it() 函数稳定将接收到的数据赋值 usart1_rxbuff ,通过 openmv_data_receive() 函数进行解码。根据上述 openmv 发送端的代码,可以得出需要首先解码帧头的 0xfe 与 0xbc,openmv_data_receive() 函数中定义 rxbuffer[4] 数组来接收每一帧的数据(作者每一帧数据有 4 个字节数据,读者朋友可以根据实际情况设置数组大小),设置 rxstate 状态位来递进判断是否正确接收到目标数据。在成功接收到 2 个帧头数据之后,通过 oled_shownum() 函数将 openmv 识别出的数字显示出来。

关键点:串口接收中断回调函数
/* user code begin ptd */
uint8_t usart1_rxbuff; //中断数据接收缓冲区
/* user code end ptd */
hal_uart_receive_it(&huart1,(void *)&usart1_rxbuff,1); /* 开启串口中断接收 */
/* user code begin 4 */
void hal_uart_rxcpltcallback(uart_handletypedef *huart)
{
uint16_t tempt;
if(huart->instance==usart1)
{
tempt=usart1_rxbuff;
openmv_data_receive(tempt);
}
hal_uart_receive_it(&huart1,(void *)&usart1_rxbuff,1); //再次开启中断接收
}
/* user code end 4 */
openmv.h:
#ifndef __openmv_h
#define __openmv_h
#include "stm32f1xx.h"
void openmv_data_receive(int16_t openmv_data); /* stm32接收端处理openmv传输的数据 */
#endif
openmv.c:
/********************************* (c) copyright **********************************
* file name : openmv.c
* author : 混分巨兽龙某某
* version : v1.0.0
* data : 2023/11/03
* contact : qq:1178305328
* description : openmv and stm32 communication files
***********************************************************************************/
#include "openmv.h"
#include "usart.h"
#include "stdio.h"
#include "oled.h"
static uint8_t number = 0;
/* stm32接收端处理openmv传输的数据 */
void openmv_data_receive(int16_t openmv_data)
{
/* 计数变量 */
static uint8_t rxcounter=0; //计数变量
/* 数据接收数组 */
static uint16_t rxbuffer[4]={0};
/* 数据传输状态位 */
static uint8_t rxstate = 0;
/* 判断数据是否为有效数据,解码 */
if(rxstate == 0 && openmv_data == 0xfe) //0xfe帧头
{
rxstate = 1; //状态位改变
rxbuffer[rxcounter++] = openmv_data; //将数据放入接收数组
}
else if(rxstate == 1 && openmv_data == 0xbc) //0xbc帧头
{
rxstate = 2; //状态位改变
rxbuffer[rxcounter++] = openmv_data; //将数据放入接收数组
}
else if(rxstate == 2) //读取目标数据(根据实际情况处理)
{
rxbuffer[rxcounter++] = openmv_data; //将数据放入接收数组
if(rxcounter>=3||openmv_data == 0xef)
{
rxstate = 3; //状态位改变
number = rxbuffer[rxcounter-1];
/* oled显示目标数字 */
oled_shownum(65,4,number,3,16);
}
}
else if(rxstate == 3) //检测是否接收到标志位
{
if(rxbuffer[rxcounter-1] == 0xef)
{
/* 计数和状态位归零 */
rxcounter = 0;
rxstate = 0;
}
else //接收错误
{
/* 计数和状态位归零 */
rxcounter = 0;
rxstate = 0;
/* 清空存放数据的数组 */
for(int i = 0;i < 4; i++)
{
rxbuffer[i] = 0x00;
}
}
}
else //整体的接收异常
{
/* 计数和状态位归零 */
rxcounter = 0;
rxstate = 0;
/* 清空存放数据的数组 */
for(int i = 0;i < 4; i++)
{
rxbuffer[i] = 0x00;
}
}
}
项目:


六、项目视频
openmv与stm32数据传输演示视频
七、代码开源
代码地址: 基于openmv与stm32的数据传输项目代码资源-csdn文库
如果积分不够的朋友,点波关注,评论区留下邮箱,作者无偿提供源码和后续问题解答。求求啦关注一波吧 !!!
发表评论