主题
字号
CHAPTER 08 ≈ 30 MIN READ

下一步怎么做:从能动到可控的完整路线图

🔄 上一章回顾

上一章我们把镜头从一条命令拉远到整个仓库,沿着 Git 历史和源码盘点了项目已经完成的部分、留好接口但还没填血肉的部分、以及等待实机验证的部分。现在我们知道了项目走到哪一步,该规划接下来怎么走了。

🎯 本章你会学到

  • 如何在 Keil 中建立一次可复现的编译构建
  • 通信验证的步骤和常见问题排查
  • 四个电机通道的逐一测试方法
  • 安全停止机制(运动许可)的设计思路
  • PID 控制的基本原理——让电机真正按目标速度运转
  • 航向、操作机构的接入顺序

从已经走通的路继续向前

故事开始的时候,上位机只发来一条简简单单的 START_MOTION。现在我们已经知道,这条命令会进入 USART1,经过协议解析和命令分发,再由 motion_start_motion() 把 PWM 3000 写到四个电机通道。那一刻四个轮子应该开始转动——如果接线正确的话。

这条链路看起来很短,但它证明了新架构的主干已经形成:上位机说得清楚,下位机听得懂,协议层没有丢包,运动层能把数字变成电机输出。就像盖房子时先搭好承重墙,墙稳了,后面才能放心地砌砖、装窗、布线。

接下来的工作适合像搭桥一样进行。先确认脚下这一段能够稳定承重,再向前接下一段。每接通一个功能,就留下编译、通信和实机证据。这样遇到问题时,可以很快判断故障发生在协议层、任务调度、运动计算、接线还是机械结构上,而不至于一上来就"整车不动"然后无处下手。

下面这张图是整个后续开发的大致顺序。你可以把它当成一份路线图,不一定要严格线性执行,但建议大体按这个方向推进:

flowchart LR A["可重复构建"] --> B["协议冒烟测试"] B --> C["单轮与轮位验证"] C --> D["停止与安全联锁"] D --> E["编码器闭环与档位"] E --> F["航向与精确运动"] F --> G["升降 / 夹爪 / 推杆"] G --> H["整车联调记录"]

先让构建和通信稳定,再让车轮听话,然后加上安全和反馈,最后接入操作机构。

先认识工程的几个入口

第一次打开大型嵌入式仓库,很容易在几百个文件之间迷路。文件夹一层套一层,文件名看起来都差不多,函数名又特别长。

其实对于初学者来说,最有效的阅读方式不是从第一行开始顺序读,而是沿着"一条命令到底怎么被处理"的实际路径去读。想象一下:上位机发了一个 START_MOTION,它像一封信,从串口进来,经过好几道门,最后变成电机输出。我们只需要跟着这封信走,就能把仓库里最核心的几条线索串起来。

入口 阅读时关注的内容 为什么要看这里
USER/WHEELTEC.uvprojx 芯片型号、编译器版本、目标名、源码分组和输出设置 确认工程能在你的电脑上打开和编译
USER/main.c systemInit()、FreeRTOS 启动和 app_task 的创建 了解程序上电后第一件事做什么
USER/system.c USART1、PWM、ICM20948、使能开关和四路编码器初始化 知道硬件外设在哪里被配置
LOWER_CONTROLLER/APP/app_main.c 字节怎样进入协议解析器 跟踪"收到串口数据后发生了什么"
LOWER_CONTROLLER/APP/command_dispatcher.c 17 条命令目前各自走向哪里 看到每条命令对应哪个处理函数
LOWER_CONTROLLER/BOARD/ USART1、115200、PWM 3000 和 A/D/B/C 轮位映射 确认硬件参数和轮位配置
LOWER_CONTROLLER/MOTION/motion_controller.c 平移、旋转、停止和电机方向引脚 追踪运动命令如何变成电机输出

拿到仓库后,建议先双击 USER/WHEELTEC.uvprojx 打开 Keil,看看工程的"目录树"长什么样。注意芯片是不是 STM32F407VE,目标名是不是 Mec_Car,源码有没有按 HARDWAREUSERLOWER_CONTROLLER 这样分组。这一步不需要编译,只是熟悉战场。

然后打开 main.c,找到 systemInit() 和 FreeRTOS 的任务创建代码。这里回答的问题是:程序上电后第一件事做什么?哪些任务在跑?app_task 是我们自己业务逻辑的入口,相当于整个下位机程序的"前台接待"。

