主题
字号
CHAPTER 07 ≈ 48 MIN READ

项目进度总览:哪些已经做好,哪些还在路上

项目状态截至 2026-07-14 · Git HEAD fc3ef5d

🔄 上一章回顾

上一章我们学习了反馈与安全机制——编码器如何充当"计步器"报告轮子转了多少、看门狗如何在程序卡死时自动重启、命令超时如何像"外卖超时自动取消"一样保护机器人。这些机制确保了机器人不会在失控时造成损害。

🎯 本章你会学到

  • 如何通过 Git 历史了解项目的开发进程
  • 17 条协议命令中,哪 6 条已经能工作,哪 11 条还在等待实现
  • 八个新模块各自完成到了什么程度
  • "代码写好了"和"车上验证过了"之间到底有多大距离

把镜头从一条命令拉到整个仓库

前面几章我们一直跟着 START_MOTION 这条命令在代码里"行走"。它从 USART1 进入 STM32,经过协议解析和命令分发,最后让四路电机得到 PWM 信号。沿着这条路,我们看到了一段完整的软件链路:串口收到字节、协议把它翻译成命令、分发器决定该做什么、运动模块把命令变成电机输出。

现在把镜头拉远,问一个自然的问题:整个 RoboGame 2026 下位机项目,到底已经完成了哪些部分?哪些部分刚刚留好接口、还没填入真正的业务逻辑?哪些结论还只是代码层面的推断,还需要真正把小车上电、把轮子放到地上才能证明?

本章以 2026 年 7 月 14 日看到的仓库状态为准,Git(代码的时光机,每次修改都可以保存一个快照)当前**提交(commit)**是 fc3ef5d。这个提交本身产生于 2026 年 6 月 13 日,也就是说,虽然我们看到它时已经 7 月中旬,但代码本身停留在 6 月中旬的版本。

状态判断时,我会优先查看当前源码和 USER/WHEELTEC.uvprojx 这两个"一手的代码证据",然后再参考各模块的 README 与 Git 历史作为辅助说明。

💡 零基础小课堂:什么是 Git?

如果你从来没听说过 Git,不用担心,只需要记住几个概念:

  • Git = 代码的时光机。每次修改代码后,你都可以保存一个"快照",以后随时可以回到任何一个快照
  • 提交(commit) = 按下存档键。每次"提交"就是把当前代码状态保存下来
  • HEAD = 当前最新的存档点。就像游戏里的"最新存档"
  • 提交号(如 fc3ef5d)= 每个存档点的唯一编号,像身份证号一样不会重复

慢慢来,后面看到这些词时就不会觉得陌生了。

💡 七道门——嵌入式功能从"想到"到"做到"要过几关?

很多刚接触嵌入式的同学容易把"代码写好了"和"功能完成了"混为一谈。实际上,一个功能从构思到真正可用,通常要跨过好几道门:

  1. 设计阶段:脑子里想清楚要做什么
  2. 编码阶段:把想法写成 .c / .h 文件
  3. 入工程阶段:文件加入 Keil(写嵌入式程序的专用编辑器)工程,会被编译进固件
  4. 编译通过阶段:Keil 不报错,生成 .axf.hex.bin 等文件
  5. 烧录阶段:把固件写到 STM32 的 Flash 里(烧录就是把程序写进芯片,就像往 U 盘里拷文件)
  6. 单板验证阶段:上电后串口能收到 ACK,LED 会闪
  7. 整车验证阶段:电机真转、小车真走、机构真动

每一道门都可能卡住。本章的目标就是把"到哪一道门了"这件事讲清楚,避免你误以为代码里写了闭环控制,车上就真的能跑闭环。

💡 零基础小课堂:AXF / HEX / BIN 是什么?

上面提到编译会生成三种文件,它们的关系是这样的:

  • AXF:带调试信息的程序文件——开发时用,可以设断点、看变量值
  • HEX:可以烧录进芯片的文件——烧录工具最常用的格式
  • BIN:最原始的程序文件——纯二进制数据,体积最小

你可以把它们想象成同一篇文章的三种格式:AXF 是带批注的草稿,HEX 是排好版的印刷稿,BIN 是纯文字版。

完成度在嵌入式项目里有好几种不同含义。文件已经写好,只说明设计进入了代码;文件已经加入 Keil 工程,说明它会参与目标构建;编译通过、烧录成功、电机实转、整车联调,又是更进一步的证据。

