2026年电赛校赛备战MSPM0G3507+keil讲解(上)-----2025年电赛E题小车篇

个人主页:
wengqidaifeng

✨ 永远在路上,永远向前走

个人专栏:
数据结构

文章目录

本篇只讲底盘,不讲云台。核心目标是把 MSPM0G3507 底盘跑稳、跑准、跑得可调。

如果说前提篇是在画地图,那这一篇就是正式下场修路。底盘看起来只是"循迹小车",但真正做过的人都知道,它远不止这么简单:你要让车知道自己在哪条线、什么时候该转弯、什么时候只是暂时丢线、什么时候该继续直行穿过缺口、什么时候该停,以及停下以后还能把状态说清楚。

这就是为什么我把底盘单独拿出来讲。它是整套系统的地基,地基不稳,后面的云台和联动都白搭。

如果你是第一次接触这类项目,可以先把底盘上的名词认全。后面所有调试,基本都绕不开这些部件:

  • MSPM0G3507:底盘主控,大脑。
  • 8 路灰度传感器:看黑线用的眼睛。
  • TB6612:电机驱动,负责把控制信号变成电机能跑的电流。
  • 直流减速电机:真正带动车轮的东西。
  • 编码器:测速度和位移的反馈器。
  • OLED:调试时最友好的显示屏。
  • K1 / K2:底盘按键,负责启动、停止、切圈数。
  • UART:和云台交换状态的串口。

你可以先把它们想成一辆小车的"感官系统"和"运动系统":

  • 感官系统负责告诉它"我在哪、线在哪、快不快、对不对";
  • 运动系统负责把这些信息变成前进、转弯和停车。
    该篇所涉及代码均在GitHub仓库

一、先把底盘的任务说透

这套 2025 年电赛 E 题的底盘部分,核心不是"能动",而是"能按题目要求稳定地走完一整套流程"。

从工程角度看,MSPM0G3507 底盘承担的是这些职责:

  • 读取 8 路灰度传感器,判断车是否压在线上;
  • 控制 TB6612 驱动的左右电机;
  • 读取编码器,得到左右轮实际速度;
  • 做速度闭环和循迹闭环;
  • 识别直角转弯和路口标记;
  • 穿过长缺口后重新找线;
  • 统计圈数和总进度;
  • 通过 OLED 把关键状态显示出来;
  • 通过 UART 向 STM32 云台上报底盘状态。

如果你第一次看这些功能,可能会觉得很碎。其实它们可以按"输入、处理、输出"三层来理解:

层级 具体内容 通俗理解
输入 灰度传感器、编码器、按键 告诉车现在发生了什么
处理 状态机、PID、计圈逻辑 决定下一步该怎么做
输出 电机、OLED、UART、蜂鸣器/LED 把决定变成动作和提示

只要先按这个三层框架去看代码,底盘就不会显得那么乱。

也就是说,底盘不是一个"电机驱动器",而是一个完整的实时控制系统。

在代码里,这些逻辑都被收敛到一个很清楚的入口:

  • empty.c
  • app_chassis.c
  • chassis_state.h

empty.c 只负责初始化和主循环,真正的调度和控制都在 app_chassis.c


二、工程入口其实很干净

先看入口:

c 复制代码
int main(void)
{
    SYSCFG_DL_init();
    app_chassis_init();

    while (1) {
        app_chassis_loop();
    }
}

这段代码很朴素,但思路是对的。

底盘系统并不把所有事塞进 while(1),而是把周期任务拆给 1ms 中断:

c 复制代码
void TIMER_1MS_INST_IRQHandler(void)
{
    switch (DL_TimerG_getPendingInterrupt(TIMER_1MS_INST)) {
        case DL_TIMER_IIDX_ZERO:
            app_chassis_on_1ms_tick();
            break;
        default:
            break;
    }
}

这样分的好处很明显:

  • 快速且固定周期的事情放在中断里;
  • OLED 刷新、UART 发送这类稍重的事情放主循环;
  • 逻辑更清晰,也更容易调参。

你如果以后复盘代码,一定要先记住这个结构:
1ms tick 是节拍,主循环是后台。


三、底盘状态不是散的,而是集中管理的

底盘最重要的不是某个函数,而是两个结构体:

  • chassis_config_t
  • chassis_state_t

定义都在 chassis_state.h。

1. chassis_config_t 是"可调参数表"

