STM32驱动DS18B20单总线温度采集系统设计
单总线(1-Wire)是一种低引脚数、多设备共享通信总线的技术标准,其核心原理是通过精确微秒级时序控制实现主机与从机间半双工数据交换。该技术显著降低嵌入式系统硬件复杂度,避免传统I²C/SPI地址冲突与模拟传感器ADC调理难题。在工业测温、环境监控及电池供电设备中具备高可靠性与低功耗优势。本文以STM32F103C8T6为控制器,结合DS18B20数字温度传感器与SSD1306 OLED显示屏,完
1. 系统架构与硬件设计原理
DS18B20 是一款基于单总线(1-Wire)协议的数字温度传感器,其核心价值在于仅需一根数据线即可完成供电、时序同步与数据通信,极大简化了嵌入式系统中温度采集的硬件布线复杂度。在本系统中,DS18B20 与 STM32F103C8T6 微控制器构成最小测温节点,配合 SSD1306 驱动的 0.96 英寸 OLED 屏幕实现本地可视化,通过有源蜂鸣器提供声光报警,并利用 USART2 将温度数据实时上传至 PC 端串口调试助手。整个系统不依赖外部电源供电——DS18B20 工作于寄生电源模式(Parasitic Power Mode),由 MCU 的 GPIO 引脚在数据传输间隙提供瞬态能量,这使得硬件设计仅需三根线:VDD(接 3.3V)、GND 和 DQ(数据线,接 GPIOB_Pin1)。
该架构的工程意义在于:它规避了传统模拟温度传感器(如 LM35)所需的 ADC 采样、参考电压校准、运放调理等环节,也绕开了多点测温时 I²C 或 SPI 总线地址冲突问题。DS18B20 内部集成了高精度温度传感元件、12 位模数转换器、非易失性 EEPROM(用于存储用户配置)、以及符合 Dallas Semiconductor(现 Maxim Integrated)严格定义的单总线协议引擎。其典型测温范围为 -55℃ 至 +125℃,精度可达 ±0.5℃(-10℃ 至 +85℃ 区间),分辨率可编程为 9~12 位,对应温度转换时间从 93.75ms(9 位)至 750ms(12 位)。本项目采用默认 12 位分辨率,兼顾精度与响应速度。
OLED 屏幕选用 SSD1306 控制器,通过 I²C 接口(SCL: GPIOB_Pin6, SDA: GPIOB_Pin7)与 MCU 连接,其自发光特性无需背光电路,功耗极低,且对比度高、视角宽,非常适合电池供电或空间受限的嵌入式设备。蜂鸣器选用 5V 有源型,由 GPIOA_Pin5 经 NPN 三极管(如 S8050)驱动,MCU 输出高电平时导通,蜂鸣器发声;低电平时截止,静音。这种设计避免了 MCU IO 口直接驱动大电流负载的风险,提高了系统可靠性。
USART2 被配置为异步全双工通信,TX 引脚为 GPIOA_Pin2,RX 引脚为 GPIOA_Pin3,波特率设为 115200bps,满足温度数据实时上报需求。所有外设均挂载在 APB2 总线上(GPIOA/B)或 APB1 总线上(USART2),时钟配置严格遵循 STM32F10x 参考手册中的时钟树结构:系统时钟(SYSCLK)由 HSE(8MHz 晶振)经 PLL 倍频至 72MHz,APB2 总线(AHB)分频系数为 1,故 GPIO 时钟为 72MHz;APB1 总线分频系数为 2,故 USART2 时钟为 36MHz。此配置是后续精确控制单总线时序的前提。
2. 单总线协议底层驱动实现
DS18B20 的通信完全依赖于对单总线物理层的精确时序控制,其协议栈分为复位脉冲(Reset Pulse)、存在脉冲(Presence Pulse)、写时隙(Write Time Slot)和读时隙(Read Time Slot)四大基本单元。HAL 库无法直接支持此类微秒级、非标准的时序波形,因此必须采用裸机 GPIO 操作,通过 __NOP() 指令插入精确延时,或使用 SysTick 定时器实现更稳定的微秒级延时。
2.1 GPIO 引脚初始化与模式切换
DQ 数据线(GPIOB_Pin1)在通信过程中需频繁切换输入/输出模式,这是单总线协议的核心机制。初始化时,该引脚被配置为开漏输出(Open-Drain Output)并上拉至 3.3V(通过 4.7kΩ 外部上拉电阻),这是单总线“线与”逻辑的物理基础。代码中通过宏定义封装了三种基本操作:
#define DQ_H() do { GPIOB->CRH &= ~(0x0F << (1*4)); \
GPIOB->CRH |= (0x01 << (1*4)); \
GPIOB->BSRR = GPIO_BSRR_BS1; } while(0)
#define DQ_L() do { GPIOB->CRH &= ~(0x0F << (1*4)); \
GPIOB->CRH |= (0x00 << (1*4)); \
GPIOB->BSRR = GPIO_BSRR_BR1; } while(0)
#define DQ_READ() ((GPIOB->IDR & GPIO_IDR_IDR1) ? 1 : 0)
DQ_H() 将引脚设置为推挽输出高电平,实际效果是释放总线,使其依靠上拉电阻升至高电平; DQ_L() 将引脚设置为推挽输出低电平,主动将总线拉低; DQ_READ() 则先将引脚切换为浮空输入模式( GPIO_Mode_IN_FLOATING ),再读取引脚电平状态。模式切换的关键在于 GPIOB->CRH 寄存器的配置: 0x01 表示推挽输出(Output mode, max speed 10 MHz), 0x00 表示推挽输出(Output mode, max speed 2 MHz),而输入模式则需将 CNFy[1:0] 置为 0b01 (浮空输入)或 0b10 (上拉/下拉输入)。本项目采用浮空输入,因上拉电阻已存在。
2.2 复位与存在检测(Reset & Presence Detection)
复位序列是所有单总线通信的起点,其时序要求极为严苛:
- 主机(MCU)发出至少 480μs 的低电平复位脉冲;
- 随后释放总线,进入接收状态;
- 从机(DS18B20)在 15~60μs 内检测到上升沿后,于 60~240μs 内拉低总线作为存在脉冲(Presence Pulse);
- 主机在 60~240μs 后采样,若读到低电平,则表明从机在线。
实现此逻辑的函数 DS18B20_Rst() 返回值为 1 表示成功检测到从机, 0 表示失败(传感器断开或损坏)。其核心代码如下:
uint8_t DS18B20_Rst(void)
{
uint8_t i;
DQ_H(); // 释放总线
Delay_us(2); // 确保总线稳定在高电平
DQ_L(); // 主机拉低,启动复位
Delay_us(480); // 保持低电平 480μs
DQ_H(); // 主机释放总线
Delay_us(30); // 等待从机拉低(约 15~60μs)
i = DQ_READ(); // 采样存在脉冲
Delay_us(300); // 等待存在脉冲结束(约 60~240μs)
return i == 0 ? 1 : 0; // 读到低电平表示从机在线
}
Delay_us() 函数基于 SysTick 定时器实现,其精度远高于 HAL_Delay() (后者最小单位为 ms)。例如,在 72MHz 系统时钟下,SysTick 配置为每 1μs 中断一次, Delay_us(1) 即执行一次 while(--us) 循环。此处 Delay_us(480) 精确生成 480μs 低电平,是确保通信可靠性的第一道门槛。若此步骤失败,后续所有操作均无意义,系统应立即在 OLED 上显示 “Lo Sensor” 提示用户检查硬件连接。
2.3 读写时隙与时序控制
单总线协议规定,每个读/写时隙持续 60~120μs,主机在时隙起始处发起操作,从机在时隙中段响应。写“0”时,主机需在时隙开始后 1~15μs 内拉低总线并保持至少 60μs;写“1”时,主机仅需在 1~15μs 内拉低 1~15μs 后即释放。读操作中,主机在时隙开始后 1~15μs 内拉低 1~15μs 后释放,然后在 15~60μs 内采样总线电平:若为低则读得“0”,高则为“1”。
这些操作被封装为 DS18B20_WriteBit(uint8_t bit) 和 DS18B20_ReadBit(void) 函数。以写操作为例:
void DS18B20_WriteBit(uint8_t bit)
{
DQ_L(); // 主机拉低
Delay_us(2); // 保持低电平约 2μs
if(bit) {
DQ_H(); // 写1:快速释放
}
Delay_us(60); // 等待时隙结束
DQ_H(); // 确保总线释放
Delay_us(2); // 为下一个时隙准备
}
读操作则更为关键,因为需要精确把握采样窗口:
uint8_t DS18B20_ReadBit(void)
{
uint8_t bit;
DQ_L(); // 主机拉低启动时隙
Delay_us(2);
DQ_H(); // 释放总线
Delay_us(15); // 等待从机响应
bit = DQ_READ(); // 在 15~60μs 窗口内采样
Delay_us(45); // 完成整个时隙(60μs)
return bit;
}
一个字节(8 位)的读写即是对上述位操作的循环调用,低位在前(LSB First)。例如, DS18B20_WriteByte(0xCC) 是跳过 ROM 指令, DS18B20_WriteByte(0x44) 是启动温度转换指令。这些指令的正确发送,是 DS18B20 能够进入工作状态的先决条件。
3. 温度数据获取与数值解析
DS18B20 的温度数据以 16 位补码形式存储在其内部 RAM 的第 0 和第 1 字节(LSB 和 MSB)。当主机发送 0xBE (Read Scratchpad)指令后,DS18B20 会依次返回 9 字节数据,其中前两个字节即为温度值。解析过程需严格遵循其数据格式规范。
3.1 温度值读取流程
完整的温度读取流程包含四个阶段:
1. 复位与存在检测 :调用 DS18B20_Rst() ,确认传感器在线;
2. 跳过 ROM :发送 0xCC 指令,忽略设备唯一 ID,适用于单传感器系统;
3. 启动转换 :发送 0x44 指令,触发 DS18B20 开始温度测量;
4. 读取结果 :再次复位后,发送 0xBE 指令,读取 9 字节 RAM 数据。
其中,第 3 步(启动转换)后必须等待足够时间。对于 12 位分辨率,最大转换时间为 750ms。为保证可靠性,代码中采用轮询方式:在发送 0x44 后,短暂延时(如 1ms),然后不断复位并读取存在脉冲,直到 DS18B20 返回存在脉冲(表明转换完成)。这是一种简洁有效的软件等待策略,避免了阻塞式长延时。
3.2 16 位补码解析与温度计算
读取到的 temp_data[0] (LSB)和 temp_data[1] (MSB)组合成一个 16 位整数 raw_temp 。DS18B20 的温度分辨率是 0.0625℃/LSB,即 1/16 ℃。因此,真实温度 T 的计算公式为:
T = raw_temp × 0.0625
然而, raw_temp 是一个有符号整数(最高位为符号位),其数值范围为 -55×16 = -880 至 +125×16 = +2000。例如, raw_temp = 0x0191 (十进制 401)对应 401 × 0.0625 = 25.0625℃ ; raw_temp = 0xFF9C (十进制 -100)对应 -100 × 0.0625 = -6.25℃ 。
在 C 语言中,直接将两个 uint8_t 组合成 int16_t 即可自动处理符号扩展:
int16_t raw_temp = (temp_data[1] << 8) | temp_data[0];
float temperature = raw_temp * 0.0625f;
此处 0.0625f 是一个 float 类型常量,强制编译器进行浮点运算。乘法结果 temperature 即为摄氏度浮点值。项目中将 temperature 乘以 10 并取整( temp_int = (int)(temperature * 10) ),是为了方便后续在 OLED 上分别显示整数部分和小数部分,避免了浮点数格式化带来的额外代码开销和 Flash 占用。
关于字幕中提到的“为什么乘以 0.0625”的疑问,其根源在于 DS18B20 的内部 ADC 设计。该芯片的 ADC 输出是一个 12 位数字量,其满量程对应 -55℃ 至 +125℃,总跨度为 180℃。12 位能表示 4096 个离散值,因此每个 LSB 对应的温度变化为 180 / 4096 ≈ 0.0439453125℃ 。但 DS18B20 并未将整个量程线性映射,而是将 0℃ 定义为 0x0000 ,正向温度按 0.0625℃/LSB 递增,负向温度按相同步长递减。这是一种行业惯例,由芯片硬件逻辑固化,开发者只需遵循数据手册即可,无需深究其模拟电路细节。
4. OLED 显示驱动与界面设计
SSD1306 OLED 屏幕采用 I²C 接口,其驱动逻辑相对独立于温度采集,但二者在主循环中紧密协同。本项目使用基于 HAL 库的 SSD1306 移植代码,其核心是 SSD1306_Fill() (清屏)、 SSD1306_DrawChar() (画字符)和 SSD1306_DisplayString() (显示字符串)等函数。
4.1 字符显示原理与坐标系统
SSD1306 的显存为 128×64bit,即 128 列 × 8 行,每行 16 字节(128/8)。屏幕坐标系以左上角为原点 (0, 0),X 轴向右,Y 轴向下。一个标准 ASCII 字符(5×8 点阵)占据 5 列 × 8 行,因此在 128×64 分辨率下,一行最多显示 128/5 ≈ 25 个字符,一屏最多显示 64/8 = 8 行。本项目界面设计为两行:第一行固定显示 “DS18B20 Temp:”,第二行动态显示温度值及单位。
4.2 温度值的分段显示实现
由于 temp_int 是一个放大了 10 倍的整数(如 25.3℃ 存储为 253),将其拆分为整数部分和小数部分需进行整数运算:
int temp_int = (int)(temperature * 10);
int temp_whole = temp_int / 10; // 整数部分,如 253/10 = 25
int temp_frac = temp_int % 10; // 小数部分,如 253%10 = 3
随后,将 temp_whole 的各位数字提取出来。对于两位数(0~99),可分解为十位 temp_whole / 10 和个位 temp_whole % 10 。最终,温度值在 OLED 上的显示顺序为:
- 第二行起始位置(X=0, Y=1)显示十位数字;
- X=8, Y=1 显示个位数字;
- X=16, Y=1 显示小数点 “.”;
- X=24, Y=1 显示小数位数字;
- X=32, Y=1 显示单位 “°C”。
这一过程完全避免了 sprintf() 等字符串格式化函数,节省了宝贵的 RAM 和 Flash 空间,是资源受限嵌入式系统的典型优化手法。
4.3 错误状态提示
当 DS18B20_Rst() 返回 0 ,表明传感器断开,此时 OLED 不再显示温度,而是清屏后在第一行居中显示 “Lo Sensor”。该字符串长度为 10 个字符,居中需计算起始 X 坐标: (128 - 10*5) / 2 = 39 。此提示是系统健壮性的关键体现,它将硬件故障转化为用户可理解的视觉信息,大幅降低了现场调试难度。
5. 蜂鸣器报警逻辑与阈值控制
报警功能是本系统的人机交互核心,其实现逻辑简洁而有效:当实时温度 temperature 超过预设阈值(29.0℃)时,驱动蜂鸣器发声;温度回落至阈值以下时,关闭蜂鸣器。该逻辑完全在主循环中实现,未使用中断,因其对实时性要求不高(毫秒级响应已足够)。
5.1 GPIO 驱动与电气隔离
蜂鸣器由 GPIOA_Pin5 驱动。该引脚配置为推挽输出( GPIO_Mode_Out_PP ),最大输出速度为 50MHz。由于有源蜂鸣器工作电流通常为 20~30mA,已超过 STM32 IO 口的绝对最大额定值(25mA),故必须采用三极管进行电流放大。电路连接为:PA5 → 1kΩ 限流电阻 → S8050 基极;S8050 发射极接地;S8050 集电极 → 蜂鸣器正极;蜂鸣器负极接 VCC(5V)。当 PA5 输出高电平时,S8050 饱和导通,蜂鸣器两端形成 5V 压差而发声;PA5 输出低电平时,S8050 截止,蜂鸣器无电流通过。
5.2 报警阈值的工程考量
阈值设为 29.0℃ 是一个典型的工程折中。它高于人体舒适温度(25~27℃),能有效避免环境温度小幅波动引发的误报;同时又低于许多电子设备的安全工作上限(如 35~40℃),具备一定的预警价值。在代码中,该阈值被定义为 #define ALARM_THRESHOLD 29.0f ,便于后期根据具体应用场景调整。报警判断逻辑为:
if (temperature >= ALARM_THRESHOLD) {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_SET); // 蜂鸣器响
} else {
HAL_GPIO_WritePin(GPIOA, GPIO_PIN_5, GPIO_PIN_RESET); // 蜂鸣器停
}
此逻辑直接、高效,无任何中间状态或去抖动处理,因为温度变化本身就是一个缓慢过程(热惯性),天然具备抗干扰能力。若需增加报警持续时间或声音模式(如间歇鸣叫),可在该 if 语句内加入定时器或状态机,但本项目保持了最简设计。
6. 串口数据上报与调试验证
USART2 是系统与上位机通信的唯一通道,其配置直接影响开发调试效率。本项目将其配置为:波特率 115200,8 数据位,1 停止位,无校验,硬件流控关闭。此配置在 36MHz 时钟下,波特率误差小于 0.1%,确保了通信的高可靠性。
6.1 格式化输出与调试信息
温度数据通过 printf() 重定向至 USART2 输出。 printf() 的底层实现依赖于 fputc() 函数,该函数被重写为调用 HAL_UART_Transmit() :
int fputc(int ch, FILE *f) {
HAL_UART_Transmit(&huart2, (uint8_t *)&ch, 1, HAL_MAX_DELAY);
return ch;
}
每次主循环迭代,系统都会输出一行格式化的温度数据,例如:
Temp: 25.30°C
此字符串由 printf("Temp: %.2f°C\r\n", temperature); 生成。 %.2f 指定浮点数保留两位小数, \r\n 是 Windows 系统识别的换行符。这种实时、连续的数据流,是验证传感器读数准确性、观察温度变化趋势、排查通信故障的最直接手段。
6.2 硬件连接验证流程
完整的系统验证应覆盖所有硬件节点:
- 传感器连接 :拔掉 DS18B20,观察 OLED 是否显示 “Lo Sensor”,串口是否停止输出温度数据;
- 温度变化 :用手握住传感器,观察 OLED 数值是否平稳上升,串口数据是否同步更新,蜂鸣器是否在 29.0℃ 时准确触发;
- 报警解除 :松开手后,温度下降,观察蜂鸣器是否在低于 29.0℃ 时立即关闭;
- 显示一致性 :对比 OLED 显示值与串口调试助手打印值,二者应完全一致(因同源 temperature 变量)。
这一系列测试构成了一个闭环的硬件-软件联调流程,确保了从物理层信号采集到应用层人机交互的全链路正确性。
7. 主循环任务调度与系统集成
整个系统的运行心脏是 main() 函数中的无限 while(1) 循环。在此循环内,各模块按确定性顺序执行,形成一个轻量级的协作式调度器。其执行序列为:
- 传感器复位与存在检测 :
if(DS18B20_Rst()) { ... },这是所有后续操作的前提; - 温度采集与解析 :执行跳过 ROM、启动转换、读取 RAM、计算
temperature; - OLED 显示更新 :根据
temperature或错误状态刷新屏幕内容; - 蜂鸣器状态更新 :依据
temperature与阈值比较结果,设置 PA5 电平; - 串口数据上报 :调用
printf()输出当前温度值。
这种顺序执行模型的优势在于逻辑清晰、易于理解和调试,缺点是各模块的执行周期受最长任务(通常是 DS18B20 的 750ms 转换时间)制约。在本项目中,这是可接受的,因为温度本身是慢变参数。若需更高频率的其他任务(如高速 PWM 控制),则应考虑引入 FreeRTOS 等实时操作系统,将 DS18B20 读取封装为一个独立任务,并通过队列或信号量与其他任务通信。
8. 工程实践中的常见问题与解决方案
在实际部署本系统时,我曾多次遇到以下典型问题,其解决方案具有普适性:
8.1 传感器无法识别(“Lo Sensor”常驻)
最常见的原因是 DQ 线上拉电阻失效或虚焊。单总线协议要求严格的 4.7kΩ 上拉,若使用 10kΩ 电阻,复位脉冲后的上升沿斜率过缓,DS18B20 无法在规定时间内检测到;若电阻脱焊,则总线始终为低电平,复位失败。解决方案:用万用表测量 DQ 引脚对地电压,正常应为 3.3V;若为 0V,检查上拉电阻焊接与阻值。
8.2 温度读数跳变或不更新
这往往源于时序偏差。例如, Delay_us() 函数在不同编译优化等级下, __NOP() 指令的实际执行周期可能变化。在 Keil MDK 中,若开启 Optimize for Time ,编译器可能合并或删除 __NOP() ,导致延时严重缩水。解决方案:将 Delay_us() 函数声明为 __attribute__((naked)) 或 #pragma push / #pragma pop 禁用优化,并用示波器实测 DQ 波形,校准延时参数。
8.3 OLED 显示乱码或不亮
I²C 总线冲突是主因。本项目中,GPIOB_Pin6(SCL)和 Pin7(SDA)若被误配置为其他复用功能(如 TIM3_CH1/CH2),会导致 I²C 通信失败。解决方案:严格检查 MX_GPIO_Init() 中相关引脚的 GPIO_Mode 和 GPIO_PuPd 配置,确保其为 GPIO_MODE_AF_OD (复用开漏)并启用上拉;同时确认 MX_I2C1_Init() 已正确调用。
8.4 串口数据丢失或乱码
波特率不匹配是首要怀疑对象。若 PC 端串口调试助手设置为 9600,而 MCU 配置为 115200,则必然乱码。解决方案:在 MX_USART2_UART_Init() 中, huart2.Init.BaudRate 必须与上位机软件严格一致;其次,检查 HAL_UART_Transmit() 的超时参数 HAL_MAX_DELAY ,若设为 0 ,则函数立即返回,数据可能未发送完毕,应设为一个合理的毫秒值(如 100 )。
这些问题的解决过程,本质上是将抽象的代码逻辑,与具体的物理世界(电压、时序、焊接点)进行反复比对和校准的过程。每一次成功的调试,都是对嵌入式系统“软硬一体”本质的一次深刻认知。
openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。
更多推荐

所有评论(0)