手把手教你用STM32F103C8T6驱动DS18B20,附完整代码和LCD1602显示教程
基于STM32F103的智能温度监测系统开发实战
在嵌入式开发领域,温度监测是最基础也最实用的应用场景之一。对于刚接触STM32系列单片机的开发者来说,如何将传感器数据采集、处理与显示完整实现,是一个极具学习价值的项目。本文将使用STM32F103C8T6这款性价比极高的MCU,搭配DS18B20数字温度传感器和LCD1602显示屏,构建一个实时温度监测系统。
1. 硬件准备与电路连接
1.1 核心器件选型与特性
STM32F103C8T6作为本项目的核心控制器,是一款基于ARM Cortex-M3内核的微控制器,具有以下优势:
- 72MHz主频,性能足够处理传感器数据
- 64KB Flash和20KB SRAM,满足程序存储需求
- 丰富的GPIO和外设接口
- 广泛的社区支持和开发资源
DS18B20是一款数字温度传感器,其特点包括:
- 单总线接口,仅需一个GPIO即可通信
- 测量范围:-55°C至+125°C
- 精度:±0.5°C(-10°C至+85°C范围内)
- 内置12位ADC,分辨率可达0.0625°C
LCD1602作为显示模块,具有:
- 16字符×2行的显示能力
- 支持标准4位或8位并行接口
- 低功耗,易于集成
1.2 硬件连接方案
以下是推荐的连接方式(基于4位数据模式):
| STM32引脚 | 连接目标 | 备注 |
|---|---|---|
| PA0 | DS18B20 DATA | 需接4.7kΩ上拉电阻 |
| PB0-PB3 | LCD1602 D4-D7 | 数据线 |
| PB4 | LCD1602 RS | 寄存器选择 |
| PB5 | LCD1602 RW | 读写控制 |
| PB6 | LCD1602 E | 使能信号 |
| 3.3V | LCD1602 VCC | 电源 |
| GND | LCD1602 GND | 地线 |
注意:DS18B20的数据线必须接4.7kΩ上拉电阻至3.3V,否则无法正常通信。
2. 开发环境配置
2.1 工具链搭建
推荐使用以下开发工具:
- STM32CubeIDE:官方集成开发环境,包含编译器、调试器和硬件配置工具
- STM32CubeMX:图形化引脚配置工具(已集成在CubeIDE中)
- 串口调试助手:用于调试输出
安装步骤:
- 从ST官网下载并安装STM32CubeIDE
- 创建新工程,选择STM32F103C8T6作为目标芯片
- 通过CubeMX配置时钟树和引脚功能
2.2 工程基础配置
关键配置参数:
- 系统时钟:72MHz(外部8MHz晶振倍频)
- GPIO模式:
- DS18B20数据引脚:推挽输出/浮空输入
- LCD控制引脚:推挽输出
- 调试接口:SWD(Serial Wire Debug)
时钟配置代码示例:
void SystemClock_Config(void) { RCC_OscInitTypeDef RCC_OscInitStruct = {0}; RCC_ClkInitTypeDef RCC_ClkInitStruct = {0}; // 配置主PLL RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE; RCC_OscInitStruct.HSEState = RCC_HSE_ON; RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON; RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE; RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9; HAL_RCC_OscConfig(&RCC_OscInitStruct); // 配置时钟树 RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2; RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK; RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1; RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2; RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1; HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2); }3. DS18B20驱动实现
3.1 单总线协议解析
DS18B20采用单总线通信协议,其基本时序包括:
- 初始化序列:主机发送复位脉冲,从机回应存在脉冲
- ROM命令:如跳过ROM(0xCC)或匹配ROM(0x55)
- 功能命令:如启动温度转换(0x44)或读取暂存器(0xBE)
关键时序参数(单位:微秒):
| 操作 | 最小值 | 典型值 | 最大值 |
|---|---|---|---|
| 复位脉冲 | 480 | 480 | 960 |
| 从机响应 | 15 | 60 | 240 |
| 写0时间 | 60 | 60 | 120 |
| 写1时间 | 1 | 15 | 15 |
| 读采样时间 | 1 | - | 15 |
3.2 驱动程序实现
以下是核心驱动函数的实现:
// 初始化DS18B20 uint8_t DS18B20_Init(void) { GPIO_InitTypeDef GPIO_InitStruct = {0}; // 配置GPIO为输出模式 GPIO_InitStruct.Pin = DS18B20_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct); // 发送复位脉冲 HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET); delay_us(480); HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET); // 切换为输入模式等待响应 GPIO_InitStruct.Mode = GPIO_MODE_INPUT; HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct); delay_us(60); uint8_t response = HAL_GPIO_ReadPin(DS18B20_PORT, DS18B20_PIN); delay_us(420); return response; // 0表示设备存在,1表示无设备 } // 写入一个字节 void DS18B20_WriteByte(uint8_t data) { GPIO_InitTypeDef GPIO_InitStruct = {0}; GPIO_InitStruct.Pin = DS18B20_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct); for(uint8_t i=0; i<8; i++) { HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET); delay_us(2); if(data & 0x01) { HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET); } delay_us(60); HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET); data >>= 1; delay_us(2); } } // 读取一个字节 uint8_t DS18B20_ReadByte(void) { uint8_t data = 0; GPIO_InitTypeDef GPIO_InitStruct = {0}; for(uint8_t i=0; i<8; i++) { GPIO_InitStruct.Pin = DS18B20_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct); HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET); delay_us(2); HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET); GPIO_InitStruct.Mode = GPIO_MODE_INPUT; HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct); delay_us(8); data >>= 1; if(HAL_GPIO_ReadPin(DS18B20_PORT, DS18B20_PIN)) { data |= 0x80; } delay_us(50); } return data; }3.3 温度读取与处理
完整的温度读取流程:
- 初始化DS18B20
- 发送跳过ROM命令(0xCC)
- 发送启动温度转换命令(0x44)
- 等待转换完成(典型750ms)
- 再次初始化DS18B20
- 发送跳过ROM命令(0xCC)
- 发送读取暂存器命令(0xBE)
- 读取两个字节的温度数据
- 将原始数据转换为实际温度值
温度转换代码示例:
float DS18B20_GetTemp(void) { uint8_t tempL, tempH; int16_t temp; float temperature; DS18B20_Init(); DS18B20_WriteByte(0xCC); // 跳过ROM DS18B20_WriteByte(0x44); // 启动转换 HAL_Delay(750); // 等待转换完成 DS18B20_Init(); DS18B20_WriteByte(0xCC); // 跳过ROM DS18B20_WriteByte(0xBE); // 读取暂存器 tempL = DS18B20_ReadByte(); // LSB tempH = DS18B20_ReadByte(); // MSB temp = (tempH << 8) | tempL; temperature = temp * 0.0625; // 12位分辨率 return temperature; }4. LCD1602显示驱动
4.1 LCD初始化与基本指令
LCD1602的4位接口初始化序列:
- 等待15ms(上电稳定时间)
- 发送函数设置命令(0x28) - 4位模式,2行显示,5x8点阵
- 发送显示开关控制(0x0C) - 显示开,光标关,闪烁关
- 发送清屏命令(0x01)
- 发送输入模式设置(0x06) - 地址自动递增,不移位
初始化代码实现:
void LCD_Init(void) { HAL_Delay(15); // 上电等待 // 切换到4位模式 LCD_WriteCmd(0x33); LCD_WriteCmd(0x32); // 函数设置 LCD_WriteCmd(0x28); // 4位,2行,5x8 // 显示控制 LCD_WriteCmd(0x0C); // 显示开,光标关 // 清屏 LCD_WriteCmd(0x01); HAL_Delay(2); // 输入模式 LCD_WriteCmd(0x06); // 地址自增 }4.2 数据写入与字符串显示
核心写入函数实现:
// 写入命令 void LCD_WriteCmd(uint8_t cmd) { HAL_GPIO_WritePin(LCD_RS_PORT, LCD_RS_PIN, GPIO_PIN_RESET); // 命令模式 HAL_GPIO_WritePin(LCD_RW_PORT, LCD_RW_PIN, GPIO_PIN_RESET); // 写操作 // 高4位 HAL_GPIO_WritePin(LCD_D4_PORT, LCD_D4_PIN, (cmd >> 4) & 0x01); HAL_GPIO_WritePin(LCD_D5_PORT, LCD_D5_PIN, (cmd >> 5) & 0x01); HAL_GPIO_WritePin(LCD_D6_PORT, LCD_D6_PIN, (cmd >> 6) & 0x01); HAL_GPIO_WritePin(LCD_D7_PORT, LCD_D7_PIN, (cmd >> 7) & 0x01); LCD_Enable(); // 低4位 HAL_GPIO_WritePin(LCD_D4_PORT, LCD_D4_PIN, (cmd >> 0) & 0x01); HAL_GPIO_WritePin(LCD_D5_PORT, LCD_D5_PIN, (cmd >> 1) & 0x01); HAL_GPIO_WritePin(LCD_D6_PORT, LCD_D6_PIN, (cmd >> 2) & 0x01); HAL_GPIO_WritePin(LCD_D7_PORT, LCD_D7_PIN, (cmd >> 3) & 0x01); LCD_Enable(); HAL_Delay(1); } // 写入数据 void LCD_WriteData(uint8_t data) { HAL_GPIO_WritePin(LCD_RS_PORT, LCD_RS_PIN, GPIO_PIN_SET); // 数据模式 HAL_GPIO_WritePin(LCD_RW_PORT, LCD_RW_PIN, GPIO_PIN_RESET); // 写操作 // 高4位 HAL_GPIO_WritePin(LCD_D4_PORT, LCD_D4_PIN, (data >> 4) & 0x01); HAL_GPIO_WritePin(LCD_D5_PORT, LCD_D5_PIN, (data >> 5) & 0x01); HAL_GPIO_WritePin(LCD_D6_PORT, LCD_D6_PIN, (data >> 6) & 0x01); HAL_GPIO_WritePin(LCD_D7_PORT, LCD_D7_PIN, (data >> 7) & 0x01); LCD_Enable(); // 低4位 HAL_GPIO_WritePin(LCD_D4_PORT, LCD_D4_PIN, (data >> 0) & 0x01); HAL_GPIO_WritePin(LCD_D5_PORT, LCD_D5_PIN, (data >> 1) & 0x01); HAL_GPIO_WritePin(LCD_D6_PORT, LCD_D6_PIN, (data >> 2) & 0x01); HAL_GPIO_WritePin(LCD_D7_PORT, LCD_D7_PIN, (data >> 3) & 0x01); LCD_Enable(); HAL_Delay(1); } // 使能信号 void LCD_Enable(void) { HAL_GPIO_WritePin(LCD_E_PORT, LCD_E_PIN, GPIO_PIN_SET); delay_us(1); HAL_GPIO_WritePin(LCD_E_PORT, LCD_E_PIN, GPIO_PIN_RESET); delay_us(100); }4.3 温度显示实现
在LCD上显示温度值的完整流程:
- 清屏
- 设置显示位置(第一行)
- 显示静态文本(如"Temperature:")
- 设置显示位置(第二行)
- 将浮点温度值转换为字符串
- 显示温度值和单位
实现代码:
void LCD_DisplayTemp(float temp) { char buffer[16]; LCD_WriteCmd(0x01); // 清屏 HAL_Delay(2); LCD_SetCursor(0, 0); // 第一行 LCD_PrintStr("Temperature:"); LCD_SetCursor(0, 1); // 第二行 sprintf(buffer, "%.2f C", temp); LCD_PrintStr(buffer); } // 设置光标位置 void LCD_SetCursor(uint8_t x, uint8_t y) { uint8_t addr; if(y == 0) { addr = 0x80 + x; } else { addr = 0xC0 + x; } LCD_WriteCmd(addr); } // 打印字符串 void LCD_PrintStr(char *str) { while(*str) { LCD_WriteData(*str++); } }5. 系统集成与优化
5.1 主程序架构
一个典型的主循环实现:
int main(void) { HAL_Init(); SystemClock_Config(); LCD_Init(); DS18B20_Init(); float temperature; while(1) { temperature = DS18B20_GetTemp(); LCD_DisplayTemp(temperature); HAL_Delay(1000); // 每秒更新一次 } }5.2 常见问题排查
DS18B20无响应:
- 检查上拉电阻是否连接(4.7kΩ)
- 确认时序延迟准确,特别是复位脉冲
- 验证电源电压稳定(3.0-5.5V)
LCD显示异常:
- 检查对比度调节电位器设置
- 确认4位/8位模式设置正确
- 验证使能信号(E)的脉冲宽度足够
温度读数不稳定:
- 确保DS18B20远离热源和散热器
- 考虑添加软件滤波(如移动平均)
- 检查电源噪声,必要时增加滤波电容
5.3 进阶优化方向
低功耗设计:
- 使用STM32的睡眠模式
- 降低采样频率
- 关闭不必要的外设时钟
多传感器支持:
- 实现DS18B20的ROM搜索算法
- 支持多个传感器在同一总线上
温度报警功能:
- 设置上下限阈值
- 触发蜂鸣器或LED报警
数据记录:
- 添加EEPROM存储历史数据
- 实现简单的温度变化曲线显示
在实际项目中,我发现DS18B20的响应时间会随环境温度变化,在极端温度下转换时间可能超过标准值,因此建议将等待转换完成的时间设置为800ms以确保可靠性。
