基于STM32与DS18B20的LCD温度监控系统:C语言嵌入式开发实战

发布时间:2026/9/5 18:05:14
基于STM32与DS18B20的LCD温度监控系统:C语言嵌入式开发实战 简介本资源是一套面向嵌入式开发工程师与高校电子类专业学生的LCD温度监控系统完整实现方案聚焦于C语言驱动开发、硬件接口控制与低功耗无线通信集成。项目以温度采集—LCD实时显示—Zigbee无线传输为主线解决工业现场、智能终端等场景中对环境/设备温度可视化监控与远程上报的核心需求。压缩包共286个文件含128个C源码实现传感器驱动、LCD显示逻辑、主控调度、70个头文件模块化接口定义、24个汇编文件用于时序敏感的ADC采样与LCD刷新优化以及协议文档、IDE工程文件和硬件手册等辅助资料总大小18.64MB。已有304人学习下载提供可直接编译运行的多层架构代码、Zigbee子设备串口透传协议详解、BL55073液晶驱动芯片适配说明及完整README入门指南便于读者快速理解目录结构、复现功能并拓展多节点组网应用。1. 项目缘起为什么用C语言和LCD做温度监控最近在整理一些老项目的代码翻出来一个挺有意思的东西一个基于C语言和LCD显示模块的温度监控系统。这玩意儿虽然现在看起来技术栈有点“复古”但它的设计思路和实现细节对于想深入理解嵌入式系统、传感器数据采集和实时显示的朋友来说依然是个绝佳的练手项目。它不依赖复杂的操作系统框架就是最纯粹的“裸机”编程能把C语言的指针、结构体、定时器、中断、串口通信这些基础知识点串起来形成一个完整的闭环。这个项目的核心目标很简单实时采集环境温度并在一个字符型LCD比如常见的1602或2004上清晰地显示出来。听起来简单对吧但真动手做起来从选型、电路连接、驱动编写、数据处理到稳定显示每一步都有不少门道。比如温度传感器用DS18B20还是DHT11LCD是并口驱动还是I2C转接板采样频率多高合适数据显示要不要做滤波防抖动这些都是在源码背后需要思考的问题。今天我就把这个项目的设计思路和核心源码拆开揉碎了讲清楚你可以把它看作一个“麻雀虽小五脏俱全”的嵌入式入门实战案例。2. 硬件选型与系统架构设计做嵌入式项目硬件是地基。选型不对代码写得再漂亮也是白搭。这个温度监控系统硬件上主要就三块主控MCU、温度传感器、LCD显示模块。2.1 主控MCU的选择为何是STM32F103C8T6在众多单片机中我选择了经典的“蓝桥杯”神器——STM32F103C8T6。原因有几个首先它基于ARM Cortex-M3内核性能对于这个项目绰绰有余有丰富的定时器、GPIO和通信接口USART、I2C、SPI。其次它的社区资源极其丰富各种库函数标准库、HAL库和教程一抓一大把出了问题容易找到解决方案。最后成本低廉开发板比如最小系统板二十块钱左右就能拿下非常适合学习和原型开发。当然你也可以用51单片机如STC89C52来实现代码会更精简。但STM32的优势在于它为你打开了更广阔的世界比如后续你想加入数据存储SD卡、无线传输ESP8266、或者更复杂的UISTM32都能提供更好的支持。在这个项目里我们主要用到它的GPIO模拟时序驱动传感器和LCD、定时器用于精确延时和定时采样以及一个USART可选用于调试信息输出。2.2 温度传感器DS18B20的“单总线”哲学温度传感器我选了DS18B20。这是一个数字温度传感器最大特点就是采用“单总线”1-Wire协议。顾名思义只用一根数据线DQ就能完成供电和数据通信通常还需要VCC和GND。这大大节省了MCU的IO口资源布线也简单。注意DS18B20对时序要求非常严格。它的通信协议完全由MCU通过拉高拉低DQ线来实现包括初始化、写一位、读一位等操作。时序偏差几个微秒就可能导致通信失败。因此在代码中我们需要用精准的延时函数通常基于系统滴答定时器SysTick来严格控制高低电平的持续时间。这也是为什么很多新手在驱动DS18B20时容易卡住的原因——延时不准。DS18B20的精度是±0.5°C测量范围-55°C到125°C完全满足室内环境监控的需求。它内部有9到12位的分辨率可配置分辨率越高转换时间越长。我们一般用12位分辨率转换时间约750ms。2.3 LCD显示模块并口直驱 vs I2C模块LCD模块我用了最经典的1602字符液晶16列x2行。关于驱动方式这里有个关键选择是直接使用LCD的并口8位或4位数据线3根控制线还是使用市面上常见的I2C转接板并口直驱需要占用MCU较多的IO口4位模式需7个IO8位模式需11个IO。优点是速度最快控制最直接没有中间层理解底层时序的好方法。代码需要自己实现忙状态检测、写命令、写数据等底层函数。I2C转接板一个很小的电路板插在LCD背面将并口信号转为I2C通信。只需要占用MCU的2个IO口SCL和SDA。优点是节省IO接线简单。缺点是多了一层抽象你需要先驱动PCF8574这类I2C IO扩展芯片然后再通过它来控制LCD增加了复杂性且刷新速度稍慢。为了更深入地理解LCD工作原理我们这个项目采用4位并口直驱的方式。这样虽然多用了几个IO但你能彻底搞清楚LCD控制器如HD44780的指令集和读写时序这是嵌入式显示技术的基石。系统架构很简单STM32的GPIO连接DS18B20的DQ线另一组GPIO连接LCD的RS、RW、E以及D4-D7数据线。主程序在一个循环中定时读取温度处理后刷新LCD显示。3. 核心驱动层代码实现与解析驱动层是连接硬件和应用的桥梁写好了驱动上层应用逻辑就会非常清晰。这部分代码的健壮性直接决定了整个系统的稳定性。3.1 DS18B20驱动精确到微秒的时序控制DS18B20的驱动是整个项目对时序要求最苛刻的部分。它的通信序列包括初始化复位脉冲存在脉冲、写时序写0、写1、读时序。下面给出核心函数并附上关键注释。首先需要定义DQ引脚并实现微秒级延时函数。这里假设我们使用STM32标准库并配置好了SysTick定时器1us中断一次。// ds18b20.h #ifndef __DS18B20_H #define __DS18B20_H #include stm32f10x.h // 定义DS18B20连接的GPIO端口和引脚 #define DS18B20_PORT GPIOB #define DS18B20_PIN GPIO_Pin_12 // 引脚操作宏定义提高代码可读性和执行效率 #define DS18B20_DQ_OUT_HIGH() GPIO_SetBits(DS18B20_PORT, DS18B20_PIN) #define DS18B20_DQ_OUT_LOW() GPIO_ResetBits(DS18B20_PORT, DS18B20_PIN) #define DS18B20_DQ_IN() GPIO_ReadInputDataBit(DS18B20_PORT, DS18B20_PIN) // 设置DQ引脚为推挽输出模式用于写操作 #define DS18B20_DQ_MODE_OUT() do{ \ GPIO_InitTypeDef GPIO_InitStructure; \ GPIO_InitStructure.GPIO_Pin DS18B20_PIN; \ GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; \ GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; \ GPIO_Init(DS18B20_PORT, GPIO_InitStructure); \ }while(0) // 设置DQ引脚为浮空输入模式用于读操作 #define DS18B20_DQ_MODE_IN() do{ \ GPIO_InitTypeDef GPIO_InitStructure; \ GPIO_InitStructure.GPIO_Pin DS18B20_PIN; \ GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; \ GPIO_Init(DS18B20_PORT, GPIO_InitStructure); \ }while(0) // 函数声明 void DS18B20_Init(void); void DS18B20_Reset(void); uint8_t DS18B20_Check(void); void DS18B20_WriteByte(uint8_t dat); uint8_t DS18B20_ReadByte(void); float DS18B20_ReadTemp(void); #endif// ds18b20.c #include ds18b20.h #include delay.h // 假设有一个精准的微秒延时函数 delay_us() // 初始化DQ引脚 void DS18B20_Init(void) { RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); // 初始化为输出模式 DS18B20_DQ_MODE_OUT(); DS18B20_DQ_OUT_HIGH(); // 默认拉高 } // 复位DS18B20 void DS18B20_Reset(void) { DS18B20_DQ_MODE_OUT(); DS18B20_DQ_OUT_LOW(); delay_us(480); // 保持低电平480us以上典型值480-960us DS18B20_DQ_OUT_HIGH(); delay_us(60); // 释放总线等待15-60us后DS18B20会拉低总线 } // 检测DS18B20是否存在 uint8_t DS18B20_Check(void) { uint8_t retry 0; DS18B20_DQ_MODE_IN(); // 等待DQ被DS18B20拉低存在脉冲 while (DS18B20_DQ_IN() retry 200) { retry; delay_us(1); } if (retry 200) return 0; // 超时未检测到存在脉冲 retry 0; // 等待存在脉冲结束DS18B20释放总线DQ被上拉电阻拉高 while (!DS18B20_DQ_IN() retry 240) { retry; delay_us(1); } if (retry 240) return 0; // 超时存在脉冲异常 return 1; // 检测成功 } // 向DS18B20写入一个字节低位在先 void DS18B20_WriteByte(uint8_t dat) { uint8_t i; DS18B20_DQ_MODE_OUT(); for (i 0; i 8; i) { DS18B20_DQ_OUT_LOW(); // 开始写时间隙 delay_us(2); // 拉低后延时约2us if (dat 0x01) { DS18B20_DQ_OUT_HIGH(); // 写1 } else { // 写0保持低电平 } delay_us(60); // 保持时间总共写时间隙至少60us DS18B20_DQ_OUT_HIGH(); // 释放总线为下一个时间隙准备 dat 1; // 准备下一位 delay_us(2); // 写周期之间的恢复时间 } } // 从DS18B20读取一个字节低位在先 uint8_t DS18B20_ReadByte(void) { uint8_t i, dat 0; for (i 0; i 8; i) { dat 1; // 先右移最后读到的位会在最高位循环结束后再调整 DS18B20_DQ_MODE_OUT(); DS18B20_DQ_OUT_LOW(); delay_us(2); // 拉低至少1us启动读时间隙 DS18B20_DQ_OUT_HIGH(); DS18B20_DQ_MODE_IN(); // 释放总线准备读取 delay_us(12); // 等待约12us后读取电平 if (DS18B20_DQ_IN()) { dat | 0x80; // 读到的位是1 } // 如果读到的位是0则dat对应位已经是0因为右移过 delay_us(50); // 等待读时间隙结束 } return dat; } // 读取温度值返回浮点数 float DS18B20_ReadTemp(void) { uint8_t tempL, tempH; int16_t temp; float temperature; DS18B20_Reset(); if (DS18B20_Check() 0) { return -999.9; // 返回一个错误值 } DS18B20_WriteByte(0xCC); // 跳过ROM命令单设备时常用 DS18B20_WriteByte(0x44); // 启动温度转换命令 delay_ms(750); // 等待转换完成12位分辨率约750ms DS18B20_Reset(); DS18B20_Check(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读取暂存器命令 tempL DS18B20_ReadByte(); // 温度低字节 tempH DS18B20_ReadByte(); // 温度高字节 // 将两个字节组合成一个16位有符号整数 temp (tempH 8) | tempL; // DS18B20输出的是补码形式且精度为0.0625°C/LSB temperature temp * 0.0625; return temperature; }实操心得DS18B20的驱动调试最好用逻辑分析仪抓一下时序波形。没有的话就靠“盲调”和串口打印。常见问题1. 延时函数不准务必用示波器或SysTick校准你的delay_us()。2. 上拉电阻DQ线通常需要一个4.7KΩ的上拉电阻到VCC确保总线空闲时为高电平。3. 电源干扰如果供电不稳DS18B20可能工作异常可以在VCC和GND之间加一个0.1uF的退耦电容。3.2 LCD1602驱动理解HD44780指令集LCD1602的驱动相对规整关键是理解其控制器HD44780的指令集和4位初始化序列。我们采用4位数据模式只使用D4-D7D0-D3悬空。// lcd1602.h #ifndef __LCD1602_H #define __LCD1602_H #include stm32f10x.h // 根据实际硬件连接修改这些宏定义 #define LCD1602_RS_PORT GPIOB #define LCD1602_RS_PIN GPIO_Pin_10 #define LCD1602_RW_PORT GPIOB #define LCD1602_RW_PIN GPIO_Pin_11 #define LCD1602_E_PORT GPIOB #define LCD1602_E_PIN GPIO_Pin_13 #define LCD1602_D4_PORT GPIOB #define LCD1602_D4_PIN GPIO_Pin_14 #define LCD1602_D5_PORT GPIOB #define LCD1602_D5_PIN GPIO_Pin_15 #define LCD1602_D6_PORT GPIOC #define LCD1602_D6_PIN GPIO_Pin_13 #define LCD1602_D7_PORT GPIOC #define LCD1602_D7_PIN GPIO_Pin_14 // 函数声明 void LCD1602_Init(void); void LCD1602_WriteCmd(uint8_t cmd); void LCD1602_WriteData(uint8_t dat); void LCD1602_Clear(void); void LCD1602_SetCursor(uint8_t row, uint8_t col); void LCD1602_PrintStr(char *str); #endif// lcd1602.c #include lcd1602.h #include delay.h // 内部函数写4位数据高4位 static void LCD1602_Write4Bits(uint8_t data) { // 根据data的高4位设置D4-D7引脚 if (data 0x08) GPIO_SetBits(LCD1602_D4_PORT, LCD1602_D4_PIN); else GPIO_ResetBits(LCD1602_D4_PORT, LCD1602_D4_PIN); if (data 0x04) GPIO_SetBits(LCD1602_D5_PORT, LCD1602_D5_PIN); else GPIO_ResetBits(LCD1602_D5_PORT, LCD1602_D5_PIN); if (data 0x02) GPIO_SetBits(LCD1602_D6_PORT, LCD1602_D6_PIN); else GPIO_ResetBits(LCD1602_D6_PORT, LCD1602_D6_PIN); if (data 0x01) GPIO_SetBits(LCD1602_D7_PORT, LCD1602_D7_PIN); else GPIO_ResetBits(LCD1602_D7_PORT, LCD1602_D7_PIN); // 产生使能信号E的下降沿 GPIO_SetBits(LCD1602_E_PORT, LCD1602_E_PIN); delay_us(1); // 使能脉冲宽度至少450ns GPIO_ResetBits(LCD1602_E_PORT, LCD1602_E_PIN); delay_us(40); // 命令执行时间 } // 内部函数写一个字节命令或数据 static void LCD1602_WriteByte(uint8_t data, uint8_t mode) { if (mode 0) { GPIO_ResetBits(LCD1602_RS_PORT, LCD1602_RS_PIN); // RS0写命令 } else { GPIO_SetBits(LCD1602_RS_PORT, LCD1602_RS_PIN); // RS1写数据 } GPIO_ResetBits(LCD1602_RW_PORT, LCD1602_RW_PIN); // RW0写操作 // 先写高4位再写低4位 LCD1602_Write4Bits(data 4); LCD1602_Write4Bits(data 0x0F); } // 初始化LCD16024位模式 void LCD1602_Init(void) { // 初始化所有用到的GPIO为推挽输出 GPIO_InitTypeDef GPIO_InitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_GPIOC, ENABLE); GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode GPIO_Mode_Out_PP; GPIO_InitStructure.GPIO_Pin LCD1602_RS_PIN; GPIO_Init(LCD1602_RS_PORT, GPIO_InitStructure); // ... 类似地初始化RW, E, D4-D7引脚 ... // 省略具体引脚初始化代码原理相同 delay_ms(15); // 上电等待LCD内部复位完成15ms // 4位模式初始化序列关键 LCD1602_Write4Bits(0x03); // 尝试设置为8位模式 delay_ms(5); LCD1602_Write4Bits(0x03); delay_us(150); LCD1602_Write4Bits(0x03); delay_us(150); LCD1602_Write4Bits(0x02); // 切换到4位模式 delay_us(150); // 以下发送完整的命令现在已经是4位模式一次发8位命令要分两次 LCD1602_WriteCmd(0x28); // 功能设置4位数据2行显示5x8点阵 LCD1602_WriteCmd(0x0C); // 显示控制开显示关光标不闪烁 LCD1602_WriteCmd(0x06); // 进入模式设置读写后光标右移整屏不移动 LCD1602_Clear(); // 清屏 LCD1602_WriteCmd(0x80); // 设置DDRAM地址为0即第一行开头 } // 写命令 void LCD1602_WriteCmd(uint8_t cmd) { LCD1602_WriteByte(cmd, 0); } // 写数据 void LCD1602_WriteData(uint8_t dat) { LCD1602_WriteByte(dat, 1); } // 清屏 void LCD1602_Clear(void) { LCD1602_WriteCmd(0x01); delay_ms(2); // 清屏命令需要较长执行时间 } // 设置光标位置 row: 0-1, col: 0-15 void LCD1602_SetCursor(uint8_t row, uint8_t col) { uint8_t addr; if (row 0) { addr 0x80 col; // 第一行DDRAM地址从0x80开始 } else { addr 0xC0 col; // 第二行DDRAM地址从0xC0开始 } LCD1602_WriteCmd(addr); } // 显示字符串 void LCD1602_PrintStr(char *str) { while (*str) { LCD1602_WriteData(*str); } }避坑指南LCD1602初始化失败十有八九是4位初始化序列没做对。上面代码中的三次写0x03和一次写0x02是标准流程必须严格遵守时序和延时。另外清屏命令0x01执行时间较长约1.64ms必须给予足够延时否则后续命令可能失效。如果显示乱码检查数据线连接是否牢固以及发送的字符数据是否在ASCII码范围内。4. 应用逻辑与数据处理从裸数据到稳定显示驱动写好了相当于打通了“任督二脉”。接下来就是应用层逻辑如何组织这些底层能力实现一个稳定、好用的温度监控功能。4.1 主程序框架与定时采样一个典型的嵌入式裸机程序主循环通常采用“超级循环”配合定时器中断的模式。对于温度监控我们不需要极高的刷新率比如1秒更新一次就足够了可以用SysTick或者基本定时器产生一个1秒的中断在中断服务函数中设置一个标志位在主循环中检测这个标志位来执行温度读取和显示刷新。// main.c #include stm32f10x.h #include ds18b20.h #include lcd1602.h #include delay.h #include stdio.h volatile uint8_t g_update_flag 0; // 1秒更新标志 // SysTick中断服务函数1ms中断一次 void SysTick_Handler(void) { static uint32_t tick_count 0; tick_count; if (tick_count 1000) { // 每1000ms1秒 tick_count 0; g_update_flag 1; // 置位更新标志 } } // 简单的浮点数转字符串函数用于LCD显示 void FloatToStr(float f, char *buf, uint8_t precision) { int integer_part (int)f; float fractional_part f - integer_part; if (fractional_part 0) fractional_part -fractional_part; for (int i 0; i precision; i) { fractional_part * 10; } int frac_int (int)(fractional_part 0.5); // 四舍五入 sprintf(buf, %d.%0*d, integer_part, precision, frac_int); } int main(void) { float temperature; char disp_buf[17]; // 1602一行16字符加一个结束符 // 系统初始化 Delay_Init(); // 初始化延时函数基于SysTick DS18B20_Init(); LCD1602_Init(); // 初始化SysTick配置为1ms中断一次 if (SysTick_Config(SystemCoreClock / 1000)) { while (1); // 初始化失败死循环 } // 显示初始信息 LCD1602_SetCursor(0, 0); LCD1602_PrintStr(Temp Monitor); LCD1602_SetCursor(1, 0); LCD1602_PrintStr(Initializing...); delay_ms(1000); LCD1602_Clear(); while (1) { if (g_update_flag) { g_update_flag 0; // 清除标志 // 1. 读取温度 temperature DS18B20_ReadTemp(); // 简单的错误处理 if (temperature -50.0 || temperature 100.0) { // 可能是读取错误可以重试一次或显示错误信息 LCD1602_SetCursor(1, 0); LCD1602_PrintStr(Sensor Error!); continue; } // 2. 数据处理这里可以加入滤波算法比如一阶滞后滤波 // static float last_temp 0; // temperature 0.7 * last_temp 0.3 * temperature; // 简单滤波 // last_temp temperature; // 3. 格式化显示字符串 FloatToStr(temperature, disp_buf, 1); // 保留一位小数 // 构造完整的显示字符串例如Temperature:25.6C // 由于1602只有16列需要精简显示 LCD1602_SetCursor(0, 0); LCD1602_PrintStr(Temp:); LCD1602_SetCursor(0, 6); // 从第6列开始显示数值 LCD1602_PrintStr(disp_buf); LCD1602_WriteData(0xDF); // 显示度符号°在LCD的CGRAM中0xDF常被定义为° LCD1602_WriteData(C); // 清空第二行剩余部分避免上次显示残留 LCD1602_SetCursor(1, 0); LCD1602_PrintStr( ); // 16个空格 // 可以在第二行显示其他信息如时间、状态等 } // 主循环中可以处理其他任务如按键扫描等 // ... } }4.2 温度数据的滤波处理直接从传感器读出的温度值可能会有小的跳动尤其是在传感器刚上电或环境有轻微气流时。为了显示更稳定通常需要加入软件滤波。上面代码注释中提到的**一阶滞后滤波也称指数加权平均滤波**是一种简单有效的方法。其公式为本次滤波值 α * 上次滤波值 (1-α) * 本次采样值。其中α是滤波系数0α1α越接近1滤波效果越强但响应越慢。α0.7是一个常用的起始值。你可以根据实际跳动情况调整α。更复杂的还有中值滤波连续采样N次取中间值、滑动平均滤波等。对于DS18B20由于其本身是数字输出且分辨率高轻微的跳动是正常的加一个轻量级的滤波就能让显示效果提升很多。4.3 显示优化与用户交互基本的显示完成了还可以做一些优化单位显示如上所示使用LCD自定义字符功能CGRAM显示度符号“°”。需要先向CGRAM写入度符号的点阵数据。负温度显示DS18B20读出的16位数据是补码形式我们的DS18B20_ReadTemp函数已经处理了直接显示float型负数即可。注意在格式化字符串时负号会占一个字符位置。报警功能可以设置一个温度阈值当超过阈值时让LCD背光闪烁或者控制一个LED灯、蜂鸣器。这只需要在主循环中增加一个判断逻辑。历史记录如果MCU Flash或外接EEPROM有空间可以定时存储温度值后续再通过串口上传到电脑分析。5. 系统调试与问题排查实战记录代码写完了烧录进去LCD不亮或者显示乱码传感器读不出数据这些都是家常便饭。下面是我在调试这个项目时遇到的一些典型问题及排查思路希望能帮你少走弯路。5.1 LCD1602只有背光亮无任何字符显示可能原因1对比度问题。这是最常见的原因。1602有一个VO引脚对比度调节通常接一个10K电位器的中间抽头电位器两端接VCC和GND。调节这个电位器直到字符隐约出现。如果接错了或者电位器阻值不合适就可能完全看不到显示。可能原因2初始化失败。用调试器单步跟踪LCD1602_Init函数或者通过串口在初始化每一步后打印日志确认初始化序列的每个命令都成功执行了。重点检查4位初始化序列那几条命令的时序和延时。可能原因3硬件连接错误。用万用表逐一检查RS、RW、E、D4-D7这7根线是否与MCU引脚正确连接有没有虚焊、短路。特别是RW引脚如果悬空或接错LCD可能处于读取状态而非写入状态。可能原因4电源问题。确保LCD的VCC和GND供电正常通常是5V。有些1602模块3.3V也能工作但可能亮度不足或不稳定。5.2 DS18B20始终返回-999.9或固定值85可能原因1时序不精确。这是最大的嫌疑。用逻辑分析仪连接DQ线抓取复位、写命令、读数据的波形严格按照DS18B20数据手册的时序图对比。检查delay_us()函数的准确性。在STM32中如果用循环实现微秒延时受编译器优化和中断影响很大强烈建议使用定时器如SysTick实现精准延时。可能原因2上拉电阻缺失或阻值不对。单总线必须有一个上拉电阻通常4.7KΩ确保总线在空闲时被拉至高电平。如果没有这个电阻DQ线可能无法被正确拉高导致通信失败。可能原因3电源模式问题。DS18B20有寄生电源模式只接DQ和GND和外接电源模式接VCC、DQ、GND。我们通常用外接电源模式更稳定。如果使用寄生模式在温度转换期间总线必须保持高电平以提供能量时序控制更复杂。可能原因4传感器损坏或接触不良。换一个DS18B20试试。检查连接线是否牢固。5.3 温度显示值跳动剧烈可能原因1未进行软件滤波。如4.2节所述加入一阶滞后滤波。可能原因2电源噪声。传感器和MCU的电源纹波可能影响ADC虽然DS18B20是数字的但电源噪声会影响其内部振荡器。在VCC和GND之间靠近传感器引脚处加一个0.1uF的陶瓷电容。可能原因3环境因素。如果传感器暴露在气流中如风扇附近或者测量热源表面温度本身就会快速变化。给传感器加一个小的金属罩或使用导热硅脂粘贴可以使其温度更稳定。5.4 程序运行一段时间后死机可能原因1堆栈溢出。检查是否在中断服务函数SysTick_Handler中做了太多事情比如调用耗时的函数DS18B20_ReadTemp。中断服务函数应尽可能短小只设置标志位。可能原因2看门狗未喂狗。如果开启了独立看门狗IWDG或窗口看门狗WWDG必须在主循环中定期喂狗否则会导致复位。可能原因3内存泄漏或数组越界。虽然C语言裸机程序很少动态内存分配但检查是否有全局数组或局部数组访问越界这可能会破坏关键数据导致程序跑飞。调试嵌入式系统分而治之和借助工具是关键。先把LCD显示固定字符串调通再单独调传感器读取并通过串口打印最后把两者结合起来。逻辑分析仪、示波器、串口调试助手是你的好朋友。没有硬件工具时充分利用MCU的GPIO翻转和串口打印日志也能解决大部分问题。这个基于C语言的LCD温度监控项目虽然代码量不大但涵盖了嵌入式开发从硬件到软件的完整链条。把它吃透你对单片机系统的理解会上一个台阶。本文还有配套的精品资源点击获取