ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

STM32实战:一套可复现的开源工程,原理图+代码+仿真全解析

STM32实战:一套可复现的开源工程,原理图+代码+仿真全解析 1. 我为什么把整套STM32工程直接摊开一个可复现项目的自我要求最近整理手头的一套STM32项目时我做了个决定把代码、原理图、仿真三样东西完整开源出来。身边不少朋友问我开源就开源丢个代码仓库不就行了为什么连原理图和仿真工程都要一起给说实话我见过太多所谓的“开源STM32项目”点开仓库只有一堆散落的.c和.h文件没有原理图没有说明没有仿真工程。你想复现得自己猜引脚接法猜初始化顺序猜外设配置。运气好能跑起来运气不好就是三天三夜的排查。这种项目说白了只是“把代码公开”算不上真正的开源。评价一个STM32开源项目值不值得看我会先确认三件事能不能看懂硬件连接能不能直接编译烧录能不能在没有板子的情况下先跑仿真。代码、原理图、仿真三件套齐全这条复现链路才是通的。我这次开源的这套项目就是按照这个标准来整理的。这套项目以STM32F103C8T6为主控做的是一个多通道环境数据采集器采集温湿度、光照强度通过OLED实时显示同时把数据打包通过串口发送给上位机。听起来不复杂但麻雀虽小五脏俱全单总线传感器时序、I2C屏幕驱动、状态机设计、串口协议解析、低功耗优化全都有涉及。无论你是准备做毕业设计、电子竞赛还是想找一个能二次开发的基础工程这套东西都能直接用。文章后面我会把原理图设计的几个关键取舍、代码组织方式、仿真验证手段以及我踩过的坑逐一讲清楚。这些都是项目里真实的处理方式不是抄参考手册的复述。1.1 开源不等于丢压缩包三件套各自解决什么问题先说说代码、原理图、仿真这三样东西到底各管哪一段。原理图解决的是“硬件长什么样”的问题。哪个引脚接了什么东西电源怎么供给上下拉电阻放在哪里晶振电路怎么设计这些问题只有原理图能回答。没有原理图代码里的GPIO初始化就是一堆无根之萍。代码解决的是“逻辑怎么跑”的问题。初始化顺序、外设配置、数据结构、状态迁移全部体现在这里。仿真解决的是“没有硬件时怎么验证”的问题。Proteus电路仿真能验证原理层面的连通性MDK的逻辑分析仪能看引脚时序这两者在硬件到手之前就能帮你筛掉大部分低级错误。缺了其中任何一样复现成本都会成倍上升。你想想一个新手拿到代码不知道PB9接的是蜂鸣器还是LED他怎么调拿到原理图但没有仿真工程画完板子才发现I2C上拉电阻没加他又怎么排查所以这次开源我把三件套当成一个整体来整理。工程里专门建了Hardware、Simulation、Docs三个顶层目录分别放AD格式的原理图源文件、Proteus仿真工程和MDK代码工程。任何人拿到压缩包按顺序打开这三个目录就能走通“看硬件→跑仿真→烧代码”的完整链路。1.2 这套项目的选型逻辑为什么是F103C8T6选STM32F103C8T6这颗芯片不是因为它新恰恰是因为它足够旧、足够稳、足够普及。这颗芯片的资源对这个项目来说刚好够用64KB Flash20KB RAM三个USART两个I2C两个SPI一个12位ADC还有大量GPIO。采集温湿度、光照、驱动OLED和串口通信外设占用率大约只有四成留出了充足的扩展空间。价格上也友好核心板或者自己画PCB打样单片成本能控制在十块钱上下。更重要的是生态。F103系列是ST出货量最大的产品线之一网上随便一搜就是大量例程和讨论帖。这意味着我开源之后使用者遇到问题能搜到足够多的参考不至于孤立无援。对一个开源项目来说生态成熟度直接影响它的生命力。当然如果追求更低功耗可以选择L系列如果追求更高算力可以选择G4或H7系列。但这套项目的定位是教学和基础验证F103C8T6在性价比和普适性之间取得了最好的平衡。后面讲代码架构时你会看到我这套代码的分层方式是完全独立于芯片型号的换到其他M3、M4内核的芯片上只需要改驱动层就能整体移植。2. 原理图里的几个关键取舍电源、时钟、下载与传感器外围原理图是这个开源项目里最容易被忽视、但实际分量最重的部分。代码写得再漂亮硬件连接不合理一切白搭。下面我把这次设计里几个花过心思的地方摊开讲。2.1 电源通路设计为什么用了两个LDO而不是一个这套项目的供电输入是5V USB板上需要3.3V给MCU和传感器供电。很多人会直接用一个AMS1117-3.3搞定但我用了两路LDO一路给MCU数字部分供电一路单独给传感器和OLED供电。原因很简单传感器和OLED在工作时电流波动比较明显OLED刷新瞬间的电流尖峰尤其突出。如果和MCU共用一路LDO尖峰可能会造成VDD纹波过大严重时甚至导致MCU复位。分开供电之后数字部分和模拟外设部分之间的干扰路径被切断了系统的稳定性明显提升。去耦电容的摆放也按分区来做。MCU电源引脚附近放一个10uF钽电容并联一个100nF陶瓷电容传感器插座旁边同样放一组。大电容负责低频储能小电容负责高频去耦这在EMC上的效果远好于只用一个大电容。2.2 晶振电路的设计8MHz主晶振和32.768kHz RTC晶振的选择F103C8T6的时钟源可以用内部HSI也可以用外部HSE。我选了外部8MHz晶振加PLL倍频到72MHz原因是HSE的精度远高于HSI串口通信时如果时钟偏差太大会出现乱码。8MHz晶振的两个负载电容取了20pF这是根据晶振本身的负载电容参数算出来的原则是让振荡电路工作在晶振标称的并联谐振频率附近。32.768kHz的RTC晶振我也加了虽然这套项目的RTC功能只是用于记录采集时间但在低功耗模式下LSE比LSI省电得多精度也高一个数量级。这里有个常见的坑RTC晶振的两个负载电容不能太大通常取6~12.5pF取大了反而会停振。我第一次画板子时照搬了主晶振的20pF结果RTC走时慢得离谱后来换成6.8pF才恢复正常。2.3 SWD下载与串口ISP两种方式都保留下载调试接口是原理图里最基础但也最容易出错的部分。这次我同时设计了SWD四线接口和串口ISP电路。SWD只需要SWDIO、SWCLK、GND、3.3V四根线比JTAG少占用一大堆引脚用ST-Link或者DAP-Link都能连。串口ISP则通过BOOT0引脚和USART1实现方便在没有仿真器的情况下用USB转TTL模块烧录程序。这里有一个细节串口ISP电路里的BOOT0跳线没有直接接地或者接3.3V而是用一个2.54mm跳线帽引出。正常运行时跳线帽接GND需要ISP烧录时接到3.3V然后复位。这样设计的好处是灵活性高缺点是需要手动操作。如果不想用跳线帽可以用一个三极管做自动下载电路但要额外占用一个DTR/RTS信号来做电平转换逻辑复杂一些。考虑到这套项目的定位手动跳线帽反而更直观新手一眼就能看懂BOOT0的作用。2.4 传感器与OLED的外围电路上拉电阻和滤波电容一个都不能少DHT11温湿度传感器是单总线协议数据线默认需要接一个4.7kΩ上拉电阻到3.3V。这个电阻不加总线空闲时电平不确定读取时序就会飘。OLED用的是I2C接口的SSD1306方案SDA和SCL同样各接一个4.7kΩ上拉电阻。F103的I2C外设虽然内部可以配置为开漏输出但外部上拉电阻是必须的内部上拉根本无法满足I2C的时序要求。光敏电阻模块和蜂鸣器驱动电路放在一起说。光敏模块的输出是数字量直接进MCU的GPIO即可但为了抗干扰我在ADC采样引脚加了一个100nF的滤波电容到GND。蜂鸣器则用了一颗NPN三极管做驱动因为MCU引脚直接驱动蜂鸣器电流不够而三极管可以把驱动能力放大约几十倍。注意三极管基极一定要串联一个1kΩ限流电阻否则基极电流过大会拉低MCU引脚电平甚至损坏引脚。3. 代码组织从ST官方例程之外我更推荐的分层方案代码部分是绝大多数人拿到开源项目之后第一个打开的东西。但打开之后怎么快速看懂取决于代码的组织方式。ST官方例程好用不假但它把所有文件平铺在一起而且命名风格以芯片型号为前缀文件一多就乱。这开源项目里我用了另一种分层思路。3.1 一个更清爽的目录结构按职责划分而不是按外设划分先直接把我这次开源的工程目录结构亮出来Project/ ├── Core/ │ ├── Inc/ │ └── Src/ │ ├── main.c │ ├── stm32f1xx_it.c │ └── system_stm32f1xx.c ├── Drivers/ │ ├── CMSIS/ │ └── STM32F1xx_HAL_Driver/ ├── Hardware/ │ ├── bsp_dht11.c/h │ ├── bsp_oled.c/h │ ├── bsp_light.c/h │ └── bsp_beep.c/h ├── App/ │ ├── app_main.c/h │ ├── data_processor.c/h │ └── protocol.c/h ├── MDK-ARM/ └── Simulation/Core和Drivers是CubeMX生成的底层支持基本上不需要动。Hardware目录放的是具体的板级驱动每个外设对应一组文件比如bsp_dht11.c负责DHT11的时序读写bsp_oled.c负责SSD1306的驱动。App目录是业务逻辑层data_processor.c把原始数据转成工程单位protocol.c负责组帧、解析和校验。这样的分层逻辑是底层驱动不关心业务业务层不关心寄存器。你要改显示内容只动App你要换一个传感器只改Hardware。两者通过明确的接口函数连接互相不污染。这才是能让别人快速看懂、快速改动的工程结构。3.2 标准库、HAL库、LL库这个项目为什么选了HALF103系列的开发库选择无非三种标准外设库、HAL库、LL库。标准外设库已经停止更新了新上手的人再用它遇到问题连官方支持都难找。LL库效率高但抽象程度低写起来接近寄存器操作适合对性能有极端要求的场景。HAL库虽然代码量大运行效率比标准库略低一点但它的优势是抽象统一、生态成熟、CubeMX直接支持。这套项目用HAL库还有一个实际考虑HAL库的接口函数名和CubeMX生成的初始化代码是绑定的。使用者如果想把我的工程移植到别的F1系列芯片上用CubeMX重新生成初始化代码驱动层和应用层几乎不用改。这就是生态的价值。当然HAL库那种“函数套函数”的风格偶尔会让人抓狂但这套项目里我对实时性要求并不苛刻HAL的间接层开销完全可以接受。真需要高精度时序的地方——比如DHT11的微秒级延时——我直接写了寄存器操作绕开HAL后面会讲。3.3 单总线传感器为什么必须用状态机DHT11读取的工程化处理DHT11是很多初学者的入门传感器但也是很多人调不通的坎。看参考手册的时序图感觉很简单主机拉低开始信号DHT11拉低响应然后连续输出40bit数据。但真写代码的时候如果你用delay循环去死等时序问题就来了delay的时间精度不够。HAL库的HAL_Delay以毫秒为最小单位而DHT11的时序是微秒级的。中断会打断时序。开启串口接收中断后微秒级延时会被打断导致读取失败。阻塞式读取会拖垮系统。整段时序大约需要5ms期间MCU什么都干不了。我在工程里用了状态机处理。核心思路是把DHT11的读操作拆成多个状态发送起始信号、等待响应、读取40bit、解析数据每个状态的切换放在一个周期调用的函数里跑不阻塞系统。直接贴一段关键代码这是状态机的核心部分typedef enum { DHT11_START, DHT11_WAIT_RESPONSE, DHT11_READ_DATA, DHT11_PARSE, DHT11_IDLE } dht11_state_t; uint8_t dht11_poll(dht11_dev_t *dev) { switch (dev-state) { case DHT11_START: HAL_GPIO_WritePin(dev-port, dev-pin, GPIO_PIN_RESET); delay_us(18000); // 主机拉低 18ms HAL_GPIO_WritePin(dev-port, dev-pin, GPIO_PIN_SET); delay_us(30); // 释放总线后延时30us dev-state DHT11_WAIT_RESPONSE; dev-timeout 100; break; case DHT11_WAIT_RESPONSE: if (HAL_GPIO_ReadPin(dev-port, dev-pin) GPIO_PIN_RESET) { dev-state DHT11_READ_DATA; dev-bit_count 0; dev-byte_index 0; memset(dev-raw, 0, 5); } else if (--dev-timeout 0) { dev-state DHT11_IDLE; return DHT11_ERR_NO_RESPONSE; } break; case DHT11_READ_DATA: dht11_read_bits(dev); // 内部用精确延时采样40bit if (dev-byte_index 5) { dev-state DHT11_PARSE; } break; case DHT11_PARSE: if (dht11_check_crc(dev-raw)) { dev-humidity dev-raw[0] dev-raw[1] * 0.1f; dev-temperature dev-raw[2] dev-raw[3] * 0.1f; dev-state DHT11_IDLE; return DHT11_OK; } dev-state DHT11_IDLE; return DHT11_ERR_CRC; case DHT11_IDLE: default: break; } return DHT11_BUSY; }用状态机的收益很明显主循环每次调用dht11_poll它最多执行一个状态的动作就返回不会卡死系统。即使DHT11长时间无响应也有timeout兜底不至于让整个系统挂掉。至于最底层的微秒级延时我写了一个简单的delay_us循环通过读取DWT寄存器实现比HAL_Delay精度高一个数量级void delay_us(uint32_t us) { DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; while (DWT-CYCCNT us * 72); // 72MHz主频下1us约72个时钟周期 }这套组合理论上可以完美应对DHT11的时序要求但在实际项目中还是踩过坑后面第六部分会专门讲中断对它的影响。3.4 串口协议帧从裸数据到可解析的传输格式传感器数据采集到了OLED也显示了但要把数据发给上位机必须有一个双方都认可的协议。我定义了一个非常精简的帧格式帧头长度命令字数据区校验0xAA 0x551字节1字节N字节CRC8帧头用两个字节避免单字节误判。长度字段表示数据区字节数命令字区分不同类型的数据帧。CRC8校验用的是多项式0x31成本低足够检测误码。上位机只要按这个格式解析就能完整还原整个数据包。组帧代码在protocol.c中对外只暴露两个接口protocol_pack()和protocol_parse()。App层拿原始数据调用pack得到一包字节再把字节交给串口发送上位机发来的配置命令则走parse解析结果直接回调到App层的命令处理函数。协议层做主的好处是以后想加个WiFi模块、加个蓝牙透传协议不用动只要把串口换成对应的无线通道即可。这就是模块化的红利前期多花半小时设计后期能省无数个加班的晚上。4. 没有硬件也能验证Proteus、MDK软仿真和在线调试的配合仿真这段我想多说几句实话仿真不能替代实物但在硬件回来之前它能帮你干掉至少一半的低级问题。真正高水平的开发者从来不是“一把过”的天才而是能利用一切工具把试错成本提前的人。这套项目用了三层仿真手段每一层解决不同的问题。4.1 Proteus仿真做到什么程度就够了Proteus对STM32的仿真很多人误以为要把整个工程完整跑通才有意义。其实不是这样。我用Proteus主要验证三件事电源网络的连通性、GPIO映射的正确性、以及简单外设的时序逻辑。比如DHT11在Proteus里放一个虚拟器件OLED用Proteus自带的液晶模型替代STM32F103C8T6的固件直接加载MDK编译生成的hex文件点击运行就可以看到点亮的OLED和变化的传感器数值。仿真工程要特别注意元器件的模型参数。Proteus的虚拟晶振默认工作频率可能和实际不一致我通常把Crystal Frequency改成8MHz并把程序里的SystemCoreClock配置核对一遍。如果仿真不正常优先检查晶振频率配置和GPIO模式设置这两处是对不上最容易出问题的地方。这次开源的仿真工程里我把完整的Proteus工程连同使用说明一起放了进去打开就能跑不用自己画电路。但这套仿真只验证逻辑不验证实际电气特性尤其是供电纹波、信号完整性问题仿真里根本看不出来属于正常现象。4.2 MDK逻辑分析仪调时序问题最被低估的工具很多人打开Keil MDK就是编译下载完全忽略了一个利器——软件仿真下的逻辑分析仪窗口。当硬件还没到手或者手头没有示波器时MDK的逻辑分析仪可以直接观察GPIO输出波形用它来核对DHT11的时序是再好不过的。操作路径是进入Debug模式不连接仿真器选择Simulator然后在View菜单打开Logic Analyzer窗口添加需要观察的GPIO引脚。我用它抓过一次DHT11的起始信号发现拉低时间偏长了近2ms立即定位到是delay_us的循环次数计算有误在仿真环境里就修掉了。如果没有这层验证这个bug会直接带到板子上到时候只能用示波器一点一点查调试效率低好几倍。需要提醒的是MDK软仿真对时间精度的模拟受限于指令周期模拟的准确性复杂外设的时序只能做到“定性”而不能“定量”。对于判断“这个信号有没有、切换顺序对不对”这类问题它非常称职对于精确测量脉宽还是得靠真家伙。4.3 在线调试ST-Link断点和实时变量监视是最后的防线当硬件终于到手在线调试仿真就要担当最后的防线角色了。熟悉这套流程的人都知道在线调试最大的价值不是看代码跑没跑而是能在断点处查看变量、观察内存、单步跟踪。我这次项目里用ST-Link配合MDK做在线调试只设了两个断点一个在数据解析完成处一个在串口发送完成处。没有任何传感器数据时这两个断点的触发频率能帮我判断系统是否卡死、协议是否正常。变量窗口里我添加了温湿度原始帧的字节数组和解析结果的浮点值方便对比。在线调试很少用来“找代码Bug”因为它能帮你看到的都是浅层问题。真正的Bug往往在你不设断点的时候才会出现比如时序被断点打断之后的异常行为。所以我的建议是在线调试只用来验证关键路径不要用它替代逻辑思考。4.4 仿真和实物的差异哪类问题必须在实物上解决仿真救不了所有人的问题必须坦诚地说清楚边界模拟信号的噪声和纹波Proteus仿真不了必须实测。GPIO的驱动能力和压降仿真模型不会告诉你带载后实际电平会跌到多少。时序响应在实际晶振起振时间不同仿真里可能一切正常板上就是跑不起来。低功耗电流几乎无法通过软件仿真获得可靠数据。所以把这套项目的仿真工程当成“第一轮筛子”用千万不要以为仿真通过就等于板子没问题。仿真通过后的打样、焊接、实测永远是一个嵌入式开发者逃不掉的功课。5. 开源工程的可复现性设计目录、BOM、README和许可证代码、原理图、仿真都有了还差最后一步怎么让拿到工程的人花最少的时间把项目跑起来。这步做得好不好直接决定一个开源项目的口碑。5.1 一个别人拿到手就能上手的目录长什么样我见过太多开源项目根目录下面十几个文件夹名字还都是Project1、NewFolder这种。打开压缩包之后第一反应是恐惧。为了避免这种体验我把整个仓库的顶层目录分成五个语义非常清楚01_Hardware_DesignAD格式原理图源文件、原理图PDF、BOM清单02_Firmware_SourceMDK工程源码也就是前面代码部分的整个目录03_SimulationProteus仿真工程04_Datasheets用到的主要元器件数据手册DHT11、SSD1306、STM32F103C8T6等README.md项目说明和快速入门指南BOM清单是很多人忽略的部分。我用Excel表格列出了每个元器件的型号、封装、数量、备注和参考商家甚至标注了哪些器件可以用现成模块替代。这样做的好处是别人想复刻硬件不需要自己对着原理图一个个查封装。5.2 README要写什么、怎么写才有人看README是这个开源项目的门面。我写README的习惯是第一屏必须告诉读者三件事——这是什么、能不能用、怎么用。废话留在后面关键先讲。直接列一下我这份README的提纲项目简介两句话说完能采集什么通过什么显示和传输硬件清单和接线表一张表格列出模块名、引脚连接、注意事项快速开始三个步骤用STM32CubeProgrammer烧录hex文件、用Proteus打开仿真工程、用串口助手查看数据详细文档指向Docs目录里的原理图说明和协议说明常见问题把前面踩过的坑整理成FAQ比如“OLED不亮”、“DHT11读数为零”的排查步骤开源许可证下面单独讲写README最大的忌讳是“什么都想说但什么都没说清楚”。接线表比满篇文字好用一百倍新手照着接就能亮这就是最好的文档。5.3 开源许可证不是随便选的最后必须提一嘴许可证这是很多人忽略但实际很重要的环节。没有许可证的代码仓库法律上默认保留所有权利别人拿你的代码用了严格来说都有侵权风险。这套项目我选择的是MIT License。它足够宽松别人可以自由使用、修改、商用只要保留版权声明即可。MIT许可证对初学者最友好不会劝退想拿代码做毕设或者产品原型的人。如果是希望别人使用后必须开源相同协议的修改代码可以考虑GPL但那就不适合普通分享型开源项目了。许可证文本直接放到仓库根目录并顺手把版权年份和作者名都补上。别嫌这个步骤麻烦真遇到纠纷的时候它就是唯一的保护伞。6. 翻过车才记得住的五个坑真实排查链路分享既然是开源就必须把失败的教训也开源出来。下面这五个坑有些是我前期设计时踩的有些是网友复现时反馈给我之后才定位到的。我把完整排查过程写出来你以后遇到同类问题能少走弯路。6.1 复位电容太大导致下载器连不上第一版板子打样回来焊完最小系统板满怀信心地把ST-Link插上去结果MDK提示“Target not connected”。反复插拔、换线、换下载器都没用。排查链路是这样的先量3.3V供电正常再量复位引脚电平发现NRST被拉到了1.2V左右——不对正常应该接近3.3V。追查复位电路发现我为了“保险”在NRST引脚上放了一个10uF的电容。上电瞬间这个电容充电时间过长导致复位引脚长时间处于低电平ST-Link自然无法建立连接。换回100nF标准复位电容问题立刻消失。这个教训教会我复位电容不是越大越稳100nF是经过大量实践验证的标准值除非有专门的低功耗设计需求否则不要自作聪明去改它。6.2 夹在中断里的DHT11时序这是比较隐蔽的一个问题。DHT11的微秒级时序对延时精度非常敏感我的delay_us已经足够精确但一旦开启了串口接收中断中断处理函数执行期间延时循环就被打断了。传感器侧等待响应超时读取失败率飙升。排查时发现一个规律只要代码里启用串口中断DHT11的读取失败率就从0%升到80%。一开始怀疑是IO冲突逐一屏蔽外设后确认是中断影响。解决思路有两个DHT11读时序期间临时关闭中断读完后恢复。实现简单但不够优雅。用定时器输入捕获模式来精确测量脉冲宽度完全不用阻塞延时。这套项目里最终采用了方案1。因为在读取DHT11的几毫秒窗口内关闭全局中断对系统其他功能几乎没有影响。代码里封装成dht11_read_enter_critical()和dht11_read_exit_critical()两个接口以后想改成方案2只动这一层就行。6.3 原理图里最容易被忽略的网络标号问题画原理图时网络标号的命名随意后来踩了个大坑。我把3.3V的网络标号在不同页里面一个叫VCC_3V3一个叫3V3实际上它们应该是一个网络但AD不会告警因为它们是两个不同的网络名。结果就是芯片的3.3V供电引脚和外设模块的3.3V供电引脚在电气上完全没连通板子回来外设全部不工作。这类问题其实完全可以在画图阶段避免。我的经验是项目开工前先定义好一份网络命名规范比如网络命名规则主电源正极VCC_5VMCU电源VCC_3V3模拟参考地AGND数字地GND复位信号NRST命名规范出来之后在AD里打开ERC检查把所有Unconnected Net和Single Net都扫一遍再发出去打样。别嫌麻烦一颗芯片画错两个网络板子回来的快递费和时间成本够你画十张原理图了。6.4 OLED上电白屏真正的排查起点不是I2COLED显示白屏新手第一反应是改代码换个I2C地址试试。但这个项目里我把排查过程整理成了更高效的链路。首先要确认供电。用万用表量OLED的VCC引脚这个最容易被忽略。我碰到过因为OLED模块上的电源跳线帽没有插上导致模块完全不上电的情况那一瞬间感觉自己花半小时改代码纯属浪费时间。第二步确认I2C地址。SSD1306的默认地址是0x787位模式下是0x3C但如果模块的SA0引脚被拉高地址会变成0x7A。代码里的地址格式是8位还是7位也很容易搞混这一点我的建议是多用HAL库的I2C探测函数扫描总线上所有设备一步到位确认地址对不对。第三步才轮到初始化时序。如果供电和地址都对白屏大概率是初始化序列没按SSD1306要求的时序发送。对比数据手册一段一段核对尤其是ChargePump设置必须开启内部升压才能点亮屏幕。这个排查链路能帮你省下大量无谓的代码审阅时间。硬件问题优先用硬件手段排除不要一上来就改软件。6.5 仿真正常、实物失灵的反向排查先怀疑自己再怀疑Bug仿真通过之后板子回来一跑就出问题这是最让人崩溃的情况。Proteus里明明正常实物却不行大概率是仿真模型“过于理想化”。我遇到过的情况有几十种这里列三个最常见的仿真里OLED点亮实物白屏。原因多半是仿真模型对I2C时序不敏感而实物对上升沿时间有要求。排查办法是先看I2C上拉电阻是否正常其次看时钟频率是否过高。仿真里DHT11能读实物读数为0。这种情况里一半是上拉电阻没焊或者虚焊另一半是传感器数据线接错引脚。用示波器看数据线波形能立即分辨两种情况。仿真里蜂鸣器正常实物声音很小。原因是仿真模型不模拟三极管饱和压降和电流增益的离散性实际三极管的基极电流可能不够。解决方法是减小基极限流电阻阻值或者换一个放大倍数更高的三极管。每次遇到“仿真和实物不一致”我都会提醒自己仿真永远是实物的近似不是实物的替身。排查方向应该是“先怀疑硬件实现和仿真模型的差异”而不是“先怀疑代码有隐藏Bug”。把两者当成互相验证的工具而不是互相替代的方案才能高效定位问题。7. 基于这套工程还能怎么玩扩展方向与后续计划开源项目最怕的是孤岛就是别人拿走了但没有继续生长的能力。这套工程在设计时特意留了几个扩展口后续完全可以基于它二次开发出更多有趣的东西。最简单的一种扩展是换传感器。硬件层的驱动接口是统一的新增一个传感器只需要照着bsp_dht11.c的套路写一组初始化、读取、解析函数然后在App层的数据采集逻辑里加一个分支即可。整个流程不需要动主循环不需要动协议层半小时就可以让新传感器上线。已经有人在我的基础上做了本地化气象站把DHT11换成SHT30增加了一个BMP280气压传感器OLED换成了1.54寸彩屏数据通过ESP8266上传到服务器。他反馈说整个改造过程没有动一层协议代码只在Hardware目录里加了两个驱动文件。听到这个消息我觉得这次开源的价值才真正落地了。后续我计划再补一份详细的硬件焊接与调试视频以及一套基于STM32CubeMX的重新生成教程。前者适合完全没有硬件经验的初学者后者适合想把这套工程移植到其他STM32型号的进阶用户。开源的意义就在于此不是炫耀我做了个什么而是让看到的人都能站在这个基础上做出自己的东西。对我来说真正有价值的东西从来不是代码敲了多少行而是这些代码被别人用起来之后又生出了多少新的可能。这套工程开源的这几天陆续有人提交了Issue有人在讨论区贴出了自己的扩展方案还有人在问能不能增加按键交互。这些反馈让我觉得当初决定把代码、原理图、仿真一起整理出来是一个非常正确的选择。
返回列表