
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、SDASPI要四根线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 PackProteus里确认有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或C8R6/R8都行仿真里差别不大DS18B20在“Temperature Sensors”分类里OLED 128x64 I2C在“Displays”分类里选SSD1306驱动的RES电阻用于上拉虽然OLED模型内部有但加上更保险CRYSTAL晶振8MHzCAP电容22pF两个用于晶振BUTTON可选用于复位接线方案STM32的PA0接DS18B20的DQ引脚同时接一个4.7kΩ上拉电阻到3.3VSTM32的PB6接OLED的SCLPB7接OLED的SDA晶振接在PD0和PD1OSC_IN和OSC_OUT上配22pF电容到地BOOT0接10kΩ下拉到地BOOT1也下拉这里有个细节Proteus里STM32的电源引脚默认是隐藏的不需要手动接。但你要在STM32的属性里确认“Use ROM/RAM”设置正确否则程序可能跑不起来。4.2 Keil工程配置与代码框架Keil里新建工程选STM32F103C6然后添加启动文件startup_stm32f10x_md.s和标准外设库文件。如果你用HAL库也行但标准库在仿真里更轻量编译出来的hex更小加载更快。代码框架分四个模块延时模块delay.c提供delay_us()和delay_ms()DS18B20驱动ds18b20.c提供DS18B20_Init()、DS18B20_ReadTemp()OLED驱动oled.c提供OLED_Init()、OLED_ShowString()、OLED_ShowNum()主函数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/160.0625。4.4 仿真调试与波形验证代码编译出hex后在Proteus里双击STM32把Program File指向hex文件Clock Frequency设成8MHz和晶振一致。点运行如果OLED亮了并且显示温度基本就成功了。如果没显示按这个顺序排查看电源Proteus里STM32的VDD和VSS虽然隐藏但要在属性里确认电压是3.3V。看复位NRST引脚有没有被意外拉低。看时钟用Proteus的虚拟示波器接在OSC_OUT上看有没有8MHz波形。看I2C用I2C调试器抓SCL和SDA看有没有起始信号和地址帧。看单总线用示波器接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°C85°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工程文件我整理好了需要的可以拿去直接跑但建议你先自己照着搭一遍踩过的坑才是自己的。