STM32与DS18B20温度报警系统实战:从单总线时序到OLED显示 简介这份资源面向电子信息、自动化等专业的嵌入式课程设计学习者提供一套基于STM32F401与DS18B20传感器的温度报警系统完整实现方案。设计涵盖温度实时采集、LCD液晶显示、手动设置预警参数、LED指示灯报警并支持-100至100摄氏度范围内的阈值预警同时可在屏幕上显示设计者姓名、学号与题目名称等信息适合作为课设参考或嵌入式入门练手项目。压缩包共190个文件约9.27MB包含Keil5工程源码c与h文件、Proteus仿真工程pdsprj、pdsbak、编译产物hex、axf、o、crf等以及设计报告文档覆盖从代码编写到仿真验证的完整流程。目前已有12670人学习下载读者可据此快速搭建软硬件环境理解DS18B20单总线时序、HAL库外设驱动与报警逻辑设计并对照报告梳理设计思路与排错方法。1. 从一块“哑巴”开发板说起这个温度报警项目到底能干什么很多人第一次拿到 STM32 开发板烧录完点灯程序之后就不知道下一步该做什么了。板子上的 LED 一闪一闪但跟真实世界没有任何交互说白了就是个“哑巴”板子。而基于 STM32 的温度报警设计恰好是打破这个僵局最合适的练手项目——它把传感器、单片机、显示、报警输出串成了一条完整的链路让你第一次感受到“代码真的在感知物理世界”。这个项目适合谁如果你正在做嵌入式课程设计或者刚学完 GPIO、定时器、串口想找一个能写进简历、又能真正跑起来的综合案例那它非常对口。核心功能很明确用 DS18B20 采集环境温度STM32 读取数据后做判断超过设定阈值就触发蜂鸣器和 LED 报警同时把温度值显示在 OLED 或 LCD 上。技术栈覆盖了单总线协议、GPIO 驱动、定时器、中断、显示驱动麻雀虽小五脏俱全。2. 硬件架构与选型为什么是 DS18B20 加 STM32 这套组合2.1 主控与传感器的搭配逻辑STM32 系列型号很多做温度报警一般选 F103C8T6 这类资源够用、资料丰富的型号。它有一个 12 位 ADC、多个定时器、充足的 GPIO跑这个项目绰绰有余。温度传感器选 DS18B20 而不是热敏电阻或 LM35原因有几个DS18B20 是数字输出直接给出摄氏度数值不需要额外标定和 ADC 换算它用单总线协议一根数据线就能通信接线简单测量范围 -55 到 125 摄氏度精度 ±0.5做室温报警完全够用。整个系统的硬件连接大致是这样的DS18B20 的数据脚接 STM32 的一个 GPIO比如 PA0同时接一个 4.7k 上拉电阻到 3.3VOLED 显示屏用 I2C 接口接 PB6 和 PB7蜂鸣器接一个 GPIO比如 PB8通过三极管驱动报警 LED 接 PC13。电源部分用 USB 供电或者 3.3V 稳压模块都行。注意DS18B20 的上拉电阻不能省很多人接线时忘了这个电阻结果读出来永远是 85 摄氏度或者直接读不到设备。2.2 单总线协议的时序要求DS18B20 最让人头疼的地方就是单总线时序。它没有独立的时钟线所有通信靠一根数据线上的电平持续时间来区分 0 和 1。STM32 主频 72MHz 的情况下一个机器周期大约 13.9ns而 DS18B20 要求微秒级的延时精度。常见做法是用 SysTick 或者定时器来做微秒延时不要用简单的 for 循环因为编译器优化等级一变循环次数就不准了。初始化时序是这样的主机拉低总线至少 480 微秒然后释放等待 15 到 60 微秒DS18B20 会拉低总线 60 到 240 微秒作为应答。写时序分写 0 和写 1写 0 是拉低总线 60 微秒后释放写 1 是拉低 1 到 15 微秒后释放。读时序类似主机拉低至少 1 微秒然后在 15 微秒内采样总线电平。// 微秒级延时函数基于 SysTick void delay_us(uint32_t us) { uint32_t start SysTick-VAL; uint32_t ticks us * (SystemCoreClock / 1000000); while (1) { uint32_t now SysTick-VAL; uint32_t elapsed (start now) ? (start - now) : (start SysTick-LOAD - now); if (elapsed ticks) break; } } // DS18B20 复位函数 uint8_t ds18b20_reset(void) { uint8_t presence; GPIO_InitTypeDef gpio; // 切换为推挽输出 gpio.GPIO_Pin DS18B20_PIN; gpio.GPIO_Mode GPIO_Mode_Out_PP; gpio.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(DS18B20_PORT, gpio); GPIO_ResetBits(DS18B20_PORT, DS18B20_PIN); // 拉低总线 delay_us(480); // 保持至少 480us GPIO_SetBits(DS18B20_PORT, DS18B20_PIN); // 释放总线 delay_us(60); // 等待应答 // 切换为浮空输入 gpio.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(DS18B20_PORT, gpio); presence GPIO_ReadInputDataBit(DS18B20_PORT, DS18B20_PIN); delay_us(240); // 等待应答结束 return presence; // 0 表示存在1 表示不存在 }这段代码里delay_us用 SysTick 当前值来计算经过的时钟数比空循环靠谱得多。ds18b20_reset先拉低 480 微秒再释放并等待 60 微秒最后读取总线电平判断器件是否存在。注意 GPIO 模式在输出和输入之间切换这是单总线通信的典型操作。参数DS18B20_PIN和DS18B20_PORT根据实际接线定义改板子的时候只改这两个宏就行。2.3 温度读取与数据格式转换DS18B20 读出来的原始数据是 16 位低 4 位是小数部分高 12 位是整数部分。正温度直接乘 0.0625 得到摄氏度负温度需要先取反再加一。常见做法是定义一个浮点变量存结果显示的时候保留一位小数。float ds18b20_read_temp(void) { uint8_t temp_l, temp_h; int16_t raw; float result; ds18b20_reset(); ds18b20_write_byte(0xCC); // 跳过 ROM ds18b20_write_byte(0x44); // 启动温度转换 // 等待转换完成12 位精度需要约 750ms delay_ms(750); ds18b20_reset(); ds18b20_write_byte(0xCC); ds18b20_write_byte(0xBE); // 读取暂存器 temp_l ds18b20_read_byte(); temp_h ds18b20_read_byte(); raw (temp_h 8) | temp_l; if (raw 0x8000) { // 负温度 raw ~raw 1; result -(raw * 0.0625f); } else { result raw * 0.0625f; } return result; }0xCC是跳过 ROM 命令总线上只有一个器件时用这个最省事。0x44启动转换0xBE读暂存器。转换时间跟精度有关12 位精度下最大 750 毫秒如果嫌慢可以降到 9 位精度只要 93.75 毫秒但分辨率就只有 0.5 度了。raw 0x8000判断符号位负温度取反加一后再乘系数。这个函数返回浮点温度值后面报警判断和显示都用它。3. 软件模块拆解从主循环到报警逻辑的完整链路3.1 主程序框架与任务调度这个项目不需要 RTOS裸机跑一个主循环就够了。主循环里做三件事读温度、更新显示、判断报警。但要注意DS18B20 转换需要 750 毫秒不能一直阻塞在那里等。常见做法是用定时器产生一个 1 秒的软定时标志主循环检测到标志才去读一次温度这样 CPU 不会白白空转。int main(void) { uint32_t last_tick 0; float temperature; float threshold 30.0f; // 报警阈值默认 30 度 SystemInit(); delay_init(); uart_init(115200); oled_init(); ds18b20_init(); buzzer_init(); led_init(); oled_show_string(0, 0, Temp: ); oled_show_string(0, 2, Alarm: ); while (1) { // 每 1 秒读取一次温度 if (HAL_GetTick() - last_tick 1000) { last_tick HAL_GetTick(); temperature ds18b20_read_temp(); // 串口打印调试信息 printf(Temperature: %.1f C\r\n, temperature); // 更新 OLED 显示 oled_show_float(48, 0, temperature, 1); // 报警判断 if (temperature threshold) { buzzer_on(); led_on(); oled_show_string(48, 2, ON ); } else { buzzer_off(); led_off(); oled_show_string(48, 2, OFF); } } } }HAL_GetTick()返回系统启动后的毫秒数用它做时间差判断比直接延时更灵活。阈值threshold设成 30 度实际用的时候可以改成按键可调。串口打印printf需要重定向fputc函数这个在 STM32 里是常规操作。OLED 显示部分oled_show_float是自己封装的函数把浮点数转成字符串再显示。3.2 报警输出与阈值调节蜂鸣器和 LED 的控制很简单就是 GPIO 置位和复位。但实际做的时候蜂鸣器最好用 PWM 驱动这样声音频率可控不会太刺耳。如果只是简单开关有源蜂鸣器直接给高电平就行无源蜂鸣器需要 2kHz 左右的方波。阈值调节一般用两个按键一个加一个减。按键消抖用 20 毫秒延时判断两次电平。调好的阈值可以存在 STM32 的 Flash 里掉电不丢。Flash 读写用 HAL 库的HAL_FLASH_Program函数注意要先解锁再擦除再写入。// 按键扫描返回按键值 uint8_t key_scan(void) { static uint8_t key_up 1; if (key_up (KEY_ADD 0 || KEY_SUB 0)) { delay_ms(20); // 消抖 key_up 0; if (KEY_ADD 0) return 1; if (KEY_SUB 0) return 2; } else if (KEY_ADD 1 KEY_SUB 1) { key_up 1; } return 0; } // 在主循环中处理按键 uint8_t key key_scan(); if (key 1 threshold 100.0f) threshold 0.5f; if (key 2 threshold 0.0f) threshold - 0.5f;key_scan用了一个静态变量key_up来保证一次按下只触发一次避免长按连续触发。阈值加减步进 0.5 度范围限制在 0 到 100 度之间。调完之后可以把threshold写进 Flash 指定地址下次上电先读出来。3.3 显示驱动的移植要点OLED 驱动一般用 I2C 接口的 SSD1306 芯片网上能找到现成的驱动库。移植的时候主要改三个地方I2C 的 GPIO 定义、初始化函数里的延时、还有字库数组。如果用的是硬件 I2C配置好时钟和引脚就行如果用软件模拟 I2C那延时函数要跟 DS18B20 的延时区分开因为 I2C 速率可以到 400kHz延时太长会拖慢刷新。显示内容布局建议这样第一行显示当前温度第二行显示报警阈值和报警状态。刷新的时候不要整屏清空再重画那样会闪。只更新变化的区域比如温度值那几位数字。SSD1306 的显存是 128x64 位按页写入每次更新一页或者一个区域。提示OLED 初始化之前最好先延时 100 毫秒等电源稳定否则偶尔会出现花屏或者不亮的情况。4. 避坑与排查调试这个项目时最容易翻车的几个地方4.1 温度读数一直是 85 或者 -127现象是串口打印出来的温度固定不变要么 85 要么 -127。85 是 DS18B20 的上电默认值说明转换命令没发出去或者没等待转换完成。原因通常是复位时序不对或者写字节函数里延时不够。解决方法是拿示波器看数据线波形确认复位脉冲宽度是否达到 480 微秒写时序里每个位的时间是否在规格范围内。没有示波器的话把延时函数里的参数加大一倍试试如果读数变了说明就是时序问题。4.2 OLED 不亮或者显示乱码先检查 I2C 地址对不对SSD1306 一般是 0x78 或 0x7A。地址不对的话初始化命令发过去没反应屏幕自然不亮。再检查上拉电阻I2C 的 SDA 和 SCL 都需要 4.7k 上拉。如果显示乱码多半是字库取模方式跟驱动不匹配比如驱动按列取模字库按行取模显示出来就是乱的。重新生成字库或者改驱动里的显示函数。4.3 蜂鸣器一直响或者不响一直响可能是 GPIO 初始电平设错了初始化的时候应该先输出低电平。不响的话先量蜂鸣器两端电压有电压但不响说明蜂鸣器坏了或者驱动电流不够加个三极管。无源蜂鸣器直接给高电平是不响的必须给方波。用定时器输出 PWM频率设 2kHz 到 4kHz 之间占空比 50%。4.4 程序跑一段时间就死机这种现象通常是栈溢出或者中断冲突。先看有没有大数组定义在局部变量里比如uint8_t buffer[1024]这种放到全局或者用static修饰。再检查中断优先级SysTick 中断优先级如果比串口中断低串口接收大量数据时可能阻塞延时函数。把 SysTick 优先级设成最高或者延时函数里关中断的时间尽量短。4.5 按键调节阈值时数字跳变按键消抖没做好或者key_scan函数里逻辑有漏洞。按一次加了好几次说明消抖延时不够或者key_up标志没正确复位。把消抖时间加到 30 毫秒并且在按键释放后再清标志。另外阈值变量如果是浮点数显示的时候注意四舍五入不然会出现 29.9999 这种显示。5. 二次开发与扩展把这个项目改成你自己的东西5.1 从单点测温到多点组网现在只接了一个 DS18B20如果想测多个位置的温度单总线可以挂多个器件每个器件有唯一的 64 位 ROM 地址。用0x55匹配 ROM 命令代替0xCC跳过 ROM就能分别读取每个传感器。常见做法是先发0x33读 ROM 命令把所有器件的地址读出来存到数组里然后轮询读取。这样一套系统能覆盖一个房间的多个角落做温室监控或者机房温度监测都够用。5.2 加上无线传输和上位机有线串口只能短距离传输想远程监控就加一个无线模块比如通过串口透传把温度数据发到上位机。上位机用 Python 的 PySerial 库接收数据再用 Matplotlib 画实时曲线。STM32 这边只需要把printf的内容改成固定格式的字符串比如T:25.3,A:0上位机解析这个格式就行。import serial import matplotlib.pyplot as plt from collections import deque ser serial.Serial(COM3, 115200, timeout1) temps deque(maxlen100) while True: line ser.readline().decode(utf-8, errorsignore).strip() if line.startswith(T:): temp float(line.split(,)[0][2:]) temps.append(temp) plt.clf() plt.plot(temps) plt.pause(0.01)这段 Python 代码用deque存最近 100 个温度值每次收到新数据就重绘曲线。COM3改成实际串口号Windows 和 Linux 下串口名不一样。这个上位机脚本可以进一步扩展成带报警记录和导出 CSV 的功能。5.3 低功耗改造思路如果做电池供电的版本低功耗是必须考虑的。STM32 有睡眠、停止、待机三种低功耗模式。温度报警这种场景可以用停止模式定时器唤醒醒来读一次温度判断完继续睡。DS18B20 转换期间 CPU 也可以睡等转换完成再唤醒。这样平均电流能从几十毫安降到几百微安两节五号电池能撑几个月。改造的时候注意进入停止模式前要把 GPIO 设成模拟输入或者保持状态避免漏电流。唤醒源用 RTC 或者外部中断都行。调试低功耗的时候万用表串在电源线上看电流如果比预期大很多逐个检查外设时钟有没有关掉。5.4 我踩过的一个坑刚开始做这个项目的时候我把 DS18B20 的延时函数写成了空循环在 Keil 里仿真跑得好好的烧到板子上温度就乱跳。后来用示波器一看编译器优化等级从 Level 0 改成 Level 3 之后空循环被优化掉了延时完全不对。从那以后我每次写微秒级延时都强制用 SysTick 或者硬件定时器再也不敢用空循环了。另外DS18B20 的转换等待时间一定要给足12 位精度下 750 毫秒是最大值给 700 毫秒都可能读到旧数据。希望这些经验能帮你少走点弯路。本文还有配套的精品资源点击获取