
简介本资源是一套基于STM32F103的智能控温水杯完整嵌入式设计工程面向电子类专业本科生、嵌入式初学者及课程设计/毕业设计实践者解决多传感器融合控制、人机交互与蓝牙远程协同等典型物联网应用开发问题。压缩包含287个文件涵盖57个C源文件如stm32f10x_adc.c、stm32f10x_i2c.c等外设驱动、58个编译中间文件.o/.d/.crf、8个Keil工程配置文件.uvprojx/.uvoptx/.sct等以及OLED显示、DS18B20测温、舵机杯盖控制、继电器加热/直流电机制冷等核心功能模块代码整体大小23.88MB。已有152人学习下载资源提供可直接在Proteus 8.15中仿真的完整软硬件方案包含蓝牙手机端通信协议实现、阈值动态设置逻辑、自动/手动双模式切换机制及多任务状态显示界面配套.hex固件与详细工程结构便于快速理解STM32多外设协同开发流程与实际工程组织方式。 做嵌入式这么多年手边的东西能智能化改造的几乎都折腾过一遍。去年冬天在办公室开会倒好的热水忙起来忘了喝等想起来已经凉透了来回接水又麻烦就想干脆自己动手做个能一直维持在合适温度的杯子。这正是今天要聊的——基于STM32的智能控温水杯设计。主控选了STM32F103C8T6核心功能就三件事实时测温、自动加热、恒温锁定。做之前也搜过市面上的智能保温杯要么价格贵要么协议封闭没法自己扩展索性从零开始设计实现。这篇内容适合正在学STM32的嵌入式新手也适合对DIY硬件感兴趣、想把手边物件智能化的朋友我会把方案选型、电路设计、软件逻辑到调试排错整个链路都讲一遍踩过的坑也会一并整理出来。1. 项目整体设计与方案选型1.1 智能控温水杯的功能需求拆解做项目第一步不是画原理图而是把需求写清楚。我最初列的需求只有一条“让杯子里的水一直保持60度左右”。但拆开一想这个简单需求背后包含了不少工程问题怎么测水温、怎么加热、怎么控温不失控、人机交互怎么做、电池能撑多久、杯子没水了怎么办。逐一展开后就变成下面这份功能清单实时温度采集与显示精度正负1摄氏度以内目标温度可调默认55摄氏度入口舒服可在45到70摄氏度之间调节自动加热与恒温维持温度波动控制在正负1.5摄氏度以内OLED屏幕显示当前温度、目标温度和加热状态两个按键完成模式切换和温度加减锂电池供电Type-C充电尽量延长待机时间防干烧保护检测到异常温升立刻断电过热保护温度超过80摄氏度强制关闭加热这里重点说说我为什么把“目标温度可调”放在需求里。一开始我以为固定55度就够了后来想想不同场景差异很大泡蜂蜜水45度、日常饮用55度、泡茶65度到70度。如果硬件上不做调节功能后期想改就只能改程序重新烧录非常蠢。所以按键调温是第一天就确定的硬需求宁可多花一点交互逻辑的时间也要做。防水和密封也要考虑水杯是液体环境电路板不可能完全避开蒸汽和水汽。我用的方案是电路板与杯体物理隔离传感器探头单独封装后用防水胶密封加热片贴在杯底外侧。这个思路在硬件设计阶段就要定下来否则后面打样出来才发现没法密封就要全部推翻重来。1.2 为什么主控选STM32而不是Arduino或ESP32主控选型对比过三个方向8位单片机比如STC89C52、Arduino Nano、ESP32、STM32F103C8T6。8位单片机直接排除因为ADC、定时器PWM、低功耗模式都要额外折腾而且开发调试体验一般。Arduino Nano开发速度快但有一个致命问题后续想扩展蓝牙、WiFi或者接更复杂的传感器和算法时性能和外设资源会比较紧张库函数封装太厚也不利于理解底层原理。ESP32性能很强、也带WiFi做一杯联网测温的水杯绰绰有余。但它的生态偏物联网方向如果项目核心是控温算法和硬件电路就有点杀鸡用牛刀。更重要的是ESP32进入深度睡眠后的唤醒机制和定时器使用跟STM32差异很大我用STM32更顺手而且STM32F103C8T6只要几块钱一片物料成本确实低。STM32F103C8T6这颗料在智能控温场景里有几个很实用的点72MHz主频完全够跑温度滤波和PID运算1毫秒到1秒的定时器范围广多个ADC通道可以同时采集水温、电池电压和环境温度还支持多种低功耗模式。HAL库和标准库资料铺天盖地遇到问题搜索引擎一搜就能找到解决方案。对于这个项目它的性价比和生态优势非常明显。选型时也考虑了未来扩展如果后续要加手机App控制可以直接在I2C总线上挂一个蓝牙透传模块比如HC-08或JDY-23程序里加一个串口命令解析就行不需要换主控。这也是选STM32的一个重要原因——外设资源多后续升级不用动硬件架构。1.3 温度采集与加热方案的对比取舍温度采集方案我对比了三种DS18B20数字温度传感器、NTC热敏电阻、PT100铂电阻。PT100精度最高但需要仪表放大器或专用调理电路成本和体积都不适合塞进杯体。NTC便宜、响应快但要自己搭分压电路、做ADC采样、再查表或套Steinhart-Hart公式换算还要考虑参考电压精度和线材电阻调试工作量主要集中在软件标定部分。DS18B20是单总线数字输出直接读出一个16位的温度值分辨率最高0.0625摄氏度-10到85摄氏度范围内的精度典型值是正负0.5摄氏度对这个项目完全够用。虽然单总线时序对实时性有一点要求但HAL库加延时函数完全可以搞定。实际做下来DS18B20配4.7k上拉电阻线长控制在20厘米以内读数非常稳定。水质、水蒸气对数字信号的影响也比模拟信号小很多所以我最终选了DS18B20。加热方案对比了继电器开关控制和MOS管PWM调功。继电器体积大、有机械寿命、通断时还有滴答声而且开关式控温必然导致温度过冲和回落幅度大做不出平滑的恒温效果。MOS管PWM调功是更好的选择用定时器输出可调占空比的PWM信号驱动N-MOS管实现对加热片功率的连续调节。温度接近目标时降低占空比温度远离时全功率加热恒温精度可以从正负2摄氏度提升到正负1摄氏度以内。加热片本身选的5V/5W PTC加热膜贴在杯底外侧标称功率适中。PTC有自限温特性温度超过设计值后电阻自动增大、功率下降这相当于多了一层物理保护和软件保护形成双保险。选5W而不是10W的原因有三条电池容量有限、杯体小散热面积有限、小功率更容易控制过冲。实测下来从室温25度加热到55度大约需要8到10分钟这个速度对于日常使用完全能接受毕竟不是烧开水。2. 硬件电路设计关键点2.1 最小系统与电源供电设计STM32F103C8T6的最小系统包括8MHz外部晶振、复位电路10k上拉加0.1uF电容到地、BOOT0下拉到地从Flash启动、SWD调试接口PA13/PA14。这些是标准配置值得注意的是电源去耦电容每个电源引脚旁边放一个100nF陶瓷电容位置尽量靠近引脚这能有效避免高频噪声导致单片机随机复位。VDD和VDDA之间加一个10欧姆电阻和1uF电容滤波ADC采样会更稳。供电链路的整体方案是18650锂电池标称3.7V满电4.2V→ TP4056充电模块 → 电源路径 → 升压到5V → LDO降到3.3V给MCU和传感器。为什么要升压到5V因为加热片是5V额定电压如果直接用电池3.7V供电功率只能达到额定的55%左右功率与电压平方成正比加热速度会明显变慢。所以用了MT3608升压模块把电池电压升到5V再在5V基础上用AMS1117-3.3稳压给MCU供电。这里有个工程细节加热时大电流会让电池电压瞬间跌落如果不做处理后级LDO可能因为输入电压不足而输出抖动单片机会复位。我的做法是MCU的供电独立放在升压输出之后加热驱动直接从升压输出取电两者在PCB上分开走线功率地和信号地在电池负极单点汇聚。实测加热开启瞬间MCU供电纹波控制在50mV以内没有出现复位。充电和放电同时进行的情况也需要考虑。TP4056充电芯片最大充电电流设了1A如果边充电边全功率加热总电流可能达到2.5A左右。TP4056和MT3608的散热压力都很大所以我在固件里加了一条逻辑检测到充电状态充电芯片的充电指示信号为低时限制加热最大占空比不超过60%这样总电流控制在1.8A以内实测稳定运行没有过热问题。2.2 温度采集电路与加热驱动电路DS18B20的接口电路非常简单VCC接3.3VGND接GNDDQ引脚通过4.7k电阻上拉到3.3V然后接到STM32的一个GPIO我用的PA1。这个4.7k上拉电阻是必须的没有它通信会不稳定经常出现读到85摄氏度这种错误值DS18B20的上电默认值或者干脆超时无响应。还有一点要提醒DS18B20有三种供电模式——外部供电、寄生供电和读写用强上拉供电。为了省一根线有些人会玩寄生供电只用两根线但这个模式下传感器转换期间需要总线维持高电平提供电流和单片机通信逻辑容易互相干扰。我这个项目里老老实实用外部供电三根线可靠性优先。加热驱动电路我用的低边N-MOS开关方案。电路结构是STM32的PA8输出PWM → 100欧姆串联电阻 → NPN三极管S8050→ 10k下拉电阻 → P-MOS不对更常规的是直接用N-MOS做低边驱动。实际电路是这样的MCU的PWM引脚通过100欧姆电阻接到N-MOSAO3400的G极同时在G极和S极之间加一个10k下拉电阻防止上电瞬间误触发。D极接加热片负极加热片正极接5VS极直接接地。AO3400的VGS阈值在1.4V左右3.3V的GPIO电平可以直接驱动不需要额外的电平转换。为了降低PWM开关损耗我选了100Hz的PWM频率。有朋友问为什么不用20kHz避开音频噪声——因为加热片是大热容负载100Hz和20kHz对控温效果没有本质区别低频反而开关损耗小、对走线要求低。实际测试时用示波器看了G极波形上升沿和下降沿都小于1微秒没有振铃MOS管发热可以忽略不计。为了保护电池和电路我在电池正极串联了一个可恢复保险丝PPTC500mA保持电流/1A跳闸电流加热回路单独串了一个3A的保险丝。这个设计在短路测试时救了我一次当时不小心把5V和GND搭在一起PPTC立刻跳闸保护了电池如果没有这个元件电池很可能要报废。2.3 OLED显示、按键与电池电量检测的接口设计显示模块选的0.96寸SSD1306 OLEDI2C接口SCL接PB6SDA接PB7STM32F103的I2C1引脚。OLED的I2C地址默认是0x3C程序里配置好地址就行。I2C总线上需要上拉电阻我用了4.7k对3.3V上拉。虽然STM32的I2C内部有弱上拉但外接上拉能显著提高通信稳定性尤其是线比较长或者OLED模块本身没有上拉电阻的情况下。OLED屏幕放在杯盖位置用FPC软排线连接主板。装的时候要注意一点OLED工作的供电电压范围通常是3.3V到5V我直接用3.3V供电亮度完全够还省去了电平转换问题。屏幕刷新策略上温度数字每秒刷新一次就够了不需要高速刷新否则既增加功耗又容易闪烁。按键用了两个轻触开关KEY1模式键接PB0KEY2加减键接PB1都配置成内部上拉输入按下为低电平。实际做过对比内部上拉电阻约30k到50k对低速按键完全够用不需要外接上拉。软件上用10ms轮询加延时消抖效果稳定。有的方案用定时器中断处理按键我这个项目里按键不是高频操作放主循环里扫描就够了省去中断上下文切换的麻烦。电池电压检测用的是ADC1的通道4PA4通过一个分压网络接到电池正极。分压电阻选择要特别注意既要保证ADC输入电压不超过3.3V又要控制静态功耗。我用的是100k和200k串联分压电池满电4.2V时ADC输入约2.8V留了余量这条分压支路的静态电流只有14微安对电池续航影响可以忽略。软件里用公式“电池电压 ADC值 × 3.3 / 4096 × 3”还原再加一个简单的一阶低通滤波消除噪声。3. 软件架构与控温逻辑实现3.1 主程序状态机与任务调度这个项目没有上RTOS因为任务就那么几个读温度、跑控温逻辑、刷新OLED、扫按键、看门狗喂狗。裸机的轮询调度完全跑得过来。我定义了五个状态IDLE待机、HEATING快速加热、HOLDING恒温维持、COOLING自然降温、FAULT故障保护。状态之间通过温度事件切换这样的架构比一坨if-else堆起来的逻辑好维护得多后续加功能也方便扩展。主循环里的任务顺序固定读取温度传感器 → 更新控温状态机 → 刷新OLED → 扫描按键 → 喂狗。每个周期大约10毫秒温度传感器因为转换时间要750ms所以实际上是每10ms读一次OneWire暂存器只读上次转换结果每800ms发一次转换命令。这样既能保证温度的实时性又不会频繁发起转换导致总线拥堵。看门狗用的是IWDG独立看门狗溢出时间设为2秒主循环喂狗。这个设计有个坑要说一下如果温度转换期间阻塞超过2秒看门狗会复位系统。DS18B20转换最坏时间是750ms理论上不会超时但万一传感器损坏导致总线拉低延时函数会卡在那里。所以我在读DS18B20时加了超时保护每次读时序都判断返回值一旦通讯失败马上退出并置错误标志不会傻等。这样看门狗永远有活路。配合状态机我还加了一个未接水检测逻辑。DS18B20测的是杯底温度如果杯子里没水加热片的热量无法被水吸收杯底温度会快速上升。我在HEATING状态里判断如果温度在3秒内上升超过5摄氏度就判定为干烧立刻进入FAULT状态。这个保护逻辑在空杯测试中非常灵敏比单纯依靠最高温度阈值更及时因为等到温度达到80摄氏度时加热片可能已经热得不行了。3.2 DS18B20温度采集驱动与滤波算法DS18B20属于单总线器件时序要求的精度比较高能到微秒级。使用HAL库时我直接用delay_microseconds()函数实现了三个基本时序复位脉冲480us低电平后释放总线等480us、写比特写0是60us低电平、写1是15us低电平加45us高电平、读比特单片机拉低1us后释放总线在15us内采样。驱动代码的核心部分如下// 初始化DS18B20返回0表示成功1表示失败 uint8_t ds18b20_reset(void) { uint8_t presence 0; DS18B20_DQ_OUT_LOW(); delay_us(480); DS18B20_DQ_OUT_HIGH(); delay_us(70); presence DS18B20_DQ_READ(); // 读从机存在脉冲 delay_us(410); return presence; }主逻辑里读温度是两步先发0x44启动温度转换等待一段时间后再发0xBE读取暂存器中的16位温度数据。为了提高效率我让转换命令和读取命令分开转换期间MCU去干别的事比如刷新OLED每800ms再回来读结果。这样整个系统不会被传感器阻塞。温度数据处理方面直接读出的原始值需要乘以0.0625才是摄氏度。我建议不要直接用浮点数STM32F103没有硬件FPU浮点运算虽然也能跑但没必要。我的做法是用整数保存原始LSB值乘以625再除以10000得到以十分之一摄氏度为单位的整数温度。这样显示和比较都方便还避免了浮点误差。滤波算法我测试过两种。滑动平均滤波窗口大小为5能有效滤除偶发毛刺但滞后大概3到5秒控温时容易导致过冲一阶低通滤波新值 旧值 × 0.8 当前值 × 0.2响应快、滞后小更适合做实时控温。最后选了后者。如果只做显示不做控温滑动平均滤波看起来更稳但控温系统需要快速感知温度变化一阶低通是更合理的选择。3.3 控温策略滞回控制与PID的工程取舍很多人在控温项目上第一反应就是上PID但实际做水杯这种大热容、大延时的系统时标准位置式PID直接上往往效果很差。原因很简单水温系统的滞后时间可能长达几十秒P项太大会振荡I项太大会过冲D项对噪声又极其敏感。所以我的控温策略是两段式温度远离目标时用全功率快速加热温度接近目标时切换到滞回控制或低功率PWM逼近。实际控温逻辑如下假设目标温度T目标55度当前温度T当前。如果T当前 T目标 - 5加热占空比输出80%全速加热不用100%是为了留一点余量防止电池压降导致系统不稳。如果T当前在T目标 - 5 到 T目标 - 0.5 之间占空比按线性公式计算占空比 (T目标 - T当前) / 5 × 100%但上限60%。一旦温度达到T目标 - 0.5进入HOLDING状态用滞回控制温度低于T目标 - 1时以30%占空比加热温度高于T目标以上时停止加热。这套策略的好处是末端加热功率小、不会出现过冲。实测恒温状态下温度在54.5到55.5摄氏度之间波动基本满足正负1摄氏度的要求。PID方案我也试过一版调参花了一个晚上效果确实更好一些温度波动能控制在正负0.5摄氏度以内但对传感器噪声非常敏感一旦DS18B20线路上有干扰微分项就会疯狂跳动。如果不做D项限幅加热MOS管会像抽风一样频繁开关。所以我最终用的是折中方案恒温维持阶段用小功率PWM加滞回不追求极限精度但系统鲁棒性非常好。关于PID调参的经验如果读者非要试我可以给一组粗略的初始值采样周期500msKp15Ki0.02Kd5。实际调整时用整定法先把Ki和Kd设为0只加Kp直到系统出现轻微振荡然后记录振荡周期T再按Ziegler-Nichols经验公式算出Ki和Kd。但记住水杯的热惯性特别大纯P控制就会有较大的稳态误差一定需要I项来消除。4. 实操过程与核心环节实现4.1 开发环境搭建与工程初始化开发环境我用的STM32CubeMX Keil MDK组合。CubeMX负责生成初始化代码Keil负责编译调试。如果你习惯用VSCode也可以配GCC工具链但Keil对刚上手的人更友好一些调试界面直观在线仿真断点查看变量比命令行GDB好使。用CubeMX配置工程的步骤是选择芯片型号为STM32F103C8Tx在Pinout视图里把PA1设为GPIO_OutputDS18B20、PA8设为TIM1_CH1PWM输出、PB6/PB7设为I2C1、PA4设为ADC1_IN4、PB0/PB1设为GPIO_Input。时钟树配置里外部8MHz晶振作为HSESYSCLK设为72MHzAPB1为36MHzAPB2为72MHz。这样定时器时基和ADC采样时钟都能得到最佳分配。一个重要细节是DS18B20用的GPIO必须配置为开漏输出模式。因为开漏模式下引脚只能拉低拉高靠外部上拉电阻这样才能在引脚输入输出切换时避免上下管同时导通产生反向电流。如果用推挽输出模式输出高电平会直接和DS18B20的漏极开路输出产生冲突严重时可能损坏器件。ADC配置要注意采样时间。STM32F103的ADC采样时间可以设置从1.5周期到239.5周期对于高阻源比如电池分压网络的输出阻抗约66.7k采样时间设短了会导致采样电容没充满读到的电压偏小。我设的55.5周期加上12位分辨率实测误差在20mV以内完全够用。生成工程后我习惯在main.c里加一个状态回调结构体把所有外设操作包起来。这样看起来干净后期调试也方便。同时把编译优化等级设置为-O2因为Keil默认的-O0生成的代码在72MHz下跑温度滤波时会有可能出现周期性卡顿开启优化后运行更流畅。4.2 核心代码模块实现详解整个固件拆成七个模块ds18b20.c、pid_ctrl.c其实我后来主要用滞回、oled_display.c、key_scan.c、adc_battery.c、power_manage.c、main.c。每个模块职责单一接口清晰不会出现改一处崩一片的问题。控温状态机是核心代码大致如下void ctrl_temperature(void) { int16_t cur_temp get_temperature(); // 单位0.1°C int16_t target settings.target_temp; // 单位0.1°C switch (ctrl_state) { case IDLE: if (cur_temp target - 10) { ctrl_state HEATING; TIM1-CCR1 800; // 80%占空比 } break; case HEATING: if (cur_temp target - 50) { // 接近目标切换到逼近模式 ctrl_state APPROACH; } break; case APPROACH: { int16_t diff target - cur_temp; uint16_t duty diff * 12; // diff*12/100 duty% if (duty 600) duty 600; // 限制最大60% if (duty 100) duty 100; // 限制最小10% TIM1-CCR1 duty * 10; // 换算到10位PWM if (cur_temp target) ctrl_state HOLDING; break; } case HOLDING: if (cur_temp target - 10) { TIM1-CCR1 300; // 30%占空比加热 } else if (cur_temp target 5) { TIM1-CCR1 0; // 停止加热 } if (cur_temp target - 100) ctrl_state HEATING; break; case FAULT: TIM1-CCR1 0; // 禁用PWM输出 OLED_ShowFault(); break; } }这段代码里的占空比换算要特别留意。TIM1的ARR我设的是1000所以CCR1的值就是0到1000对应0到100%占空比。800就是80%300就是30%逻辑上很直观。如果你想输出引脚上直接看到高电平开启加热需要在CubeMX里把PWM极性设置为High如果要反逻辑就把PWM极性设成Low。这个配置项经常有人搞反导致上电就加热或者怎么也不加热。OLED显示模块用SSD1306的驱动核心是设置好初始化序列和显存缓冲。我用的库是网上常见的0.96寸OLED驱动支持中文显示需要给字库。因为显示内容不多温度数字、目标温度、状态图标我只用了ASCII字符和几个取模好的小图标没有做完整的中文字库省了很多Flash空间。按键扫描部分我用的是主循环里每10ms读一次GPIO检测到按下后先延时20ms再确认确认是一致低电平才算是有效按键。短按KEY1切换显示模式显示当前温度/显示目标温度长按3秒进入设置模式短按KEY2加减目标温度步进2摄氏度。设置模式下OLED自动提示当前目标值3秒无按键自动退出设置模式并保存到Flash。4.3 硬件联调与实测数据记录所有模块代码写完后第一次上电调试前先做了三件事用万用表检查电源输出电压、检查所有信号线没有短路、把程序通过ST-Link烧进去。然后再接传感器和加热部分避免一个低级错误毁掉整个电路板。联调过程中第一次加热测试就发现了一个现象显示温度在某个时刻突然从28度跳到75度然后马上又跳回来看了半天发现是DS18B20的转换结果读取时序出错。根源是我给DS18B20发转换命令后没等转换完成就发读取命令传感器返回的是上次转换的旧数据。解决办法是加一个状态标志位发完转换命令后把下一轮读取延迟800ms。这个坑大家如果遇到温度跳动优先检查这里。实际控温测试我记录了完整的升温曲线。室温25度目标55度测试环境是敞口玻璃杯加350ml水。前6分钟温度从25度平稳升到46度左右第8分钟到52度第9分钟时到达54.5度后进入APPROACH模式。由于末端占空比从80%降到30%温度惯性被有效抑制最终稳定在54.8到55.4度之间没有出现超过1度的过冲。整个过程大约10分钟电池电压从4.1V降到3.95V消耗约180mAh电量照这个耗电速度充满电的18650电池可以支持大约20次完整加热循环。恒温阶段我又连续测了60分钟温度波动最大幅度0.7度主要波动发生在刚开门窗有冷风的时候。整体表现完全符合预期。这里我要强调一个实际做的时候很容易忽略的点测试时不要用金属杯子或导热性太强的容器因为杯壁热量散失快、温度均匀性差传感器采集到的温度波动会明显偏大这不是电路的问题而是物理环境的问题。5. 常见问题与排查技巧实录5.1 温度读数跳变或误差偏大的排查温度读数跳变是最常见的问题我遇到过几种典型情况。第一种是DS18B20读取到85摄氏度固定值这通常是通信时序失败后返回的默认上电值。排查方法是先查复位时序用示波器看DQ引脚上是否有480us的低电平脉冲再看读时序时数据线是否能在15us内返回有效电平。检查上拉电阻是否虚焊或者阻值不对4.7k是标准值用10k也能工作但抗干扰能力会下降。第二种是温度读数比实际水温偏高或偏低好几度。这个往往是传感器封装贴合不好导致的。如果探头没有和杯底金属紧密接触中间有空气层热阻会非常大测到的温度和实际水温差异可能达到5度以上。解决方法是填充导热硅脂然后用机械方式固定探头贴紧杯底。我后来还加了一块铝片压在探头上效果非常明显。第三种是温度显示在正常值附近随机跳动1到2度。这通常和电源噪声有关如果加热MOS管开关时把噪声耦合进DS18B20的数据线会导致读到的数据异常。处理办法是DS18B20的三根线尽量和加热线分开放置不要在PCB上长距离平行走线如果有条件可以在数据线上串一个100欧姆电阻。5.2 加热过冲大和温度振荡的解决思路过冲问题在控温项目里几乎必然遇到。我第一次用纯滞回控制时目标55度结果温度冲到了58度才停下来然后慢慢回落到52度又重新加热整个温度曲线像锯齿一样。根因是加热片的余热效应温度传感器已经达到目标值但加热片和杯体本身还有大量余热在继续加热水体。解决过冲的通用思路就是提前降功率。我采用的APPROACH模式就是在距离目标5度时开始逐步降低占空比让加热功率和热散失逐渐平衡而不是等到目标温度才一刀切。实测过冲从3.2度降到0.6度效果显著。另外我调整了传感器测量周期从800ms改成500ms提升控温反应的实时性也减少了一部分过冲。温度振荡在目标值附近来回穿越超过1度通常有两个原因采样周期太长导致控制动作滞后或者加热功率阶梯太大。增大采样频率有效但前提是DS18B20的时序要扛得住频繁的转换会消耗更多电流。功率阶梯方面我将在恒温维持阶段的加热功率从30%进一步降到20%振荡幅度缩小了一半。如果你的系统温度在目标值附近小幅快速抖动优先检查是不是传感器噪声太大而不是控温逻辑问题先加滤波再看效果。5.3 续航短和低功耗模式的配置笔记第一版固件没有做低功耗整机待机电流大约40mAOLED亮着、单片机72MHz全速跑如果一直用一颗2000mAh的18650也就撑50个小时左右如果加上每天加热几次一天半就没电了这肯定不行。后来做的优化分三层。第一层是降低主频不需要跑重计算时把系统时钟从72MHz切换到8MHz内部HSI。这一层就省了大约一半的MCU电流。第二层是OLED开关屏策略设置无操作60秒后自动熄屏按下任意按键唤醒。OLED亮屏时电流约20mA左右熄屏后几乎为零。第三层是MCU进Stop模式在熄屏基础上如果检测到温度保持在目标值附近且按键无操作超过5分钟进入Stop模式用RTC定时唤醒每1秒采一次温度温度偏离设定值超过3度才唤醒全功能。实测优化后纯待机电流从40mA降到了12mA左右其中大部分是升压芯片和LDO的静态功耗。如果只计算MCU本身的功耗在Stop模式下电流只有10uA左右外设的静态功耗才是大头。所以在低功耗设计中单片机的低功耗模式只是一个环节外围电路的待机电流管理同样重要比如加热控制部分的MOS管栅极电阻通过10k下拉到地在待机时不会有额外电流OLED模块的电源被一个PMOS管开关控制熄屏时完全断电这些措施叠加起来才真正解决了续航问题。关于这个项目的扩展方向我后来还做了一版带蓝牙透传的通过手机App可以查看温度曲线和历史数据。核心思路就是串口接HC-08模块把温度数据和控制状态用JSON字符串发出去手机端用一个简单的串口调试App就能解析。整个改造花了不到半天STM32的串口资源非常充足加个模块完全不影响之前的控温逻辑。最后再分享一个小技巧调试控温系统时建议在固件里加一个串口日志输出函数把状态机状态、当前温度、目标温度、占空比这些关键参数存到一个环形缓冲里通过串口定时打印。联调时我用电脑串口助手实时看这些数据比盯着OLED屏幕有效率得多。温度曲线可以用串口波形工具画出来调节参数看得一清二楚这个习惯让我少走了很多弯路。本文还有配套的精品资源点击获取