简介一套基于STM32的智能饮水机Proteus 8.15仿真资源面向嵌入式初学者及课程设计、毕业设计人群重点演示水位检测报警、温度检测自动加热和按键手动设置阈值等功能帮助在没有实物板卡的条件下快速理解软硬件协同逻辑。压缩包共283个文件、约12.23MB核心内容涵盖Proteus仿真工程pdsprj、Keil MDK工程uvprojx、C语言源码.c/.h、编译生成的hex固件以及PDF/Word说明文档和操作演示视频各类文件分工明确便于直接打开和二次修改。代码基于STM32F10x标准外设库涉及定时器、ADC等常用外设模块配合已配置好的Proteus电路可直观观察低于水位阈值时的报警状态、低于温度阈值时的加热动作以及按键修改上下限后的运行变化是学习STM32编程与Proteus联合仿真的完整样例。目前已有171人学习下载适合需要快速搭建实验环境、验证控制逻辑的开发者参考。1. 基于 STM32 的智能饮水机 Proteus 8.15 仿真先解决加热干烧再谈外观饮水机最容易出问题的不是加热丝而是控制逻辑低水位时还在加热、到达 95°C 后继电器抖动、保温回差太小导致频繁启停。用硬件调试这些问题要接温控器、继电器和电源成本不低把 STM32 和外围电路放进 Proteus 8.15 做仿真则能在几十秒内把阈值、延时、状态机改完重新看效果。这个标题里的 rar 包具体内容我没有读但这类仿真项目最有价值的部分不是电路图本身而是温度采样、水位保护和继电器控制的闭环。它适合准备 STM32 课程设计也适合在打板前验证固件状态。下面按我习惯的流程从系统拆分写到 Proteus 8.15 中的调参技巧。2. 把智能饮水机拆成四个回路元器件选型和 Proteus 接线智能饮水机在 Proteus 里仿真的核心不是把外观画漂亮而是把加热腔、水箱、出水口抽象成电气节点。温度检测回路用热敏电阻或电位器分压产生 0-3.3V 模拟电压水位检测回路用开关模拟浮球通过上拉输入得到高电平或低电平加热执行回路让 STM32 的 GPIO 经三极管或 ULN2003 驱动继电器触点串在加热灯电源端显示与调试回路让 LCD1602 显示温度和水位USART1 接 VIRTUAL TERMINAL。这四个回路之间没有复杂电气耦合所以在 Proteus 8.15 里画起来很直接。2.1 四个回路的边界和仿真侧重点温度回路要关注 ADC 的基准电压和采样通道。Proteus 8.15 里 STM32F103C8 的 ADC 是 12 位参考电压默认等于 VDD也就是 3.3V仿真时如果给 MCU 只接 5V没有 3.3VADC 值和 GPIO 高电平会一直不正常。水位回路的重点是输入引脚内部上拉/下拉设置开关一端接 3.3V、另一端接 PA1PA1 设GPIO_Mode_IPD下拉输入闭合时读到高电平断开时读到低电平反过来接就把 PA1 改成GPIO_Mode_IPU。加热回路的重点是隔离而不是功率Proteus 里用 RELAY 表示继电器线圈端接 NPN 三极管集电极基极电阻取 1kΩ 到 10kΩ否则仿真中 GPIO 直接带不动模型。显示和调试回路则决定你仿真时能不能看懂状态。LCD1602 用 4 位模式可以省 4 个 IOProteus 里 LM016L 的 D0-D3 悬空即可USART1 的 TX 引脚接虚拟终端的 RX两个器件的地必须连到同一个 NET。把四个回路的电源域分开而不是全部接在同一个 5V 上是仿真图能重复运行的前提。2.2 Proteus 8.15 元件选型表与参数设置功能元件名参数设置仿真替代建议主控STM32F103C8时钟 8MHzProgram File 空双击后选 hex 文件热敏电阻POT-HGMin 0Max 1k滑臂输出模拟温度电压避免手动算阻值水位开关SW-SPST默认断开闭合表示低水位消失继电器RELAY线圈电压 5V触点另一端接加热灯(泛光)加热指示LAMP电压 12V 或 5V用灯泡亮灭代替电热丝显示器LM016L4 位模式RS 接 PC0EN 接 PC1D4-D7 接 PC2-PC5虚拟终端VIRTUAL TERMINAL波特率 9600接 PA9(TX1)参数表里的 POT-HG 是 Proteus 里最容易用错的元件。必须要让高端接 3.3V低端接 GND中心抽头接 PA0如果只接了高端和中心抽头中心抽头电压会被 ADC 输入阻抗影响仿真结果看似正常换到真实电路里就会漂。LAMP 的电压要大于 STM32 的 GPIO 高电平否则继电器触点闭合后灯不会亮起来一般仿真里设 12V 比较直观但需要额外一个 12V 电源端子。2.3 GPIO 引脚分配与 STM32 外设初始化我把引脚规划固定下来PA0 作为 ADC1_IN0 采温度PA1 作为水位状态输入PA2 作为继电器控制输出PC0-PC5 接 LM016LPA9-PA10 接 USART1。这样 LCD 和调试串口分在两条总线上初始化代码不会绕。void Periph_Init(void) { GPIO_InitTypeDef gpio; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOC, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE); // PA0 模拟输入给 ADC1 gpio.GPIO_Pin GPIO_Pin_0; gpio.GPIO_Mode GPIO_Mode_AIN; GPIO_Init(GPIOA, gpio); // PA1 水位输入下拉闭合水位开关时读到高电平 gpio.GPIO_Pin GPIO_Pin_1; gpio.GPIO_Mode GPIO_Mode_IPD; GPIO_Init(GPIOA, gpio); // PA2 推挽输出控制继电器 gpio.GPIO_Pin GPIO_Pin_2; gpio.GPIO_Mode GPIO_Mode_Out_PP; gpio.GPIO_Speed GPIO_Speed_2MHz; GPIO_Init(GPIOA, gpio); }代码说明标准外设库的RCC_APB2Periph_GPIOA不等同于给 ADC1 提供时钟ADC1 的时钟也要挂在 APB2 上所以单独ENABLEADC1。GPIO_Mode_AIN是 ADC 输入的唯一正确模式写成浮空输入GPIO_Mode_IN_FLOATING在真实板子上采样会受引脚阻抗干扰仿真里表现不明显。PA2 输出速度选 2MHz 就够了继电器是低频负载选 50MHz 在 Proteus 里会增加开关噪声逻辑分析仪波形边上会出现毛刺。LCD 和 USART 的初始化放在同一函数里但需要注意RCC_APB2Periph_GPIOC中包含了所有 PC 引脚时钟不用逐个使能。3. STM32 固件ADC 采样、加热状态机与虚拟串口输出3.1 温度采样从 ADC 原始值到“温度表”仿真中用电位器模拟 NTC 分压温度换算可以直接用线性关系。真实工程里 NTC 的阻值与温度是非线性的常见做法是预先把 0°C 到 100°C 每 5°C 对应的 ADC 值存成表再用插值法取中间值但这个仿真项目把电位器满量程映射为 0-100°C 更利于调整代码也更短。在 3.3V 参考电压下12 位 ADC 满量程是 4095电压百分比 adc * 100 / 4095就是当前模拟温度。uint16_t ADC_Read(void) { ADC_RegularChannelConfig(ADC1, ADC_Channel_0, 1, ADC_SampleTime_55Cycles5); ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); return ADC_GetConversionValue(ADC1); } uint16_t adc_filter(void) { uint32_t sum 0; uint8_t i; for (i 0; i 16; i) { sum ADC_Read(); } return (uint16_t)(sum 4); }两次采样之间至少间隔几十微秒否则 Proteus 的 ADC 模型可能来不及完成转换。均值滤波窗口取 16 次是因为它等于右移 4 位编译器能优化成移位运算改成 8 次或 32 次也行但 8 次时电位器快速拖动会产生明显跳变32 次会让温度变化响应慢半拍。采样时间参数55Cycles5是库里的命名实际含义是 55.5 个 ADC 时钟周期对工频 50Hz 没有特别意义主要用来保证内部采样电容充满仿真速度低于 50% 时采样频率会跟着降不用特意改这个参数。3.2 迟滞加热状态机和防干烧优先级状态机包含三个状态HEAT 加热、KEEP 保温、FREEZE 缺水保护。状态迁移条件放在集中函数里不要散落到主循环的 if 中仿真的边界条件才容易验证。具体迁移关系如下表。当前状态触发条件下一状态继电器输出HEAT温度 95°CKEEP关闭HEAT水位信号无效FREEZE关闭KEEP温度 85°CHEAT开启KEEP水位信号无效FREEZE关闭FREEZE水位恢复且温度 90°CKEEP关闭FREEZE水位恢复且温度 90°CHEAT开启但下一轮进入 KEEPFREEZE 到 HEAT 的路径设置成先 KEEP 再加热而不是直接放开是为了避免水位刚恢复瞬间产生一次加热脉冲。在 Proteus 仿真中这个细节看不到明显影响但真实饮水机里水位恢复后热敏电阻温度可能还在 90°C 以上直接加热会多一次无效继电器动作。代码实现如下void Heater_Update(float temp, uint8_t water_ok) { uint8_t relay 0; if (!water_ok) { state FREEZE; relay 0; } else { switch (state) { case HEAT: if (temp 95.0f) { state KEEP; } break; case KEEP: if (temp 85.0f) { state HEAT; } break; case FREEZE: state (temp 90.0f) ? KEEP : HEAT; break; } relay (state HEAT); } GPIO_WriteBit(GPIOA, GPIO_Pin_2, relay ? Bit_SET : Bit_RESET); }参数说明95°C 和 85°C 组成 10°C 迟滞区间。如果不需要沸腾只做 45°C 保温饮水机就把上阈值改成 45°C、下阈值改成 40°C。不要在 KEEP 分支里直接写else state HEAT那样会让主循环每次刷新时都尝试改变状态状态变量含义就不清晰了。water_ok的定义要和外设匹配PA1 设成下拉输入闭合开关读到高所以water_ok GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1)返回的Bit_SET视为有水。3.3 用 SysTick 和 USART1 做运行状态输出调试回的闭环要靠日志。Proteus 仿真中最好的数据显示方式是 USART1 接 VIRTUAL TERMINAL而不是反复看 LCD。为了不让刷新节奏被打乱用 SysTick 做时基主循环里每 500ms 打印一次温度、水位和继电器状态。USART1 初始化代码集中在Debug_UART_Init()中。void Debug_UART_Init(uint32_t baud) { USART_InitTypeDef usart; RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE); usart.USART_BaudRate baud; usart.USART_WordLength USART_WordLength_8b; usart.USART_StopBits USART_StopBits_1; usart.USART_Parity USART_Parity_No; usart.USART_Mode USART_Mode_RX | USART_Mode_TX; USART_Init(USART1, usart); USART_Cmd(USART1, ENABLE); }在主循环中打印字符串时保留float容易引发堆栈问题。建议打印整数扩展值char line[64]; int t_x10 (int)(temp * 10); sprintf(line, t%d.%d W%d RLY%d\r\n, t_x10/10, t_x10%10, water_ok, relay_st); Debug_Puts(line);说明t_x10在 0-99 之间只占一个字节而 float 在 printf 类函数中会触发大量栈空间。MDK-ARM 默认栈大小是 0x400Proteus 仿真 STM32 时对栈溢出非常敏感一旦出现 Unhandled exception先看调试输出里的sprintf是否带%f。把USART_SendString参数设计成char *而不是可变参数能进一步压缩栈开销。虚拟终端需设置同样的 9600 波特率否则打印出来是乱码。4. Proteus 8.15 联调hex 加载、时钟配置和虚拟终端排错4.1 在 Keil5 生成 hex 并在 Proteus 8.15 中加载开始仿真前先在 Keil5 里把工程准备好。第一次在 MDK 中创建 STM32 工程需要先安装对应的 STM32 芯片包否则 Device 列表里找不到 STM32F103C8。芯片包装好后在 Options for Target 的 Output 页勾选 Create HEX File这样每次编译都会生成.hex。接着把 hex 文件放到一个纯英文路径下例如D:\stm32_water\build\water.hex。中文路径在 Proteus 8.15 中加载时经常表现为灰色不可选或者 MCU 无动作。打开 Proteus 8.15从库中放置 STM32F103C8双击它在 Program File 右侧选择刚才的 hex。此时不需要把源码放进 ProteusProteus 只仿真二进制程序。如果 Keil5 编译通过但 Proteus 里没有任何反应先确认 Program File 是否真正选中了 hex再检查 MCU 属性里的 Clock Frequency。默认 8MHz 对应 STM32 的 HSI 振荡器如果你的代码里SystemInit()没有修改 PLL则此值保持 8MHz 即可。提示Proteus 里加载 hex 后如果 MCU 图标上出现红色问号说明文件名或路径有问题先检查英文路径。4.2 时钟、复位和电源设置Proteus 的 STM32 模型支持两种时钟来源一种是内部 HSI另一种是外部晶振。内部 HSI 是 8MHz外部晶振要连两个电容再到 GND代码中如果使用了RCC_HSEConfig等待 HSE Ready外部晶振缺失会卡死在启动初始化中。建议仿真时不接外部晶振Keil 工程也不开启 HSE。若必须验证外部晶振在 Proteus 里放置 CRYSTAL 12MHz 或 8MHz并将 MCU 属性中的时钟源改为相应模式。电源是另一个常见盲区。STM32F103C8 的 VDD 必须接 3.3V而 Proteus 默认电源端子只有 VCC/VDD 标签如果直接把 VCC 接成 5VGPIO 高电平会接近 5VADC 参考也变成 5V和代码里的4095 - 3.3V不一致。通常做法是给 MCU 专门画一个 3.3V 电源端子LCD1602 和继电器用 5V 电源端子。两边的地要共地否则虚拟终端和 ADC 非但不能工作Proteus 还可能出现 Simulation is not running 的提示。4.3 用虚拟终端观察 STM32 的调试输出在 Proteus 左侧终端模式里选择 VIRTUAL TERMINAL放到原理图上把 RX 端连接到 STM32 的 PA9。双击虚拟终端把 Baud Rate 改成 9600、Data Bits 8、Parity None这和Debug_UART_Init(9600)的配置一一对应。运行仿真后如果虚拟终端窗口没有输出依次检查三点STM32 的 PA9 是否连到 VIRTUAL TERMINAL 的 RX不是 TXUSART1 的时钟是否在RCC_APB2PeriphClockCmd里打开程序是否真的调用了Debug_Puts。有些教程会在USART_SendString里用空循环等待发送完成这在仿真中会拖慢主循环 500ms 节奏建议改成 FIFO 或直接丢尾Proteus 的虚拟终端缓冲足够容纳几百字节。4.4 联调中的常见问题排查表问题现象最直接原因处理方法MCU 引脚无波形hex 路径不存在或中文字符换纯英文路径重新加载LCD 全灭或乱码LCD 初始化时序不对检查 RS/EN 和 D4-D7 顺序ADC 恒为 0 或 4095POT-HG 低端悬空或 PA0 未设 AIN确认电位器三端都有网络标签继电器一直吸合状态机停在 HEAT温度阈值没到拖动电位器温度会改变虚拟终端乱码或空白波特率不一致或 TX/RX 接反统一 9600检查交叉连接程序卡在启动文件CubeMX 生成代码开启外部晶振关掉 HSE改用 HSI表格里最后一条常见于用 STM32CubeMX 的人。CubeMX 在 Clock Configuration 里默认选择 HSE但 Proteus 仿真图如果没画晶振程序启动就卡死在等待 HSE Ready。这个问题与 Proteus 8.15 版本无关属于配置策略从 CubeMX 导入到 MDK 后把时钟源改成 HSI 即可。5. 把电位器当温度旋钮调出真实的加热节奏5.1 用滑变电位器模拟温度观察迟滞和防干烧仿真开始前把 POT-HG 的中心抽头调整到 80% 位置虚拟终端应显示 80°C 左右继电器为 ON。缓慢往上拖电位器温度超过 95°C 后继电器应变为 OFF状态从 HEAT 变成 KEEP。此时开始向下拖电位器不要看到 90°C 就停止而是等到 85°C 以下继电器才重新吸合。这组操作证明 10°C 迟滞区间有效。如果继电器在 90°C 附近来回抖动说明代码里把阈值写成了单点判断回去检查状态机的分支。接着拨动水位开关让 PA1 变为低电平。无论电位器在 100% 还是 60%继电器输出都必须立刻为 OFF虚拟终端会打印W0。把水位开关恢复到闭合状态会先进入 KEEP只有温度低于 90°C状态才允许再次进入 HEAT。这个“先保温再加热”的顺序用普通的 if-else 实现容易漏状态机则已经把 FREEZE 分支单独写出来了。5.2 用 Logic Analyzer 看继电器开关波形Proteus 左侧仪器栏的 Logic Analyzer 可以直接观察数字信号。把通道 A 接到 PA2通道 B 接到 PA1运行 30 秒仿真。停止后你可以看到加热脉冲PA2 高电平时间段对应加热低电平时间段对应保温两个脉冲之间的间隔就是加热冷却周期。若脉冲宽度波动很大说明 ADC 均值窗口太小把adc_filter()的 16 次提高到 32 次即可。最后一次编译前把Debug_UART里的打印频率从 500ms 改成 2s虚拟终端日志减少后Logic Analyzer 波形更容易对齐。将要调试的状态码用另一个 GPIO比如 PA3输出平时只输出 0 或 1这样仿真运行速度更快Logic Analyzer 上也能看清状态机切换。配合 Proteus 的图表模式ADC 采样电压和继电器逻辑可以放在同一张图表里对照不需要反复看虚拟终端滚动。本文还有配套的精品资源点击获取