把这些证据分开记录,初学者就能清楚理解"代码里有"与"车上验证过"之间的距离,不会在调试时找错方向。


从原厂小车工程长出新的下位机

仓库不是从零开始写的。2026 年 4 月 30 日,项目导入了 WHEELTEC C50X 的原厂工程。这套原厂代码就像一辆已经能跑的"样板车":电机能转、编码器能读、串口能发、IMU 能初始化、FreeRTOS 能调度、STM32 标准库也已经配好。

对于刚接手的同学来说,这是一个非常宝贵的起点——你不需要自己从头写 PWM 驱动,也不用担心时钟树配错导致串口乱码。

但原厂工程的问题也很典型:它是为了展示小车能力而写的,里面可能同时存在多个演示任务(比如平衡车任务、手柄遥控任务、OLED 显示任务),彼此耦合在一起,而且命名和目录结构不一定适合 RoboGame 的比赛逻辑。如果直接在上面加比赛代码,很容易写着写着就陷入"改一处、崩三处"的困境。

所以 2026 年 6 月 6 日,项目做了一次关键的"整理手术":重新整理商家源码,只保留 Mec_Car 这一个目标,并创建了 LOWER_CONTROLLER/ 目录。电机、编码器、串口、IMU、FreeRTOS 和 STM32 标准库这些"底层器官"继续提供基础能力,而新的比赛业务代码则获得了单独、清楚的代码边界。

你可以把它理解为:把原厂那辆整车的"底盘和动力系统"保留下来,但在上面重新搭了一个"比赛专用的驾驶舱和任务控制台"。

随后一周的提交把工作台逐渐接通。我们按时间线来看一下:

时间 提交 留下的关键结果 用人话说
2026-04-30 813481c 导入 WHEELTEC 原厂工程 把厂家给的"样板车代码"拷进来
2026-06-06 d87d486 重新整理商家源码基线 大扫除:理清哪些代码要留、哪些可以去掉
2026-06-06 927d447 Keil 工程只保留 Mec_Car 目标 砍掉多余的编译目标,只编译我们需要的
2026-06-06 c88087b 建立 APP、PROTOCOL、TRANSPORT、MOTION、SENSORS、ACTUATORS、BOARD、COMMON 的目录边界 给代码按功能分好"房间"
2026-06-08 be94cd7 app_task、UART 通道、协议解析、命令分发和响应链路接入 主任务跑起来了,命令能收能回
2026-06-09 7eae974de7cb006b23148 调整 ICM20948、精简初始化、清理冲突依赖 修修补补:传感器初始化和代码冲突问题
2026-06-11 6e17464 加入 FlyMcu 烧录程序 准备好了"把程序写进芯片"的工具
2026-06-12 6743876 更新协议与生成的消息编解码代码 协议"合同"更新了一版
2026-06-13 0191992fc3ef5d 接入部分运动命令,通信口切到 USART1 第一组运动命令能用了,串口换到 USART1

这条时间线告诉了我们什么?

这条历史线说明当前阶段的重点非常明确:先把新的软件骨架接通,再让第一组运动命令落到真实电机接口。 编码器闭环、航向角解算、升降夹爪这些更复杂的功能,位于下一段工作中。换句话说,现在不是"所有功能都做好了",而是"主干已经能通电,叶子功能可以一片一片往上长"。

💡 初学者常见疑问:为什么 6 月 13 日之后就没有新提交了?

这很正常,不用担心。嵌入式项目经常出现"代码停在某一个稳定节点,但开发和调试还在继续"的状态。可能的原因有很多:作者在写文档、在等硬件、在调车、在忙考试,或者在憋一个大改动。我们本章只根据 7 月 14 日看到的仓库状态来做判断,不猜测背后的故事。


十七条命令分别走到了哪里

协议头文件 comm_protocol.h 定义了 17 条命令。对于初学者来说,可以这样理解这个文件的作用:它是上下位机之间的"合同"。合同里写明了每一条命令叫什么名字、编号是多少、参数有几个字节、每个字节代表什么意思。只要上下位机都遵守这份合同,不管上位机是 Python 脚本、ROS(Robot Operating System,机器人操作系统,一个常用的机器人软件框架)节点还是 PC 上位机,都能向 STM32 发命令。

