openmv和stm32串口通信实现小车循迹
时间: 2025-07-20 12:19:40 浏览: 0
<think>我们正在处理用户关于OpenMV与STM32串口通信实现小车循迹的问题。根据引用内容,我们知道OpenMV负责图像处理并通过串口将数据发送给STM32,STM32根据接收到的数据控制小车运动。
步骤:
1. 硬件连接:确保OpenMV的串口引脚(如UART3的TX和RX)与STM32的串口引脚正确连接,注意交叉连接(TX接RX,RX接TX)。
2. OpenMV端的串口配置:在OpenMV上配置串口,设置波特率(通常为115200)并初始化。
3. OpenMV图像处理与数据发送:OpenMV对图像进行处理(例如循迹线检测),然后将处理结果(如小车的偏移量、状态等)通过串口发送给STM32。
4. STM32端串口配置:在STM32上配置对应的串口,设置相同的波特率,并启用串口中断以接收数据。
5. STM32数据处理与电机控制:STM32根据接收到的数据计算PID控制量,调整电机速度,实现循迹。
具体实现:
一、OpenMV端(以划分检测区域方案为例):
1. 初始化摄像头和串口。
2. 在循环中捕获图像,进行灰度化、二值化等处理。
3. 将图像划分为几个区域(如左、中、右),检测每个区域内是否有黑线(轨迹)。
4. 根据检测结果生成一个状态值(例如:0b000表示无线,0b001表示右边有线,0b010表示中间有线,0b100表示左边有线等)或者计算偏移量。
5. 将状态值或偏移量通过串口发送给STM32。
示例代码(OpenMV):
```python
import sensor, image, time, pyb
# 初始化摄像头
sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE)
sensor.set_framesize(sensor.QQVGA)
sensor.skip_frames(time=2000)
# 初始化串口(UART3)
uart = pyb.UART(3, 115200) # 根据实际连接的串口调整
while(True):
img = sensor.snapshot()
# 二值化处理,根据实际场景调整阈值
img.binary([(0, 64)]) # 假设黑线在0-64灰度值
# 划分区域:将图像分成左、中、右三个区域
width = img.width()
height = img.height()
region_width = width // 3
# 分别检测三个区域
left_region = img.get_region(0, height//2, region_width, height//2)
mid_region = img.get_region(region_width, height//2, region_width, height//2)
right_region = img.get_region(2*region_width, height//2, region_width, height//2)
# 统计每个区域的黑色像素数量
left_count = left_region.get_statistics().l_mean() < 50 # 假设二值化后黑色为0,白色为255,这里用平均灰度判断
mid_count = mid_region.get_statistics().l_mean() < 50
right_count = right_region.get_statistics().l_mean() < 50
# 生成状态值:用3位二进制数表示,从左到右分别代表左中右区域
state = 0
if left_count:
state |= 0b100
if mid_count:
state |= 0b010
if right_count:
state |= 0b001
# 发送状态值(一个字节)
uart.write(bytearray([state]))
time.sleep_ms(100) # 控制发送频率
```
二、STM32端(以状态控制为例):
1. 配置串口接收中断,当接收到一个字节时,更新状态值。
2. 在主循环或中断中根据状态值计算PID输出(或直接使用状态值控制电机)。
3. 根据PID输出调整左右电机速度。
示例代码(STM32 HAL库):
在串口中断回调函数中:
```c
uint8_t rx_data;
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance == USART3) // 假设使用USART3
{
// 将接收到的数据赋值给状态变量
g_cThisState = rx_data;
// 重新启动接收
HAL_UART_Receive_IT(&huart3, &rx_data, 1);
}
}
```
在控制任务中(如定时器中断或主循环):
```c
// 定义全局变量
uint8_t g_cThisState = 0;
uint8_t g_cLastState = 0;
float g_fHW_PID_Out = 0.0;
float g_fHW_PID_Out1 = 0.0;
float g_fHW_PID_Out2 = 0.0;
// PID结构体(假设已经初始化)
PID pidOpenmv_Tracking;
// 控制逻辑
if (g_cThisState != g_cLastState)
{
// 根据状态计算PID输出,这里的状态可以映射为期望的偏差
// 例如:状态0b100(只有左边有线)表示小车需要向右转,即左轮加速,右轮减速
// 我们可以将状态转换为一个偏差值,比如:0b100 -> -0.5, 0b010 -> 0, 0b001 -> 0.5
float set_value = 0.0;
switch (g_cThisState)
{
case 0b100: set_value = -0.5; break;
case 0b010: set_value = 0.0; break;
case 0b001: set_value = 0.5; break;
case 0b110: set_value = -0.3; break; // 左边和中间有线,稍微向左偏
case 0b011: set_value = 0.3; break; // 中间和右边有线,稍微向右偏
default: set_value = 0.0; // 其他情况保持直行
}
// 使用PID计算输出(这里假设PID_realize函数已经实现,使用位置式PID)
g_fHW_PID_Out = PID_realize(&pidOpenmv_Tracking, set_value);
// 计算两个电机的速度(基础速度0.5,然后根据PID输出调整)
g_fHW_PID_Out1 = 0.5 + g_fHW_PID_Out; // 左电机
g_fHW_PID_Out2 = 0.5 - g_fHW_PID_Out; // 右电机
// 限幅
if (g_fHW_PID_Out1 > 1.2) g_fHW_PID_Out1 = 1.2;
if (g_fHW_PID_Out1 < 0) g_fHW_PID_Out1 = 0;
if (g_fHW_PID_Out2 > 1.2) g_fHW_PID_Out2 = 1.2;
if (g_fHW_PID_Out2 < 0) g_fHW_PID_Out2 = 0;
// 控制电机
motorPidSetSpeed(g_fHW_PID_Out1, g_fHW_PID_Out2);
g_cLastState = g_cThisState;
}
```
另一种方案是线性回归:OpenMV通过线性回归计算出轨迹线的角度和偏移量,然后将这些浮点数发送给STM32。这种方式需要发送多个字节(例如角度和偏移量各4字节),可以使用结构体打包发送,在STM32端解析。
注意:串口通信协议可以自定义,但要保证双方一致。例如,可以定义数据帧格式:起始字节(如0xAA)+ 数据(1字节状态或多字节浮点数)+ 结束字节(如0x55)。或者使用字符串发送,但解析稍复杂。
线性回归方案OpenMV端示例:
```python
# 在OpenMV循环中
# ... 图像捕获和二值化后
line = img.get_regression([(0,64)], robust=True)
if line:
# 计算偏移量(以图像中心为基准)
offset = line.x1() - img.width()//2 # 或者使用更复杂的计算
# 发送偏移量(假设用4字节浮点数)
uart.write(bytearray(struct.pack('f', offset)))
else:
# 没有检测到线,发送一个特殊值(如0)
uart.write(bytearray(struct.pack('f', 0.0)))
```
在STM32端,需要接收4字节并组合成浮点数,然后根据偏移量进行PID控制。
总结:
1. 划分区域方案简单,但精度较低,适合简单路径。
2. 线性回归方案精度高,适合复杂路径,但计算量大。
根据实际需求选择方案,并注意通信协议的设计。
相关问题:
1. OpenMV与STM32串口通信中如何处理数据丢失或错误?
2. 除了串口通信,OpenMV和STM32之间还可以通过哪些方式通信?
3. 如何优化OpenMV的图像处理算法以提高循迹的实时性?</think>### OpenMV与STM32串口通信实现小车循迹功能
#### 一、系统架构概述
1. **硬件结构**
- OpenMV:负责图像采集与处理(摄像头模块)
- STM32:负责电机控制和运动决策(主控制器)
- 通信接口:通过UART串口(TX/RX)连接,波特率建议115200
2. **工作流程**
```mermaid
graph LR
A[OpenMV图像采集] --> B[轨迹识别处理]
B --> C[串口发送状态数据]
C --> D[STM32接收数据]
D --> E[PID计算控制量]
E --> F[电机调速]
```
#### 二、OpenMV端配置(Python)
```python
# OpenMV 串口通信配置
import pyb, sensor, image, time
# 初始化摄像头
sensor.reset()
sensor.set_pixformat(sensor.GRAYSCALE)
sensor.set_framesize(sensor.QQVGA) # 160x120分辨率
sensor.skip_frames(time=2000)
# 初始化UART3 (P4/P5引脚)
uart = pyb.UART(3, 115200) # 波特率需与STM32一致
while True:
img = sensor.snapshot()
# 轨迹识别算法(示例:阈值二值化)
img.binary([(0, 64)]) # 根据实际场景调整阈值
# 方案1:区域划分检测
regions = [
img.find_blobs([(100,255)], roi=(0, 80, 40, 40)), # 左区域
img.find_blobs([(100,255)], roi=(60, 80, 40, 40)), # 中区域
img.find_blobs([(100,255)], roi=(120,80,40,40)) # 右区域
]
# 生成状态字节 (bit0:左, bit1:中, bit2:右)
state = 0
if regions[0]: state |= 0b001
if regions[1]: state |= 0b010
if regions[2]: state |= 0b100
# 方案2:线性回归(精度更高)
# line = img.get_regression([(100,255)])
# if line: uart.write("%d,%d" % (line.theta(), line.rho()))
# 发送状态数据 (1字节)
uart.write(bytearray([state]))
time.sleep_ms(20) # 控制发送频率
```
#### 三、STM32端配置(C语言)
```c
// STM32串口接收配置 (以HAL库为例)
uint8_t rx_data;
HAL_UART_Receive_IT(&huart3, &rx_data, 1); // 启用串口中断
// 中断回调函数
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) {
if(huart->Instance == USART3) {
g_cThisState = rx_data; // 更新视觉识别状态
HAL_UART_Receive_IT(&huart3, &rx_data, 1); // 重新启用接收
}
}
// PID控制电机
void Motor_Control() {
// 状态映射为偏差值 (示例)
float deviation = 0;
switch(g_cThisState) {
case 0b001: deviation = -0.5; break; // 仅左侧有线
case 0b100: deviation = 0.5; break; // 仅右侧有线
case 0b010: deviation = 0; break; // 中间有线
}
// PID计算
g_fHW_PID_Out = PID_Calculate(&pid, deviation);
// 电机速度分配
float left_speed = BASE_SPEED + g_fHW_PID_Out;
float right_speed = BASE_SPEED - g_fHW_PID_Out;
// 限幅保护 (0.0~1.0)
left_speed = fmaxf(0, fminf(1.0, left_speed));
right_speed = fmaxf(0, fminf(1.0, right_speed));
// 设置电机速度
MOTOR_SetSpeed(MOTOR_LEFT, left_speed);
MOTOR_SetSpeed(MOTOR_RIGHT, right_speed);
}
```
#### 四、关键实现细节
1. **通信协议优化**
- 数据帧结构:`0xAA + [数据] + 0x55`(添加帧头帧尾)
- 校验机制:使用累加和校验或CRC校验
- 示例帧:`AA 03 55` 表示检测到右侧轨迹
2. **PID参数整定**
- 比例系数P:决定响应速度
- 积分系数I:消除静态误差
- 微分系数D:抑制振荡
- 典型值范围:P=0.3~0.8, I=0.001~0.01, D=0.05~0.2
3. **抗干扰设计**
- 图像处理:中值滤波 + 形态学操作
- 数据滤波:接收端滑动平均滤波
```c
#define FILTER_SIZE 5
uint8_t filter_buf[FILTER_SIZE];
uint8_t filter_index = 0;
// 滑动平均滤波函数
uint8_t Moving_Average(uint8_t new_val) {
filter_buf[filter_index++] = new_val;
if(filter_index >= FILTER_SIZE) filter_index = 0;
uint16_t sum = 0;
for(int i=0; i<FILTER_SIZE; i++)
sum += filter_buf[i];
return sum / FILTER_SIZE;
}
```
#### 五、调试技巧
1. **OpenMV调试工具**
- 使用IDE内置的帧缓冲区查看实时图像处理效果
- 通过串口终端打印调试信息:`print("State: %d" % state)`
2. **STM32状态监控**
```c
// OLED显示当前状态
sprintf(oled_buf, "State: %d", g_cThisState);
OLED_ShowString(0, 2, oled_buf); // [^2]
```
3. **波形分析**
- 使用STM32的DAC输出PID波形
- 通过示波器观察电机响应曲线
> **注意**:实际应用中需根据赛道特征调整视觉算法参数,复杂赛道建议采用线性回归方案提高控制精度[^1]。
阅读全文
相关推荐
















