☰
不用开发板学STM32:Proteus仿真DS18B20与OLED温度采集
2026/10/9 1:43:05 网站建设 项目流程

1. 为什么我劝你先别急着买开发板

很多人一提到学STM32,第一反应就是先下单一块开发板,然后对着教程点灯、串口打印、跑马灯三件套走一遍。但说实话,我见过太多人开发板买回来吃灰的——焊好的板子往抽屉一扔,教程看了三集就再也没打开过。问题出在哪?不是STM32难,而是学习路径太重了。你得先装Keil、装芯片包、配下载器驱动、搞懂BOOT引脚、处理各种下载失败的问题,光环境搭建就能劝退一半人。

所以当我第一次看到“不用开发板学习STM32”这个思路时,我是很认同的。核心逻辑很简单:用Proteus仿真替代实物硬件,把DS18B20温度采集和OLED显示这两个经典外设跑通,你就能理解STM32的GPIO操作、单总线时序、I2C通信协议这几个核心知识点。而且仿真环境有个天然优势——你可以随时暂停、单步调试、用虚拟示波器看波形,这在实物上反而不好操作。

这篇文章要聊的就是这么一套完整的仿真方案:在Proteus里搭一个STM32最小系统,挂上DS18B20温度传感器和OLED显示屏,用Keil写代码,编译出hex文件加载到仿真里跑。你不需要买任何硬件,一台电脑就够。适合谁看?刚学完C语言、想入门STM32但不想先花钱买板子的学生;或者手头有开发板但想验证某个外设逻辑的工程师;再或者带课的老师,想给学生演示时序但实验室设备不够用。

关键词先摆出来:STM32、DS18B20、OLED、温度采集、Proteus仿真、单总线时序、I2C通信。这些词后面会反复出现,每一个我都会拆开讲清楚。

2. 整体方案设计与选型思路

2.1 为什么选DS18B20而不是LM75或热敏电阻

温度采集的方案太多了,热敏电阻加ADC、LM75走I2C、DHT11单总线、DS18B20单总线,各有各的适用场景。我选DS18B20来配合这个仿真项目,理由有三条。

第一,单总线协议是STM32学习里绕不开的坎。DS18B20只用一根数据线就能完成供电和通信(寄生电源模式),这对理解GPIO的输入输出切换、微秒级延时、时序窗口这些概念特别有帮助。你调通了DS18B20,再去搞DHT11或者单总线EEPROM,基本就是换个命令字的事。

第二,测量范围够用且精度合理。DS18B20的测温范围是-55°C到+125°C,在-10°C到+85°C区间内精度是±0.5°C,分辨率可以通过配置寄存器设成9到12位。对于仿真演示来说,这个精度完全够看,而且它的温度值格式是16位带符号数,处理起来能顺便练一下数据类型的转换。

第三,Proteus里有现成的仿真模型。这一点很关键。LM75在Proteus里也有模型,但DS18B20的模型更成熟,你可以直接在元件库里搜到,还能双击修改它模拟输出的温度值。这意味着你不需要真的拿个打火机去烤传感器,在仿真里改个数字就能看到OLED上的温度变化。

2.2 OLED选I2C接口还是SPI接口

OLED显示屏常见的有两种驱动方式:I2C和SPI。I2C只要两根线(SCL、SDA),SPI要四根线(SCK、MOSI、CS、DC)甚至更多。在Proteus仿真里,我建议用I2C接口的0.96寸OLED,原因很实际。

仿真环境下,接线越少越不容易出错。SPI的OLED在Proteus里需要接CS片选、DC数据命令切换、RES复位,线一多就容易接错,而且SPI的时序在仿真里调试起来比I2C麻烦。I2C虽然速率慢一点,但0.96寸128x64的屏幕刷新一帧也就几十毫秒,温度显示这种应用完全感觉不到延迟。

另外,I2C的协议本身也是STM32学习的重要一环。你可以用硬件I2C外设,也可以用GPIO模拟I2C。我建议先用GPIO模拟I2C,因为这样你能完全掌控时序,遇到问题好排查。等模拟的跑通了,再换成硬件I2C对比一下,理解会更深刻。

2.3 软件工具链的选择

Keil MDK是STM32开发的老牌工具,虽然界面老旧,但资料多、问题好搜。Proteus 8.9以上版本对STM32F103的支持比较稳定,我实测用Proteus 8.13跑STM32F103C6没问题。芯片包需要单独安装,Keil里装STM32F1系列的Device Family Pack,Proteus里确认有STM32F103的仿真模型。