但"合同里有这条命令"和"这条命令能真的让小车动起来"是两回事。合同只是约定了接口;命令分发器拥有具体动作之后,它才会真正推动机器人,或者向上位机返回有意义的数据。

当前仓库里,有 6 条命令已经具备具体行为,其余 11 条命令虽然已经进入参数解析和错误回复链路,但合法参数只能得到 NOT_IMPLEMENTED 的回复。下面我们逐条看:

命令 当前行为 用人话说
PING 检查空参数,回复 ACK OK 问一句"你在吗?",确认通信正常
MOVE_BY_TIME 解析方向、档位和持续时间,回复 NOT_IMPLEMENTED "按时间走"——接口有了,实现还没写
MOVE_BY_DISTANCE 解析方向、档位、距离和容差,回复 NOT_IMPLEMENTED "按距离走"——接口有了,实现还没写
START_MOTION 根据方向输出固定 PWM 3000,回复执行状态 按一个方向开始持续走,不管走多远
ROTATE_BY_TIME 解析方向、档位和持续时间,回复 NOT_IMPLEMENTED "按时间转"——接口有了,实现还没写
ROTATE_BY_ANGLE 解析方向、档位、角度和容差,回复 NOT_IMPLEMENTED "按角度转"——接口有了,实现还没写
START_ROTATION 根据顺时针或逆时针方向输出固定 PWM 3000 开始原地旋转,不管转多少度
STOP_MOTION 清零 A、B、C、D 四路 PWM 和方向引脚 紧急刹车,所有轮子立刻停下
GET_HEADING 检查空参数,回复 NOT_IMPLEMENTED "我现在朝哪个方向?"——还没实现
MOVE_LIFT_TO_HEIGHT 解析目标高度,回复 NOT_IMPLEMENTED "把升降台升到某高度"——还没实现
SET_LIFT 解析升降方向和档位,回复 NOT_IMPLEMENTED "升降台往上/往下"——还没实现
STOP_LIFT 检查空参数,回复 NOT_IMPLEMENTED "升降台停下"——还没实现
SET_GRIPPER 解析夹爪开合状态,回复 NOT_IMPLEMENTED "夹爪张开/合上"——还没实现
START_PUSHROD 解析推杆方向,回复 NOT_IMPLEMENTED "推杆伸出/缩回"——还没实现
STOP_PUSHROD 检查空参数,回复 NOT_IMPLEMENTED "推杆停下"——还没实现
GET_PAYLOAD_VERSION 回复 ACK,并发送 payload version telemetry 20260612 查看固件的"身份证号码"
TEST_COMMAND 回复 ACK,并把输入整数作为测试 telemetry 返回 回音壁——你喊什么它就回什么

已经能跑起来的六条命令

  1. PING 这是通信链路最基础的"心跳"命令。上位机发一个 PING,下位机回复 ACK OK,说明"我能收到你、我也能回你"。调试时第一件事往往就是反复发 PING,确认串口接线、波特率、帧格式都没问题。如果连 PING 都不通,后面任何运动命令都不用试。

  2. GET_PAYLOAD_VERSION 这条命令回复一个版本号 20260612。你可以把它理解为固件的"身份证"——上位机启动时可以先读一下版本号,确认自己正在和下位机的哪个协议版本对话。如果以后协议升级,版本号也要跟着改,上位机就能根据版本号选择不同的命令集。

  3. TEST_COMMAND 这是一条专门给调试用的"回音壁"命令。上位机发一个 32 位整数,下位机原样把它作为 telemetry 送回。你可以用它来验证:

    • 发送的帧格式是否正确;
    • 参数能不能被正确解析;
    • 下位机的响应链路是否工作。
  4. START_MOTION 前面章节已经详细讲过。它根据方向参数输出固定 PWM 3000,让小车朝某个方向平移。注意这里是"固定 PWM",不是"按距离走的闭环控制"——也就是说,它只管让轮子以固定速度转,不管走多远、也不看编码器反馈。

  5. START_ROTATIONSTART_MOTION 类似,只不过它输出固定 PWM 让小车原地旋转。顺时针或逆时针由方向参数决定。

  6. STOP_MOTION 把 A、B、C、D 四路 PWM 和方向引脚全部清零。这是安全相关的命令:不管你之前在让车往哪走,只要收到 STOP_MOTION,所有输出立刻停掉。

已经留了接口、还没实现的十一条命令

