简介:面向电子设计开发者、单片机应用工程师及家电安全设计相关专业的学生,这是一份基于单片机的饮水机防干烧控制系统设计与仿真资料。整体设计以STC89C52为主控,针对传统饮水机防干烧可靠性不足的问题,采用故障树分析法(FTA)建立故障树模型,顶事件为饮水机发生干烧,并识别出液位传感器无反馈同时温度传感器无反馈、液位传感器无反馈同时继电器触点无法断开等最小割集,从而为硬件电路与软件流程的针对性优化提供依据。压缩包内含1个PDF文件,大小约1.27MB,覆盖系统总体框图、硬件模块设计、软件流程、Proteus调试仿真及可靠性优化方案。文档重点介绍了电容式非接触液位传感器的选型与安装位置前移、三继电器触点串联防粘连、温度传感器双单元并联等设计细节,并包含LCD12864显示、声光报警和继电器驱动电路,便于读者按图复现或用于课程设计、毕业设计。已有184人学习浏览,适合需要增强饮水机防干烧可靠设计能力、理解产品级可靠性分析与嵌入式开发流程的读者。
1. 半夜被烧糊味唤醒的那个场景,就是这套设计的真正需求来源
半夜被干烧饮水机那股糊味呛醒,加热管烧红再断电,运气好换根加热管,运气不好连外壳都变形——这是我在某实验室亲眼见过的一次翻车现场,也是后来认真重做“基于单片机的饮水机防干烧控制系统设计与仿真”的出发点。这个系统说白了,是用一片单片机时刻盯着“水还有没有、温度正不正常”,一旦缺水或温度异常,立即切断加热电源,同时声光报警,把安全落到执行层;最后还要在仿真环境里完整验证一遍逻辑,避免把方案直接焊到实物上才发现方向性错误。这套内容适合正在做单片机课程设计的同学、想给老式饮水机加装保护功能的 DIY 爱好者,以及准备把防干烧功能写进产品需求预研的工程师。
2. 硬件方案定盘:传感器选型、最小系统与 Proteus 电路搭建
硬件决定安全边界:水位传感器给出“有没有水”这一最底层事实,温度传感器作为第二道防线,继电器作为最终切断手段。任何一个环节选错,后面的软件写得再漂亮都会在关键时刻掉链子。
2.1 三种水位检测方案,怎么选才不给自己挖坑
防干烧对水位信号的需求其实很朴素:只需要区分“水位正常”和“水位低于安全值”。市面上常见的三种水位检测方式,我在这套设计里都对比过,它们的取舍非常明显。
| 检测方式 | 输出信号 | 实物成本 | 仿真建模难度 | 主要问题 |
|---|---|---|---|---|
| 浮球式磁簧开关 | 开关量,高/低电平 | 低 | 低,一个开关代替 | 浮球卡滞、水面波动时抖动 |
| 干簧管多水位 | 多个开关量 | 中 | 中,多个开关组合 | 位置安装精度要求高 |
| 电极式 | 开关量或模拟量 | 最低 | 低 | 电极腐蚀、水垢附着导致误判 |
我见过不少课程设计直接用电极式,因为最便宜、仿真里也好搭。但它有一个容易被忽略的长期风险:电极长期泡在开水里,表面会结水垢,水垢厚到一定程度后电阻变大,本来“有水”的状态会慢慢变成“缺水”,于是系统明明有水却反复报警、反复断电。反过来,电极腐蚀也可能让两个电极之间形成导电通路,把“缺水”误判成“有水”,这才是防干烧最怕的失效模式——水位检测失效,保护被跳过。
所以我在真正动手前换了思路:用非接触式的磁簧开关方案,浮球里的磁铁带动干簧管通断,水垢和蒸汽不会直接影响触点。仿真里只需要用一个普通开关或者按钮模拟浮球翻转。如果还想做“水位偏低预警”和“干烧断电”两级保护,就在水箱外壁不同高度放两个干簧管,分别接两个 IO 口。
提示:仿真里没有真正的浮球,用一个按键或滑动开关代替即可。仿真验证的重点是逻辑时序,不是传感器内部的物理原理。
2.2 单片机和执行电路选型:为什么 89C52 内核足够用
这套系统的输入是两个开关量或一个单总线温度信号,输出是继电器、蜂鸣器和指示灯。对单片机的要求只有几条:IO 数量不少于 8 个、有定时器、能稳定工作在工业级温度范围。用 51 内核的 89C52 芯片正好覆盖这些要求,而且配套资料和 Proteus 仿真库非常成熟,我从开始做仿真到编译出 HEX 文件,基本没因为芯片型号浪费过时间。
执行部件方面,我一开始也犹豫过晶闸管方案:双向晶闸管无触点、不会打火,适合纯阻性加热负载。但防干烧属于安全保护,必须考虑“失效模式”——晶闸管一旦击穿,多数情况是短路,加热回路会一直导通,这是保护系统最不想看到的。继电器就不一样,它断开后是物理断点,即使控制信号丢失,只要继电器本身是好的,负载侧就是安全的。
继电器驱动上,单片机的 IO 口直接驱动不了线圈。常见做法是加一颗 NPN 三极管,比如 8050,基极串联 1kΩ 电阻,发射极接地,集电极接继电器线圈,线圈另一端接 +5V。基极电流大约是 (5V-0.7V)/1k≈4.3mA,8050 的放大倍数足够让 5V 继电器稳定吸合。线圈电流按 50mA 计算,8050 的集电极电流能力也完全扛得住。这套三极管驱动方案在仿真里和实物上行为一致,是最好排查的一版电路。
2.3 Proteus 最小系统搭建:元件清单、接线和三个参数坑
我把 Proteus 工程里需要放的元件列成了一张清单,照着加就行。要注意 Proteus 里很多元件拖出来时的默认参数不是我们要的,不改参数直接跑,经常出现“程序不执行”或“继电器不动”的玄学问题。
| 元件 | Proteus 模型 | 参数 | 用途 |
|---|---|---|---|
| 单片机 | AT89C52 | 晶振频率设 12MHz | 主控 |
| 晶振 | CRYSTAL | 12MHz | 时钟源 |
| 瓷片电容 | CAP | 22pF ×2 | 晶振负载电容 |
| 电解电容 | CAP-ELEC | 10μF ×1 | 上电复位 |
| 电阻 | RES | 10kΩ ×1、1kΩ ×2 | 复位和驱动限流 |
| 驱动三极管 | 2N2222 或 8050 | 默认即可 | 驱动继电器 |
| 继电器 | RELAY | 线圈电压 5V | 加热回路通断 |
| 蜂鸣器 | BUZZER | 5V 有源 | 报警输出 |
| 按钮 | BUTTON | 默认 | 模拟水位开关 / 手动复位 |
| 发光二极管 | LED | 默认红色 | 加热状态指示 |
接线顺序我建议从最小系统开始搭:先把单片机和电源、晶振、复位电路放好,确认能运行后再往外接传感器和执行电路。
第一步,晶振接在 XTAL1 和 XTAL2 之间,两个 22pF 电容各自一端接晶振引脚,另一端接 GND。第二步,复位电路用 10μF 电容串联在 RST 和 VCC 之间,10kΩ 电阻接在 RST 和 GND 之间,这是经典的上电复位结构。第三,EA/VPP 引脚必须接 VCC,否则单片机默认从外部 ROM 取指令,仿真里程序跑不起来。第四,把单片机电源引脚和 GND 接好,Proteus 中如果元件没有显式供电,要注意检查电源端子是否连接正确。最后再接水位开关到 P3.2、蜂鸣器到 P1.1、继电器驱动三极管基极到 P1.0。
这中间有三个参数坑:第一个是晶振频率,Proteus 双击单片机后,在属性里把 Clock Frequency 改成 12MHz,不改的话程序时序全错;第二个是继电器的线圈电压,默认可能是 12V,而我们的供电是 5V,要改成 5V 并在属性里把线圈电阻设成 100Ω 左右;第三个是蜂鸣器选型,有源蜂鸣器只要通电就会响,仿真里更容易观察,无源蜂鸣器需要程序输出方波驱动,别买错型号。
3. 软件逻辑:状态机、消抖和故障自锁怎么写
硬件搭好后,保护逻辑全靠软件。这一章直接给出一套能落到工程里的 51 C 程序框架,分成状态机、水位消抖、温度保护和编译设置四步讲清楚。
3.1 把控制过程拆成五个状态,防止“想到哪写到哪”
我最早写防干烧程序时习惯用一堆 if 嵌套,结果逻辑绕在一起,自己都分不清当前到底在“加热”还是在“缺水保护”。后来改成状态机写法,五个状态一目了然:待机、加热、温度巡检、缺水保护、故障自锁。下面这段代码就是整个控制骨架。
#include <reg52.h> typedef enum { ST_IDLE = 0, ST_HEAT, ST_TEMP_CHECK, ST_LOW_WATER, ST_FAULT } sys_state_t; sys_state_t state = ST_IDLE; sbit RELAY = P1^0; // 继电器控制,高电平吸合 sbit BEEP = P1^1; // 蜂鸣器 sbit LED = P1^2; // 加热指示灯 sbit WATER = P3^2; // 水位信号,低电平表示有水 void relay_on(void) { RELAY = 1; } void relay_off(void) { RELAY = 0; } void main(void) { while (1) { switch (state) { case ST_IDLE: if (water_ok()) state = ST_HEAT; break; case ST_HEAT: relay_on(); LED = 1; if (!water_ok()) { relay_off(); LED = 0; state = ST_LOW_WATER; } else if (read_temp() > MAX_TEMP) { relay_off(); LED = 0; state = ST_FAULT; } break; case ST_LOW_WATER: BEEP = 1; if (water_ok()) { BEEP = 0; state = ST_IDLE; } break; case ST_FAULT: BEEP = 1; // 故障自锁,必须手动复位才解除 break; default: state = ST_IDLE; break; } delay_ms(10); } }这里有两种保护路径:水位掉到安全线以下时进入缺水保护,水位恢复后自动回到待机;但是温度超高时直接进故障自锁,不会自动恢复。原因是“缺水”可能是正常使用中水被烧干,重新加水后可以继续工作;而“温度异常高”大概率是干烧已经发生或者温度传感器检测到异常,这时候让系统自行恢复,风险太大。参数 MAX_TEMP 的取值看饮水机类型,普通加热型饮水机可以设到 95℃,如果系统同时接了温控器,可以把阈值放低到 90℃。
提示:故障自锁状态里不要做任何自动跳转,唯一出口是外部复位。这是安全设计里很关键的一条:保护动作触发后,宁可要求人工介入,也不让设备在异常条件下自己尝试恢复。
3.2 水位信号消抖:连续采样,别让一次抖动触发断电
水位开关在现实中不是干净的 0/1 信号。水面晃动、浮球机械回弹、甚至继电器吸合引起的振动,都会让水位信号在短时间内跳变。如果不处理,最直接的结果是继电器频繁吸合断开,触点打火,加热管忽开忽停。
我的习惯是对水位信号连续采样五次,每次间隔 10ms,五次里至少三次读到统一结果,才认为状态成立。这样一次毛刺根本推不动状态变化,驱动加热状态更稳定。
char water_ok(void) { char i; char cnt = 0; for (i = 0; i < 5; i++) { if (WATER == 0) // 低电平 = 有水 cnt++; delay_ms(10); } return cnt >= 3; // 5次里有3次判定有水 }这个函数的参数其实很有讲究。采样次数太少,滤波效果不明显;采样次数太多,比如 50 次,每次消抖要花 500ms,水位真的掉下去时保护动作会被拖慢,这在干烧场景里是致命的。5 次、间隔 10ms 是我实测过的一个平衡点,总耗时约 50ms,继电器动作时间大约在十几个毫秒到几十毫秒,两者匹配,不会出现“信号已经判定缺水、继电器还没来得及断开、加热还在继续”的窗口期。
3.3 温度保护与传感器自检:把故障也当成一种需要保护的状态
温度保护的单总线温度读取代码比较长,但核心逻辑可以讲清楚:主程序每隔 500ms 发起一次温度转换,然后读取 16 位温度值,转换成分数后的摄氏温度。对应代码里就是 read_temp() 这个函数做的事。
这里有个容易被忽视的坑:DS18B20 读到 -55℃ 或者 85℃ 这两个极端值,往往不是真实温度,而是传感器故障、接线松脱或时序错误。防干烧系统如果只做“温度太高就断电”,遇到传感器故障时会以为温度正常,从而一直加热,这是很危险的行为。所以我通常在 read_temp() 里做一层判断,返回值大于 125℃ 或者小于 -20℃,直接按温度异常处理,系统进入故障自锁,而不是继续加热。
3.4 用 Keil 编译出 HEX 文件:三步设置和两个典型错误
Keil 编译 51 工程,很多人卡在生成不了 HEX 文件,其实就三个设置。
第一步,新建工程时在芯片列表里选 89C52,不同版本的 Keil 里这个芯片可能在 Atmel 或者 NXP 分类下面,能搜到就行。第二步,把写好的 C 文件添加到工程,然后在 Options for Target 的 Output 选项卡里勾选 Create HEX File。第三步,编译时留意 Build Output 窗口有没有报错,没有报错就会在同一目录下生成对应文件名的 HEX。
我遇到过的两个典型编译错误:一是忘记包含 reg52.h,导致 sbit 和特殊功能寄存器报 undefined identifier;二是代码量稍大时默认的 Small 内存模式不够用,报 code memory 溢出,这时可以在 Target 选项卡把 Memory Model 改成 Large,或者检查是不是有数组开得过大。
代码编译通过只是第一步,真正要证明“防干烧”有效,还得靠仿真环境把各种异常场景逐个跑一遍,这是下一章的内容。
4. 仿真验证:用 Proteus 把六类场景跑透
仿真不是把代码放进去看灯亮不亮,而是要模拟异常输入、观察保护动作,并且验证保护动作之后的行为对不对。这一章的每一步,都是照着我在某实验室里实际调试的流程写的。
4.1 加载 HEX 并启动仿真:两个注意事项
在 Proteus 里双击单片机芯片,在 Program File 一栏选择刚才编译出来的 HEX 文件,把 Clock Frequency 设为 12MHz,然后点击右下角的运行按钮。这时候如果电路接对了,默认状态下应该能看到单片机引脚出现电平变化。
有两个需要注意的细节。一是水位开关初始状态要设置为“有水”,也就是让 P3.2 读取到低电平,否则系统一上电就认为缺水,直接报警,没法观察正常加热流程。二是继电器模型的线圈电压要在放置时就确认是 5V,如果用的是默认 12V 继电器模型,即便程序把 P1.0 拉高,继电器也可能吸合不彻底,指示灯却已经亮了,容易造成误判。
4.2 六个测试用例:从正常加热到故障自锁
仿真验证一定要有清单,不能随手点开关看个大概。我整理了一张测试用例表,每个用例都有明确操作和预期结果,照着跑一遍,逻辑漏洞基本都能暴露出来。
| 用例编号 | 操作 | 预期结果 | 验证目的 |
|---|---|---|---|
| TC1 | 水位开关保持闭合 | 继电器吸合,加热 LED 亮 | 正常加热路径 |
| TC2 | 水位开关断开 | 继电器释放,蜂鸣器响,LED 灭 | 缺水立即断电 |
| TC3 | 水位恢复 | 蜂鸣器停,系统回到待机并可重新加热 | 缺水状态可恢复 |
| TC4 | 温度传感器模拟输出超过阈值 | 继电器释放,蜂鸣器响,进入故障自锁 | 干烧断电 |
| TC5 | 故障自锁后恢复水位 | 无变化,继电器保持断开 | 自锁不可自动解除 |
| TC6 | 按下手动复位按钮 | 蜂鸣器停,回到待机 | 人工复位路径 |
TC2 和 TC5 是这套设计里最容易写错的点。有些实现方案把“缺水”和“干烧”合并成一个状态,直接自锁,结果用户加完水发现设备仍然不工作,只能拔电源,使用体验很差。而我上面给出的方案中,缺水是“可恢复异常”,干烧是“不可恢复异常”,两者分开处理,既保护设备也兼顾使用便利。
TC4 里模拟温度超阈值有两种做法:法一是把温度传感器模型直接短路或开路,看系统能不能检测到异常;法二是在传感器模型属性里调整返回值。我更推荐用前一种,因为它同时验证了传感器故障自检逻辑。
4.3 三层验证手段:电平探针、观察窗口和延时边界
第一层是 Proteus 里的电压探针和逻辑探针,放在继电器控制引脚、水位引脚、蜂鸣器引脚上,可以直观看到信号高低。第二层是直接看元件动作,继电器模型有没有变成吸合状态、蜂鸣器有没有变色高亮,这些都比读电平直观。第三层是我比较看重的,用交互式开关快速切换水位信号,模拟水面波动,观察继电器是不是也跟着频繁动作。如果消抖参数设得小,这一层最容易翻车。
验证时还要注意一个时间边界:从水位开关断开到继电器真正断开,软件消抖耗时约 50ms,继电器动作耗时几十毫秒,加起来通常不到 100ms。在仿真里可以用逻辑分析仪测一下 P1.0 从高到低的完整时间线,确认它小于你定的安全阈值。比如你要求 200ms 内完成断电,实测超过 150ms 就开始告警,说明需要加大继电器驱动能力或者缩短消抖采样次数。
5. 避坑清单:水位误判、继电器抖动和仿真与实物之间的温差
这一章是我最想让你直接复制的内容。以下五条坑,我基本都踩过或者帮别人排查过,每一条都按下“现象 → 原因 → 解决”的格式写清楚。
5.1 水位正常但继电器不断颤动,加热灯忽亮忽灭
现象:仿真里手动快速拨动水位开关,或者在实物上水面晃动时,继电器咔嗒咔嗒响,加热状态快速抖动。
原因:水位信号没有消抖,或者消抖采样次数太少。我最早用 2 次采样、间隔 5ms,毛刺照样能通过,因为水面波动的毛刺持续时间可能超过 10ms。
解决:把消抖逻辑写成上一章的 five_samples() 函数,连续 5 次、间隔 10ms、3 次一致才算有效。同时确认读取水位和操作继电器的代码在同一个状态机里,不要在中断里直接操作继电器,避免状态冲突。
5.2 继电器触发逻辑写反,一上电就进入干烧状态
现象:程序跑起来,继电器默认吸合,加热灯常亮,水位开关根本不起作用。
原因:IO 口的初始化把继电器引脚写成了默认低电平,恰好这个继电器模块是低电平触发,于是上电瞬间继电器就吸合了。这是实物和仿真里最容易踩的坑,因为不同继电器模块的触发逻辑不统一,有高电平触发也有低电平触发。
解决:先把继电器控制逻辑抽象成 relay_on() 和 relay_off() 两个函数,不要在 main 里直接写 P1^0 = 0 这样散落的代码。然后写代码前实测一次继电器模块:单片机上电后先让控制引脚输出高电平,如果继电器断开,那就是高电平触发;如果吸合,则是低电平触发。代码里要按实际模块调整。
5.3 Proteus 仿真里单片机不动,程序像没烧进去
现象:Proteus 点击运行后,单片机芯片保持红色高亮,IO 引脚没有任何输出,LED 和继电器都不动。
原因:最常见的三种:一是单片机属性里的 Program File 没设置;二是晶振频率没改,默认频率和程序不匹配;三是复位电路接错,RST 一直保持高电平,单片机被按死在复位状态。
解决:双击单片机确认 Program File 已加载 HEX 文件;把 Clock Frequency 改为 12MHz;用万用表探针检查 RST 引脚在上电后应该落到低电平,如果一直是高电平,复查复位电路的电容和电阻方向,以及 VCC 是不是没有接上。
5.4 温度传感器读数固定为 85℃,温度保护失效
现象:DS18B20 在仿真或者实物中读到的温度一直是 85℃,程序判断温度正常,干烧保护始终不会触发。
原因:DS18B20 上电后内部寄存器默认值就是 85℃ 左右,只有正确发起温度转换指令后才会刷新。如果程序没有调用 Convert T 命令,或者转换时间不够就立刻去读,读到的就是旧值。另外,数据引脚缺少上拉电阻也会出现类似的固定读数问题。
解决:读取流程严格按“初始化 → 跳过 ROM → 启动温度转换 → 等待 750ms → 读取温度”的顺序来做。数据线上加 4.7kΩ 上拉电阻。Proteus 仿真的时序比实物宽容,实物上这个问题更容易出现,不要因为仿真通过了就直接上实物。
5.5 仿真一切正常,实物一开继电器,单片机直接重启
现象:实物上第一次给继电器供电,单片机瞬间重启,LED 全灭,程序重新跑。
原因:继电器线圈是感性负载,断电瞬间会产生反向电动势,直接冲击单片机的电源引脚和复位电路。我在仿真里没有遇到这个问题,因为仿真模型默认忽略了线圈的反电动势。
解决:在继电器线圈两端反向并联一只 1N4007 二极管,阴极接 +5V、阳极接三极管集电极端。同时在单片机电源引脚附近加上 100μF 电解电容和 0.1μF 瓷片电容。如果继电器功率比较大,建议用独立电源给继电器供电,中间用光耦隔离。
6. 从仿真到实物:三个必须验证的边界
6.1 感性负载的续流二极管不能省,这是保硬件底线的第一课
仿真跑通后,我怀着很大的自信去搭实物,结果第一次给继电器上电,单片机就重启了。后来才知道线圈反向电动势的厉害。续流二极管的接法有严格方向:负极接电源正端,正极接三极管的集电极,这样断电瞬间线圈产生的反向电流有泄放回路,不会冲击单片机。这条只要做一次实物就该养成习惯,千万别在仿真里觉得多余就跳过去。
6.2 温度采集点不能只放一个,热积累是干烧的隐蔽前置信号
真实饮水机的加热腔里,底部和顶部温度差异很大。干烧发生时,底部加热管附近温度先飙升,而顶部水腔因为残留蒸汽可能仍是常温。如果你只把 DS18B20 贴在顶部,系统很可能会对干烧毫无感知。我习惯在加热腔底部和上部各放一个采集点,底部负责干烧判断,上部负责沸腾判断。温度探头外部要加不锈钢盲管隔离,既防漏电也防长期泡水腐蚀。
6.3 实物验证三段法:模拟水位、观察动作、持续压测
把仿真代码烧进实物后,别急着真接加热管。先用可调电位器和轻触开关模拟水位变化,观察继电器和蜂鸣器是否按状态机工作;然后用示波器抓继电器控制引脚的电平跳变,确认消抖和保护动作时间;最后用功率电阻或者白炽灯代替加热丝,连续跑 5 分钟空烧测试,确认温度保护能在合理时间内触发。
做完这三步,基本可以确定这套防干烧逻辑从仿真到实物没有走样。我最终的感受是:防干烧设计最大的价值不是“能控加热”,而是“异常到来时,系统能按照预期安全停下”。仿真给了我们反复调整的机会,但真正要交付的,是实物在传感器失效、电源波动、用户误操作这些边界情况下仍然不会把设备推向危险状态。希望这套从选型、状态机到避坑的流程,能帮你少走一段弯路。
本文还有配套的精品资源,点击获取