再往里面走,system.c 负责把各种硬件初始化好:串口、PWM、IMU、编码器、使能开关。可以把它理解为"开机自检和硬件准备工作"。

真正处理命令的地方在 LOWER_CONTROLLER/APP/app_main.c 是接待处,command_dispatcher.c 是分发窗口,motion_controller.c 是执行车间。

遇到"为什么车轮这样转"的问题,可以从 command_dispatcher.c 跟到 motion_controller.c,再看 board_motion_config.hHARDWARE/motor.h。遇到"为什么收不到命令"的问题,可以从 board_uart_config.h 跟到 uart_irq.ctransport_uart.c 和协议解析器。

跟着数据走

嵌入式代码最怕"不知道从哪里看起"。只要记住跟着数据走这四个字,沿着数据流寻找答案,阅读范围会很快缩小,大部分问题都能定位到具体文件。

建立一次可以复现的构建

在继续加功能之前,第一件该做的事是:确保当前代码能稳定编译,并且知道编译出来的是什么。

💡 零基础小课堂:几个关键词

  • IDE(集成开发环境,Integrated Development Environment)——专门用来写代码的软件,就像 Word 是专门写文档的。Keil µVision 就是嵌入式开发最常用的一款 IDE。
  • 编译(compile)——把人写的 C 代码翻译成芯片能看懂的机器语言。就像把中文翻译成英文,翻译完才能"给外国人看"。
  • 烧录(flash/program)——把编译好的程序写进芯片,就像拷文件到 U 盘。芯片断电后程序不会丢失。

USER/WHEELTEC.uvprojx 是现有的 Keil µVision 工程。目标名为 Mec_Car,芯片配置为 STM32F407VE。工程记录的编译器是 ARMCC 5.06 update 7,设备包是 Keil.STM32F4xx_DFP.3.1.1

确保工具版本一致

同一份工程在不同版本的编译器上可能报错。很多初学者遇到"别人电脑能编译,我电脑报错"的情况,往往就是编译器或设备包版本不一致。安装 Keil 后,请核对你的 ARMCC 版本和设备包版本,保持和工程文件记录的一致。

构建输出和自动化小工具

工程把输出目录设为 OBJ/,输出名为 Mec_Car,并开启 HEX 生成。构建完成后,有几个自动运行的小工具会帮你处理产物:

这些小工具不需要你手动运行,Keil 在编译流程中会自动调用它们。

AXF、HEX、BIN 三种文件的区别

编译会生成几种不同格式的文件,它们的用途各不相同:

仓库还保存了 Windows 程序 FlyMcu2188.exe,用于把固件烧进单片机。源码工程使用指南.pdf 讲到 target 选择、全量编译和 HEX 产物;FlyMcu 的端口、波特率、芯片选项和 BOOT 状态将在现场烧录时确认并写入记录。

第一次构建的目标

第一次构建的目标很朴素,但非常有价值:让 HEAD fc3ef5d 在指定工具链中完整编译,并保存构建记录。建议用一个简单的文本文件记录这些信息:

日期:2026-07-14          # 构建日期
提交:fc3ef5d             # Git 提交号,方便回溯
编译器:ARMCC 5.06 update 7  # 编译器版本
错误:0                   # 编译错误数量(必须为 0)
警告:3(记录具体内容)     # 警告可以暂时忽略,但要记录
产物:Mec_Car.hex, Mec_Car.bin  # 确认生成了这两个文件

产物被 .gitignore 忽略很正常,构建记录仍可以单独保存。这样,后续源码变化造成编译问题时,会有一个明确的可用基线——你可以随时回退到这个提交,确认"这个版本至少是能编译的"。

在车轮接触地面前验证通信

电机转起来之前,强烈建议先把通信链路跑通。原因很简单:如果后面车轮不转,你得先确定是电机的问题,还是命令根本没送到单片机。

通信测试可以让机器人保持静止,只看不摸。USART1 的当前配置是 115200 波特率,协议帧头为 0xAA 0x55,payload version 是 20260612。这几个数字先记住:

三条必测命令

TESTS/ 目前只有 README。后续加入的第一个上位机脚本可以依次发送下面这三条命令:

  1. PING:确认收发通道是通的,单片机还活着。
  2. GET_PAYLOAD_VERSION:确认上下位机协议版本一致。
  3. TEST_COMMAND:确认一个 32 位整数能否完整往返,检查大小端、字节序和 CRC。

