📚 本文是 「从 0 到 1 带你打电赛 · 小车电控篇」 系列(共 12 篇)第 3 篇。
前两篇我们把”这比赛在比什么””出题人爱出什么”聊清楚了。从这一篇开始,我们正式动手——但先别急着掏出电烙铁。一支队伍的上限,往往不是某个调参的瞬间决定的,而是开赛前那个”0 阶段”就埋下的:人怎么分工、主控选谁、代码骨架怎么搭。
这就好比盖楼。地基和承重结构得在挖第一锹土之前想清楚,等楼盖到一半才发现柱子位置不对,那不是修修补补能解决的,是要拆了重来的。而电赛只有四天三夜,没有”重来”的预算。这一篇我们就把这套地基打牢,让你后面写算法、调参数的时候,能做到”现场只改参数,不改架构”。
三个人,怎么分这摊活
小车赛道的队伍通常是三个人。最舒服、也最常见的分工是这样切的:
- 硬件 / 机械:负责选电机、画 PCB 或飞线、电源、机械结构、传感器布板与焊接。这个人手要稳、要懂模拟电路,是整车能不能”通电不冒烟”的第一道关。
- 电控软件:负责主控固件——电机驱动、PID、循迹、状态机、各路通信的整合。这是把所有零件”捏成一辆会跑的车”的人。
- 视觉算法:负责摄像头那一摊——巡线、识别色块/二维码/数字,把图像处理成一个个”结论”发给主控。
💡 分工不是切三块互不相干的地
三块活之间咬合得非常紧。硬件选了什么电机,直接决定电控能不能跑闭环;视觉发什么格式的数据,电控就得按什么格式去解析。所以接口要在开工第一天就定死:电机驱动用哪个芯片、编码器多少线、视觉和主控之间的通信帧长什么样。接口一旦钉死,三个人就能各干各的,最后再联调——而不是天天互相等。
这里先埋个伏笔:视觉和主控之间”只传结论不传整张图”的协议怎么设计,是《视觉与通信》一篇的重头戏,这里点到为止。
主控选谁:为什么 2024 年起电赛主推 TI 的 MSPM0
如果你是 2023 年以前打电赛,主控基本就是 STM32。但从 2024 年开始,电赛官方主推的是 TI 的 MSPM0G3507,尤其是智能小车这类题(比如 2024 年 H 题”自动行驶小车”)。很多人第一反应是”我 STM32 还没玩明白,又要换芯片?”——别慌。这俩的关系,我们后面会讲透:STM32 是你练手的”训练场”,MSPM0 是你上场的”主战场”,而且练通的算法能整段搬过去。
先看 MSPM0G3507 这颗芯片本身。它是一颗 Arm Cortex-M0+ 内核、主频 80MHz 的”混合信号”单片机。所谓”混合信号”,意思是它不只擅长跑数字逻辑,模拟外设也特别强——这正好戳中小车的需求。核心规格:
| 资源 | MSPM0G3507 | 对小车意味着什么 |
|---|---|---|
| 内核 / 主频 | Cortex-M0+ @ 80MHz | 跑 PID、状态机绰绰有余 |
| Flash / SRAM | 128KB / 32KB | 装得下整车固件 |
| ADC | 2 个 12 位、4Msps、最多 17~27 通道、带 FIFO | 可同步采样、一次扫完 8 路灰度 |
| 片上运放 OPA | 2 个零漂斩波、可编程增益最高 32 倍 | 灰度/微弱信号免外挂运放 |
| 定时器 | 7 个(2 个高级 TIMA + 5 个 TIMG)、最多 22 路 PWM | 出 PWM 驱动电机、测编码器 |
| 通信 | 4×UART、2×SPI、2×I2C、1×CAN-FD | 接屏、蓝牙、陀螺仪、上位机 |
💡 片上运放是个隐藏福利
MSPM0 自带的 2 个运放(OPA),可以理解成手机自带的美颜相机:以前你采灰度这种微弱信号,得在板子上外挂一颗运放芯片来放大,费板子、费焊盘;现在芯片里就带了能放大信号的”内置镜头”,放大完直接喂给 ADC。STM32 的 G0 系列没有集成运放,从 STM32 迁到 MSPM0,正好能用片上 OPA 把外部分立运放替掉。
一个必须提前知道的”反直觉硬伤”:硬件编码器接口只有一路
这是 MSPM0 相对 STM32 最大的坑,选型和布线之前必须想清楚,否则到现场才发现就晚了。
STM32 几乎每个定时器都能配成编码器接口模式,你想测两个电机的转速,随便挑两个定时器就行。但 MSPM0G 全芯片只有一个定时器(常说的 TIMG8)硬件支持 QEI 正交编码器解码。也就是说,硬件层面你只能”白嫖”一路编码器。
那双电机怎么办?两条路:
- 路线 A(硬件 QEI):用那唯一一路 TIMG8,硬件自动计数 + 判方向,几乎不占 CPU。这是首选。
- 路线 B(GPIO 外部中断):A/B 两相各接一个 GPIO,上升沿触发中断,在中断里读另一相的电平判方向,再用一个 20ms 周期的定时器去数脉冲算速度。代价是高转速时容易漏脉冲、把中断占满。
所以双电机的典型方案是:一路走硬件 QEI,另一路退回 GPIO 中断(给它最高优先级),或者上一颗外部计数芯片。
⚠️ 真实踩坑
很多新手在 MSPM0 上想给两个电机各配一路硬件编码器,结果发现只有一个定时器支持 QEI,第二路只能退回 GPIO 中断,高速时丢脉冲、速度测不准,车跑起来一边快一边慢。这个坑的根源不是代码写错,而是选型时没数清楚硬件资源。
编码器测速本身的原理很直白,就像数脉冲过了几个:车每跑一段,电机转一点,编码器吐出一串脉冲;你每隔固定时间(比如 20ms)数一次这段时间内新增了多少脉冲,数得越多说明跑得越快。A、B 相脉冲的先后顺序,还能告诉你是前进还是后退。具体的编码器接法和 IMU 融合,我们留到《感知:灰度/电磁/编码器/IMU》一篇细讲。
开发环境:SysConfig、IDE 和两种下载方式
SysConfig:TI 版的”装修图纸软件”
TI 给 MSPM0 配了一个图形化配置工具叫 SysConfig,作用相当于 STM32 玩家熟悉的 CubeMX。
它就像装修前用图纸软件拖拽布局:你在图上点哪个插座(引脚)接什么电器(外设),软件自动帮你把”水电图”(一个叫 ti_msp_dl_config.h 的头文件,里面有 SYSCFG_DL_init() 初始化函数)画好,你不用自己一根根去配寄存器。配 PWM 在 TIMER-PWM 分类下、配编码器在 TIMER-QEI、配 ADC 在 ADC12、配运放在 OPA。配完它会生成一堆实例宏,比如 PWM_0_INST、QEI_0_INST、UART_0_INST,你代码里直接用这些名字就行。
⚠️ SysConfig 第一坑:改完一定要保存
SysConfig 改完配置如果不保存,生成的 ti_msp_dl_config.h 不会更新,你编译出来的还是旧配置——明明改了引脚却没生效,能让你查半天。每次改完务必 Ctrl+S。
配 PWM 的时候,有个关系要记牢:
$$\text{频率}=\frac{\text{时钟}}{\text{分频}\cdot(\text{period}+1)}$$
向上计数时,占空比 $=\frac{\text{CCR}}{\text{period}+1}$。立创开发板的电机驱动例程用 80MHz 时钟、period 设 10000,实测出来约 8kHz。运行时动态改占空比就一行:
// 设置占空比 (向上计数: duty = CCR/(period+1))
DL_TimerG_setCaptureCompareValue(PWM_0_INST, 1000, DL_TIMER_CC_1_INDEX);
// 如果 SysConfig 里没勾 "Start Timer",需要手动启动一次
DL_TimerG_startCounter(PWM_0_INST);
// 读编码器 QEI 计数
uint32_t cnt = DL_Timer_getTimerCount(QEI_0_INST);
注意 MSPM0 的 API 全是 DL_ 前缀(DriverLib 的意思),这和 STM32 的 HAL_/LL_ 不一样。再记两个小口诀:PWM 占空比建议别超过 95%;改 PWM 频率(改 period)会同时改变占空比的分辨率(period 越小,占空比能分的档越粗),要兼顾。PWM 频率电赛常用 8kHz 到 32kHz 这个区间,低于 20kHz 左右电机会有可闻的”啸叫”,想安静点就往高了选。
IDE:CCS / Keil / IAR 怎么挑
- CCS:TI 官方的,免费,基于 Eclipse,内置 SysConfig,配套最全。新手最省心。
- Keil MDK:电赛圈最主流,因为大家生态熟、学校积累多。但要注意版本——需要 uVision v5.38a 以上 + AC6 编译器 v6.16 以上,版本太旧装不上 MSPM0 的支持包。
- IAR:也支持,用的人相对少。
- VSCode:它本身不是官方 IDE,但社区常用 VSCode + Keil(靠插件)或 VSCode + CMake + GCC 来获得更现代的编辑体验,编译下载还是靠 Keil 或命令行。
一句话:图省心、从零起步用 CCS;要沿用学校积累、跟队友保持一致就用 Keil。
下载:SWD 仿真器 vs BSL 串口
这俩的区别,可以类比成上网方式:
- SWD(专线宽带):用板载的 XDS110 或外接 DAP-Link / J-Link,四根线(SWCLK / SWDIO / GND / 3V3)。不仅能下载,还能实时看程序在干嘛、打断点单步调试。开发期首选。
- BSL 串口(手机热点应急):用 TI 的 UniFlash 工具,通过 USB 转串口把程序灌进去,没仿真器也能用,但只能灌不能调。
BSL 这条路有两个非常容易翻车的细节:
🔥 BSL 两大坑
- 只认
.txt/.hex文件,绝不认 CCS 生成的.out。有人拿.out去烧,UniFlash 死活不认,还以为是工具坏了。 - 按键时序要掐准:同时按住 BSL + RST 约 5 秒 → 松开 RST → 3 秒内点 Load Image → 烧完(约 10~20 秒)松开 BSL → 再按一次 RST。时序没掐准就反复连不上。
结论很简单:开发期老老实实用板载 XDS110 或 DAP-Link 走 SWD,能调试又省事;BSL 留作”手头没仿真器”时的应急手段。
重头戏:让”算法和芯片解耦”的分层框架
到这里,才是这一篇真正的核心。前面铺垫了这么多,就是为了引出一个问题:STM32 练手、MSPM0 上场,凭什么练通的算法能整段搬过去?
答案不在芯片,在代码架构。
为什么要分层
设想一下,如果你的 PID 函数里直接写满了 HAL_GPIO_WritePin(...) 这种 STM32 专属的芯片 API,那换到 MSPM0 的时候,整个 PID 函数都得跟着改——因为它和具体芯片”焊死”在一起了。这就是没分层的下场:换芯片 = 满地找改 = 平移变重写。
分层架构的思路,像点外卖:
- 你(App 应用层):只对着 App 下单,决定”我要吃啥、车要怎么跑”,从不进后厨。这里放状态机和决策。
- App 平台(中间件层):把订单翻译给骑手和商家,承载 PID、滤波、通信协议、元素识别这些和芯片无关的算法。
- 骑手 / 灶台(驱动层 + HAL):干脏活累活,真正去配寄存器、读写引脚。
换城市(换芯片)时,你点外卖的方式(算法)一字不改,变的只是当地的骑手和店家(驱动层)。这就是”算法和芯片解耦”的本质。
这套范式不是我们瞎编的,它直接借鉴了智能车圈久经考验的逐飞(SeekFree)开源库的分层结构:

