Files
origincar_controller/doc/ackermann_steering_calibration.md
cyy_mac 93ca37e36c 完善阿克曼控制与高速串口遥测
- 校正舵机中位、转向符号和阿克曼后轮差速模型\n- 增加航向角速度辅助及遥控通道调试开关\n- 将速度环提升至 200Hz,并按实际 dt 计算 PI 积分\n- 将 IMU 启动校准缩短为 2 秒\n- 为 USART3 增加 DMA 发送和 MCU 采样时间戳
2026-08-12 18:51:48 +08:00

18 KiB
Raw Blame History

阿克曼转向标定与拟合说明

本文记录阿克曼小车「(Vx, Vz) → 舵机 PWM + 左右后轮速度」控制律的标定数据、 拟合方法、单位约定,三种控制模式(标定运动学 / 直接映射 / 横摆闭环),以及 串口通信200 Hz DMA 发送、921600 波特率)。对应代码:

  • 控制律:BALANCE/balance.cAkm_Car 分支, Akm_Curvature_To_Servo()(拟合)与 Akm_Norm_To_Servo()(直接满行程映射)。
  • 开关宏:BALANCE/robot_select_init.h;舵机直行点 SERVO_INITHARDWARE/motor.h
  • 串口:HARDWARE/usartx.cdata_task / USART3_SEND / uart3_dma_tx_init 波特率在 BALANCE/system.csystemInit

1. 硬件与坐标约定

  • 后驱阿克曼:MOTOR_A = 左后轮,MOTOR_B = 右后轮前轮由舵机TIM12 CCR2转向。
  • 输入语义(上位机下发):
    • Vx = 后轴中心线速度,单位 m/s。
    • Vz = 绕转弯中心的旋转角速度 wz单位 rad/s。
  • 符号约定:
    • 上位机 Vz 遵循 ROS 约定:Vz > 0 = 逆时针 = 左转
    • 标定表 / 舵机拟合使用相反符号:曲率 κ > 0 = 右转
    • 代码中 kappa_fit = -Vz/Vx,使 Vz > 0 得到 kappa_fit < 0(左转), 与拟合域一致。

2. 标定方法(手推法)

电机失能、舵机使能,手动推动小车走出稳定圆弧,对每个舵机 PWM 记录:

  • y 向半截距弧对应的纵向半截距cm
  • x 向半弓高(弦的矢高 / 半弓高cm
  • 由几何反推后轴中心转弯半径 Rcm1/R

x 向半弓高的符号用于判定左右PWM > ~1670 为右转(κ 取正), 低于该点为左转(κ 取负)。

三个"中位"值(易混,务必区分)

名称 含义 代码位置
遥控 CH1 中位 1500 遥控摇杆物理中点 AKM_REMOTER_CH1_MIDbalance.c
舵机机械中位 1600 舵机行程几何中点 (MIN+MAX)/2 —(不再直接使用)
直行点 SERVO_INIT 1670 实测 1/R≈0、车真正走直线的点 SERVO_INITmotor.h:52

关键:所有控制律的舵机中位都对齐到 SERVO_INIT = 1670(真正的直行点), 而非机械中位 1600。开机上电、Mode 1 / Mode 2 的零位、CH1 覆盖都以它为基准:

  • CH1 覆盖时舵机 = Remoter_Ch1 + (SERVO_INIT - AKM_REMOTER_CH1_MID),即整体 平移 1670 - 1500 = +170,使摇杆居中 = 车轮回正。
  • SERVO_INIT 一处,下游所有路径自动对齐。

原始标定数据

servo(pwm) y半截距/cm x半弓高/cm R/cm 1/R (1/cm)
2000 34 34 34.00 0.029412
1900 52.5 49.5 52.59 0.019015
1800 90 60 97.50 0.010256
1700 167.2 60 262.97 0.003803
1670 120 -1 7200.5 0.000139
1600 120 -23 324.54 0.003081
1500 89 -60 96.01 0.010416
1400 61 -60 61.01 0.016391
1300 43 -44 43.01 0.023250
1200 35.5 -34.3 35.52 0.028152
1100 30 -28.9 30.02 0.033310

直行点约在 PWM 16701/R ≈ 0x 半弓高 ≈ 0并非舵机机械中位 1600。

3. 单位换算(关键,曾导致 100 倍错误)

标定表 1/R 列以 1/cm 为单位R 用厘米)。而代码里的曲率来自 kappa = wz / Vx(均为 SI单位是 1/m。两者相差 100 倍。

拟合前必须把 R 从 cm 换算为 m再取 kappa = 1/R_m(并带符号):

servo kappa (1/m, +=右)
2000 +2.9412
1900 +1.9015
1800 +1.0256
1700 +0.3803
1670 +0.0139
1600 -0.3081
1500 -1.0416
1400 -1.6391
1300 -2.3250
1200 -2.8152
1100 -3.3310
  • 曲率量程:κ ∈ [3.331, +2.941] 1/m。
  • 最小转弯半径 R_min ≈ 0.30 m/ 0.34 m
  • 代码中 AKM_KAPPA_MAX = 3.331f

4. 拟合方法与结果

servo = f(kappa)kappa 为自变量1/m做多项式最小二乘。 比较一/二/三次并用留一交叉验证LOO评估对未见点的泛化

拟合 全量 max残差 全量 rms LOO max误差 LOO rms
一次 56.8 28.3
二次 12.3 7.6 15.1 10.2
三次 12.6 7.5

结论:二次拟合最优。三次不再改善(过拟合),一次残差过大。

最终系数1/m 单位)

