3. 基础数学工具回顾:向量与矩阵运算、三角函数、四元数与欧拉角基础、Sincos查表法优化

做运动控制,说白了就是跟数学打交道。很多同学一上来就啃四元数、旋转矩阵,结果代码跑起来全是坑。我个人习惯是先把基础工具磨利索,再上战场。这一章咱们就把那些绕不开的数学工具过一遍,重点放在“怎么用”和“别踩什么坑”上。

本章核心脉络:从向量/矩阵运算出发,建立空间描述基础;通过三角函数与欧拉角理解旋转直观含义;引入四元数解决万向锁问题;最后用查表法把三角函数算得又快又准。四步走,搞定运动学解算的数学地基。

运动学解算数学工具体系 数学基础工具 向量与矩阵运算 三角函数 四元数与欧拉角 Sincos查表法 坐标变换 / 雅可比 角度解算 / 波形生成 姿态插值 / 无万向锁 实时性优化 / 降功耗 运动学解算 + STM32 硬件联调 每个模块都是独立工具,但在运动学解算中需要组合使用。先理解单个,再串联成系统。

3.1 向量与矩阵运算:空间描述的基石

机械臂的每个关节位置、速度、力,本质上都是向量。而从一个坐标系到另一个坐标系的变换,就是矩阵乘法。我在调试六轴机器人时,80% 的 bug 都出在矩阵乘法的顺序上——左乘还是右乘,结果天差地别。

3.1.1 向量基础运算

  • 点积(内积): 结果是一个标量,用于计算投影、夹角。公式:a·b = |a||b|cosθ。在运动学里,判断两个向量是否垂直(点积为0)很常用。
  • 叉积(外积): 结果是一个向量,垂直于原两向量构成的平面。用于计算力矩、角速度方向。注意叉积不满足交换律:a×b = -(b×a)。
  • 模长与归一化: 单位向量在方向控制中至关重要。归一化时记得判零,否则除零异常会让你的电机原地起飞。

我的习惯:在 STM32 上做向量运算时,尽量用 float32 而不是 double。Cortex-M4 有 FPU,单精度运算快一倍。精度够用就行,别浪费周期。

3.1.2 矩阵运算与旋转矩阵

旋转矩阵是 3x3 的正交矩阵,行列式为 +1。说白了,它描述了一个坐标系相对于另一个坐标系的朝向。

运算类型 数学表达 运动学应用场景
矩阵乘法 C = A × B 连续坐标变换(链式法则)
矩阵转置 AT 旋转矩阵的逆(正交矩阵特性)
矩阵求逆 A-1 从工具坐标系回到基坐标系
齐次变换矩阵 4x4 矩阵 同时描述旋转 + 平移(DH 参数法)

我曾经踩过的坑:在 STM32 上用 arm_mat_mult_f32 做矩阵乘法时,输入矩阵的维度必须严格对齐。有一次我把 3x4 和 4x3 搞反了,结果算出来的位置直接飞到奇异点,差点撞坏夹具。一定要先打印维度检查。

3.2 三角函数:角度解算的命根子

运动学正解和逆解里,sin、cos、atan2 几乎是每行代码都在用。尤其是 atan2(y, x),它比 atan(y/x) 好得多——能自动处理象限,避免除零。

3.2.1 常用三角函数关系

  • sin²θ + cos²θ = 1: 用于归一化校验,或者从 sin 求 cos 时避免开方误差。
  • 和差化积: 在傅里叶变换、滤波器设计里经常用到,但运动学里用得少,了解即可。
  • atan2 的返回值范围: (-π, π]。注意和 atan 的 (-π/2, π/2) 区别开。我在做关节角度限位判断时,就因为用错了 atan 导致角度越界没检测到。

3.2.2 角度单位与归一化

STM32 的数学库默认用弧度。但很多编码器输出的是脉冲数或角度值。我建议在接口层统一转成弧度,内部运算全用弧度,只在最后输出给电机时再转回去。这样可以减少单位混淆的 bug。

角度归一化小技巧:把任意角度映射到 (-π, π] 区间:
angle = fmod(angle + M_PI, 2 * M_PI) - M_PI;
注意 fmod 对负数取模的结果在不同编译器下有差异,建议自己写一个归一化函数。

3.3 四元数与欧拉角基础:绕开万向锁

欧拉角直观,但有个致命问题——万向锁。当两个旋转轴重合时,会丢失一个自由度。我在做云台控制时就遇到过:俯仰到 90° 时,航向和横滚突然乱跳。后来换成四元数,问题立刻解决。

3.3.1 欧拉角的三种常用顺序

