工巡赛实战:用OpenMV4 H7和STM32F103C8T6实现色块追踪与OLED显示(附完整代码)
2026/6/7 6:42:15 网站建设 项目流程

工巡赛实战:OpenMV4 H7与STM32F103C8T6的色块追踪系统开发全解析

在智能硬件竞赛中,视觉识别与嵌入式控制的结合一直是技术难点。本文将完整呈现一个基于OpenMV4 H7摄像头和STM32F103C8T6微控制器的色块追踪系统开发过程,从硬件选型到代码实现,再到系统联调,手把手教你打造一个稳定可靠的竞赛级解决方案。

1. 系统架构设计与硬件选型

1.1 核心硬件组件分析

OpenMV4 H7作为视觉处理核心,其优势在于:

  • 内置MicroPython开发环境,简化图像处理算法实现
  • 支持QVGA分辨率下30fps的图像采集
  • 集成硬件JPEG编码器和浮点运算单元
  • 提供丰富的机器视觉库,包括色块识别、人脸检测等

STM32F103C8T6(蓝桥杯/工巡赛常用型号)特点:

  • Cortex-M3内核,72MHz主频
  • 64KB Flash + 20KB SRAM
  • 丰富的外设接口(3个USART、2个SPI、2个I2C)
  • 成本低廉且生态完善

1.2 硬件连接方案

关键连接点配置表:

设备接口STM32对应引脚功能说明
OpenMV UART3_TXPB11 (USART3_RX)串口数据接收
OpenMV UART3_RXPB10 (USART3_TX)串口数据发送
OLED SCLPB6 (I2C1_SCL)显示时钟线(I2C模式)
OLED SDAPB7 (I2C1_SDA)显示数据线(I2C模式)

注意:实际连接时需确保共地,避免电平不匹配问题。建议使用逻辑电平转换器处理3.3V与5V系统的互联。

2. OpenMV端色块识别实现

2.1 视觉算法核心代码

import sensor, image, time, pyb from pyb import UART # 初始化摄像头 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) # 红色阈值配置(需根据实际环境调整) red_threshold = (10, 100, 127, 32, -43, 67) # 串口初始化 uart = UART(3, 115200) uart.init(115200, bits=8, parity=None, stop=1) def find_max_blob(blobs): max_size = 0 for blob in blobs: if blob.pixels() > max_size: max_blob = blob max_size = blob.pixels() return max_blob def pack_data(cx, cy, cw, ch): return bytearray([ 0x2C, # 帧头1 0x12, # 帧头2 cx & 0xFF, (cx >> 8) & 0xFF, # X坐标(16位) cy & 0xFF, (cy >> 8) & 0xFF, # Y坐标(16位) 0x5B # 帧尾 ]) while True: img = sensor.snapshot() blobs = img.find_blobs([red_threshold]) if blobs: max_blob = find_max_blob(blobs) img.draw_rectangle(max_blob.rect()) img.draw_cross(max_blob.cx(), max_blob.cy()) data = pack_data(max_blob.cx(), max_blob.cy(), max_blob.w(), max_blob.h()) uart.write(data)

2.2 关键参数调试技巧

  1. 颜色阈值调整

    • 使用OpenMV IDE中的阈值编辑器工具
    • 确保在不同光照条件下测试
    • LAB颜色空间中:
      • L:亮度分量(0-100)
      • A:红绿分量(-128到127)
      • B:黄蓝分量(-128到127)
  2. 性能优化

    • 降低分辨率可提高帧率(QQVGA比QVGA快约4倍)
    • 限制ROI(Region of Interest)减少处理区域
    • 关闭自动增益和白平衡以获得稳定结果

3. STM32端数据接收与处理

3.1 串口中断配置

// USART3初始化(PB10/PB11) void USART3_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; // 时钟使能 RCC_APB1PeriphClockCmd(RCC_APB1Periph_USART3, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // GPIO配置 GPIO_InitStruct.GPIO_Pin = GPIO_Pin_10; // TX GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; GPIO_Init(GPIOB, &GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin = GPIO_Pin_11; // RX GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, &GPIO_InitStruct); // USART参数配置 USART_InitStruct.USART_BaudRate = 115200; USART_InitStruct.USART_WordLength = USART_WordLength_8b; USART_InitStruct.USART_StopBits = USART_StopBits_1; USART_InitStruct.USART_Parity = USART_Parity_No; USART_InitStruct.USART_Mode = USART_Mode_Rx | USART_Mode_Tx; USART_InitStruct.USART_HardwareFlowControl = USART_HardwareFlowControl_None; USART_Init(USART3, &USART_InitStruct); // 中断配置 NVIC_InitStruct.NVIC_IRQChannel = USART3_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority = 1; NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStruct); USART_ITConfig(USART3, USART_IT_RXNE, ENABLE); USART_Cmd(USART3, ENABLE); }

3.2 数据解析状态机

