拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32实战:基于FreeRTOS的智能仓储环境监测系统开发详解

简介本资源是一套基于STM32F103C8T6的智能仓储环境监测系统完整工程实现面向嵌入式初学者、课程设计学生及物联网实践开发者解决小型仓储场景下温湿度、光照、烟雾等多参数实时感知与闭环调控问题。项目融合Proteus仿真与真实硬件逻辑支持本地按键交互、OLED可视化及ESP8266远程监控具备加热、散热、通风、声光报警等完整执行链路。压缩包含335个文件涵盖56个编译中间文件.o、55个源码备份.zbak、55个配置文件.crf、38个头文件.h、36个C源文件.c及Keil工程.uvprojx、Hex固件、启动汇编与链接脚本等核心开发资产总大小16.99MB。已有79人学习下载提供可直接编译运行的完整代码结构、模块化驱动ADC、I2C、TIM、RCC等、阈值设定逻辑实现及无线通信接口封装便于理解嵌入式系统软硬协同设计全流程。 做嵌入式开发这几年我陆陆续续做过不少基于STM32的小项目但真正让我觉得“做了一个完整产品”而不是“点亮一颗LED”的是这个智能仓储环境监测系统。它不是一个DEMO而是一套能实际放进仓库里跑起来、能持续记录数据、能联动风机和加热器、还能出报表的完整设备。如果你的工作或毕业设计正好需要做类似的环境监测类项目这篇文章应该能帮你省下至少两三个月的踩坑时间。这套系统的核心功能一句话就能讲清楚围绕STM32做主控采集仓库里的温湿度、烟雾浓度、光照强度通过LCD实时显示支持本地按键设定阈值超限时自动控制风机、加热器、补光灯同时把数据通过ESP8266上报到上位机或云平台。硬件上有传感器、执行器、显示器、通信模块软件上有嵌入式驱动、状态机、协议解析、任务调度算是把嵌入式开发里最常见的知识点都串起来了。不管你是想学STM32实战的初学者还是准备参加电赛/做毕设的学生这套方案都值得完整看一遍。1. 系统整体设计与方案选型1.1 设计思路先想清楚要采集什么、控制什么我一般接到一个项目不会先画电路板而是先把系统拆成几个功能块感知层、控制层、执行层、交互层、通信层。感知层就是各种传感器负责“摸环境”控制层就是STM32主控负责“做决策”执行层是继电器、风扇、加热器这些“干活的东西”交互层是屏幕、按键负责“让人知道状态”通信层负责“把数据传到别的地方”。这种分层思维对后面的代码组织、硬件接线、问题排查都有好处。具体到这个项目仓库环境监测最核心的参数有三个温湿度影响货物存储条件比如电子元器件怕潮、纸制品怕火、烟雾浓度火灾预警、光照强度避免阳光直射某些敏感物资也用于智能补光。执行端的逻辑也很直白温度过高开风扇湿度过大开除湿器或加热器烟雾超标声光报警并打开排烟风机光照不足自动补光。这套逻辑在代码里就是几个if判断但对应的硬件接线和驱动设计才是工作量的大头。1.2 主控选型为什么选STM32F103C8T6主控我选了STM32F103C8T6这颗芯片在嵌入式圈子里几乎人手一颗。有人可能会问现在STM32F4、H7都出来了为什么不选更强的原因很简单这个项目的负载完全用不到F4级别的算力而F103的资源恰好覆盖所有需求。它有64KB Flash、20KB RAM三个USART、两个I2C、两个SPI、一个ADC12个通道足够驱动两个传感器I2C的SHT30和模拟量的MQ135、一个OLED显示屏I2C或SPI、一个ESP8266USART1、一个按键模块GPIO、两个继电器GPIO和一路蜂鸣器PWM。从性价比角度说C8T6的国产替代版比如GD32F103、MM32F103现在几块钱就能拿到资料却和ST原厂互通拿来做产品验证或者学生项目都极其合适。而且F103系列的资料多、例程全遇到任何奇怪问题基本都能在网上找到别人的解决方案这对开发效率的提升是实打实的。1.3 传感器与执行器选型传感器选型是我在这个项目里比较纠结的部分因为传感器直接决定了数据的可靠程度。先说说温湿度。DHT11太粗糙精度±2度湿度±5%做仓库监测这种需要连续记录数据的场景不太够看我最后选了SHT30I2C接口精度±0.3度、湿度±2%RH价格也就几块钱同系列的SHT31、SHT35可以通过替换焊盘兼容。如果你手头只有DHT11也不是不能用但数据曲线会丑很多。烟雾传感器用的MQ135这是一个经典的气体传感器对烟雾、甲醛、苯都有响应输出模拟电压。它的特点是便宜、灵敏度可调缺点是需要预热、非线性和温漂。后面我会详细讲怎么标定。光照传感器用的BH1750也是I2C接口直接输出勒克斯Lux不用自己做复杂的换算。执行器方面我用了两个5V继电器模块一个控制排风扇一个控制加热器/除湿器另外加了一个无源蜂鸣器做声光报警。继电器模块用光耦隔离STLINK或调试器供电建议和继电器电源分开不然电机一启动单片机可能直接复位。这里踩过坑后面会细说。2. 硬件电路与接线细节2.1 STM32最小系统与调试接口很多新手刚开始喜欢买一块现成的开发板这没错但如果你要做实际部署开发板占地方又贵到最后还是要画一块自己的板子。这个项目我用的是自己打的PCB核心电路就那几个部分电源5V转3.3V、晶振8M、复位电路、SWD调试口、BOOT配置。调试口我强烈建议引出SWD而不是JLINK的20针接口只用SWIO、SWCLK、GND三个脚就能下载和调试省IO还省空间。但这里有一个坑必须提醒如果SWD引脚被复用成其他功能或者程序里不小心禁用了JTAG/SWD下过一次程序后第二次可能就下载不进去了。网上搜“STM32禁用JTAG”会出现一堆帖子基本都是这个问题。解决办法是按住复位键的同时点下载在下载瞬间松开复位或者用串口ISP方式先擦除Flash再重新烧录。我当初第一次碰到这问题的时候折腾了整整一晚后来才明白是GPIO初始化时把SWD引脚给改了。2.2 传感器与执行器接线方式整个系统的接线我整理了一个表方便你对照着做模块接口类型STM32引脚供电备注SHT30温湿度I2CPB6(SCL), PB7(SDA)3.3V上拉电阻4.7kBH1750光照I2CPB6(SCL), PB7(SDA)3.3V可软件设置地址MQ135烟雾ADCPA15V输出经分压接ADCOLED 0.96寸I2CPB6(SCL), PB7(SDA)3.3V和传感器共用I2CESP8266USART1PA9(TX), PA10(RX)3.3V注意电平匹配继电器1风扇GPIOPA45V光耦隔离继电器2加热GPIOPA55V光耦隔离蜂鸣器PWM/GPIOPA63.3V低电平触发按键GPIOPA0, PA23.3V上拉输入这里有一个很容易忽略的点SHT30、BH1750、OLED都挂在同一组I2C总线上地址必须错开。SHT30默认地址是0x44BH1750是0x23OLED是0x3C这三个地址没有冲突可以放心挂在一起。如果后面要加其他I2C设备需要先查一下地址避免冲突。电源方面我的做法是外部输入直流12V经过一个MP1584降压模块降到5V然后再用LDOAMS1117-3.3降到3.3V给MCU和传感器供电。继电器模块和蜂鸣器直接吃5V。需要特别注意的是MQ135的加热电阻功率不小发热明显工作电流有150mA左右如果从AMS1117取电3.3V那一路会被拖垮所以MQ135的5V供电要单独从降压模块走。2.3 PCB布局经验PCB布局上吃过几次亏这里讲一下最关键的几条。第一模拟地和数字地要单点连接MQ135输出的模拟信号线尽量短不要走直角避免和继电器驱动线并行走长距离。第二继电器和蜂鸣器这类感性负载必须在两端并联续流二极管1N4007或RC吸收电路否则关断瞬间的反向电动势会把单片机的GPIO打坏。第三ESP8266的供电不能直接从MCU的3.3V引脚拉因为WiFi发射瞬间电流能到300mA以上电压跌落会导致模块反复重启。我给ESP8266单独放了一个AMS1117-3.3并且并联了两个100uF的电解电容实测稳定很多。3. 软件架构与核心代码实现3.1 开发环境STM32CubeMX HAL库 Keil现在做STM32开发我强烈建议直接用STM32CubeMX生成初始化代码再配合HAL库写业务逻辑不要自己手动去翻寄存器了。当然我理解还是有人喜欢标准外设库标准库觉得网上教程多、代码透明。但客观说ST官方现在已经不更新标准库了新出的芯片根本不支持。如果你是做产品而不是学单片机原理直接上HAL库才是正路。CubeMX里需要做的配置如下RCCHSE外部晶振8MHzSYSDebug Serial WireSWDI2C1Standard Mode100kHzADC1IN1单通道连续转换模式开启DMA循环请求USART1115200-8-N-1使能空闲中断IDLEUSART2115200用于调试打印TIM21ms定时中断作为系统时间基准FreeRTOSCMSIS_V1接口创建数据采集、显示刷新、通信上报三个任务这个配置下来CubeMX生成的代码已经帮你处理好了时钟树、GPIO初始化、DMA中断这些底层杂事后续主要工作就集中在业务逻辑上。3.2 ADC多通道DMA采集别让传感器数据采集卡死CPU这个项目里ADC采集我用了两个通道PA0采集MQ135烟雾电压PA1采集一个NTC热敏电阻分压作为备用测温。一开始我直接用阻塞方式轮询ADC发现只要采集一启动主循环就卡住几百微秒显示刷新、按键扫描全都不流畅。后来改成ADC多通道DMA循环采样效果立竿见影。具体配置流程是这样的在CubeMX里把ADC1的两个通道的采样时间拉长到239.5周期开启Scan Conversion Mode和Continuous Conversion Mode然后使能DMA的Circular模式数据宽度都是Half Word16位内存地址递增。DMA的Buffer长度设为2对应两个通道。因为DMA是循环模式硬件会持续把ADC转换结果搬运到内存数组里CPU完全不用介入。需要读数据时直接从数组里取最新值就行。比如uint16_t adc_buf[2] {0}; // 在while循环里读最新数据 uint16_t mq135_raw adc_buf[0]; uint16_t ntc_raw adc_buf[1];注意在CubeMX里CIRCULAR模式的DMAHAL库会自动维护一个半传输和全传输中断你可以利用这两个中断做双缓冲但我这个数据量很小直接循环读数组就够了不需要额外处理。3.3 串口空闲中断接收ESP8266回传的不定长数据串口接收不定长数据是STM32开发里非常经典的问题。以前用标准库的时候很多人都是一次接收一个字节到数组里然后靠定时器或自定义协议判断一帧数据结束了没有。现在用HAL库配合空闲中断IDLE Interrupt可以非常优雅地解决。思路是这样的串口接收每产生一个字节中断DMA就把数据搬到接收缓冲区而空闲中断是当串口线在一段时间内没有数据传输时触发的刚好代表“一帧数据从开始到结束”。我在USART1上使能了空闲中断然后在中断回调函数里处理一帧完整数据void HAL_UARTEx_RxEventCallback(UART_HandleTypeDef *huart, uint16_t Size) { if (huart-Instance USART1) { esp_rx_len Size; memcpy(esp_rx_buf, esp_dma_buf, Size); esp_rx_ok 1; HAL_UARTEx_ReceiveToIdle_DMA(huart1, esp_dma_buf, ESP_RX_MAX_LEN); } }这个方案的核心是我先开启一次HAL_UARTEx_ReceiveToIdle_DMA之后所有收到的数据会通过DMA自动存到缓冲区空闲中断一次就回调一次Size参数就是这一帧数据的大小。处理完一帧之后再重新开启下一次接收。实测下来这个方案接收ESP8266发来的不定长AT指令回包非常稳定不会丢帧也不会把两帧粘在一起。如果你还在用HAL_UART_Receive_IT一个字节一个字节地接建议赶紧换掉。3.4 传感器驱动编写SHT30的CRC校验与MQ135的标定传感器驱动这块SHT30虽然I2C协议简单但ST官方的HAL库函数有一点需要注意SHT30读取的6个字节数据最后两位是CRC校验很多教程里都是直接忽略校验只拿前4个字节。虽然大多数时候数据是准的但在电磁环境复杂的仓储现场偶尔会出现一个跳变的错误值这时候CRC校验就很有意义了。SHT30的CRC校验用的是CRC-8多项式0x31HAL库自带HAL_CRC但直接计算有点麻烦我写了一个轻量的软件CRCuint8_t sht30_crc8(uint8_t *data, uint8_t len) { uint8_t crc 0xFF; for (uint8_t i 0; i len; i) { crc ^ data[i]; for (uint8_t j 0; j 8; j) { if (crc 0x80) crc (crc 1) ^ 0x31; else crc 1; } } return crc; }MQ135的标定是个大课题。它的输出是0~5V的模拟电压气体浓度越高电压越大但这个关系不是线性的。网上能搜到MQ135的灵敏度特性曲线是横轴为浓度、纵轴为电阻比的曲线实际工程里很少做精确标定更常用的方法是用阈值判断烟雾是否超标。我在程序里先采集洁净空气中的基准电压比如预热10分钟后读取到1.2V然后设定一个偏移量比如0.5V当电压超过基准偏移时判定为烟雾报警。这种“相对阈值法”虽然测不出绝对浓度但作为火灾预警完全够用。这里要特别提醒MQ135上电后需要预热5分钟左右输出才会稳定所以程序里要加一个开机初始化延时否则刚开机那两秒钟读取的数据会虚高导致误报警。3.5 FreeRTOS任务划分采集、显示、上报互不阻塞这个项目的软件架构如果全写在一个while循环里也能跑但逻辑会变得很难维护。比如OLED刷新需要几十毫秒如果在这个时间里去读ADC、发串口就会出现数据延迟或丢包。所以我用了FreeRTOS把系统拆成几个独立任务任务名优先级周期功能SensorTask3500ms读取SHT30/BH1750/MQ135DisplayTask21000ms刷新OLED显示AlertTask4事件驱动超限报警与继电器控制CommTask12000ms通过ESP8266上报数据任务之间通过FreeRTOS队列传递数据。SensorTask把采集结果打包成一个结构体发送到队列DisplayTask和CommTask分别从队列中读取数据并处理。这样采集任务不会等待显示和通信实时性有了保证。串口打印和显示抢资源的问题也顺便解决了因为每个任务都有自己的数据源不需要用同一个全局变量。这里有一条经验FreeRTOS里任务函数的局部变量尽量用静态或动态分配不要在任务栈里放大数组比如char buf[512]很容易把任务栈压爆。我每个任务的栈大小都设成了256字Word实测足够。4. 数据通信与上位机方案4.1 ESP8266使用体验与TCP上报ESP8266现在几乎是STM32联网的标准搭档唯一的麻烦是它的AT指令模块型号众多固件版本也乱。我用的ESP-01S经典模块GPIO口就两个适合做纯透传。第一次用的时候先接USB转TTL在PC上测试确认AT固件支持ATCIPSTART和ATCIPSEND再接到STM32上。因为STM32的USART1是3.3V电平ESP8266也是3.3V电平理论上可以直连。但ESP-01S的WiFi天线和走线容易引入干扰我实际使用中碰到过不少次通信异常最后检查发现是电源纹波太大。ESP8266的供电必须干净我的做法是单独用一个AMS1117-3.3给它供电并在模块附近加一个470uF电解电容和0.1uF瓷片电容组合实测掉线率降低了很多。STM32和ESP8266之间我用的是透传模式STM32往USART1发JSON格式字符串ESP8266直接转发到TCP服务器。JSON格式如下{dev:W001,t:25.6,h:58.4,smoke:1.35,lux:230}服务器端我写了一个简单的Python TCP服务脚本监听指定端口收到数据后存MySQL并推送Web端展示。如果你不想自己写服务器可以接巴法云、OneNET这类物联网平台用MQTT协议上报ESP8266刷MQTT固件就能直接连。不过如果用MQTTSTM32这边的代码需要做相应的MQTT协议打包比TCP透传要多一点工作量。4.2 上位机与数据可视化做数据可视化最省事的就是用Node-RED或者Grafana。我在本地Windows机器上装了Node-RED弄一个MQTT BrokerMosquitto然后ESP8266通过MQTT发布主题warehouse/sensorNode-RED订阅这个主题把数据转存到InfluxDB再用Grafana画曲线。全程都是开源工具不花钱效果却很像商业系统。这里有个小的坑ESP8266的MQTT库PubSubClient默认的KeepAlive是15秒如果WiFi信号不稳定很容易在两次心跳之间掉线导致Server端显示“Connection Lost”。把KeepAlive调大到60秒或者干脆在程序里加一个重连机制掉线后自动重新连接。4.3 报警推送微信还是邮件报警推送是这个系统里比较受关注的一块。最简单粗暴的实现是让ESP8266直接通过HTTP GET请求第三方推送接口比如Server酱、企业微信机器人发消息到微信。这个方案的优点是实现起来只用一个HTTP请求但缺点是依赖第三方服务稳定性看运气。更稳妥的方案是自建MQTT服务器利用MQTT的遗嘱消息Last Will and Testament来检测设备离线。当STM32突然断电或者网络断开时MQTT Broker会代替设备发布一条遗嘱主题服务器端收到后立刻发报警邮件或短信。这个机制在工业级场景里比较常用也值得在智能仓储这类项目里借鉴。5. 常见问题与排查技巧实录5.1 STM32延时函数卡死这个问题的搜索结果非常多说明大家都遇到过。我用HAL库的时候习惯用HAL_Delay()但这个函数在某些情况下会卡死尤其是中断里调用HAL_Delay()的时候。原因是HAL_Delay依赖HAL_IncTick()来维护一个全局变量uwTick而这个函数是在SysTick中断里被调用的。如果在高优先级中断里调用HAL_DelaySysTick中断又恰好被阻塞uwTick不再递增延时函数就永远等不到目标值。解决办法有两个一是不要在中断里调用HAL_Delay改成标志位超时轮询二是用DWT的Cycle Counter来做高精度延时完全不依赖SysTick。DWT是Cortex-M3内核自带的调试单元可以像定时器一样数CPU周期数实现微秒级延时void delay_us(uint32_t us) { DWT-CYCCNT 0; DWT-CTRL | DWT_CTRL_CYCCNTENA_Msk; uint32_t start DWT-CYCCNT; uint32_t ticks us * (SystemCoreClock / 1000000); while (DWT-CYCCNT - start ticks); }这段代码在F103上实测很稳我后来把显示刷新和传感器读操作里的所有延时都换成了DWT版本。5.2 ADC采样数值跳变数值跳变是ADC应用里最典型的坑。我一开始直接用PA1读MQ135发现数据在1.0V~1.5V之间来回跳完全没法用。检查了硬件和软件最终定位为两个问题第一ADC参考电压不够稳定。STM32的VREF内部连到VDDA如果VDDA上有高频噪声ADC结果肯定会抖。我在VDDA和VSSA之间加了LC滤波效果立竿见影。第二采样时间不够长。MQ135的内阻有几十千欧如果ADC采样时间太短采样电容还没充满就开始转换了误差会非常大。我在CubeMX里把采样时间从1.5周期改成了239.5周期效果很明显。另外ADC结果还应该做软件滤波。我用了中位值平均滤波连续采样10次去掉最大最小值再求平均。处理之后的数据曲线平滑很多也不会太迟钝。5.3 OLED显示花屏与闪烁0.96寸OLED在I2C模式下偶尔会出现花屏特别是在电机、继电器动作瞬间。排查了很久发现是电源瞬间跌落导致OLED内部控制器复位但MCU没有复位两边状态不一致了。解决办法是给OLED供电也加一个100uF电容然后软件上每隔一段时间重新初始化一次OLED比如每小时强制刷新一次初始化序列。虽然治标不治本但能保证画面始终是正常的。还有一个小技巧是用I2C的OLED显示大数据量内容时如果单次刷屏间隔太短容易造成I2C总线拥堵。我的做法是把显示任务周期设为800ms~1s每次只更新变化的部分比如只刷新温湿度的数字区域画面明显流畅了。5.4 ESP8266通信不稳定一例有一次客户反馈设备经常连不上服务器我远程排查发现ESP8266经常处于“ATCWJAP连接失败”的状态。后来过去现场才发现仓库里有一台大功率对讲机一发射就干扰WiFi。这个跟硬件不好没关系纯粹是现场环境问题。解决办法是在ESP8266的电源脚上加强滤波、把天线位置抬高并增加自动重连逻辑每次上电先连WiFi如果5秒没连上就重启ESP8266再试。另外AT指令模式下的ESP8266有一个常见的坑模块上电后会先输出一长串乱码式启动信息如果你在MCU侧等待“ready”字符串再发指令很容易因为时序问题失败。我的程序里直接用一个状态机如果200ms内收到任何包含“OK”或“ready”的字符串就认为模块已启动如果收到“ERROR”或超时就发送“ATRST”重启模块。6. 项目延展从“能跑”到“好用”6.1 增加更多传感器与执行器这个系统的框架其实可以很容易扩展。比如仓储里要测VOC或二氧化碳可以挂SGP30或SCD30要测水浸可以接一个水浸传感器到GPIO要监测门禁状态加一个干簧管就行了。因为主控的IO和I2C总线都有富余我后来还加了一个GPS模块做位置定位用于户外移动仓储箱也是挂USART2上和调试串口共用只是调试时切换一下引脚复用。6.2 本地存储SD卡和Flash轮询记录仓储数据讲究可追溯光靠Grafana上的云端数据不够断网时还需要本地存储。这块我实现了两个方案一个是SD卡通过SPI接口读写文件系统用FatFS每小时生成一个CSV文件方便后用Excel分析另一个是在STM32内部Flash里做一个环形缓冲区保存最近24小时的关键数据掉电不丢失。内部Flash方案更简单不需要外挂硬件但容量有限只能存关键告警数据而不是全量数据。6.3 低功耗与电池供电如果你需要在没有外部电源的仓库里部署低功耗设计就绕不开。STM32F103有STOP模式可以把整体功耗降到微安级别。我的做法是MCU默认进入STOP模式靠RTC闹钟每5分钟唤醒一次唤醒后采集数据、发送报文、再回STOP。传感器这边SHT30本身有单次测量模式也支持掉电模式MQ135的加热电阻功耗大只能从硬件上通过MOS管开关控制测量时再通电。这样整体下来平均功耗能控制在10mA以内用18650电池加太阳能板就能维持数周。7. 写在最后的几点实战心得做完这个项目之后我最大的体会是STM32项目真正做到最后难的不是单片机本身而是传感器数据怎么采集干净、通信怎么稳定、现场问题怎么排查。如果你正在做一个类似的项目建议你在前期就把电源设计、传感器标定、通信协议结构想清楚这几个地方一旦出问题后面改起来成本非常高。最后再分享一个小技巧调试阶段不要直接上FreeRTOS先在裸机while循环里把所有外设调通再切换到RTOS。这样能大幅降低出bug排查的复杂度。等你把裸机版本跑通了再引入任务划分、队列通信整个系统会清晰很多。这套系统到现在我已经迭代了三版从最初的面包板飞线到现在两层PCB加外壳已经在朋友的一个小型零配件仓库里稳定运行了三个多月只出现过两次ESP8266掉线自动重连的情况其余时间都运行正常。它的硬件成本加起来不到两百块却解决了一个需要专人每天跑几趟仓库去查看温湿度、检查烟雾报警的痛点。我觉得这种“花小钱解决真实问题”的嵌入式项目才是STM32最具魅力的应用场景。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门