
简介面向电子类毕设学生、嵌入式入门者及Proteus仿真爱好者这份Proteus万年历仿真实验工程以STM32为主控实现万年历、温度显示、闹钟设置等常用功能覆盖从STM32外设初始化到Proteus联合仿真的完整开发链路能有效解决实际调试验证难、源码与仿真不配套的问题。压缩包内共291个文件大小约8.97MB以C/H源码、uvprojx工程文件、pdsprj仿真工程、hex可烧录文件为主同时包含o/axf/map等编译中间文件可用于回溯构建过程其中stm32f10x_tim.c、stm32f10x_adc.c等驱动代码对应定时、采集等关键模块便于按需精读。目前已有489人学习配套B站讲解视频也被多次引用学习时可先看视频再对照代码和仿真图形成“讲解源码仿真”的闭环。拿到后可获得可直接运行的Proteus仿真工程与完整STM32工程源码既适合课程设计/毕业设计参考也可作为学习STM32定时器、RTC、按键扫描、数码管显示等知识点的综合案例在此基础上扩展语音播报、WiFi同步等进阶功能。1. 为什么选择用 Proteus 跑 STM32 万年历项目价值与学习地图很多人一看到“Proteus 仿真 STM32”这个组合第一反应是“能行吗”——毕竟 Proteus 传统上更擅长 51 这类 8 位单片机的仿真Cortex-M 内核的调试一直是个麻烦事。但我可以明确说STM32 在 Proteus 里的仿真方案现在已经相当成熟尤其是像万年历这种带时钟、带温度采样、带人机交互的中小型课题反而是它最合适的应用场景。这个项目的本质是什么是让 STM32F103 同时干三件事读取日历时间、采集环境温度、处理用户按键并驱动显示和闹钟输出。在真实硬件上做你得准备开发板、DS1302 模块、DS18B20 温度传感器、OLED 屏、按键、蜂鸣器、杜邦线一堆东西还要担心接线虚焊、电源不稳这些物理世界的破事。而在 Proteus 里所有元件都是模型化的连线即通、点击即改整个项目的验证成本被压到了最低非常适合课设、毕设前期的方案验证以及自学 STM32 做整机整合训练。这个项目能学到的东西非常密RTC 类芯片的三线时序读写、单总线温度传感器的时序精确控制、状态机设计打底的人机交互逻辑、I2C 驱动的显示输出还有 Keil MDK 与 Proteus 联调的完整流程。可以说一个万年历仿真项目走完你在 STM32 上常用的外设接口基本都摸过一遍了。从实际带项目的经验看这套仿真还有一个特别实在的价值——编码和调 Bug 的速度比真机快很多。改一行时序代码真机上要重新编译、下载、拔线、复位一轮起码一分钟Proteus 里改完直接重启仿真10 秒完成同一件事。对于初学者来说这种“改一下就能立刻看效果”的反馈循环极其重要它直接把学习曲线拉平了不少。这篇文章就按我实际做完整个项目的顺序来写从系统架构、仿真环境搭建、核心器件使用到代码编写逻辑和最后的调试排错全程带着源码思路和避坑经验走。你可以直接拿这篇做参考去复现整个实验。2. 系统整体架构STM32 在万年历里到底扮演什么角色做嵌入式项目有个习惯动手写代码之前先把系统的数据流和任务分清楚。万年历这个项目看着小实际上涉及器件不算少如果一上来就裸写代码很容易陷入“显示乱了但我不知道是哪里乱的”这种泥潭。我先把整个系统的架构摆出来后面所有代码和调试都围绕这个框架展开。2.1 核心器件选型与分工这个项目在 Proteus 里用到的关键器件并不多每一颗都有自己的明确职责STM32F103C8主控芯片负责所有逻辑处理和时序生成。选它不是因为性能多强纯粹是因为 Proteus 对 F103 系列的仿真支持最完善而且 Keil 里直接用标准外设库或者 HAL 库都行资料多、踩坑少。DS1302实时时钟芯片负责记录年、月、日、时、分、秒。这芯片特别老但还在大量教学项目里用原因是它只有三根线和单片机交互写起来不复杂特别适合讲“时序协议怎么实现”这件事。仿真里它还能精准跑秒比用 STM32 内部定时器计时再换算日历时间要准确得多也省心得多。DS18B20单总线数字温度传感器负责采集环境温度。它在 Proteus 里有现成模型能设置当前温度数值仿真时你可以直接改这个值来模拟环境变化非常直观。0.96 寸 OLEDI2C 接口显示设备。为什么不选 LCD1602因为万年历要显示的内容比较多——日期、星期、时间、温度、闹钟提示1602 那种 16 列 2 行的小屏排版起来非常憋屈。OLED 12864 的显示区域大得多而且 I2C 只需两根线还能顺便练一下 I2C 总线的使用。独立按键 3~4 个负责用户交互。这个项目的功能是“可设闹钟”那就一定需要按键来完成模式切换、数值加减、确认保存这几类操作。蜂鸣器闹钟响铃的输出设备。仿真里用 SPEAKER 模型就行简单粗暴。2.2 模块间通信关系与数据流把上面的器件按照通信关系串起来整个系统的运行逻辑是这样的按键输入 → STM32读取/设置DS1302时间、读取DS18B20温度、比较闹钟时间 ↓ 读取 DS1302 时钟数据 ↓ 上传 STM32 做格式转换 ↓ OLED 显示时间日期温度 ↓ 闹钟触发时 蜂鸣器输出注意一个容易被忽略的细节万年历本体的时间基准是 DS1302不是 STM32 的 systick 或者定时器。这在仿真里尤为重要——Proteus 的仿真速度受主机性能影响如果程序跑得慢了或者主机卡了你用 STM32 内部计时换算的时间就会漂移一天下来能差出几分钟去。DS1302 是独立芯片它自己用晶振走时即使主程序卡顿也不影响时间推进。这也是为什么真实万年历产品通常都会配一颗专用 RTC 芯片的原因。2.3 为什么不用 STM32 内部 RTC这里值得多说一句。STM32 内部是带 RTC 模块的理论上可以直接用仿真里也能工作但我不建议初学者在这个项目里用。主要原因有三条内部 RTC 依赖备份域供电和 LSE 外部低速晶振在 Proteus 里配置起来比较麻烦而且教程参考少遇到问题不好排查用内部 RTC 还需要额外写一套秒计数转年月日的算法包括闰年判断、每月天数表这本身是一大坨容易出 Bug 的代码DS1302 芯片本身就内置这套日历算法你只需要给它读改写寄存器它自动完成大小月、闰年判断。把复杂的东西交给硬件不是懒惰是工程上的正确选择。这个选择在后面实际编码时能省掉大量时间。记住这个原则能用现成模块解决的绝不自己造轮子。3. Proteus 仿真环境搭建为什么说这是最容易卡住新人的环节从我的经验看这个项目 80% 的“做不出来”都卡在环境上而不是代码上。Proteus 跑 STM32 和跑 51 有个本质区别Proteus 默认不带 STM32 的调试能力必须搭建一套虚拟的烧录链路。我第一次搞这个的时候也折腾了差不多一个晚上所以这里把完整流程写透你照着走能省下很多时间。3.1 版本兼容你的 Proteus 必须能支持 STM32 仿真先说硬性门槛。Proteus 对 STM32 的支持是 8.x 之后才逐渐完善的8.0 以下版本基本别想跑 STM32。我个人的使用体验是 8.9 以上的版本比较稳元件库里能搜到“STM32F103C8”还能看到完整的引脚定义和仿真模型。如果你的版本元件库里根本搜不到 STM32F103C8换新版本吧别浪费时间在网上找插件补丁那些都不如一个正经的新版本靠谱。安装的时候有一个细节Proteus 默认不装所有的仿真模型库但你搜出 STM32F103C8 的时候它通常会自动下载对应的模型文件前提是你处于在线状态。如果离线安装完搜不到芯片多半是模型库没装全去安装目录的 Library 文件夹看一眼有没有 STM32 开头的文件没有就重新装一次完整的。3.2 必须安装 ST-Link 虚拟仿真器组件这是 Proteus 做 STM32 仿真最核心的一步也是很多人卡住的地方。光有芯片模型还不够Proteus 需要一个虚拟的调试下载器把 Keil 编译出来的 hex 或 elf 文件“烧录”进仿真芯片。最常见的方案是用“ST-Link V2”这个 Proteus 组件它出现在元件搜索里的名字是ST-LINK/V2不是“STLINK”或者别的什么搜索的时候注意别打错。放置 ST-Link V2 组件之后有一个关键的接线动作它的 SWDIO 和 SWCLK 两根线必须分别连接到 STM32 的 PA13SWDIO和 PA14SWCLK。SWDIO 和 SWCLK 是 STM32 片上调试接口专用的复用引脚默认就是这两个脚别接错。VCC 和 GND 接好电源就行了。这一步接错了后面 Keil 里怎么点下载都是失败。3.3 Keil 端配置生成 Proteus 认识的固件代码写完之后Keil MDK 那边要做两件事第一步开启调试器的“下载到外部 Flash”模式。菜单栏 “Options for Target” - “Debug” 选项卡右侧调试器下拉框选择“ST-Link Debugger”然后勾选下方的“Download to Flash”选项。注意这里不要选成普通的 Use Simulator而是要选带 ST-Link 的硬件调试模式这样才能触发 Keil 在编译后把固件通过 ST-Link 协议“烧录”出去。第二步修改烧录算法。在“Utilities”选项卡里点击“Settings”进入 Flash Download 页面先把原有的 Flash Algorithm 清空然后添加一个大小与 STM32F103C8 匹配的 Flash 算法比如 64K Flash 的那一个。这一步很多人忽略但不做的话下载时会报错说找不到可用的烧录算法。我在实际操作中发现Proteus 对烧录算法的型号匹配比真机更挑剔必须选对。完成这两步之后在 Keil 里点击 Load下载按钮如果一切正常Proteus 里的仿真会直接开始运行。以后每次改完代码在 Keil 里重新编译、下载Proteus 端会自动加载新的固件并重新开始仿真不需要手动烧录文件。3.4 晶振与电源配置一个很容易被忽略的细节STM32F103C8 在 Proteus 里默认使用内部 RC 振荡器还是外部晶振这是有讲究的。如果你在代码里初始化系统时钟时用的是外部高速晶振HSE那在 Proteus 的原理图里就必须给 STM32 接上一个晶振模型通常在8MHz左右同时两个引脚上各自接一个20pF 左右的电容到地。不接的话程序运行到时钟切换那一步会直接卡住表现就是“下载成功了但仿真完全没反应”。还要注意供电问题。Proteus 里 STM32 模型的 VDD 引脚要接到电源端**同时片上的 VDDA模拟电源引脚也需要接电源有些芯片模型还会把这些引脚单独引出来。漏接任何一个电源引脚都会导致仿真时芯片不工作或者 ADC 采样异常。我习惯在引脚属性里把所有需要供电的引脚统一标记为 VCC/VDD然后用电源标签统一连接这样不会漏。4. 核心电路原理解读模拟模块设计中的关键细节与选型理由环境搭好了接下来就要把芯片周围的电路搭对。万年历这个项目外围电路不多但每一个都有讲究尤其是 DS18B20 和 DS1302 的电路连接方式直接决定了你看不到数据还是偶尔出错。4.1 DS18B20 温度采集电路上拉电阻不是摆设DS18B20 是单总线设备所有数据传输都在一根 I/O 线上完成所以这根线的电平状态至关重要。在 Proteus 里放一个 DS18B20然后把它的 DQ 引脚接 STM32 的某个 GPIO我通常用PB1中间一定要加一个4.7kΩ 左右的排阻上拉到 VCC。为什么不加不行因为 DS18B20 的数据线是开漏结构器件本身只能把总线拉低不能主动输出高电平。总线上的高电平完全依赖外部上拉电阻。如果在 Proteus 里省掉这个电阻就算代码时序完全正确你也永远读不到温度数据因为总线上根本形不成有效的高电平状态——这是很多新手在这个模块上翻车的头号原因。除了上拉电阻DS18B20 的供电方式也值得说一下。仿真里我直接用外部电源给它供电寄生供电模式虽然省一根线但时序上更脆弱不适合仿真调试。具体接法VDD 接 5V或者 3.3V具体看 Proteus 模型支持GND 接地DQ 通过 4.7kΩ 上拉到 VCC。4.2 DS1302 时钟电路三线接口与备用电源DS1302 和 STM32 之间只有三根线SCLK串行时钟、I/O数据、CE片选使能。在仿真图里把这三根线分别接到 STM32 的三个 GPIO我用的是PA0、PA1、PA2分别对应 CE、SCLK、I/O另外也要给 DS1302 接上晶振——仿真模型里一般自带 32.768kHz 晶振但最好还是按照模型属性确认一下。这里有一个仿真特有但容易被忽略的点DS1302 的VCC1备用电源引脚接不接在真实电路里这个引脚通常接一个纽扣电池这样主电源断了时间还能继续走。在 Proteus 仿真里即使不接备用电源只要主电源不断DS1302 依然能正常走时。所以为了简单不接也可以但我个人建议在图上画出备用电池的符号让它更接近真实产品形态后面如果要把这个设计做成实物电路图可以直接复用。DS1302 和 STM32 之间的电平匹配要留意。DS1302 的工作电压范围宽2.0V~5.5VSTM32 的 GPIO 是 3.3V 电平二者可以直接连接不需要电平转换。但如果你把 DS1302 的 VCC2 接到了 5V数据线上的高电平会被上拉到 5V回到 STM32 引脚时理论上超过 3.3V 的耐压范围在 Proteus 里一般不会真的烧芯片但这是一个不好的设计习惯。建议把 DS1302 的 VCC2 接到 3.3V 电源和 STM32 保持同一电平域。4.3 OLED 显示电路I2C 上拉与地址确认OLED 0.96 寸模块的 I2C 接口只需要接四根线VCC、GND、SCL时钟、SDA数据。在 Proteus 里我把它接在PB6SCL和 PB7SDA上这是 STM32F103 的 I2C1 硬件外设的默认引脚。I2C 总线同样是开漏结构所以 SCL 和 SDA 两根线上各需要一个上拉电阻。在仿真软件里很多 OLED 模型内部已经集成了上拉但为了稳定我仍然会在外部加上两个 4.7kΩ 上拉。加了之后即使 OLED 的内部上拉不存在不同模型不一样总线电平也是正确的。另外要注意 OLED 的 I2C 地址。常见的 0.96 寸 OLED 模组 I2C 地址是0x787 位地址 0x3C或者 0x7A7 位地址 0x3D具体由模块背面电阻决定。在 Proteus 里使用 OLED 模型时一定要先查一下这个模型默认的 I2C 地址是多少然后在代码里的 OLED 初始化函数中设置一致。我在第一次做这个项目时就在这里卡了快半小时代码库默认地址是 0x78但 Proteus 模型是 0x7A结果屏幕一直白屏。排查方法也很简单代码里把地址在两个值之间切换一下总有一个是对的。4.4 蜂鸣器输出电路与按键输入设计蜂鸣器我直接用了 Proteus 元件库里的“SOUNDER”模型通过一个 NPN 三极管驱动比如 2N2222STM32 的 GPIO 输出高电平让三极管导通蜂鸣器就响。仿真里其实可以更偷懒一点直接把蜂鸣器串个电阻接在 GPIO 和地之间模型也能响但加上三极管会更接近真实电路。我建议保留三极管驱动结构因为你后面做实物的时候这个电路可以直接抄过去。按键部分我给每个按键配置了一个 10kΩ 下拉电阻按键按下时 GPIO 读取到高电平松开时下拉把电平拉低。按键输入要加一个 100nF 左右的去抖电容吗在仿真里因为 Proteus 的按键模型没有机械抖动其实不需要硬件去抖但代码里的软件去抖逻辑还是建议写——等做成真机的时候这个经验会派上用场。5. 固件核心实现DS1302 时序、DS18B20 采集与显示驱动环境、电路都搞定了接下来是重头戏——代码。这一节我按模块拆开讲每一块都会给出核心逻辑和关键代码你组装的时候就知道每一段代码在干什么、为什么要这么写。5.1 初始化与系统时钟第一行代码就决定成败STM32F103C8 在 Proteus 里默认的系统时钟来源比较特殊它是通过内部 HSI 或者外部 HSE 来提供的。为了简化仿真流程我强烈建议在代码里直接使用内部时钟HSI 8MHz 倍频到 72MHz或者干脆保持默认时钟不动只初始化需要的 GPIO 和外设。为什么这么说因为 Proteus 仿真里如果用外部晶振需要确保晶振模型工作正常否则系统时钟初始化会卡在等待 HSE ready 的死循环里。我实际测试下来8MHz HSI 直接作为系统时钟已经足够跑万年历这种负载了不需要追求 72MHz 的最大性能。所以我的初始化代码大致是这样void System_Clock_Init(void) { // 设置 Flash 等待周期这里使用内部时钟不需要配置 PLL // 保持默认 8MHz 内部时钟然后使能需要的 GPIO 时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); }如果你还是想用外部晶振 PLL 倍频到 72MHz那可以参考标准库里的 SystemInit 函数但记得在 Proteus 原理图里把外部 8MHz 晶振画上否则代码会在 HSE 就绪等待那里死循环。这也是仿真和真机的一个显著差异真机是硬件自己震荡仿真里晶振也是一个“模型”不画就没有输出。5.2 DS1302 读写时序三线协议的最小实现DS1302 的协议本质上是 SPI 的一个变种但它没有标准的 SPI 外设支持必须用 GPIO 模拟时序。读写 DS1302 有个关键点每个字节都是低位在前LSB first这和大多数人的直觉相反写代码的时候要留意。读 DS1302 时间信息的核心函数大致是这样static uint8_t DS1302_ReadByte(void) { uint8_t i, dat 0; for (i 0; i 8; i) { dat 1; if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_2)) { dat | 0x80; } DS1302_SCLK_High(); delay_us(2); DS1302_SCLK_Low(); delay_us(2); } return dat; }为什么要先让 dat 右移再置位最高位因为协议要求低位先从 I/O 线上读出来所以我每收一位就把这一位挪到字节的最高位然后 dat 不断右移这样低位就会慢慢往高位走最终拼出一个正确的字节。如果你反过来先把 dat 左移再放到最低位读出来的数据就是反的那个痛我懂——第一版就是这么写错的。写时序的时候要注意数据在 SCLK 上升沿被 DS1302 锁存所以在拉高 SCLK 之前必须先把数据位准备好。这个顺序颠倒的话发送的数据是乱的后面调起来非常痛苦。有了读写字节的底层函数读时间就是组合命令的过程先发送“读特定寄存器”的命令字节最高位固定为 1后面跟着寄存器地址然后连续读取 7 个字节得到秒、分、时、日、月、星期、年。DS1302 的时间寄存器存放的是BCD 码不是十进制数。也就是说秒寄存器读到 0x55 表示 55 秒0x12 表示 12 秒。如果代码里直接把这个值当十进制打印屏幕上会出现“秒 85”这种离谱的数字。所以解读数据前一定要做 BCD 转十进制static uint8_t BCD_to_DEC(uint8_t bcd) { return (bcd 4) * 10 (bcd 0x0F); }同理在设置闹钟时间的时候需要把十进制数转换成 BCD 码再写入寄存器也就是反过来((val / 10) 4) | (val % 10)。5.3 DS18B20 温度采集单总线的一线生机DS18B20 的单总线协议比 DS1302 更“硬核”——它要求主机精确控制多个微秒级别的延时而且读写时序的判定条件非常严格。Proteus 仿真里有一个优势就是微秒级延时可以写得很精确不需要考虑真机上的指令周期偏移。但反过来如果你在延时函数里写得太随意仿真同样会失败因为模型会严格检查时序窗口。完整的 DS18B20 读取流程分三步复位脉冲主机拉低总线至少 480μs然后释放DS18B20 会在 60~240μs 内拉低总线作为存在脉冲。跳过 ROM 命令0xCC因为总线上只有一个传感器不需要匹配序列号直接跳过可以节省大量时间。启动温度转换0x44然后等待转换完成通常需要 750ms再发送读暂存器命令0xBE连续读 2 个字节组合成 12 位温度数据。温度数据的解析是一个很容易搞错的点。DS18B20 返回的是带符号的 12 位二进制补码低 4 位是小数部分int16_t raw (temp_high 8) | temp_low; float temperature raw * 0.0625f;为什么要乘以 0.0625因为 DS18B20 的分辨率是 12 位每个 LSB 代表 0.0625°C即 1/16°C。如果你直接把 raw 值除以 16也能得到相同结果就是整型和浮点运算的选择问题。这里踩过的一个坑是在 Proteus 里点击 DS18B20 模型可以修改当前环境温度但修改之后需要等下一次温度转换完成才能读到新值。如果程序里连续读取的速度太快你可能读了好几轮还是旧温度误以为代码有问题。实际测试中我建议代码里的温度采集周期不小于 1 秒既符合器件特性也不会把 CPU 空耗在读温度上。5.4 OLED 显示驱动I2C 命令与控制流的安排OLED 显示模块我用的是一款常见 0.96 寸 SSD1306 控制器。在代码层面你需要实现 SSD1306 的初始化命令序列、设置显示位置、把显存上传到屏幕这几个基本功能。显示的逻辑其实很简单SSD1306 内部有一块 1024 字节的显存128x64 像素每字节控制 8 个像素点你在单片机里维护一个同样大小的数组修改这个数组就是在“画图”然后通过 I2C 把整个数组发送给 OLED 即可刷新屏幕。不需要每次修改一个像素就刷一次屏那样 I2C 会忙死。万年历的主界面布局我是这样排的第一行星期几 日期例如“FRI 2025-01-17”第二行时间例如“12:30:45”第三行温度例如“Temp: 26.5C”第四行闹钟状态例如“Alarm: 07:30 ON”显示的时候有个小技巧数字字符要自己写一个 ASCII 字库数组把 0~9、冒号、字母 C/F 等常用字符做成 8x16 的点阵数据。如果是中文字符每个字需要 16x16 的字库占用 Flash 空间更大。我第一次做的时候偷懒直接在显示字符串函数里用if-else判断字符是什么然后发送对应的点阵代码丑但能用。后来发现把字库做成const unsigned char ASCII[][16]数组按字符 ASCII 码索引代码又干净又容易扩展——这是做显示类项目的基本功强烈建议一开始就养成这个习惯。5.5 按键状态机与闹钟设置模块把逻辑撑起来的设计方案万年历不是只显示时间就完事的核心要求是“可设闹钟”。这就意味着界面要能在“正常运行模式”和“设置模式”之间切换还要在设置模式下完成参数修改和确认保存。这个交互过程如果不用状态机去管理代码会变成一团乱麻。我设计的状态机非常简单清晰状态 0正常运行显示时间、日期、温度、闹钟状态按键“模式”按下后进入状态 1。状态 1设置闹钟-小时当前闹钟的小时数高亮闪烁“加”和“减”键调整小时“确认”键进入状态 2。状态 2设置闹钟-分钟当前闹钟的分钟数高亮闪烁“加”和“减”键调整分钟“确认”键保存闹钟设置并返回状态 0。有几点设计上要注意一是在设置状态下DS1302 的正常走时不能被干扰我只修改闹钟寄存器不修改 RTC 时间寄存器这样时间不会乱跳二是“模式”键在设置状态下应该作为“返回”键方便用户退回去重设而不需要一路确认到底三是按键扫描要带上简单的软件去抖通常是检测到电平变化之后延时 10~20ms 再读一次确认稳定了才执行逻辑。闹钟触发逻辑很直白每次主循环刷新时间的时候读取当前“时”和“分”和闹钟寄存器里保存的“时”“分”做比较相等就控制蜂鸣器引脚输出一个方波信号让蜂鸣器发出“滴滴”的声音。为了让闹钟声音不刺耳我让蜂鸣器以 2Hz 的频率交替响和停持续 30 秒后自动安静。6. 从仿真到现实主循环组织与调试排错经验整个项目的代码写完之后核心主循环其实简单得让人意外。这种“外围很复杂、主循环很清爽”的状态恰恰说明模块化做得好——每个模块的驱动代码各自封装主循环只负责调度不需要关心底层实现。6.1 主循环的运行节奏设计我采用的是一个超级循环结构但有意识地给每个任务分配了不同的执行频率避免所有代码都在最密集的节奏上跑while (1) { // 每10ms扫描一次按键带软件去抖 if (time_flag_10ms) { Scan_Key(); } // 每250ms刷新一次OLED显示 if (time_flag_250ms) { Update_Display(); } // 每1s读取一次DS1302时间实际走时由DS1302自己完成 if (time_flag_1s) { Read_DS1302_Time(current_time); } // 每1s读取一次DS18B20温度 if (time_flag_1s_temperature) { Read_DS18B20_Temperature(temperature); } // 每100ms检查一次闹钟是否应该响 if (time_flag_100ms) { Check_Alarm(); } }这些time_flag是靠一个 SysTick 中断或者 TIM2 定时器中断来置位的。这样做的好处是一目了然而且不会出现因为某个函数耗时过长导致其他功能卡住的问题。如果你用 HAL 库可以用HAL_GetTick()配合全局变量做时间片如果用标准外设库就初始化一个 1ms 的定时器中断在中断里累加计数器并置位标志位。两种方式都行选你顺手的。6.2 排查过程实录OLED 白屏问题的完整定位链路前面提到 OLED 的 I2C 地址不匹配问题这里把完整的排查过程还原一下这套思路可以用在绝大多数“仿真能跑但外设没反应”的场合。现象程序编译下载之后OLED 完全白屏没有显示任何内容但 Proteus 仿真没有报错其它功能时间、温度在调试窗口看起来正常。第一步确认 I2C 通信有没有产生。我在代码的 OLED 写命令函数里加了一个 GPIO 翻转语句让 PB6 在每次发送数据前拉低再拉高。仿真跑起来之后用 Proteus 的虚拟示波器也可以直接把引脚电平可视化看 PB6 的波形结果确实有波形说明 I2C 通信在发生。第二步确认 OLED 地址。我查了 Proteus 里 OLED 模型的属性发现它的 I2C 地址是 0x7A。而我代码里的全局宏定义是#define OLED_ADDR 0x78。这就找到问题了——地址都不一致SSD1306 根本不会响应主机发送的数据。处理方式把宏改成0x7A重新编译下载OLED 立刻开始正常显示。这个案例说明一个通用的调试策略先确认物理层有没有信号再确认协议层地址对不对最后才怀疑上层逻辑。顺序反了会浪费大量时间。6.3 几个逼疯过我的仿真特有坑第一个坑Proteus 仿真速度极慢。如果你的电脑性能一般Proteus 跑 STM32 仿真可能只有实际速度的 10%~20%OLED 刷新和 DS18B20 的 750ms 转换时间都会拖得非常长。这时候可以在 Proteus 的“Debug”菜单里开启“Run at full speed”或者调高仿真速度代价是调试信息的刷新变慢。如果只是为了看最终效果全速运行没问题。第二个坑DS1302 走时不准。其实不是芯片不准是仿真速度被降低了DS1302 收到的时钟脉冲也变慢了所以它显示的时间比真实时间慢。这是仿真环境造成的假象不代表代码有问题。遇到这种情况直接在 Proteus 的 Run 菜单里选择全速运行时间就走准了。第三个坑蜂鸣器不响。我遇到过蜂鸣器模型明明输出高电平却没声音的情况最后发现是蜂鸣器模型本身的问题——Proteus 的“SOUNDER”模型默认频率太高超过了 wav 音频的输出范围实际上人的耳朵听不见。后来我换成“BUZZER”模型或者调整了蜂鸣器模型的输入频率让它用 2kHz 左右的方波驱动声音就正常了。仿真里“听不见”不代表“没有信号”先看波形再判断输出是否正常。6.4 把仿真做成实物的衔接建议做仿真实验的人十有八九后面要做实物。这里有一个衔接建议特别想说从仿真到实物的移植其实非常顺利因为代码几乎不用改。GPIO 定义、DS1302/DS18B20 的时序函数、SSD1306 的显示驱动全部可以原样搬过去唯一要改的是硬件延时函数的精度——真机上的delay_us可能需要微调因为不同主频下的指令周期不一样。另外真机上要注意 DS18B20 的 DQ 线要接上拉电阻这一点仿真里强调了真机也不能忘。还有一个从仿真到实物的心理预期管理真机的时序没有仿真那么宽容如果 DS18B20 偶尔读回错误数据在代码里加一个简单的校验比如连续读两次两次一致才采用就足够了不需要从底层彻底重写时序驱动。7. 最后的工程建议怎么把这个项目打磨成自己的作品如果只是照着本文把代码跑通那你获得的是一个能用的仿真实验。但如果想让这个项目成为一份值得写进履历或课程设计报告的作品我有几个实际建议。第一给项目加一个“设置时间”的功能。万年历只显示时间不设置时间在逻辑上是残缺的——DS1302 出厂默认时间往往是 2000 年 1 月 1 日不能用按键校准的话这个万年历就没法实际使用。实现方式非常简单复用现有的设置模式状态机增加一个“设置时间”分支修改 DS1302 的寄存器即可。这个功能加完之后项目从“演示型”变成了“可用型”。第二在 OLED 上做一个简单的动画或图标比如整点报时、闹钟到点的闪烁提示、温度过低/过高的颜色反转显示虽然 OLED 单色反转视觉效果仍然很清晰的这些都是成本极低但提升体验的细节写进报告里也好看。第三把代码的模块结构梳理清楚每个模块DS1302、DS18B20、OLED、按键、蜂鸣器单独一个文件头文件声明接口函数主程序只做调度。这不仅是良好工程习惯也方便老师或面试官快速看懂你的设计思路。我见过太多课设代码从头到尾塞在一个 main.c 里两千多行挤在一起调试和答辩都痛苦。最后说一句关于调试心态的话。仿真项目的最大优点就是“错了不炸”你可以随意改参数、看波形、打断点。我做这个项目的时候光是 DS18B20 的时序就来回调了三个多小时从复位脉冲宽度到读时隙延时一点点试。每一次失败都是一次对协议更深的理解拿到波形图的时候那种满足感是直接抄现成代码永远体会不到的。希望你也把这个过程当成一种享受而不只是拿一份程序交差了事。本文还有配套的精品资源点击获取