
STM32做环境监测项目说难不难说简单也不简单。这次我把自己做的图书馆环境监测系统完整开源出来包括STM32F103C8T6主控的完整代码、原理图、Proteus仿真工程整套资料可以直接对着复现也可以用在毕设、课程设计甚至产品原型里。这套系统主要解决的是图书馆这类室内公共空间里温湿度、光照、烟雾三类环境参数的实时采集、显示与控制问题采集到的数据会实时显示在OLED屏上同时根据阈值自动控制风扇、补光灯和声光报警器整个过程不依赖上位机独立运行。这套硬件方案还把传感器选型、继电器驱动、ADC采样链路都讲清楚了仿真部分也做了完整的适配说明。说实话图书馆环境监测这个题目非常适合作为STM32从入门到进阶的练手项目传感器种类多GPIO、ADC、I2C、定时器、中断几乎全用上了代码逻辑又不算复杂一个人两三周完全可以拿下。下面我把整个项目从设计思路到硬件原理图、再到代码实现和仿真验证的完整过程尽量用说人话的方式讲明白。1. 项目概述与核心设计思路1.1 为什么做“图书馆环境监测”最早是朋友在高校图书馆做馆员跟我抱怨纸质书库的温湿度控制全靠人工经验空调开不开、除湿机什么时候启动没有一个量化依据。北方冬天湿度能掉到20%以下纸张发脆南方梅雨季湿度能飙到80%以上书页会发霉、起皱。更别说古籍特藏库对光照和温湿度的要求远高于普通阅览区。所以表面上看这是个嵌入式项目本质上是在解决一个真实的空间管理痛点。顺着这个场景想下去你会发现需求其实非常清晰第一要能实时采集温湿度、光照强度和烟雾浓度第二要在本地直观显示这些数据第三要有自动控制能力超温就开风扇、光线不足就补光、烟雾浓度异常就声光报警。这三条需求正好对上了一套典型的传感器采集加执行器控制的嵌入式闭环系统。更重要的是这套方案不只适用于图书馆档案馆、机房、实验室、仓库、恒温恒湿鱼缸、农业大棚这些场景改一改传感器参数和阈值就能直接复用。1.2 系统架构与硬件选型主控我选了STM32F103C8T6Cortex-M3内核72MHz主频64KB Flash、20KB SRAM40引脚封装三四块钱一片资料多到看不完CubeMX还能一键生成初始化代码。对于这个项目来说性能冗余很大但正是因为冗余后面想加无线模块、加屏幕、加按键都会很从容。传感器选型上温湿度用的是DHT11光照用光敏电阻模块烟雾用MQ-2。这三个模块几乎是最经典、最便宜的入门组合加起来不到十块钱却能把环境监测系统该有的几类物理量都覆盖到。当然你也完全可以把DHT11升级成SHT30把光敏模块换成BH1750数字光照传感器代码结构上我做了模块化封装换传感器只需要改底层驱动文件上层逻辑完全不用动。系统整体架构分为四层传感器采集层、主控处理层、显示与交互层、执行器控制层。采集层负责把温湿度、光照、烟雾浓度转换成电信号主控层负责数据的读取、滤波、标定和逻辑判断显示层用0.96寸OLED实时刷新数据执行层用继电器控制风扇、补光灯用蜂鸣器做报警。这个分层思路是嵌入式项目能不能快速迭代的关键后面扩功能、换传感器、加通信都是往对应层里填代码不会牵一发动全身。项目开源文件里我把文档、硬件、软件、仿真分别放在Docs、Hardware、Firmware、Simulation四个目录下。Hardware下面是立创EDA格式的原理图和PCB文件转成PDF也放了一份Firmware是完整的Keil工程Simulation是Proteus工程文件可以直接打开跑仿真。整个项目的定位是“开箱即用”但是我希望拿到这套资料的读者不要只跑一遍演示就丢在一边能把每个模块的实现逻辑吃透才算真正有收获。2. 硬件设计原理图与PCB要点2.1 最小系统电路与传感器接口设计STM32F103C8T6的最小系统不复杂但是每一颗电阻电容都有讲究。8MHz晶振配两个20pF负载电容复位引脚接10k上拉电阻和104电容到地BOOT0引脚直接下拉到GND让芯片从Flash启动电源部分在VDD引脚就近放104去耦电容再加一个10uF钽电容做低频滤波。这里我特别说一句很多同学画原理图习惯把所有电容堆在一个角落这在低速数字电路里可能没问题但在ADC采样和继电器动作的项目里电源去耦不到位会导致采样值跳来跳去。DHT11的数据引脚接在PB12上这里有两个细节容易被忽略。第一DHT11是单总线协议数据线是开漏结构必须外接4.7k到10k的上拉电阻否则读出来的数据永远是0xFF。第二主控引脚要配置成开漏输出模式这样在输出高电平时引脚实际是被上拉电阻拉高的符合单总线释放总线的逻辑。我见过不少人在GPIO模式上翻车用推挽输出去模拟单总线短时间能工作但遇到长线或干扰就容易时序错乱。OLED用I2C接口挂到PB6和PB7上这是STM32F103的I2C1引脚。SSD1306驱动芯片正常工作需要有上拉电阻4.7k比较合适。OLED模块上一般已经焊了上拉但如果用杜邦线延长超过10厘米建议在主控端再补一组上拉否则屏幕偶尔会花屏。MQ-2模块的处理要格外小心。这个模块内置了一个LM393比较器可以输出数字信号DO同时还有一路模拟电压AO。我的接法是AO接PA1做ADC采样DO接PB1做快速阈值中断输入。这里有一个很多人踩过的坑MQ-2模块如果5V供电AO输出范围是0到5V而STM32的ADC输入范围是0到3.3V直接接上去轻则采样值长期满偏重则烧坏引脚。我在原理图里加了一组分压电阻R1取20k、R2取33k5V经过分压后最大输出电压约为3.1V给ADC留了安全余量。计算过程很简单Vout5×(33/(2033))≈3.11V。2.2 ADC采样信号链路的完整性ADC采样看起来就是把传感器输出接到PA1引脚但实际工程里信号链路的完整性对测量精度影响很大。首先所有模拟信号的地线要统一接到一个模拟地网络再通过单点连接的方式接到数字地上这样继电器、蜂鸣器工作时产生的地弹噪声不会直接叠加到传感器信号上。其次传感器供电建议从3.3V经过一个LC滤波后再供给避免DHT11和光敏模块之间通过电源轨互相干扰。光敏电阻模块的接线简单AO接PA2DO接PB0。这类模块有个特点是输出会受供电电压影响供电稳不稳直接决定测量值可不可信。我实际测试过用USB供电和用独立5V适配器供电光敏模块的输出电压在同一光照下能差出0.3V左右。所以正式版原理图里我用了AMS1117-3.3稳压芯片5V适配器进来先经过稳压再给传感器供电效果好了很多。执行器驱动部分继电器不能直接接在GPIO上原因很简单STM32引脚最大输出电流约20mA而继电器线圈吸合电流通常需要50到100mA直接驱动会烧引脚。原理图里我用了一个S8050三极管做放大GPIO通过1k电阻控制基极线圈反向并联1N4007续流二极管。这个二极管非常关键继电器断电瞬间会产生一个反向电动势没有续流二极管的话轻则导致主控复位重则击穿三极管。蜂鸣器同样采用了三极管驱动用PNP管做低电平触发和继电器的NPN方案错开方便软件上做逻辑区分。电源部分我统一从5V适配器取电然后分成两路一路直接给继电器和蜂鸣器的驱动级供电另一路经过AMS1117降到3.3V给主控和传感器供电。继电器驱动级和逻辑级电源分开是为了避免继电器吸合瞬间的大电流把3.3V拉垮这个经验是从实际故障里总结出来的。2.3 PCB布局与打板经验原理图画完之后转PCB布局有几个原则性建议。传感器接口器件放在板子边缘方便接外设继电器放在板子一角远离DHT11防止继电器线圈的磁场和热量干扰温湿度测量蜂鸣器不要贴近晶振否则蜂鸣声的机械振动会传导给晶振导致主频抖动。DHT11本身建议焊接成插件并把感应头露出PCB之外不要紧贴PCB表面这样可以减少板上其他器件发热带来的温漂。电源走线要加粗至少40mil以上。模拟信号线尽量短避免和继电器控制线并行。我在第一版PCB上因为贪图走线方便把ADC输入线绕到了继电器下面结果继电器一动作ADC采样值就跳几十个LSB。后来重新拉线把模拟线缩短并包地处理问题就消失了。打板的话建议用立创EDA直接下单5块钱5片一周内到货。板子拿到手先别上芯片用万用表量一下3.3V对地有没有短路再量一下各关键节点的电压是否正确。我习惯先焊电源部分确认稳压输出正常后再焊主控最后接传感器和外设这样排查问题范围会小很多。3. 软件架构与核心代码实现3.1 工程结构与主流程设计软件工程用STM32CubeMX加Keil MDK搭建HAL库版本1.8.0以上即可。CubeMX里需要配置的资源有GPIO、ADC1、I2C1和SysTick定时器。DHT11的单总线时序我放在普通GPIO上直接模拟没有用额外的定时器输入捕获因为DHT11的时序宽度是微秒级别SysTick延时足够了。工程代码按功能模块拆分每个模块一个.c和.h文件dht11.c负责温湿度读取oled.c负责屏幕显示adc_util.c负责多通道ADC采样和滤波control.c负责阈值判断和控制逻辑bsp_led.c和bsp_buzzer.c负责指示灯和报警器。这样一个典型的嵌入式工程结构好处是编译时间短逻辑清晰遇到问题能快速定位到具体文件。主循环的逻辑非常简单伪代码如下while (1) { // 读取温湿度DHT11两次读取间隔至少1秒 if (s_tick - last_dht11_read 1000) { dht11_read(humidity, temperature); last_dht11_read s_tick; } // 每200ms采集一次ADC并做均值滤波 if (s_tick - last_adc_sample 200) { smoke_voltage adc_util_read_avg(SMOKE_ADC_CH); light_voltage adc_util_read_avg(LIGHT_ADC_CH); last_adc_sample s_tick; } // 刷新OLED显示 if (s_tick - last_display 500) { oled_show_all(temperature, humidity, smoke_voltage, light_voltage); last_display s_tick; } // 执行控制逻辑 control_update(temperature, humidity, smoke_voltage, light_voltage); }这里有一个很重要的设计决策所有外设的读取和刷新都放在主循环里用时间片轮询方式调度不用复杂的RTOS。因为系统状态机很简单任务只有四五个用时间片轮询足够稳定还能避免多线程带来的共享资源竞争问题。等以后要加网络通信、加多级菜单交互再迁移到FreeRTOS也不迟。3.2 DHT11单总线驱动细节讲解DHT11是整个项目里最容易让人抓狂的外设核心在于单总线协议对时序要求比较严格。读数据的完整过程是主机先把数据线拉低至少18ms让DHT11识别到开始信号然后释放总线并延时20到40us再切换成输入模式等待DHT11的响应。DHT11正常会先拉低80us表示响应然后再拉高80us准备发送数据。紧接着就是40位数据每位数据都由一个50us的低电平和一段高电平组成高电平持续26到28us代表逻辑0持续70us代表逻辑1。40位数据分别是湿度整数、湿度小数、温度整数、温度小数和校验和校验和的规则是前四个字节相加取低八位如果等于校验字节则数据有效否则丢弃本次读取。DHT11读取的完整驱动函数如下uint8_t DHT11_ReadData(uint8_t *humidity, uint8_t *temperature) { uint8_t buf[5] {0}; uint8_t i, j; // 主机拉低至少18ms触发DHT11发送数据 HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); delay_ms(20); HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); delay_us(30); // 切换为输入模式等待DHT11应答 GPIO_InitTypeDef gpio {0}; gpio.Pin DHT11_PIN; gpio.Mode GPIO_MODE_INPUT; gpio.Pull GPIO_PULLUP; HAL_GPIO_Init(DHT11_PORT, gpio); // 等待低电平响应超时返回失败 if (WaitPinLevel(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET, 100) ! HAL_OK) { SetDHT11OutputMode(); return 1; } if (WaitPinLevel(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET, 100) ! HAL_OK) { SetDHT11OutputMode(); return 2; } if (WaitPinLevel(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET, 150) ! HAL_OK) { SetDHT11OutputMode(); return 3; } // 依次读取8个字节里的每一位 for (j 0; j 5; j) { for (i 0; i 8; i) { while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_RESET); delay_us(40); if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET) { buf[j] | (0x80 i); } while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) GPIO_PIN_SET); } } // 恢复为输出模式准备下一次读取 SetDHT11OutputMode(); // 校验和验证 if ((uint8_t)(buf[0] buf[1] buf[2] buf[3]) buf[4]) { *humidity buf[0]; *temperature buf[2]; return 0; } return 4; }这段代码踩过的坑我总结成三条。第一读取完数据后一定要把GPIO恢复成输出模式并拉高否则下次读取时总线电平不确定DHT11不会应答。第二微秒级延时函数在设计时要注意如果使用HAL_Delay的话最小单位是1ms完全不够用需要自己基于SysTick写一个delay_us函数。第三等待电平跳变时一定要加超时机制因为DHT11一旦损坏或者接线不良read函数会卡死在while循环里整个系统就像死机了一样。我加了一个带计次上限的等待函数超时直接返回错误码问题就好排查多了。3.3 OLED显示与ADC多通道采样OLED驱动基于经典的SSD1306我用的是I2C接口方式。初始化流程包括打开显示、设置显示时钟分频、设置复用率、设置显示偏移、开启显示等一般直接用成熟的驱动库代码就行。显示内容上第一行显示温度和湿度第二行显示烟雾浓度和光照状态第三行显示系统运行状态。OLED显示频率不需要太快500ms刷新一次足够因为人眼对屏幕内容的变化感知在100ms级别再快只会浪费CPU周期。我还加了两个小设计超温时屏幕上显示的温度会加上感叹号报警状态时屏幕会变亮这些细节通过改变OLED的对比度寄存器实现。ADC部分STM32F103的ADC1可以配置多个通道我用的是PA1采烟雾电压、PA2采光照电压。每次采样连续采集10次去掉最大值和最小值后取平均这样处理之后采样值非常稳定。ADC采样时间配置为55.5个周期虽然在快速变化的信号上这个采样时间偏慢但对于烟雾和光照这类缓变信号完全够用。电压计算公式为voltage adc_value × 3.3 ÷ 4095这个4095对应12位ADC的最大值很多新手容易写成4096虽然数值上差别不大但严格的转换应该用4095。3.4 控制逻辑与阈值设置控制逻辑是整个系统的灵魂。如果只是简单地对阈值做比较继电器会在临界点附近频繁吸合断开不仅噪声大还会缩短继电器寿命。我的做法是加入滞回控制也就是到达上限后必须低于下限才会复位中间留出2℃左右的回差。比如温度控制温度升到30℃开启风扇降到28℃才关闭风扇这中间的2℃回差就把振荡问题消除了。烟雾报警的逻辑更严格一些。因为MQ-2传感器刚上电的前几分钟输出会漂移所以我做了一个开机预热流程系统启动后前60秒不参与报警判断只更新显示同时记录当前烟雾电压作为零点基准。正常运行时的报警阈值是“零点电压0.5V”这样在不同环境下都能自适应。光照控制相对简单光照电压低于某个阈值就打开补光灯高于阈值再关闭也做了0.3V的回差处理。控制输出的实现全部由control.c模块统一管理传感器读取和输出控制分层隔离。这样做的好处是如果以后想改控制策略比如温度改成PID控制风扇转速只需要改control.c其他模块完全不用动。4. Proteus仿真搭建与验证4.1 仿真工程搭建步骤完整的Proteus仿真工程我放在了Simulation目录下这里说一下搭建过程。Proteus版本建议8.9以上我用的是8.13。新建工程后在元件库里搜索以下几个关键元件STM32F103C8、DHT11、LM016L、POT-HG、LED-BIBY、SOUNDER、RESPACK。一个让很多人困惑的点是Proteus里没有SSD1306的I2C OLED模型。我仿真方案里用的是LM016L这个字符型LCD来替代OLED代码里做了一个显示驱动抽象层通过宏定义切换#define DISPLAY_USING_OLED 1 #define DISPLAY_USING_LCD 0仿真工程里把宏改成使用LCD就能显示同样的内容。这样做有个额外的好处验证了显示驱动层的可移植性以后换屏幕器件成本很低。Proteus中STM32F103C8的模型需要导入.hex文件才能仿真。先通过Keil编译工程生成hex然后在Proteus里双击芯片在Program File一栏选择生成的hex文件。Crystal Frequency填8000000对应板上的8MHz晶振。POT-HG电位器用来模拟烟雾传感器和光敏电阻的模拟电压输出通过调整电位器旋钮就可以改变ADC输入电压从而模拟环境参数变化。4.2 仿真代码适配与易踩的坑仿真环境下的代码和实物代码有几个关键差异。第一仿真中不需要处理DHT11的上拉电阻Proteus模型内部已经处理好了所以如果你在实物上遇到DHT11读取失败不要直接怀疑仿真逻辑。第二仿真中GPIO翻转速度远快于实物我曾经遇到过因为延时函数写得不准确、在仿真里能跑而实物不工作的情况所以代码里的所有延时我都用了基于SysTick的微秒级延时函数保证时序一致。第三Proteus仿真STM32的ADC输入范围要注意仿真中如果给ADC引脚接入超过3.3V的电压采样值会饱和为4095不会烧毁仿真芯片这会掩盖实物的分压问题。因此我在仿真中特意保留了ADC输入分压电路确保仿真流程和实物一致。通电仿真时还要注意一个性能问题Proteus仿真STM32本身比较吃CPU如果打开实时帧率模式又会变慢。建议把Proteus的帧率限制在每秒20帧仿真运行速度反而更稳定。另外如果仿真过程中DHT11读不到数据可以先检查芯片有没有导入hex文件很常见的错误是把hex导入了LCD模型上。4.3 仿真验证流程与测试用例仿真工程的验证流程我按五步走。第一步运行Keil工程编译生成hex确认0错误0警告。第二步在Proteus中加载hex并启动仿真观察LCD屏幕上温度和湿度是否正常显示。第三步拖动温湿度传感器的滑动条改变数值观察显示是否同步更新。第四步调整烟雾电位器的输出电压观察报警器和蜂鸣器是否触发。第五步调整光照电位器观察补光灯的开关逻辑是否正常。我习惯用表格把每一个测试场景记录下来方便对比和排查测试场景操作方式预期结果实测结果正常运行上电后观察温湿度、电压值刷新正常2秒内显示稳定超温报警温度变量设为32℃风扇开启屏幕提示温度高风扇动作正常烟雾报警调整烟雾电位器超过阈值蜂鸣器长鸣报警灯亮1秒内进入报警态光照不足调整光照电位器低于阈值补光灯打开补光灯正常阈值恢复反向调整各变量各执行器恢复正常回差逻辑有效从仿真结果来看整个系统的逻辑链路是完整的传感器、主控、执行器闭环能够在Proteus环境中正确响应。但我也要说句实话仿真通过只代表逻辑层没有问题不代表实物可以高枕无忧。4.4 仿真与实物的差异分析仿真永远替代不了实物验证。DHT11在仿真里是一个理想模型没有任何电气噪声和信号完整性干扰但真实的DHT11会被电源纹波、线缆长度、上拉电阻阻值甚至空气流动影响。ADC采样在仿真中极其干净而实物的模拟信号会叠加各种干扰。继电器在仿真中只是一个符号但实物继电器动作瞬间的电流冲击和磁场干扰是真实存在的。所以我的建议是先用仿真验证逻辑再用实物验证时序和信号完整性。仿真阶段主要发现逻辑设计问题实物阶段主要发现电路设计问题。两者的关系是互补不是替代。5. 常见问题与调试技巧实录5.1 传感器与显示问题速查做这个项目遇到问题最多的集中在三个模块我把典型现象和解决方法整理成表格故障现象可能原因解决方案OLED屏不亮或白屏I2C地址不对或初始化时序问题扫描I2C地址确认器件地址0.96寸OLED通常是0x3COLED偶尔花屏I2C上拉电阻过小或线路过长检查上拉电阻杜邦线长度控制在10cm内DHT11读到的湿度永远是0引脚上拉电阻缺失或GPIO模式不对确认DHT11数据引脚有4.7k到10k上拉引脚配置为开漏输出ADC采样值反复跳变电源纹波大或采样未加滤波改用均值滤波检查3.3V去耦电容是否靠近引脚继电器频繁吸合断开阈值没有滞回区间代码中增加2℃或0.3V的回差处理MQ-2刚上电报警传感器预热期间输出漂移增加开机预热60秒期间禁用报警逻辑蜂鸣器声音很小驱动三极管基极电阻偏大基极电阻改为1k确保蜂鸣器工作电流足够遇到问题第一步永远是把现象描述清楚第二步缩小范围。比如OLED不亮先用逻辑分析仪看I2C总线上有没有波形再看应答位是否正确最后再看代码初始化顺序一层层排查比拿着万用表乱戳效率高得多。5.2 继电器干扰与电源稳定性这个项目里最典型的复杂环境干扰来自继电器。继电器吸合瞬间的浪涌电流会通过电源轨传导到传感器导致ADC采样值跳变、OLED闪烁。我实测过几次解决办法有三个。第一继电器驱动级独立供电不要把继电器电源和传感器电源用同一个LDO。第二继电器线圈必须并联续流二极管这个是原理图层面的硬性要求。第三主控程序和ADC采样要避开继电器动作的瞬间可以在控制函数发出继电器切换指令后延时20ms再进行ADC采样这个思路叫软件避让。如果想让系统更稳定可以把继电器换成固态继电器或者MOS管开关。固态继电器没有机械触点不会产生电弧干扰但是成本和导通压降会高一些。对于图书馆环境监测这种每天动作不是特别频繁的场合普通电磁继电器加续流二极管完全够用。5.3 开源工程目录说明与二次开发建议整个开源工程我按下面的目录组织LibraryEnvMonitor/ ├── Docs/ # 项目文档、芯片手册、README ├── Hardware/ │ ├── Schematic/ # 立创EDA/PDF格式的原理图 │ └── PCB/ # PCB源文件和Gerber文件 ├── Firmware/ │ ├── Core/ # CubeMX生成的启动代码 │ ├── Drivers/ # HAL库驱动 │ └── User/ # 用户代码dht11、oled、adc、control ├── Simulation/ │ └── proteus/ # Proteus仿真工程文件 └── README.md # 项目说明和快速上手指引拿到工程后建议先从README开始看里面我写了快速上手的五步流程打开原理图理解电路连接、打开CubeMX工程看引脚配置、打开Keil工程编译烧录、打开Proteus跑仿真、按调试手册排查问题。如果只是复制粘贴代码不把引脚和电路对应起来看遇到问题会非常被动。二次开发方向上我给出三个思路供参考。第一把显示模块换成带触摸的TFT屏幕增加按键设置菜单可以实现在设备端直接修改报警阈值。第二增加ESP8266或ESP32模块通过串口把数据发到MQTT服务器手机端就能实时查看环境数据这是目前物联网方向最常见的做法。第三接入低功耗模式使用STM32的STOP模式配合RTC定时唤醒可以让设备用锂电池撑很久适合在档案馆这种不方便布线的场景部署。5.4 关于代码可维护性的一点心得代码的可维护性往往比代码本身能不能跑更重要。从一开始写这个项目我就坚持每个功能模块一个源文件、每个源文件对外只暴露必要的接口。比如主循环只调用dht11_read()获取数据根本不需要关心DHT11内部时序怎么实现的。这样有两个好处一个是排查问题范围小另一个是换硬件时只需要改底层实现上层逻辑完全复用。CubeMX生成的代码我尽量不手动修改所有用户代码都放在User目录下。因为CubeMX重新生成工程时会覆盖Core目录下的代码如果把用户代码混在里面重新生成一次就全丢了。这是很多开源项目令人头疼的问题我见过太多人改完CubeMX生成的main.c然后重新生成后所有修改全部消失。代码里我还加入了串口打印调试信息的功能USART1通过9600波特率输出日志。调试阶段串口打印变量值非常有用比看OLED屏幕直观。量产阶段可以把日志功能关掉把CPU资源省下来。最后说一个我在实际调试中的心得这个项目我在仿真环境里跑通只花了两天但在实物上调试DHT11和ADC花了一周。后来总结发现问题基本集中在引脚模式配置、上拉电阻、延时精度这三个方面。所以如果你在复现时遇到问题优先检查这三个点八成问题都能解决。做嵌入式就是这样原理图、代码、仿真、实物每一层都会有惊喜但解决一个问题的过程就是对这个系统理解加深一层的过程。希望这套开源资料能帮你少踩几个坑把更多时间花在真正有意思的功能扩展上。