一条不能破的铁律
分层能不能起作用,全看一条铁律守不守得住:
❗ 铁律:上层只调下层、下层绝不反调;上层禁止直接调芯片 API
这就像公司汇报:下属向上汇报(对上提供接口),老板不会替下属干活,下属也别绕过流程直接跑去动机房(直接调 HAL_GPIO_WritePin 这类最底层 API)。一旦中间件、应用层里混进了芯片专属 API,分层就破了,换芯片时你得满地找它们、一个个改,平移直接变成重写。
逐飞库就是这么干的:设备层(管摄像头/陀螺/屏的初始化)只允许调驱动层的接口,不准碰最底层的芯片 API。这样换 MCU 只改驱动层,中间件和 App 原样平移。
这套库有多能打?同一份逐飞库已经移植到了 RT1064、英飞凌 TC264、STC32,以及 TI 的 MSPM0G3507(对应 2025 TI 板电赛)——上层算法共用,差异全集中在底层驱动。这就是分层解耦价值的活证据。
一个可照抄的目录骨架
落到实处,你的工程目录可以这么切:
project/
├── App/ # 应用层:你写的核心逻辑
│ ├── fsm.c/h # 状态机:待命/起步/直道/弯道/路口/停靠/找回/急停
│ └── control_task.c # 控制环调度:采集→融合→串级PID→输出→保护
├── Middleware/ # 中间件层:芯片无关的算法
│ ├── pid.c/h # PID(位置式/增量式)
│ ├── filter.c/h # 滤波(一阶低通/互补)
│ └── protocol.c/h # 通信帧解析
├── Driver/ # 驱动层:换芯片主要改这里
│ ├── motor.c/h # 电机:把"带符号速度"翻译成PWM+方向
│ ├── encoder.c/h # 编码器读速
│ ├── gray.c/h # 灰度采集
│ └── uart.c/h # 串口收发
└── HAL/ # 芯片相关:DriverLib / 寄存器
└── (SysConfig 生成的 ti_msp_dl_config.h 等)
关键在于接口的方向:App 调中间件和驱动,中间件和驱动里绝不出现任何芯片专属 API。比如电机驱动这一层,对上只暴露一个”给我一个带符号的速度”的接口,符号决定正反转、绝对值决定 PWM 大小,至于底下怎么配 PWM 寄存器,全藏在驱动层里:
// 中间件层:PID 算出一个带符号输出,完全不知道芯片是谁
int pwm = (int)pid_calc(&speed_pid, target_speed, get_encoder_count());
// 驱动层:把带符号输出翻译成具体的正反转 + PWM(这层才碰芯片)
void motor_set(int pwm) {
if (pwm > 0) set_motor(0, pwm); // 正转
else if (pwm < 0) set_motor(-pwm, 0); // 反转(取绝对值)
else stop_motor();
}
pid_calc 这个函数里只有数学,没有一行芯片代码,所以它在 STM32 上调通了,搬到 MSPM0 上一个字都不用改。这就是”练通的算法整段平移”的底气。
STM32 ↔ MSPM0 平移心法
很多人迁移时的第一反应是”把每一个 HAL_xxx 逐行翻译成 DL_xxx“。这是最痛苦、最容易错的做法,千万别这么干。
TI 官方在《从 STM32 到 MSPM0 迁移指南》(文档号 ZHCABX9B,能搜到)里给出的标准心法是:先把你的应用逻辑理解透,然后拿一份最接近的 MSPM0 官方例程当骨架,把你的 PID、循迹这些算法层原样搬进去,只重写外设的初始化和读写。
🧩 一个好懂的比喻
把算法从 STM32 平移到 MSPM0,好比把同一道菜从燃气灶搬到电磁炉。菜谱(算法逻辑)一字不改,只是把”开燃气阀”的动作换成”按电磁炉按钮”(HAL 换成 DriverLib)。聪明的做法不是把每个燃气动作硬翻译,而是直接拿一份电磁炉的现成菜谱当模板,把你的料倒进去。
官方给的工具链对照表,迁移时贴在显示器边上很有用:
| STM32(ST 工具) | MSPM0(TI 工具) | 说明 |
|---|---|---|
| CubeIDE | CCS | IDE |
| CubeMX | SysConfig | 图形化配置 |
| CubeProgrammer | UniFlash | 烧录 |
| CubeMonitor | GuiComposer | 上位机监控 |
| HAL 库 | TI Driver | 高层驱动 |
| LL 库 | DriverLib(DL_ 前缀) |
底层驱动(TI 建议大多数人直接用这个,省内存、性能好) |
有几个外设层面的差异,是平移时最容易”反咬”算法层假设的地方,列成清单逐条核对:
- ☐ GPIO 拆成了两块:STM32 一个 GPIO 概念,MSPM0 拆成 GPIO(读写 / 中断)+ IOMUX(引脚复用),配引脚和读写是分开的。
- ☐ 硬件编码器接口只有一路 QEI(前面强调过的硬伤),双电机方案要提前定好。
- ☐ UART 的 FIFO 只有 4 字节(STM32 是 8 字节),高速收发更容易溢出,而且不支持自动波特率检测。
- ☐ UART 中断里不要照搬习惯去调
NVIC_ClearPendingIRQ()——官方教程明确提醒过,照搬 STM32 写法会出问题。 - ☐ 时钟树、NVIC、中断模型和 STM32 不一样,从官方 example 起步能帮你绕开大部分坑。
官方迁移指南把整个流程总结成五步,照着走就行:① 对照产品系列表选定 MSPM0 器件 → ② 订 LaunchPad 开发板(LP-MSPM0G3507)→ ③ 装 IDE + SDK → ④ 软件移植(从最接近的官方例程改起,不是逐行替换 API)→ ⑤ 调试验证。
有哪些现成的模板和库可以站在肩膀上
完全从零搭框架很费时间。下面这几个仓库都是经过电赛验证的,按你的处境挑一个当起点:
- TI 官方 mspm0-sdk:DriverLib + 海量外设例程(GPIO / Timer / PWM / ADC / UART / DMA / OPA 全都有)。这是所有上层方案的底座,也是”从最接近的 example 改起”那个 example 的来源。一手权威,必装。
- ZLC_MSPM0_Peripheral_Library:2024 电赛 H 题一等奖(山大威海)的外设库,编码器、电机(DRV8701E 双路)、舵机 PWM、8 路灰度、IMU、无线串口一应俱全,还附了设计报告和环境配置文档。想看一等奖的完整车软件栈长什么样,看这个。
- PIDCarTemplate-MSPM0G3507:两轮 / 四轮 PID 小车模板,封装好了 Delay / Encoder / Motor / 按键(短按长按双击)/ MPU6050 / OLED / 蓝牙 / 灰度,clone 下来调 API 就能开发。从零起步直接套模板的首选。
- woai66 的逐飞 MSPM0 移植库:把上面讲的逐飞分层库移植到了 MSPM0,对应 2025 TI 板电赛。想要”芯片解耦平移”那套完整范本,看它。
- danshoujieyi/TI-MSPM0G3507:电赛指定板模板,裸机版 + FreeRTOS 版 + RT-Thread 版都有(Keil5 + VSCode)。想上 RTOS 或要 VSCode 工作流的直接用。
- MSPM0-CMAKE-GCC-Template:GCC + CMake 模板,想脱离 Keil、用 VSCode + 命令行现代工作流的首选。
更全的开源仓库点评(含视觉车、平衡车、仿真工具),我们放在收官的《现场作战手册》一篇里集中评注,这里先给够你起步用的几个。
一些选型和架构上的经验之谈
最后几条是过来人趟出来的,写代码之前先记在心里。
💡 电机选型,先于算法
一等奖队伍(ZLC)选的是带霍尔编码器的减速直流电机,而不是步进或空心杯——步进体积大、空心杯没法测速,只有带编码器的减速直流电机才能跑闭环 PID。换句话说,要做速度 / 位置闭环,选型阶段就必须锁定带编码器的电机,减速比还会影响测速分辨率和扭矩。算法再强,没编码器也没法闭环。
⚠️ MPU6050 的温漂是头号坑
2024 H 题有队伍用 MPU6050 做无指示线直行,发现陀螺仪温漂严重——偏航角随温度漂移,车越跑越偏,直道走不直。消费级 MPU6050 精度有限,上电要静置做零偏校准,长时间跑要考虑温补或换更高级的 IMU(如 ICM、IMU660ra),直行最好融合编码器双反馈,而不是只信陀螺仪。陀螺仪具体怎么校准、怎么融合,留到《感知:灰度/电磁/编码器/IMU》一篇。
❗ 调度方式:电赛首选前后台裸机,别盲目上 RTOS
整车软件的调度,主流且最稳的选择是”前后台裸机”:主循环(后台)跑显示、通信这些慢任务,定时器中断(前台)跑固定周期的控制环。它时序确定、开销极小、容易验证。
要不要上 FreeRTOS / RT-Thread,像出门用不用导航:去楼下小卖部(任务才 3~5 个)凭记忆最快;只有跨城多点配送(任务多、要真并发)才值得开导航——而导航本身也耗油(RTOS 内核要占几 KB,还可能引入优先级反转、同步 bug)。任务少时硬上 RTOS,纯属给自己加难度。
调度、控制环时序、状态机怎么把所有模块串成一辆会跑的车,是《状态机与整车软件》那一篇的主题,到时候我们会给出完整的主控制环伪代码和状态图。
把这一篇的地基打牢,你就有了一套”算法写一次、芯片换着用”的框架。但框架终究是个空壳,真正让车动起来的第一步,是让电机听话——电机驱动、PWM 和电源这套电控地基,我们下一篇见。
📚 本文是 「从 0 到 1 带你打电赛 · 小车电控篇」 系列(共 12 篇)第 3 篇。




