C#编码器实战:通过A相B相精准计算里程的工程实现

1次阅读
没有评论

共计 2521 个字符,预计需要花费 7 分钟才能阅读完成。

image.webp

真实场景痛点

去年调试 AGV 小车时,我们遇到过因里程累计误差导致的撞库事故:编码器在 3 米行程内漏计了 17 个脉冲,最终定位偏差达到 8 厘米。更严重的是在数控机床进给系统中,类似的误差会导致加工件批量报废。

C# 编码器实战:通过 A 相 B 相精准计算里程的工程实现

信号采集方案对比

查询式采样(轮询)

  • 优点:实现简单,适合低速场景(<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

多电机干扰解决方案

  1. 物理层:磁环滤波器(TDK ZCAT2032-0930)
  2. 软件层:自适应阈值算法
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 存储策略

采用写平衡算法延长存储器寿命:

  1. 每次写入变换地址偏移量
  2. 使用 CRC32 校验数据完整性
  3. 关键参数备份三份
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 范围内。关键收获是:硬件消抖不可省略,而软件算法要留足安全余量。

正文完
 0
评论(没有评论)