核对 ACK 的原命令序号、命令 ID 和状态,再核对版本或测试 telemetry。

故意制造错误

通信能跑通还不够,还得确认它能正确拒绝错误数据。可以故意加入错误帧:

这一步像先打电话确认双方语言一致。只要 PING、版本和测试值都能稳定往返,接下来的电机问题就可以集中到运动层与硬件层,而不是在串口和协议上反复绕圈。

💡 零基础小课堂:冒烟测试

冒烟测试(smoke test) 这个名字来自早期电子工程师给新电路板通电时,先看它会不会冒烟。如果通电后没冒烟,说明至少没有短路或严重错误,可以继续做下一步测试。在软件里,冒烟测试指的是"运行最基本的几个功能,确认系统没有严重故障"。我们上面的 PING + 版本 + 测试值三步,就是一次通信层的冒烟测试。

⚠️ 第一次调试最常见的问题

如果通信测试跑不通,先检查这四个最常见的原因:

  1. 波特率不一致——上位机和下位机的波特率设置必须完全相同(当前是 115200)
  2. TX/RX 接反——上位机的 TX 要接下位机的 RX,反过来也一样,这是最容易犯的接线错误
  3. GND 没有共地——上位机和下位机的 GND 必须连在一起,否则电平参考不同,通信会乱码
  4. USB 转串口驱动没装——电脑需要安装对应芯片(如 CH340、CP2102)的驱动程序

让四个通道依次说话

通信没问题之后,再来碰电机。但正式测试整车方向前,车轮适合先悬空。原因很简单:如果某个通道方向反了、PWM 写错通道了,或者某个轮子接线松了,悬空时最多是空转,不会把机器人一下子撞出去。

motion_test_motor_channels_loop() 已经提供一个通道测试:A、B、C、D 分别以 PWM 1200 运行一秒,然后全部停止两秒。当前主任务没有调用它,调试时需要用临时测试入口接入,验证结束后恢复 app_task

⚠️ 临时测试代码一定要删掉!

很多人调试时为了省事,把测试入口长期留在 app_task 里,结果正式联调时机器人一上电就自转,找半天才发现是调试代码没删。养成"加进去 → 验证完 → 立刻删掉或注释掉"的习惯,能避免很多奇怪的问题。

观察记录应同时写下"哪个接口获得输出"和"车身哪个轮位转动"。当前配置期望:

轮位正确后再核对转向。左后和右前使用 -1 方向系数。这个方向系数是什么意思呢?简单说,就是告诉电机"你的正转对车身来说是反转"。因为四个电机的安装方向不同,即使 PWM 都是正的,车身也未必往同一个方向走。通过给某些通道乘以 -1,可以让"前进命令"真正产生前进效果。

接线变化写入 board_motion_config.h,运动层继续使用左前、左后、右前、右后的逻辑顺序。这样即使以后换电机、换线序,也只需要改配置文件,不需要动运动控制的核心算法。

四个通道逐一确认后,可以发送 START_MOTION 的前进、后退、左移、右移,再发送两种 START_ROTATION。每次动作后都发送 STOP_MOTION,确认 PWM 和方向引脚清零。当前三种 gear 都会得到固定 PWM 3000,因此这轮测试关注方向组合和停止效果,而不是速度大小。

做好记录

建议把每一步的结果都拍照或录视频,同时在记录表里写下:命令、期望动作、实际动作、是否通过。这些记录在后面调闭环和整定参数时会非常有用。

先把停止能力接牢

在让机器人跑得快之前,必须先让它停得稳。这是机器人开发里最容易被新手忽视、但又最重要的一条原则。

车轮落地前,安全链需要从"收到停止命令就停"扩展到"通信异常和任务异常也能停"。使能开关 PE4 已经初始化,下一步是让 APP 读取它并维护统一的运动许可

💡 什么是"运动许可"?

你可以把它理解成一个总闸门——只有当所有条件都满足时,闸门才打开,电机才会响应运动命令。这些条件包括:

  • 使能开关已打开(硬件层面)
  • 通信正常(最近收到过有效命令)
  • 没有检测到故障

一旦任何一个条件不满足,闸门立刻关闭,所有输出被强制清零。所有底盘和执行机构入口都读取这份许可,许可撤销时进入同一个安全停止函数。

