OpenMV + STM32 串口双向通信完整示例(基于HAL库)

单片机/PLC wes 17 hours ago (2026-07-30) 18 views
📑 本文导航

简介

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

说明
  1. OpenMV 使用 UART1:P4(TX)、P5(RX);STM32 USARTx 的 RX、TX 交叉对接
    • OpenMV‑P4(TX) → STM32‑RX
    • OpenMV‑P5(RX) → STM32‑TX
    • GND 和 STM32 GND 必须相连,共地!不共地一定乱码
  2. 电平都是 3.3V,不要接 5V 单片机 TX,会烧坏 OpenMV 引脚;5V 单片机需要电平转换
  3. 波特率:115200,8‑N‑1
  4. 通信协议自定义文本协议:
    • OpenMV → STM32:B,cx,cy\r\n 代表检测到色块;没有目标:B,-1,-1\r\n
    • STM32 → OpenMV:发送CMD1\r\n开启识别;CMD0\r\n暂停识别

一、OpenMV 完整代码(MicroPython)

python
import sensor, image, time, pyb
# 初始化串口 UART1 P4 TX P5 RX
uart = pyb.UART(1, 115200)
# 摄像头初始化
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time = 2000)
sensor.set_auto_gain(False)
sensor.set_auto_whitebal(False)
clock = time.clock()
# 颜色阈值,使用IDE阈值编辑器获取
red_threshold = (10, 80, 20, 127, 0, 127)
enable_detect = True # 识别开关,由STM32指令控制
while True:
 clock.tick()
 # 接收STM32命令
 if uart.any():
 cmd = uart.readline()
 if cmd:
 cmd = cmd.decode().strip()
 if cmd == "CMD1":
 enable_detect = True
 elif cmd == "CMD0":
 enable_detect = False
 if not enable_detect:
 time.sleep_ms(50)
 continue
 img = sensor.snapshot()
 blobs = img.find_blobs([red_threshold], pixels_threshold=20, area_threshold=20, merge=True)
 cx = -1
 cy = -1
 if blobs:
 max_blob = max(blobs, key=lambda b: b.pixels())
 cx = max_blob.cx()
 cy = max_blob.cy()
 img.draw_rectangle(max_blob.rect())
 img.draw_cross(max_blob.cx(), max_blob.cy())
 # 发送数据 B,cx,cy\r\n
 send_buf = "B,%d,%d\r\n" % (cx, cy)
 uart.write(send_buf)
 #print(send_buf)

二、STM32 端示例(HAL 库伪代码框架,可直接移植)

功能:串口接收 OpenMV 数据,解析B,cx,cy;可以发送 CMD0/CMD1 控制 OpenMV 启停
使用串口接收中断,开启 \n换行符接收完成判断
c
/*
USART初始化 115200 8N1
开启串口接收中断,开启接收换行检测
*/
typedef struct
{
 uint8_t flag_ok; //解析成功标志 1有效
 int16_t cx;
 int16_t cy;
}OpenMV_Data_t;
OpenMV_Data_t openmv_data;
// 接收缓冲区
uint8_t uart_rx_buf[64];
uint8_t rx_idx = 0;
//串口中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
 uint8_t ch;
 if(huart->Instance == USART1)
 {
 HAL_UART_Receive(huart, &ch, 1, 0);
 if(ch != '\n' && rx_idx < 62)
 {
 uart_rx_buf[rx_idx++] = ch;
 }
 else
 {
 uart_rx_buf[rx_idx] = '\0';
 //解析字符串 "B,150,110\r"
 if(uart_rx_buf[0] == 'B')
 {
 int a,b;
 if(sscanf((char*)uart_rx_buf,"B,%d,%d",&a,&b)==2)
 {
 openmv_data.flag_ok = 1;
 openmv_data.cx = a;
 openmv_data.cy = b;
 }
 }
 rx_idx = 0;
 memset(uart_rx_buf,0,sizeof(uart_rx_buf));
 }
 HAL_UART_Receive_IT(huart,&ch,1);
 }
}
//发送命令给OpenMV
void OpenMV_SendCmd(UART_HandleTypeDef *huart,uint8_t cmd)
{
 if(cmd == 1)
 {
 HAL_UART_Transmit(huart,(uint8_t*)"CMD1\r\n",5,100);
 }
 else
 {
 HAL_UART_Transmit(huart,(uint8_t*)"CMD0\r\n",5,100);
 }
}

