1. 先拆需求这套仓库环境控制系统到底要做什么前段时间帮一个朋友的物料仓库做了一套环境控制系统。仓库不大但堆放的全是怕潮怕尘的电子元件和纸质包装前阵子因为返潮一批货直接报废损失够买好几套自动化设备了。当时他找我的需求很简单把温湿度盯住粉尘浓度盯住该通风通风、该除湿除湿人不用天天跑现场手机上能看到实时数据就好。选型几乎没有悬念——STM32做主控、ESP8266上云这是同类项目里最经典、资料最多、性价比也最高的组合。做完之后我发现这套系统的价值不只是能测能控而是把监测、控制、远程感知三条链路串成了一个闭环。文章主要写给正在做类似毕设、课程设计或者想给自己仓库、地下室、机房做环境改造的朋友如果你已经能点灯、会串口收发那整篇内容可以直接照着抄。1.1 三个核心能力测得到、控得住、看得见拆开标题看这个系统本质上是三件事。第一是测。仓库环境里最影响物品状态的就是温湿度和粉尘。温度高了加速老化湿度高了直接发霉短路粉尘浓度过高轻则影响设备散热重则存在安全隐患。所以监测模块要有温湿度传感器和粉尘传感器采集频率不需要太高几秒一次完全够用。第二是控。光有数据没有动作那只是个高级温度计。系统的价值在于自动联动温度超了开风扇湿度超了开除湿机粉尘浓度超了启动排风。这个环节的核心不是能不能开——继电器控制风扇开关非常容易——而是什么时候开、什么时候关、怎么避免频繁启停。我在后面专门讲回差控制这是整个项目里最容易被忽视但最重要的逻辑。第三是上云。仓库一般不是常驻人的地方跑一趟成本很高。通过ESP8266把数据传到云平台手机端就能看实时温湿度、历史曲线配合平台的推送功能还能做超阈值报警。这是整个项目最能体现物联网价值的部分也是答辩时最好讲清楚的部分。1.2 仓库场景的特殊性为什么不能直接拿温度计凑合有人会问我买个带WiFi的温湿度计插在仓库里不行吗还真不行。一是普通温湿度计没有控制输出不能驱动风机和除湿机二是粉尘监测比温湿度麻烦得多几十块的检测仪没法把数据开放出来给单片机用三是你需要的是一套可编程、可改逻辑的系统而不是厂家写死的APP。STM32的价值就在这里——你想让它在湿度超过70%时同时打开两台风机或者在粉尘超标且温度低于5℃时只开加热不开排风这些规则都能自己改。另外仓库这种场景对稳定性要求比较高。无人值守意味着出了问题不会有人第一时间发现所以系统要能自我恢复。我在主程序里加了独立看门狗万一程序跑飞自动复位重启而不是死在那里等着人去断电。这在普通桌面实验里无所谓但在工程项目里是基本要求。1.3 功能分层与资源规划先保证闭环再谈体验我给自己的规划分了三个优先级第一优先级实时采集温湿度、粉尘浓度本地串口或OLED显示继电器控制通风、除湿阈值自动启停。第二优先级ESP8266联网上云云端查看数据。第三优先级报警推送、历史记录、多节点组网、控制策略远程下发。做项目最忌讳一上来就把功能堆满。我的建议顺序是先把采集和控制跑通形成本地闭环再上云最后折腾报警和远程控制。STM32F103C8T6的资源64KB Flash、20KB RAM、3个UART、2个ADC做这个项目绰绰有余但如果你同时想接LCD屏幕、多个传感器、多个ESP8266就要重新算IO和中断资源了。我下面给的IO分配表就是基于C8T6最小系统板 一个ESP8266 两路继电器 两个传感器这个典型配置来的。2. 硬件选型与接线模块怎么挑、引脚怎么分、电源怎么处理硬件选型是整个项目的地基。很多同学喜欢追新传感器但实际上仓库环境控制这种场景技术栈应该优先选资料多、坑少、便宜、好买的。我最终确定的方案是STM32F103C8T6最小系统板 DHT11或BME280 夏普GP2Y1010AU粉尘传感器 两路继电器模块 ESP8266-01S。2.1 传感器选型对比DHT11/BME280、GP2Y1010AU的取舍先看温湿度传感器。市面上最容易买到、资料最多的是DHT11和DHT22以及BME280、SHT30这类数字I2C传感器。我做了个对比表传感器接口精度温度/湿度价格稳定性适合场景DHT11单总线±2℃ / ±5%RH约3元一般时序敏感毕设、低成本演示DHT22单总线±0.5℃ / ±2%RH约15元较好要求不高的工程应用BME280I2C/SPI±0.5℃ / ±3%RH约8元好附带气压推荐首选SHT30I2C±0.2℃ / ±2%RH约10元好更精确的场景DHT11的精度只能说够用胜在便宜、教程铺天盖地。如果你要做毕业设计我建议直接用BME280数字I2C接口配合标准库读起来其实并不复杂代码也不比DHT11长多少而且数据稳定得多。我自己调试的时候DHT11在环境湿度接近饱和时读数会明显不准这对控制除湿机这个动作来说很要命。如果手头只有DHT11也没关系下面代码我两种都给了思路。粉尘传感器我们用的是夏普GP2Y1010AU这是经典的光学粉尘传感器。很多文章会推荐攀藤PMS5003这类激光粉尘传感器它直接输出PM2.5浓度数字值UART接口精度确实好很多但价格是GP2Y1010AU的五六倍功耗也大。GP2Y1010AU输出的是模拟电压需要STM32的ADC去采集。精度上它不是实验室级但对于判断仓库粉尘是否超标、要不要开排风完全够用关键是学会正确的驱动和采样方法——这个我在第三章展开讲。2.2 联网与执行ESP8266-01S和继电器模块ESP8266我选的是ESP8266-01S模块原因很简单便宜、体积小、带板载天线引出引脚足够用。它只是承担透传角色——STM32通过串口给它发AT指令它负责连接WiFi、连接云平台、收发MQTT消息。注意ESP8266-01S的高电平触发引脚和GPIO0、GPIO2在启动时有模式选择作用所以STM32这边只需要用到它的UART TX/RX和RST引脚其它引脚不要乱接。继电器模块我用了两路一路控制排风扇一路控制除湿机。如果还要控制加热器就再加一路。市面上的继电器模块分为高电平触发和低电平触发很多默认是低电平触发买的时候一定要看清接反了表现就是单片机一上电继电器就吸合非常冤枉。模块上一般有跳线可以切换触发方式我建议全部设为高电平触发这样逻辑上更直观引脚输出高电平 设备启动。2.3 IO分配一张表说清楚但注意两处冲突一套能直接开工的IO分配如下以STM32F103C8T6为标准外设引脚说明温湿度DHT11PB0单总线数据需外接4.7k-10k上拉电阻温湿度BME280PB6/PB7I2C1SCL/SDA地址0x76或0x77粉尘传感器LEDPA1TIM2_CH2输出PWM频率100Hz、占空比3.2%粉尘传感器VoutPA0ADC1_IN0模拟输入继电器1排风扇PA4高电平触发继电器2除湿机PA5高电平触发继电器3加热器PA6预留ESP8266 TX/RXPA2/PA3USART2连接ESP8266的RXD/TXD交叉调试串口PA9/PA10USART1连接USB转TTL两个必须避开的冲突点要说清楚。第一PA13/PA14/PA15是JTAG/SWD调试引脚很多人做调试器的时候占用了这些脚然后发现自己没法下载程序了要在代码里调用GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);把JTAG关掉、只留SWD才能自由使用PA13/PA15等引脚。第二PA2/PA3接了ESP8266之后就不能再拿去做按键或者接其它串口设备了如果对UART引脚有特殊需求可以利用AFIO做重映射比如UART2可重映射到PD5/PD6但在C8T6这种小封装上可用的引脚不多规划阶段就要留好余量。2.4 电源与地线ADC怕什么、继电器怕什么电源设计是这类项目翻车最多的地方比代码里出错难查多了。我整理了几个关键原则STM32的3.3V由板载LDO提供一般没问题传感器模块我单独用5V供电避免和MCU抢电流。GPIO2Y1010AU的LED驱动需要5V同时它的Vout输出范围包含0-3.6V接到STM32的PA0前最好串一个1k电阻做保护避免超过3.3V时灌入ADC引脚。ESP8266-01S的峰值电流能到300mA以上WiFi发射瞬间如果和STM32共用同一个3.3V稳压源WiFi一发射就会把电压拉低导致单片机复位。我的做法是ESP8266用单独的5V转3.3V模块供电或者至少在主电源上并联一个470μF以上的电解电容。继电器模块的供电最好和MCU供电分开只共地。继电器吸合瞬间线圈电流突变会在电源线上产生很大的尖峰如果共用同一个电源轨单片机极易复位。这个坑我在第六章详细扒。3. STM32端采集代码温湿度时序、粉尘脉冲驱动与ADC滤波硬件接线完毕接下来是软件。工程我用标准库写的一是这类传感器和AT指令控制逻辑基本不依赖HAL的高级抽象标准库代码更直接二是网上绝大多数现成驱动都是标准库抄起来省事。用CubeMX生成HAL工程的朋友也不用担心思路完全一样只是初始化代码的写法不同。3.1 DHT11读取单总线时序的关键代码和常见翻车点DHT11和STM32之间用的是单总线协议只有一根数据线靠严格的时序区分0和1。整个读取流程是这样的主机先把总线拉低至少18ms发起读取然后释放总线DHT11响应时先把总线拉低80μs再拉高80μs接着连续发送40位数据8位湿度整数、8位湿度小数、8位温度整数、8位温度小数、8位校验和。每一位数据的区分标准是看高电平时长26-28μs的高电平代表070μs的高电平代表1。核心的读位函数是这样的uint8_t DHT11_ReadBit(void) { uint8_t retry 0; while (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 0) { if (retry 200) return 0xFF; // 超时保护防止死等 delay_us(1); } delay_us(40); // 等待40us进入判别窗口 if (GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN) 1) return 1; // 40us后仍是高电平说明是长脉宽 - 1 else return 0; }这里有几个最容易翻车的点。第一单总线对微秒级延时要求很严格如果延时函数用的是普通循环并且恰好在读取过程中被中断打断就会读到错误的位所以我建议在DHT11读取期间关闭可屏蔽中断或者至少不要让高频率的中断在这段时间触发。第二一定要加超时退出机制不要写成死等循环——如果传感器接线掉了或者上拉电阻漏焊程序会永久卡在读位函数里这在无人值守设备上是灾难。第三DHT11两次读取间隔至少要1秒以上它内部采样周期就是1秒你读快了它根本不更新数据拿到的永远是上一次的缓存。如果你用BME280代码反而简单得多I2C读取校准系数配置过采样读原始数据套官方补偿公式计算出温湿度和气压。BME280的补偿公式一长串建议直接移植现成的bsp库但要注意I2C地址SDO引脚接地时地址是0x76接高电平时是0x77跟OLED屏幕通常0x3C不一样别搞混。3.2 GP2Y1010AU粉尘传感器为什么LED要脉冲驱动GP2Y1010AU的内部结构是一个红外LED、一个光电二极管、一个信号处理电路。红外LED发出的光穿过空气后被光电二极管接收粉尘颗粒越多接收到的光强变化越大输出模拟电压也随之变化。但它的LED不是让你一直点亮而是必须用脉冲驱动——这点是绝大多数新手栽跟头的地方。看数据手册会发现它要求的LED驱动信号是周期10ms100Hz、脉冲宽度0.32ms。为什么有两层原因。第一红外LED连续点亮会让光电二极管产生一个很强的直流偏置粉尘浓度导致的微小光强变化被淹没在直流信号里输出信号信噪比很差。第二这个传感器本身就是按照脉冲模式设计的连续点亮导致LED过流发热寿命急剧下降几小时后读数就开始漂移。所以驱动LED的正确做法是让STM32的定时器输出PWM波形周期10ms、占空比3.2%。用TIM2的通道2输出到PA1// 假设定时器时钟为72MHz分频720计数频率100kHz TIM_TimeBaseStructure.TIM_Period 999; // 10ms TIM_TimeBaseStructure.TIM_Prescaler 719; // 72MHz / 720 100kHz TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_Pulse 32; // 0.32ms TIM_OC2Init(TIM2, TIM_OCInitStructure); TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_Cmd(TIM2, ENABLE);简单理解PA1上会产生一个每10ms闪一次、每次闪0.32ms的信号驱动粉尘传感器的LED就像拿着手电筒每隔一会闪一下看一眼而不是一直照着你。Vout引脚输出的就是每次闪光时检测到的电压峰值高度这个高度和粉尘浓度正相关。3.3 ADC同步采样与滑动滤波从电压到浓度的换算Vout接到PA0ADC1_IN0接下来要解决一个时序问题什么时候去采样最准确理想情况下应该在LED脉冲点亮后约0.28ms的位置采样也就是脉冲中后段。两种实现方式第一种是同步采样用PWM定时器的比较事件来触发ADC注入采样保证每一次采样都刚好落在LED点亮窗口内。这种方法精度最高但配置复杂不建议新手一开始就用。第二种是我实际采用的方法ADC连续快速采样取最大值。因为Vout的电压变化和LED脉冲是同步的脉冲出现时Vout会冲高到一个峰值脉冲结束后回落。只要在采集周期内连续采样几十到上百次必然能撞上峰值取最大即可uint16_t ReadDustPeak(void) { uint16_t max_v 0; uint16_t v; for (int i 0; i 100; i) { ADC_SoftwareStartConvCmd(ADC1, ENABLE); while (ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC) RESET); v ADC_GetConversionValue(ADC1); if (v max_v) max_v v; } return max_v; }采样完成后数据还需要滑动平均滤波。粉尘信号本身波动比较大直接用单次峰值做控制判断可能导致继电器频繁误动作。我维护一个长度为20的环形缓冲区#define DUST_WINDOW 20 uint16_t dust_buf[DUST_WINDOW]; uint8_t dust_idx 0; uint32_t dust_sum 0; void Dust_Update(void) { uint16_t peak ReadDustPeak(); dust_sum - dust_buf[dust_idx]; dust_buf[dust_idx] peak; dust_sum peak; dust_idx (dust_idx 1) % DUST_WINDOW; }滤波后的ADC码值dust_sum / DUST_WINDOW就是本次有效读数。电压和浓度之间的换算我不建议用网上流传的那些固定系数因为GP2Y1010AU不同批次、不同电路的零点电压差异很大。稳妥做法是先在洁净环境下记录底噪电压作为零点再参照典型灵敏度0.5V对应的粉尘浓度约为0.1mg/m³做线性换算。如果条件允许用一支标准粉尘仪做一次现场标定把系数修正到你自己的电路上。对绝大多数场景来说看相对变化趋势已经足够判断该不该开排风了。4. 自动控制策略回差控制、继电器驱动与看门狗兜底采集到可靠数据后控制逻辑就变成了用数据做决策的问题。这一章是整个系统的灵魂——因为决策逻辑如果写不好硬件再稳定效果也是恼人的。4.1 为什么要回差控制一次继电器误动作引发的思考我最早写的逻辑非常简单湿度大于70%就开除湿机小于70%就关掉。上电测试第一天就露馅了——湿度在69%和71%之间来回跳继电器咔咔咔跟着不停吸合释放几分钟后继电器模块烫得能煎鸡蛋。原因是传感器数据总有噪声环境本身也有微小的波动。当设定值正好落在测量值抖动范围内时系统就会在开和关之间反复切换。解决思路叫回差控制滞回控制把开机点和关机点分开中间留一个缓冲区。比如设定湿度超过70%时启动除湿机等湿度降低到62%时才关闭。这样中间差了8个百分点除非环境剧烈波动否则不会出现临界点抖动。这个思路和烧水壶的温控开关一个道理水烧到指定温度断电但不会在断电瞬间又立刻通电因为温度要降下去一段才会重新接通。对继电器这种机械触点设备回差控制不只是逻辑上的优化更是保护执行机构、延长寿命的刚需。4.2 控制逻辑实现阈值表和代码我最终的项目控制规则如下条件动作温度 ≥ 32℃或粉尘浓度 ≥ 0.1mg/m³打开排风扇湿度 ≥ 70%RH打开除湿机温度 ≤ 28℃ 且 粉尘浓度 ≤ 0.06mg/m³关闭排风扇湿度 ≤ 60%RH关闭除湿机代码上用一个简单状态机来管理避免那种又长又臭连续if elsevoid Control_Task(void) { if (temp 32.0f || dust 0.10f) Relay_Set(RELAY_FAN, ON); else if (temp 28.0f dust 0.06f) Relay_Set(RELAY_FAN, OFF); if (hum 70.0f) Relay_Set(RELAY_DEHUMIDIFIER, ON); else if (hum 60.0f) Relay_Set(RELAY_DEHUMIDIFIER, OFF); }注意每个控制动作都放在主循环里周期执行而不是用阻塞式延时。主循环跑一圈大概是几百毫秒加上回差区间继电器根本不会频繁动作。如果将来要接多个设备还可以给每台设备单独加最小动作间隔比如继电器吸合后至少保持5秒进一步保护机械触点。4.3 继电器与看门狗无人值守的两个保命措施继电器驱动在电气上要单独说清楚。STM32的GPIO最高只能输出约3.3V/8mA左右而5V继电器的线圈需要50-70mA电流直接驱动会烧引脚。正确方案是中间加一级驱动用NPN三极管如S8050或ULN2003达林顿管再配合线圈两端的续流二极管。如果你买的是现成的继电器模块模块上已经集成了驱动三极管和续流二极管直接用模块就好但一定要确认模块的触发端能否被STM32的3.3V高电平可靠触发。部分模块对输入高电平要求接近5VSTM32给3.3V时继电器不动作或动作不可靠这种情况下要把模块的触发方式换到低电平触发或者用光耦隔离驱动板做中转。如果直接从零搭继电器电路关键元件是这样的GPIO → 1kΩ限流电阻 → NPN三极管基极三极管集电极接继电器线圈一端线圈另一端接5V线圈两端反向并联一个1N4148或1N4007续流二极管负极接5V。这样gpio给高电平三极管导通继电器吸合断电瞬间续流二极管把线圈感生电流泄放掉不会反向击穿三极管。单片机的保命手段则是一句看门狗。我用独立看门狗IWDG超时设定2秒主循环每转一圈喂一次狗。万一某个函数陷入死循环或者传感器读取卡住看门狗会自动复位单片机系统从异常状态恢复到可控状态。无人值守场景下这比任何复杂报警都管用。5. ESP8266上云AT指令链路、巴法云MQTT接入与数据帧设计本地闭环打通后最后一环是把数据送上网。这块内容多且杂我把自己的实践过程拆开讲。5.1 上云路线怎么选ATTCP、ATMQTT、还是ESP8266独立干活ESP8266有三种主流的干活方式第一种ESP8266刷AT固件STM32通过串口发AT指令控制它联网。这是最简单、最适合STM32主导系统的方案也是我用的方案。STM32采集传感器数据通过AT指令把JSON数据包直接发布到云平台MQTT逻辑清晰排查问题也方便。第二种ESP8266刷Arduino或MicroPython固件由ESP8266自己连传感器再上云。这种方案等于ESP8266独立成为一个物联网节点STM32就失业了。对仓库环境控制这种需要本地模拟量采集、继电器控制、看门狗守护的场景来说没必要把控制权从STM32手里交出去。第三种也是很多教程喜欢但不推荐的让ESP8266发HTTP请求到云平台API。这个方案链路简单但HTTP包头开销大云端响应慢断线重连逻辑还得写在AT指令循环里后期维护比较痛苦。MQTT比HTTP更适合这种低功耗、频繁上报的传感器场景。5.2 巴法云MQTT接入实战AT指令完整链路云平台我选了巴法云原因是它对国内用户友好MQTT接入免费创建主题后在控制台能看到所有平台的接入示例而且自带小程序和微信推送做完了手机端立刻有东西可以看。接入之前先确认ESP8266的AT固件版本支持MQTT指令。官方AT固件1.7以上才有ATMQTTUSERCFG这一组命令我默认用的是官方AT 2.x固件。如果手头的ESP8266模块是老固件先用ATGMR查看版本太老就先烧录新固件。整个接入流程如下AT // 测试模块是否响应返回OK ATCWMODE1 // 设置为Station模式 ATCWJAP你的WiFi名字,你的WiFi密码 // 连接路由器 ATMQTTUSERCFG0,1,NULL,你的UID,你的UID,0,0, ATMQTTCONN0,mqtt.bemfa.com,9501,1 ATMQTTPUB0,你的设备主题,{\temp\:25.3,\hum\:60.5,\dust\:45.2},1,0这里面的UID就是巴法云控制台里的设备私钥相当于你在这个平台的唯一身份标识设备主题是你在控制台创建的topic ID。STM32侧把这些AT指令封装成函数void ESP8266_SendCmd(UART_HandleTypeDef *huart, char *cmd) { UART_SendString(huart, cmd); UART_SendString(huart, \r\n); // 所有AT指令必须以\r\n结尾 }需要注意的是ATCWJAP连接路由器、ATMQTTCONN连接云端这些操作耗时较长1-3秒不等STM32发完指令后不能只做固定延时要加上接收成功标志状态判断。我的做法是发完AT指令后每50ms检查一次串口接收缓冲区如果超时3秒还没收到OK或ERROR就重新发送该指令最多重试3次。这套机制在离线环境下尤为重要否则WiFi断了系统就直接卡死在等待里。5.3 JSON数据帧设计与断线重连上报的payload我用了最简单的JSON格式{temp:25.3,hum:60.5,dust:45.2,fan:1,dry:0}包含温湿度、粉尘浓度以及当前两个继电器的状态。把执行状态也上报上去好处是在手机端能看到系统判断该通风了而且真的打开了风扇而不是只能看到一堆传感器数据。断线重连的策略我放在主循环里定期用ATMQTTSTAT查一次连接状态如果返回的不是正常状态就按顺序重新执行ATMQTTCONN。时间上有个技巧云平台一般要求客户端定期发心跳包否则会自动断开连接巴法云这边我每60秒重新发布一次最新数据既完成了数据更新也顺便当心跳。如果你的传感器变化慢完全可以把上报周期拉长到2分钟、5分钟省流量也省电。5.4 迁移到OneNET/阿里云要改哪些东西如果答辩老师或项目需求要求用OneNET或阿里云核心改动只有三个MQTT服务器地址、端口、ClientID/用户名/密码这几个鉴权参数。OneNET的MQTT接入用的通常是产品ID、设备ID、APIKey这套体系你只要在产品控制台创建好产品设备把鉴权参数填进ATMQTTUSERCFG和ATMQTTCONN里topic改成产品/设备的格式其余代码基本原样复用。阿里云IoT平台的鉴权稍微复杂点ClientID、UserName和Password里包含用设备Secret做HMAC-SHA1签名计算出的密钥这部分需要预先在PC上算好再固化到STM32代码里不建议在板子上跑完整签名算法既占资源又难调。我的建议是先用巴法云把整条链路跑通再根据平台差异逐个替换——协议是MQTT的时候换平台只是换参数不伤筋动骨。6. 五天调试实录掉过的坑和根因都整理在这了写代码的时间其实只占一小半大头全花在调试上。我把自己踩过、且很多同学大概率也会踩的五个坑列出来每个都给根因和解决方案。6.1 ESP8266恢复出厂后AT无响应别在OK上死等我第一次上电时想恢复出厂设置发送ATRST后立刻在循环里等OK返回。程序卡了好几分钟怎么都不出来。原因很简单ATRST之后模块会重启重启过程需要一两秒甚至更久此时串口上没有任何返回而我的循环在等OK模块重启完成后输出的是乱码或启动日志根本不含OK然后循环就假死了。正确做法是ATRST发送后先延时2秒再发送AT并等待OK。而且任何AT指令的响应接收都不能用死等必须带超时计数超时就重发。这个改完以后后面接WiFi、接MQTT我再也没卡过死循环。6.2 DHT11偶发读失败延时精度与单总线冲突现象是DHT11偶发返回错误数据读到的湿度和温度突然变成255。排查下来有一个隐蔽原因我在系统里用了SysTick做delay_us但ESP8266的UART接收中断频率很高DHT11在读位等待高电平的过程中如果恰好发生中断延时会多出几十微秒导致0被误判成1。解决方案是DHT11读取期间关闭全局中断读取结束后再打开__disable_irq(); DHT11_ReadData(); __enable_irq();这里也有个教训单总线传感器虽然便宜但它对微秒级时序的敏感程度远超想象。你需要先确认项目的整个中断环境是否复杂如果中断多且频繁直接换BME280这类I2C传感器能省掉至少半天调试时间。6.3 ADC采样值漂移根因在粉尘LED的供电粉尘传感器刚接上时我发现读数一直在漂关了继电器也漂。先用万用表量5V电源轨发现电压在4.7V到5.1V之间跳。GP2Y1010AU的Vout和它的LED供电直接相关电源电压不稳模拟输出自然跟着抖。根因是LED驱动和MCU共用了一个5V稳压源LED脉冲导通瞬间拉低了电压。解决方法很土但有效在粉尘传感器的5V供电引脚旁边并一个100μF电解电容和一个0.1μF陶瓷电容靠近模块放置同时确认LED驱动PWM的地线、传感器地线、STM32地线在一点汇聚不要形成环路。电容并上之后ADC读数稳定了不止一个数量级。6.4 继电器吸合瞬间单片机复位这是最诡异的一个现象程序跑得好好的风扇一开STM32就重启。用示波器量3.3V引脚发现继电器吸合瞬间3.3V被砸出一个大坑掉到2.5V以下。原因在电源配置上继电器模块、MCU最小系统板、ESP8266全部挤在面包板上用同一个5V输入供电。继电器线圈吸合瞬间电流冲击很大5V被瞬间拉低板上LDO输出3.3V跟着跌穿MCU复位电压。解决方法是继电器模块独立供电单独一路5V电源或者干脆用12V继电器配12V电源所有模块只共用参考地线不共用电源轨。这个改动之后开关继电器时单片机再没复位过。6.5 PA2/PA3被占用UART与GPIO的引脚冲突项目后期想加一个按键做本地手动切换模式随手选了PA2然后发现ESP8266数据全部错乱。查了半天才反应过来PA2是USART2_TX引脚ESP8266的数据线正挂在上面。这种低级错误本质上是引脚利用率规划不仔细——我一开始做IO分配表的时候就把所有复用功能引脚一起列出来对比而不是用到哪个查哪个。教训是设计阶段就把所有外设需要的引脚画成图标出USART、SPI、I2C、ADC、PWM这些复用通道的默认位置再做功能叠加否则后面每加一个外设都要挪引脚挪完还有可能忘了改初始化代码。整套系统从画原理图到云端看到第一条数据前后大约一周。中间最花时间的不是代码本身而是排查电源和时序这两个看不见摸不着的问题。做完之后我的一个强烈感受是这种环境控制系统的难点根本不在单片机能做什么而在传感器数据能不能信、控制动作会不会误触发。把回差控制、滑动滤波、看门狗这些基础功夫做扎实远比堆功能重要。如果你也想做类似项目建议先用手头的传感器跑通采集和本地控制再谈上云——顺序反了的话排错的时候会非常痛苦。
阅读完成 · 觉得有帮助?