3. 冗余设计:硬件冗余与软件冗余

各位同学,咱们今天聊一个很实在的话题——冗余设计。

说白了,就是给系统多留几条后路。温室控制系统不像手机,死机了重启就行。你想想看,要是冬天夜里加热系统突然挂了,一棚的苗子可能就全完了。我见过太多这样的教训,所以对冗余设计,我个人是有点执念的。

3.1 硬件冗余:给关键部件配个“备胎”

硬件冗余,就是给关键设备多配一套。一套干活,一套待命。我习惯把硬件冗余分成两种:双机热备和三模冗余。

3.1.1 双机热备

双机热备,就是两台设备同时运行。一台主,一台从。主设备干活,从设备同步数据。主设备一旦挂了,从设备立刻顶上。

我在一个大型连栋温室项目里用过这个方案。当时控制柜里放了两台PLC,一台西门子S7-1200做主站,一台S7-1200做从站。心跳信号每100ms检测一次。主站挂了,从站500ms内接管控制权。

关键参数:

  • 心跳检测周期:100ms
  • 切换时间:<500ms
  • 数据同步方式:双端口RAM

嗯,这里要注意。双机热备最怕什么?最怕切换时数据不一致。比如主设备刚发出“开窗50%”的指令,还没来得及保存状态就挂了。从设备接管后,它不知道窗户已经开了多少。所以数据同步一定要做实时。

避坑指南:

我曾经在一个项目中,双机热备切换成功了,但窗户开度数据没同步。结果从设备接管后,以为窗户是关着的,又发了一次开窗指令。窗户直接开到100%,大棚里灌了一夜冷风。从那以后,我要求所有状态变量必须双机同步,一个都不能漏。

3.1.2 三模冗余

三模冗余,就是三套设备同时运行。三套都干活,然后投票表决。两票一致就输出那个结果。这比双机热备更可靠,但成本也更高。

我一般只在生命攸关的场景用三模冗余。比如育苗温室的CO₂浓度控制,浓度高了会伤苗,低了不长。三个传感器同时采集,取中间值输出。如果一个传感器坏了,另外两个还能正常工作。

冗余方式 成本 可靠性 适用场景
双机热备 一般温室控制
三模冗余 极高 育苗、组培等关键环节

我的经验:

三模冗余不是越多越好。我见过有人搞五模冗余,结果投票逻辑复杂得要命,反而容易出bug。三模就够了,两票一致就输出,简单可靠。

3.2 软件冗余:代码层面的“双保险”

硬件冗余解决的是设备故障。但软件也会出问题啊。逻辑错误、边界条件没处理好、甚至编译器优化导致的bug,这些硬件冗余都管不了。所以还得有软件冗余。

3.2.1 N版本编程

N版本编程,就是同一个功能,让N个团队各自独立写一套代码。然后同时运行,投票输出。这听起来有点奢侈,但确实有效。

我记得有个项目,要求控制温室内的温度波动不超过±0.5℃。我们让三个团队分别写了PID控制算法。一个用增量式PID,一个用位置式PID,一个用模糊PID。三套代码同时跑,取中间值输出。

// 版本A:增量式PID
float pid_incremental(float setpoint, float actual) {
    float error = setpoint - actual;
    float output = Kp * (error - last_error) + Ki * error + Kd * (error - 2*last_error + last_last_error);
    last_last_error = last_error;
    last_error = error;
    return output;
}

// 版本B:位置式PID
float pid_positional(float setpoint, float actual) {
    float error = setpoint - actual;
    integral += error;
    float output = Kp * error + Ki * integral + Kd * (error - last_error);
    last_error = error;
    return output;
}

// 版本C:模糊PID
float pid_fuzzy(float setpoint, float actual) {
    // 模糊规则表,根据误差和误差变化率调整Kp、Ki、Kd
    // 代码略...
}

为什么会这样?因为不同团队对同一问题的理解不同,写出来的代码bug也不同。一个团队的代码有bug,另外两个团队的可能没有。投票机制就能把bug过滤掉。

N版本编程的要点:

  • 团队之间不能交流,避免“共同错误”
  • 使用不同的编程语言或算法
  • 投票机制要简单,一般取多数或中位数

3.2.2 恢复块

恢复块,说白了就是给代码加个“后悔药”。主模块先跑,如果检测到结果异常,就回滚到上一个安全状态,然后跑备用模块。

我习惯在关键控制逻辑里用恢复块。比如温室灌溉控制,主模块根据土壤湿度计算灌溉量。如果计算结果超出合理范围(比如湿度100%还让灌溉),就触发恢复块,改用备用模块的保守策略。

// 恢复块示例
bool recovery_block() {
    // 主模块
    float result = main_module();
    if (is_valid(result)) {
        return result;
    }
    
    // 检测到异常,回滚
    rollback();
    
    // 备用模块
    float backup_result = backup_module();
    if (is_valid(backup_result)) {
        return backup_result;
    }
    
    // 两个模块都挂了,进入安全模式
    enter_safe_mode();
    return SAFE_DEFAULT;
}

避坑指南:

我曾经在恢复块里犯过一个低级错误。主模块和备用模块共用了同一个全局变量。主模块把变量改坏了,回滚时没恢复。备用模块一跑,用的还是坏数据。结果两个模块都输出错误结果。所以恢复块一定要做好状态保存和恢复,每个模块要有独立的上下文。

3.3 冗余设计的成本与收益

冗余不是免费的。每增加一套冗余,成本、功耗、体积都会增加。我个人建议,只在以下场景做冗余:

  • 故障会导致重大损失的(比如育苗温室、组培室)
  • 维护困难、无法及时修复的(比如偏远地区的温室)
  • 安全相关的(比如CO₂浓度、温度极限控制)

你想想看,一个普通温室,加热器坏了,大不了苗子长得慢点。但育苗温室加热器坏了,一批苗子可能就全废了。所以冗余设计要“看菜下饭”,别盲目堆料。

我的建议:

先做FMEA(故障模式与影响分析),找出最关键的故障点。然后针对这些点做冗余。别想着全冗余,那成本谁也扛不住。我一般只对传感器、控制器、执行器这三个环节做冗余,通信链路看情况。

好了,冗余设计就聊这么多。记住一句话:冗余不是万能的,但没有冗余是万万不能的。下一章咱们聊聊故障检测与诊断,看看怎么在故障发生前就把它揪出来。