Jetson Nano与STM32协同:深度学习视觉目标检测与舵机控制实战
简介这是一套面向嵌入式AI初学者与课程设计者的端侧智能控制实战项目资源聚焦Jetson Nano与STM32协同完成图像识别→指令下发→舵机精准响应的完整闭环。资源覆盖从数据集采集标注、YOLOv5s模型训练、TensorRT加速优化到STM32通过UART/I2C接收Jetson指令并驱动舵机转动的全链路实现适用于毕业设计、大创竞赛、工程实训等实践场景。压缩包共111个文件142.82MB含38个C源码与40个头文件构成的STM32底层驱动工程涵盖TIM、USART、ADC、I2C等关键外设3个Paddle Lite模型文件model、params、pdiparams及配套YAML配置、Python部署脚本、Keil工程uvprojx/uvoptx和实操演示MP4视频。已有1339人学习下载所有代码经真机测试可直接烧录运行附带keilkill.bat一键清理脚本与详细README说明大幅降低复现门槛。1. 项目整体思路与系统架构设计做这个项目的起因其实很直接我一直想搞一个能“看懂”物体并做出响应的桌面级机械臂但市面上的成品方案要么是纯视觉但控制封闭要么是可编程但完全没有AI能力两者能打通并且留有足够二次开发空间的方案几乎没有。所以我决定自己搭一套用Jetson Nano当“大脑”负责跑深度学习模型做目标检测再用STM32当“小脑”负责实时控制舵机。这套组合做完之后功能上完全可以扩展成视觉抓取、人脸追踪云台、巡检小车等而且每一个环节都是可以独立复用的。1.1 核心需求解析先说这个项目到底解决了什么问题。单独用Jetson Nano去控制舵机不是不行但实时性很难保证——Jetson Nano跑的是Linux系统进程调度、Python解释器开销、USB转舵机控制板驱动延迟这些加在一起会让舵机响应出现几十到几百毫秒的抖动。而单独用STM32去接摄像头做目标检测算力又完全不够跑个轻量级模型都勉强。所以架构上最合理的分工是Jetson Nano只负责“看见”和“思考”也就是摄像头采集、模型推理、目标坐标解算STM32只负责“行动”也就是解析指令、生成PWM波形、驱动舵机转到位。两者之间用串口UART通信数据量小、协议简单、实时性有保证。这个思路在很多实际产品里都是这么做的比如扫地机器人的视觉导航模块和底盘驱动模块就是分离的。1.2 方案选型与硬件搭配选Jetson Nano而不是树莓派核心原因是CUDA生态。Jetson Nano带128个Maxwell架构的CUDA核心可以跑TensorRT加速的深度学习模型推理速度比树莓派的CPU硬扛快一个数量级。2024年之后官方主推的是Jetson Orin Nano系列性能更强但如果你手头是老的Jetson Nano 4GB版本做这个项目也完全够用。STM32我选的是STM32F103C8T6也就是大家常说的“蓝丸”核心板。选它不是因为性能强而是因为资料多、便宜、舵机控制这种任务它对时序的要求完全不虚。F103的主频72MHz定时器分辨率足够输出平滑的PWM波形而且HAL库和标准库的代码示例网上应有尽有。如果你对实时性有更高要求换STM32F405或者G431也行但项目逻辑不用改。舵机这块普通的SG90舵机9g舵机扭矩小适合做云台这种轻负载如果计划后面挂机械臂建议上MG996R或者MG995扭矩大不少但要注意电流需求更大必须独立供电。我后来在项目中就是先用SG90做验证稳定后才换成MG996R这个顺序能省掉很多排查故障的时间。2. 数据集准备与深度学习模型训练这个项目的数据集准备是很多人容易忽略的环节。很多新手上来就直接下载一个公开数据集然后训个模型就开始部署结果一到自己的实际场景里准确率暴跌。核心原因很简单你的摄像头角度、光照、目标物体外观和训练集差异太大。所以我采用的是“自采数据公开数据混合”的策略目标放在识别几个固定颜色的物体上这样数据集既好做模型也好收敛。2.1 数据采集与标注实操数据采集我建议直接用Jetson Nano的CSI摄像头接口配合OpenCV来完成不要用USB摄像头因为CSI接口的延迟和CPU占用都更低。采集时不要只正对着拍要模拟实际部署时可能出现的角度变化俯视、侧视、远近、顺光、逆光各种组合都要覆盖。我当时采集了大约800张图片涵盖了红、绿、蓝三种颜色的方块在不同位置、不同光照下的情况这些图片分到训练集、验证集、测试集的比例大约是7:2:1。标注工具我推荐用LabelImg它虽然界面老一点但是生成的是Pascal VOC格式的XML文件后续转成YOLO格式非常方便。如果标注的目标数量多可以试试LabelStudio或者X-AnyLabeling这类半自动辅助工具。不过我个人的经验是几百张图片的量级下手工标注反而更快因为你不需要花时间去配置模型辅助标注的环境。标注的时候有一条经验边界框紧贴目标边缘就行不要把背景框进去太多否则模型会学到不必要的背景特征。2.2 模型选型与训练参数模型这块我对比过两个方向一个是YOLOv5n/YOLOv8n这类目标检测模型另一个是MobileNetV3SSD这种轻量级检测模型。实际测试下来YOLOv8n在Jetson Nano上的TensorRT FP16精度下推理延迟在30ms左右检测精度比MobileNet SSD高不少而且训练和导出的工具链更成熟。所以最终选型就锁定了YOLOv8n。训练参数直接抄作业的话可以这样设输入分辨率640x640batch size 16epochs 100优化器SGD初始学习率0.01权重衰减0.0005。这里有个容易踩的坑如果数据量只有几百张epochs不要开太大50到100就能收敛开300个epochs很容易过拟合模型在训练集上表现很好但一到真实场景就乱识别。判断过拟合的一个直观方法就是观察训练日志里验证集mAP的曲线如果验证集mAP在某个epoch后不再上升甚至下降而训练集loss还在降基本就是过拟合了。数据增强是提升模型泛化能力的关键一步我强烈建议在训练时打开YOLO自带的增强项包括HSV色域扰动、随机翻转、平移缩放。尤其是HSV色域扰动它对光照变化非常有效因为实际部署时不可能保证每次光照都一样。我试过关闭增强和打开增强两组对比实验实测在逆光场景下打开增强的模型误检率下降了约40%。2.3 训练结果与精度验证整个训练过程我用了大概40分钟用一张消费级显卡最终在验证集上的mAP50能达到0.97以上。不过mAP只是一个参考真正要看的还是部署到Jetson Nano之后在实际场景的检测效果。测试的时候我特意选了模型没怎么见过的角度和光照条件发现确实会有一些漏检这也是为什么我建议大家尽量在部署环境里做数据采集而不是在电脑上随便找图片。训练完成之后会得到最好的权重文件best.pt这个文件不能直接部署到Jetson Nano上跑除非你想忍受每帧几百毫秒的延迟。接下来就必须走模型转换和TensorRT优化这一步。3. Jetson Nano端模型部署与推理优化模型部署是整个项目里最容易卡壳的环节因为环境配置牵扯到JetPack版本、PyTorch版本、CUDA版本、TensorRT版本之间的兼容性。Jetson Nano的JetPack 4.6.1自带TensorRT 8.2和CUDA 10.2但如果你按照普通电脑上的pip命令去装PyTorch百分之百装不上因为Jetson Nano是ARM架构。正确的方式是去NVIDIA官网的论坛下载对应JetPack版本的PyTorch轮子文件.whl然后本地pip安装。3.1 环境搭建与依赖安装基础环境这样装首先确认JetPack版本直接在终端里运行cat /etc/nv_tegra_release看到版本号之后去NVIDIA的官方索引下载对应的PyTorch。我用的是PyTorch 1.8.0版本对应的torchvision是0.9.0这两个版本需要严格匹配。然后装ultralytics库用于加载YOLOv8模型再装onnx和onnxruntime-gpu用于模型导出和验证。这里提醒一句如果不是必要不要在Jetson Nano上直接跑训练它的4GB内存根本不够折腾。模型导出我推荐走YOLOv8n.pt - ONNX - TensorRT Engine这条链路。先用ultralytics库把pt导出为ONNX格式命令是yolo export modelbest.pt formatonnx opset12。导出的时候注意输入尺寸固定为640x640后面转TensorRT也要保持一致。然后使用trtexec工具把ONNX转为TensorRT引擎文件.engine命令是/usr/src/tensorrt/bin/trtexec --onnxbest.onnx --saveEnginebest.engine --fp16加上--fp16参数能启用半精度推理速度几乎翻倍而精度损失在目标检测任务里基本可以忽略。实测下来FP16的TensorRT引擎推理速度大约是FP32的1.8倍对项目来说非常关键。3.2 推理程序设计与坐标解算有了TensorRT引擎之后需要自己写推理代码。推荐用Python的pycuda配合TensorRT的Python API来加载引擎并进行推理网上有很多现成的YOLOv5/v8的TensorRT推理脚本可以改。核心逻辑分三步读取摄像头帧、预处理到640x640、执行推理并解析输出。预处理时注意要做letterbox处理也就是把图像等比缩放到640x640并在四周填充灰边而不是直接拉伸否则目标形状会变形导致检测精度下降。推理输出是一个多维数组需要通过后处理解析出检测框坐标、置信度和类别ID。后处理代码里要重点关注的三个参数是置信度阈值、NMS的IoU阈值和锚框尺寸我实际用下来置信度阈值设为0.45、NMS IoU阈值设为0.45效果最好。坐标解算是把检测框从640x640的缩放坐标系映射回原始画面坐标系。因为后面要控制舵机云台追踪物体所以还需要把检测框的中心点转换成云台的偏航和俯仰角度。这一步我建议在代码里增加一个平滑滤波比如用一个简单的低通滤波器或者移动平均值避免检测框抖动导致舵机来回摆动。实际测试中不加滤波时舵机明显在高频抖动加了滤波之后转动就非常平滑了这是提升体验感的一个关键点。3.3 模型部署踩坑记录部署过程中的坑主要集中在三点一是ONNX导出时如果用的是高版本ultralytics库可能会遇到算子不兼容的问题解决办法是降低opset版本或者手写部分算子替换二是TensorRT引擎构建时显存不足4GB版本的Jetson Nano确实比较容易爆显存解决办法是关闭桌面环境释放显存或者在trtexec命令里加上--maxWorkspaceSize1GB限制工作空间三是推理时GPU利用率很低帧率却不高这通常是因为预处理和后处理都在CPU上执行形成了瓶颈可以试试用cv2.dnn或torchvision的GPU张量操作来加速。这些都是实战中非常容易卡住的地方我整理成一张速查表放在文末方便大家直接对照排查。4. Jetson Nano与STM32通信协议设计到了通信这一步就要把“大脑”和“小脑”打通了。Jetson Nano的GPIO里有一组UART默认是ttyTHS1STM32也有USART外设两者连接直接用TX接RX、RX接TX、GND共地就行。这里最容易犯的错误就是TX接TX、RX接RX结果完全通不了这个我在第一次接线时也犯过。因为两边都默认数据引脚是输出信号就对顶了。4.1 串口通信参数与硬件连接Jetson Nano侧的UART默认波特率是115200但要注意Jetson Nano的GPIO UART默认被控制台占用了需要先禁用控制台功能才能使用。步骤是sudo nano /etc/nv-l4t-usb-device-mode-runtime.service把相关控制台配置注释掉或者用systemctl停掉nvgetty服务。STM32侧我用的USART1配置为115200、8N18位数据、无校验、1位停止位这个配置要和Jetson Nano完全一致不然收发的数据全是乱码。硬件连接上Jetson Nano的UART电平是3.3VSTM32的USART也是3.3V电平所以可以直接连接不需要电平转换。但如果是用Jetson Nano的USB转TTL模块比如CP2102连接STM32那就要注意USB转TTL模块的输出电平是否匹配部分模块是5V电平需要确认支持3.3V跳线设置。此外还有一个非常容易被忽视的点两边必须共地否则串口通信会因为参考电平不一致而出现随机乱码。很多人通信不稳定排查半天最后发现就是地线没接。4.2 通信协议帧格式定义串口通信的难点不在于收发而在于数据解析的稳定性。因为串口是字节流没有严格的帧边界如果接收方不知道一帧数据从哪里开始、在哪里结束解析就会错乱。所以必须定义一套协议帧格式。我设计的帧格式如下字节位置内容说明00xAA帧头10x55帧头确认20x01数据长度从第3字节到倒数第2字节的字节数30x01命令字0x01表示舵机角度控制4高字节舵机通道号5低字节目标角度值0-18060x0D校验和前面所有字节的累加和取低8位70x0A帧尾这个格式看起来简单但设计上包含了几个关键点帧头用两个固定字节0xAA 0x55来减少误判概率长度字段让接收方知道要读多少个数据字节校验和用于检测数据是否在传输过程中出错帧尾用来确认一帧数据的结束。实际调试中我发现校验和非常重要因为Jetson Nano的USB转串口在某些高负载情况下偶尔会丢字节如果没有校验舵机就可能接收到错误的角度值而乱转。4.3 双端代码实现思路Jetson Nano侧用Python的pyserial库发送指令代码逻辑很简单先根据检测到的目标中心坐标计算出舵机应该转到的角度然后按帧格式打包发送。一个需要注意的细节是发送频率不要太高10-20Hz就足够了舵机物理上也不可能响应更快的指令反而会增加总线负担和抖动概率。STM32侧用HAL库的串口接收中断来实现。我强烈建议不要用简单的阻塞式接收而是使用HAL_UART_Receive_IT()配合空闲中断IDLE line interrupt来接收不定长数据。这样STM32在接收到一帧完整的数据后才会触发处理逻辑避免了逐字节解析的状态机复杂度。如果用的是标准库也可以开启USART_IT_RXNE和USART_IT_IDLE来实现同样的效果。接收数据的处理逻辑就两步第一步循环查找帧头找到0xAA 0x55之后才认为有效帧开始第二步校验长度和校验和校验通过则解析出角度值并更新PWM占空比。如果校验失败直接把当前缓冲区清空重新找帧头就行。这种状态机处理方式在实际项目中非常可靠我在连续运行24小时的测试中没有出现过一次解析错误。5. STM32端PWM舵机控制实现舵机控制的本质就是产生特定占空比的PWM波形。市面上绝大多数舵机SG90、MG996R等都是模拟舵机控制信号是50Hz的PWM也就是周期20ms其中高电平时间在0.5ms到2.5ms之间变化对应舵机角度范围约0度到180度。也就是说高电平1.5ms时舵机在中间位置90度0.5ms时在0度2.5ms时在180度。5.1 定时器PWM配置详解STM32F103的定时器能够直接输出PWM波形不需要CPU持续干预。我用的是TIM2的通道1PA0引脚配置为PWM模式1输出频率50Hz。具体配置时要注意几个参数的计算定时器时钟是72MHz分频器PSC设为71那么定时器计数频率就是1MHz也就是每个计数周期1微秒自动重装载值ARR设为19999那么PWM周期就是20000微秒即20ms正好是50Hz。这里重点说一下角度和占空比的换算。因为一个PWM周期是20000微秒而能控制的占空比部分只有2000微秒0.5ms到2.5ms对应0到180度所以每度大约对应11.1微秒。换算公式是CCR 500 (angle / 180.0) * 2000其中500对应0.5ms0度2000对应2.5ms180度。这个公式我建议直接写在代码注释里方便后续调整。如果用的是180度舵机角度范围就是0到180如果用的是360度连续旋转舵机则角度控制变成速度控制那就是另一种玩法了。5.2 舵机供电与电源设计舵机供电是整个项目里最容易出问题的地方没有之一。Jetson Nano和STM32还可以勉强用同一个5V电源但舵机绝对不能直接挂在开发板的5V引脚上。普通SG90堵转时电流可能到500mA到800mAMG996R堵转电流甚至能到2A以上。开发板的稳压器根本扛不住这么大的瞬态电流一拉电流电压就掉轻则舵机无力抖动重则开发板直接重启。我的做法是给舵机单独供电用一个5V 5A的直流电源适配器正极接舵机的红线负极接舵机的棕线同时把电源负极和STM32的GND连接在一起。信号线直接从STM32的PA0引出。这样电源回路和控制信号回路就完全隔离了舵机的瞬态大电流不会拉垮控制板的电压。如果你没有5V开关电源用3节18650锂电池串联加降压模块也行但一定要保证电流输出能力大于2A。另外还有一个小细节舵机电源的两端要加一个470uF以上的电解电容和一个0.1uF的陶瓷电容用于吸收舵机瞬间启停产生的尖峰电压。我在做这个项目之前第一次直接裸奔跑舵机结果发现STM32偶尔会复位加上电容之后这个问题再也没有出现过。选型电容的时候注意耐压值要大于5V一般选10V或16V的电解电容就行。5.3 平滑转动与限位保护舵机在接收到角度指令后如果直接从当前位置跳到目标位置速度过快会产生很大的冲击力尤其是当舵机带动机械臂或云台时这种冲击会严重影响机械结构的寿命。所以我加了一个增量步进算法每次控制周期比如每10ms让舵机角度只移动一小步比如1到2度直到到达目标角度。这个效果非常明显舵机转动变得非常顺滑机械结构的晃动也大大减小。限位保护同样重要。如果模型误检或者通信错误导致STM32收到一个超出范围的90度以外角度直接去执行的话可能会让舵机损坏或者机械臂撞到限位块。所以我在解析出角度后加了一个钳位检查只允许0到180度范围内的值生效超出范围就丢弃并保持当前角度。另外我还在代码里保存了一个“上次有效角度”变量如果一帧数据校验失败就直接沿用上次的角度避免舵机因未知错误乱动。6. 常见问题与排查技巧实录做这个项目的过程中我前前后后踩了不少坑这里挑几个最典型的整理出来。这些问题如果不提前预防排查起来非常耗时我希望能帮大家节省一些调试时间。现象可能原因解决方案Jetson Nano和STM32串口通信全是乱码波特率不匹配RX/TX接反未共地统一波特率TX接RX、RX接TX检查GND是否连通串口一直接收不到数据Jetson Nano的UART被控制台占用禁用nvgetty服务重启后再次确认端口映射舵机上电后抖动但不动供电电流不足PWM频率不对舵机独立供电确认PWM周期为20ms50Hz舵机能动但角度不准确角度换算公式错误PWM精度不够检查CCR计算公式确认定时器分频系数模型推理速度很慢只有5帧没有用TensorRT没有启用FP16CPU后处理瓶颈转TensorRT引擎并加--fp16优化预处理和后处理检测框乱跳导致舵机抖动没有加平滑滤波检测置信度阈值太低对目标中心坐标做低通滤波提高置信度阈值到0.5以上6.1 通信乱码与数据丢失的排查思路通信乱码的排查要按顺序来第一步用示波器或者逻辑分析仪看USART引脚的波形确认电平是否正常第二步用串口调试助手分别测试Jetson Nano和STM32各自的收发是否正常第三步再把两边的地线用万用表测一下连通性。这样做的好处是把问题域一步步缩小避免上来就改代码。我遇到过一种很奇怪的情况Jetson Nano和STM32单独连电脑都正常但互相连的时候偶发丢包最后发现是USB转TTL模块质量差影响了信号完整性换了一根好线就好了。6.2 舵机抖动与响应迟钝的调优方向舵机抖动一般从两个方向排查供电和PWM信号。先用万用表测舵机供电电压如果电机转动时电压跌落超过0.3V基本就是电源没跟上。排除电源问题之后再看PWM波形用示波器看PA0引脚上的波形是否稳定、占空比是否准确。如果波形有点毛刺可以在信号线上串联一个100欧姆电阻来抑制反射。响应迟钝的现象通常是PWM更新频率过低导致的检查一下是不是定时器中断里做了太多其他事情导致PWM占空比更新不及时。6.3 整体联调中的注意事项整个系统联调的时候我的建议是分三步走每一步都验证清楚了再进入下一步。第一步先用串口调试助手给STM32手动发固定的角度指令验证舵机控制是否正常第二步在Jetson Nano上写一个简单的测试脚本循环发送一组角度序列验证串口通信链路是通的第三步把摄像头检测、坐标解算、角度映射、舵机控制全部串起来跑真实的追踪场景。这样每步出了问题都能快速定位不会出现“不知道是检测错、通信错还是执行错”的情况。如果最终发现整体响应延迟还是偏高可以排查一下每个环节的耗时Jetson Nano的模型推理通常需要20到30ms串口发送几乎可以忽略STM32的PWM更新是毫秒级舵机本身的机械响应时间在100到200ms左右。所以整个系统的主要延迟瓶颈在舵机本身而不是在计算和通信环节。如果要做追踪类应用这个延迟是可以接受的如果要做抓取类应用建议在PID闭环或者更高速的舵机伺服方案上做文章。我个人做完这整套项目后最大的体会是Jetson Nano和STM32的配合其实是一个很好的IoT边缘计算架构样板——高算力设备和实时控制设备各司其职既不用让单片机去跑复杂的AI推理也不用让Linux系统去干硬实时的活。这种“AI算力MCU控制”的组合可以复用到非常多场景里后续你还能往这个项目里加ROS通信、加视觉伺服闭环、加多舵机联动扩展空间非常大。但无论往哪个方向扩展这套数据到部署、通信到控制的链路都是地基把它打扎实了后面做什么都会顺手很多。本文还有配套的精品资源点击获取