
做嵌入式这些年陆陆续续做过不少小项目但真正让我觉得“可以拿得出手开源”的粮仓环境安防监测系统算一个。这个项目我最初是给一个做农业仓储的朋友做的原型验证后来整理成了一套完整的开源资料包含STM32工程代码、原理图PDF、Proteus仿真工程和接线说明文档。不管你是刚学完STM32基础、想找个综合项目练手的在校生还是想快速搭一套环境监测原型的工程师这套东西都能直接拿来改、拿来跑。今天我把整个项目的设计思路、硬件电路要点、软件实现细节、仿真搭建过程以及我在调试过程中踩过的坑一次性写清楚。先说这个项目能干什么。它围绕“粮仓环境安全”这个核心场景实现了五个维度的监测和响应环境温湿度实时采集、烟雾浓度检测、火焰/明火侦测、人体闯入感应以及对应的声光报警和排风扇联动控制。数据会同步显示在一块0.96寸OLED屏上按键可以调节报警阈值。整套系统的主控是STM32F103C8T6传感器用了DHT11、MQ-2、火焰传感器和HC-SR501人体感应模块输出端接了蜂鸣器、LED指示灯和继电器控制的排风扇。这些模块都是市面上最常见的型号成本低、资料多非常适合学习和二次开发。1. 项目概述与方案设计思路1.1 粮仓环境监测到底要解决什么问题粮仓存储最怕三件事受潮发霉、高温自燃、虫害和人为闯入。受潮发霉和温度过高是缓慢变化的过程靠人工巡检很难及时发现而火灾和闯入则是突发情况一旦发生损失就是灾难性的。所以一套合格的粮仓安防系统必须同时具备“持续监测”和“快速告警”两种能力。持续监测针对温湿度这类缓变量要能够定时采集、实时显示、超限报警快速告警针对烟雾、明火、人体入侵这类突变信号要做到毫秒级响应并联动外部设备比如切断电源、打开排风扇、拉响警报。在实际项目中还有一个容易被忽略的需求成本必须可控。粮仓往往不止一个可能是一片仓库群每个仓位都部署一套系统的话单套硬件成本超过两三百块就很难推广。这也是我选择STM32F103C8T6作为主控的原因之一——这颗芯片本身只要几块钱外围电路也极其简单整套硬件物料成本可以压在七八十元以内。对于学生做课程设计、创客做农业物联网原型或者小型仓储做试点部署这个成本都是可以接受的。1.2 为什么选用STM32F103C8T6STM32F103C8T6是意法半导体Cortex-M3内核的经典芯片72MHz主频64KB Flash20KB SRAM片上资源对于这个项目来说绰绰有余。我选它还有几个具体的理由第一ADC模块有2个每个支持10个通道采集MQ-2的模拟电压、预留扩展传感器都很方便第二I2C、USART、SPI等常用外设齐全接OLED屏、接ESP8266做数据上云都有现成接口第三3.3V供电、5V兼容的逻辑电平设计让它可以直接驱动5V供电的传感器模块不用做电平转换第四也是最重要的一点它的资料和学习生态太丰富了Keil环境配置、固件库函数、Hal库例程随便搜就是一大堆遇到问题很容易找到参考。这里顺便说一句选型思路很多初学者喜欢一上来就选最新最贵的芯片其实对于这种以传感器采集和逻辑控制为主的项目F103C8T6这种“老将”反而是最稳的选择。它不像某些新芯片那样需要折腾编译器和烧录工具链也不存在库函数找不到的烦恼。我见过不少同学用HAL库加CubeMX生成工程结果卡在时钟配置和引脚复用上换回标准外设库或者直接寄存器操作问题反而简单了。这个项目我最终用标准外设库SPL写的代码量不大逻辑清晰也方便新手对照数据手册逐行理解。1.3 系统整体架构与工作流程整套系统的工作流程可以概括为传感器采集数据 → 主控逻辑判断 → 显示与告警联动。温度湿度由DHT11单总线读取烟雾浓度由MQ-2输出模拟电压、经过STM32的ADC1通道采样换算火焰信号由数字量输出的火焰传感器检测人体感应由HC-SR501直接输出高低电平。主控每500毫秒刷新一次传感器数据把处理结果通过I2C发送到OLED屏显示同时持续比较当前值和设定阈值一旦超过阈值就拉高蜂鸣器引脚、切换LED状态、控制继电器吸合排风扇。这里有一个设计上的细节值得展开说报警阈值不能写死必须支持按键调节。为什么因为不同粮仓的容积、堆粮高度、通风条件都不同同样的烟雾浓度绝对值在不同场景下风险等级完全不同。所以我在代码里把温度、湿度、烟雾三个阈值都定义成了可修改的全局变量配合两个按键设置键、加/减键循环调整并把阈值显示在OLED屏的设定界面中。这个设计看似简单却让系统从“固定场景专用”变成了“可现场配置的通用方案”。2. 硬件电路与原理图设计要点2.1 最小系统电路设计STM32F103C8T6的最小系统包含四部分供电电路、时钟电路、复位电路和下载调试电路。供电电路我用的是USB 5V输入经过AMS1117-3.3稳压芯片降到3.3V给主控供电同时在输入端和输出端各加一个10uF电解电容和0.1uF瓷片电容去耦。这里的电容选择不是随便放的大电容负责储能和稳定电压小电容负责滤除高频噪声两者配合才能保证MCU在传感器电机启停、继电器吸合瞬间不出现电压跌落导致的复位。时钟电路用的是典型的8MHz无源晶振加两个20pF负载电容给HSE提供振荡源系统时钟通过PLL倍频到72MHz。复位电路就是10K上拉电阻加一个0.1uF电容按键接地触发复位。下载电路我用了一个10针的SWD接口只接了SWDIO、SWCLK、GND和3.3V四根线用ST-Link V2下载调试。这里要提醒一下很多人做最小系统时容易忽略BOOT0引脚的设置我直接通过10K电阻把BOOT0拉低让它从Flash启动省去了跳线帽的麻烦。2.2 传感器与执行器接口电路DHT11需要一根数据线连接MCU的PA6引脚在数据线上接一个4.7K上拉电阻到3.3V。因为DHT11输出的是开漏信号这个上拉电阻是必须的不接的话通信时序会不稳定经常出现读取出错。MQ-2模块的模拟输出接PA1ADC1通道1数字输出DO也预留了PA2引脚接法方便用户切换使用方式。火焰传感器模块的数字输出接PB0HC-SR501人体感应模块的输出接PB1。这些模块都是现成的传感器小板板上自带比较器和电位器调节灵敏度所以接口电路很干净主要就是把VCC、GND、DO、AO对应接好。执行器部分要注意继电器的驱动。STC的ULN2003或者S8050三极管都可以我这里用了S8050三极管搭建的驱动电路MCU引脚通过1K电阻接三极管基极发射极接地集电极接继电器线圈线圈两端反向并联一个1N4148续流二极管。这个续流二极管非常关键继电器线圈在断电瞬间会产生反向感应电动势不加二极管的话很容易击穿三极管和MCU引脚。很多新手在这里翻车以为继电器直接接引脚就能转结果一吸合就复位就是没处理反向电动势。蜂鸣器我用的是有源蜂鸣器模块同样通过三极管驱动低电平触发接在PB12引脚。OLED屏用的是I2C接口的四脚版本SDA接PB7SCL接PB6供电3.3V。这里把关键引脚分配整理成一张表方便对照接线外设/传感器接口类型信号引脚说明DHT11单总线PA6需要4.7K上拉电阻MQ-2AO模拟量PA1ADC1通道1MQ-2DO数字量PA2预留备用火焰传感器数字量PB0低电平表示检测到火焰HC-SR501数字量PB1高电平表示有人OLEDI2CI2C1PB6/PB7地址0x78蜂鸣器数字输出PB12低电平触发LED指示数字输出PB13高电平点亮继电器数字输出PB14高电平吸合按键设置数字输入PA4按下为低电平按键调节数字输入PA5按下为低电平2.3 原理图设计中的几个关键细节原理图画起来不难但有三个地方我想单独拎出来说这些是我实际画图时反复改过的点。第一是电源树的规划。整套系统有5V和3.3V两个电压域传感器模块大部分是5V供电而MCU、OLED、蜂鸣器是3.3V。如果所有传感器都接5VMQ-2的AO输出最大电压是5V直接进ADC可能超过3.3V参考电压导致采样失真。所以我给MQ-2的AO输出加了一个10K和20K电阻组成的分压电路把最大电压缩到3.33V再用一个电压跟随器或者直接接一个0.1uF滤波电容进ADC。这个分压比例要按实际传感器模块的最大输出电压算我用的模块最大输出约5V所以选了1:2分压。如果你们的模块输出电压不同这个分压电阻值要重新算。第二是按键电路的消抖问题。很多教程里STM32按键都只接一个上拉电阻加GND靠软件延时消抖这在教学场景没问题但在这个项目里我做了硬件消抖——按键并联一个104电容同时软件里保留20ms延时消抖。为什么加电容因为这个系统有继电器和蜂鸣器它们动作时会产生电磁干扰如果按键没有硬件滤波有可能在电磁干扰瞬间被误触发导致阈值被乱改。加了104电容后误触发的概率大大降低。第三是防止传感器热插拔损伤。粮仓环境中传感器线路会走线较长现场调试时经常需要带电拔插传感器。我在DHT11和MQ-2的信号线上预留了ESD保护二极管阵列用BAT54S和串联的100欧姆电阻。ESD器件能吸收静电放电能量串联电阻则限制了插拔瞬间的浪涌电流。这个设计在普通桌面上调试时感受不到作用但真到了现场接线、多次插拔的情况下它能保护MCU引脚不被打坏。2.4 布线布局与PCB注意事项如果只做仿真原理图就够了但要做实物的话PCB布局有几点经验值得分享。我把MCU放在板子中央左侧排布传感器接口右侧排布电源和继电器区域OLED屏接口放在板边方便屏幕外接安装。电源线和地线走线宽度至少1mm信号线0.3mm继电器和蜂鸣器附近的地线单独走避免大电流回灌干扰MCU模拟采样。数字地和模拟地用0欧姆电阻单点连接这是减少ADC采样噪声的经典做法。还有一个容易踩坑的地方是OLED屏和蜂鸣器不要放得太近。OLED的I2C信号是高阻抗的数字信号蜂鸣器启动瞬间的电流变化会产生电磁辐射如果走线平行且靠得很近OLED显示会出现花屏。我的做法是在PCB上把OLED的I2C走线包地处理并在OLED电源引脚处加一个10uF电解电容。实测下来蜂鸣器工作期间OLED依然能稳定显示没有出现水波纹或雪花点。3. 软件代码实现与核心逻辑3.1 工程结构与模块划分这套代码我按功能模块拆成了几个文件main.c负责主循环和状态机调度dht11.c封装温湿度读取adc.c处理MQ-2烟雾采样oled.c驱动OLED显示buzzer.c和relay.c分别管报警和继电器控制threshold.c管理阈值存储和按键调节。每个模块提供简洁的函数接口比如DHT11_Read_Temperature()、ADC_Get_SmokeValue()、OLED_Display_Update()、Buzzer_Alarm_On()。这样划分的好处是你想把这个系统改成“鸡舍环境监测”或者“机房温湿度报警”只需要改main.c里的业务逻辑传感器驱动基本不用动。工程根目录下我还放了一个README详细说明了Keil MDK的版本要求、库文件路径、烧录步骤和接线说明。用Keil打开工程文件后记得先在Options for Target里检查芯片型号是否为STM32F103C8然后在Utilities设置里选好ST-Link或J-Link的Flash算法不然下载会出现“No Algorithm found”报错。3.2 DHT11温湿度数据读取DHT11的驱动是整个项目里时序最麻烦的部分但它真的很适合用来理解单总线通信协议。DHT11每次通信发送40位数据8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和。校验和的算法是前四个字节相加取低8位如果相等说明数据有效。这套代码里我严格按数据手册的时序要求写了延时主机拉低总线至少18ms启动通信然后释放总线20~40usDHT11响应后先拉低80us再拉高80us之后每位数据是50us低电平加26~28us表示0或70us表示1的高电平。读取时用读取GPIO电平配合定时器计数来区分0和1实现方法如下uint8_t DHT11_ReadByte(void) { uint8_t i, byte 0; for (i 0; i 8; i) { while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) RESET); // 等待50us低电平结束 delay_us(40); // 延时到高电平中段 if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) SET) byte (byte 1) | 1; else byte (byte 1); while (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_6) SET); // 等待高电平结束 } return byte; }这部分有两点要特别注意。第一DHT11对时序非常敏感延时函数必须准确建议用SysTick做微秒延时而不是简单的for循环空转因为编译优化会改变空循环的耗时。第二DHT11两次读取间隔至少1秒频繁读取会导致传感器内部温度升高数据反而失真。我在主循环里加了标志位每500ms读一次实际间隔足够。3.3 烟雾浓度采集与ADC处理MQ-2传感器在洁净空气中的输出电压约为0.1~0.3V在烟雾环境中会升高到2~3V经过分压电路后进入ADC的电压范围是0~3.3V。代码里我用ADC1的规则通道组轮询采集PA1引脚的电压值转换成12位数字量。为了提高稳定性我做了连续采样16次、去掉最大和最小值后取平均的软件滤波。这个滤波方法虽然简单但对MQ-2这种输出带有随机波动的传感器来说很有效能滤掉大部分的毛刺。uint16_t ADC_GetSmokeValue(void) { uint32_t sum 0; uint16_t sample[16]; uint8_t i; for (i 0; i 16; i) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); sample[i] ADC_GetConversionValue(ADC1); delay_ms(1); } // 简单冒泡排序去掉最大最小值实际项目中建议用插入排序或者直接排序后取中段 for (i 0; i 15; i) { // 省略排序代码实际工程有完整实现 } for (i 0; i 14; i) sum sample[i]; return sum / 14; }ADC计算出的数值并不能直接代表烟雾浓度因为MQ-2的响应曲线是近似对数关系。所以在实际应用里我不去计算具体的ppm浓度值而是直接使用ADC归一化后的百分比值作为报警判断依据。比如设定阈值80%当ADC采样值经过滤波和归一化后达到80%就触发报警。这样省去复杂的浓度换算而且对于“是否安全”的布尔判断来说已经足够。如果后续需要做数据上云和趋势分析还可以在软件里根据MQ-2的官方曲线拟合一个浓度公式这里先不展开。3.4 OLED显示界面与状态管理OLED驱动我用的是0.96寸SSD1306I2C接口代码里包含了基本初始化函数、清屏函数、画点函数和显示字符串函数。由于SSD1306不带中文字库我预先生成了“温”、“度”、“烟”、“雾”、“报”、“警”等常用字的字模存在一个const数组里。如果你要显示其他汉字可以用PCtoLCD2002软件生成字模注意取模方式是阴码、逐列式、逆向输出这是SSD1306最常见的取模方式。界面管理上我设计了三屏循环第一屏显示当前温湿度和烟雾状态第二屏显示阈值设置第三屏显示系统状态包括人体感应和火焰传感器状态。按键短按切换界面长按进入阈值调节模式。初始状态显示当前数据大数字方便远距离读数如果是夜晚环境OLED的亮度和对比度也可以调节代码里预留了对比度控制的接口理论上支持软件调节亮度等级。实际使用中OLED还有一个特点就是功耗低在3.3V供电时整屏全亮电流大概10~20mA比LCD屏幕省很多适合需要电池供电的场合。3.5 阈值报警与联动控制逻辑报警判断逻辑是整个系统安全性的核心不能只做一个简单的“超过阈值就响”。我在代码里实现了带迟滞的比较逻辑当温度或烟雾值超过阈值时开启报警当数值回落到阈值以下5%时才解除报警。如果不用迟滞当检测值在阈值附近上下波动时蜂鸣器会反复启停不仅吵人还会缩短继电器寿命。5%的迟滞窗口是我根据传感器波动幅度确定的经验值你可以根据现场情况调整。void Alarm_Check(void) { if (sys.temperature temp_threshold || sys.smoke_percent smoke_threshold || sys.flame_detect 1 || sys.pir_detect 1) { if (alarm_enabled 0) { alarm_enabled 1; Buzzer_Alarm_On(); Relay_On(); LED_On(); } } else { if (alarm_enabled 1 sys.temperature temp_threshold - 2 sys.smoke_percent smoke_threshold - 5) { alarm_enabled 0; Buzzer_Alarm_Off(); Relay_Off(); LED_Off(); } } }继电器控制也做了一点人性化处理报警触发时排风扇立即启动但报警解除后风扇继续运行30秒再关闭目的是把残余烟雾排出。这个延时要写在主状态机里而不是用delay阻塞否则延时的30秒内系统无法响应其他事件粮仓里真要有突发情况就被耽误了。我用了SysTick计时每10ms进去一个tick主循环里用秒数差值判断延时是否到期。3.6 主程序状态机流程主程序的骨架是一个典型的超级循环加状态机结构核心代码如下int main(void) { system_init(); // 时钟、GPIO、ADC、I2C、延时初始化 OLED_Init(); OLED_Clear(); while (1) { if (timer_500ms_flag) { timer_500ms_flag 0; DHT11_Read_Data(temperature, humidity); smoke_percent ADC_GetSmokeValue() * 100 / 4096; flame_detect GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_0); pir_detect GPIO_ReadInputDataBit(GPIOB, GPIO_Pin_1); } Key_Scan(); Alarm_Check(); OLED_Display_Update(); // 其他非阻塞任务... } }主循环看起来就是“采集-判断-显示”三件事轮转但每个环节都必须是非阻塞的。比如OLED屏幕刷新用了分时复用不是一次性画完整屏而是利用屏的显存把刷新率控制在10Hz左右避免占用CPU过多时间影响传感器时序。按键扫描和报警判断尽量精简保证每次循环时间保持在几毫秒以内这样系统的实时性才有保障。4. 仿真环境搭建与验证4.1 Proteus仿真电路搭建步骤在把代码烧进实物之前我建议先做一轮Proteus仿真这样可以快速验证电路逻辑和程序逻辑是否正确尤其是传感器接线和工作时序。Proteus里搭建这套电路的大致步骤是这样新建工程从元件库中搜索并放置STM32F103C8T6、DHT11、MQ-2Proteus里有MQ-2模型有的版本可能叫MQ-2 Semiconductor Sensor、LED、蜂鸣器、继电器、OLED显示屏SSD1306模型可选Proteus 8.10以上版本自带OLED屏组件、按键、电阻、电源等元件。按下图连接电路STM32的PA6接DHT11数据脚PA1接MQ-2的AO输出注意仿真中分压电路还是要加PA4、PA5接两个按键PB6、PB7接OLED的SCL和SDAPB12接蜂鸣器PB13接LEDPB14接继电器驱动端。用Keil生成HEX文件在Proteus中双击STM32芯片在Program File里选择生成的.hex文件设置晶振频率为8MHz。点击左下角的运行按钮观察OLED是否有显示改变仿真的传感器参数例如MQ-2的电阻值、DHT11的数值验证报警功能是否触发。仿真里有一个非常关键的操作Proteus的DHT11模型需要设置初始温度、湿度值在元件属性面板里可以直接改MQ-2在仿真中通常用一个可调电阻代替传感器输出调节电阻值即可模拟烟雾浓度的变化。我就是这样逐步验证了ADC采样通道、OLED驱动、报警联动逻辑在仿真环境下的正确性。4.2 仿真运行中常见的问题仿真不会像实物那样出现焊接短路但也有它特有的麻烦。最常见的问题是晶振不振荡或者程序不运行——这时候要检查STM32模型里有没有正确加载HEX文件以及晶振频率是否和代码里配置的PLL参数一致。还有一点是OLED显示乱码或白屏通常是因为I2C地址或者初始化时序对不上需要检查OLED模型的I2C地址是否为0x3C有些模型是0x3D。DHT11读不出数据的时候先检查上拉电阻是否接了再确认读取间隔是否大于1秒这两个问题是DHT11仿真的重灾区。Proteus仿真还有一个很有用的调试功能它能实时查看GPIO引脚的电平状态。在仿真运行时点击引脚的绿色方块波形会弹出你可以直观看到PWM信号或单总线时序是否正确。我用这个功能对比过DHT11的实际波形和代码逻辑排查了一次延时过长导致的通信超时问题。4.3 仿真与实物联调的差异仿真毕竟不是实物有几处差异必须心里有数。第一仿真中传感器的响应是理想化的MQ-2的模拟电压变化是线性的而实物的MQ-2在通电初期会有一个“预热漂移”过程输出电压会缓慢下降大约3~5分钟后才稳定。所以在实物调试时不能上电就立刻校准阈值一定要等传感器稳定后再设置。第二仿真的供电环境是完美的不存在电磁干扰和电源纹波如果你在实物上发现ADC采样值跳变、OLED偶尔花屏那大概率是供电或者布线问题而不是代码问题。第三DHT11在仿真中不带传感误差而实物的DHT11湿度误差可达±5%RH温度误差±2°C如果用于精确测量场合作参考可以做质量控制就要换SHT30这种更精准的传感器。跑完仿真代码和硬件连接基本验证通过后就可以烧到实物板子上调了。实物的电源供应要特别注意继电器吸合时电流瞬间可达100mA以上如果用USB供电稳压芯片的电流余量必须留够否则一吸合USB就掉线。我的方案是USB口加一个100uF大电容并且在3.3V输出端再加50uF左右的电容阵列实测继电器频繁吸合也不会重启。5. 常见问题与排查技巧实录5.1 DHT11读不到数据或者数据不变这是玩DHT11的经典问题了。先检查接线VCC是3.3~5VDAT接PA6GND接GND数据线上必须要有4.7K上拉电阻。然后用逻辑分析仪或者示波器看DAT引脚的波形正常情况主机拉低18ms后DHT11应该在20~40us后回一个80us低电平和80us高电平如果没有这个响应说明传感器坏了或者供电有问题。还有一种可能是延时函数不准确尤其是用for循环空转的延时在编译优化等级高的时候会被优化掉大半时间导致时序完全错乱。我建议统一用SysTick做微秒延时。5.2 ADC采集数值跳变严重如果你的ADC数值在几十到几百之间乱跳先排查电源问题。用万用表量一下3.3V和5V电压是否稳定如果电压波动超过50mV就需要在电源端加电容滤波。然后是ADC参考电压STM32的VREF接的是VDDA如果VDDA有干扰ADC精度就会下降。我的板子上给VDDA加了一个LC滤波串联一个10uH电感和一个1uF电容接到3.3V。最后是软件滤波我前面说的去掉极值取平均的方法对随机噪声有很好的抑制效果。5.3 OLED白屏或者花屏白屏一般有三个原因I2C接线错误、OLED的供电不足、初始化时序不匹配。先测OLED的VCC和GND确保有3.3V左右的电压并且足够稳定。然后用逻辑分析仪抓SCL和SDA的波形看是否有正常的ACK应答。如果波形正常但还是白屏多半是初始化时序不对SSD1306上电后需要等待至少100ms才能发初始化命令有些屏要等得更久我代码里特意在OLED_Init()开头加了一个200ms延时。花屏往往是I2C信号受到干扰排查方法是把I2C走线尽量短或者降低I2C速率。我的代码里把I2C时钟设为100KHz标准模式比400KHz的快速模式稳定很多。5.4 继电器反复吸合断开这个问题的根源就是我在3.5节说的阈值附近振荡。解决办法就是迟滞比较我已经写在了Alarm_Check函数里。有一个细节是迟滞窗口不要设得太小比如温度阈值设30°C迟滞窗口设1°C那么温度在29°C和30°C之间波动时蜂鸣器还是会频繁启停。我最终设的是温度迟滞2°C烟雾迟滞5%经过现场实测不会误触发响应速度也够快。5.5 蜂鸣器声音很小或者无声先区分有源蜂鸣器和无源蜂鸣器。有源蜂鸣器内部有振荡源只要给直流电压就响无源蜂鸣器需要给它脉冲信号才会发声。我这个项目用的是有源蜂鸣器如果你的模块是无源的代码就不能只拉高引脚而要用PWM输出特定频率的信号比如2.7KHz方波驱动它。另外检查驱动电路如果三极管没有完全导通蜂鸣器两端电压不够就会声音小。测量三极管的集电极电压如果蜂鸣器工作时还有1V以上的压降说明三极管深度饱和不够可以减小基极限流电阻从1K降到470欧姆。5.6 程序能编译但下载失败下载失败最常见的原因是ST-Link驱动问题或者Flash算法配置错误。看看设备管理器是否识别到ST-Link如果识别不到换一根USB线或装一下驱动。Keil的Options for Target里Debug选项卡选择ST-Link Debugger点Settings能看到设备ID就说明连接正常Utilities选项卡里记得勾选Use Debug Driver并选好STM32F10x高密度Flash算法。还有一个很少人注意的点F103C8的Flash是64KB属于中容量产品如果算法选成了128KB的高密度型号下载虽然能成功但后面容易出莫名其妙的问题建议统一用正确的算法。5.7 系统在继电器动作时复位这是典型的电源跌落问题。继电器线圈一吸合瞬间电流很大如果电源带载能力不够或者滤波电容太小MCU的VDD会掉到复位电压以下导致系统重启。解决思路有四个方向一是用大电容储能在电源输入端加一个470uF电解电容二是继电器供电不要和MCU供电走同一条支路继电器直接接5V电源而MCU用独立的3.3V稳压输出三是在继电器的驱动管旁并联一个100uF电容就近补偿电流四是把继电器的续流二极管接好注意方向不要反。这四条同时做下来复位问题基本能根治。6. 一些调试体会和可以扩展的方向做这个项目最大的感受是嵌入式系统的坑往往不在“高大上”的技术点上而在电源完整性、时序准确性、接口滤波这些看起来不起眼的细节里。调试过程中最费时间的不是写代码而是在继电器动作导致复位和DHT11时序不稳这两个问题上。继电器复位问题花了我整整一个下午排查最后发现是USB线的内阻太大换了一根粗线加一个大电容就解决了DHT11时序问题则是因为延时函数被编译优化掉了一部分改用SysTick后一次通过。这些经验放在这里希望后来者少走弯路。如果你们想基于这个项目继续扩展我建议先往物联网方向走利用STM32的USART接口接一个ESP8266模块把温湿度和烟雾数据通过MQTT协议上报到云平台在手机上就能远程监控。再进一步可以加一个SIM800L模块报警时发短信通知管理人员这样即使粮仓晚上无人值守也能及时告警。硬件上只需要多预留两个串口和几个GPIO代码上增加一个esp8266.c模块就行。这套系统本身已经把所有基础功能跑通了做扩展其实就是在现有骨架上加肉难度并不大。最后分享一个小经验开源项目最怕“只给代码不给上下文”。所以我这次除了代码和原理图还配了一份详细的项目文档里面包含每个引脚的接线定义、每个模块的初始化流程、Proteus仿真的操作截图以及一份“从零开始复现”的十分钟视频教程。你在拿到这套资料后建议先不要急着改功能而是先按文档把原版跑通一遍跑通之后再逐步替换传感器、修改阈值、增加功能。这样即使过程中出了新问题也知道是自己改出来的问题还是原有的问题排查起来会清晰得多。