在主循环中使用

c
while (1)
{
 if(openmv_data.flag_ok == 1)
 {
 openmv_data.flag_ok = 0;
 int16_t cx = openmv_data.cx;
 int16_t cy = openmv_data.cy;
 if(cx == -1 || cy == -1)
 {
 //没有识别到目标,小车执行预设动作
 }
 else
 {
 //cx范围0~319,中间160,根据偏差做PID控制
 int err = cx - 160;
 //使用err做电机PID,让目标维持画面中心
 }
 }
 HAL_Delay(10);
}

三、循迹小车完整工程框架

逻辑说明:OpenMV 识别赛道黑线,输出黑线中心 cx;STM32 接收 cx 做 PID,控制左右电机,使黑线保持画面 X=160 中心。

OpenMV 循迹代码(只处理画面底部 ROI,减少运算)

python
import sensor, image, time, pyb
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()
#黑线阈值,现场调试修改
black_line = (0,40, -20,20,-30,20)
# ROI只看画面底部:x=0 y=180 w=320 h=60
roi = (0,180,320,60)
while True:
 clock.tick()
 img = sensor.snapshot()
 blobs = img.find_blobs([black_line],roi=roi,pixels_threshold=15,area_threshold=15,merge=True)
 cx = -1
 if blobs:
 max_b = max(blobs,key=lambda x:x.pixels())
 cx = max_b.cx()
 img.draw_rectangle(max_b.rect())
 uart.write("T,%d\r\n"%cx)

STM32 端循迹 PID 框架

通信接收逻辑同上,收到T,cx
  • 目标中心点 target = 160
  • 误差 error = cx - target
cx=-1 代表丢失黑线,小车可以减速、微调寻找黑线。
c
//简单位置式PID示例
float Kp = 0.35f;
float Ki = 0;
float Kd = 0.1f;
int error = 0;
int last_error = 0;
float integral = 0;
void pid_calc(int cx)
{
 if(cx == -1)
 {
 //丢失赛道,执行寻线策略
 motor_set(0,0);
 return;
 }
 error = cx - 160;
 integral += error;
 float diff = error - last_error;
 float out = Kp*error + Ki*integral + Kd*diff;
 last_error = error;
 //基础速度
 int base_speed = 120;
 int left = base_speed + out;
 int right = base_speed - out;
 //限幅
 left = constrain(left,0,200);
 right = constrain(right,0,200);
 motor_set(left,right);
}

四、常见通信 bug 排查清单

  1. 完全收不到数据
    • TX/RX 是否交叉;有没有共地;波特率是否一致;串口引脚有没有选错
  2. 收到乱码
    • 没有共地!最常见;电平不匹配;两边波特率不一致
  3. 数据断断续续
    • OpenMV 代码不要使用高分辨率;不要加过多 print 打印;print 会占用串口
  4. 经常解析失败
    • 必须以\r\n作为帧结束标志,不要靠逗号分割判断一帧结束;不要使用不定长字符串裸解析
  5. 识别不到黑线
    • 在 IDE 阈值编辑器现场调 HSV;光照变化优先硬件补光,不要死调算法

五、学习拓展

  1. 先在 OpenMV‑IDE 看串口输出,确认数据正常,再接 STM32
  2. 先静态手拿目标,看 STM32 解析数值是否正确,再上电跑小车
  3. 竞赛项目建议加简单滤波:连续多帧有效 cx 取平均,抑制跳变