剩下的命令都已经能完成"收到命令 → 检查参数格式 → 参数合法则返回 NOT_IMPLEMENTED"这个流程。这一步虽然看起来只是"回个错误码",但其实很有价值:

💡 `NOT_IMPLEMENTED` 不是 bug,是诚实的进度标记

初学者可能觉得返回"未实现"很丢脸,恨不得先把所有命令都写成"假装成功"的空壳函数。但在团队项目里,NOT_IMPLEMENTED 其实非常有用:上位机开发者看到它,就知道这条命令的接口已经稳定,可以开始写上位机逻辑;下位机开发者看到它,也知道下一步该填什么坑。

相反,如果返回虚假的 ACK,上位机以为功能可用,调试时反而会浪费大量时间。

这张表也展示了"协议入口"和"完整功能"之间的进度。比如 MOVE_BY_DISTANCE 已经拥有 distance_mmtolerance_mm 和参数校验;当前控制板会返回 NOT_IMPLEMENTED,清楚告诉上位机"我知道你要按距离走,但距离闭环还没接好"。一旦编码器反馈与距离控制接入,这条命令就能继续向真实动作推进,而不需要再改协议格式。


八个新模块的实际状态

LOWER_CONTROLLER/ 里的目录名描绘了目标架构,目录中的源码和 Keil 工程文件则显示当前落地程度。我们可以把 LOWER_CONTROLLER/ 想象成一个小型工厂的车间布局图:每个目录是一个车间,车间门口挂了牌子(目录名),但车间里设备安装到什么程度,还得进去看。

模块 当前可确认的状态 用人话说
APP app_task、命令分发和 ACK/telemetry 发送已经加入 Keil 目标 前台接待已上岗,能接电话、转内线
PROTOCOL 帧解析、CRC、命令参数解码和响应编码已经接入,payload version 为 20260612 翻译科正常运转,能读懂全部 17 种信件
TRANSPORT USART1 中断收字节、256 字节接收缓冲和逐字节发送已经接入 收发室正常运转,一封封信按序收发
BOARD USART1、115200、固定 PWM、轮位映射和方向系数已有集中配置 设备档案室已建好,换硬件只改这里
MOTION 持续平移、持续旋转和停止已接入;档位、时间、距离、角度和闭环仍待完成 车能走能停,但还不会定速巡航和自动泊车
SENSORS 编码器和 ICM20948 底层驱动已经初始化;新的航向、编码器反馈和限位接口仍待接入 传感器通电了,但数据还没送到业务层
ACTUATORS 升降、夹爪、推杆的协议入口和规划已经存在,业务源码与硬件接口仍待接入 图纸画好了,设备还没安装
COMMON 职责边界已经写入 README,公共类型与工具文件仍待按实际需要增加 公共工具间挂了牌子,工具以后慢慢添

各模块的简单解读

旧的 HARDWARE/ 继续承担 GPIO、定时器、电机、编码器和 IMU 驱动。新的模块像一层整齐的接线板,把比赛命令连接到这些底层能力。USER/main.c 当前只创建新的 app_task,原厂 Balance_task、显示任务、手柄任务和数据上报任务已经退出启动链路。这意味着程序启动后,不再跑原厂那些演示任务,而是直接跑我们自己的比赛主任务。

💡 初学者常见疑问:为什么 MOTION 里已经写了 START_MOTION,却说闭环没接好?

因为 START_MOTION 目前只是"开环输出固定 PWM"。它告诉电机:"以 3000 占空比转起来",但不问"你到底转没转、转多快、走多远"。

闭环控制则需要在输出 PWM 的同时,不断读编码器脉冲、计算实际速度、和目标速度比较、再动态调整 PWM。代码里可能有编码器读取函数,但这些函数还没有被运动控制算法调用起来。


代码证据与实车证据之间

看完模块状态,我们再来审视一个更根本的问题:这些"代码层面的完成"到底算不算"功能完成"?

USER/WHEELTEC.uvprojx 是 Keil 的工程文件(记录了哪些源代码参与编译),目标名是 Mec_Car,芯片为 STM32F407VE。工程文件已经包含 APP、协议、UART 和运动控制的 .c 文件,说明新的主链路进入了构建清单。

项目配置会生成 AXF 和 HEX,并在构建后调用 fromelf 生成 BIN。这些都能证明:当前代码在 Keil 眼里是一份可以参与编译的合法工程

