手把手教你用HAL库实现OpenMV与STM32通信:从零开始的实战指南
你有没有遇到过这样的场景?
机器人要抓取一个红色小球,摄像头识别到了位置,可主控却“收不到消息”;或者图像处理飞快,但控制端数据乱码、丢包不断……问题不在算法,而在于——视觉和控制之间的桥梁没搭好。
今天我们就来解决这个关键环节:如何让OpenMV把看到的世界,准确无误地告诉STM32?
这不是一篇堆砌术语的手册复制文,而是一份真正带你“跑通第一行通信代码”的实战笔记。我们将使用STM32 HAL库 + OpenMV MicroPython,通过UART串口完成稳定可靠的数据交互,适合刚入门嵌入式视觉系统的同学快速上手。
为什么是OpenMV + STM32?
先说清楚:我们不是在比谁更强,而是看谁更配。
- OpenMV,像是系统的“眼睛”。它集成了摄像头和Cortex-M4/M7处理器,运行MicroPython,能轻松完成颜色识别、二维码读取、AprilTag定位等任务。
- STM32,则是“大脑”或“手脚”。它负责运动控制、逻辑决策、外设驱动(比如电机、显示屏、Wi-Fi模块)。
两者分工明确:一个专注感知,一个专注执行。只要它们之间有一条低延迟、高可靠性、易调试的通信链路,整个系统就能活起来。
而最简单、最稳定的连接方式之一,就是——UART串口通信。
选UART的理由:简单才是硬道理
虽然I2C、SPI也能通信,但在OpenMV与STM32这类异构系统中,UART有不可替代的优势:
| 对比项 | UART | I2C | SPI |
|---|---|---|---|
| 引脚数 | 2(TX/RX) | 2(SDA/SCL) | 3~4(SCK/MOSI/MISO/CS) |
| 协议复杂度 | 极简 | 中等(地址+ACK) | 高(时钟极性/相位) |
| 跨平台兼容性 | 极强 | 需匹配电平和速率 | 主从固定,配置繁琐 |
| 远距离传输能力 | 较好(TTL可达1m以上) | 差(受总线负载影响大) | 差 |
更重要的是,OpenMV默认开放了多个UART接口,STM32几乎每个型号都带至少两个USART,双方都不需要额外硬件转换(都是3.3V TTL电平),接上线就能聊。
✅ 推荐波特率:115200 bps—— 快到足以传坐标、够稳不会轻易出错。
硬件怎么连?别搞反了!
这是新手最容易翻车的地方:TX接TX?还是RX接RX?
记住一句话:发送接接收,接收接发送。
具体连线如下:
| OpenMV 引脚 | 连接到 | STM32 引脚 |
|---|---|---|
P4 (UART3_TX)→ | → | PA10 (USART1_RX) |
P5 (UART3_RX)← | ← | PA9 (USART1_TX) |
| GND | —— | GND |
⚠️ 注意:
- OpenMV的UART3对应的是P4(TX)和P5(RX)
- STM32引脚需根据CubeMX配置的实际映射确定
- 务必共地!否则信号基准不同,通信必失败
如果你不确定引脚功能,可以用万用表测 continuity 或查阅官方引脚图。
STM32端:用HAL库搭建“数据守门员”
我们现在要做的,是在STM32这边建立一个永不中断的数据监听机制。不能轮询浪费CPU,也不能丢包。
第一步:用STM32CubeMX生成基础工程
打开STM32CubeMX,新建项目,选择你的芯片(如STM32F407VG),然后做以下设置:
- 启用USART1(或其他可用串口)
- 设置模式为Asynchronous Mode
- 波特率设为115200
- 数据位8,停止位1,无校验
- 在NVIC选项卡中勾选Global interrupt
- 生成代码(Toolchain: MDK-ARM / SW4STM32 均可)
CubeMX会自动生成huart1句柄,并初始化串口时钟和GPIO。
第二步:开启中断接收,避免阻塞
很多人一开始写法是这样的:
while (1) { HAL_UART_Receive(&huart1, buf, 4, 100); // 阻塞等待 }这不行!一旦等待超时或数据未满,就会卡住,错过下一帧。
正确做法是:启动一次中断接收,收到数据后自动回调,再重新开启下一次接收。
1. 定义接收缓冲区
#define RX_BUFFER_SIZE 64 uint8_t rx_buffer[RX_BUFFER_SIZE]; uint8_t temp_byte; // 单字节暂存(用于DMA或中断触发)2. 启动中断接收(放在main函数开头)
// 开启单字节中断接收 HAL_UART_Receive_IT(&huart1, &temp_byte, 1);注意:这里只接收1个字节!因为我们要逐字节判断帧头帧尾。
第三步:编写中断回调函数
当收到一个字节时,会进入中断服务程序,最终调用HAL_UART_RxCpltCallback()。
我们在main.c中添加这个回调函数:
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart->Instance == USART1) { static uint8_t rx_index = 0; static uint8_t packet[RX_BUFFER_SIZE]; if (temp_byte != '
') { // 不是结束符,存入缓冲区 if (rx_index < RX_BUFFER_SIZE - 1) { packet[rx_index++] = temp_byte; } } else { // 收到换行符,表示一帧结束 packet[rx_index] = '