从0到1带你打电赛·小车电控篇(三):整车怎么搭——三人分工、硬件选型与「算法和芯片解耦」的代码框架

前两篇我们把”这比赛在比什么””出题人爱出什么”聊清楚了。从这一篇开始,我们正式动手——但先别急着掏出电烙铁。一支队伍的上限,往往不是某个调参的瞬间决定的,而是开赛前那个”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_INSTQEI_0_INSTUART_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 两大坑

  1. 只认 .txt / .hex 文件,绝不认 CCS 生成的 .out。有人拿 .out 去烧,UniFlash 死活不认,还以为是工具坏了。
  2. 按键时序要掐准:同时按住 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 和电源这套电控地基,我们下一篇见。



本文由 云间辞 原创,发布于 HBZGC 的博客

转载请保留链接: https://cloudlay.cn/nuedc-car-03-build-and-architecture/

暂无评论

发送评论 编辑评论


|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