ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Proteus仿真STM32驱动DS18B20温度采集与OLED显示实战

2026/9/13 10:19:26 拓冰建站 浏览量
Proteus仿真STM32驱动DS18B20温度采集与OLED显示实战 简介一套基于STM32单片机的DS18B20温度采集与Proteus仿真资源面向嵌入式初学者和单片机爱好者完整演示了从传感器数据读取、OLED屏实时显示到超阈值蜂鸣器报警的开发流程。压缩包共127个文件大小约2.97MB主要包含HAL库风格的C源码与头文件、可直接烧录的hex文件、Proteus仿真工程文件及相关说明文档便于在无实物硬件的条件下完成功能验证与调试。项目通过一线总线协议读取DS18B20温度结合OLED显示驱动和阈值比较逻辑覆盖了STM32的GPIO、定时器、中断等核心外设应用并给出蜂鸣器报警的完整控制思路。目前已有344人学习使用适合作为课程设计参考或入门练手模板能够帮助读者跳脱纯理论直观理解嵌入式软件的分层架构、协议时序和传感器调试方法。1. 为什么用 Proteus 仿真跑 STM32DS18B20OLED 这条链路做温度采集很多人第一反应是直接买一块 STM32 开发板和 DS18B20 传感器回来接线但拿到板子之前Proteus 仿真就能把 STM32、DS18B20、OLED 整条链路跑通。这个标题本质上是把一个完整的嵌入式计算模型拆成了三个可独立验证的部分单片机负责单总线时序传感器负责把温度变成数字量OLED 负责把结果可视化。对做课设、毕设或者项目预研的人来说Proteus 仿真的价值不在于替代实物验证而在于把协议调试和硬件排错分开时序写错了看波形IIC 地址错了看屏幕都不用动烙铁。这套方案适合两类人一类是还没买到硬件、想先把代码逻辑跑通再下单的另一类是已经调试过实物想回头把时序边界和上拉电阻这些隐性条件理清楚的。下面从最关键的时序开始讲。2. DS18B20 单总线时序与 STM32 精确延时实现DS18B20 难读不是因为寄存器复杂而是单总线协议对时间窗口有硬性要求。Proteus 仿真里这个要求不会放宽反而因为仿真步长的原因对延时不精确的代码更敏感。先把协议参数列出来再写代码才有依据。2.1 单总线协议的关键时间窗口DS18B20 是单总线从机数据线既是电源入口也是通信线外部必须接 4.7k 上拉电阻。所有操作都由主机发起一次完整的温度读取至少包含两次复位脉冲和两组指令序列。时序段参数范围说明复位低电平480us ~ 960us主机拉低总线表示通信开始存在脉冲60us ~ 240us从机检测到复位后拉低应答主机在释放总线后 60us 内采样写 0 时隙60us ~ 120us整个时隙保持低电平写 1 时隙低电平 1us ~ 15us拉低后尽快释放靠上拉电阻恢复高电平读时隙主机拉低 1us释放后 15us 内采样整个时隙完整执行 60us位间间隔最短 1us连续两个时隙之间需要恢复时间这些参数有一个容易被忽略的细节主机拉低总线之后DS18B20 是在释放后的 15us 到 60us 之间把数据放到总线上的所以读时隙的采样点必须落在 15us 附近太早读到高电平太晚读到从机释放总线后的高电平两种情况都会导致数据错乱。Proteus 里读到全 0xFF大部分问题不是代码跑飞而是采样窗口没踩对。2.2 用 DWT 做微秒级延时Proteus 下时序不发散的基础STM32 标准库的Delay函数很多是基于 SysTick 的毫秒延时但单总线需要微秒级精度。我一般不用 for 循环空转因为编译优化等级一变延时时间就变了。Cortex-M3 内核内置了一个 DWT 周期计数器可以在不打断 SysTick 的前提下实现稳定的微秒延时。#include stm32f1xx_hal.h // 初始化 DWT 延时功能必须在 SystemClock_Config 之后调用 void DWT_Delay_Init(void) { CoreDebug-DEMCR | CoreDebug_DEMCR_TRCENA_Msk; // 使能 DWT 跟踪单元 DWT-CYCCNT 0; // 周期计数器清零 DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; // 启动周期计数 } // 微秒级延时us 取值范围建议 1 ~ 100000 void DWT_Delay_us(uint32_t us) { uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000U); while ((DWT-CYCCNT - start) ticks); }这段代码的原理是读取内核时钟周期数STM32F103 在 Proteus 中按 72MHz 主频运行时一个时钟周期是 1/72us所以延时 us 数乘以 72 就是需要等待的周期数。SystemCoreClock是 HAL 库在启动时从 RCC 配置里读到的值不需要自己写死换不同主频的 STM32 型号也通用。注意DWT_Delay_Init必须在时钟初始化完成之后调用否则计数器频率不对整个时序都会偏。用这个延时函数替代 HAL_DelayDS18B20 的复位脉冲宽度才能精确落在 480us 到 960us 之间。2.3 单总线读写时序的 GPIO 模拟代码DS18B20 的数据引脚需要双向输入输出STM32 的 GPIO 配置成开漏输出模式最合适。开漏模式下单片机只能主动拉低总线释放后由外部上拉电阻把总线拉高这就模拟了单总线的线与逻辑。#define DS18B20_GPIO_PORT GPIOB #define DS18B20_GPIO_PIN GPIO_PIN_1 // 将引脚切换为开漏输出模式 static void DS18B20_SetOutput(void) { GPIO_InitTypeDef gpio {0}; gpio.Pin DS18B20_GPIO_PIN; gpio.Mode GPIO_MODE_OUTPUT_OD; // 开漏输出必须外部上拉 gpio.Speed GPIO_SPEED_FREQ_HIGH; HAL_GPIO_Init(DS18B20_GPIO_PORT, gpio); } // 将引脚切换为输入模式 static void DS18B20_SetInput(void) { GPIO_InitTypeDef gpio {0}; gpio.Pin DS18B20_GPIO_PIN; gpio.Mode GPIO_MODE_INPUT; gpio.Pull GPIO_NOPULL; // 上拉由外部电阻负责 HAL_GPIO_Init(DS18B20_GPIO_PORT, gpio); } // 复位脉冲返回 1 表示检测到 DS18B20 存在脉冲 uint8_t DS18B20_Reset(void) { uint8_t presence 0; DS18B20_SetOutput(); HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_RESET); DWT_Delay_us(480); // 拉低 480us 以上产生复位脉冲 HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET); DWT_Delay_us(70); // 释放总线等待从机应答 DS18B20_SetInput(); if (HAL_GPIO_ReadPin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN) GPIO_PIN_RESET) { presence 1; // 从机拉低总线存在脉冲有效 } DWT_Delay_us(410); // 补齐剩余复位窗口总时长约 960us return presence; }这里有三个参数需要调复位低电平时间、释放后等待时间、最终补齐时间。低电平 480us 是最低要求给到 500us 更稳释放后等 70us 是为了避开上拉电阻的上升沿同时保证落在从机应答窗口内最后再延时 410us让整个复位过程接近 960us给从机内部状态机留出恢复时间。Proteus 仿真里如果复位总时间太短后续读到的温度会固定在 85 度这是 DS18B20 上电复位后的默认值说明复位没有真正完成。2.4 写时隙和读时隙的代码实现写一个 bit 的关键是区分写 0 和写 1 的保持时间。写 0 要求整个时隙都保持低电平写 1 则是在拉低一小段时间后立刻释放总线。// 向 DS18B20 写入一个字节低位在前 void DS18B20_WriteByte(uint8_t data) { DS18B20_SetOutput(); for (uint8_t i 0; i 8; i) { if (data 0x01) { // 写 1拉低 6us然后释放总线 HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_RESET); DWT_Delay_us(6); HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET); DWT_Delay_us(60); // 补齐整个写时隙 } else { // 写 0整个时隙保持低电平 HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_RESET); DWT_Delay_us(60); HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET); DWT_Delay_us(6); // 位间恢复时间 } data 1; } } // 从 DS18B20 读取一个字节高位在前返回 uint8_t DS18B20_ReadByte(void) { uint8_t data 0x00; for (uint8_t i 0; i 8; i) { data 1; DS18B20_SetOutput(); HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_RESET); DWT_Delay_us(2); // 拉低总线启动读时隙 HAL_GPIO_WritePin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN, GPIO_PIN_SET); DWT_Delay_us(8); // 从机开始驱动总线等待数据稳定 DS18B20_SetInput(); if (HAL_GPIO_ReadPin(DS18B20_GPIO_PORT, DS18B20_GPIO_PIN) GPIO_PIN_SET) { data | 0x80; // 读到高电平当前 bit 为 1 } DWT_Delay_us(50); // 补齐读时隙并留出位间间隔 } return data; }写时隙里写 1 的拉低时间取 6us比数据手册的最小值 1us 宽裕同时远小于 15us 的典型上限这样即使延时有一两个微秒的误差也不会掉出有效区间。读时隙的采样延时取 8us是从主机释放总线开始计算的此时从机已经把数据驱动到总线上读取得到的电平就是真实数据。每次读完一个 bit 后延时 50us是为了让总时长接近 60us避免连续读时下一个时隙被提前触发。3. STM32 温度采集主程序与 OLED 显示驱动接入时序代码就位后整个工程就分成两条线一条是 STM32 顺序执行 DS18B20 的 ROM 指令和温度转换命令另一条是把读到的原始温度值换算成浮点数再通过 IIC 总线送到 OLED。这两条线在 Proteus 仿真里最容易出现的衔接问题是OLED 初始化占用的时间过长导致 DS18B20 的转换还没完成就执行了读取命令读到上一次的缓存值。3.1 工程结构与初始化顺序一个清晰的 STM32 工程至少需要四个文件主函数、DS18B20 驱动、OLED 驱动、延时函数。初始化顺序也有讲究。初始化步骤调用的函数说明时钟树配置SystemClock_Config设置 72MHz 主频精确延时DWT_Delay_Init必须在时钟配置之后调试串口MX_UART1_Init可选用于输出调试信息OLEDOLED_InitSSD1306 上电初始化序列DS18B20无显式初始化函数首次 Reset 即可完成初始化DS18B20 不需要单独的初始化函数第一次调用DS18B20_Reset()就是初始化。OLED 初始化则必须放在 DS18B20 读取之前因为 SSD1306 控制器上电后需要一系列配置命令才能点亮如果先读温度再初始化屏幕第一次显示会延迟一个刷新周期。Proteus 仿真中 OLED 模型初始化比真实屏幕快但代码执行顺序不能依赖这个差异。3.2 读取 DS18B20 温度的核心函数读取温度的标准流程是复位、发跳过 ROM 命令 0xCC、发启动转换命令 0x44、等待转换完成、再次复位、发跳过 ROM 命令、发读暂存器命令 0xBE、连续读两个字节。等待转换的时间取决于分辨率配置12 位分辨率下典型转换时间是 750ms这个时间不能省。// 读取 DS18B20 温度值返回摄氏度浮点数 float DS18B20_GetTemperature(void) { uint8_t temp_lsb 0; uint8_t temp_msb 0; int16_t raw_temp 0; float temperature 0.0f; DS18B20_Reset(); // 复位总线 DS18B20_WriteByte(0xCC); // 跳过 ROM适用于单设备 DS18B20_WriteByte(0x44); // 启动温度转换 HAL_Delay(750); // 等待转换结束12 位需要 750ms DS18B20_Reset(); // 再次复位开始读数据 DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); // 读暂存器命令 temp_lsb DS18B20_ReadByte(); // 暂存器第 0 字节温度低字节 temp_msb DS18B20_ReadByte(); // 暂存器第 1 字节温度高字节 raw_temp (int16_t)((temp_msb 8) | temp_lsb); temperature raw_temp * 0.0625f; // 12 位分辨率下 LSB 为 0.0625 度 return temperature; }这个函数里的关键参数是0.0625f。DS18B20 出厂默认 12 位分辨率温度寄存器一共 16 位其中低 4 位是小数部分整数部分占 12 位所以每个 LSB 代表 0.0625 摄氏度。如果型号被配置成 9 位分辨率换算系数要改成 0.5f但这需要在配置寄存器里写入新值函数里没有做这一步默认按 12 位处理。负温度的场景下temp_msb的最高位是符号位把两个字节拼成int16_t再乘系数C 语言的符号扩展会自动处理负数不需要手动取反。3.3 SSD1306 的 IIC 驱动与 OLED 显示代码OLED 模块在 Proteus 里的模型大多基于 SSD1306 控制器通信接口支持 IIC 和 SPI。这个工程用的是 IICSTM32 的硬件 IIC 在 Proteus 仿真中能正常工作但我更推荐用软件模拟 IIC 或者 STM32 HAL 库的HAL_I2C_Master_Transmit。SSD1306 的 IIC 从机地址是 0x3C左移一位后变成 0x78 作为写入地址。// 向 SSD1306 发送命令字节 static void OLED_WriteCmd(uint8_t cmd) { uint8_t buf[2] {0x00, cmd}; // 控制字节 0x00 表示后续是命令 HAL_I2C_Master_Transmit(hi2c1, 0x78, buf, 2, 100); } // 向 SSD1306 发送数据字节 static void OLED_WriteData(uint8_t dat) { uint8_t buf[2] {0x40, dat}; // 控制字节 0x40 表示后续是数据 HAL_I2C_Master_Transmit(hi2c1, 0x78, buf, 2, 100); } // OLED 初始化基础命令序列 void OLED_Init(void) { HAL_Delay(100); // 等待屏幕内部复位完成 OLED_WriteCmd(0xAE); // 关闭显示 OLED_WriteCmd(0x20); // 设置内存寻址模式 OLED_WriteCmd(0x02); // 页寻址模式 OLED_WriteCmd(0xC8); // 扫描方向从右上到左下 OLED_WriteCmd(0x40); // 显示起始行 0 OLED_WriteCmd(0x81); // 设置对比度 OLED_WriteCmd(0xCF); // 对比度值 207 OLED_WriteCmd(0xA1); // 段重映射列地址 127 映射到 SEG0 OLED_WriteCmd(0xA6); // 正常显示不反色 OLED_WriteCmd(0xA8); // 设置多路复用比 OLED_WriteCmd(0x3F); // 1/64 占空比 OLED_WriteCmd(0xD3); // 显示偏移 OLED_WriteCmd(0x00); // 偏移量 0 OLED_WriteCmd(0xD5); // 设置时钟分频 OLED_WriteCmd(0x80); // 分频因子 1频率 8 OLED_WriteCmd(0xD9); // 预充电周期 OLED_WriteCmd(0xF1); OLED_WriteCmd(0xDA); // COM 引脚配置 OLED_WriteCmd(0x12); OLED_WriteCmd(0xDB); // VCOMH 电压 OLED_WriteCmd(0x40); OLED_WriteCmd(0x8D); // 电荷泵设置 OLED_WriteCmd(0x14); // 开启电荷泵 OLED_WriteCmd(0xAF); // 开启显示 }IIC 地址 0x78 是 Proteus 模型和大多数 0.96 寸 OLED 模块共用的如果你的模块背面标注地址可选 0x3C 或 0x3D那就对应 0x78 和 0x7A。初始化命令里 0x8D 和 0x14 这两条最容易漏电荷泵不开屏幕就算初始化成功也不会亮。Proteus 仿真里如果 OLED 区域一直是黑色的优先检查这两条命令是否发送。3.4 温度值的浮点格式化与显示读取到的温度是 float 类型OLED 只能显示字符需要转成字符串。Keil MDK 默认的 C 库对浮点形如%f的格式化支持不完整需要在魔术棒里勾选 Use MicroLIB或者用整型拆分的方式手动格式化。// 将温度拆成整数和小数部分显示避免浮点格式化问题 void OLED_ShowTemperature(float temp) { int int_part 0; int frac_part 0; char buf[16]; int_part (int)temp; // 取整数部分 frac_part (int)((temp - int_part) * 10); // 取一位小数 if (frac_part 0) frac_part -frac_part; // 负数修正 sprintf(buf, T%d.%d C, int_part, frac_part); OLED_ShowString(0, 2, buf); }这里额外处理了负数小数部分如果温度是 -0.5 度整数部分是 0小数部分是 -5直接显示会变成T0.-5 C。修正后显示为T0.5 C或者T-12.3 C。OLED_ShowString内部需要根据 ASCII 码查表把字符的 8x16 点阵逐字节写入对应页地址这部分代码较长核心是维护一个包含 ASCII 可见字符的点阵字库再配合页寻址模式逐页填充。4. Proteus 仿真工程搭建元件选型与 HEX 加载Proteus 仿真跑不起来或者屏幕不亮很多时候不是代码问题而是原理图画错了。元件选错型号、网络标号不匹配、晶振设置和代码里的时钟树不一致都会让整个工程在仿真阶段卡住。先列一张实际可用的元件清单照着放就行。4.1 元件清单与关键参数元件Proteus 中的名称关键参数备注主控芯片STM32F103C8晶振 8MHz选择带 TQFP48 封装的型号温度传感器DS18B20单总线接口三个引脚DQ 需要上拉OLED 显示屏OLED SSD1306IIC 接口Proteus 8 及以上版本自带上拉电阻RES4.7kDS18B20 数据线必备晶振CRYSTAL8MHz两个负载电容 20pF退耦电容CAP100nF每个电源引脚旁边放一个复位按键BUTTON低电平复位接 NRST 引脚Proteus 中 STM32F103C8 的引脚号与真实芯片一致VDD 和 VDDA 都要接 3.3VVSS 和 VSSA 接地。DS18B20 的 DQ 引脚接到 STM32 的 PB1同时接一个 4.7k 上拉电阻到 3.3V这是仿真中保证时序正常的先决条件。OLED 的 SDA 和 SCL 分别接 PB7 和 PB6对应 STM32 的 I2C1 硬件引脚VCC 接 3.3VGND 接地。4.2 原理图画法与网络标号连接方式不建议直接用导线把每个引脚连起来Proteus 工程引脚多线拉得长反而容易看错。我一般给每个功能块分配一个网络标号再让同名的网络自动连接。网络标号分配 3.3V - STM32 VDD, OLED VCC, DS18B20 VDD GND - STM32 VSS, OLED GND, DS18B20 GND DQ - STM32 PB1, DS18B20 DQ, 上拉电阻上端 SDA - STM32 PB7, OLED SDA SCL - STM32 PB6, OLED SCL RST_N - STM32 NRST, 复位按键一端使用网络标号后Proteus 会自动忽略元件的绝对位置只要标号相同就视为连接。这里有一个细节STM32 的 PB1 是默认的 DS18B20 数据线如果你在代码里用了GPIOB_PIN_1原理图就必须用标号DQ连接到 PB1名字拼错一个字母仿真能运行但温度永远读不到。OLED 的 SDA 和 SCL 在 Proteus 模型里已经内置上拉不需要额外接电阻这一点与真实模块有差异真实 IIC 总线通常也要上拉但仿真模型自带不用画。4.3 Keil 生成 HEX 文件与 Proteus 加载方法代码写完不是直接仿真需要先用 Keil 编译出 HEX 文件再加载到 STM32 模型里。Keil 配置里默认不生成 HEX需要手动打开。// Keil MDK 生成 HEX 的开关: // 魔术棒 - Output - 勾选 Create HEX File // 编译后输出路径下会生成与工程同名的 .hex 文件 // 也可以在命令行检查编译产物 // D:\keil_project\Ds18b20Oled\Ds18b20Oled.hex在 Proteus 中双击 STM32F103C8 芯片弹出属性窗口在 Program File 一栏选择刚才生成的 HEX 文件CKS 文件通常不需要指定。然后设置时钟频率为 8MHz因为 STM32 内部 PLL 会把它倍频到 72MHz这对应代码里SystemClock_Config的配置。如果 Proteus 提示No clock connected说明晶振电路没画完整需要在 STM32 的 OSC_IN 和 OSC_OUT 引脚接上晶振和负载电容。HEX 加载成功后点击运行按钮如果 LED 没有反应优先确认 Program File 路径是否包含中文目录这个路径包含中文时 Proteus 偶尔会加载失败。5. 联调参数优化分辨率、上拉电阻与 Proteus 差异排查DS18B20 代码和仿真图都跑通之后会进入一个比想象中更耗时的阶段数据和实物对不上。比如仿真里温度跳变特别快或者 OLED 显示的数字在小数点后不停闪烁这些问题都和配置参数有关而不是逻辑错误。5.1 DS18B20 分辨率配置与转换时间匹配默认 12 位分辨率下一次温度转换需要 750ms联调时会发现温度响应很慢这不是故障是传感器的正常行为。但如果你只想快速验证 OLED 显示可以把分辨率降到 9 位转换时间缩短到 93.75ms。// 设置 DS18B20 分辨率res 取值 9、10、11、12 void DS18B20_SetResolution(uint8_t res) { uint8_t config 0x1F; // 9 位R00 R10 if (res 10) config 0x3F; // 10 位R01 R10 else if (res 11) config 0x5F; // 11 位R00 R11 else if (res 12) config 0x7F; // 12 位R01 R11 DS18B20_Reset(); DS18B20_WriteByte(0xCC); // 跳过 ROM DS18B20_WriteByte(0x4E); // 写暂存器命令 DS18B20_WriteByte(0xFF); // 报警上限 TH本工程不使用 DS18B20_WriteByte(0xFF); // 报警下限 TL DS18B20_WriteByte(config); // 配置寄存器写入分辨率 DS18B20_Reset(); // 结束写操作 }这段代码里配置寄存器的低两位决定分辨率写入之后需要用0x48命令把数据复制到 EEPROM否则掉电后设置丢失。Proteus 仿真中每次上电都是全新状态所以分辨率设置必须在每次读取前调用一次或者放在主程序初始化阶段。改完分辨率后主程序里的HAL_Delay(750)也要同步改小否则 9 位分辨率下等 750ms 会浪费大量时间。5.2 上拉电阻与总线负载的仿真差异真实电路中 DS18B20 的数据线上拉电阻通常选 4.7k这是手册推荐值。Proteus 仿真中上拉电阻的值会影响信号的上升沿时间但不是线性的电阻越大上升沿越缓复位脉冲后的存在脉冲采样点就越需要往后调。上拉电阻值上升沿特性适用场景1k上升快功耗略高总线走线较长时4.7k标准值时序稳定最常用推荐10k上升慢省电短距离面包板但读时序易出错实际调参中如果读到的温度偶尔跳变把上拉电阻从 4.7k 换成 1k 往往能解决问题。但这不是支持你无限减小电阻的理由电阻太小会让 DS18B20 的低电平无法被正确识别因为总线上电压被拉低后仍可能高于逻辑低电平阈值。Proteus 仿真里这个现象同样存在我一般保持 4.7k只在时序采样点确实读不到时才调整。5.3 常见故障现象与排查方向联调阶段的故障通常可以归因到三类延时不对、地址不对、配置不对。下面的表是从实际调试中归纳的排查顺序按概率排列。故障现象可能原因排查步骤OLED 完全不亮电荷泵未开启检查 0x8D 和 0x14 命令是否执行OLED 花屏IIC 地址错误确认是 0x78 还是 0x7A温度固定 85 度复位未完成增大复位低电平时间到 600us温度跳变无规律读时序采样点过早把读采样延时从 8us 调整到 10us仿真速度极慢HAL_Delay 过长降低分辨率到 9 位缩短等待时间程序跑不起来HEX 路径中文工程路径改为纯英文目录温度固定 85 度这个问题值得单独强调。DS18B20 上电后暂存器里的默认值就是 85 度代表没有执行过有效转换。出现这个读数优先检查复位后是否等待了足够长的存在脉冲采样时间而不是检查 0x44 命令是否发送成功。Proteus 中可以用虚拟示波器直接观察 DQ 引脚的波形如果复位脉冲的高电平部分没有出现明显的从机应答低脉冲说明 DS18B20 模型没有正确识别复位信号。6. 用虚拟终端和示波器双通道验证 STM32 单总线时序工程跑通之后OLED 能显示温度了但对做过实物的人而言仿真和实物的行为仍然可能有差异。最后一个环节是验证时序是否真的正确而不是只看结果。Proteus 提供了虚拟终端和虚拟示波器可以无侵入地观察 DS18B20 的通信细节。把 STM32 的 USART1 重定向到 printf在读取温度之后把原始值和换算值都打印出来是最快的验证方式。// 重定向 printf 到 USART1注意要包含 stdio.h int fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 100); return ch; } // 在读取温度后输出调试信息 printf(raw%04X temp%.2f\r\n, raw_temp, temperature);Proteus 里从左侧工具栏选择 Virtual Terminal放到原理图上把 RXD 引脚接到 STM32 的 PA9TXD 引脚接到 PA10然后在虚拟终端属性里设置波特率为 1152008 位数据无校验1 位停止位。运行仿真后虚拟终端会实时打印每次读取的原始值。如果你发现 raw 值始终是 0x0550 之类的固定值而温度在 OLED 上正常变化那是 printf 缓存问题在printf前加setvbuf(stdout, NULL, _IONBF, 0)关闭缓冲即可。示波器验证时序的方法更直观把 DQ 网络标号接到 Virtual Oscilloscope 的 A 通道设置触发电平为 1.5V时基设为 100us/格。运行仿真后按一下复位示波器上会出现一组低脉冲序列。复位脉冲宽度应该在 480us 到 960us 之间读时隙每个周期 60us间隔清晰。如果看到读时隙的波形间距忽长忽短说明DWT_Delay_us没有被正确初始化此时 OLED 上大概率显示乱码或固定值。观察完波形之后把 DWT 延时函数里的SystemCoreClock打印出来确认是否为 72000000这一步能排除仿真时钟配置错误导致的时序偏移。本文还有配套的精品资源点击获取