这里有个坑要提前说:Proteus仿真STM32的时钟频率和实际芯片有差异。仿真里CPU的指令执行时间并不是精确的1/72MHz,所以那些依赖精确延时的代码(比如DS18B20的微秒级延时)在仿真里可能会偏慢或偏快。解决办法后面会讲,核心思路是用循环延时配合示波器校准,而不是死磕理论计算值。

3. 核心细节解析与实操要点

3.1 DS18B20的单总线时序到底怎么理解

DS18B20的通信协议说复杂也不复杂,但第一次接触的人容易被各种时间参数搞晕。我用一个生活化的类比来解释:单总线就像两个人用摩尔斯电码聊天,只有一根线,你说的时候我不能说,我说的时候你不能说,而且每个“滴”和“答”的持续时间都有严格规定。

具体到DS18B20,所有通信都由主机(STM32)发起。主机拉低总线一段时间再释放,这个低电平的持续时间决定了是写0还是写1,或者是一个复位脉冲。DS18B20在特定时间窗口内采样总线电平,从而判断主机发的是什么。

关键时序参数我列个表,这些数字必须记住,写代码的时候要对照着调:

操作类型时序名称时间要求说明
复位复位低电平480-960μs主机拉低至少480μs
复位存在脉冲60-240μsDS18B20拉低回应
写0低电平时间60-120μs整个时隙至少60μs
写1低电平时间1-15μs然后释放总线
读采样窗口15μs内主机拉低后15μs内采样
读时隙总长至少60μs两次读之间间隔至少1μs

在STM32上实现这些延时,通常用循环空转或者DWT周期计数器。循环空转的好处是不依赖额外外设,坏处是不同编译器优化等级下延时不一样。我的做法是写一个delay_us()函数,然后在Proteus里用虚拟示波器看实际波形,微调循环次数直到时序满足要求。

注意:Proteus仿真STM32时,如果用的是正点原子那种delay_us()函数(基于SysTick),在仿真里可能会因为时钟配置问题导致延时不准。建议在仿真阶段先用简单的for循环延时,等逻辑跑通了再换成精确延时。

3.2 OLED的I2C驱动要点

OLED的I2C驱动分两层:底层是I2C的字节读写,上层是SSD1306控制器的命令和数据写入。SSD1306的I2C地址通常是0x78(写地址),有些模块是0x7A,这个要看具体模块的手册。在Proteus里,你可以双击OLED元件查看它的I2C地址设置。

初始化SSD1306有一长串命令要发,包括设置对比度、显示模式、扫描方向、时钟分频等等。这些命令不需要你从头记,网上有现成的初始化序列,直接抄就行。但你要理解几个关键命令的作用:

  • 0xAE/0xAF:关闭/开启显示。调试的时候可以先关显示,等数据写完了再开,避免看到花屏。
  • 0x20:设置内存寻址模式。通常用0x00(水平寻址)或0x02(页寻址)。页寻址适合局部刷新,水平寻址适合整屏刷新。
  • 0xB0-0xB7:设置页地址(0-7页,每页8行像素)。
  • 0x00-0x0F和0x10-0x1F:设置列地址的低4位和高4位。

显示字符的原理就是取模。你要显示一个字符,得先有这个字符的点阵数据。常用的取模软件是PCtoLCD2002,设置成“阴码、逐列式、顺向”,生成的字模数组直接放到代码里。0.96寸OLED是128x64像素,显示16x16的汉字一行能放8个,显示8x16的ASCII字符一行能放16个。

3.3 GPIO模拟I2C的代码结构

用GPIO模拟I2C,核心就是控制SCL和SDA两根线的电平变化。SDA和SCL都要配置成开漏输出(GPIO_Mode_Out_OD),这样总线才能被外部上拉电阻拉高。Proteus里的OLED模型内部有上拉,所以你在STM32端配置成开漏输出就行。

模拟I2C的基本函数有这几个:

// SDA方向切换:输出模式用于发送,输入模式用于接收 void I2C_SDA_Out(void); void I2C_SDA_In(void); // 起始信号:SCL高时SDA从高变低 void I2C_Start(void); // 停止信号:SCL高时SDA从低变高 void I2C_Stop(void); // 发送一个字节,返回从机应答 uint8_t I2C_SendByte(uint8_t data); // 接收一个字节,发送应答或非应答 uint8_t I2C_RecvByte(uint8_t ack);

每个函数的实现都要配合延时。I2C标准模式是100kHz,快速模式是400kHz。在仿真里,我建议先用低速跑通,延时给大一点,比如每个电平变化后延时5-10μs。等逻辑没问题了,再逐步减小延时提高速率。

