ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

基于MSPM0G3507与JY61P的自动行驶小车:从姿态融合到PID闭环控制

基于MSPM0G3507与JY61P的自动行驶小车:从姿态融合到PID闭环控制 简介基于MSPM0G3507主控的2024年电赛H题自动行驶小车完整工程源码配套JY61P九轴姿态传感器与OLED显示模块面向电赛参赛者、嵌入式学习者及课程设计场景解决小车自动行驶、姿态采集、电机控制与状态显示等完整链路问题。资源包共50个文件以C源码、头文件、Makefile构建脚本、CCS工程配置及说明文档为主另有SysConfig外设初始化文件和调试配置压缩包仅146KB便于快速获取。已有61人学习参考。工程预配置CCS开发环境采用标准HAL驱动结构包含OLED字模数据与底层驱动、JY61P串口协议解析、核心控制逻辑以及完整项目配置文件开箱即用。源码既适合电赛备赛时对照实现也可作为课程设计原型和嵌入式实践案例帮助理解传感器融合与PWM电机控制的具体工程写法适合用于深入理解嵌入式实时控制工程实践。1. 项目引入与总体方案先说说这道题的实际情况。2024年电赛H题“自动行驶小车”放到嵌入式赛道上核心就一句话让一辆小车在未知赛道上自己跑完遇到障碍能停遇到岔路能选能爬坡能过环岛最好还能实时把状态打到一个屏幕上给你看。很多队伍一开始就在主控选型上耗了很久而我们这次直接锁定了TI的MSPM0G3507外加维特智能的JY61P陀螺仪姿态模块和一块0.96寸OLED屏。这篇内容不是给你贴一堆代码就完事我会把整套工程从硬件接线、通信协议、源码结构、PID调参到实际跑车遇到的坑全部过一遍。无论你是准备下一届电赛还是单纯想在MSPM0G3507上玩一下姿态融合和电机控制这套思路都能直接迁移。MSPM0G3507属于TI新推出的M0系列主频能到80MHz片上资源在这个价位段相当能打配合JY61P这样带串口输出的六轴模块几乎不需要自己写I2C和SPI驱动去读原始加速度和角速度大幅减少了调参时间。OLED在这里不是摆设它承担了整个调试过程的信息面板车速、陀螺仪角度、赛道元素识别结果、PID输出值全都靠它显示。题目的难点从来不只是“能不能动”而是“能不能稳定地跑完”。我们在前期的方案评审里把赛道元素拆成了直道、弯道、十字路口、环岛、坡道、障碍停车几种然后针对每一种工况规划控制逻辑。JY61P输出的偏航角Yaw是最关键的量用它来判断车身姿态、转弯角度和是否进入环岛MSPM0G3507的两个定时器同时采集左右编码器脉冲结合PID闭环控制电机转速OLED只负责把内部状态吐出来方便现场调试时快速判断逻辑走到哪一步。这套方案能通吃大多数赛道布局最核心的原因就是姿态融合数据独立于赛道视觉控制闭环独立于姿态更新。JY61P自己完成卡尔曼滤波MCU拿到的已经是干净的角度值不需要在中断里做矩阵运算电机的速度闭环放在定时器中断里主循环专门跑赛道状态机。这样即使屏幕刷新慢一点、串口打印多一点也不会影响轮子转动的实时性。1.1 JY61P为什么比裸读MPU6050省心如果你用过MPU6050一定经历过自己写DMP库、调I2C时序、处理四元数转欧拉角那一套。MPU6050本身不输出直接可用的角度你需要自己把原始值做姿态解算而且I2C在长线下抗干扰一般电赛现场电机一开读取值经常飘。JY61P则直接把六轴数据在模块内部融合好通过串口以文本或二进制帧格式输出。我们用的是二进制帧中的0x55 0x53类型也就是角度数据包。模块默认波特率115200数据格式为帧头0x55类型0x53然后是Roll、Pitch、Yaw各自的高低字节最后是校验和。MCU端只需要一个串口中断按状态机收包校验通过后拆出三个short型数据即可。JY61P的Yaw就是小车转向控制的核心依据。1.2 MSPM0G3507外设分配思路MSPM0G3507的引脚资源足够丰富不需要为了复用纠结太久。我在这个项目里做的外设分配是串口1接JY61P串口0留给调试和上位机两个定时器TIMG0、TIMG1做编码器接口模式四个PWM通道输出给TB6612驱动左右电机I2C0接OLED。GPIO里还需要几个用来检测电磁传感器或灰度传感器取决于赛道方案以及一个按键用于模式切换和启动。这里特别强调一下MSPM0G3507的定时器虽然支持编码器模式但引脚映射必须查数据手册并不是任意引脚都能进正交解码输入。我们在第一版板子上就踩过这个坑TIMG0编码器引脚默认是PA0和PA1但PA1又被我复用来做按键检测结果两路编码器脉冲怎么都不进中断。后面用SysConfig重新分配才解决。2. 硬件接线与通信链路搭建硬件是整个项目的地基。我之前见过不少队伍代码写得还行结果一上电电机不转、OLED花屏、陀螺仪数据乱跳最后查来查去发现是共地问题或者电源纹波太大。电赛小车属于典型的“大电流负载敏感传感器”混合系统电机启动瞬间电流能到1A以上必须从电源拓扑上就做隔离。电源方案我们是这样设计的两节18650电池串联标称7.4V经过一个AMS1117-5.0给逻辑电路再经过一个RT9013-3.3给MCU和传感器供电。电机单独走7.4V由TB6612内部逻辑控制。这个做法的好处是电机大电流波动和数字电路电源完全隔开避免陀螺仪数据在电机启动时产生跳变。OLED和JY61P都挂在3.3V上I2C上拉电阻用4.7k。2.1 JY61P接线与串口参数JY61P一共六个引脚VCC、GND、TX、RX、SL、KEY。VCC接3.3VTX接MSPM0G3507的RX引脚RX接MSPM0G3507的TX引脚。SL和KEY是模块功能配置引脚SL决定通信接口悬空或拉高表示串口模式拉低是I2C模式KEY用于进入校准模式。保持悬空即可。串口配置参数如下波特率1152008位数据位1位停止位无校验。JY61P默认就是115200如果你之前改过模块配置上电后可能输出其他内容建议先用USB转TTL拿到电脑上看一下数据是否正常再接入MCU。模块上电后第一帧有时不完整代码里必须做帧头检测和超时处理。2.2 OLED用I2C还是SPI我强烈建议用I2C版本的0.96寸OLED128x64分辨率SSD1306控制器。原因很简单I2C只需要两根线接线少不容易错而且MSPM0G3507的I2C外设配合DMA之后刷屏效率也够用。虽然SSD1306的I2C速率理论上能跑到400kHz但实际用100kHz模式也更稳。SPI版本确实刷新更快但在小车上多四根线意义不大。OLED的I2C地址一般有两种0x3C和0x3D取决于模块上CS/SA0引脚的电平绝大多数模块默认0x3C。写驱动的时候建议做一个宏定义方便切换。我见过有人因为地址搞错排查了一下午最后发现就是地址位不对。2.3 电机驱动与编码器接线电机驱动用的是TB6612FNG支持两路电机正反转单路最大电流1.2A对小车的TT电机来说绰绰有余。TB6612的PWMA、PWMB接MCU的定时器PWM输出AIN1、AIN2、BIN1、BIN2是方向控制引脚。这里有个细节TB6612的STBY引脚不能悬空必须拉高才能工作有人会漏接。编码器方面小车上的TT电机一般是霍尔编码器输出A、B两相正交信号。接法上要接到支持正交解码的定时器引脚MSPM0G3507的SysConfig里可以直接把某个定时器配置成Encoder模式硬件自动计数不需要额外写解码逻辑。我用的是1000线编码器这里注意很多TT电机标称的“13线”“26线”是霍尔片数不是编码器线数。如果买的是带减速箱的TT电机用实测值换算轮子转速就行。2.4 电路设计注意事项电池电压检测不要直接接MCU引脚7.4V远超3.3V承受范围必须用电阻分压后进入ADC。我们用的两个10k电阻分压ADC读取范围0-3.3V再乘以比例系数得到实际电压。OLED和JY61P的VCC都必须接3.3V不要接到5V上SSD1306的某些模块虽然标注能到5V但用3.3V最稳妥。全部模块共地这是最容易被忽略的问题。3. 工程源码结构与核心代码解析整个工程我按模块化思路拆成了这几个文件每个文件职责单一方便队伍里不同人并行开发。目录结构大致如下project/ ├── src/ │ ├── main.c │ ├── mspm0_timer_pwm.c │ ├── mspm0_uart_gyro.c │ ├── mspm0_i2c_oled.c │ ├── pid.c │ ├── control.c │ └── track.c ├── inc/ │ ├── mspm0_timer_pwm.h │ ├── mspm0_uart_gyro.h │ ├── mspm0_i2c_oled.h │ ├── pid.h │ ├── control.h │ └── track.h └── startup/main.c只负责初始化外设和调用主循环里的状态机track.c是赛道元素识别和处理逻辑是整个工程里最烧脑的部分control.c把角度误差转换成PWM输出pid.c就是标准的增量式PID实现。这样做的好处是调车的时候只需要改control.c和track.c其他底层驱动基本不动。3.1 JY61P数据接收与解析JY61P的数据帧格式如下一帧11字节0x55开头第二个字节是数据类型0x53代表角度包接着是Roll、Pitch、Yaw三个short类型数据最后是校验和。校验和的算法是从帧头开始到最后一个数据字节全部累加取低8位等于最后一个字节。代码实现时用状态机接收#define FRAME_HEADER 0x55 #define TYPE_ANGLE 0x53 void UART0_IRQHandler(void) { uint8_t byte UART_readChar(UART0_BASE); switch (gyro_rx_state) { case 0: if (byte FRAME_HEADER) gyro_rx_state 1; break; case 1: if (byte TYPE_ANGLE) gyro_rx_state 2; else gyro_rx_state 0; break; case 2: default: gyro_rx_buf[gyro_rx_index] byte; if (gyro_rx_index 8) { uint8_t sum FRAME_HEADER TYPE_ANGLE; for (int i 0; i 7; i) sum gyro_rx_buf[i]; if (sum gyro_rx_buf[7]) { int16_t roll (gyro_rx_buf[1] 8) | gyro_rx_buf[0]; int16_t pitch (gyro_rx_buf[3] 8) | gyro_rx_buf[2]; int16_t yaw (gyro_rx_buf[5] 8) | gyro_rx_buf[4]; gyro_yaw yaw / 32768.0f * 180.0f; gyro_update_flag 1; } gyro_rx_state 0; gyro_rx_index 0; } break; } }注意数据类型是有符号shortJY61P输出的yaw范围是-180到180转换成角度时需要除以32768再乘180。在中断里只做数据接收和解析不要做PID计算避免打断电机控制的实时性。3.2 OLED驱动与状态面板OLED驱动我用的就是常见的SSD1306自己实现了初始化、清屏、显示字符串、显示数字这几个函数。其中显示数字函数利用了格式化输出然后丢给显示字符串函数。因为MSPM0G3507是M0内核没有硬件浮点单元所以格式化浮点数非常耗时我改成把角度和速度乘以100转成整数再显示避免在OLED刷新时占用太多CPU。OLED的刷新频率不要太高正常刷新50ms一次就够了。我们做了一个四级信息页通过按键切换第一页显示Yaw角、Pitch角第二页显示左右编码器速度第三页显示PID的P、I、D三个参数第四页显示当前赛道状态机的枚举值。比赛现场调车时直接按一下按键就能看到内部状态比接串口调试方便太多。3.3 电机速度PID速度环用的是增量式PID控制量加到PWM上。核心公式是int32_t PID_calc(PID_t *pid, int32_t target, int32_t current) { int32_t error target - current; int32_t d_error error - pid-last_error; pid-integral error; if (pid-integral pid-integral_max) pid-integral pid-integral_max; if (pid-integral -pid-integral_max) pid-integral -pid-integral_max; float output pid-Kp * error pid-Ki * pid-integral pid-Kd * d_error; pid-last_error error; return (int32_t)output; }积分限幅特别重要我之前因为忘记加小车在直线段速度还没起来时积分一直累加等到转弯时输出直接溢出导致车子猛地往前冲。加了一组正向和反向限幅之后这个问题就消失了。速度PID的采样周期我们设置成10ms也就是100Hz。这个频率对电机的响应来说是够用的太高的频率会让编码器脉冲数太少计算出来的速度波动很大太低又会响应迟钝。3.4 转向控制逻辑转向控制用的是位置式PD控制。用JY61P的Yaw角作为反馈量目标值根据赛道状态动态调整。比如直道时目标Yaw是0左弯时目标Yaw是-30右弯时目标Yaw是30。然后根据当前Yaw和目标Yaw的差值通过PD计算左右轮的速度差。转向PD的P项我最终调到0.8D项0.15。P太大车子会蛇形走位P太小过弯会推头D项太大则转弯时会有明显顿挫感。这个参数调节没有捷径只能让车子压在跑道上一点点加。3.5 赛道状态机赛道元素识别是整个工程最核心的部分。我用了一个简单的状态机枚举typedef enum { TRACK_START, TRACK_STRAIGHT, TRACK_TURN_LEFT, TRACK_TURN_RIGHT, TRACK_CROSS, TRACK_LOOP, TRACK_OBSTACLE, TRACK_PARK, } track_state_t;每次传感器值更新后先判断当前状态再根据传感器组合决定是否跳转到下一个状态。比如进入环岛时灰度传感器的模式会从“中间黑白黑”变成“连续几拍全白”加上JY61P的Yaw角变化速率就能准确识别出环岛入口。单纯靠灰度传感器的组合判断容易误判结合陀螺仪的角速度变化后正确率大幅提升。4. 调试流程与常见问题调试过程大概是整个比赛中最折磨人的阶段。第一天代码写完小车怎么都不走第二天发现是PID极性反了——误差为正时应该加速结果减了速。所以我想把调试中真正有效的流程和容易踩的坑一次性写清楚。4.1 上电先看OLED别看轮子第一次上电先不装电机驱动板的电源只给MCU和传感器供电。这时OLED应该亮起来并显示当前角度和速度。如果OLED不亮先查I2C地址和SDA/SCL引脚是否接反如果OLED亮但JY61P数据不变检查串口引脚映射和波特率。我建议把JY61P的原始数据用串口打印到电脑上看一遍确定数据正常再往下走。这个阶段能排除大部分接线问题。4.2 电机不转或只有一个方向转电机不转大多不是代码问题而是TB6612的STBY没拉高或PWM引脚没配置成复用功能。只朝一个方向转大概率是方向控制引脚写反了或者PWM占空比为0时方向引脚也是无效电平。排查时先用固定占空比50%的测试代码让两个电机分别正转和反转确认硬件和驱动没问题再上PID闭环。4.3 陀螺仪Yaw漂移JY61P上电后需要静止几秒模块内部完成初始对准。如果上电后马上拿起来动Yaw角会有一定漂移。更关键的是JY61P的Yaw角在没有磁力计辅助时短时间精度可以长时间会逐渐累积误差。电赛跑一圈下来一般不超过20秒漂移量在可接受范围内。如果跑完一整圈发现转向角误差偏大可以考虑在发车线上加一段直线校准让JY61P静止时把当前Yaw强制置零。4.4 编码器读数跳变或者不准编码器读数跳变一般是接线不良或者电机驱动PWM干扰。PWM引脚和编码器信号线不要平行走线最好用双绞线或屏蔽线。还有一种情况是编码器电源和MCU不是同一路稳压导致电平不匹配。我们把编码器统一接到3.3V同时把编码器信号线串联了一个1k电阻读取就稳定多了。4.5 过弯甩尾和环岛打转过弯甩尾常见原因是速度PID的Ki太大导致出弯时速度超调。处理办法是降低Ki或者把转向时目标速度整体降低。环岛打转则是因为进入环岛后Yaw角持续增大如果状态机没有及时切换为“环岛内直行”模式车子会一直转下去。我们在track.c里加了一个角速度积分判断当Yaw角度变化累计达到340度左右时强制切换到“环岛出口”状态小车就能稳定出环岛。5. 开发环境与其他补充最后说说MSPM0G3507的开发环境和一些工程细节。这个系列比较新网上的中文资料相比STM32少很多很多队伍卡在开发环境上其实部署起来并不难。TI提供一个SysConfig图形化配置工具可以直接生成引脚和外设的初始化代码。MSPM0G3507的SDK里也带了大量例程包括UART、I2C、定时器编码器、PWM等基本可以照搬过来改。我推荐用Keil MDK或者IAR如果你熟悉这两者的话上手很快。如果喜欢开源工具链也可以试试GCCVS Code但SysConfig生成代码的CMake集成需要自己处理稍微麻烦一点。5.1 SysConfig配置串口和定时器的注意点SysConfig里配置UART时要确认UART的波特率时钟源正确。MSPM0G3507的UART时钟可以选择BUSCLK或者LFCLK如果选到LFCLK32.768kHz是达不到115200波特率的必须选BUSCLK。定时器配置为Encoder模式时也要注意计数位宽MSPM0G3507的定时器是16位还是32位取决于具体实例如果计数溢出就需要在溢出中断里维护一个高16位的变量。5.2 烧录与调试心得MSPM0G3507支持SWD调试使用XDS110或者DAP-Link都可以。烧录前建议先读一下芯片的SWD引脚是否被复用如果SWDIO被配置成GPIO可能导致无法连接调试器。此时按住复位键的同时点击烧录等烧录连接上再松开复位就能救回来。5.3 源码获取与后续扩展这套源码在我的实际测试中跑标准电赛赛道含直线、弯道、十字、环岛、停车标志的成功率在九成以上。不同场地摩擦力不同PID参数可能需要微调但整体逻辑框架是通用的。后续如果要扩展方向有两个一是接入摄像头做视觉巡线用OpenMV或者K210识别赛道元素二是换用带编码器的直流减速电机配合更细粒度的速度环进一步提升稳定性。最后说一个我实际调车中发现的小技巧给底盘贴一层薄绒布胶带轮子更换为软质橡胶轮能让编码器读数更稳定转弯也不容易打滑。这个细节很多队伍直到比赛前才发现而它不花多少钱却能直接影响圈速。本文还有配套的精品资源点击获取
返回列表