Dummy七日学习(七)

Day 7:系统串联 + 调参实战指南


0. Day 7 定位

前几天是"读懂代码",今天是"会用代码"。目标是两件事:

  1. 脑内完整走通整个系统的控制流(不用看代码,能说出来)
  2. 知道怎么调参------改哪个参数、观察哪个现象、预期什么结果

1. 系统串联总复习:用"问题链"的方式记住整个架构

不要死记硬背。用一串递进式问题把整个系统串起来:

问题链

复制代码
Q1: 上位机发了一条 ">30,-20,90,0,0,0" ,这串字符去哪了?
    → USB-CDC 中断 → usbIrqTask → usbServerTask → ASCII_protocol_parse_stream
    → OnUsbAsciiCmd → commandHandler.Push(字符串) → commandFifo 消息队列
    → Respond("15") 回到上位机

Q2: 谁把这个命令从队列里取出来?
    → ControlLoopUpdate 线程(Normal 优先级)
    → commandHandler.Pop(osWaitForever) 阻塞等待
    → ParseCommand(字符串)

Q3: 这个命令最终变成什么发到了 CAN 总线上?
    → sscanf → 6 个 float → MoveJ(30,-20,90,0,0,0)
    → 校验限位 → 算 deltaJoints → AbsMaxOf6 找最慢关节
    → time = maxAngle × reduction / jointSpeed
    → 反算其余关节速度 → dynamicJointSpeeds
    → MoveJoints(targetJoints)
    → 逐个关节 CtrlStepMotor::SetAngleWithVelocityLimit(angle, vel)
    → CAN 0x07 帧:[NodeID<<7 | 0x07] + [position_float] + [velocity_float]

Q4: 关节 F103 收到 CAN 帧后,谁在处理?
    → HAL_CAN_RxFifo0MsgPendingCallback
    → id 匹配 → OnCanCmd(0x07, data, 8)
    → SetCtrlMode(COMMAND_POSITION) + SetPositionSetPoint(pos × 51200)

Q5: 关节的 20kHz 循环里发生了什么?
    → TIM4 中断 @50μs:
      encoder->UpdateAngle() → SPI 读 MT6816 → 校验 → 查校准表 → rectifiedAngle
      CloseLoopControlTick():
        - 跨圈检测:realLapPositionLast→realLapPosition, delta→realPosition
        - 速度估计:指数滤波, estVelocity
        - 超前角补偿:CompensateAdvancedAngle → estLeadPosition
        - estPosition = realPosition + estLeadPosition
        - 安全检测:堵转?→ Sleep; 过载?→ 标记
        - PositionTracker.CalcSoftGoal(goalPosition) → 梯形速度曲线 → softPosition, softVelocity
        - CalcDceToOutput(softPosition, softVelocity):
            Kp × pError + (Ki × pError + Kv × vError)积分 + Kd × vError → outputCurrent
        - CalcCurrentToOutput(outputCurrent) → estPosition ± 90° → SetFocCurrentVector
        - TB67H450: sin_map 查表 → PWM CCR → RC滤波 → VREF → H桥 → 线圈电流

Q6: 关节怎么告诉核心"我到了没"?
    → 每 50μs 自动回复 CAN 0x23:[当前位置_float] + [完成标志_byte]
    → 完成标志 = (state == STATE_FINISH) ? 1 : 0

Q7: 核心怎么知道 6 个关节全到了?
    → HAL_CAN_RxFifo0MsgPendingCallback → OnCanMessage
    → cmd=0x23: motorJ[id]->UpdateAngleCallback(pos, isFinished)
    → UpdateJointAnglesCallback():
        for i=1..6:
          if motorJ[i]->state == FINISH → jointsStateFlag |= (1<<i)
    → IsMoving() = (jointsStateFlag != 0b1111110)

Q8: 全部到了之后发生了什么?
    → [Sequential 模式] while(IsMoving()) 退出 → Respond("ok")
    → [Interruptable 模式] 早就在第一步返回 "ok" 了