实操心得:Proteus仿真I2C时,如果发现OLED不亮或者显示乱码,先检查I2C地址对不对,再检查起始信号和停止信号的时序。用Proteus的I2C调试器可以抓包看数据,这个功能比实物调试还方便。

4. 实操过程与核心环节实现

4.1 Proteus工程搭建步骤

打开Proteus,新建工程,按下面的清单把元件找齐:

  • STM32F103C6(或C8,R6/R8都行,仿真里差别不大)
  • DS18B20(在“Temperature Sensors”分类里)
  • OLED 128x64 I2C(在“Displays”分类里,选SSD1306驱动的)
  • RES(电阻,用于上拉,虽然OLED模型内部有,但加上更保险)
  • CRYSTAL(晶振,8MHz)
  • CAP(电容,22pF两个,用于晶振)
  • BUTTON(可选,用于复位)

接线方案:

  • STM32的PA0接DS18B20的DQ引脚,同时接一个4.7kΩ上拉电阻到3.3V
  • STM32的PB6接OLED的SCL,PB7接OLED的SDA
  • 晶振接在PD0和PD1(OSC_IN和OSC_OUT)上,配22pF电容到地
  • BOOT0接10kΩ下拉到地,BOOT1也下拉

这里有个细节:Proteus里STM32的电源引脚默认是隐藏的,不需要手动接。但你要在STM32的属性里确认“Use ROM/RAM”设置正确,否则程序可能跑不起来。

4.2 Keil工程配置与代码框架

Keil里新建工程,选STM32F103C6,然后添加启动文件(startup_stm32f10x_md.s)和标准外设库文件。如果你用HAL库也行,但标准库在仿真里更轻量,编译出来的hex更小,加载更快。

代码框架分四个模块:

  1. 延时模块:delay.c,提供delay_us()和delay_ms()
  2. DS18B20驱动:ds18b20.c,提供DS18B20_Init()、DS18B20_ReadTemp()
  3. OLED驱动:oled.c,提供OLED_Init()、OLED_ShowString()、OLED_ShowNum()
  4. 主函数:main.c,初始化外设,循环读取温度并显示

主循环的逻辑很简单:

