共计 2521 个字符,预计需要花费 7 分钟才能阅读完成。
真实场景痛点
去年调试 AGV 小车时,我们遇到过因里程累计误差导致的撞库事故:编码器在 3 米行程内漏计了 17 个脉冲,最终定位偏差达到 8 厘米。更严重的是在数控机床进给系统中,类似的误差会导致加工件批量报废。

信号采集方案对比
查询式采样(轮询)
- 优点:实现简单,适合低速场景(<100Hz)
- 缺点:CPU 占用率随采样频率线性增长,实测在 1kHz 采样时单核占用达 12%
// 基础轮询示例
while (true)
{var a = ReadGPIO(A_PIN);
var b = ReadGPIO(B_PIN);
Thread.Sleep(1); // 1ms 间隔
}
中断式采样
- 优点:零延迟响应,实测 10kHz 时 CPU 占用 <3%
- 缺点:需要硬件支持边沿触发,在树莓派等平台上可能受限
// 中断注册代码
GPIO.AddCallback(A_PIN, Edge.Rising, OnPulse);
核心实现模块
1. 硬件消抖方案
使用 System.Threading.Timer 实现软件消抖(硬件消抖 RC 电路参数:R=10kΩ, C=0.1μF):
record EncoderSignal(bool A, bool B, long Timestamp);
EncoderSignal _lastValid;
Timer _debounceTimer = new(DebounceInterval);
void OnPinChanged()
{_debounceTimer.Change(DebounceInterval, Timeout.Infinite);
}
void OnDebounceElapsed()
{var current = new EncoderSignal(ReadA(), ReadB(), Stopwatch.GetTimestamp());
if (current != _lastValid)
{ProcessSignal(current);
_lastValid = current;
}
}
2. 四倍频解码算法
状态机流程(时间复杂度 O(1)):
stateDiagram
[*] --> State00
State00 --> State10: A 上升
State00 --> State01: B 上升
State10 --> State11: B 上升
State10 --> State00: A 下降
State11 --> State01: A 下降
State11 --> State10: B 下降
State01 --> State11: A 上升
State01 --> State00: B 下降
3. 里程累计与溢出保护
private uint _totalPulses;
private const uint MAX_COUNT = uint.MaxValue - 1_000_000; // 保留安全余量
void AddPulses(int delta)
{if (delta > 0 && _totalPulses > MAX_COUNT - delta)
{OnOverflow(); // 切换到高精度模式或报警
return;
}
_totalPulses = unchecked(_totalPulses + (uint)delta);
}
性能优化实战
CPU 负载测试数据
| 采样间隔 (μs) | 脉冲频率 (Hz) | CPU 占用率 (%) |
|---|---|---|
| 1000 | 500 | 6.2 |
| 200 | 2000 | 18.7 |
| 50 | 5000 | 43.1 |
多电机干扰解决方案
- 物理层:磁环滤波器(TDK ZCAT2032-0930)
- 软件层:自适应阈值算法
float dynamicThreshold = initialThreshold;
void UpdateThreshold(float sample)
{dynamicThreshold = 0.7f * dynamicThreshold + 0.3f * sample;}
避坑指南
机械振动过滤
移动平均窗口法(窗口大小推荐 5 - 7 个周期):
Queue<long> _pulseIntervals = new(7);
bool IsValidPulse(long interval)
{_pulseIntervals.Enqueue(interval);
if (_pulseIntervals.Count > 7) _pulseIntervals.Dequeue();
var avg = _pulseIntervals.Average();
return interval > avg * 0.3; // 排除短于 30% 平均值的脉冲
}
EEPROM 存储策略
采用写平衡算法延长存储器寿命:
- 每次写入变换地址偏移量
- 使用 CRC32 校验数据完整性
- 关键参数备份三份
void SavePosition(uint position)
{var block = new Memory<byte>(new byte[8]);
BinaryPrimitives.WriteUInt32LittleEndian(block.Span, position);
var crc = Crc32Algorithm.Compute(block.Span[..4]);
BinaryPrimitives.WriteUInt32LittleEndian(block.Span[4..], crc);
_currentSlot = (_currentSlot + 1) % 3;
EEPROM.Write(_baseAddress + _currentSlot * 8, block);
}
扩展思考
当引入 Z 相信号时,需要:
1. 增加归零中断服务程序
2. 设计位置补偿算法(考虑归零时的机械回差)
3. 在 EEPROM 中存储多圈计数
void OnZPhase()
{
_multiTurnCounter = _totalPulses / PULSES_PER_REV;
_totalPulses %= PULSES_PER_REV;
SaveMultiTurnData();}
异常处理要点
try
{var signal = await ReadEncodersAsync(TimeSpan.FromMilliseconds(100));
}
catch (TimeoutException)
{_logger.Warning("信号线断路或编码器故障");
EnterSafeMode();}
通过这套方案,我们将某生产线 AGV 的里程误差控制在±2mm/10m 范围内。关键收获是:硬件消抖不可省略,而软件算法要留足安全余量。
正文完