如果你能不看代码、不看我写的文档,完整回答这 8 个问题,你就掌握了这个项目 80% 的核心逻辑。


2. 调参实战:DCE 四参数怎么调

2.1 调参前的准备工作

步骤 1:切换到 Tuning 模式

复制代码
上位机发: #CMDMODE 4

步骤 2:选择要调的关节,设正弦运动

复制代码
上位机发: #SET_TUNING_FLAG 1      (只让 J1 动,bit0=1)
上位机发: #SET_TUNING_FREQ_AMP 1 10   (1Hz, ±10°)

步骤 3:观察 OLED 或通过 #GETJPOS 读取实时角度

复制代码
上位机发: #GETJPOS
回复: ok 9.87 -19.52 89.93 0.12 -0.05 0.01
       (J1 在 10° 附近正弦摆动,说明正常)

2.2 调 kp(位置比例项)--- 影响"追得快不快"

默认值:J1 = 1000(量产版),200(35 电机默认)

实验

复制代码
#SET_DCE_KP 1 200    → 跟踪很慢,实际角度明显滞后于目标正弦波
#SET_DCE_KP 1 500    → 跟踪快了一些
#SET_DCE_KP 1 1000   → 跟踪基本跟上了
#SET_DCE_KP 1 2000   → 开始出现轻微振荡(kp 太大)
#SET_DCE_KP 1 5000   → 明显振荡!电机会发出"滋滋"声

观察方法 :用 #GETJPOS 定时读取 J1 角度,和目标正弦值(1Hz, ±10°)对比。如果实际值总是比目标小(幅度不足)→ kp 太小。如果实际值来回跳(超过目标后又弹回来)→ kp 太大。

物理直觉:kp 就是弹簧的劲度系数。kp 太小 = 弹簧太软,拉不到位;kp 太大 = 弹簧太硬,拉过头来回弹。

2.3 调 kv(速度前馈项)--- 影响"跟得及时不及时"

默认值:80(所有关节相同)

实验

复制代码
#SET_DCE_KV 1 0      → kv=0,去掉速度前馈。在正弦波速度最快处(过零点),
                        位置跟踪出现明显滞后
#SET_DCE_KV 1 160    → kv 翻倍。跟踪滞后减少,但可能出现高频微振

观察方法:在正弦波的"过零点"(速度最大处),观察实际位置是否滞后于目标。滞后越大 → kv 太小(或需要更大)。在"峰值处"(速度=0 处),观察是否有微振。微振 → kv 太大。

物理直觉:kv 是"预判"。你知道目标速度是 30r/s,不等位置出偏差你就提前按这个速度发力。kv=0 就是"永远等偏差出现了再反应"(慢半拍)。

2.4 调 ki(位置积分项)--- 影响"最后一点追不追得上"

默认值:200~300

实验

复制代码
#SET_DCE_KI 1 0      → ki=0。在正弦波峰值处(速度=0),可能存在 ±0.5° 的稳态误差
#SET_DCE_KI 1 500    → ki 加大。稳态误差很快消除,但 ki 太大可能在换向时过冲

观察方法:在正弦波峰值(速度刚好降到 0 的瞬间),此时只有位置误差、没有速度误差。如果实际值停在目标值附近但差一点(如目标 10°,实际 9.7° 就不动了)→ ki 太小。

物理直觉:ki 是"耐心"。kp 说"还差 0.1°,算了不追了",ki 说"虽然只差 0.1°,但已经持续 100ms 了,我再推一把"。

易错提醒:ki 不要和 kp 一起大幅调!先定 kp/kv,最后微调 ki。ki 最容易引起振荡。

2.5 调 kd(速度阻尼项)--- 影响"会不会抖"

默认值:200~250

实验

复制代码
#SET_DCE_KD 1 0      → kd=0。电机在换向时可能出现"嘎嘎"的振动声
#SET_DCE_KD 1 500    → kd 加大。振动消失,但跟踪响应变迟钝