顺序 表示 典型应用
ZYX 偏航 → 俯仰 → 横滚 无人机、机械臂末端姿态
ZYZ 偏航 → 俯仰 → 偏航 某些工业机器人腕部
XYZ 横滚 → 俯仰 → 偏航 IMU 姿态解算(常用)

你想想看,如果只用欧拉角做插值,中间姿态可能会绕远路。四元数就没有这个问题——它用四个数表示旋转,本质是在四维单位球面上做最短路径插值(Slerp)。

3.3.2 四元数核心公式

一个四元数 q = w + xi + yj + zk,其中 w 是实部,x、y、z 是虚部。单位四元数满足 w² + x² + y² + z² = 1。

  • 旋转向量转四元数: 绕单位轴 u 旋转 θ 角:q = [cos(θ/2), u·sin(θ/2)]
  • 四元数乘法: 对应旋转的复合。注意顺序:q1 * q2 表示先做 q2 旋转,再做 q1 旋转。
  • 四元数转欧拉角: 公式略复杂,但网上有现成代码。我建议直接复制经过验证的版本,别自己推导——我试过,容易错。

避坑指南:四元数乘法不满足交换律。在 STM32 上实现时,一定要确认你的库用的是左乘还是右乘约定。我曾经因为搞反了顺序,让机械臂画了一个完美的“8”字——本来应该画直线的。

3.4 Sincos查表法优化:把三角函数跑得更快

在 STM32 上调用 sinf() 和 cosf() 虽然方便,但每次调用要几十个微秒。如果你在 1kHz 的控制循环里算 6 个关节的 sin/cos,光三角函数就吃掉 30% 的 CPU。查表法可以把时间压到几微秒。

3.4.1 查表法原理

说白了,就是提前算好一批 sin 值存到数组里,运行时用角度查索引。比如把 0~2π 分成 1024 份,每份对应的 sin 值提前算好。查表时做一次线性插值,精度可以做到 0.1% 以内。

// 1024 点 sin 查表,角度范围 [0, 2π)
#define TABLE_SIZE 1024
float sin_table[TABLE_SIZE];

void init_sin_table(void) {
    for (int i = 0; i < TABLE_SIZE; i++) {
        sin_table[i] = sinf(2.0f * M_PI * i / TABLE_SIZE);
    }
}

float fast_sin(float angle) {
    // 将角度归一化到 [0, 2π)
    angle = fmodf(angle, 2.0f * M_PI);
    if (angle < 0) angle += 2.0f * M_PI;

    float idx = angle * TABLE_SIZE / (2.0f * M_PI);
    int i0 = (int)idx;
    int i1 = (i0 + 1) % TABLE_SIZE;
    float frac = idx - i0;

    // 线性插值
    return sin_table[i0] + frac * (sin_table[i1] - sin_table[i0]);
}

3.4.2 精度与性能权衡

表大小 内存占用 最大误差(近似) 单次调用时间(STM32F4 @168MHz)
256 1 KB 0.6% ~1.2 μs
1024 4 KB 0.04% ~1.5 μs
4096 16 KB 0.002% ~2.0 μs
标准 sinf() — 双精度 ~25 μs

注意:查表法虽然快,但如果你频繁修改表(比如动态调整精度),要注意缓存一致性。在 STM32 上,表最好放在 const 段或 DTCM RAM 里,避免被 Cache 刷掉。我遇到过查表结果偶尔跳变,最后发现是表被放在了慢速内存区。

3.4.3 同时计算 sin 和 cos 的技巧

很多算法需要同时用 sinθ 和 cosθ。如果分别查两次表,浪费了一半时间。可以利用三角恒等式:cosθ = sin(θ + π/2)。或者更直接——把 sin 和 cos 放在一个结构体里,一次查表算出两个值。

typedef struct {
    float sin_val;
    float cos_val;
} sincos_t;

sincos_t fast_sincos(float angle) {
    sincos_t result;
    // 先算 sin
    result.sin_val = fast_sin(angle);
    // cos 用查表 + 相位偏移
    result.cos_val = fast_sin(angle + M_PI_2);
    return result;
}

嗯,这里要注意:angle + M_PI_2 可能会超出表范围,记得在 fast_sin 里做归一化。我一般把归一化放在查表函数内部,这样调用方不用操心。

总结一下:向量和矩阵是描述空间的语言,三角函数是解算角度的工具,四元数帮我们绕开欧拉角的坑,查表法让三角函数跑得飞快。这四个工具组合起来,就能搭建运动学解算的数学骨架。下一节我们就要用这些工具去解算正逆运动学了——到时候你会发现,数学基础越扎实,调试越轻松。