
简介这是一份面向STM32嵌入式开发学习者和本科毕业设计学生的环境监测系统完整源码项目基于STM32微控制器实现多类环境参数的采集、处理与显示可直接用于课程设计或毕业设计参考。压缩包共1100个文件以613个C源码文件和312个头文件为主体同时包含IAR工程文件、Keil工程配置文件、Hex固件、库文件及说明文档覆盖从底层驱动到应用逻辑的完整代码链整体大小为31.88MB。已有183人学习下载。源码均经过本地编译验证、可正常运行评审得分在95分以上项目难度适中结构清晰适合需要快速理解STM32外设驱动、传感器数据采集与嵌入式系统整合开发的读者。无论是按要求复现实验还是在此基础上扩展功能完成毕业设计都能获得扎实的起点。1. 从 DSP 库文件看 stm32 环境监测系统的工程组成一个环境监测毕设源码包真正决定它能不能编译过、跑得稳的往往是藏在工程目录里那几个不起眼的.a静态库文件和数学查表.c文件。打开这个工程我习惯先不看main.c而是先确认libarm_cortexM4lf_math.a这种 DSP 库文件与当前编译器、芯片型号是不是匹配。这个包里的库针对 Cortex-M4 内核做了小端/大端、带不带 FPU 的拆分说明目标板大概率是 STM32F4 系列ADC 采集到的浮点数据可以直接用硬件浮点单元做滤波和特征计算不需要软件模拟 double跑起来差距非常明显。整个系统覆盖温湿度采集、光照/粉尘检测、OLED 显示和串口上报这些常见环节难度在毕业设计层级里中等偏上能在一周内理顺数据流又不至于一眼看完毫无收获。源码包说明中标注“评审分 95 分以上”这种分数只能当参考关键是编译链能不能一次跑通以及换一块板子后还稳不稳定下面按工程实际组织方式逐层拆开。2. 环境监测系统的架构和传感器选型2.1 系统分层采集、处理、显示的边界划分一套完整的 STM32 环境监测工程数据流通常切成四层底层用 HAL 库驱动 ADC、DMA、GPIO 和定时器完成物理量采集中间层把 ADC 原始码值换算成工程单位再用平滑滤波去掉传感器噪声上层把温度、湿度、光照、粉尘浓度打包成结构化文本最外面是 OLED 和串口这种人机出口。分层的好处是替换传感器时不用动业务逻辑比如把 DHT11 换成 SHT30只需要重写中间层的读取函数上报和显示代码原封不动。这个源码在目录结构上也是按这个思路布置的Core/Src下放外设初始化App层放数据处理和调度循环DSP 源文件单独建组和业务代码分开。实际动手改的时候建议先沿着main.c - app_task.c - sensor_xxx.c这条调用链走一遍搞清楚每个.c文件是被谁 include 的再动传感器驱动否则很容易出现“显示正常但上报数据全是 0”这种边界问题。2.2 传感器参数与量程设计环境监测的传感器选型讲究“够用且好买”不是越贵越好毕设场景尤其如此。下面这张表是这个工程里常见的传感器组合和关键参数换传感器时直接对照接口类型和输出方式就能判断驱动力度。传感器输出方式供电接口典型量程分辨率DHT11单总线数字3.3V/5V开漏 GPIO温度 0~50℃、湿度 20%~90%RH1℃、1%RHBME280I2C/SPI 数字3.3VI2C温度 -40~85℃、湿度 0~100%RH0.01℃、0.008%RHGP2Y1010AU0F模拟电压5VADC粉尘浓度 0~0.5mg/m³0.1V/0.1mg光敏电阻 分压模拟电压3.3VADC0~3.3V12bit土壤湿度探头模拟电压3.3VADC0~3.3V12bitDHT11 的温湿度精度一般但胜在驱动简单、不容易坏做毕设演示足够如果评审老师追问“湿度误差”就把 BME280 的 I2C 驱动补上代码里保留原有 DHT11 路径两个传感器同时采上报字段分开。GP2Y1010AU0F 这类粉尘传感器输出的是模拟电压需要接运放或直接用 ADC 采样记得先确认 ADC 输入引脚不是 5V 容忍脚否则长时间工作容易烧引脚。2.3 为什么源码包里会出现 CMSIS-DSP 库看到arm_common_tables.c和arm_dct4_init_f32.c时不要觉得多余这两个文件是 FFT 和 DCT 变换的旋转因子表与初始化表f32后缀表示单精度浮点版本。环境监测工程用 DSP 库最常见的场景不是做频谱分析而是做均值滤波、标准差计算和简单的滑动窗统计。比如粉尘传感器原始电压毛刺大直接映射成浓度值会不停跳动用arm_mean_f32先做一段窗口平均曲线立刻平滑一个数量级#include arm_math.h #define SAMPLE_BUF_SIZE 64 float32_t adc_volt_buf[SAMPLE_BUF_SIZE]; float32_t dust_volt; // 对 64 点电压样本做一次均值滤波结果写入 dust_volt arm_mean_f32(adc_volt_buf, SAMPLE_BUF_SIZE, dust_volt);arm_mean_f32的第二个参数是 blockSize也就是参与平均的样本个数第三个参数是结果指针。注意传入的数组和结果变量都必须是float32_t不要直接用float以外的类型否则在开启 FPU 的工程里可能被隐式转成 double触发软件浮点库的额外开销代码能跑但 CPU 占用率高不少。如果评审老师关心数据可靠性还可以把标准差也加上先用arm_std_f32算出窗口内波动幅度波动太大时在串口日志里打一条 warning表示当前环境不稳定。这个细节在毕业设计答辩时很容易成为加分项。3. 源码工程实现从 CubeMX 配置到串口输出3.1 引脚规划与初始化逻辑拿到这个工程后先用 STM32CubeMX 打开.ioc文件看引脚分配不要直接编译因为板子型号和外设引脚可能和你手上的开发板不一致。下面是一组兼容性较好的分配方式适合把系统接到一块普通 F4 开发板上调试外设引脚工作模式说明DHT11PB5GPIO 开漏输出/输入单总线数据线光敏电阻分压PA0ADC1_IN0光照强度采样粉尘传感器PA1ADC1_IN1粉尘电压采样土壤湿度探头PA2ADC1_IN2土壤湿度采样OLEDPB6/PB7I2C1SSD1306 显示屏串口调试口PA9/PA10USART1数据上报CubeMX 里的关键设置是 ADC 要打开 ScanConvMode 和 ContinuousConvMode通道数设为 3DMA 选择 Circular 模式。初始化生成的代码里MX_ADC1_Init会逐通道配置采样时间和 Rank下面这段是手动补出来的多通道配置片段ADC_ChannelConfTypeDef sConfig {0}; // 通道 0光敏电阻 sConfig.Channel ADC_CHANNEL_0; sConfig.Rank ADC_REGULAR_RANK_1; sConfig.SamplingTime ADC_SAMPLETIME_84CYCLES; HAL_ADC_ConfigChannel(hadc1, sConfig); // 通道 1粉尘传感器 sConfig.Channel ADC_CHANNEL_1; sConfig.Rank ADC_REGULAR_RANK_2; HAL_ADC_ConfigChannel(hadc1, sConfig); // 通道 2土壤湿度 sConfig.Channel ADC_CHANNEL_2; sConfig.Rank ADC_REGULAR_RANK_3; HAL_ADC_ConfigChannel(hadc1, sConfig);Rank 必须从 1 开始按顺序排列不能跳号。SamplingTime 决定采样电容充电时间84Cycles 对应 12MHz 左右 ADC 时钟下约 7us适合中等阻抗的信号源如果传感器输出阻抗大导致读数偏低就把采样时间调到 480Cycles。DMA 模式选 Circular配合HAL_ADC_Start_DMA启动后CPU 不用管每次转换DMA 会自动把三个通道的结果轮询写到内存数组里。3.2 ADC 与 DMA 的缓冲设计启动 DMA 采样时缓冲区长度不是 3而是一个通道一组、一组若干次采样。常见做法是每个通道存 32 或 64 个点这样滤波和 DMA 中断解耦即使主循环偶尔忙不过来数据也不会丢#define ADC_CH_NUM 3 #define ADC_SAMPLE_CNT 64 uint16_t adc_raw_buf[ADC_CH_NUM * ADC_SAMPLE_CNT]; // 三通道循环采样共 192 个采样点 HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_raw_buf, ADC_CH_NUM * ADC_SAMPLE_CNT);DMA 传输完成中断里只置一个通道就绪标志不做业务处理。主循环发现标志位置位后按adc_raw_buf[i * ADC_SAMPLE_CNT j]的索引把各通道数据取出来。这里有个很容易踩的坑CubeMX 默认生成的是单缓冲模式DMA 写满后停住需要再次调用HAL_ADC_Start_DMA才能继续。如果发现上位机数据每隔一小段就卡一下多半是缓冲模式没改成 Circular或者NbrOfConversion没改成 3改完以后数据流就连续了。换算电压时要用 3.3V 而不是 5V 作为参考如果板上有外部基准或者 PA0 接了分压电阻还要先测一下空载电压float adc_volt (float)adc_raw_buf[0] * 3.3f / 4095.0f;这段代码里 4095 是 12 位 ADC 的满量程值如果用的是 16 位外部 ADC 或 STM32F3 系列的 12 位但配置成 10 位都要对应调整。电流型传感器一般外接 I/V 电阻换算公式要额外乘一个跨阻系数不要直接套电压映射。3.3 DHT11 单总线时序读取DHT11 的时序精度是微秒级的HAL_Delay最小粒度只有 1ms直接用会读到全 0xFF。工程里一般会补一个基于 DWT 的微秒延时函数DWT 是内核调试外设不需要额外定时器static void delay_us(uint32_t us) { DWT-CYCCNT 0; while (DWT-CYCCNT us * (SystemCoreClock / 1000000)); }读取一个字节的核心逻辑是判断高电平宽度DHT11 的数据位 “0” 高电平约 26us“1” 高电平约 70us所以采样点放在高电平开始后的 40us 处最稳妥uint8_t dht11_read_byte(void) { uint8_t i, byte 0; for (i 0; i 8; i) { uint16_t timeout 10000; while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_RESET) if (--timeout 0) return 0xFF; delay_us(40); if (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET) { byte | (0x80 i); } timeout 10000; while (HAL_GPIO_ReadPin(DHT11_GPIO_Port, DHT11_Pin) GPIO_PIN_SET) if (--timeout 0) return 0xFF; } return byte; }逻辑说明第一个 while 等待低电平结束代表数据位开始延时 40us 后判断数据线电平如果还是高说明高电平宽度超过 40us判断为 “1”否则为 “0”。两个 while 都加了 timeout防止传感器损坏或接线松动时主循环卡死。返回后还要校验湿度整数、湿度小数、温度整数、温度小数、校验和五位数据校验和等于前四位累加的低 8 位才认为本次读取有效无效数据直接丢弃等下一轮采样周期再读不要用脏值刷新 OLED。3.4 串口数据打包与 printf 重定向上报数据用 JSON 行格式每个字段是 key-value一行一条记录后续用上位机解析非常方便。MDK 工程里重定向printf到串口最省事的方式是开启 MicroLIB 然后实现fputcint fputc(int ch, FILE *f) { HAL_UART_Transmit(huart1, (uint8_t *)ch, 1, 0xFFFF); return ch; }主循环里这样输出一行数据printf({\temp\:%.1f,\humi\:%.1f,\dust\:%.3f,\light\:%.1f}\r\n, temp, humi, dust_volt, light_lux);逻辑说明%.1f表示保留一位小数dust_volt已经是换算后的电压值而不是原始 ADC 码。注意转义字符\必不可少否则上位机json.loads直接报错。GCC 工具链下fputc改成_write或_write_r具体函数名看 syscalls 实现IAR 则是在DLib里配置__write回调。串口波特率建议固定 1152008N1和本节后面 Python 脚本保持一致如果接蓝牙模块或 ESP8266波特率可能需要降到 9600 提高稳定性。4. 编译链接与 DSP 库排错4.1 Keil 里正确链接 math 库源码包里那六个.a文件不是全都要加入工程选错一个就会出现一堆 “undefined symbol” 或者 “library is not compatible” 错误。先看文件命名的三段信息第一段是编译器厂商libarm是 ARMCC/AC5 和 AC6 通用iar是 IAR 专用第二段cortexM4说明目标内核是 Cortex-M4第三段带f表示硬浮点版本带b表示大端。STM32F407/F429 这种带 FPU 的芯片在 Keil 里用 AC5 编译时选libarm_cortexM4lf_math.a用 IAR 时选iar_cortexM4lf_math.a。F103 是 Cortex-M3 内核不能直接用这些库需要换成arm_cortexM3l_math.a。把库文件加入工程后还要在 C/C 选项卡里定义两个宏缺少任何一个都会导致arm_math.h条件编译走错分支ARM_MATH_CM4 __FPU_PRESENT 1如果使用的是 AC6还需要确保 Target 页里选了 “Use FPU” 而不是 “Not used”。编译一次后打开生成的.map文件确认库有没有真正链接进去。Windows 命令行下在工程目录执行findstr /C:arm_mean_f32 build\project.map有输出说明arm_mean_f32已经解析到库里对应的目标文件被链接器拉进来了没有输出则说明函数被优化掉或者头文件路径不对。检查时注意.map文件路径以工程实际输出位置为准。4.2 常见链接错误与定位方法最典型的错误是L6218E: Undefined symbol arm_rfft_fast_init_f32这种符号名字以arm_开头但库文件找不到。原因一般是把.a文件加进工程后编译器选项里的宏定义没配对arm_math.h认为当前芯片不支持 DSP 指令很多接口函数被#ifdef排除掉了。解决办法不是换库而是回 4.1 节核对ARM_MATH_CM4宏。第二种常见问题是编译时报错提示某个源文件里的浮点函数和libarm_cortexM4lf_math.a不兼容。这通常是工程里部分.c文件用 AC5 编译、部分用 AC6 编译导致的混链。Keil 中选中所有源文件统一在 Options 里修改编译器版本不要只改单个文件。第三种问题是 IAR 工程误用了libarm开头库链接器直接报Fatal Error[Lc002]把库换成iar_前缀即可。4.3 硬件层面的稳定性问题软件编译过了不代表系统能稳定工作。晶振起振失败是最隐蔽的一个坑系统时钟没有跑在 168MHzADC 采样率和 DHT11 时序都会偏表现是串口输出乱码或者温湿度每隔几十秒跳一次。换板子时先检查 CubeMX 里 HSE_VALUE 是不是 25MHz 或 8MHz和开发板实际晶振对齐如果晶振匹配没问题再排查 PLL 配置。ADC 参考电压不稳定会导致同样的光线下电压值漂移尽量用开发板上独立的 3.3V 基准给 PA0 供电不要把伺服电机或继电器和传感器共用电源。DHT11 引线超过 20cm 时建议在数据线和 GND 之间加一个 4.7kΩ 上拉到 3.3V采样周期不要低于 1 秒很多偶发读数为 0 的问题都能靠这两条改善。注意换芯片型号后一定要在 Debug 页重新选 Flash 算法否则烧录到一半会报 “No Algorithm found”这个和 DSP 库选择是独立问题但经常被误判为代码问题。5. 上位机验证与传感器数据校准5.1 用 Python 把串口数据画成实时曲线调试环境监测系统时光看串口助手里的文本不够直观一个几十行的 Python 脚本就能把温度曲线实时画出来也方便答辩时演示数据是连续变化的import serial import json import matplotlib.pyplot as plt from collections import deque ser serial.Serial(COM3, 115200, timeout1) temperatures deque(maxlen200) plt.ion() fig, ax plt.subplots() while True: line ser.readline() if line.startswith(b{): data json.loads(line) temperatures.append(data[temp]) ax.clear() ax.plot(temperatures) ax.set_ylabel(temperature (C)) ax.set_xlabel(sample) plt.pause(0.1)deque(maxlen200)只保留最近 200 个点内存占用恒定长时间挂机也不会卡。json.loads(line)要求单片机侧输出的每一行都是完整 JSON不能有额外的调试打印混进去所以业务代码里的printf调试信息要打到另一个串口或用LOG_LEVEL宏关掉。如果曲线跳变频繁先去查滤波窗口长度而不是怀疑传感器坏了。5.2 两点校准与线性修正环境监测系统里最容易丢分的环节是“测出来的温度/湿度与标准表对不上”。先用传感器采两个已知状态下的原始电压或 ADC 码值比如 0℃ 冰水混合物和沸水考虑海拔沸水温度要修正也可以用标准温湿度计做两个稳定工况点校准点参考值传感器读值低温点15.0℃15.8℃高温点30.0℃31.2℃校准后的输出用线性映射修正y_out (y_s1 - y_s0) / (x_s1 - x_s0) * (x_raw - x_s0) y_s0其中x_raw是传感器当前读值x_s0和x_s1是两个校准点的传感器读值y_s0和y_s1是参考值。实现时把四个系数写成结构体在校准模式下通过串口命令写入 Flash不要每次编译都改代码。DHT11 出厂时以 5V 供电标定3.3V 供电会在湿度高位产生正偏差这是很多环境监测项目读数偏高的原因校准能修正一部分但极端湿度下误差依然存在答辩时主动说明这一点反而显得严谨。5.3 扩展成远程上报节点的最小改动源码里串口输出的 JSON 行已经让系统具备接入外部模块的能力。把 DHT11 和 ADC 数据打包后不打印到调试串口而是通过预留的 USART2 发到一个 AT 指令 WiFi 模块模块以 TCP 方式把数据推到局域网服务器。需要注意 TX 引脚的电平匹配5V 模块要在 STM32 TX 和模块 RX 之间串一个 1kΩ 电阻。整个工程的数据流不改变只替换出口层评审时可以在现场用手机热点演示一次远程查看数据这比单纯在屏幕上显示数字更有说服力。本文还有配套的精品资源点击获取