观察方法:听声音!kd 不够时会有高频的机械振动声。这是 DCE 控制律的高速振荡传导到了机械结构上。

物理直觉:kd 是"减震器"。路面颠簸(速度突变)时,减震器吸收冲击不让车身(位置)跟着抖。

2.6 调参顺序(重要!)

复制代码
第 1 步:ki=0, kd=0, kv=80(固定 kv)
第 2 步:逐步加大 kp,直到跟踪够快但还没振荡 ← 定 kp
第 3 步:微调 kv,减少高速跟踪滞后              ← 定 kv
第 4 步:加 ki,消除稳态误差                     ← 定 ki
第 5 步:加 kd,抑制可能的振荡                   ← 定 kd

绝不要上来四个参数一起调------你不知道是哪个参数引起的效果。


3. 调试速查手册:常见问题 → 解决方法

现象 可能原因 排查方法
电机完全不转 IsEnabled()=false 或堵转保护触发 !START 使能;检查 jointsStateFlag
电机转了但位置不对 编码器未校准 触发校准(双键同按或 CAN 0x02)
电机抖动/异响 kp 太大 或 kd 太小 按上面方法调 DCE 参数
跟踪慢半拍 kv 太小 加大 kv
到位后有小偏差 ki 太小 加大 ki
换向时"嘎嘎"响 kd 太小 或 齿隙未消除 加大 kd,检查校准是否完成
CAN 通信超时 总线负载太高或硬件故障 用示波器看 CAN_H/CAN_L 波形
OLED 显示 FPS 很低 某线程栈溢出或死循环 检查 FreeRTOS 各线程栈使用量
机械臂到不了某些位置 关节限位或 IK 无解 #GETJPOS 看当前角度,和 angleLimitMin/Max 对比

4. 完整的启动→运动→停止→关机流程

复制代码
1. 上电
   ├─ 核心 F407 启动 FreeRTOS → 初始化通信 → 等待 Fibre 协议树
   ├─ 6 个关节 F103 各自启动 → 读 DIP 开关确定 CAN ID → 加载 EEPROM → 使能 TIM4 20kHz
   └─ F103 进入闭环循环(未使能状态,只读编码器不回传电流)

2. 使能
   ├─ 发 `!START` → SetEnable(true) → CAN 0x01(id=0广播)
   ├─ 各关节收到 → modeRunning = VELOCITY(或上次的 defaultMode)
   └─ FOC 开始输出电流 → 电机有力(但不转,因为 goalPosition=当前位置)

3. 运动
   ├─ 发 `>30,-20,90,0,0,0`
   ├─ MoveJ → 算同步速度 → MoveJoints → CAN 0x07
   ├─ 关节 PositionTracker → DCE → FOC → 电机转动
   └─ 全部完成 → Respond("ok")

4. 连续运动
   ├─ Interruptable 模式:上位机 100Hz 持续发 > 命令
   ├─ 每次 ParseCommand 更新 targetJoints
   └─ 后台 200Hz ControlLoopFixUpdate 持续 MoveJoints

5. 停止
   ├─ 发 `!STOP` → EmergencyStop → MoveJ(当前位置) → MoveJoints → SetEnable(false)
   └─ 所有关节 Sleep → 无力 → 机械臂可手动推动

6. 关机
   ├─ 发 `!DISABLE` → SetEnable(false)
   ├─ 发 `#REBOOT 0` → 全部关节重启(可选)
   └─ 断电

5. 代码中的"调试钩子"------你可以利用的观察点

