4. 信号采集与预处理:编码器信号的“净化”与“倍增”
各位同学,咱们接着聊。电梯平层精度要想做到毫米级,编码器信号的质量是根基。你想想看,如果信号本身都是抖的、毛刺多的,后面算法再牛也白搭。这一节,我就把编码器信号采集、抗抖动滤波和倍频技术这些实战经验,掰开了揉碎了讲给你听。
4.1 编码器信号(A/B/Z相)的采集要点
编码器输出三路信号:A相、B相和Z相。A和B相位差90度,用来判断方向和速度。Z相是零位脉冲,每转一圈出一个,用于绝对位置校准。
采集时,我个人习惯注意三点:
- 电气隔离:编码器线缆长,容易引入共模干扰。我建议用高速光耦或磁耦隔离,比如6N137或ISO7240。不隔离的话,干扰可能直接打坏MCU的IO口,我吃过这个亏。
- 上拉电阻:多数编码器是开集电极输出,必须加上拉电阻。阻值选4.7kΩ到10kΩ,具体看线长和速率。线长了选小一点,不然上升沿太缓。
- 硬件去抖:在进入MCU之前,加一个RC低通滤波,时间常数约1~10μs。这能滤掉大部分高频毛刺,减轻软件负担。
小提示: 我曾在现场遇到过编码器信号偶尔丢步,排查半天发现是接插件松动。后来我要求所有编码器接头必须用带锁扣的型号,从此再没出过类似问题。
4.2 抗抖动滤波:中值滤波 vs 卡尔曼滤波
信号采集进来,抖动是难免的。电梯运行时的振动、电磁干扰,都会让脉冲边沿出现毛刺。怎么处理?我常用的两种方法:中值滤波和卡尔曼滤波。
4.2.1 中值滤波——简单粗暴但有效
中值滤波的原理很简单:连续采N个值,排序后取中间那个。比如连续采5个位置值,去掉最大最小,取中间3个的平均。这能有效抑制脉冲性的毛刺。
代码示例(C语言):
// 中值滤波函数,窗口大小5
uint16_t median_filter(uint16_t new_value) {
static uint16_t buffer[5] = {0};
static uint8_t index = 0;
uint16_t temp[5];
uint8_t i, j;
buffer[index++] = new_value;
if (index >= 5) index = 0;
// 拷贝到临时数组并排序
for (i = 0; i < 5; i++) temp[i] = buffer[i];
for (i = 0; i < 4; i++) {
for (j = i+1; j < 5; j++) {
if (temp[i] > temp[j]) {
uint16_t t = temp[i];
temp[i] = temp[j];
temp[j] = t;
}
}
}
return temp[2]; // 取中值
}
嗯,这里要注意:窗口大小选3或5就够了。选太大,响应会变慢,电梯急停时位置更新跟不上。
4.2.2 卡尔曼滤波——更平滑,但计算量大
卡尔曼滤波适合信号有随机噪声的场景。它通过预测和更新两步,不断修正估计值。说白了,就是“猜一个值,然后用测量值去修正”。
在电梯平层控制中,我一般只用一维卡尔曼滤波,状态量就是位置。公式不复杂:
// 一维卡尔曼滤波
typedef struct {
float x; // 估计值
float p; // 估计误差协方差
float q; // 过程噪声
float r; // 测量噪声
} kalman_1d_t;
float kalman_update(kalman_1d_t *kf, float z) {
// 预测
float p_pred = kf->p + kf->q;
// 卡尔曼增益
float k = p_pred / (p_pred + kf->r);
// 更新
kf->x = kf->x + k * (z - kf->x);
kf->p = (1 - k) * p_pred;
return kf->x;
}
实战对比: 中值滤波适合毛刺明显的场景,卡尔曼滤波适合噪声平稳的场景。我个人的经验是:电梯低速运行时用中值滤波,高速运行时用卡尔曼滤波。为什么?高速时毛刺相对少,但随机噪声多,卡尔曼更合适。
避坑指南: 我曾经在调试时发现卡尔曼滤波发散,位置越算越偏。后来发现是过程噪声q设得太小,导致滤波器过于相信预测值,不信任测量值。调参时,q和r的比例很关键,建议从1:100开始试。
4.3 信号倍频技术(4倍频)
编码器的物理分辨率有限,比如2500线的编码器,一圈才2500个脉冲。但电梯平层要求0.1mm精度,怎么办?用倍频技术。
4倍频的原理很简单:利用A相和B相的上升沿和下降沿,每个周期内可以检测到4个边沿变化。这样,分辨率就提高了4倍。2500线的编码器,4倍频后变成10000脉冲/圈。
实现方式:
- 硬件方式:用FPGA或CPLD,直接对A/B相边沿计数。速度快,不占CPU资源。
- 软件方式:用MCU的外部中断,捕获A/B相的边沿。我建议用定时器输入捕获模式,效率更高。
软件4倍频代码思路:
// 假设A相接INT0,B相接INT1
// 每次中断记录当前A/B电平,判断方向并计数
volatile int32_t encoder_count = 0;
void EXTI0_IRQHandler(void) { // A相中断
uint8_t a = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
uint8_t b = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1);
if (a == b) {
encoder_count++; // 正转
} else {
encoder_count--; // 反转
}
EXTI_ClearITPendingBit(EXTI_Line0);
}
void EXTI1_IRQHandler(void) { // B相中断
uint8_t a = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_0);
uint8_t b = GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1);
if (a != b) {
encoder_count++; // 正转
} else {
encoder_count--; // 反转
}
EXTI_ClearITPendingBit(EXTI_Line1);
}
注意: 软件倍频时,中断响应时间必须小于编码器最小脉冲宽度的1/4。比如编码器最高频率100kHz,脉冲宽度10μs,那么中断响应必须在2.5μs内完成。否则会丢脉冲。我一般用主频72MHz以上的MCU,中断函数里只做最精简的操作。
好了,信号采集和预处理就讲到这里。下一节我们聊聊如何用这些干净的信号做速度估算和位置闭环。记住一句话:信号质量决定控制下限,滤波和倍频是基本功,别偷懒。