4. 关节编码器:增量式编码器、式编码器、关节角度读取与校准
四足机器人的每条腿,说白了就是一个多关节机械臂。要让机器人站稳、走路、跳跃,首先得知道每个关节转到了什么位置。这个任务,就交给编码器了。
我刚开始做四足机器人时,觉得编码器不就是测个角度嘛,能有多复杂?结果第一次让机器人站起来,腿抖得像筛糠一样。查了半天,发现是编码器零点没校准。嗯,从那以后我再也不敢小看这个环节了。
4.1 编码器的两种主流方案
关节编码器分两大类:增量式和式。它们各有各的脾气,选型时得想清楚。
| 特性 | 增量式编码器 | 式编码器 |
|---|---|---|
| 输出信号 | 脉冲序列(A、B、Z相) | 数字编码(二进制、SSI、SPI) |
| 断电记忆 | 不记忆位置 | 记忆位置 |
| 分辨率 | 取决于线数,可做很高 | 取决于位数,通常12-20位 |
| 上电行为 | 需要回零操作 | 直接读取当前角度 |
| 抗干扰能力 | 脉冲易受干扰 | 数字信号更稳定 |
| 成本 | 较低 | 较高 |
增量式编码器,我习惯叫它"脉冲计数器"。它输出A、B两路相位差90°的方波,通过判断哪个信号先跳变,就能知道电机是正转还是反转。还有一个Z相脉冲,每转一圈输出一次,用来做零点参考。
式编码器就省心多了。它内部有一圈格雷码盘,每个位置对应唯一的编码值。上电就能告诉你关节在什么角度,不需要回零。我在项目里用过BISS-C协议的式编码器,16位分辨率,一圈65536个位置,精度完全够用。
我的建议:如果做科研样机,预算有限,增量式编码器配合霍尔传感器做上电寻零,性价比很高。如果是量产产品,或者对安全性要求高(比如人形机器人),直接上式编码器,省掉很多麻烦。
4.2 关节角度读取实战
不管用哪种编码器,最终都要把原始信号换算成关节角度。这里我以STM32平台为例,讲讲具体怎么读。
4.2.1 增量式编码器读取
增量式编码器通常接在定时器的编码器模式引脚上。STM32的定时器自带硬件解码,不用自己写逻辑判断。
// 初始化定时器为编码器模式
void Encoder_Init(TIM_TypeDef* TIMx) {
TIM_EncoderInterfaceConfig(TIMx, TIM_ENCODERMODE_TI12,
TIM_ICPOLARITY_RISING, TIM_ICPOLARITY_RISING);
TIMx->ARR = 0xFFFF; // 最大计数值
TIM_Cmd(TIMx, ENABLE);
}
// 读取当前脉冲计数值
int16_t Encoder_GetCount(TIM_TypeDef* TIMx) {
return (int16_t)TIM_GetCounter(TIMx);
}
// 换算为角度(假设编码器线数为500线,4倍频后每圈2000脉冲)
float Encoder_ToAngle(int16_t pulse_count) {
float angle = (float)pulse_count / 2000.0f * 360.0f;
return angle;
}
这里有个坑:计数值会溢出。定时器是16位的,最大65535。如果电机一直朝一个方向转,计数值会从65535翻到0,角度就跳变了。我一般用有符号的int16_t来读,这样溢出时自动变成负数,配合外部圈数计数器就能解决。
我曾经踩过的坑:增量式编码器上电时计数值是随机的。如果不做回零,机器人一上电就以为关节在0度,直接给一个很大的控制量,轻则电机过载,重则扫齿。所以,增量式编码器必须配合回零流程。
4.2.2 式编码器读取
式编码器一般通过SPI或SSI接口读取。以AS5048A为例,14位分辨率,SPI通信。
// 读取AS5048A角度值(14位)
uint16_t AS5048A_ReadAngle(void) {
uint16_t raw_value;
// 发送0xFFFF读取命令
HAL_SPI_TransmitReceive(&hspi1, (uint8_t*)0xFFFF, (uint8_t*)&raw_value, 2, 100);
// 解析数据,去掉奇偶校验位
raw_value = (raw_value >> 2) & 0x3FFF;
return raw_value;
}
// 换算为角度
float AS5048A_ToDegree(uint16_t raw) {
float angle = (float)raw / 16384.0f * 360.0f;
return angle;
}
式编码器读取时要注意时序。SPI时钟频率不能太高,否则数据容易出错。我一般用1MHz,稳定可靠。
4.3 关节角度校准——让机器人认识"零位"
校准是编码器应用中最容易被忽视、但最关键的环节。你想想看,编码器装在电机轴上,但电机轴和关节输出轴之间还有减速器、连杆。编码器读到的角度,和关节实际角度之间,存在一个固定的偏移量。
校准的目的,就是找到这个偏移量,把它补偿掉。
4.3.1 机械零位标定
我常用的方法很简单:把机器人每条腿手动掰到标准站立姿态,然后记录此时每个编码器的读数,作为零位偏移。
// 零位偏移结构体
typedef struct {
float hip_offset; // 髋关节零位偏移(度)
float thigh_offset; // 大腿关节零位偏移
float calf_offset; // 小腿关节零位偏移
} JointOffset_t;
JointOffset_t joint_offset[4]; // 四条腿
// 标定函数:在标准姿态下调用一次
void CalibrateZeroPosition(void) {
for(int i = 0; i < 4; i++) {
joint_offset[i].hip_offset = ReadHipAngle(i);
joint_offset[i].thigh_offset = ReadThighAngle(i);
joint_offset[i].calf_offset = ReadCalfAngle(i);
}
// 保存到Flash
SaveOffsetToFlash(joint_offset);
}
// 实际使用时减去偏移
float GetActualAngle(int leg_id, JointType_t joint) {
float raw_angle = ReadRawAngle(leg_id, joint);
float offset = joint_offset[leg_id].hip_offset; // 举例
return raw_angle - offset;
}
小技巧:标定时可以用一个水平仪或者角度尺,确保每条腿的关节真的在标准位置。我见过有人凭感觉掰,结果四条腿零位不一致,机器人走起来一瘸一拐的。
4.3.2 软件滤波与异常处理
编码器读数偶尔会跳变。尤其是增量式编码器,线缆屏蔽不好时,一个脉冲干扰就能让角度突变几度。机器人控制器如果直接拿这个突变值去算力矩,关节会猛地抖一下。
我一般加一个简单的限幅滤波:
// 限幅滤波
float AngleFilter(float current_angle, float last_angle, float max_delta) {
float delta = current_angle - last_angle;
if(delta > max_delta) {
return last_angle + max_delta;
} else if(delta < -max_delta) {
return last_angle - max_delta;
} else {
return current_angle;
}
}
// 使用示例
float last_angle = 0.0f;
float current_angle = ReadEncoder();
float filtered_angle = AngleFilter(current_angle, last_angle, 5.0f); // 每周期最大变化5度
last_angle = filtered_angle;
这个滤波器很简单,但很实用。最大变化量要根据机器人的运动速度来设。四足机器人跑步时,关节角速度可能达到每秒几百甚至上千度,限幅值设得太小反而会限制正常运动。
4.4 知识结构总览
下面这张图把编码器相关的核心逻辑串起来了,方便你对照理解。
4.5 避坑指南与经验总结
做了这么多年机器人,编码器相关的坑我踩过不少。挑几个典型的说说:
- 线缆屏蔽:增量式编码器的脉冲信号很容易被电机驱动器的PWM干扰。我建议用双绞屏蔽线,屏蔽层单端接地。曾经有一次,我偷懒用了普通排线,结果机器人一走路,编码器读数就乱跳,排查了两天才找到原因。
- 上电顺序:式编码器虽然不用回零,但上电瞬间的SPI通信可能出错。我习惯在控制器初始化完成后,延时50ms再读第一次角度,确保编码器内部已经稳定。
- 机械安装:编码器与电机轴的连接要牢固。我用过顶丝固定的编码器,振动几次就松了,角度读数开始漂移。后来改用弹簧夹紧式,再没出过问题。
- 多圈处理:如果关节可以连续旋转(比如髋关节),增量式编码器需要记录圈数。我一般用一个32位变量,高16位记圈数,低16位记当前计数值,这样能覆盖±32767圈的范围。
核心要点:编码器是机器人的"眼睛"。眼睛看不清,腿脚再灵活也没用。选型时想清楚要不要断电记忆,调试时老老实实做零位标定,运行时加一层滤波保护。这三步做到位,关节角度数据基本就稳了。