servo = 1656.373 + 140.548 * kappa - 7.654 * kappa^2

代码宏(BALANCE/balance.c

#define AKM_SERVO_C0   1656.373f
#define AKM_SERVO_C1    140.548f
#define AKM_SERVO_C2     (-7.654f)
#define AKM_SERVO_MIN   1100
#define AKM_SERVO_MAX   2000
#define AKM_KAPPA_MAX   3.331f

为什么用拟合而非查表 + 线性插值

  • 数据是手推测得,含测量噪声。查表被迫穿过每个噪声点,两点间直线段会 放大噪声LOO 显示查表 max 误差 23 / rms 14明显差于二次拟合15 / 10
  • 拟合在端点外推时形状正确(曲线两端明显弯),线性插值只能按最后一段 斜率外推,越推越偏。
  • 拟合在节点处平滑,无斜率突变;运行成本仅两次乘加,比查找区间还省。

轴距 L 去哪了

标定表端到端直接测了 servo → R,已把「舵机 PWM → 前轮转角 → R = L/tan(δ)」整条链路及传动比、轮胎侧偏等真实效应吸收进拟合系数。 因此无需再显式写 R = Axle_spacing/tan(δ)(那反而依赖不准的假设传动比)。 后轮差速只用轮距 track 与 κ,本就不含 L。阿克曼几何没有丢而是以更贴合 实车的实测形式嵌入拟合。

5. 控制律Mode 0标定运动学默认

AKM_DIRECT_MAP = 0 时:

kappa_geom = Vz / Vx            (Vx≈0 时置 0阿克曼无前进不能转向)
kappa_fit  = clamp(-kappa_geom, ±AKM_KAPPA_MAX)
Servo      = f(kappa_fit)                          # 二次拟合
MOTOR_A(左) = Vx * (1 + 0.5*track*kappa_fit)       # 后轮差速
MOTOR_B(右) = Vx * (1 - 0.5*track*kappa_fit)

物理特性:舵机角只决定转弯半径 R = 1/κ。

  • 固定舵机角、改 Vx → wz 随之变(走同一圆,快慢不同)。
  • 固定 wz、改 Vx → κ = wz/Vx 变 → 舵机角变(正确行为)。

Vxwz 可行域κ_max ≈ 3.33 /m

|wz| <= Vx * κ_max

Vx (m/s) 可用 wz 范围 (rad/s)
0.2 [0.67, 0.67]
0.3 [1.00, 1.00]
0.5 [1.67, 1.67]
1.0 [3.33, 3.33]

例:固定 wz=0.12,需 Vx ≥ 0.036 m/s 才不被夹紧。之前 AKM_KAPPA_MAX 误设为 0.0334(对应 R≈30 m导致任何速度下都被夹死、改速度舵机不动 已随单位修正解决。

6. 控制律Mode 1直接映射调试用

AKM_DIRECT_MAP = 1 时(脱离阿克曼物理,用于隔离调试舵机/电机):

vz_norm = clamp(Vz / AKM_DIRECT_VZ_FULL, ±1)
Servo   = Akm_Norm_To_Servo(vz_norm)     # 以 SERVO_INIT 为零位的分段满行程映射
MOTOR_A(左) = Vx                          # 无差速
MOTOR_B(右) = Vx
  • Vz 线性铺满整个舵机行程 [AKM_SERVO_MIN, AKM_SERVO_MAX],与速度无关。
  • 零位对齐直行点Akm_Norm_To_ServoSERVO_INIT=1670(而非机械中位 1600为中心左右两侧各自缩放到自己的端点即使中位偏置也能用满全行程
    span = (norm>=0) ? (SERVO_INIT - AKM_SERVO_MIN)   # 左侧行程 1670-1100=570
                     : (AKM_SERVO_MAX - SERVO_INIT)   # 右侧行程 2000-1670=330
    Servo = SERVO_INIT - norm * span
    
  • Vz > 0(左)→ norm>0 → 靠近 AKM_SERVO_MIN左端符号与 ROS 一致。
  • Vx 原样给左右电机,不做曲率/差速运算。
  • 满量程输入由 AKM_DIRECT_VZ_FULL(默认 1.0 rad/s)设定。

AKM_DIRECT_VZ_FULL 的物理含义 = 上位机会发的最大 angular.zVz 到该值时 舵机打满。它只决定"Vz→行程"的比例,不改变峰值转角(峰值由舵机端点 1100/2000 决定,恒为 ~28°左 / 25°右。太小→小 Vz 就饱和、失去比例控制;太大→常用区间 只用到一小段行程、转角偏小。当前定为 1.0Vz=±1.0 打满),把行程摊到 ±1 rad/s 全区间,规划分辨率比 0.5 时翻倍。Vz 在 RX 中断里以 rad/s 到达 usartx.c XYZ_Target_Speed_transition: raw/1000

7. 控制律Mode 2横摆角速度闭环 / 简化扭矩矢量)

AKM_DIRECT_MAP = 0AKM_YAW_ASSIST = 1 时启用。

动机(为什么要有 Mode 2Mode 0 里 κ = Vz/Vx,同一个转向指令 Vz 在 高速时曲率被 Vx 除小,舵机自动回正 —— 这就是"高速转弯打不动"的根源。Mode 2 主动解耦 w 与 v:舵机由 Vz 直接决定(与 Mode 1 一样,不再除以 Vx 所以大 Vz 在任何速度都给出大前轮角;阿克曼只作为后轮差速的前馈参考 再叠加 IMU 横摆角速度 PI 闭环(简化扭矩矢量)。控制链路:

Vz ─(直接满行程映射, 与Vx无关)→ 舵机主转向 Akm_Norm_To_Servo   ← 方向主控,解耦
Vx ───────────────────────────→ 左右轮基速                      ← 驱动主控,解耦
(Vx, κ_cmd) ─(阿克曼几何)→ r_ref ─→ 后轮差速前馈 + IMU PI          ← akm 仅做前馈

计算fit 域 κ>0=右ROS Vz>0=左=CCWr>0=左转):

vz_norm = clamp(Vz / AKM_DIRECT_VZ_FULL, ±1)   # 转向指令,不含 Vx —— 关键解耦点
Servo   = Akm_Norm_To_Servo(vz_norm)          # 与 Mode 1 完全相同的满行程直接映射
                                              # 以 SERVO_INIT 为零位,不走拟合曲线
κ_cmd   = -vz_norm * AKM_KAPPA_MAX             # 仅用于构造下方前馈参考,不驱动舵机

r_ref   = -κ_cmd * Vx                         # 阿克曼几何前馈的期望横摆角速度
r_meas  = AKM_GYRO_Z_SIGN * gyro[2] / AKM_GYRO_Z_TO_RADPS
r_filt += AKM_YAW_IMU_LPF * (r_meas - r_filt)  # 轻度一阶低通

e_r     = r_ref - r_filt
dv_ff   = AKM_YAW_FF_ALPHA * 0.5 * track * r_ref  # 几何前馈
dv_fb   = AKM_YAW_KP * e_r + AKM_YAW_KI * ∫e_r    # PI 反馈(矩形积分, dt=1/200s
dv      = clamp(dv_ff + dv_fb, ±AKM_YAW_MAX_DIFF_RATIO*|Vx|)  # 带抗积分饱和

MOTOR_A(左) = Vx - dv
MOTOR_B(右) = Vx + dv

要点:

  • 解耦是核心vz_norm 只由 Vz 决定,不再 Vz/Vx,所以高速大转向不再被 几何"稀释"。Vx 独立设定驱动基速。
  • 舵机走直接满行程映射,不走拟合Mode 2 的舵机与 Mode 1 / CH1 调试一致, 用 Akm_Norm_To_Servo(vz_norm) 把转向指令线性铺满行程(以 SERVO_INIT 为 零位),不再调用标定拟合 f(κ)。这样"高速转弯打不动"从根上消失——舵机角 只看指令、与速度无关。(早期版本这里错用了 f(κ_cmd),仍隐含耦合,已改正。)
  • 阿克曼降级为纯前馈κ_cmd = -vz_norm*AKM_KAPPA_MAX 只用来构造 r_ref ——"若车按该曲率走出的理想横摆角速度",仅喂给后轮差速前馈;实际横摆由 IMU PI 收敛,可克服前轮几何/打滑带来的偏差。κ_cmd 不参与舵机计算。
  • 退化关系AKM_YAW_FF_ALPHA = 1AKM_YAW_KP = AKM_YAW_KI = 0 时, dv = 0.5*track*r_ref = -0.5*track*κ_cmd*Vx,即纯几何差速。α 是平滑旋钮: 0 = 纯 IMU 反馈1 = 纯几何前馈。
  • 低速冻结|Vx| < AKM_YAW_MIN_SPEED 时清零积分并停用差速(低速横摆角 速度信噪比太差)。
  • 用原始角速度(gyro[2] 去零偏后的 LSB不做航向角积分。

上位机规划用:发布 Vz → 大致前轮转角 δ

Mode 2 里舵机只由 Vz 决定(与 Vx 无关),所以上位机可直接按下表估转角。 换算链路(非线性,因为 PWM→R 那段是标定曲线):

vz_norm = clamp(Vz / AKM_DIRECT_VZ_FULL, ±1)      # 默认 VZ_FULL=1.0 → Vz=±1.0 打满
Servo   = SERVO_INIT - vz_norm * span             # 左 span=570, 右 span=330
R       = 1 / |1/R(Servo)|                         # 1/R 由 §2 标定表(1/cm)插值再×100
δ       = atan(L / R)                              # L = Akm_axlespacing = 0.160 m

用当前宏值(SERVO_INIT=1670, MIN=1100, MAX=2000, VZ_FULL=1.0)、按 §2 标定表 1/R 列插值算出的对照表(δ 为后轴等效前轮转角,正数只表大小,方向见末列):

Vz (rad/s) vz_norm Servo(PWM) R(m) δ(°) 方向
+1.0 +1.0 1100 0.30 28.1
+0.8 +0.8 1214 0.36 23.7
+0.6 +0.6 1328 0.47 18.8
+0.5 +0.5 1385 0.57 15.6
+0.4 +0.4 1442 0.72 12.5
+0.3 +0.3 1499 0.95 9.5
+0.2 +0.2 1556 1.59 5.8
+0.1 +0.1 1613 3.95 2.3
0.0 0.0 1670 0.0
0.1 0.1 1703 2.50 3.7
0.2 0.2 1736 1.63 5.6
0.3 0.3 1769 1.21 7.5
0.4 0.4 1802 0.96 9.5
0.5 0.5 1835 0.75 12.0
0.6 0.6 1868 0.62 14.5
0.8 0.8 1934 0.44 19.8
1.0 1.0 2000 0.34 25.2

要点(上位机规划务必注意):

  • 左右不对称:直行点 1670 偏向右端,左侧行程 570、右侧仅 330所以同样 |Vz| 左转角比右转角略大(满量程 28° 左 vs 25° 右)。这是机械中位偏置导致的, 已被 Akm_Norm_To_Servo 的分段缩放吸收。
  • |Vz| ≥ 1.0 全部饱和到端点28°左 / 25°右再大的 Vz 不会有更大转角。 VZ_FULL 从 0.5 提到 1.0 后,满量程指令 = ±1.0 rad/s同样的舵机行程摊到更宽的 Vz 区间,规划分辨率翻倍(峰值转角不变)。
  • 非线性Vz→δ 不是直线(低 Vz 段每 0.1 约 +3°高 Vz 段趋缓),规划时按表 插值而非线性外推。
  • R 是后轴中心转弯半径,δ = atan(L/R) 是等效前轮转角;实车受轮胎侧偏/打滑 影响,表值为标定静推的近似,动态下 IMU 横摆环会再做修正。

调参顺序(用户建议):

  1. 先标定陀螺零偏(开机静止采样,代码已做)。
  2. 实测确认 gyro[2] 符号:命令左转,确认 gyro[2] > 0;若相反把 AKM_GYRO_Z_SIGN 改为 -1.0f
  3. Kp0.05~0.10)起步,逐步加大到临界前。
  4. 有稳态误差再加一点 Ki
  5. 最后引入前馈 α = 0.2~0.5 减轻 PI 负担。

8. 开关一览(BALANCE/robot_select_init.h

默认 含义
AKM_SERVO_DEBUG_REMOTE_CH1 1 1 = 舵机直接由遥控 CH1 驱动,覆盖所有控制律(板级调试)
AKM_DIRECT_MAP 1 1 = 直接映射调试模式0 = 交给 Mode 0 / Mode 2
AKM_DIRECT_VZ_FULL 1.0f Mode 1 & Mode 2 共用Vz 满行程量程rad/s= 上位机会发的最大 angular.z
AKM_YAW_ASSIST 0 1 = 横摆闭环Mode 2仅在 AKM_DIRECT_MAP=0 时生效
AKM_YAW_KP / AKM_YAW_KI 0.10 / 0.00 横摆角速度误差 PI 增益m/s per rad/s
AKM_YAW_FF_ALPHA 0.00f 几何前馈混合系数0=纯反馈1=纯几何差速=Mode 0
AKM_YAW_MIN_SPEED 0.10f 低于此速度冻结横摆环m/s
AKM_YAW_MAX_DIFF_RATIO 0.35f 差速幅度上限(占
AKM_GYRO_Z_TO_RADPS 3754.9f gyro[2] LSB→rad/sFS ±500dps
AKM_GYRO_Z_SIGN +1.0f IMU +z 与 ROS+=左)符号对齐,实测确认
AKM_YAW_IMU_LPF 0.30f 横摆角速度一阶低通系数0=无滤波)

模式互斥与优先级:

  • AKM_SERVO_DEBUG_REMOTE_CH1 = 1 时,舵机总是由 CH1 覆盖,优先于任何控制 律计算出的舵机值(电机差速仍按所选模式运行)。
  • AKM_DIRECT_MAPAKM_YAW_ASSIST 都作用于整条控制律:AKM_DIRECT_MAP = 1 优先(直接映射);= 0 时再看 AKM_YAW_ASSIST1 = Mode 2 横摆闭环, 0 = Mode 0 标定运动学)。

9. 串口通信ROS ↔ STM32

频率与方向

方向 频率 机制 代码位置
控制环 200 Hz Balance_taskRATE_200_HZ balance.c:350
发送TXSTM32→ROS 200 Hz DMA 非阻塞 usartx.c data_task
接收RXROS→STM32 中断驱动 USART3_IRQHandlerRXNE usartx.c
  • 只保留 USART3ROS。原先 data_task 20 Hz 阻塞式群发 USART1/3/5+CAN 现已删掉 USART1/USART5/CAN 发送,只留 USART3。
  • 帧长 24 字节(SEND_DATA_SIZEVz 在 RX 中断里由 XYZ_Target_Speed_transition 解析:raw/1000 + (raw%1000)*0.001rad/s

为什么改 DMA 非阻塞发送

旧的 usart3_send 是忙等:USART3->DR = data; while((USART3->SR&0x40)==0);data_taskBalance_task 同为优先级 4FreeRTOS 抢占 + 时间片1ms tick。 同优先级下忙等无法被控制环抢占20→200 Hz 会放大抖动、白耗 CPU。改成 DMA 后:

  • USART3_SEND 只触发一次 DMA 传输DMA1 Stream3 / Channel4 = USART3_TX CPU 立即返回shift-out 期间不占用控制环。
  • 上一帧未发完则跳过本周期(DMA_GetCmdStatus != DISABLE 判定),不忙等。
  • 需要把 FWLIB/src/stm32f4xx_dma.c 加入 Makefile原先未编译该驱动

波特率 921600

USART3 波特率 115200 → 921600system.c:129uart3_init(921600))。

  • 一帧 24 字节 = 240 bit8N110 bit/byte的 shift-out 时间:
    • 115200240/115200 ≈ 2.08 ms
    • 921600240/921600 ≈ 0.26 ms(快 8 倍,远低于 5 ms 周期)
  • 带宽占用200 Hz × 24 B = 4800 B/s两档都绰绰有余提速主要是留余量。
  • APB1=42 MHz16 倍过采样分频后实际约 913 k偏差 ~0.9%UART 容忍 <2.5%)。
  • ROS 端必须同步改 921600,否则乱码——这是最易漏的一步。

10. 复现拟合

标定原始脚本与中间产物在 calibration/ 目录。核心步骤:

  1. R(cm) → R(m)kappa = sign / R_msign 由 x 半弓高定,+ 为右)。
  2. (kappa, servo) 做二次最小二乘,得 C0/C1/C2。
  3. 用 LOO 交叉验证确认二次优于查表与三次。
  4. AKM_KAPPA_MAX = max(|kappa|)