它里面放的是会跟着实车调试变化的参数,比如:

  • target_laps
  • cruise_speed_ticks
  • lost_line_speed_ticks
  • speed_kp / ki / kd
  • track_kp / ki / kd
  • corner_forward_ms
  • corner_turn_ms
  • corner_turn_speed
  • wheel_diameter_mm
  • encoder_ppr

你可以把它理解成"写在代码里的调试面板"。

2. chassis_state_t 是"运行时实况"

它保存的是实时状态,比如:

  • 当前模式 mode
  • 故障位 faults
  • 当前圈数 current_lap
  • 当前循迹误差 track_error
  • 左右轮速度 left_speed_ticks / right_speed_ticks
  • 左右轮目标 left_target_ticks / right_target_ticks
  • PWM 输出 left_pwm / right_pwm
  • 通信在线状态 comm_online
  • 云台状态 gimbal_state

这就很重要了。因为后面的 OLED 显示、通信上报、故障判断,都是围绕这个状态结构展开的,而不是各写各的。


四、底盘状态机到底怎么跑

底盘的模式定义在 chassis_state.h 中:

c 复制代码
CHASSIS_MODE_IDLE
CHASSIS_MODE_RUN
CHASSIS_MODE_CORNER
CHASSIS_MODE_FINISHED
CHASSIS_MODE_FAULT

这套状态机的意义非常实际。

  • IDLE:上电待机,不跑;
  • RUN:正常循迹;
  • CORNER:进入直角转弯;
  • FINISHED:完成目标圈数;
  • FAULT:出现故障并锁定。

这里最容易忽略的一点是:
"停车"不等于"故障"。

app_chassis_request_stop() 里,普通停车和故障停车是分开的。正常按 K1 停止时,通常只是回到 IDLE;但如果出现急停、通信超时或编码器异常,就会进入 FAULT

这对比赛很重要。因为比赛里你需要的是"能安全停住",而不是"每次停住都像出事了"。


五、1ms 调度是这套底盘的中枢

app_chassis_on_1ms_tick() 是整个底盘的心跳。

它每 1ms 做这些事:

  1. 系统毫秒计数加一;
  2. 处理短时提示;
  3. 扫按键;
  4. 采灰度;
  5. 每 5ms 跑一次循迹 PID;
  6. 每 5ms 跑一次速度 PID;
  7. 每 100ms 刷一次 OLED;
  8. 每 20ms 触发一次 UART 状态上报;
  9. 检查云台通信是否超时。

这段逻辑在 app_chassis.c 里非常核心。

为什么是 5ms 和 20ms

这不是拍脑袋定的,而是比较典型的实时控制节奏:

  • 1ms:系统节拍;
  • 5ms:速度环和循迹环,既不太慢,也不会太抖;
  • 20ms:通信状态上报,足够给云台看,不会刷太频繁;
  • 100ms:OLED 刷新,显示够及时,还不会拖慢控制。

这也是你写任何嵌入式项目都该有的意识:
不是越快越好,而是每个任务都要配对合适的周期。


六、8 路灰度循迹是底盘的眼睛

底盘最先接触赛道的,是 8 路灰度传感器。驱动在 bsp_track8.c。

它输出的不是"黑 / 白"两个简单状态,而是:

  • line_mask:哪些传感器压到了黑线;
  • raw_mask:原始 GPIO 电平;
  • active_count:当前有多少路有效;
  • error:加权后的循迹偏差;
  • line_valid:当前是否真的看到线。

这几个值很关键,因为后面的转弯、找线、计圈都要依赖它们。

1. 不要只看单个传感器

循迹不是"最中间那个黑了就往左 / 往右",而是看整组传感器的分布。

例如:

  • 中间几路黑了,说明车大致在中心附近;
  • 左边黑得多,说明车往左偏;
  • 右边黑得多,说明车往右偏;
  • 大面积全黑,往往意味着路口或转角。

2. track_black_level 很重要

灰度模块有些是黑线低电平,有些是黑线高电平。

所以代码里不是写死"黑线一定是 0 或 1",而是通过:

c 复制代码
g_chassis_cfg.track_black_level

来配置。

如果这项设错了,最典型的表现就是:

  • OLED 上的 mask 反了;
  • 车一上赛道就像没看到线;
  • 你以为是 PID 没调好,其实是极性错了。

这就是为什么调车第一步通常不是调 PID,而是先看 bitmask。


七、电机和编码器:底盘的手脚和反馈

