尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

2023电赛E题实战:STM32与Jetson Nano协同运动目标追踪系统

2023电赛E题实战:STM32与Jetson Nano协同运动目标追踪系统 简介2023年电赛E题“运动目标控制与自动追踪系统”的赛题实现资源面向电子设计竞赛备赛学生与嵌入式视觉开发者整合STM32控制源码、Jetson Nano上的OpenCV识别代码及电路板PCB原理图设计是覆盖软硬件全链路的完整方案。STM32代码按模块拆分含摇杆控制、二维云台舵机控制、蓝牙通信、蜂鸣器、LED灯、STM32与Jetson Nano通信的UART、定时器、PWM等OpenCV部分实现铅笔、A4纸、红绿激光等目标识别。代码注释详细标注管脚连接与资源使用情况函数风格接近HAL库便于理解与复现。资源共五百五十个文件压缩包约二十一MB主要包括C/C和H源码、Keil工程文件、Python脚本、编译中间文件及PDF说明文档目录结构清晰。已有1671人学习下载配套作者文章可辅助理解环境配置与整体逻辑能显著节省备赛时间适合需要快速掌握控制与视觉协同方案的学习者。 2023年电赛E题——运动目标控制与自动追踪系统可能是那年题里最有工程味的一道。前几年偏向电源和信号处理方向这道题一出来就把视觉、控制、结构三大块全部拉满。我们队的最终方案是STM32F103C8T6做底层控制Jetson Nano跑OpenCV视觉识别中间用串口对接整套电路板从原理图到打样全是自己画的。这篇文章会把STM32端源码的框架、Jetson Nano上的OpenCV识别流程、PCB原理图的设计细节一次说透篇幅不长但全是实际跑过的代码和踩过的坑适合准备电赛的同学和想入门嵌入式视觉开发的工程师参考。1. 电赛E题的整体设计复盘为什么是STM32Jetson Nano而不是单芯片方案1.1 题目到底在考什么2023年E题要求系统能够识别并自动追踪运动目标通常是一个光源或特定颜色的小车。核心闭环听起来简单摄像头采集图像、识别目标坐标、换算云台角度、驱动电机转动、让目标保持在画面中央。但真正做完发现整个系统的瓶颈根本不在某个单点而在于识别速度和控制响应之间的配合。如果只追求识别精度算力拉满就行如果只追求控制响应裸机PWM直接转就行。难的是两者同时兼顾。视觉端算完一帧要100毫秒这100毫秒里云台如果完全不动目标早就跑出画面了。所以整个系统的实时性取决于每个环节延迟的叠加而不是某一个模块的速度。这是我们队最开始没有意识到的问题也是后期调试花时间最多的地方。1.2 方案选型对比市面上常见的方案有三类直接用OpenMV或K210跑神经网络。优点是上手快缺点是帧率受限K210在跑轻量级目标检测模型时单帧推理大约200-300毫秒跟踪速度快一点的目标根本来不及。树莓派加USB摄像头。树莓派算力比K210强但实时性也不够稳定而且整机体积偏大装在小车或云台上都不友好。STM32加Jetson Nano。Jetson Nano算力足够跑更复杂的视觉模型STM32负责实时电机控制分工明确出问题时也容易定位。我最终选第三套方案。还有一个重要原因STM32F103C8T6作为底层控制芯片参考资料极多遇到问题搜索解决的效率高。电赛总共四天三夜选型时能不能快速调通比性能绝对上限更重要。Jetson Nano这边虽然Linux环境对刚接触的同学不太友好但只要系统刷好、OpenCV环境没问题Python开发效率反而很高。1.3 双核分工的具体边界Jetson Nano跑JetPack系统用OpenCV处理摄像头画面识别目标并计算偏移坐标然后通过串口把坐标发给STM32。STM32收到坐标后通过PID算法计算云台应该转动的角度输出两路PWM控制舵机。很多队伍想用STM32直接读摄像头做图像处理但STM32F103C8T6的算力摆在那里哪怕只是找色块中心帧率也就5-8帧根本追不上快速移动的目标。反过来如果用Jetson Nano直接控制舵机Linux不是实时系统线程调度抖动会让PWM输出不稳定云台会一卡一卡。把实时要求高的PWM控制放回STM32把算力要求高的图像处理放到Jetson是这个方案最核心的设计逻辑。2. STM32端源码设计电机驱动、云台控制与串口通信2.1 代码架构总览STM32端的源码我按模块拆了几部分system文件夹时钟、GPIO、定时器等底层初始化motor文件夹舵机云台的控制两路PWM输出usart文件夹串口接收与解析control文件夹坐标误差的PID计算这个分层是我做项目时比较习惯的做法好处是比赛期间换控制逻辑时不需要翻动底层代码。比如后期发现PID参数需要改只动control文件夹里的内容就行不会影响已经调通的串口和PWM部分。2.2 云台驱动的关键代码云台用的是两个舵机一个控制俯仰、一个控制水平。STM32通过定时器产生PWM控制舵机角度PWM周期选20毫秒也就是50Hz对应脉宽0.5毫秒到2.5毫秒映射0到180度。核心初始化代码部分简化void TIM_PWM_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_TIM1, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_8 | GPIO_Pin_9; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period 1999; TIM_TimeBaseStructure.TIM_Prescaler 719; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM1, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 75; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM1, TIM_OCInitStructure); TIM_OC2Init(TIM1, TIM_OCInitStructure); TIM_CtrlPWMOutputs(TIM1, ENABLE); TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_OC2PreloadConfig(TIM1, TIM_OCPreload_Enable); TIM_ARRPreloadConfig(TIM1, ENABLE); TIM_Cmd(TIM1, ENABLE); }这里有个换算系统时钟72MHz预分频719后变成100kHz计数器周期2000PWM频率就是100kHz除以2000等于50Hz。定时器计数一次对应0.01毫秒0.5毫秒脉宽对应计数值502.5毫秒对应250。代码里TIM_Pulse初始值设75对应的是1毫秒左右的脉宽舵机大约在0度附近。实际使用中我做了一个小改动有些舵机中位不在1.5毫秒死区还特别大脉宽映射范围我最终收窄到0.6毫秒到2.4毫秒留一点余量避免舵机堵转发热。如果直接用满0.5到2.5毫秒的极限值舵机在行程末端容易发烫比赛现场出现这种问题很难排查。2.3 串口协议设计STM32和Jetson Nano之间用串口通信波特率115200。协议设计要解决两个基本问题一是数据帧怎么分割二是多字节数据怎么保证不粘包。我设计的协议帧格式字段长度说明帧头2字节0xA5 0x5A数据长度1字节后续数据字节数X坐标2字节目标中心X高字节在前Y坐标2字节目标中心Y高字节在前标志位1字节1表示检测到目标0表示丢失校验和1字节数据区累加和低8位帧尾2字节0x0D 0x0A为什么加标志位Jetson端不仅要告诉STM32目标在哪还要告诉STM32我到底有没有看到目标。如果当前帧没有检测到目标标志位置0STM32就保持上一次角度不变而不是乱转。这个细节很关键因为实际场地里目标短暂离开画面是常有的事如果不加这个标志位视觉端发送一个无效坐标过来云台就会抽风一样乱动。2.4 接收解析与PID控制STM32串口接收用了空闲中断方式一帧数据接收完毕后触发中断解析。这样做的好处是CPU不需要频繁进串口中断只在整帧到达后处理一次。具体做法是让串口工作在空闲中断模式配合DMA接收代码简单而且稳定。PID控制部分云台的水平和垂直两个自由度分别独立控制核心函数int Position_PID(float setpoint, float feedback, PID_t *pid) { pid-error setpoint - feedback; pid-integral pid-error; if (pid-integral pid-integral_max) pid-integral pid-integral_max; if (pid-integral -pid-integral_max) pid-integral -pid-integral_max; pid-derivative pid-error - pid-last_error; pid-last_error pid-error; return pid-kp * pid-error pid-ki * pid-integral pid-kd * pid-derivative; }调PID的经验先把KI设为0单独调KP和KD等稳态效果好了再加KI消除静差。KP过大会导致云台在目标位置附近震荡KD过小会让人觉得云台眼神飘忽。我们最终用的KP大约0.8、KD约0.15KI只给了很小的0.05因为舵机本身有机械阻尼积分项太强反而容易超调。3. Jetson Nano上的OpenCV源码目标检测与坐标换算3.1 环境配置的注意点Jetson Nano刷JetPack系统后自带OpenCV很多刚接触的同学会遇到import cv2就报错的问题。这里有个常见坑JetPack预装的是Python 3.6但环境里可能有多个Python版本pip install opencv-python可能装错位置或者装上的是不带GPU加速的版本跑起来很慢。我建议直接用系统预装的OpenCV不要额外pip安装。验证方式python3 -c import cv2; print(cv2.__version__)能看到版本号就够了。如果确实需要自己编译OpenCV注意编译时间Jetson Nano编译一次全量OpenCV大概3到4小时四天三夜的比赛里这个时间耗不起。能用预装库就绝不自己编译这是我这次项目最实在的一条经验。3.2 目标检测的实现赛题通常用光源做目标我用HSV颜色空间过滤配合轮廓检测的方式。为什么不用YOLO这类深度学习模型因为比赛现场算力虽然够但模型初始化时间和每一帧的推理延迟都偏高如果没有提前准备好训练数据现场标注再训练根本不现实。传统图像处理方法对单一颜色的目标识别效果已经很稳定关键是实时性好每帧处理时间可以控制在20毫秒以内。核心流程通过GStreamer管道读取摄像头帧转换到HSV颜色空间用inRange函数过滤出目标颜色区域用GaussianBlur降噪用findContours提取轮廓取最大轮廓的矩计算目标中心坐标import cv2 import numpy as np import serial import time cap cv2.VideoCapture( nvarguscamerasrc ! video/x-raw(memory:NVMM), width640, height480, framerate30/1 ! nvvidconv ! video/x-raw, formatBGRx ! videoconvert ! video/x-raw, formatBGR ! appsink, cv2.CAP_GSTREAMER ) ser serial.Serial(/dev/ttyTHS1, 115200, timeout0.05) lower_red np.array([0, 100, 100]) upper_red np.array([10, 255, 255]) while True: ret, frame cap.read() if not ret: continue hsv cv2.cvtColor(frame, cv2.COLOR_BGR2HSV) mask cv2.inRange(hsv, lower_red, upper_red) mask cv2.GaussianBlur(mask, (5, 5), 0) contours, _ cv2.findContours(mask, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) if len(contours) 0: c max(contours, keycv2.contourArea) area cv2.contourArea(c) if area 30: continue M cv2.moments(c) if M[m00] ! 0: cX int(M[m10] / M[m00]) cY int(M[m01] / M[m00]) data bytearray([0xA5, 0x5A, 0x05]) data (cX 8 0xFF, cX 0xFF) data (cY 8 0xFF, cY 0xFF) data (1,) checksum sum(data[2:]) 0xFF data.append(checksum) data (0x0D, 0x0A) ser.write(data)这里有两个容易踩的细节。第一个是摄像头管道配置Jetson Nano默认的CSI摄像头输出格式是NVMM必须通过nvvidconv转到BGR格式才能给OpenCV用直接传普通V4L2格式读帧延迟会大很多。第二个是轮廓面积阈值场地里难免会有零星噪点设置一个最小面积阈值可以过滤掉小光斑干扰阈值太小误判太多太大又可能漏掉远距离的目标我最后设在30到50像素之间。3.3 像素坐标到云台角度的换算这是整个视觉部分最核心的换算。摄像头画面中心点和目标像素点之间的偏差除以焦距对应的像素数得到角度偏差。但不用去查摄像头内参那么麻烦实际调试时直接标定像素差对应角度的比例系数就够了。具体做法先记录目标在画面中心时云台的角度值然后把目标放到画面边缘记下像素偏差和云台需要转的角度算出一个比例系数。比如画面水平方向640个像素云台水平行程约120度粗略来看每个像素对应约0.19度。实际并不是完全线性的尤其广角镜头边缘畸变明显。我用分段系数来处理画面中央区域用较大的像素到角度系数靠近边缘区域用较小的系数。实测下来比单一系数更准不过需要花一点时间测量多组数据。比赛现场没有那么多标定时间可以在赛前把这项工作做完现场只做微调。4. PCB与原理图设计从模块验证到打板4.1 电路框架与模块选型系统供电是12V电池经过DC-DC降压到5V和3.3V两路。5V给舵机和Jetson Nano供电3.3V给STM32最小系统供电。Jetson Nano开发板DC口支持5V输入需要用降压模块先把12V转成5V。模块选型上5V降压我用的是MP1584纹波小、效率高电流输出能力也够。3.3V用AMS1117-3.3舵机不从这个LDO取电只给MCU和传感器供电负载不大所以发热可以接受。舵机单独从5V电源轨供电这是很重要的设计决策后面会讲原因。4.2 原理图关键节点原理图里我重点做了几个部分STM32最小系统F103C8T68MHz晶振复位电路BOOT0下拉到GND。这个下拉电阻很关键很多人画板子时省了导致芯片偶尔进不了程序。电源树12V输入经过MP1584转5V5V经过AMS1117转3.3V。输入输出都要加滤波电容大容值的电解电容加小容值的陶瓷电容并联使用。舵机接口三针排针信号脚串联一个100欧电阻。这个电阻可以抑制舵机信号线上的反射和振铃让PWM波形更干净。串口接口Jetson的UART引脚和STM32的USART1之间直接TTL对接注意Jetson的UART是3.3V电平STM32的USART1配置成复用推挽输出后也是3.3V电平不需要额外电平转换。如果用了5V供电的USB转串口模块就必须检查电平是否匹配。4.3 PCB布局布线经验四天三夜的时间不可能画太复杂的板但有几个细节直接影响稳定性和调试速度晶振尽量靠近MCU引脚走线短直两边加地过孔包围避免信号干扰。电源部分和数字部分的地单点接地。当时我画板为了赶时间没做地分割导致舵机转动瞬间STM32偶尔复位。后来把电机电源和逻辑电源分离用磁珠连接模拟地和数字地才解决。舵机PWM信号线远离电源线。舵机启动瞬间电流变化率很大会在电源线上感应出噪声如果信号线跟电源线平行走线这个噪声会耦合到PWM信号里造成舵机抖动。PCB打样建议选2层板嘉立创打样便宜速度快比赛前提前下单是特别重要的经验。别等代码调完再画板子代码和硬件同步进行不然画板的时间会挤占调代码的时间。4.4 焊接与上电顺序上电前先用万用表量关键节点是否有短路3.3V对地、5V对地这两项必须量。再上电测电压是否正常。我第一次做板子时犯过一个经典错误AMS1117引脚焊反导致输出电压变成2.2V单片机不启动排查了很久才发现是封装方向看错了。上电顺序也有讲究先给STM32上电确认程序跑起来、串口正常输出日志再给舵机供电。如果所有电源一起上舵机上电瞬间的大电流可能会导致MCU电压跌落重启。5. 联调阶段踩过的坑从串口丢包到云台抖动5.1 ST-Link连接失败的排查链路很多同学在比赛现场遇到报错no stm32 target found这里我捋一下完整的排查思路。第一步用万用表量STM32的3.3V是否正常。芯片供电不足或者VDD引脚虚焊是最常见的原因比赛现场环境复杂各种电磁干扰容易让芯片进入异常状态。第二步检查BOOT0是不是低电平。如果BOOT0被拉高芯片上电后会进入系统存储器引导模式不运行用户程序SWD接口也不响应。这个检查只需要万用表量一下BOOT0引脚电压。第三步看SWDIO和SWCLK引脚连接是否可靠。排针接触不良、杜邦线松动都有可能导致连接失败。用镊子轻轻碰一下排针如果报错信息时有时无基本就是接触问题。第四步如果以上都正常把ST-Link的复位线也接上尝试连接时按住芯片复位键有时候能解决芯片进入低功耗模式后SWD不响应的问题。实测下来这个排查顺序能覆盖九成以上的连接失败场景。我自己遇到最多的是第三步电赛现场环境嘈杂搬动设备的时候杜邦线很容易松动后来我干脆把SWD接口画到了PCB上直接用排针焊接连接彻底解决了。5.2 串口通信丢包和粘包Jetson的UART在Linux下默认可能被系统控制台占用需要修改设备树或者使用配置命令禁用控制台否则数据会混入系统日志。这一步很关键很多同学串口数据乱码就是这个问题。具体判断方法在Jetson终端里输入ls /dev/ttyTHS1能看到设备节点存在但测试发送数据时如果混有系统日志内容说明控制台占用没有解除。我是在系统启动参数里把console指定到了别的接口让ttyTHS1完全空闲出来。另外一个必须注意的点Jetson和STM32之间必须共地。串口通信的电平参考是双方的地如果不共地电平参考不同乱码是必然的严重时还可能损坏串口引脚。我用的是同一个电源系统的地不存在这个问题但如果Jetson用独立电源供电就一定要把地线连过来。5.3 云台抖动与响应过慢云台抖动的原因通常有两个PID参数没调好或者舵机供电不足。PID参数的问题在前面已经说过了这里重点说一下供电。舵机瞬间启动电流可以达到1A以上如果电源线太细或者稳压芯片输出电流不够舵机一转动电压就掉单片机和视觉模块一起受影响。我最后给舵机单独一路供电电源线加粗到24AWG电压跌落从0.4V降到了0.1V以内云台运转明显平稳了。这里有一个判断供电是否充足的简单方法在舵机电源引脚旁边并联一个470uF电解电容如果抖动明显减轻说明是电源动态响应不足如果没变化问题大概率在PID参数。5.4 误识别处理视觉方面最容易被骗的是场地里的其他光斑和反光。我的方案是加面积阈值和颜色范围细分并且只在目标进入画面中央区域时才发送坐标数据。具体做法是先在画面中心画一个ROI区域比如宽度400、高度300的一个矩形。只有目标中心落在这个ROI内部才认为检测有效发送坐标数据。如果目标在ROI外标志位置0STM32保持当前角度不变。这样就不会被画面边缘的杂光和反光干扰云台也不会因为场地里的其他光斑而乱转。最后再分享一个我认为对整体进度最有帮助的小技巧调试时把视觉端的实时画面通过VNC或者HDMI输出到一个显示屏上叠加显示检测框、目标中心坐标和当前云台角度。这个调试界面能让你一眼看出是整个链路的哪个环节出了问题——是视觉识别框偏了还是串口数据没发出去还是云台角度没跟上。电赛时间是倒着数的能把问题定位时间从半小时缩到五分钟就是最大的优势。本文还有配套的精品资源点击获取
返回列表