int main(void) { float temp; Delay_Init(); OLED_Init(); OLED_ShowString(0, 0, "Temp:", 16); while(1) { temp = DS18B20_ReadTemp(); OLED_ShowString(0, 2, " ", 16); // 清除旧数据 OLED_ShowFloat(0, 2, temp, 16); delay_ms(500); } }

4.3 DS18B20温度读取的完整流程

DS18B20读一次温度要经过三个步骤:复位、跳过ROM、启动转换、等待转换、复位、跳过ROM、读暂存器。每一步都有对应的命令字:

  • 0xCC:跳过ROM(只有一个传感器时用)
  • 0x44:启动温度转换
  • 0xBE:读取暂存器(前两个字节就是温度值)

温度值的处理要注意:DS18B20返回的是16位数据,低4位是小数部分,高12位是整数部分(12位分辨率时)。如果温度是负的,高5位是符号位,全是1。处理代码大概长这样:

float DS18B20_ReadTemp(void) { uint8_t tempL, tempH; int16_t raw; float result; DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0x44); delay_ms(750); // 等待转换完成 DS18B20_Reset(); DS18B20_WriteByte(0xCC); DS18B20_WriteByte(0xBE); tempL = DS18B20_ReadByte(); tempH = DS18B20_ReadByte(); raw = (tempH << 8) | tempL; if(raw & 0x8000) // 负温度 { raw = ~raw + 1; result = -(raw * 0.0625); } else { result = raw * 0.0625; } return result; }

0.0625是12位分辨率下的最小刻度,因为12位分辨率把1°C分成了16份,1/16=0.0625。

4.4 仿真调试与波形验证

代码编译出hex后,在Proteus里双击STM32,把Program File指向hex文件,Clock Frequency设成8MHz(和晶振一致)。点运行,如果OLED亮了并且显示温度,基本就成功了。

如果没显示,按这个顺序排查:

  1. 看电源:Proteus里STM32的VDD和VSS虽然隐藏,但要在属性里确认电压是3.3V。
  2. 看复位:NRST引脚有没有被意外拉低。
  3. 看时钟:用Proteus的虚拟示波器接在OSC_OUT上,看有没有8MHz波形。
  4. 看I2C:用I2C调试器抓SCL和SDA,看有没有起始信号和地址帧。
  5. 看单总线:用示波器接PA0,看复位脉冲和存在脉冲是否正常。

我踩过的一个坑是:Proteus里DS18B20的默认温度值是25°C,但如果你在仿真过程中修改了它的温度属性,需要先停止仿真再改,运行中改可能不生效。另外,DS18B20的转换时间在仿真里可能比实际芯片快,delay_ms(750)可以适当缩短,但建议还是保留,养成好习惯。

5. 常见问题与排查技巧实录

5.1 仿真跑不起来或程序不执行

这是最常见的问题,表现是Proteus里STM32的引脚电平没变化,OLED全黑。排查思路:

  • 确认hex文件路径没有中文和空格。Proteus对中文路径支持不好,把工程放在纯英文路径下。
  • 确认Keil里选的芯片型号和Proteus里的一致。都是STM32F103C6或C8,不能一个C6一个C8。
  • 确认启动文件选对了。STM32F103C6是中等容量,启动文件用startup_stm32f10x_md.s,用hd的会跑飞。
  • 确认BOOT0和BOOT1都接地。仿真里虽然不涉及启动模式,但Proteus的模型可能会检查这两个引脚。

5.2 OLED不亮或显示乱码

OLED的问题分几种情况:

现象可能原因解决办法
全黑I2C地址错改成0x78或0x7A试试
全黑初始化命令没发完检查初始化序列,加延时
花屏显存数据错位检查页地址和列地址设置
部分显示取模方式不对改成逐列式、顺向
闪烁刷新太频繁降低刷新率,加延时

我遇到过一次OLED显示一半正常一半乱码,查了半天发现是列地址设置时高4位和低4位搞反了。SSD1306的列地址是分两次发的,低4位先发,高4位后发,顺序错了就会导致显示偏移。

5.3 DS18B20读出来一直是85°C

85°C是DS18B20的上电默认值,说明温度转换根本没执行。原因通常是:

  • 启动转换命令(0x44)没发出去
  • 复位脉冲太短,DS18B20没响应
  • 延时不够,转换还没完成就去读

解决办法:用示波器看PA0的波形,确认复位脉冲宽度在480μs以上,存在脉冲在60-240μs之间。如果波形不对,调整delay_us()的参数。

5.4 仿真里延时函数不准怎么办

这是Proteus仿真的固有问题。我的经验是:不要依赖理论计算,用示波器实测。比如你要一个500μs的延时,写个循环延时,然后在示波器上看实际宽度,再按比例调整循环次数。虽然麻烦一点,但调一次后面就准了。

另外,如果你用的是正点原子的delay_us()(基于SysTick),在Proteus里需要确认SysTick的时钟源配置正确。有些仿真模型对SysTick的支持不完整,会导致延时偏大或偏小。这种情况下,换成简单的for循环延时反而更可靠。

5.5 常见问题速查表

问题排查方向快速验证
程序不跑hex路径、芯片型号、启动文件换英文路径,核对型号
OLED黑屏I2C地址、初始化、接线用I2C调试器抓包
温度85°C转换命令、复位脉冲、延时示波器看PA0波形
温度值跳变电源干扰、上拉电阻加4.7k上拉,缩短杜邦线
显示闪烁刷新频率、清屏方式局部刷新代替整屏刷新
编译报错头文件路径、库版本检查Include Paths

独家避坑:Proteus仿真STM32时,不要开太多虚拟仪器。我同时开过示波器、I2C调试器和串口终端,结果仿真速度慢到几乎卡死。需要看哪个就开哪个,看完就关。

6. 从仿真到实物的迁移建议

仿真跑通之后,如果你想转到实物上,有几个地方需要调整。首先是延时函数,实物上可以用SysTick或者DWT做精确微秒延时,比仿真里的循环延时靠谱得多。其次是上拉电阻,实物上DS18B20的DQ引脚必须接4.7kΩ上拉,OLED的SDA和SCL也要接4.7kΩ上拉,很多模块自带上拉,但最好确认一下。最后是电源,实物上STM32和OLED都要3.3V供电,DS18B20可以3.3V也可以5V,但为了电平匹配,统一用3.3V最省事。

还有一点,实物上DS18B20的转换时间是真的750ms,不能缩短。仿真里你可以偷懒,但实物上必须等够时间,否则读出来还是85°C。

这个仿真工程我前后调了大概三个晚上,大部分时间花在I2C的时序和DS18B20的延时上。现在回头看,最难的不是写代码,而是理解时序图上的每一个时间参数对应到代码里是哪一行。一旦你把示波器上的波形和代码里的延时对应起来,后面再调其他单总线器件就是复制粘贴的事了。代码和Proteus工程文件我整理好了,需要的可以拿去直接跑,但建议你先自己照着搭一遍,踩过的坑才是自己的。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询