观察点 位置 怎么看
OLED 上的 6 关节角度 ThreadOledUpdate 每帧刷新 直接看 OLED 屏幕
OLED 上的末端 XYZABC ThreadOledUpdate 调用 currentPose6D 直接看 OLED 屏幕
OLED 上的 FPS 1000000 / (micros() - t) 正常应该 ≥ 80 FPS
串口 printf _write() 同时发到 USB + UART4 在关节端代码里加 printf()
jointsStateFlag dummy.jointsStateFlag 8 位 0b1111110 = 全完成
CAN 回复的完成标志 data[4] 在 0x23 回复中 OnCanMessage 里加日志
堆剩余空间 xPortGetMinimumEverFreeHeapSize() 启动时输出一次

最实用的调试技巧 :在关节端 CloseLoopControlTick() 里,每 1000 次循环(= 50ms)printf 一次 estPosition, softPosition, goalPosition,用这三个值画图就能看出跟踪效果。


6. Day 7 检查清单

  • 能不看文档,从"上位机发命令"到"电机转动"完整口述整个数据流
  • 能说出 DCE 四参数各自的作用和调参顺序
  • 知道 Tuning 模式下怎么设频率和幅度
  • 知道怎么通过 #GETJPOS 和 OLED 观察跟踪效果
  • 知道常见问题(不动/抖动/慢半拍/异响)的排查方向
  • 能说出 FreeRTOS 8 个线程中哪几个和运动控制相关
  • 知道 jointsStateFlag 什么时候变成 0b1111110
  • 能把 CAN 0x07(发位置+速度)、0x23(回位置+完成)、0x01(使能)三条最常用的命令和代码位置对应上

7. 六天学习总结:你最需要记住的 10 个文件

按学习优先级排序:

优先级 文件 为什么重要
★★★ motor.cpp (F103) 整个闭环控制的大脑------CloseLoopControlTick()
★★★ dummy_robot.cpp (F407) MoveJ/MoveL/ParseCommand------多轴协调核心
★★★ interface_can.cpp (F103) CAN 命令全集------关节和核心的通信协议
★★ 6dof_kinematic.cpp (F407) FK + IK------运动学数学核心
★★ motion_planner.cpp (F103) 5 种 Tracker------运动平滑的关键
★★ tb67h450_base.cpp (F103) FOC 电流矢量输出------硬件驱动
★★ main.cpp (F407 UserApp) FreeRTOS 线程创建------系统启动入口
communication.cpp (F407) 通信初始化------USB/UART/CAN 全部在此启动
encoder_calibrator_base.cpp (F103) 编码器校准------系统精度的基础
can.c (F103 Core) CAN 硬件配置 + 接收中断------通信底层

8. 结语

六天的精读计划到此结束。总结一句话:这个项目的精髓在于分布式 FOC 闭环(关节端 20kHz)+ CAN 实时通信 + 6-DOF 运动学协调(核心端 200Hz) 三层架构。把这三层的接口和职责边界理解清楚,你就掌握了这个机械臂固件的全部核心思想。

相关推荐
知识分享小能手1 小时前
R语言入门学习教程,从入门到精通,R语言获取数据 (8)
开发语言·学习·r语言
sensen_kiss1 小时前
CAN302 Technologies for E-Commerce 电子商务技术 Pt.8 网络安全(Secure the Web)
网络·学习·安全·web安全
通信小呆呆1 小时前
注意力机制用于信号同步:从匹配滤波到可学习对齐
人工智能·学习·机器学习·信息与通信
YangYang9YangYan2 小时前
2026运营岗位学习数据分析对于提升个人能力的价值
学习·数据挖掘·数据分析
吃好睡好便好2 小时前
在Matlab中绘制抛物三维曲面图
开发语言·人工智能·学习·算法·matlab·信息可视化
Bechamz2 小时前
大数据开发学习Day33
大数据·学习
星夜夏空992 小时前
STM32单片机学习(12)——串口通信相关概念
stm32·单片机·学习
HSunR2 小时前
神经网络 从函数到transformer学习笔记
神经网络·学习·transformer
袁小皮皮不皮3 小时前
HCIP-BFD 学习笔记
运维·服务器·网络·笔记·网络协议·学习·智能路由器