// 全局变量 typedef struct { uint16_t x; uint16_t y; uint16_t width; uint16_t height; } BlobData; BlobData target_blob; uint8_t rx_buffer[10]; uint8_t rx_index = 0; uint8_t parse_state = 0; void USART3_IRQHandler(void) { static uint8_t data; if(USART_GetITStatus(USART3, USART_IT_RXNE) != RESET) { data = USART_ReceiveData(USART3); switch(parse_state) { case 0: // 等待帧头1 if(data == 0x2C) { rx_buffer[rx_index++] = data; parse_state = 1; } break; case 1: // 等待帧头2 if(data == 0x12) { rx_buffer[rx_index++] = data; parse_state = 2; } else { parse_state = 0; rx_index = 0; } break; case 2: // 接收数据 rx_buffer[rx_index++] = data; if(rx_index >= 8 || data == 0x5B) { parse_state = 3; } break; case 3: // 校验帧尾 if(data == 0x5B) { // 解析有效数据 target_blob.x = rx_buffer[2] | (rx_buffer[3] << 8); target_blob.y = rx_buffer[4] | (rx_buffer[5] << 8); update_display(); } // 重置状态机 parse_state = 0; rx_index = 0; break; } } }

4. OLED显示优化与系统集成

4.1 显示驱动优化技巧

针对0.96寸OLED(SSD1306驱动)的实用优化:

  1. 双缓冲技术

    uint8_t oled_buffer[128][8]; // 显存缓冲区 void OLED_Refresh() { for(uint8_t page=0; page<8; page++) { OLED_WriteCmd(0xB0 + page); // 设置页地址 OLED_WriteCmd(0x00); // 列地址低4位 OLED_WriteCmd(0x10); // 列地址高4位 for(uint8_t col=0; col<128; col++) { OLED_WriteData(oled_buffer[col][page]); } } }
  2. 局部刷新

    • 只更新变化区域对应的显存
    • 减少全屏刷新频率

4.2 坐标显示实现

void show_blob_info(BlobData blob) { char str[16]; // 清空显示区域 oled_fill_rect(0, 0, 128, 16, 0); // 显示坐标信息 sprintf(str, "X:%4d Y:%4d", blob.x, blob.y); oled_show_string(0, 0, str); // 显示尺寸信息 sprintf(str, "W:%4d H:%4d", blob.width, blob.height); oled_show_string(0, 2, str); // 绘制简易位置指示器 uint8_t pos_x = blob.x * 128 / 320; // QVGA宽度归一化 oled_draw_line(pos_x-3, 30, pos_x+3, 30, 1); oled_draw_line(pos_x, 27, pos_x, 33, 1); }

5. 系统调试与性能优化

5.1 常见问题排查指南

现象可能原因解决方案
无数据接收波特率不匹配检查双方波特率设置是否一致
数据错乱帧同步丢失增加帧头帧尾校验,添加CRC校验
显示闪烁刷新频率过高限制刷新率至30Hz以下
色块识别不稳定环境光干扰增加补光灯或调整阈值
系统死机堆栈溢出检查中断嵌套,优化内存使用

5.2 实时性优化策略

  1. OpenMV端

    • 使用image.find_blobs()roi参数限定检测区域
    • 开启sensor.set_auto_exposure()避免曝光波动
    • 采用pyb.delay()替代time.sleep()获得更精确时序
  2. STM32端

    • 使用DMA传输减少CPU开销
    • 优化中断服务程序,避免长时间关中断
    • 对显示数据进行低通滤波处理
// 简易的一阶低通滤波实现 void filter_blob_data(BlobData *new_data, BlobData *filtered, float alpha) { filtered->x = alpha * new_data->x + (1-alpha) * filtered->x; filtered->y = alpha * new_data->y + (1-alpha) * filtered->y; filtered->width = alpha * new_data->width + (1-alpha) * filtered->width; filtered->height = alpha * new_data->height + (1-alpha) * filtered->height; }

6. 扩展应用与进阶开发

6.1 多目标追踪实现

通过修改数据协议支持多个色块识别:

# OpenMV端多色块处理 max_blobs = 3 # 最大追踪目标数 while True: img = sensor.snapshot() blobs = img.find_blobs([red_threshold], merge=True) if blobs: blobs.sort(key=lambda b: b.pixels(), reverse=True) for i in range(min(max_blobs, len(blobs))): blob = blobs[i] data = pack_data(i, blob.cx(), blob.cy(), blob.w(), blob.h()) uart.write(data)

6.2 无线传输方案

替换有线串口为无线模块(如HC-05蓝牙):

  1. 硬件修改

    • OpenMV UART3连接蓝牙模块TXD/RXD
    • STM32端使用另一个USART连接蓝牙模块
  2. 协议增强

    • 增加数据包序号防止丢包
    • 添加简单的应答机制
// 带序号的数据包结构 #pragma pack(push, 1) typedef struct { uint8_t header[2]; uint16_t packet_id; uint16_t x; uint16_t y; uint16_t width; uint16_t height; uint8_t checksum; uint8_t footer; } WirelessPacket; #pragma pack(pop)

在实际项目部署中,我们发现硬件连接器的可靠性至关重要。使用高质量的排针和杜邦线,或者直接焊接关键数据线路,可以显著降低接触不良导致的问题。对于需要频繁移动的场景,建议采用带锁紧装置的连接器。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询