资讯动态

STM32F407实战:DS18B20单总线温度采集与TFT局部刷新

发布时间:2026/10/20 13:53:30 来源:尧图企业网站定制
简介本资源面向嵌入式开发初学者与物联网实践者围绕STM32F407微控制器与DS18B20数字温度传感器的结合应用提供一套可运行的完整工程案例。项目通过单总线协议驱动DS18B20采集高精度温度数据并借助TFT屏幕与GUI库将结果以仪表盘或温度计样式直观呈现帮助读者理解GPIO开漏配置、单总线时序控制及SPI/I2C显示驱动等关键环节。压缩包共248个文件约10.96MB以h头文件、c源文件、o目标文件、d依赖文件及crf编译记录为主另含uvprojx工程配置、axf可执行映像与map映射文件覆盖Library、Project、User、Listing、Output等模块便于直接编译烧录与二次开发。目前已有1006人学习下载适合作为嵌入式系统、单总线通信与GUI设计的练手项目。1. 从一根单总线和一块 TFT 说起这个 STM32F407 温度采集项目到底能干什么如果你手头正好有一块 STM32F407 开发板、一颗 DS18B20 温度传感器外加一块 TFT 彩屏却不知道怎么把它们串成一个能看、能改、能扩展的小系统这份资源就是冲着你来的。它把「单总线协议读取温度 → 数据格式化 → TFT GUI 实时刷新」这条链路完整跑通不是那种只点个灯就结束的 Demo。我第一次拆这类项目时最直观的感受是DS18B20 的时序比想象中娇气TFT 刷屏又比想象中吃资源两者凑一起正好逼着你把 GPIO 模拟时序、定时器延时、屏幕局部刷新这些基本功过一遍。它适合刚学完 STM32 外设、想找一个「有输入有输出有界面」的练手项目的开发者也适合需要给现有工程加一个低成本温度显示模块的人。下面按「能干什么 → 怎么跑 → 怎么改」的顺序把这份源码拆开讲。2. 架构与目录STM32F407 工程里 DS18B20 和 TFT 是怎么分工的2.1 整体分层从传感器到屏幕的数据流这个项目虽然体量不大但分层是清晰的常见做法是分成三层硬件驱动层、数据处理层、界面显示层。硬件驱动层负责 DS18B20 的单总线时序和 TFT 的底层写点/写块数据处理层把原始的温度寄存器值换算成带小数的摄氏度并做简单的滤波界面显示层负责把温度值画到屏幕指定区域控制刷新频率。数据流是这样的定时器周期性触发采集 → DS18B20 驱动完成复位、跳过 ROM、温度转换、读暂存器 → 数据处理层把 16 位原始值乘以 0.0625 得到摄氏度 → 界面层判断数值是否变化变化才局部刷新避免整屏重绘导致闪烁。这个「变化才刷」的策略是这类项目能不能跑得顺眼的关键后面避坑章节会细说。2.2 目录结构每个文件夹对应什么职责拿到源码后目录通常长这样不同工程模板命名略有差异但职责一致Project/ ├── Core/ │ ├── Inc/ │ │ ├── main.h │ │ ├── ds18b20.h // 传感器驱动接口 │ │ ├── tft_gui.h // 界面绘制接口 │ │ └── delay.h // 微秒级延时 │ └── Src/ │ ├── main.c // 初始化与主循环 │ ├── ds18b20.c // 单总线时序实现 │ ├── tft_gui.c // 温度显示与刷新逻辑 │ └── delay.c // 基于 SysTick 的延时 ├── Drivers/ │ └── STM32F4xx_HAL_Driver/ └── MDK-ARM/ // 或 STM32CubeIDE 工程文件ds18b20.c是核心单总线所有时序都在这里tft_gui.c决定显示效果delay.c提供微秒延时DS18B20 对延时精度要求高这个文件不能随便用 HAL 的毫秒延时替代。main.c只做初始化和调度逻辑尽量别往主循环里堆。2.3 关键配置时钟、GPIO 与定时器STM32F407 主频一般跑到 168MHzDS18B20 的 GPIO 需要配置成开漏输出加外部上拉通常 4.7kΩ或者用推挽输出加方向切换。常见做法是配置一个 GPIO 为输出模式用于拉低总线读取时切换为输入模式。TFT 如果是 SPI 接口需要配置 SPI 外设和片选、数据命令引脚如果是 FSMC 并口屏配置会复杂一些但刷新更快。定时器方面用一个基本定时器比如 TIM6产生 1 秒或 2 秒的采集节拍中断里置一个标志位主循环检测标志位再执行采集避免在中断里做耗时操作。这个结构比在while(1)里裸延时优雅得多也方便后续加其他任务。3. 跑起来从零编译到屏幕出数的完整步骤3.1 环境准备与工程打开先确认你用的 IDE。这类工程常见两种Keil MDK 和 STM32CubeIDE。压缩包里一般两个版本的工程文件都有选你顺手的。以 Keil 为例安装好 STM32F4 的器件支持包打开MDK-ARM下的工程文件编译前检查一下芯片型号是否选对STM32F407 系列具体型号按你板子来。如果用的是 CubeIDE直接File → Open Projects from File System导入工程根目录它会自动识别.project文件。导入后先别急着编译看一眼main.h里的引脚宏定义确认和你板子的实际接线一致。3.2 接线核对DS18B20 和 TFT 的引脚对应接线错了后面所有调试都是白费。DS18B20 三根线VCC、GND、DQ。DQ 接哪个 GPIO 由ds18b20.h里的宏决定常见是某个 GPIO 的 Pin 0 或 Pin 1。TFT 如果是 SPI 屏至少需要 SCK、MOSI、CS、DC、RST、BLK 六根线对应关系在tft_gui.h或屏幕驱动文件里。提示DS18B20 的 DQ 线上一定要接 4.7kΩ 上拉到 3.3V很多「读出来一直是 85℃」的问题都是上拉缺失或阻值不对导致的。核对完接线把板子通过调试器连上电脑供电正常后编译下载。3.3 编译下载与首次运行观察编译如果报错先看是不是缺少某个驱动文件或者头文件路径没加。这类工程常见报错是cannot open source input file xxx.h去工程设置里的 Include Paths 把Core/Inc加进去即可。下载后复位正常现象是屏幕先清屏或显示一个背景色然后温度数值出现在指定位置大约每 1 到 2 秒更新一次。如果屏幕一直白屏或花屏先查 TFT 的初始化和背光引脚如果屏幕正常但温度一直是 85 或 0问题在 DS18B20 那边跳到下一章的排查部分。3.4 主循环调度逻辑主循环不复杂核心就是「等标志 → 采集 → 换算 → 刷新」while (1) { if (temp_sample_flag) // 定时器中断置位1秒一次 { temp_sample_flag 0; raw DS18B20_ReadTemp(); // 读原始16位值 if (raw ! 0xFFFF) // 简单校验排除读失败 { temp raw * 0.0625f; // 换算成摄氏度 GUI_UpdateTemp(temp); // 界面刷新内部判断是否变化 } } /* 其他任务可在此扩展 */ }temp_sample_flag由定时器中断置位保证采集节拍稳定DS18B20_ReadTemp返回原始值失败时返回一个约定值0.0625是 DS18B20 的分辨率12 位模式下每 LSB 代表 0.0625℃GUI_UpdateTemp内部做新旧值比较只有变化超过阈值才重绘减少闪烁。这个结构留了扩展口后面加湿度、加按键都方便。4. 核心代码拆解单总线时序与 TFT 局部刷新4.1 DS18B20 复位与存在脉冲检测单总线所有通信都从复位开始时序错了后面全错uint8_t DS18B20_Reset(void) { uint8_t presence; DS18B20_DQ_OUT(); // DQ设为输出 DS18B20_DQ_LOW(); // 拉低总线 delay_us(480); // 保持至少480us DS18B20_DQ_HIGH(); // 释放总线 delay_us(60); // 等待15-60us DS18B20_DQ_IN(); // 切换为输入 presence DS18B20_DQ_READ(); // 读存在脉冲0表示有器件 delay_us(420); // 等待复位时序结束 return presence; }delay_us(480)是复位低电平持续时间DS18B20 手册要求最少 480us释放后等 60us 再采样落在 15 到 60us 的窗口内读到 0 说明器件拉低了总线存在最后再等 420us 让整个时隙走完。这几个延时值不能随意改改小了器件不响应改大了拖慢采集。我一般会把delay_us用 SysTick 或 DWT 实现精度比 HAL 的HAL_Delay高得多。4.2 读写时隙一位一位地抠写一位和读一位的时序不同分开实现void DS18B20_WriteBit(uint8_t bit) { DS18B20_DQ_OUT(); DS18B20_DQ_LOW(); if (bit) { delay_us(2); // 写1拉低2us后释放 DS18B20_DQ_HIGH(); delay_us(60); } else { delay_us(60); // 写0拉低保持60us DS18B20_DQ_HIGH(); delay_us(2); } } uint8_t DS18B20_ReadBit(void) { uint8_t bit 0; DS18B20_DQ_OUT(); DS18B20_DQ_LOW(); delay_us(2); // 拉低至少1us启动读时隙 DS18B20_DQ_HIGH(); DS18B20_DQ_IN(); delay_us(10); // 等待器件输出稳定 bit DS18B20_DQ_READ(); delay_us(50); // 补足时隙 return bit; }写 1 是短低电平后释放写 0 是长低电平读时隙是主机拉低至少 1us 后释放器件在 15us 内把数据放上总线主机在 10us 左右采样最稳。这些数字来自器件手册不同批次可能略有差异但按这个来基本不会翻车。读回来的位按字节拼装就是完整的温度寄存器值。4.3 温度换算与负温度处理DS18B20 输出的是 16 位补码高 5 位是符号位float DS18B20_Convert(uint16_t raw) { float temp; if (raw 0x8000) // 负温度 { temp (float)((~raw 1) 0xFFFF) * 0.0625f; temp -temp; } else { temp (float)raw * 0.0625f; } return temp; }正温度直接乘 0.0625负温度先取补码再乘最后加负号。很多示例代码只处理正温度冬天室外一测就出错这个坑我踩过。换算完建议做一次简单滤波比如连续采三次取中间值能压掉不少跳变。4.4 TFT 局部刷新只重绘变化的区域整屏刷新在 SPI 屏上很慢局部刷新是必须的void GUI_UpdateTemp(float new_temp) { static float last_temp -100.0f; char buf[16]; if (fabsf(new_temp - last_temp) 0.1f) // 变化小于0.1度不刷 return; last_temp new_temp; sprintf(buf, %.1f C, new_temp); TFT_FillRect(TEMP_X, TEMP_Y, TEMP_W, TEMP_H, BACK_COLOR); // 清旧值区域 TFT_DrawString(TEMP_X, TEMP_Y, buf, FONT_COLOR, BACK_COLOR); }last_temp记录上次显示值变化小于 0.1℃ 直接返回避免无意义刷新TFT_FillRect只清温度数值那一小块区域不是整屏sprintf格式化后画字符串。TEMP_X/Y/W/H这些宏在头文件里定义改显示位置只动宏就行。如果屏幕还是闪把TFT_FillRect换成只清字符实际占用的宽度能再快一点。5. 避坑与排查DS18B20 和 TFT 最容易翻车的几个点5.1 温度恒为 85℃现象屏幕显示 85.0 不变。原因DS18B20 上电默认寄存器值就是 85℃说明温度转换命令没发出去或者转换没完成就读了。解决检查复位是否存在脉冲、跳过 ROM 命令0xCC和转换命令0x44是否发出、转换后是否等了足够时间12 位模式最多 750ms。我一般会在转换后延时 750ms 再读或者读状态位判断转换完成。5.2 温度跳变严重现象数值在几度范围内乱跳。原因单总线时序受中断干扰或者电源不稳。解决读时序期间关中断或者把采集放在中断优先级较高的定时器里电源脚加 0.1uF 去耦电容软件上做多次采样取中值。这个坑很玄学有时候换根短一点的杜邦线就好了。5.3 屏幕花屏或白屏现象TFT 显示乱码或全白。原因初始化序列不对、SPI 速率过高、片选或数据命令引脚接反。解决先把 SPI 速率降到 10MHz 以下试确认初始化命令和屏幕型号匹配用逻辑分析仪抓一下 SPI 波形看命令有没有发出去。并口屏则检查 FSMC 时序参数。5.4 编译通过但下载后没反应现象程序下载成功屏幕和传感器都没动静。原因时钟配置不对比如外部晶振频率和代码里写的不一致导致主频跑飞。解决核对SystemClock_Config里的晶振值和你板子上的实际晶振8M 和 25M 的配置不一样。这个错误很隐蔽因为编译不会报错。5.5 温度刷新慢半拍现象温度变化后屏幕要好几秒才更新。原因采集周期太长或者刷新判断阈值太大。解决把定时器周期从 2 秒改成 1 秒刷新阈值从 0.5℃ 降到 0.1℃。但别改太激进DS18B20 转换本身就需要时间采集周期小于转换时间会读到旧值。6. 二次开发把温度采集扩展成多传感器小系统的改造思路这份代码最值钱的地方不是它现在能显示温度而是它留出的扩展结构。我拿到手第一件事通常是把ds18b20.c里的单总线操作抽象成「一线一设备」的接口这样同一条总线上挂多个 DS18B20 时只需要改 ROM 匹配部分不用动上层逻辑。具体做法是加一个DS18B20_ReadByROM(uint8_t *rom)函数把跳过 ROM 的 0xCC 换成匹配 ROM 的 0x55 加 8 字节地址上层用设备地址区分不同传感器。界面层可以改成一个小型的组件式结构。现在GUI_UpdateTemp是写死的改成注册机制每个传感器注册一个显示区域和格式化函数主循环遍历刷新。这样加湿度传感器、加气压传感器都不用改主循环。下面是一个简化的注册结构typedef struct { float value; float last_value; uint16_t x, y, w, h; void (*read)(float *out); // 采集函数 void (*draw)(uint16_t x, uint16_t y, float v); // 绘制函数 } SensorWidget; SensorWidget widgets[4]; uint8_t widget_count 0; void Widget_Register(void (*read)(float*), void (*draw)(uint16_t,uint16_t,float), uint16_t x, uint16_t y, uint16_t w, uint16_t h) { widgets[widget_count].read read; widgets[widget_count].draw draw; widgets[widget_count].x x; /* ... 其余字段赋值 ... */ widget_count; }主循环里遍历widgets对每个组件调read再调draw新增传感器只是多注册一个。这个改法不复杂但能让工程从「一个 Demo」变成「一个小框架」。验证改造是否成功我习惯用两个手段一是拔掉某个传感器看其他传感器是否照常刷新确认解耦到位二是用调试器 watch 窗口盯住widgets数组看数值更新是否符合预期。别小看这两步很多解耦改到最后发现全局变量还在偷偷共享。还有一个实用技巧是给采集加一个软件定时器链表每个传感器可以配不同的采集周期DS18B20 慢一点其他快的传感器快一点互不拖累。这个链表用最简单的单向链表就行不需要上 RTOS。从那以后我每次拿到这类「传感器加屏幕」的工程都强制先把采集和显示解耦一遍再动手加功能不然改到第三个传感器时主循环就会变成一团乱麻。希望这份拆解能帮你少走点弯路源码跑起来后大胆改改坏了再回来对时序基本都能救回来。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