底盘驱动层在 bsp_motor_tb6612.c 和 bsp_encoder.c。

1. TB6612 负责输出

底盘工程里配置了双 TB6612,代码以四驱模式运行:

c 复制代码
bsp_motor_set_mode(BSP_MOTOR_MODE_4CH);

四驱模式的意义不是"看起来更豪华",而是:

  • 让前后轮都参与出力;
  • 提高起步和过缺口的稳定性;
  • 转弯时更容易维持姿态;
  • 低速时不容易只靠单桥带不动。

代码中还有 PWM 限幅:

c 复制代码
bsp_motor_set_pwm_limit(g_chassis_cfg.pwm_limit);

这个限幅非常重要,尤其是你第一次上车的时候。先把车开得稳,再开得快。

2. 编码器负责反馈

编码器不是"装饰件",它决定你能不能真的知道轮子跑了多少。

底盘通过编码器得到:

  • 左右轮 tick;
  • 左右轮速度;
  • 车体平均速度;
  • 后续换算成 mm/s 的实际运动速度。

protocol_stm32.c 里有一个很实用的地方:把 tick 速度换算成毫米每秒,上报给 STM32。

这能帮助云台判断底盘当前到底是快跑还是慢跑,也方便后面同步画圆、画矩形。


八、速度环和循迹环是两层关系

底盘不是一个 PID,而是两层。

1. 速度环

左轮和右轮各有一套速度 PID。

目标是:

  • 让电机输出尽量跟上目标速度;
  • 减少左右轮不一致;
  • 在负载变化时仍然保持速度稳定。

2. 循迹环

循迹 PID 根据灰度误差,算出一个差速修正量:

  • 车偏左,就让左轮慢一点、右轮快一点;
  • 车偏右,就反过来修正。

这层环路决定车是不是"沿线走",而不是"只会转但不知道往哪转"。

3. 两层怎么配合

在代码里,底盘先根据循迹误差算出左右轮的目标 tick,再由速度环去逼近实际输出。

这就是嵌入式控制里很经典的"双环"结构:

text 复制代码
循迹误差 -> 差速目标
差速目标 -> 速度闭环
PWM 输出 -> 电机
编码器反馈 -> 再回到速度环

你在实车上调的时候,通常也是:

  1. 先让速度环稳;
  2. 再加循迹环;
  3. 再去做转弯、缺口和圈数。

九、转弯和缺口不是一个问题

这部分是底盘里最像"比赛经验"的地方。

1. 直角转弯

赛道不是只会平滑转弯,很多时候是 L 形或路口,这时候你不能继续按普通循迹处理。

工程里有一整套和转弯相关的判定逻辑:

  • is_corner_candidate()
  • corner_turn_right_from_sample()
  • corner_forward_ms
  • corner_turn_ms
  • corner_min_sensors
  • corner_cooldown_ms

简单说就是:

  1. 先识别当前是不是像路口;
  2. 决定往左还是往右转;
  3. 进入转弯状态;
  4. 经过一段时间和传感器确认后,再回到循迹;
  5. 加冷却,防止同一个角被重复识别。

这比"看到黑块就猛打方向"稳定得多。

2. 长缺口

赛道里还有长缺口。车一旦进入缺口,灰度传感器会短暂看不到线。

这时不能立刻停车,而是要进入"丢线直行 / 找线"逻辑:

  • 降速;
  • 保持一个较稳的前进姿态;
  • 等重新看到线再恢复正常循迹;
  • 如果丢线太久,就进入故障或停机。

这也是为什么 lost_line_speed_ticks 很重要。

速度太高,缺口中间就容易跑飞;速度太低,又可能过不去。


十、K1 / K2 和 OLED 是最实用的调车界面

底盘按键很简单,但非常好用。

K1

在停止状态下启动;

运行时再按则停止。

K2

切换目标圈数:

text 复制代码
1 -> 2 -> 3 -> 4 -> 5 -> 1

这意味着你不用改代码就能快速测试不同的跑圈任务。

OLED 显示

底盘 OLED 上一般会显示这些内容:

  • 模式
  • 目标圈数
  • 当前圈数
  • 左右轮速度
  • 循迹误差
  • 传感器 bitmask
  • 进度
  • 故障位
  • 云台在线状态

调底盘的时候,我建议优先看这几个:

  1. M:灰度是否压线;
  2. E:误差方向对不对;
  3. VL/VR:两轮速度是否一致;
  4. G+/G-:云台通信状态;
  5. F:有没有故障锁住。