命令超时检查

命令超时需要周期性检查最后一条有效运动命令的时间。当前 app_task 使用:

// 等待串口收到下一个字节
// portMAX_DELAY 表示"永远等待,直到有数据进来"
transport_uart_receive_byte(&byte, portMAX_DELAY);

portMAX_DELAY 会一直等待下一个字节。也就是说,如果没有数据进来,这个任务会一直挂在这里,没法去做别的事情——自然也没法检查"是不是太久没收到命令了"。

要加入超时检查,有两种思路:

无论采用哪种方式,通信安静时也要有代码醒来检查时间。超时长度由上位机发送周期和实车制动测试决定,并写入板级配置和通信约定。建议初始值设得宽松一点,比如 500ms 到 1s,后续根据实际通信周期收紧。

看门狗和复位

看门狗随后负责发现任务卡死——还记得第 5 章讲过的看门狗吗?正常任务定期"喂狗",关键任务停止运行时由硬件复位。启动后的默认电机状态、复位原因记录和复位后的输出清零也应一起验证。

完成这一步后,串口拔掉、上位机退出、使能关闭和任务停顿都能得到可观察的安全结果。

用编码器把档位变成真实速度

到目前为止,gear 参数虽然存在,但其实还没真正派上用场。当前 PWM 3000 让车轮获得固定驱动,不管是 gear = 1 还是 gear = 3,输出都一样。这显然不是最终想要的行为。

从开环到闭环

编码器接入后,可以为微速、慢速和快速档位定义目标轮速,再周期性读取 A、B、C、D 的增量。控制器比较目标与测量值,逐步调整 PWM——这就是速度闭环

闭环控制听起来有点抽象,但其实很好理解。想象你在开车,目标是保持 60km/h。你一边看仪表盘(编码器反馈),一边踩油门或刹车(调整 PWM),让速度尽量接近目标。

那么,谁来当这个"自动司机"呢?答案就是 PID 控制器

💡 零基础小课堂:PID 控制

PID 是三个英文单词的首字母:Proportional(比例)、Integral(积分)、Derivative(微分)。不用被数学名词吓到——它本质上就是"定速巡航"的三种调整策略:

  • P = 比例:差距越大,调整越用力。当前速度和目标差了很多?那就大力踩油门。差得不多?轻踩一下就好。
  • I = 积分:如果差距持续存在,慢慢加大调整力度。比如你一直差 2km/h 追不上,I 会说"光靠 P 不够,我再多加一点油"。
  • D = 微分:如果差距正在缩小,提前减少调整,避免冲过头。比如速度正快速接近目标,D 会说"别再猛踩了,马上就到了,该松油门了"。

三者合力,就能让电机既快速响应、又不剧烈震荡地跟上目标速度。慢慢来,PID 一开始调不好很正常,这需要反复实验。

关键参数

要让 PID 工作,先得搞清楚几个关键参数:

数据单位统一后,SENSORS 对外提供轮速,MOTION 管理目标和控制器。原厂 BALANCE/ 中保留了 PI 控制思路,可以作为参考;新的初始化已经停用原厂 PI 参数,比赛底盘需要结合当前机械结构重新测量和整定。

整定 PID 参数

整定(tuning) 是什么意思?你可以把它想象成调收音机频道——慢慢转旋钮,找到声音最清晰的位置。PID 的三个参数(P、I、D)就像三个旋钮,需要你一边调一边观察效果,找到最合适的组合。

整定时不要心急,建议按这个步骤来:

  1. 先用一个轮子悬空做单轴测试
  2. 观察阶跃响应(step response)——突然给电机一个目标速度(比如从 0 跳到 200 RPM),看它多快能跟上、有没有剧烈震荡、稳态误差大不大
  3. 单轴稳定了再上整车
  4. 整车稳定了再加负载

从闭环到精确运动

有了稳定轮速,按时间移动可以使用 FreeRTOS 时钟结束动作,按距离移动可以累计编码器里程。持续动作与精确动作适合由运动状态机来管理。

还记得第 3 章讲过的状态机吗?运动状态机的思路是一样的——可以想成一张写着"空闲、运行、减速、完成、故障"的流程牌。命令分发器只负责接单和启动状态,运动任务持续执行,新的 STOP_MOTION 仍能随时被接收。

