📑 本文导航
简介
本文通过一些简单的示例来介绍OpenMV与STM32控制器的串口双向通信,基于Keil5 + STM32 标准库 3.5,是入门开发的基础。

说明:
- STM32 使用 标准库 V3.5,USART1,115200‑8‑N‑1;中断接收,逐字节接收,
'\n'作为一帧结束标记,缓冲区 128 字节。- OpenMV 使用 UART1 (P4‑TX、P5‑RX);波特率 115200。
- 双向通信:
- OpenMV → STM32:视觉报文,例如
B,cx,cy\r\n- STM32 → OpenMV:下发简单控制指令,例如
START、STOP,OpenMV 接收指令做简单逻辑。- 硬件接线
plaintextOpenMV P4(TX) ——> STM32 USART1‑RX(PA10) OpenMV P5(RX) ——> STM32 USART1‑TX(PA9) OpenMV GND ——> STM32 GND ✅必须共地!
第一部分:OpenMV 完整 Python 代码(双向串口)
python
import sensor, image, time, pyb
# UART1 P4(TX) P5(RX) 115200
uart = pyb.UART(1, 115200)
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(2000)
sensor.set_auto_gain(False)
sensor.set_auto_whitebal(False)
clock = time.clock()
#颜色阈值,现场修改
threshold = (10, 80, 20, 127, 0, 127)
roi = (0, 160, 320, 80)
enable_run = False #主控下发START才开始输出数据
while True:
clock.tick()
#接收STM32下发指令
if uart.any():
cmd = uart.readline()
if cmd:
cmd_str = cmd.decode("utf-8").strip()
if cmd_str == "START":
enable_run = True
elif cmd_str == "STOP":
enable_run = False
img = sensor.snapshot()
cx = -1
cy = -1
blobs = img.find_blobs([threshold], roi=roi, pixels_threshold=20, area_threshold=20, merge=True)
if blobs:
max_b = max(blobs, key=lambda x:x.pixels())
cx = max_b.cx()
cy = max_b.cy()
img.draw_rectangle(max_b.rect())
if enable_run:
#向STM32发送视觉报文 B,cx,cy\r\n
uart.write("B,%d,%d\r\n" % (cx, cy))
time.sleep_ms(20)
通信说明
- STM32 发送
START,OpenMV 才开始上报视觉坐标;发送STOP停止上报。- OpenMV 报文固定结尾
\r\n,单片机依靠\n判断一帧接收完毕。
第二部分:STM32 标准库 3.5 代码 Keil5
工程基于标准库 STM32F10x_StdPeriph_Lib_V3.5.0;USART1 (PA9‑TX PA10‑RX);中断接收。 全局缓冲区:收到完整一行后置标志位,主循环解析报文;支持向 OpenMV 发送字符串指令。
请 登录 后查看
第三部分:AprilTag 报文解析函数(标准库)
报文:
TAG,id,tx,ty,tz\r\nc
typedef struct
{
uint8_t valid;
int16_t id;
float tx;
float ty;
float tz;
}AprilTag_Data_t;
AprilTag_Data_t apriltag;
void parse_apriltag(uint8_t *buf)
{
apriltag.valid = 0;
if(strstr((char*)buf,"TAG,") == NULL) return;
int id_tmp;
float tx_tmp,ty_tmp,tz_tmp;
int ret = sscanf((char*)buf,"TAG,%d,%f,%f,%f",&id_tmp,&tx_tmp,&ty_tmp,&tz_tmp);
if(ret == 4)
{
apriltag.id = id_tmp;
apriltag.tx = tx_tmp;
apriltag.ty = ty_tmp;
apriltag.tz = tz_tmp;
if(id_tmp != -1)
{
apriltag.valid = 1;
}
}
}
FOMO 报文解析函数
报文:
F,class_id,cx,cy,score\r\nc
typedef struct
{
uint8_t valid;
int16_t cls_id;
int16_t cx;
int16_t cy;
float score;
}FOMO_Data_t;
FOMO_Data_t fomo_data;
void parse_fomo(uint8_t *buf)
{
fomo_data.valid = 0;
if(buf[0]!='F') return;
int id,x,y;
float sc;
int ret = sscanf((char*)buf,"F,%d,%d,%d,%f",&id,&x,&y,&sc);
if(ret == 4)
{
fomo_data.cls_id = id;
fomo_data.cx = x;
fomo_data.cy = y;
fomo_data.score = sc;
if(sc>0.6f)
{
fomo_data.valid = 1;
}
}
}
⚠️使用标准库的重要注意事项
- 必须共地! 没有共地必乱码、丢帧。
- 中断优先级:USART1 中断优先级不要设置为 0 最高,避免抢占其他关键中断。示例代码预抢占优先级 1。
- 缓冲区溢出保护:收到
\n才认为一帧,其他情况不断填充缓冲区,满了直接丢弃。 sscanf使用 float,标准库编译 Keil5 需要勾选 Use MicroLIB(Target 页面),否则浮点解析异常。- OpenMV 代码不要写多余
print(),print 会占用串口干扰双向通信。 - 双向通信指令结尾建议加上
\r\n,保持协议统一。 - 不要在中断服务函数做复杂运算,中断只做接收,解析全部放在 main 主循环。