ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于STM32的智能饮水机Proteus仿真:从DS18B20到继电器的温度控制设计

基于STM32的智能饮水机Proteus仿真:从DS18B20到继电器的温度控制设计 简介这份基于STM32的智能饮水机Proteus仿真工程面向嵌入式初学者与电子设计爱好者实现了水位低于阈值报警、温度低于阈值自动加热、按键手动设置阈值等功能是学习ADC采样、传感器比较控制与单片机中断/按键输入的良好示例。资源包共283个文件包含STM32项目源码.c/.h、Keil工程文件.uvprojx/.uvoptx、Proteus仿真设计.pdsprj、可烧录的HEX文件以及Word说明文档、PDF手册和演示视频.wmv压缩包大小约12.23MB项目文件结构完整便于直接打开仿真或重新编译调试。该资源已有171人浏览学习适合用于单片机课程设计、毕业设计或电子竞赛的初步方案参考。通过阅读源码和仿真原理图读者可以掌握STM32外设驱动初始化流程、阈值比较逻辑的编写方法以及Proteus环境下外围电路的设计思路与联调步骤。1. 为什么“基于STM32的智能饮水机”总出现在Proteus仿真案例里把一只智能饮水机完整跑通不一定非得先抢到一块STM32开发板。Proteus 8.15仿真这件事真正解决的是在没有实物、没有电烙铁的情况下把温度采集、加热控制、水位保护这些逻辑调到可以演示、可以答辩、可以继续加功能的程度。这个标题里的“基于STM32的智能饮水机”不是玩具它把STM32的GPIO操作、单总线协议、定时器延时、按键扫描和LCD显示全串在一起恰好是嵌入式入门阶段最该练的几个点。这类仿真项目最常见的受众是两类人一类是刚学完江科大STM32教程想找个综合任务把外设串起来的学生另一类是工作中需要快速验证控制方案但手边没有对应硬件平台的工程师。Proteus 8.15的价值不在于它能替代真实硬件而在于它把DS18B20的温度值变成可以直接拖动的变量把水位开关变成鼠标点一下就能切换的状态。调试速度比烧录实物快得多出问题时也能直接看GPIO电平不用拿万用表去戳引脚。我一般会按“电路搭建 → 控制逻辑 → 联调排错 → 移植参数”的顺序来做这个仿真下面就把每一步的选型理由、关键代码和踩坑点展开讲。2. Proteus 8.15 搭建饮水机仿真电路的元件选型与接线2.1 饮水机仿真的最小硬件清单STM32、DS18B20、继电器与加热负载智能饮水机的功能需求可以拆成四个部分测水温、显示温度、控制加热、检测缺水。对应到Proteus 8.15元件库我常用的最小清单如下。元件Proteus搜索关键词数量用途STM32F103C8STM32F103C81主控温度传感器DS18B201水温采集LCD1602液晶LM016L1显示温度与状态继电器RELAY1控制加热回路加热负载LAMP灯泡1用亮灭模拟加热状态水位开关BUTTON1模拟浮球水位信号按键BUTTON2温度加减、模式切换电位器POT-HG1LCD对比度调节为什么温度采样选DS18B20而不是NTC热敏电阻加ADC核心原因是DS18B20在Proteus 8.15里有现成的仿真模型选中元件后可以通过上下箭头直接调整温度不需要额外搭一个电压比较电路去模拟ADC输入。它的12位分辨率对应0.0625℃的精度放在饮水机0到100℃的测量范围里完全够用。更重要的是单总线协议只占一个GPIO口比NTC要省一个ADC通道。加热负载在仿真里用灯泡而不是真实的加热棒模型是因为Proteus里没有“水”这种带热容的连续介质模型。灯泡亮灭可以直接看出继电器是否吸合仿真运行速度也不至于因为热传导计算被拖慢。水位检测用BUTTON模拟干簧管浮球按钮按下表示水位正常断开表示缺水逻辑上与实物的常开型浮球开关一致。2.1.1 引脚分配表避免与JTAG引脚冲突引脚分配是仿真工程里第一个容易翻车的地方。PB3、PB4在STM32F103上默认是JTAG复用引脚如果固件里没有做AFIO重映射和JTAG关闭这两个脚作为普通GPIO是驱动不了的。Proteus仿真里同样遵循这个规则。下面是我习惯用的一组分配六个外设不会互相干扰。外设引脚配置方式DS18B20数据线DQPA0开漏输出外部4.7k上拉水位检测PA1内部上拉输入断开为缺水按键加/减PA2、PA3按下为低电平继电器控制PB1推挽输出LCD1602 RSPB8推挽输出LCD1602 ENPB9推挽输出LCD1602 D4~D7PB10~PB13推挽输出LCD用4位模式而不是8位模式RS、EN加D4到D7一共6根控制线就能跑给其他外设留出充足引脚。按键接PA2和PA3都配内部上拉按下时读到的电平从1变0。水位引脚PA1配内部上拉按钮开关一端接PA1、另一端接GND按下时读到0表示有水。2.2 单总线4.7k上拉和继电器驱动电路不能省DS18B20的DQ引脚在实物电路里必须接一个4.7kΩ上拉电阻到VCCProteus仿真图里很多人省掉这电阻也能跑但我不建议这么做。省掉上拉会让电路与真实硬件的行为不一致而且一旦固件把GPIO切成输入模式总线电平在没有上拉时是不确定的可能读到随机数据。正确的做法是DQ接PA0同时接一个4.7k电阻到3.3V电源这个电阻在Proteus元件搜索框里输入RES就可以找到。继电器驱动不要用GPIO直接推继电器线圈。STM32的GPIO输出能力有限而Proteus里的RELAY模型如果设置成真实线圈参数直接驱动会拉低单片机电平导致各种随机复位。稳妥的方案是加一个NPN三极管作为开关PB1通过1k电阻接到三极管基极三极管集电极接继电器线圈一端线圈另一端接5V发射极接地。继电器常开触点串在灯泡加热回路里线圈得电时触点闭合灯泡点亮。这套驱动电路从仿真搬到实物时也能原样使用只是实物里要把三极管换成更合适的型号并且在继电器线圈两端反并联续流二极管。2.2.1 外部晶振的取舍HSE与HSI两种做法STM32F103C8的固件时钟配置有两种常见路线。一种是用外部晶振HSE 8MHz再通过PLL倍频到72MHz这时Proteus原理图里必须放置一个8MHz晶体元件并接到OSC_IN和OSC_OUT之间否则固件初始化时等待外部时钟稳定会卡死。另一种是直接用内部HSI跳过外部晶振原理图里不需要放晶体。在Proteus 8.15做快速逻辑验证时我倾向于直接改固件用HSI少一个元件少一个不确定性但做毕业设计演示时用外部晶振方案看起来更接近真实开发板也方便后续下载到实物上跑。3. STM32饮水机控制逻辑DS18B20温度采样与加热状态机3.1 操作STM32的GPIO开漏与推挽不能混用智能饮水机的固件编写第一步就是初始化GPIO。DS18B20所在的PA0必须配成开漏输出模式继电器控制脚PB1配成推挽输出两者混用会出现一种隐蔽故障表现为单总线读取温度时好时坏温度值偶尔跳到85℃。以下是用标准外设库初始化的代码GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); // DS18B20 DQ - PA0开漏输出外部已接4.7k上拉 GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_OD; GPIO_Init(GPIOA, GPIO_InitStructure); // 继电器控制 - PB1推挽输出驱动能力更强 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_Init(GPIOB, GPIO_InitStructure); // 水位检测 - PA1上拉输入 GPIO_InitStructure.GPIO_Pin GPIO_Pin_1; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IPU; GPIO_Init(GPIOA, GPIO_InitStructure);开漏输出的特点是单片机只能主动拉低总线释放总线时依赖外部上拉电阻把电平拉高。这正好匹配单总线协议因为DS18B20在发送“0”时拉低总线发送“1”时释放总线。而继电器控制引脚需要明确的强推挽输出开漏模式下如果没有外部上拉高电平状态无法维持继电器会反复抖动。3.2 主循环内的加热滞回状态机饮水机加热控制最忌讳的写法就是“温度小于设定值就开继电器大于等于设定值就关继电器”。这种简单比较会让继电器在目标温度附近反复吸合仿真里能直接看到灯泡每秒闪好几次实物里继电器触点寿命按万次计算几天就会损坏。正确做法是引入滞回区间。设定目标温度为38℃回差为3℃时温度只有降到35℃才重新加热升到38℃就停止加热。中间这3℃区间内继电器状态保持不变不会产生任何动作。以下是可抄作业的主循环核心逻辑#define TARGET_TEMP_X10 380 // 目标温度 38.0℃用10倍精度避免浮点运算 #define HYSTERESIS_X10 30 // 回差 3.0℃ #define SAMPLE_PERIOD_MS 500 // 采样周期 500ms uint16_t temp_x10; uint8_t water_level; while (1) { temp_x10 ds18b20_read_x10(); water_level GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1); if (water_level 0) // 水位正常时PA1被拉低 { if (temp_x10 TARGET_TEMP_X10 - HYSTERESIS_X10) { relay_on(); // 低于回差下限启动加热 } else if (temp_x10 TARGET_TEMP_X10) { relay_off(); // 到达目标温度停止加热 } } else // 缺水保护优先级最高 { relay_off(); // 无条件切断加热回路 } delay_ms(SAMPLE_PERIOD_MS); // 周期结束进入下一次采样 }这段代码里有两个设计细节值得说明。第一温度变量全部用10倍整数表示38.0℃写成3803.0℃写成30。STM32F103C8的Cortex-M3内核虽然有FPU的软浮点库但浮点运算在标准外设库环境下会占用不少CPU时间而中断里做浮点运算更容易引发时序抖动整数比较完全没有这个问题。第二缺水保护放在水位判断的最外层不管温度是多少只要水位断开就立刻关闭继电器。这个优先级顺序在实际饮水机里是绝对不能反过来写的。3.3 DS18B20读取函数与Proteus时序兼容处理DS18B20是单总线器件所有通信都靠精确的时序完成。Proteus 8.15里的DS18B20模型对时序的敏感程度甚至高于真实芯片因为仿真步长会影响延时函数的实际表现。读取温度的最小流程是复位脉冲、发送跳过ROM指令、启动温度转换、等待转换完成、再次复位、读暂存器。void ds18b20_convert(void) { ds_reset(); ds_write_byte(0xCC); // Skip ROM单设备可直接跳过地址匹配 ds_write_byte(0x44); // 启动温度转换 delay_ms(750); // 12位精度最大转换时间 750ms } uint16_t ds18b20_read_raw(void) { uint8_t low, high; ds_reset(); ds_write_byte(0xCC); ds_write_byte(0xBE); // 读取暂存器 low ds_read_byte(); high ds_read_byte(); return (uint16_t)((high 8) | low); }ds_reset和ds_write_byte这类底层函数在Proteus仿真里要注意的坑是延时节奏。复位脉冲要求主机拉低总线至少480微秒然后释放总线等DS18B20拉低应答信号。如果固件开了定时器中断而中断服务函数执行时间超过几十微秒就会把复位脉冲切碎DS18B20模型直接判定通信失败。解决办法是读取温度期间关中断读完成后再打开。提示Proteus里DS18B20读出来的温度如果一直是85℃或者0xFFFF先检查是不是中断把时序打断了再把Keil的优化等级从-O2降到-O1或者-O0这两个原因占了九成。从原始值换算成10倍精度的温度值很简单12位分辨率下原始值乘以0.0625就是摄氏度再乘以10就是10倍精度整数。例如原始值0x0258表示60.0℃换算后temp_x10等于600。4. Keil与Proteus8.15联调HEX加载、运行观察与常见故障排除4.1 Keil工程配置要点芯片型号、HEX生成与优化等级仿真工程第一步是让Keil5生成HEX文件。打开Keil5新建工程时设备列表里选择STM32F103C8如果列表里没有说明本地的STM32F1系列芯片支持包没装。这也是很多“keil5兼容c51和stm32安装”问题的高发点同一个Keil5里如果先装了C51版本后来又装ARM版本两个编译器可以共存但Device数据库需要单独安装STM32F1xx_DFP支持包才能识别到F103系列。工程选项里必须勾选Output选项卡下的Create HEX File否则编译成功也只生成AXF调试文件Proteus加载不了。优化等级建议从-O0开始做仿真跑通以后再试探提高。GCC风格优化和Keil的ARMCC优化都会做死代码消除delay_us这类纯延时函数如果被优化掉单总线时序直接失效现象是温度值恒定不变或者通信无应答。4.2 Proteus 8.15中加载HEX并模拟调温、调水位在Proteus 8.15的原理图里双击STM32F103C8芯片会弹出属性窗口Program File栏目就是加载固件的地方。选择编译出的HEX文件后Crystal Frequency设置成8与固件外部晶振配置保持一致。如果固件改成了内部HSI时钟这个频率值不影响程序执行但会影响依赖波特率的虚拟串口外设计算最好还是按实际时钟源填写。运行仿真后DS18B20元件上会有两个小箭头点击向上箭头可以把仿真温度调高点击向下箭头调低。这个是整个Proteus仿真流程里最值钱的交互能力温度从35℃调到40℃不需要重新编译程序LCD上的显示和继电器状态会实时变化。水位按钮直接在原理图里用鼠标点击切换通断可以看到缺水保护逻辑是否生效。观察重点有三个位置LCD1602上显示的温度和状态文字、灯泡LAMP的亮灭、PB1引脚上接的电压探针颜色。我习惯在PB1上拖一个Logic Probe逻辑探针红色表示继电器控制信号为高电平蓝色为低电平比用肉眼看灯泡更早发现继电器抖动问题。4.3 常见故障排查对照表联调过程最常见的几类问题可以整理成下面这张对照表遇到现象时按行定位原因。现象大概率原因处理方法仿真运行后LCD完全不亮LCD对比度引脚VL悬空VL接10k电位器中间抽头调出清晰显示LCD亮但温度恒为85℃DS18B20初始化时序被中断打断读取期间关闭中断检查4.7k上拉电阻温度显示-55℃或0xFFFF复位脉冲太短或未检测到应答加长延时确认时钟频率配置正确按下水位按钮后继电器不关水位引脚配置成推挽输出改成上拉输入确认按下时读到电平为0灯泡高频闪烁回差区间太小把HYSTERESIS_X10调大采样周期调长编译后提示Cannot open fileKeil工程路径含中文或空格工程移到纯英文路径重建最后一行问题在Proteus仿真工程里特别常见。Proteus文件命名也最好用纯英文加数字像标题里的“20250319”这种日期后缀没有问题但不要出现中文文件夹名。Proteus对路径里的中文字符支持不稳定经常表现为能加载HEX但运行后程序异常或者干脆不运行。4.3.1 用虚拟示波器观察继电器抖动Proteus右下角工具栏里有Virtual Instruments其中Digital Oscilloscope可以接到PB1引脚上。调试滞回参数时把示波器时间轴调成每格2秒继电器吸合时PB1输出高电平释放时输出低电平。波形上高低电平的持续宽度直接反映了继电器的动作频率。如果高电平只维持几百毫秒就跳回低电平说明回差设置过小如果高电平持续十几秒甚至几十秒说明回差偏大水温波动会比较大。5. 从Proteus仿真到实物移植铺平路的滞回调参方法仿真和实物有一个关键差异灯泡加热模型没有热惯性电流通过瞬间发光断电瞬间熄灭温度变化是瞬时的。真实饮水机的加热管泡在水里断电后水温还会因为加热管余热继续上涨一点五到两摄氏度这个现象叫过冲。仿真里的3℃回差在实物上经常导致继电器频繁通断因为水温停在目标点附近时实际温度会反复穿越回差上下限。从仿真往硬件移植时建议维护一组独立参数定义#define TEMP_TARGET_X10 450 // 设定水温 45.0℃ #define TEMP_HYSTERESIS_X10 50 // 回差 5.0℃比仿真适当放大 #define TEMP_SAMPLE_MS 500 // 采样周期 500ms与DS18B20转换时间匹配 #define RELAY_MIN_INTERVAL_MS 30000 // 两次加热最短间隔防止频繁吸合我一般用这个MDK工程里的全局变量统计继电器吸合次数并显示在LCD第二行然后让饮水机运行十分钟记录吸合次数。如果十分钟内吸合超过二十次就说明回差太小或者加热功率过大需要把回差往大调。如果用实时时钟做记录还可以算出平均加热周期对照保温效果来微调回差。另一个实用技巧是把“两次加热最短间隔”作为约束条件在主循环里记录上次关闭继电器的时间戳如果距离现在不足三十秒即使温度低于回差下限也继续等待。这个逻辑在仿真里看不出明显必要因为灯泡负载的开关寿命几乎是无限的但在实物上它能直接避免继电器在保温阶段高频切换。仿真里这个参数设成零也能跑通真正移植前建议把它加上。调参时最有效的观察工具就是把PB1接一个LED指示灯。虽然继电器本身也会有机械吸合声但环境吵杂时听不准LED亮灭频率一目了然。仿真里同样可以靠这个LED验证逻辑只是两边的目标参数不同仿真里追求加热间隔清晰可观察实物里追求继电器每天动作次数尽可能少。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进