十一、UART 上报其实很有用

底盘不是只会自己跑,它还会向 STM32 云台上报状态。

这部分在 protocol_stm32.c 里。

上报什么

chassis_status_payload_t 里包括:

  • tick_ms
  • state
  • target_laps
  • done_laps
  • lap_index
  • segment
  • lap_progress
  • segment_progress
  • v_left_mm_s
  • v_right_mm_s
  • v_body_mm_s
  • line_error
  • turn_cmd
  • motion_flags
  • fault_bits

这已经不是"随便发个状态"了,而是把底盘的运行语义完整交给云台。

为什么云台需要这些

因为后面综合联动时,云台要知道:

  • 车现在是不是在跑;
  • 车跑到第几圈了;
  • 当前是不是在转弯;
  • 是不是能开激光;
  • 圈进度是否足够稳定,能不能拿来同步画圆或画矩形。

所以底盘不是单独存在的,它会成为整个系统的"节拍器"。


十二、底盘调试顺序,别一上来就猛加速度

这部分是经验,但非常关键。

第一步:先不装轮子,检查电机方向

确认:

  • 左右轮方向对不对;
  • PWM 输出有没有;
  • 电机能不能被单独驱动;
  • 四驱是否都接上了。

第二步:只看编码器

手转轮子,看速度符号和计数是否正确。

如果正转显示负值,先别怪 PID,先看方向是否反了。

第三步:只调速度环

离地或者低速空跑时,先把左右轮速度调平。

目标是:

  • 左右轮能跟随目标速度;
  • 不明显抖动;
  • 不容易超调。

第四步:再调循迹环

把车放到黑线上,低速让它走起来。

如果车总是左偏:

  • 先看误差符号;
  • 再看传感器极性;
  • 然后再调 track_kptrack_kd

第五步:最后再做转弯和缺口

这一步最容易翻车,但也最有成就感。

你需要不断调整:

  • corner_forward_ms
  • corner_turn_ms
  • corner_turn_speed
  • lost_line_speed_ticks
  • MARKER_IGNORE_START_MS
  • MARKER_RELEASE_MS

这几个参数组合起来,决定了底盘到底像不像一辆会比赛的小车。


十三、这一篇讲完之后,底盘逻辑就通了

如果把底盘压缩成一句话,那就是:

用 8 路灰度看路,用编码器看速度,用 PID 管输出,用状态机管行为,用 UART 把底盘状态交给云台。

它不是最花哨的部分,但它是最不能出错的部分。

下一篇我会讲 STM32 云台篇,也就是如何用 STM32F103C8T6 处理视觉、步进电机、激光、按键和 CAN 通信。那一篇会更像"瞄准系统的搭建笔记"。

相关推荐
xiangw@GZ2 分钟前
智能锁浮空系统指纹头金属环ESD防护技术分析
单片机·嵌入式硬件
ACP广源盛1392462567318 分钟前
IX7008 PCIe 交换芯片@ACP#RTX Spark 经济型 8 口扩展芯片(对比 ASM1806)
大数据·人工智能·分布式·嵌入式硬件·gpt·spark·电脑
项目題供诗32 分钟前
STM32-DMA直接存储器存储(二十)
stm32·单片机·嵌入式硬件
耳朵东先生1 小时前
STM32 开发利器:SEGGER RTT 日志打印与 Shell 实践解析
单片机·嵌入式硬件
ACP广源盛139246256731 小时前
IX6012 PCIe 交换芯片@ACP#RTX Spark 入门级 12 口存储外设扩展方案(对比 ASM1812)
大数据·人工智能·分布式·嵌入式硬件·gpt·spark·电脑
2601_958352901 小时前
对讲系统音频优化实战:解决回声、啸叫、环境噪音与远场拾音难题
嵌入式硬件·音视频·语音识别·降噪处理·音频处理模块·硬件开发模块
振南的单片机世界1 小时前
RS485组网三要素:负载、距离、终端电阻
arm开发·stm32·单片机·嵌入式硬件
小慧10241 小时前
Esp开发工具命令
单片机
redaijufeng1 小时前
stm32实现串口打印输出_stm32串口打印
stm32·单片机·嵌入式硬件
黑白园1 小时前
STM32CubeIDE配置FreeRTOS及Demo验证
stm32·单片机·嵌入式硬件