3、CSMA/CD原理在RS485上的应用:载波侦听、冲突检测、随机退避算法

各位同学,咱们今天聊点硬核的。CSMA/CD,这名字听着挺唬人,其实就是「先听再说,撞了就退」。

我在做多主站项目时,最头疼的就是总线冲突。两台设备同时发数据,总线上一片混乱。后来我琢磨着,能不能把以太网那套CSMA/CD搬过来用?嗯,还真行。

3.1 载波侦听:先听听总线有没有人说话

说白了,就是发数据前先看看总线忙不忙。RS485是半双工的,同一时间只能一个人说话。

怎么侦听?我习惯用GPIO检测收发器的RE/DE引脚状态。但更靠谱的办法是:检测总线上的电平变化

核心思路:如果总线上有数据,差分电压A-B就不会是0V。用ADC或者比较器都能判断。

我在项目中遇到过一个问题:有些廉价收发器在空闲时,A-B电压会飘。后来我加了个滞回比较器,才稳定下来。

// 伪代码:载波侦听
bool carrier_sense() {
    // 读取差分电压
    int diff = read_ab_voltage();
    // 阈值判断,我一般设200mV
    if (abs(diff) > 200) {
        return true;  // 总线忙
    }
    return false;     // 总线空闲
}

小技巧:侦听时间不能太短。我建议至少持续侦听3个位时间,防止误判。

3.2 冲突检测:怎么知道撞车了?

你想想看,如果两个站同时侦听到总线空闲,然后同时发数据,那就撞了。

RS485上怎么检测冲突?我总结了三种方法:

方法 原理 我推荐度
电平对比法 发数据时,同时读总线。如果读到的不等于发出的,说明有冲突 ⭐⭐⭐⭐⭐
CRC校验法 接收方校验CRC,如果错误,说明可能冲突了 ⭐⭐⭐
超时检测法 发完数据后,等应答。超时没收到,就当冲突处理 ⭐⭐

我个人最常用电平对比法。为什么?因为它实时性最好。发一个bit,立刻读回来对比。不一致?好,撞了。

注意:电平对比法对硬件有要求。收发器的驱动能力和接收延迟要匹配。我曾经用某款国产芯片,驱动上升沿太慢,导致误判。后来换了TI的SN65HVD3082才解决。

// 冲突检测示例
bool detect_collision(uint8_t tx_byte) {
    uint8_t rx_byte = read_byte_from_bus();
    if (rx_byte != tx_byte) {
        // 冲突!我遇到过这种情况,多半是另一个站也在发
        return true;
    }
    return false;
}

3.3 随机退避算法:撞了之后怎么办?

检测到冲突,不能马上重发。否则两个站又会同时发,继续撞。这就是死锁。

解决办法?随机退避。每个站等一个随机时间,再尝试重发。

我常用的退避算法是这样的:

  1. 初始退避窗口:比如0~10ms
  2. 随机等待:在这个窗口内随机选一个时间
  3. 指数增长:如果又冲突了,窗口翻倍。比如第二次0~20ms,第三次0~40ms
  4. 最大限制:窗口不能无限大,我一般设上限500ms

关键点:退避时间必须是随机的。如果所有站都用固定算法,那还是撞。

// 随机退避算法
void backoff(int collision_count) {
    // 计算退避窗口,指数增长
    int window = BASE_BACKOFF * (1 << collision_count);
    // 限制最大窗口
    if (window > MAX_BACKOFF) {
        window = MAX_BACKOFF;
    }
    // 随机等待
    int wait_time = rand() % window;
    delay_ms(wait_time);
}

我的经验:BASE_BACKOFF设多少合适?我建议根据波特率来。9600bps时,我设5ms。115200bps时,设1ms。你想想看,波特率越高,位时间越短,退避时间也可以相应缩短。

3.4 完整流程:从侦听到重发

把上面三步串起来,就是一个完整的CSMA/CD流程:

  1. 载波侦听:检查总线是否空闲。忙就等。
  2. 发送数据:空闲了,开始发。
  3. 冲突检测:边发边读,看有没有冲突。
  4. 无冲突:发完收工,等应答。
  5. 有冲突:立即停止发送,执行随机退避。
  6. 重试:退避结束后,回到步骤1。

我曾经在一个项目里,用这套流程实现了32个从站同时上报数据。刚开始总丢包,后来发现是退避窗口设得太小。调整后,成功率从70%提到了99.5%。

避坑指南:我曾经犯过一个错误——退避时没有关发送器。结果退避期间,发送器还挂在总线上,导致其他站发不了。记住:退避前一定要把RE/DE引脚拉低,让收发器进入接收模式。

3.5 实际应用中的注意事项

CSMA/CD在RS485上不是万能的。我总结了几点:

  • 总线长度:超过1000米时,信号延迟会变大。冲突检测的实时性会下降。
  • 节点数量:超过32个节点,建议加中继器。否则驱动能力不够。
  • 波特率:我建议不超过115200bps。再高的话,位时间太短,冲突检测来不及。
  • 接地:RS485一定要共地。我见过太多因为地电位差导致通信失败的案例。

嗯,今天就讲到这里。下一章咱们聊聊「令牌传递法」,那是另一种解决多主站冲突的思路。