状态机的好处是:同样一条 START_MOTION,在不同状态下可能有不同处理。比如正在运行时收到新命令,可以选择覆盖、排队或拒绝;发生故障时自动进入安全状态。这样代码结构清晰,调试时也更容易定位"卡在哪一步"。

再补航向和操作机构

底盘能跑、能停、能按速度跑之后,再往上加精度和操作机构。

航向控制

按角度旋转和 GET_HEADING 需要 ICM20948 提供稳定朝向。还记得第 5 章介绍过的 IMU(惯性测量单元)吗?这里主要用它的陀螺仪和磁力计来算航向角。

开发时先记录静止零漂——就是机器人不动的时候,IMU 输出的角度还在慢慢变化(第 5 章也提到过这个问题)。这个漂移如果太大,会导致积分出来的航向角越来越不准。可以通过校准、滤波或者定期用编码器修正来缓解。

验证步骤:先确认静止零漂在可接受范围内,再验证缓慢旋转一周时角度是否连续,随后把 heading 接入 SENSORS 和 telemetry。

编码器负责看轮子走了多少,IMU 负责看车身朝向怎样,两种反馈合在一起可以提高直线和旋转控制的可靠性。比如走直线时,IMU 可以检测车身有没有偏航,然后自动修正左右轮的速度差。

操作机构

升降、夹爪和推杆适合在底盘安全链稳定后接入。为什么呢?因为操作机构往往涉及机械限位、电机堵转、夹持力控制等问题,如果底盘本身还动不动乱跑,调试执行机构会非常危险和混乱。

建议按这个顺序来:

  1. 每个机构先完成单独的手动低速动作。
  2. 再加入限位读取、动作超时和停止。
  3. 最后连接六条已有协议命令。

目标高度、方向和开合状态已经有生成的参数结构,新的 ACTUATORS 层负责把这些参数变成受保护的机械动作。所谓"受保护",就是要有超时、限位、故障检测,不能一个命令下去就无脑执行到底。

整车联调记录

整车联调时,每条记录都带上 Git 提交号、固件版本、板卡与接线、发送命令、收到的 ACK/telemetry 和实际动作。构建日志证明代码进入固件,串口抓包证明命令完成往返,轮位记录和视频证明机械动作符合预期。证据连成一条线,项目的完成状态也会自然变得清楚。

小结与下一步

那条最初的 START_MOTION 已经从一个协议名称走到四路电机输出接口。它走通了一条最小的端到端链路,也把我们带到了继续开发的起点。

继续沿着构建、验证、反馈和安全这条路线前进,它会逐渐从"写下电机输出"成长为"让机器人知道自己在做什么,并且能够可靠地停下来"。

对于刚接手这个仓库的你来说,不需要一次性吃透所有文件。建议下一阶段的阅读顺序是:

  1. 先打开 Keil 工程,确认能编译当前提交。
  2. 写一个最小的上位机脚本,把 PING、版本和测试命令跑通。
  3. 悬空测试四个电机通道,确认轮位和方向。
  4. 加入超时和使能检查,让停止变得可靠。
  5. 接入编码器,把 PWM 开环变成速度闭环。
  6. 最后接入 IMU 航向和操作机构。

每一步都留下记录,每步之间都有可验证的证据。这样即使你是第一次面对这样的大型电控仓库,也能稳扎稳打地把机器人做完整。

本章新词

术语 英文 一句话解释
IDE Integrated Development Environment 专门用来写代码的软件,Keil 就是一款嵌入式 IDE
编译 compile 把人写的 C 代码翻译成芯片能执行的机器语言
烧录 flash / program 把编译好的程序写进芯片,就像拷文件到 U 盘
AXF ARM Executable Format 带调试信息的可执行文件,仿真时使用
HEX Intel HEX 文本格式的烧录文件,包含地址和数据
BIN Binary 纯二进制机器码,体积最小
冒烟测试 smoke test 运行最基本的功能,确认系统没有严重故障
运动许可 motion permit 一个总闸门,所有条件都满足时才允许电机运转
PID 控制 Proportional-Integral-Derivative 通过比例、积分、微分三种策略让电机跟上目标速度
阶跃响应 step response 突然给电机一个目标速度,观察它多快能跟上
整定 tuning 调整 PID 参数使控制效果最优,像调收音机找最清晰频道
零漂 zero drift 传感器静止时输出值仍在缓慢变化的现象
状态机 state machine 按预设流程在不同状态间切换的程序结构