
简介本资源是一套基于STM32F103的智能控温水杯完整嵌入式开发项目面向电子类专业本科生、单片机初学者及课程设计/毕业设计实践者解决温度精准调控、水质监测与人机交互集成等典型物联网终端开发问题。压缩包含287个文件总大小23.88MB涵盖57个C源文件如stm32f10x_adc.c、stm32f10x_i2c.c等底层驱动、58个编译中间文件.d、39个头文件.h及Keil工程配置.uvprojx、.uvoptx、Proteus仿真工程.pdsprj和可执行固件.hex结构完整便于理解外设驱动、状态机逻辑与多任务协同。已有152人学习下载提供从硬件原理OLED显示、DS18B20测温、舵机控制、继电器加热/制冷、滑动变阻器模拟水质检测到蓝牙手机端通信的全链路实现配套WMV演示视频与DOCX说明文档助力快速掌握传感器融合、阈值动态配置、手动/自动双模式切换及低功耗交互设计。基于STM32的智能控温水杯设计20250315先聊几句背景。天冷了之后我发现自己对“喝水”这件事的要求变得特别具体水太烫没法入口太凉又不想喝保温杯拧开晾半天还是烫嘴普通杯子几分钟就凉透。市面上有那种恒温杯垫、恒温水壶但要么只能放在桌上用要么体积太大要么温度不可调要么压根是伪恒温——只保温不控温。后来想了一下为什么不自己做一杯子于是就有了这个项目基于STM32的智能控温水杯设计。这个项目做出来是什么效果呢杯子内置锂电池杯盖上有OLED屏幕轻触按钮可以设定目标温度比如45℃、55℃、65℃内置的温度传感器每秒采样一次杯体温度MCU通过PID算法控制加热膜让杯内液体稳定在设定温度附近误差控制在±1.5℃以内。充满电之后纯保温模式能撑一个白天连续加热模式实测能撑4个小时左右。整个过程从硬件选型、电路设计、外壳结构到嵌入式软件全部自己完成非常适合电子爱好者和嵌入式初学者作为进阶项目来参考。如果你正在学STM32但手头只有流水灯和蜂鸣器这种小demo这个项目是一个很好的综合练习——它牵扯到温度传感、PWM功率控制、ADC采集、OLED显示、定时器中断、PID算法、低功耗设计几乎把STM32里最常见的外设都用了一遍。文章后面会按照我的完整设计思路来写先说什么样的杯子才叫“智能控温”然后讲硬件怎么选型、电路怎么搭、软件怎么写、低功耗怎么调最后放一些实测数据和几个翻车现场。内容比较多建议收藏了慢慢看。1. 这杯水到底要解决什么问题需求拆解与产品定义1.1 市面上“恒温杯”的真实痛点在动手之前我认真拆解过市面上现有的产品。很多标称“恒温杯”的东西本质就是一个带保温功能的普通杯子加热功能只有底座杯子离开了底座就没用还有些是“加热杯垫”直接把杯子放上去底部加热但是温度控制很粗糙经常把水加热到五六十度就断电然后等水凉了再加热用户实际喝到的水温波动特别大。更重要的是这类产品普遍没有“目标温度”的概念——你不知道它会把水维持在多少度也不知道它什么时候在加热。我要做的东西逻辑上应该更像一个“小型的恒温水浴”而不是保温杯要能实时读温度、可设定温度、自动恒温、有明确的状态显示。1.2 需求指标怎么定在设计之前我把需求写了张表后续所有选型都绕不开这张表需求项指标要求备注控温范围35℃ ~ 70℃人体可入口温度区间控温精度±1.5℃以内水温波动不超过1.5度否则喝水体验很差显示方式实时显示当前温度/目标温度OLED或者LCD段码屏交互方式物理按键切换目标温度防水场景下不考虑触摸屏供电方式内置锂电池USB充电不能拖着电源线否则失去便携意义续航保温模式至少8小时按全天使用估算安全保护防干烧、过温保护、低压保护锂电池设备必须有结构材料食品级接触材料杯体部分要能装饮料实际做下来±1.5℃这个精度对于水温控制来说不算苛刻但对于“杯子”这个体积来说就需要认真对待PID参数和传感器位置。1.3 为什么选择STM32作为主控这里必须解释一下选型逻辑因为很多人一上来就想用ESP32或者Arduino。ESP32的优势是自带WiFi可以做App联动和云端监控听起来很酷但在这类便携式产品里WiFi模块的功耗和天线占位都是大问题。Arduino的优势是开发门槛低但它不适合做低功耗产品而且如果你以后想深入做嵌入式方向Arduino的抽象层会让你对底层寄存器、外设工作方式的理解慢很多。STM32在这个项目里几乎是“完美契合”外设丰富I2COLED、单总线DS18B20、定时器PWM加热控制、ADC电池电压采样、RTC低功耗唤醒一个芯片全部搞定不需要外扩任何逻辑芯片。功耗控制灵活STM32有Sleep、Stop、Standby三种低功耗模式Stop模式下电流可以压到微安级对便携设备非常友好。资料生态成熟不管用标准库还是HAL库遇到问题基本都能搜到答案。性价比高STM32F103C8T6或者G0系列几块钱一颗比Arduino的ATmega328便宜性能却好一大截。2. 硬件选型每一颗料都有它存在的理由2.1 主控具体型号的选择我最终选了STM32F103C8T6也就是网上俗称的“C8T6蓝板”上的那颗主控。为什么不是G0系列因为当时手头的调试工具、下载器、现有工程都是基于F1的F1的资料多到你根本不需要去看芯片手册就能把工程跑起来。G0系列确实在低功耗上比F1强但F103的Stop模式实测做到几十微安也不难对于一个水杯来说完全够用。选型的时候还留了一个备用方案如果后续想把产品做小可以换到STM32G0B1CET6——同样是Cortex-M0但是封装更小外设更现代功耗也更低。不过那要重新调时钟树和外设驱动我暂时没动。2.2 温度传感器DS18B20还是NTC热敏电阻这是本项目里最关键的一颗料因为控温的前提是温度测的准、测得快。项目DS18B20NTC热敏电阻10k B值3950接口单总线一根线ADC模拟量精度±0.5℃可到±0.1℃取决于分压电阻和查表精度通常±1℃响应速度中等取决于转换精度设置快成本大概2元左右几分钱软件复杂度需要时序驱动CRC校验简单查表就行抗干扰数字信号抗干扰好模拟信号PCB布局不好时会被PWM干扰最终我选了DS18B20。原因很简单精度高、数字接口抗干扰强、软件调试的时候出问题好排查。NTC要在不同温度区间做校准而且水杯里的加热膜是PWM斩波驱动的功率地回流如果处理不好NTC采到的模拟电压会抖动解决起来麻烦。用DS18B20就不存在这个烦恼。但这里有一个细节DS18B20的响应速度并不快12位精度模式下转换时间要750ms即使设成9位精度也要93.75ms。这意味着PID控制周期只能做到“秒级”不能像电机控制那样跑几十赫兹的控制频率。我一开设想的是100ms一个控制周期加了DS18B20之后发现根本跑不到最后把PID周期定在1秒效果反而更好——原因后面说。2.3 加热执行器PTC加热膜与加热管的选择水杯的加热执行器有几种方案陶瓷PTC加热片恒温特性好表面温度自限但功率密度低加热速度一般。硅胶加热膜柔韧性好可以贴在杯体侧壁或底部热传导面积大加热均匀。不锈钢加热管功率大加热快但体积大适合电热水壶这种产品杯子里塞不进去。我选了硅胶加热膜工作电压5V功率标称15W形状是圆形的直接贴在杯体底部外侧。硅胶加热膜的柔性让它能贴合杯底的曲面配合导热硅脂导热热阻很小。5V供电15W意味着电流3A这个电流对PCB布线和锂电池放电能力都是一个考验稍后讲电路设计的时候会展开。为什么不选12V方案因为水杯要用锂电池供电6V到12V需要升压电路效率会有折扣而且升压带来的纹波和水杯这种小型结构件里的EMI问题会很麻烦。5V刚好卡在“单节锂电池升压一点点就能到”的位置用一颗升压芯片就能解决。2.4 显示与交互OLED还是段码屏显示部分最开始考虑过三种LCD1602便宜但体积大视角差功耗大不适合这个产品。段码液晶功耗低但需要定制小批量成本很高而且显示内容固定不能显示温度曲线。0.96寸OLEDI2C接口功耗可控显示内容丰富可玩性强库文件成熟。最终选了0.96寸OLED。这里要提醒一下OLED屏幕的功耗并不低全亮的时候大概20~30mA这对电池续航是个不小的影响。所以我做了软件层面的处理屏幕常亮改成“点亮5秒后自动关闭”按键唤醒亮屏时降低亮度SSD1306的对比度寄存器可以调待机时彻底关屏。这个优化后面低功耗部分会细聊。按键用的是普通轻触开关6x6x5mm那种做了防水硅胶套。水杯场景下触摸按键更酷但电容触摸按键在潮湿环境下的灵敏度会飘尤其是杯体表面可能有冷凝水容易误触发。物理按键虽然土但可靠。2.5 电源方案锂电池、充电管理与升压电池用的是18650电芯容量2600mAh。18650在市面上容易买到正品能量密度够用单节串联电压3.7V饱和4.2V这个电压范围很典型。充电管理芯片用的是TP4056线性充电最大1A。这颗芯片太经典了外围只有几个电阻电容唯一的缺点是充电时发热量较大但水杯的充电场景是放在桌上充电不是边充边用所以可以接受。从3.7V升到5V用的是MT3608升压模块最大输出电流标称2A实际长时间跑3A会发热。我后面实测的时候发现加热膜启动瞬间电流接近4APTC冷态电阻低MT3608根本扛不住换了更大的电感并加了输入电容才稳住这个翻车细节后面会专门讲。主控、传感器、OLED这些低压部分用一颗XC6206-3.3V LDO从电池直接降压到3.3V供电。为什么不从5V那边再降因为升压后的5V噪声比较大而且来回转换效率低直接从电池取电更干净也更省电。3. 电路与结构设计从原理图到能装水的外壳3.1 系统电源架构整个系统的电源拓扑是这样的18650电池3.7~4.2V ├── XC6206-3.3V LDO → 3.3V给STM32/DS18B20/OLED ├── MT3608升压 → 5V给加热膜供电 └── TP4056充电管理外部USB接入时管理充电这个拓扑里有一个关键点MCU本身用LDO直接从电池取电而加热的5V是独立从升压电路出来的。这个分路的好处是加热膜工作时的大电流不会直接造成MCU供电电压跌落。否则加热电流一抽电池电压瞬间跌到3.4V以下STM32的ADC采样就会乱DS18B20甚至会复位。我把功率地和数字地在PCB上是单点连接的连接点放在电池负极入口处这样能尽量减少功率回路的噪声耦合到数字部分。3.2 加热驱动电路MOS管选型和PWM控制加热膜是纯阻性负载控制方式是一个低边N-MOS开关管栅极由STM32的定时器PWM驱动。选的是AO3400这是一颗N沟道MOSSOT-23封装Vgs(th)大约1.2V3.3V逻辑电平驱动没问题Rds(on)在Vgs4.5V时只有40mΩ左右3A电流下损耗不到0.4W基本不需要额外散热片。电路大概是这样// 伪代码示意实际电路图见PDF附件 // STM32 TIM2_CH1 PWM输出 → 限流电阻Rg(100Ω) → AO3400栅极 // AO3400漏极接加热膜负极加热膜正极接5V // 源极接功率地 // 加热膜两端并联续流二极管SS34防止感性关断尖峰有一点要注意加热膜本身虽然是电阻性负载但实际引线会有寄生电感关断瞬间会产生电压尖峰。我最初没有加续流二极管示波器上能看到栅极驱动波形有明显的振铃后来在加热膜两端并了一个SS34肖特基二极管做续流振铃才消失。这是开关感性负载的老生常谈但对于第一次做功率控制的同学来说很容易忽略。PWM频率选多少我试过1kHz、8kHz、20kHz。1kHz的时候能听到加热膜或电容发出轻微的“滋滋”声8kHz没有声音但MOS开关损耗稍微增加。最终用的是8kHz兼顾静音和效率。控温方式采用的是定时器PWM输出占空比由PID输出值映射PID输出范围0~1000映射到0%~100%占空比。当水温远低于目标温度时PID输出饱和相当于全功率加热接近目标温度时PID输出线性下降进入比例调节状态。3.3 温度采样电路与探头位置DS18B20的数据引脚需要一个4.7kΩ上拉电阻到3.3V。这里有个坑STM32的GPIO开漏输出内部也有上拉但内部上拉阻值约40kΩ不足以驱动DS18B20的单总线必须外接。探头位置也很有讲究。最初我把DS18B20贴在杯体侧面金属外壳上结果测出来的温度比实际水温低7~8℃。原因很简单杯体外壁有空气对流散热温度本来就低于杯内液体。后来改到杯底加热膜中心探头用导热硅胶固定在杯底不锈钢表面测出来的温度才和实际水温接近。补偿方面我实测加热时探头温度会比水温高1.2℃左右在软件里做了一个动态补偿通过PID偏差修正。DS18B20的封装建议用TO-92封装的探头型不锈钢管带引线那种这样容易通过杯底的小孔伸入到外壳内部再用704硅胶密封。3.4 外壳与防水结构设计里容易忽视的细节外壳是用3D打印的分三部分杯体外壳、杯盖模块、底部电源仓。杯体部分用的是食品级PP材料打印但3D打印的PP层间强度不是很好进热水后容易变形。我在内壁加了一层不锈钢水杯作为内胆就是市面上买的那种普通不锈钢便携杯外壳只是包裹和装饰。不锈钢内胆和加热膜之间涂了导热硅脂这样热阻最小。防水是这类产品最揪心的一环。我的方案是OLED屏幕和按键在杯盖顶部内部加了一个硅胶垫圈压紧。电池仓、PCB藏在底部独立腔体和杯体水路完全隔开。所有引线过孔用704硅胶填补密封。杯口到内部电路之间没有任何通孔即使漏水也只会漏到外壳和内胆之间。实测的时候故意倒了一杯水、放了一晚上第二天打开看内部电路没有进水的痕迹。但这里要坦白防水的“绝对性”是很难保证的USB充电口我用的是带硅胶塞的防水USB座能挡溅水不能泡水。4. 嵌入式软件设计状态机驱动的控温逻辑4.1 软件分层结构这个项目的软件不算复杂但我还是做了分层方便调试和复用// 文件结构 // app/ // ├── main.c // 入口初始化调度 // ├── state_machine.c // 全局状态机 // ├── pid.c // PID控制器 // └── display.c // OLED屏幕内容管理 // drivers/ // ├── ds18b20.c // 温度传感器驱动 // ├── oled.c // OLED屏幕驱动 // ├── pwm_heat.c // 加热PWM控制 // ├── battery_adc.c // 电池电压采样 // └── key.c // 按键扫描 // bsp/ // ├── usart.c // 调试串口 // ├── i2c.c // I2C通信 // └── timer.c // 时钟与延时分层的核心原因只有一个调试。温度不准的时候只需要改ds18b20.cPID震荡的时候只需要改pid.c完全不牵动其他模块。如果全揉在main里出了bug会非常难定位。4.2 全局状态机设计整个系统围绕一个状态机运行状态定义如下状态含义进入条件主要行为BOOT开机自检上电初始化外设、检测传感器、显示开机画面STANDBY待机休眠无操作5秒后OLED关屏进入Stop低功耗模式MEASURE测温定时唤醒/按键唤醒读DS18B20温度、采样电池电压HEAT_CTRL控温运行目标温度设定完成执行PID控制、更新显示ALARM异常保护温度超限/干烧检测关闭加热、报警提示状态之间通过一个统一的事件队列来切换每个状态都有进入、执行、退出三个回调函数。这种写法看起来比“裸奔”的if-else多了一点代码量但后续加功能会非常舒服——比如我想加一个“记忆上次目标温度”的功能只需要在STANDBY退出的时候把目标温度写入Flash进BOOT的时候读回来页面完全不紊乱。typedef enum { ST_BOOT 0, ST_STANDBY, ST_MEASURE, ST_HEAT_CTRL, ST_ALARM } sys_state_t; // 状态切换示例 void sys_state_machine_run(void) { switch (current_state) { case ST_STANDBY: if (key_pressed) { sys_state_transition(ST_MEASURE); } break; case ST_MEASURE: if (temp_read_done) { if (target_temp_set) { sys_state_transition(ST_HEAT_CTRL); } else { sys_state_transition(ST_STANDBY); } } break; // ...其他状态 } }4.3 温度采集驱动DS18B20的时序与可靠性DS18B20驱动网上有很多版本但很多代码只验证过“能用”没有验证过“稳定”。我在做这个项目时重点处理了两个问题第一个是时序准确性。DS18B20对时序要求比较苛刻初始化时序中主机拉低480us以上的复位脉冲然后释放总线等待60us后读取存在应答写时序时每个时隙至少60us读时序时主机拉低1us后释放并在15us内采样。如果你用HAL_Delay()来做精度显然不够。我改用定时器微秒延时函数也就是DWT延时基于Cortex-M3内核的DWT计数器实现精度非常准。顺带说一句DWT延时在调试整个项目的过程中帮了我大忙后面在调试串口空闲中断的时候也依赖它来测时序。第二个是CRC校验。DS18B20每次读取9字节数据第9字节是CRC校验码很多例程直接忽略了这个字节。我能理解大家觉得麻烦但在产品级设备上这行代码绝对不能省。因为温度数据一旦受到干扰读出来的值可能是错到离谱的“85℃”或者“-20℃”PID控制逻辑会以为水真的到了85℃然后停止加热这时候如果实际水温只有40℃用户喝到的就是温水。我程序里加了CRC校验和异常值过滤连续3次校验失败就进入ALARM状态保证不出这种问题。温度读取的主体逻辑是float ds18b20_read_temperature(void) { uint8_t data[9]; float temp 0.0f; ds18b20_reset(); ds18b20_write_byte(0xCC); // Skip ROM ds18b20_write_byte(0x44); // Start conversion // 等待转换完成12位精度约750ms delay_ms(750); ds18b20_reset(); ds18b20_write_byte(0xCC); // Skip ROM ds18b20_write_byte(0xBE); // Read scratchpad for (int i 0; i 9; i) { data[i] ds18b20_read_byte(); } if (ds18b20_check_crc(data) ! 0) { return TEMP_ERROR; // CRC校验失败返回错误标识 } int16_t raw (data[1] 8) | data[0]; temp (float)raw * 0.0625f; // 12位分辨率LSB 0.0625℃ return temp; }实际使用中发现750ms的等待时间太长了一个1秒控制周期里大半个时间都在等转换。优化方法是把转换指令和读温度指令拆成两个阶段上一条控制周期结束时发出转换指令下一个周期开始时先读温度这样750ms的等待时间就自然被控制周期覆盖了主控不用傻等。这是嵌入式领域很经典的“流水线式”思路——把等待时间藏到后台去。4.4 控温算法为什么我最终选了增量式PID最初我用的是最简单的bang-bang控制滞回比较也就是温度低于目标温度1℃就开加热高于目标温度1℃就关加热。这个逻辑写出来10行代码实测效果却很差——水温在目标温度附近来回震荡幅度甚至超过±3℃而且因为DS18B20是贴在不锈钢内胆外壁的温度响应有滞后震荡就更严重。后来换成增量式PID。公式如下// 增量式PID float pid_incremental(float setpoint, float measurement) { float error setpoint - measurement; float delta_error error - prev_error; float output Kp * delta_error Ki * error Kd * (error - 2 * prev_error prev_prev_error); prev_prev_error prev_error; prev_error error; return output; }增量式PID的输出是控制量的增量不会被Windup问题困扰代码简洁非常适合单片机运行。PID参数整定我用的是最土的“临界比例度法”先把Ki和Kd设为0只保留Kp从很小值开始慢慢加大观察温度曲线。增大Kp直到温度出现等幅振荡记下临界增益Kc和振荡周期Tc。按经验公式计算Kp 0.6 × KcKi 2 × Kp / TcKd Kp × Tc / 8。我的最终参数大约在Kp2.8、Ki0.4、Kd0.15附近控制周期1秒。这个参数下从25℃加热到设定温度50℃第一次过冲约2℃之后在目标温度±1℃内稳定。后面实测部分会给出具体的温度曲线数据。控制周期为什么设成1秒而不是更短有三个原因DS18B20的转换时间决定了最快也就1秒钟能稳定获得一次温度数据。水温系统本身是一个大惯性系统加热膜的热量要通过不锈钢内胆再传递给水水的热容又很大温度变化本来就慢。控制频率太高反而会因为传感器噪声和量化误差导致PID输出抖动。控制频率低了之后MOS管的开关频率也低了开关损耗下降电池续航会好一些。这个现象其实是很多新手理解不了的点——以为控制越快越好但实际上对于温度这类大惯性对象秒级控制周期已经完全足够。频率不是越高越好而是要和被控对象的响应时间匹配。4.5 OLED显示与按键交互OLED屏上显示的信息分两页第一页是主状态页显示当前温度、目标温度、加热状态图标一个圆形小水滴一样的图标加热时亮起第二页是设置页显示目标温度范围的条状图通过按键/-调整。长按按键2秒切换页面短按调节目标温度。OLED驱动用I2C接口软件上用SSD1306的经典驱动库然后我在其基础上加了一个“5秒无操作自动关屏”的逻辑。关屏不是简单的清屏而是将SSD1306的Display Mode切到全灭同时把DC-DC电荷泵关掉这样能省掉屏的功耗。唤醒时重新初始化显示数据。按键扫描用了简单的状态机消抖8ms扫描周期连续读到两次相同电平才算有效。这里有个细节睡眠状态下按键触发需要配置为外部中断唤醒EXTISTM32F103的PA0引脚支持EXTI0我在低功耗代码里配置了按键上升沿唤醒。void key_init(void) { GPIO_InitTypeDef gpio_init {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); gpio_init.Pin GPIO_PIN_0; gpio_init.Mode GPIO_MODE_IT_RISING; gpio_init.Pull GPIO_PULLDOWN; HAL_GPIO_Init(GPIOA, gpio_init); HAL_NVIC_SetPriority(EXTI0_IRQn, 2, 0); HAL_NVIC_EnableIRQ(EXTI0_IRQn); } // 外部中断服务函数中唤醒MCU void EXTI0_IRQHandler(void) { if (__HAL_GPIO_EXTI_GET_IT(GPIO_PIN_0) ! RESET) { __HAL_GPIO_EXTI_CLEAR_IT(GPIO_PIN_0); sys_wakeup_flag 1; } }5. 低功耗与续航优化一杯水如何撑过一整天5.1 功耗预算表做低功耗设计之前先把所有模块的功耗列出来才清楚钱花在哪里。模块工作电流待机/关闭电流说明STM32F103C8T6运行模式约20mA72MHzStop模式约20uA主控DS18B20工作约1.5mA待机约1uA温度传感器0.96寸OLED全亮约22mA关屏约0.5uA显示MT3608升压空载约2mA关闭约1uA有EN引脚给加热膜供电加热膜15W/5V约3A无不可关闭时才能省系统静态约0.5mA—LDO静态漏电等从表中可以算出如果所有模块保持运行状态即使不加热系统电流也在44mA左右2600mAh的电池只能撑约59小时。看似不错但问题是如果不加热水很快就会凉然后PID又会频繁启动加热这个功耗才是大头。所以低功耗设计的关键不是把运行电流压到多低而是尽量缩短“加热时间段”以及在没有加热需求时让系统整体进入低功耗睡眠。5.2 STM32低功耗模式的选择STM32有Sleep、Stop、Standby三种低功耗模式我对比了一下模式电流CPU唤醒源使用场景Sleep约7mA停止任意中断不适合本场景Stop约20uA停止EXTI/RTC适合待机时用Standby约2uA掉电复位/RTC/WAKEUP引脚RAM内容丢失不适合状态保存第一次直接用Stop模式发现了一个坑进入Stop模式后I2C外设的时钟也被停了导致从Stop唤醒后OLED再刷新会失败。解决方法是唤醒后重新初始化I2C外设和OLED屏幕。后来我把所有外设的重新初始化逻辑都挂在sys_exit_standby()里这样从任意低功耗状态唤醒都能恢复。待机流程是这样的显示页面5秒无操作。清除屏幕关闭OLED的DC-DC。关闭MT3608的EN引脚断开5V加热电源。DS18B20进入待机模式不发起转换。配置RTC定时唤醒默认每30秒唤醒一次。调用HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI)。唤醒后流程更关键——不能一唤醒就立刻开加热因为此时温度数据是过期的。我先读一次DS18B20温度然后根据温度和目标值的差值决定下一步温差大于5℃持续进入控温运行模式连续测量。温差小于2℃维持待机30秒后再测一次。如果用户在睡眠期间按键唤醒直接进入控温运行模式。5.3 功耗优化带来的实际效果完成上面的优化后实测数据如下待机电流不加热、不显示、MCU停止约32uA其中MCU的20uA是大头OLED关屏后几乎不耗电。保温模式水温维持在50℃环境温度18℃平均电流约0.9A按2600mAh算续航约2.8小时——这是我没想到的结果。后来分析50℃的保温在环境18℃下热损失太大加热膜频繁启动。如果环境温度25℃、目标温度45℃平均电流降到0.5A左右续航约5小时。这和北方冬天、南方冬天的使用体验是吻合的。纯待机模式不控温只偶尔测温实测平均电流2.3mA2600mAh撑一个多月。这个模式对应的是“水已经凉了开机时只显示温度不启动加热”的场景。所以这里有个很实在的结论便携式恒温杯最大的敌人不是MCU功耗而是物理散热。加热需要的能量是硬性的省来省去都省不掉。你唯一能做的就是把“非加热时间”压短让系统赶紧睡觉。我还尝试过再加一个“预热到目标温度后自动关加热自然冷却到低于目标温度3℃再重新加热”的滞回策略实测能省大约15%的加热功耗。这个策略的本质是接受温度小幅波动换取更低的平均功耗。如果你对控温精度要求没那么高可以试试。6. 实测数据与几个翻车现场6.1 控温效果实测给杯子装了约400ml水初始水温26℃目标温度50℃环境温度24℃。从开机到温度稳定记录关键时间点时间水温状态0s26.1℃开始加热PID饱和全功率60s34.5℃加热全功率120s41.2℃加热全功率180s46.8℃PID输出开始下降240s49.4℃接近目标300s50.3℃轻微过冲360s49.8℃回转420s50.1℃稳定稳定后约3分钟的波动范围是49.2℃到50.6℃也就是±0.7℃以内达到了设计指标。注意这个波动包含了DS18B20的量化误差0.0625℃和PID的自然波动。做出来之后我连续实测了三天的使用夜间待机到第二天早上重新开机后温度显示的误差也能保持在±1℃。6.2 翻车一MOS管型号选错的教训第一次打样的时候我用了STM32的GPIO直接驱动加热膜没有用MOS管——想的是GPIO的推挽输出最大输出20mA加热膜3A的电流根本带不动所以我心想“加个三极管驱动”应该就行。结果我用S8050三极管驱动3A电流状态下三极管的饱和压降Vce(sat)大约0.3V功率损耗接近1W直接烫到手不能碰。而且三极管是电流驱动型器件基极电流需要大约150mA——STM32的GPIO根本提供不了。后来换AO3400 MOSFET问题彻底解决。这里也给新手一个选型建议驱动大电流负载优先选低电压驱动的MOSFET而不是三极管。MOSFET是电压驱动栅极基本不消耗电流几毫安的GPIO驱动电流就够了。6.3 翻车二电池电压采样不准导致充电状态误判我用STM32的ADC采样电池电压用来显示剩余电量和过放保护。一开始电路很天真电池电压经过两个电阻分压100kΩ33kΩ直接送到STM32的ADC引脚ADC的参考电压是3.3V按理说能算出来。但实测读数一直偏低3.9V的电池测出来只有3.2V。排查后发现是分压电阻阻值太大了——ADC的采样保持电容在转换瞬间需要从分压网络抽取电流100kΩ级别的源阻抗导致采样值被拉低。解决办法是换用10kΩ 3.3kΩ 分压并在ADC引脚对地并联一个100nF电容做电荷缓冲。这样源阻抗降到约7kΩ采样稳定。后来我还顺便把ADC配置改为多次采样取平均16次读数就非常稳定了。6.4 翻车三DS18B20在加热时读到的温度跳变这是最诡异的一个问题在加热时DS18B20读到的温度偶尔会突然跳到“-55℃”或者“85℃”这种极值而且出现的频率和PWM占空比高度相关——占空比越大出现越频繁但占空比为零的时候基本不会出现。一开始怀疑是温度传感器损坏换新了还是一样。后来我用示波器抓DS18B20的数据线波形发现PWM驱动的加热膜线缆对单总线产生了强烈的电磁干扰。数据线上的波形出现大量毛刺CRC校验失败。解决办法做了三步DS18B20的探头线和加热膜的走线在PCB上分开布置不在同一区域平行走线。DS18B20数据线增加一个100Ω的串联电阻位置放在MCU引脚附近可以抑制高频反射。在软件里增加“连续两次读数偏差超过3℃则丢弃本次数据”的滤波逻辑。这三步做了之后温度数据再也没有出现过异常跳变。6.5 实测过程中发现的传感器位置校准方法这里再分享一个很实用的校准方法把DS18B20探头和一根参考温度计放在同一杯水中然后在20℃到70℃范围内取5个校准点记录硬件读数和标准温度的偏差。实测发现这个偏差在整个量程内接近线性于是我用两点校准低温点和高温点算出偏移量写入Flash。之后所有温度显示和PID计算都用校准后的值。这样做完之后实测误差从±1.2℃收窄到±0.4℃。虽然DS18B20本身有±0.5℃的精度上限但通过校准可以进一步减小系统误差。7. 项目扩展方向与实际使用心得做完这个项目手里这个水杯已经在办公室用了两三个月。说几个日常使用中的真实感受和可扩展方向。首先是保温性能的物理限制。3D打印外壳的隔热效果并不好冬天在户外环境50℃的热水放置半小时就降到42℃左右加热膜启动频率会变高电池消耗明显加快。如果想让产品在寒冷环境下更好用可以给外壳内侧加一层气凝胶隔热垫或者用真空保温杯体配合加热膜这样能大幅减少热损失。其次是“目标温度记忆”几乎是必须的功能。我每次喝水都想要45℃如果每次开机都要重新从默认的60℃调下来会很烦。后来我加了一段Flash读写每次设置目标温度后写入Flash的最后一个页开机时读出来作为初始目标温度。STM32内部Flash的写入寿命通常有上万次每天设置20次也能用好几年没问题。然后是远程控制的可能性。如果你有兴趣把项目再推进一步可以加一颗ESP-01S模块通过串口和STM32通信做手机App调温、温度历史曲线。但我个人不建议做进这个杯子里——WiFi模块在工作时会有额外的辐射和功耗杯盖上加天线也影响外观。如果要做成“桌面端恒温杯”可以考虑。如果坚持便携还是专注优化低功耗和控温稳定性更实在。最后再说一个很多人会忽略的点水杯的入口安全和密封安全性。加热膜、锂电池、PCB这些部件的安全认证和材料合规在DIY阶段是不需要深思的但如果计划做成产品售卖需要认真考虑食品接触材料FDA/食品级PP/不锈钢304或316、锂电池的UN38.3认证、整机的防水等级IPX5以上以及加热元件的过温保险。我做的这台样机虽然功能完整但离量产还有一段距离。从我自己的体验来说这个项目的最大收获不是“做出来一个能恒温的杯子”而是在一个小体积、电池供电、有实时控制需求的真实场景中把嵌入式开发的各个环节都完整走了一遍从指标定义到选型评估从电路设计到软件架构从PID调参到低功耗优化再到各种电磁干扰和物理结构问题。每一个环节都不是孤立的知识点而是环环相扣的工程决策。市面上很多教程只教你“点亮一颗LED”“驱动一个屏幕”但真正让你成长的项目永远是这种把“能跑”变成“好用”的过程。如果你也想做类似的智能水杯或者便携式温控产品我的建议是先把需求表列清楚再选择一个最影响体验的核心指标比如控温精度围绕它展开选型软件架构上不要嫌麻烦状态机框架会帮你省掉大量调试时间PID参数不要期待一次调好准备一支温度计慢慢观察曲线最后一定要给自己的设备留一个串口调试口——在这个项目里UART打印的调试信息救了我无数次。这个项目目前的开源资料和代码我还整理在本地仓库包含全部的PCB工程文件、3D打印外壳文件、STM32工程源码和一份完整的BOM表。等我把它们整理成可阅读的文档后会同步更新出来。如果你想接触实际的项目而不是一直在demo里打转从这个小杯子开始方向不会错。本文还有配套的精品资源点击获取