但"可以编译"和"车上能跑"之间还有几步:

  1. 编译是否真的通过? 工程文件包含了源文件,但如果某些头文件路径没配好、或者函数声明和定义不匹配,编译时仍会报错。本章只能根据工程配置推断"应该能编",不能替 Keil 保证结果。

  2. 烧录是否成功? 仓库里有 FlyMcu2188.exe,说明作者已经为烧录(把程序写进芯片,就像往 U 盘里拷文件)准备了一个工具入口。但"有工具"不等于"已经烧过并且成功"。

  3. 上电后通信是否通? 代码逻辑上 USART1 会收会发,但真到板上,可能波特率配错、接线反了、GND 没共、USB 转串口模块不支持 115200,各种问题都会出现。

  4. 电机是否真的转? START_MOTION 输出固定 PWM 3000,但电机驱动板是否需要使能信号、方向引脚逻辑是否正确、电机电源有没有接上、PWM 频率是否和驱动板匹配,这些都要实机验证。

  5. 整车行为是否符合预期? 即使电机转了,四个轮子转的方对不对、速度一不一致、小车是不是往命令指定的方向走,也都要在地面上验证。

仓库使用 .gitignore(告诉 Git 哪些文件不需要跟踪的配置文件)将 OBJ/*.hex*.bin 作为本地产物管理,因此 Git 历史主要保存源码和工程配置。TESTS/ 当前保存了测试规划,下一次联调可以把提交号、Keil 编译结果、烧录结果、串口抓包和轮子动作写入同一份记录。

按照当前仓库证据,最新代码的编译、烧录与整车测试状态可以记为:仓库中待补验证记录。也就是说,源码已经把"该怎么验证"的路径画好了,但验证结果那一栏还空着,等真正上电、抓包、看轮子动作之后才能填上。

源码已经清楚展示固定 PWM 的输出路径,现场记录将继续回答轮子怎样实际转动。带有提交号、板卡版本、接线、命令帧、ACK 和动作结果的记录,会让这项状态继续向前推进。

两处需要同步更新的文档差异

阅读文档时还有两处值得留意的同步事项,初学者如果只看 README 可能会被带偏:

  1. 轮位映射: 真实轮位映射以 board_motion_config.h 为准,当前是左前 A、左后 D、右前 B、右后 C;但 MOTION/README.md 里的一张旧表仍写着 A、B、C、D。如果你按 README 去接线或写上位机,可能会把左右前后搞反。当前书稿采用实际配置头文件和源码里的值。

  2. 发送串口: 真实发送串口由 board_uart_config.h 选为 USART1,发送代码会进入 uart1_send();但 TRANSPORT/README.md 的发送路径示例仍保留 uart3_send()。这也是文档没跟上代码演进的表现。

⚠️ 文档和代码不一致时,以代码为准

这些差异属于"文档更新工作",不影响当前代码运行,但新手特别容易踩坑。如果你发现 README 和头文件说的不一样,永远以 .h 头文件和 .c 源码里的值为准。建议后续把 README 里的旧示例更新到和头文件一致。


本章小结

走到这里,我们可以给项目当前状态画一张清晰的快照:

项目现在已经拥有一条能够讲清楚、也能够继续扩展的主干。下一章沿着这条主干安排开发顺序:先让工程可重复构建,再验证通信和单轮动作,随后补齐安全、闭环、航向和操作机构,让每一步都留下看得见的证据。


本章新词

术语 英文 一句话解释
Git Git 代码的时光机,每次修改都可以保存一个快照
提交 commit 按下存档键,把当前代码状态保存下来
HEAD HEAD 当前最新的存档点
提交号 commit hash 每个存档点的唯一编号,像身份证号一样不会重复
.gitignore .gitignore 告诉 Git 哪些文件不需要跟踪的配置文件
AXF AXF 带调试信息的程序文件,开发时用
HEX HEX 可以烧录进芯片的文件,烧录工具最常用的格式
BIN BIN 最原始的程序文件,纯二进制数据
烧录 flash/program 把程序写进芯片的过程,就像往 U 盘里拷文件
工程文件 project file (.uvprojx) 记录了哪些源代码参与编译的配置文件
Keil Keil MDK 写嵌入式程序的专用编辑器(IDE)
ROS Robot Operating System 机器人操作系统,一个常用的机器人软件框架
NOT_IMPLEMENTED NOT_IMPLEMENTED 命令已被识别但功能尚未实现的诚实回复