4. 数字量输入输出:DI/DO接口设计、光电隔离原理、去抖动算法、状态量采集与输出控制

各位同学,咱们今天聊点实在的。数字量输入输出,简称DI/DO,是FTU和外部世界打交道的「手」和「眼」。说白了,DI就是让FTU知道外面发生了什么,DO就是让FTU去控制外面的设备。这活儿看着简单,但坑特别多。我当年刚入行时,就因为在DI接口上少加了一个电阻,现场烧了三块板子……嗯,从那以后,我对这部分内容就格外上心。

4.1 DI接口设计:怎么让FTU「看见」开关状态?

DI,Digital Input,数字量输入。FTU要采集的,通常是断路器的分合闸状态、隔离开关的位置、各种告警信号。这些信号本质上是「有电」或「没电」——也就是0和1。

但问题来了:现场环境极其恶劣。电磁干扰、长线传输、电压波动,哪个都能让信号变「脏」。所以DI接口不能简单地把一根线接到芯片引脚上完事。我个人习惯,至少要做三件事:

  • 限流保护:防止外部高压直接灌入芯片。一般串一个1kΩ~10kΩ的电阻。
  • 钳位保护:用TVS管或稳压二极管,把电压钳在安全范围内。
  • 滤波处理:加一个小电容(比如0.1μF),滤掉高频毛刺。

你想想看,如果外部是220V交流电误接入DI口,没有限流和钳位,芯片瞬间就冒烟了。我见过不止一次这种「现场教学」。

4.2 光电隔离:为什么必须「物理断开」?

这是DI/DO设计的核心。光电隔离,就是用光来传递信号,电气上完全隔离。为什么非要这样?

因为现场和FTU之间,可能存在巨大的电位差。比如接地不良时,两地之间可能有几十伏甚至上百伏的电压。如果不隔离,这个电压就会通过信号线形成回路,轻则数据错误,重则烧毁设备。

光电耦合器(光耦)的工作原理很简单:

  • 输入端:发光二极管(LED),通电就发光。
  • 输出端:光敏三极管,接收到光就导通。

电信号→光信号→电信号,中间没有电气连接。这就是「隔离」的本质。

关键参数:

  • 隔离电压:常见的有2500Vrms、3750Vrms、5000Vrms。配网FTU一般选3750Vrms以上。
  • 传输速率:普通光耦几十kbps就够用,DI信号变化很慢。
  • 电流传输比(CTR):决定了输入端需要多大电流才能驱动输出端。一般选50%~600%。

注意:光耦也有寿命问题。LED会随着时间老化,CTR会下降。我曾经在一个项目中,用了三年后,部分DI通道开始出现误判,排查下来就是光耦老化导致驱动不足。所以设计时建议留足余量,CTR选高不选低。

4.3 去抖动算法:别让「抖动」骗了你的FTU

机械开关在闭合或断开的瞬间,触点会弹跳。这个弹跳时间通常持续5ms~20ms。如果不处理,FTU会认为开关在短时间内反复动作了好几次。这在电力系统里是致命的——你可能误判为「多次分合闸」。

去抖动的思路很简单:等信号稳定了再采。常见方法有两种:

4.3.1 硬件去抖:RC滤波 + 施密特触发器

用电阻和电容组成低通滤波器,把高频抖动滤掉。再用施密特触发器整形,输出干净的方波。优点是实时性好,缺点是RC时间常数固定,灵活性差。

4.3.2 软件去抖:延时确认法

这是嵌入式系统里最常用的方法。我一般这样写:

// 简易软件去抖,适用于DI状态采集
#define DEBOUNCE_MS 20  // 去抖时间,根据开关特性调整

uint8_t debounce_di(uint8_t raw_value) {
    static uint8_t stable_value = 0;
    static uint8_t count = 0;
    static uint32_t last_tick = 0;
    
    uint32_t now = get_tick_ms();
    if (now - last_tick < DEBOUNCE_MS) {
        return stable_value;  // 时间未到,返回上次稳定值
    }
    last_tick = now;
    
    if (raw_value == stable_value) {
        count = 0;  // 和稳定值一致,计数器清零
    } else {
        count++;
        if (count >= 3) {  // 连续3次采样都不同,才认为状态变了
            stable_value = raw_value;
            count = 0;
        }
    }
    return stable_value;
}

这段代码的核心思想是:不轻易相信一次采样。连续多次采样结果一致,才认为状态真的变了。我习惯用「连续3次确认」,去抖时间20ms。实际项目中,你可以根据开关的弹跳特性调整这两个参数。

小技巧:如果DI信号变化非常慢(比如断路器状态,一天可能就变一两次),去抖时间可以设长一点,比如50ms甚至100ms。但如果是脉冲计数类的信号,去抖时间要短,否则会丢脉冲。这个取舍,得看具体场景。

4.4 DO接口设计:怎么让FTU「动手」控制?

DO,Digital Output,数字量输出。FTU通过DO去控制继电器、接触器、指示灯等。DO的设计要点和DI类似,但方向相反——现在是FTU往外送信号。

DO接口的核心是驱动能力。芯片引脚直接驱动继电器?想都别想。芯片引脚一般只能输出几毫安,而一个继电器线圈可能需要几十甚至上百毫安。所以需要驱动电路。

常用的方案:

  • 三极管驱动:小功率场景,成本低。
  • 达林顿管(如ULN2003):集成多路,驱动电流大,带续流二极管。
  • 光耦+MOSFET:需要隔离时用,驱动能力更强。

我个人偏爱ULN2003。它内部集成了7路达林顿管,每路能驱动500mA,还自带续流二极管。驱动继电器时,只需要在外部加一个限流电阻就行,非常省事。

避坑指南:我曾经在一个项目中,用ULN2003驱动直流继电器,一切正常。后来客户要求换成交流继电器,结果一上电就烧。为什么?因为ULN2003内部的续流二极管是单向的,只适用于直流感性负载。交流负载需要双向TVS或者RC吸收回路。这个教训,让我记住了「直流和交流,驱动电路不能通用」。

4.5 状态量采集与输出控制:完整的DI/DO流程

把上面这些串起来,就是一个完整的DI/DO子系统:

环节 DI(输入) DO(输出)
外部接口 开关触点、传感器 继电器、接触器、指示灯
保护电路 限流电阻、TVS、滤波电容 续流二极管、RC吸收
隔离器件 光耦(如TLP185) 光耦+驱动(如ULN2003)
MCU接口 GPIO输入,带软件去抖 GPIO输出,带状态回读
软件处理 去抖、滤波、变化检测 输出保持、互锁、故障检测

这里我想强调一点:DO输出一定要有「状态回读」功能。什么意思?就是MCU输出一个信号后,还要能读回DO口的实际电平。这样一旦驱动电路故障(比如三极管击穿),MCU能立刻发现并报警。我见过太多现场事故,都是因为「以为输出了,实际没输出」导致的。

4.6 总结:DI/DO设计的三个核心原则

嗯,讲了这么多,最后总结三条我自己的「铁律」:

  1. 隔离是底线:DI和DO必须光电隔离,没有例外。成本再紧,这条不能省。
  2. 去抖是良心:软件去抖做得好不好,直接决定了FTU上报的数据质量。别让抖动数据骗了后台。
  3. 保护要冗余:限流、钳位、滤波,能加就加。多一个电阻,可能就少一次现场故障。

DI/DO看起来简单,但它是FTU和一次设备之间的「桥梁」。这座桥要是塌了,后面的算法再牛也没用。所以,请认真对待每一个引脚、每一行代码。好了,这一章就到这儿,下一章我们聊聊模拟量采集——那又是另一番天地了。