
简介一套基于51单片机的温度控制与蓝牙控制系统全套资料面向电子类学生、嵌入式初学者以及课程设计、电子竞赛人群。内容覆盖Proteus仿真、单片机源代码、硬件原理图、实物制作流程和配套手机APP从项目搭建到整机调试形成完整闭环。系统具备环境温度实时检测与液晶显示功能手机通过蓝牙模块连接后可同步查看温度数据用户既能用板上按键也能在APP端设置温度报警上、下限断电后系统自动保存设置重启无需重新配置。当温度超出上下限时对应指示灯与继电器联动可方便地外接散热风扇或加热片适合作为恒温控制、智能家居类项目的参考方案。压缩包约46MB已有221人学习下载资料内含可直接编译的Keil源码、Proteus仿真工程、原理图及实物制作流程说明手机APP源码也一并提供便于二次开发与快速复用。1. 51单片机温度控制蓝牙系统为什么值得从头做一遍课程设计和工程实训里基于51单片机的温度控制蓝牙系统是出现频率最高的题目之一。它把 DS18B20 测温、51 单片机控制、HC-05 蓝牙透传、手机 APP、继电器驱动加热器串成一条完整的链路做一遍等于把嵌入式产品从采集到控制的常见环节全走了一遍。有人一上来就换 STM32 或 ESP32但 51 在本题里依然最稳Proteus 仿真资料最全、单总线时序好调、硬件成本几十元而且用普通 I/O 口模拟单总线协议的方式比直接调库更能讲清底层原理面试时也更容易被认可。下面按原理图、Keil C51 源代码、Proteus 仿真、手机 APP、实物制作流程的顺序推进把每一段最容易翻车的地方单独讲透。这套内容适合正在做课设、想攒完整项目经验、或者准备从仿真跨到实物的读者全程不依赖任何特定的开发板焊万能板也能跑。2. 51单片机硬件设计与原理图DS18B20、HC-05 和继电器驱动怎么接原理图决定后面代码能省多少事。这套系统的硬件可以拆成四块测温电路、单片机最小系统、蓝牙接口电路、继电器驱动电路。只要这四块的电平关系和供电关系不出错代码基本不用改硬件。2.1 测温传感器选型对比与 DS18B20 单总线电路选传感器是第一个决策点。市面上 51 课程设计最常见的三种测温方案差异非常明显对比项DS18B20LM35NTC 热敏电阻输出信号数字单总线模拟电压阻值变化是否需要 ADC不需要需要51 要外扩 ADC需要 ADC 加查表精度±0.5℃-10~85℃典型 ±0.5℃1%~5%依赖标定测温范围-55~125℃-55~150℃-40~150℃接线复杂度3 根可寄生供电3 根2 根结论很直接LM35 和 NTC 都要额外处理模拟量而 AT89C52、STC89C52RC 这类 51 单片机没有内置 ADC外扩 ADC0809 会让原理图和代码都多出一大块。DS18B20 是数字输出51 只靠一根普通 I/O 口线就能模拟时序读温度课程设计里选它几乎不需要犹豫。DS18B20 的典型接法VCC 接 5V数据手册允许 3.0~5.5VGND 接地DQ 数据脚接 51 的 P3.7并在 DQ 与 VCC 之间接一个 4.7kΩ 上拉电阻。上拉电阻的取值不是随便填的单总线空闲时靠上拉保持高电平从机应答和写 0 时要把总线拉低4.7kΩ 是手册推荐值换成 10kΩ 会让边沿变缓远距离或快速连续读写时容易出错换成 1kΩ 则拉低瞬间灌电流偏大。这里有一个实物上极其常见的坑DS18B20 的 DQ 线虚焊或者忘了加上拉电阻程序照样跑但读回来的温度会固定停在 85.0℃。85℃ 不是室温而是 DS18B20 上电后暂存器的默认值说明主机根本没读到传感器数据。仿真阶段不会暴露这个问题因为 Proteus 模型的总线状态理想化所以画原理图时要把上拉电阻当成必画元件而不是可选项。// 用串口调试助手给 HC-05 发 AT 指令时的典型配置流程 ATNAMETempCtrl // 改模块名注意 AT 指令必须回车结尾 ATUART9600,0,0 // 设波特率 9600无校验1 个停止位 ATROLE0 // 设为从机模式手机主动连它 ATPSWD1234 // 配对密码默认就是 1234这段 AT 指令在接实物前的模块配置阶段用得上。把 HC-05 的 KEY 引脚接高电平再上电模块就进入 AT 指令模式此时蓝牙不可见、只走串口需要用 USB-TTL 转接板连到电脑来配置配置完把 KEY 拉低重新上电模块才回到透传模式。2.2 HC-05 蓝牙模块与 51 单片机最小系统接线细节HC-05 是最常用的蓝牙转串口模块六针排针里真正必接的只有四根VCC、GND、TXD、RXD另外 STATE 可以做连接指示灯KEY 用来切换 AT 模式。它与 51 的连接关系如下HC-05 引脚接到 51 哪里说明VCC公共 5V板载 3.3V LDO峰值电流约 40mAGND系统 GND必须与 51 共地否则收不到数据TXDP3.0 / RXD3.3V 高电平对 51 的 TTL 输入足够RXDP3.1 / TXD 分压后接入5V 不能直进需要 1k 串 2k 分压KEY拨码开关到 VCC上电前接高进入 AT 模式STATEP1.1 或 LED 串 1k配对成功后输出高电平电平匹配是 HC-05 接线的核心。HC-05 是 3.3V 逻辑模块51 的 TXD 在高电平时输出接近 5V直接灌进 HC-05 的 RXD 长期使用会损伤模块常见做法是用两个电阻分压1kΩ 串在信号路径上2kΩ 从 HC-05 的 RXD 下拉到地分压后高电平约为 5×2/(12)≈3.33V。反过来HC-05 的 TXD 输出 3.3V 高电平而 51 的串口输入按 TTL 电平判定高电平阈值只要 2.0V 就能识别所以这一路可以直连不需要电平转换。KEY 引脚容易被忽略。很多人在实物阶段手机搜不到模块查到最后是 KEY 悬空导致模块一直在 AT 模式或者状态不确定。KEY 在正常透传模式下应该保持低电平建议原理图上用下拉电阻到地再用拨码开关或跳线帽选择是否接 VCC这样调试 AT 模式时拨一下就能切换不用反复拔线。51 最小系统本身没什么悬念AT89C52 或 STC89C52RC11.0592MHz 晶振加两个 30pF 负载电容复位电路用 10μF 电解电容和 10kΩ 电阻EA 引脚接 VCCP0 口外接 10kΩ 排阻上拉。晶振频率必须选 11.0592MHz而不是常见的 12MHz原因后面讲串口波特率时会算给你看。2.3 继电器驱动、电源去耦与完整元件清单继电器的驱动电路是原理图里第二个高频翻车点。51 的 P1、P3 口高电平输出能力很弱直接驱动继电器线圈根本吸合不了而且继电器线圈是感性负载关断瞬间会产生反向电动势轻则引起单片机复位重则打坏 I/O 口。常规做法是用一只 NPN 三极管做开关基极串 1kΩ 电阻接 P1.0发射极接地集电极接继电器线圈的一端线圈另一端接 5V同时在线圈两端反向并联一只 1N4007 续流二极管。三极管选 S8050 或 2N2222 都够用因为 5V 继电器线圈电流一般在 30~70mA。电源去耦决定了这套系统能不能长时间运行。继电器吸合瞬间电流变化很大如果 51 和继电器共享一条细走线会在 VCC 上拉出明显毛刺轻则蓝牙断连重则单片机复位。原理图上要给每颗芯片的 VCC 和 GND 之间加 104 去耦电容电源入口处并一只 100μF 电解电容继电器单独从电源根部取电不要和 HC-05 共用一根飞线。完整元件清单如下照着备料基本不会缺东西元件规格数量用途AT89C52 或 STC89C52RCDIP401主控DS18B20TO-92 封装1数字测温HC-05 蓝牙模块板载 3.3V LDO1串口透传晶振11.0592MHz1串口波特率基准瓷片电容30pF2晶振负载电解电容10μF/16V1复位电路电阻4.7kΩ1DS18B20 上拉排阻10kΩ 9 脚1P0 口上拉三极管S80501继电器驱动二极管1N40071续流继电器5V SPDT1控制加热器或风扇电阻1kΩ、2kΩ 若干4~5 只分压、基极限流、LED 限流原理图工具用立创 EDA 或者 Altium Designer 都行51 项目规模小立创 EDA 免费版足够画完还能直接导出 BOM 表去下单。画图时要特别注意三条地线USB 电源地、继电器功率地、HC-05 信号地最终要在电源根部单点汇合这样能明显减少继电器开关对数字电路的干扰。3. Keil C51 源代码DS18B20 时序、串口中断与继电器控制怎么写代码整体拆成三个独立模块DS18B20 驱动、串口收发与指令解析、温度阈值控制。51 没有硬件单总线外设DS18B20 的时序全部要靠 I/O 口模拟这是整套代码里最需要耐心的部分。Keil 里别忘了在 Options for Target 的 Output 选项卡勾选 Create HEX File否则 Proteus 没东西可加载。3.1 DS18B20 复位、写时序、读时序的 C 语言实现单总线协议只有一根数据线所有通信都由主机发起。一个完整操作周期分三步主机发复位脉冲从机回存在脉冲主机发 ROM 指令这里只用 0xCC 跳过 ROM主机发功能指令0x44 启动温度转换0xBE 读取暂存器。复位和读写都有严格的时序窗口代码里用延时函数和nop() 微调。#include reg52.h #include intrins.h sbit DQ P3^7; // 单总线数据线 void delay_us(unsigned int us) { while (us--) { _nop_(); _nop_(); // 12MHz 12T 模式下约 1~2us } } // 复位拉低 480us 以上释放后读存在脉冲 bit ds18b20_reset(void) { bit present 0; DQ 0; delay_us(500); // 主机拉低 DQ 1; // 释放总线 delay_us(60); // 等从机应答窗口 present DQ; // 读到 0 说明 DS18B20 在线上 delay_us(400); // 补足复位时间 return present; } // 写一个字节低位在前每个位时隙约 60us void ds18b20_write_byte(unsigned char dat) { unsigned char i; for (i 0; i 8; i) { DQ 0; // 产生写时隙 _nop_(); DQ dat 0x01; // 写 0 则继续拉低写 1 则释放 delay_us(60); // 保持到整个时隙结束 DQ 1; _nop_(); dat 1; } } // 读一个字节读时隙内 15us 采样 unsigned char ds18b20_read_byte(void) { unsigned char i, dat 0; for (i 0; i 8; i) { dat 1; DQ 0; // 启动读时隙 _nop_(); DQ 1; // 释放总线 _nop_(); if (DQ) dat | 0x80; // 采样点 delay_us(60); } return dat; }写时序与读时序很容易混淆。写 0 和写 1 的区别在于释放总线的时机写 0 要一直拉低满 60us写 1 则在 1us 后释放靠上拉电阻把总线拉高。读时序里主机只负责启动时隙从机在 15us 内把总线拉低表示读 0所以主机必须在启动读时隙后立刻采样晚于 15us 就会读错。这两个函数的基础时序全部来自芯片数据手册的时隙图手工测量的宽容度其实比想象中大但每个位时隙最少 60us、两个时隙之间要有 1us 以上恢复时间这两个底线不能破。下面是把原始 16 位数据转换成温度值的主函数// 读取暂存器返回 16 位原始温度实际温度 raw * 0.0625 unsigned int ds18b20_get_temp(void) { unsigned char tl, th; unsigned int raw; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过 ROM总线上只有一个设备 ds18b20_write_byte(0x44); // 启动温度转换 delay_ms(800); // 12 位分辨率最长转换时间 750ms ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读暂存器从第 0 字节开始 tl ds18b20_read_byte(); // 温度低字节 th ds18b20_read_byte(); // 温度高字节 raw ((unsigned int)th 8) | tl; return raw; }这里有一个必须说清楚的细节很多人用while(!DQ);等待转换完成但这条语句在快速单片机上会失效。原因在于 0x44 命令的最后一个写时隙结束时总线是释放状态DS18B20 需要几微秒时间处理命令并开始拉低总线这期间 DQ 处于高电平while(!DQ)会直接跳过等待读到上一次的旧温度。换成固定 750ms 延时最省心代价是每次测温阻塞约 0.8 秒主循环里每 1 秒采一次温就完全够用。4 位小数的计算很简单16 位 raw 值按 0.0625℃/位换算unsigned int 完全可以容纳 -55℃ 到 125℃ 对应的原始值范围。3.2 滞回控制温度阈值比较与继电器输出代码温度控制的执行逻辑有两种主流写法简单的阈值比较和带滞回的比较。如果只设一个阈值比如 30℃温度在 29.9℃ 和 30.1℃ 之间波动时继电器会在几秒内反复吸合断开触点打火、寿命急剧下降。这里用上限 30℃、下限 25℃ 的滞回区间来避免这个问题。#define TEMP_UPPER 30 // 上限阈值单位 ℃ #define TEMP_LOWER 25 // 下限阈值单位 ℃ sbit RELAY P1^0; // 继电器控制脚 bit relay_state 0; // 当前继电器状态1 表示吸合 // 滞回控制加热到上限断开冷却到下限重启 void temp_control(unsigned int raw) { unsigned char temp_now; temp_now (unsigned char)(raw 4); // 取整数部分忽略小数 if (temp_now TEMP_UPPER) { RELAY 0; // 停止加热 relay_state 0; } else if (temp_now TEMP_LOWER) { RELAY 1; // 开始加热 relay_state 1; } // 温度落在 25~30 之间时保持现状什么都不做 }raw 4是把 12 位整数位取出来的惯用做法因为 raw 乘以 0.0625 后高 4 位加半个字节就是整数部分直接右移四位成本最低。25℃ 到 30℃ 这 5℃ 就是滞回区间也叫回差。控制继电器这类执行器时回差越大继电器动作频率越低但温度波动也越大5℃ 对加热灯、保温箱这类大惯性对象是合理的起步值想要更精细可以缩到 2℃。如果控温精度要求再高常见做法是把继电器控制换成 PWM 调功即在 30 秒周期里按比例调整加热时间这时候滞回控制就不够看了需要改成增量式 PID。PID 在 51 上也能跑但代码量和调参工作量不是一个量级课程设计和大多数恒温场景用滞回加合适回差已经完全够用。3.3 串口初始化和蓝牙指令解析帧格式与解析状态机串口是 51 和 HC-05 之间的唯一通道波特率必须和模块一致。HC-05 出厂默认 9600、8 位数据、无校验、1 个停止位所以串口初始化也按这个配置来// 串口初始化模式 1允许接收波特率 9600 void uart_init(void) { SCON 0x50; // SM00 SM11 模式1REN1 允许接收 TMOD 0x0F; // 只改定时器 1不动定时器 0 TMOD | 0x20; // T1 工作方式 28 位自动重装 TH1 0xFD; // 重装值9600 波特率对应 0xFD TL1 0xFD; TR1 1; // 启动定时器 1 ES 1; // 开串口中断 EA 1; // 开总中断 } void uart_send_byte(unsigned char dat) { SBUF dat; while (!TI); // 等待发送完成 TI 0; }TH1 为什么是 0xFD这就是前面强调 11.0592MHz 晶振的原因。51 工作在 12T 模式下定时器 1 方式 2 的波特率计算公式是波特率 晶振频率 / (12 × 32 × (256 - TH1))。把 11.0592MHz 和 TH10xFD 代入分母是 12×32×3115211059200/1152 正好等于 9600误差为零。如果换成 12MHz 晶振算出来是 10416和 9600 差了约 8.5%串口打印出来的就是乱码。帧格式可以自己定义但一定要有帧头、长度或数据区、校验和三个要素。我给这套系统定的协议如下方向帧内容含义手机 → 单片机AA 01 00 01查询当前温度校验0x010x00手机 → 单片机AA 02 1E 20设置上限阈值为 300x1E校验0x020x1E手机 → 单片机AA 03 01 04手动吸合继电器校验0x030x01单片机 → 手机AA 81 TH TL SUM温度上报TH/TL 为 16 位 raw 值的高低字节校验和取功能码加数据区的低字节接收方重新计算后不一致就整帧丢弃。串口接收用中断实现解析逻辑写成简单的状态机// 串口中断先支持字符调试命令再解析 4 字节二进制帧 void uart_isr(void) interrupt 4 { unsigned char ch, sum; static unsigned char buf[4]; static unsigned char idx 0; if (TI) { TI 0; return; } // 发送完成标志 if (!RI) return; RI 0; ch SBUF; // 调试用字符命令Proteus 虚拟终端里直接敲 if (ch T || ch t) { uart_send_temp_report(); return; } if (ch 1) { RELAY 1; relay_state 1; return; } if (ch 0) { RELAY 0; relay_state 0; return; } // 二进制帧解析 if (idx 0 ch ! 0xAA) return; // 未同步帧头丢弃 buf[idx] ch; if (idx 4) { sum (buf[1] buf[2]) 0xFF; // 重算校验和 if (sum buf[3]) { switch (buf[1]) { case 0x01: uart_send_temp_report(); break; // 查询温度 case 0x02: TEMP_UPPER buf[2]; break; // 更新上限 case 0x03: RELAY buf[2] 0x01; relay_state (buf[2] ! 0); break; } } idx 0; // 一帧处理完回到等帧头状态 } }字符命令T、1、0是为 Proteus 仿真阶段准备的毕竟虚拟终端里直接敲字符最方便二进制帧是手机 APP 实际走的协议仿真时可以用串口助手的 HEX 发送来验证。解析状态机没有用复杂的有限状态机结构而是靠idx变量和静态数组完成4 字节固定帧长让代码量控制在 30 行以内逻辑也一目了然。要注意的是串口中断里调用了uart_send_byte这个函数内部while(!TI)会阻塞如果主程序同时也在发数据可能产生竞态这个规模的项目里风险可接受但如果你追求严谨可以改成查询方式发送。温度上报函数把 raw 值拆成高低字节加上功能码 0x81 和校验和一起发出// 上报温度AA 81 TH TL SUM void uart_send_temp_report(void) { unsigned int raw; unsigned char sum; raw ds18b20_get_temp(); uart_send_byte(0xAA); uart_send_byte(0x81); uart_send_byte((unsigned char)(raw 8)); uart_send_byte((unsigned char)raw); sum (0x81 (raw 8) raw) 0xFF; uart_send_byte(sum); }校验和的计算注意一点(unsigned char)raw取低字节后参与加法时先把 raw 强制转换再相加避免 16 位和 8 位运算混在一起产生歧义。这个函数在主循环里被调用查询命令来自手机时才会真正触发一次完整测温所以虽然测温阻塞 800ms也不会影响用户操作节奏。4. Proteus 仿真 51 单片机蓝牙系统的搭建与手机 APP 协议联调Proteus 仿真的价值在于在焊板子之前就能验证 DS18B20 时序、串口收发、继电器逻辑这三块代码是否自洽。很多 51 课程设计失败都发生在代码能编译、上板就死机而仿真能把问题提前拦截在原理图和时序层面。4.1 Proteus 元件清单与最小仿真电路搭建步骤Proteus 里新建工程后依次从元件库搜索并放置这些元件AT89C52、DS18B20、CRYSTAL、两个 30pF 电容、10μF 电解电容、10kΩ 电阻、4.7kΩ 电阻、LED 若干、绿色电源端子以及 Virtual Terminal。部分元件搜索不到时注意分类DS18B20 在元件库里的关键词就是 DS18B20Virtual Terminal 在左侧工具栏的 Virtual Instruments 面板里图标是一个带波形的小显示器。搭建步骤按下面的顺序操作比较稳妥放置 AT89C52双击设置 Clock Frequency 为 11.0592MHz这个值和晶振必须一致。最小系统X1、X2 接晶振和两个 30pF 电容到地RST 接 10μF 电解电容到 VCC、10kΩ 电阻到 GNDEA 接 VCC。DS18B20 的 DQ 接 P3.7外部 4.7kΩ 上拉到 VCC。P1.0 接一个 LED 串 1kΩ 电阻到 VCC用 LED 代替继电器线圈的吸合状态仿真里没必要放真实继电器模型。Virtual Terminal 的 RX 引脚接 AT89C52 的 TXDP3.1TX 引脚接 RXDP3.0双击把 Baud Rate 属性设为 9600。在 Keil 里编译生成 HEX 文件后双击 AT89C52在 Program File 里选择该 HEX 文件点击运行。仿真运行后在 Virtual Terminal 窗口里敲一个T单片机应当立刻回一串温度帧再敲1P1.0 的 LED 点亮。这个最小闭环验证通过说明 DS18B20 驱动、串口初始化、中断解析三条链路全部正常可以进入温度和蓝牙的联调。4.2 在 Proteus 里模拟手机蓝牙指令的两种做法Proteus 元件库没有 HC-05 官方模型教学中最常用的替代方案是 Virtual Terminal 和 COMPIM 虚拟串口两者适用场景不同。方法一Virtual Terminal 直接模拟手机。前面代码里预留的T、1、0字符命令就是为这一阶段准备的运行时点开 Virtual Terminal 窗口直接用键盘敲字符即可。这个方式适合验证控制逻辑但因为 Virtual Terminal 收发的本质是 ASCII 字符要向单片机发送AA 01 00 01这种二进制帧并不直观。方法二COMPIM 加 PC 串口助手。从元件库搜索 COMPIM 拖入画布设置 COM 端口号、波特率 9600、数据位 8、停止位 1、无校验把 COMPIM 的 RXD、TXD 分别接到单片机的 TXD、RXD。电脑上需要先装虚拟串口软件比如 Virtual Serial Port Driver创建一对互联的 COM 口比如 COM5 和 COM6Proteus 里 COMPIM 选 COM5电脑串口助手选 COM6这样模拟的是手机端通过蓝牙串口发数据。串口助手里勾选 HEX 发送输入AA 01 00 01单片机收到后回传AA 81 xx xx xx整条协议链路就和实物完全一致。仿真时改环境温度也有固定套路暂停仿真双击 DS18B20 元件在属性面板里把 Temperature部分版本显示为 Data改成目标值比如 40继续运行单片机上报的温度会随之变化。如果当前用的是 12 位分辨率模式注意改完温度后要留给它 750ms 的转换时间连续快速改值时偶尔读到上一次的缓存值属于正常现象。4.3 手机 APP 的蓝牙连接、温度上报与指令发送手机 APP 的选型决定开发效率。最快的路径是 MIT App Inventor 2浏览器打开在线开发环境用积木块拖拽出界面蓝牙部分使用 BluetoothClient 组件手机装上配套的 MIT AI2 Companion 就能直接调试不需要安装 Android Studio适合以功能演示为主的课程设计。如果对原生开发更熟悉Android Studio 里走 Bluetooth SPP 流程核心连接代码如下private BluetoothSocket connectBluetooth(String mac, OutputStream out) throws Exception { BluetoothAdapter adapter BluetoothAdapter.getDefaultAdapter(); if (adapter null || !adapter.isEnabled()) { throw new Exception(蓝牙未开启); } BluetoothDevice device adapter.getRemoteDevice(mac); // SPP 服务的标准 UUIDHC-05 全系支持 UUID spp UUID.fromString( 00001101-0000-1000-8000-00805F9B34FB); BluetoothSocket socket device.createRfcommSocketToServiceRecord(spp); socket.connect(); // 阻塞放到子线程执行 out socket.getOutputStream(); return socket; }这段代码的关键是 UUID00001101-0000-1000-8000-00805F9B34FB这是蓝牙串口服务 SPP 的固定标识HC-05 这类模块都实现这个服务换别的 UUID 会连接失败。socket.connect()是阻塞调用必须在子线程里执行否则 Android 会报 NetworkOnMainThreadException这一点新手很容易忽略。连接成功后发送温度查询指令字节数组按协议组帧byte[] queryFrame new byte[] { (byte) 0xAA, 0x01, 0x00, 0x01 // 校验 0x01 0x00 }; outputStream.write(queryFrame); // 阻塞读取响应AA 81 TH TL SUM共 5 字节 byte[] resp new byte[5]; inputStream.read(resp); // 实际应用需要先读到 0xAA 再读 4 字节 int tempRaw ((resp[2] 0xFF) 8) | (resp[3] 0xFF); float tempC tempRaw * 0.0625f; // 换算成摄氏度注意resp数据里的高低字节顺序要和 C 代码里uart_send_temp_report保持一致先高字节后低字节。resp[2]和resp[3]分别对应 TH 和 TL这个顺序错了温度会差出几百倍。APP 界面至少包含四个元素设备 MAC 地址输入框、连接按钮、温度显示 TextView、继电器开关按钮有条件再加一个阈值设置输入框发AA 02 xx xx帧就形成了完整的远程控制闭环。蓝牙权限适配也要提前做Android 6 到 11 需要 BLUETOOTH、BLUETOOTH_ADMIN 和定位权限Android 12 以上则改用 BLUETOOTH_SCAN 和 BLUETOOTH_CONNECT只用目标 SDK 31 以下的老写法会在新手机上直接崩溃。5. 实物制作流程与蓝牙、温度跳变的排错技巧从仿真跨到实物最大的变量从逻辑变成电气。焊接顺序、上电检查、模块配置这三个环节按顺序做好大部分问题都能在上电前被消灭。实物制作不要按原理图从头焊到尾而是按功能块逐级点亮。5.1 焊接顺序与上电前检查常见做法是先焊电源部分USB 供电就直连 5V9V 电源则先接 LM7805 或 AMS1117-5.0 降压上电后用万用表确认输出稳定在 5V再焊最小系统。晶振起振可以用示波器看没有示波器就烧一个 LED 闪烁程序验证最小系统接着单独写一个只读温度的程序串口打印确认 DS18B20 能读数再接继电器、最后接 HC-05这样每一步的故障边界都很清晰。上电前用万用表蜂鸣档测一遍 VCC 和 GND 之间有没有短路重点检查电解电容正负极、二极管方向、三极管三个脚的顺序。HC-05 建议最后接因为电平接错最容易烧的就是它。模块供电要从电源根部拉线不要在继电器附近取电否则继电器吸合瞬间的压降会让蓝牙模块直接掉线。5.2 HC-05 蓝牙模块连接不上、温度乱跳的定位表实物阶段最常见的几个问题按现象归类对号入座现象可能原因排查顺序手机搜不到 HC-05模块没进入可配对状态、供电不足量 VCC、看 STATE 脚 LED 是否慢闪搜得到但连不上密码错误、已被其他设备占用删除手机旧配对PIN 输入 1234连上但收不到数据波特率不匹配、RXD/TXD 接反确认模块 9600确认交叉连接温度固定 85℃DQ 线悬空、上拉电阻缺失检查 DQ 焊接补 4.7kΩ 上拉温度上下乱跳线路过长、供电干扰线长控制在 20cm 内加去耦电容继电器吸合时单片机复位电源塌陷继电器线圈两端并 1N4007加大电容每一条都对应实际项目的失败经历。比如温度固定 85℃仿真里永远不会出现因为 Proteus 的 DS18B20 模型总线状态理想但实物上 DQ 线虚焊或上拉电阻漏焊DS18B20 上电后暂存器就是 85℃ 这个默认值程序读回来自然不会变。串口乱码则要先看波特率公式里的晶振再考虑 HC-05 是否被改成过别的波特率用 ATUART 查一下最直接。5.3 用滑动平均给 DS18B20 去跳变DS18B20 在小数位上跳 0.25℃ 是正常的但在继电器、加热器共板的场景里电源波动会让读值偶尔跳个 1~2℃这时候滤波比换传感器更实际。滑动平均是性价比最高的做法代码量小效果立竿见影#define SAMPLE_N 16 // 窗口大小2 的幂次方便除法优化 static unsigned int sample_buf[SAMPLE_N]; static unsigned char sample_idx 0; static unsigned int sample_sum 0; // 返回滑动平均后的原始温度值 unsigned int filtered_temp_raw(void) { unsigned int raw ds18b20_get_temp(); sample_sum - sample_buf[sample_idx]; // 先减旧值 sample_buf[sample_idx] raw; sample_sum raw; // 再加新值 sample_idx (sample_idx 1) % SAMPLE_N; return sample_sum / SAMPLE_N; }窗口用 16 时sample_sum 的最大值是 16 个 125℃ 对应的原始值约 16×200032000还没超出 unsigned int 的 65535 上限但窗口加到 32 就接近边界了到时候要么把求和变量改成 unsigned long要么窗口保持 16。滑动平均的本质是把瞬时毛刺摊薄到整个窗口里毛刺对输出的影响变成原来的 1/16代价是温度变化响应慢了约 16 秒对继电器控温这种大惯性对象完全划算。最后提醒插座里的一个细节滑动平均窗口里每次调用ds18b20_get_temp()都会阻塞 750ms主循环的周期要按 采样时间加滤波时间合计来设计把采样周期定在 1 秒、窗口取 16蓝牙上报和控温两头都兼顾这也是我在实物上最终确定的参数。本文还有配套的精品资源点击获取