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

说明
- OpenMV 使用 UART1:P4(TX)、P5(RX);STM32 USARTx 的 RX、TX 交叉对接
- OpenMV‑P4(TX) → STM32‑RX
- OpenMV‑P5(RX) → STM32‑TX
- GND 和 STM32 GND 必须相连,共地!不共地一定乱码
- 电平都是 3.3V,不要接 5V 单片机 TX,会烧坏 OpenMV 引脚;5V 单片机需要电平转换
- 波特率:115200,8‑N‑1
- 通信协议自定义文本协议:
- OpenMV → STM32:
B,cx,cy\r\n代表检测到色块;没有目标:B,-1,-1\r\n - STM32 → OpenMV:发送
CMD1\r\n开启识别;CMD0\r\n暂停识别
- OpenMV → STM32:
一、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 排查清单
- 完全收不到数据
- TX/RX 是否交叉;有没有共地;波特率是否一致;串口引脚有没有选错
- 收到乱码
- 没有共地!最常见;电平不匹配;两边波特率不一致
- 数据断断续续
- OpenMV 代码不要使用高分辨率;不要加过多 print 打印;print 会占用串口
- 经常解析失败
- 必须以
\r\n作为帧结束标志,不要靠逗号分割判断一帧结束;不要使用不定长字符串裸解析
- 必须以
- 识别不到黑线
- 在 IDE 阈值编辑器现场调 HSV;光照变化优先硬件补光,不要死调算法
五、学习拓展
- 先在 OpenMV‑IDE 看串口输出,确认数据正常,再接 STM32
- 先静态手拿目标,看 STM32 解析数值是否正确,再上电跑小车
- 竞赛项目建议加简单滤波:连续多帧有效 cx 取平均,抑制跳变