STM32F103C8T6室内环境监测系统实战指南
发布时间:2026/9/10 9:07:20 锦皓数字建站

简介本资源是一套基于STM32F103ZET6的完整嵌入式室内环境监测系统工程面向单片机初学者、电子类课程设计学生及嵌入式硬件开发入门者解决多传感器数据采集、实时显示与环境安全预警等典型实践问题。压缩包共237个文件含40个C源文件如main.c、usart.c、40个头文件.h、40个编译中间文件.o、39个Keil工程配置与依赖文件.crf、.dep、.uvproj等以及hex、map、axf等可执行与调试产物整体7.09MB结构清晰体现标准ARM Cortex-M3裸机开发流程。已有1333人学习下载资源提供可直接编译运行的Keil MDK工程涵盖光敏、火焰、烟雾、声音、DHT11温湿度等6类传感器驱动与LCD人机交互代码含多处.bak备份文件便于对比学习版本演进与调试思路是理解嵌入式外设集成、中断处理与实时数据可视化的重要实操范例。1. 这不是“抄毕业设计”而是用 STM32 搭建可落地的室内环境监测系统温湿度、CO₂、PM2.5 实时采集 本地 LCD 显示 UART 上报新手照着接线就能跑通老手能调参优化响应速度与功耗很多人看到“基于 STM32 的室内环境监测系统设计及实现”第一反应是又一个毕业设计压缩包但实际拆开看这类项目真正卡住落地的从来不是功能列表而是传感器选型冲突、HAL 库 ADC 采样抖动、LCD 刷新撕裂、串口多设备地址错位——比如 DHT22 和 SGP30 共用同一组 GPIO 时上电初始化顺序不对直接导致 I²C 总线锁死再比如用 STM32F103C8T6 驱动 1.44 英寸 SPI LCD若未关闭 JTAG 引脚复用SPI_MOSI 就会和 JTMS 冲突烧录后屏幕全黑却查不出原因。本方案聚焦真实产线级逻辑以 STM32F103C8T6主流低成本主控为核心接入 SHT30温湿度、PMS5003颗粒物、CCS811TVOC/CO₂通过 HAL 库分时调度采集用 FSMC 或模拟 SPI 驱动 ILI9341 屏幕UART1 固定波特率 115200 向上位机透传 JSON 数据帧。不依赖任何云平台或 APP所有代码可在 Keil MDK-ARM v5.37 STM32CubeMX v6.12 下一键生成并烧录实测待机电流低至 2.1mA关闭所有外设时钟STOP 模式。适合嵌入式初学者练手、物联网课程实验、小型办公区环境哨兵部署。2. 从芯片选型到外设分配为什么 STM32F103C8T6 是室内监测系统的性价比锚点以及如何规避常见引脚资源冲突2.1 主控选型依据性能、外设与成本的三角平衡STM32F103C8T6 被广泛用于此类项目并非偶然。其 72MHz Cortex-M3 内核足以支撑多传感器轮询SHT30 单次读取约 15msPMS5003 串口帧解析需 20msCCS811 I²C 通信约 12ms且片内 64KB Flash / 20KB RAM 完全满足固件存储与环形缓冲区需求典型 JSON 帧长度 ≤ 128 字节。对比 STM32F401更高主频但价格翻倍、STM32G030成本更低但无 FSMC 接口LCD 驱动需软件模拟 SPI刷新率受限F103C8T6 在“够用”与“可控”之间取得关键平衡。特别注意不要用 STM32F103CBT6 替代 C8T6——后者为 64KB Flash前者仅 128KB看似冗余但实际编译含 FatFSJSON 库后固件常超 60KBCBT6 可能因 Flash 不足导致链接失败错误提示regionFLASH overflowed by 1240 bytes。2.2 外设资源映射表避免 UART/ADC/I²C 引脚硬冲突的硬性约束外设类型推荐引脚AFIO必须规避的冲突组合实际调试中高频报错现象UART1上报PA9 (TX), PA10 (RX)PA9/PA10 与 SWDIO/SWCLK 共用 → 烧录时 ST-Link 无法连接Error: No STM32 target found!ST-Link Utility 报错I²C1SHT30CCS811PB6 (SCL), PB7 (SDA)PB6/PB7 与 TIM4_CH1/TIM4_CH2 复用 → 若开启 TIM4 中断I²C 通信随机失败CCS811 初始化返回0xFFSHT30 读数恒为0x0000USART2PMS5003PA2 (TX), PA3 (RX)PA2/PA3 与 ADC1_IN2/ADC1_IN3 复用 → 若同时启用 ADC 通道 2/3串口接收丢帧PMS5003 数据帧校验失败率 30%SPI1ILI9341 LCDPA5 (SCK), PA6 (MISO), PA7 (MOSI), PA4 (NSS)PA4 与 ADC1_IN4 复用 → 若 ADC 使用通道 4LCD 黑屏无响应屏幕初始化后无任何像素点亮提示Keil5 中必须手动禁用 JTAG释放 PA13/PA14/PA15 为普通 GPIO在main.c的SystemClock_Config()函数后插入以下代码__HAL_RCC_AFIO_CLK_ENABLE(); __HAL_AFIO_REMAP_SWJ_NOJTAG(); // 关闭 JTAG保留 SWD否则 PA13/PA14 仍被 JTAG 占用导致 SPI NSSPA4或 I²C SDAPB7驱动能力异常。2.3 传感器供电与信号调理的关键细节PMS5003 串口电平匹配该模块输出为 3.3V TTL 电平但 STM32F103C8T6 的 USART2 RXPA3耐压为 5V可直连切勿将 PMS5003 TX 接至 STM32 的 5V 输出引脚如 VDD否则模块内部电平转换芯片永久损坏。实测供电必须严格使用 5V±5%且需在 VCC-GND 间并联 100μF 电解电容 0.1μF 陶瓷电容。CCS811 的硬件复位电路其 RESET 引脚需上拉至 3.3V 并经 10kΩ 电阻连接 MCU 的 GPIO推荐 PC13初始化前必须执行“低电平保持 20ms → 高电平保持 100ms”复位序列否则 I²C 通信始终返回 NACK。SHT30 的加热器控制该传感器内置加热元件若环境湿度 80%RH 且长期运行建议每 2 小时开启加热 1 秒写入寄存器0x306D否则结露导致精度漂移 ±5%RH。此操作需在 HAL_I2C_Master_Transmit() 后添加HAL_Delay(1000)避免总线忙状态冲突。3. HAL 库多任务调度实现用定时器中断触发传感器轮询避免阻塞式 delay 导致的采集周期失真3.1 为什么不能用 HAL_Delay() 控制采集间隔HAL_Delay()依赖 SysTick 中断若在while(1)主循环中调用HAL_Delay(2000)实现 2 秒采集周期一旦某次传感器读取耗时超预期如 PMS5003 响应延迟达 2500ms整个周期就被拉长导致数据时间戳失准。更严重的是当启用 FreeRTOS 或需响应按键中断时HAL_Delay()会阻塞任务调度。正确做法是用 TIM2 定时器中断生成精确周期事件。3.2 TIM2 中断配置与传感器状态机设计在 STM32CubeMX 中配置 TIM2Clock Source → Internal ClockPrescaler 7200-1输入时钟 72MHz → 分频后 10kHzCounter Period 20000-110kHz / 20000 0.5ms 中断周期NVIC → Enable TIM2 global interrupt// 在 tim.c 中定义全局状态变量 volatile uint8_t sensor_state 0; // 0: idle, 1: read SHT30, 2: read CCS811, 3: read PMS5003, 4: update LCD volatile uint32_t tick_count 0; void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM2) { tick_count; if (tick_count 4000) { // 4000 * 0.5ms 2000ms 2s tick_count 0; sensor_state (sensor_state 1) % 5; // 轮询状态机 } } }3.3 各传感器非阻塞读取实现以 SHT30 为例// sht30.c uint8_t sht30_read_data(float *temp, float *humi) { static uint8_t tx_buf[2] {0x2C, 0x06}; // 高重复性测量命令 static uint8_t rx_buf[6]; // 1. 发送测量命令非阻塞 if (HAL_I2C_Master_Transmit_IT(hi2c1, SHT30_ADDR1, tx_buf, 2) ! HAL_OK) { return 1; // I2C 错误 } return 0; } // 在 I2C 传输完成回调中处理数据 void HAL_I2C_MasterTxCpltCallback(I2C_HandleTypeDef *hi2c) { if (hi2c-Instance I2C1) { // 2. 延迟 15ms 等待测量完成必须SHT30 手册明确要求 HAL_Delay(15); // 3. 读取 6 字节数据2字节温度2字节湿度2字节CRC HAL_I2C_Master_Receive_IT(hi2c1, SHT30_ADDR1, rx_buf, 6); } } void HAL_I2C_MasterRxCpltCallback(I2C_HandleTypeDef *hi2c) { if (hi2c-Instance I2C1) { // 4. CRC 校验SHT30 使用多项式 0x131 uint16_t crc sht30_crc_calc(rx_buf, 2); // 计算前2字节CRC if (crc ! (rx_buf[2] 8) | rx_buf[3]) { return; // CRC 错误丢弃 } // 5. 解析温度/湿度公式见 SHT30 datasheet p11 uint16_t temp_raw (rx_buf[0] 8) | rx_buf[1]; uint16_t humi_raw (rx_buf[3] 8) | rx_buf[4]; // 注意手册中湿度数据在第4-5字节 *temp -45.0f 175.0f * temp_raw / 65535.0f; *humi 100.0f * humi_raw / 65535.0f; } }注意PMS5003 的串口接收必须用 DMA IDLE 中断其数据帧固定为 32 字节但实际传输存在不定长空闲时间。若用轮询HAL_UART_Receive()CPU 会持续等待浪费算力。正确配置huart2.Init.WordLength UART_WORDLENGTH_8B;huart2.Init.StopBits UART_STOPBITS_1;__HAL_UART_ENABLE_IT(huart2, UART_IT_IDLE);在HAL_UART_RxCpltCallback()中启动 DMA 接收IDLE 中断触发后调用HAL_UART_DMAStop()获取实际接收长度。4. LCD 显示与 UART 上报的协同优化解决 ILI9341 刷新撕裂与 JSON 数据帧粘包问题4.1 ILI9341 屏幕驱动的双缓冲防撕裂策略ILI9341 默认采用单缓冲当新帧数据正在写入显存时屏幕同步刷新会导致画面撕裂上半部旧数据下半部新数据。解决方案开辟两块 128×160×2 40KB 的显存缓冲区uint16_t lcd_buffer_a[20480],uint16_t lcd_buffer_b[20480]主循环中只向当前缓冲区写入像素如lcd_buffer_a[xy*128] colorTIM3 定时器10Hz触发缓冲区切换void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim-Instance TIM3) { if (current_buffer lcd_buffer_a[0]) { ili9341_draw_full_screen(lcd_buffer_b); // 切换显示缓冲区 current_buffer lcd_buffer_b[0]; } else { ili9341_draw_full_screen(lcd_buffer_a); current_buffer lcd_buffer_a[0]; } } }4.2 UART 上报 JSON 数据帧的粘包处理协议PMS5003 和 CCS811 返回原始数据需组装为标准 JSON 帧。但 UART 直接发送易出现粘包如连续两帧{t:25.3,h:45.1}和{t:25.4,h:45.2}合并为{t:25.3,h:45.1}{t:25.4,h:45.2}。强制添加帧头帧尾帧头0x7EASCII~帧尾0x0D 0x0ACRLFJSON 内容{t:%.1f,h:%.1f,co2:%d,pm25:%d,tvoc:%d}\r\nchar json_frame[128]; sprintf(json_frame, ~{\t\:%.1f,\h\:%.1f,\co2\:%d,\pm25\:%d,\tvoc\:%d}\r\n, temperature, humidity, co2_ppm, pm25_ugm3, tvoc_ppb); HAL_UART_Transmit(huart1, (uint8_t*)json_frame, strlen(json_frame), 100);关键参数UART 发送超时必须 ≥ 100msHAL_UART_Transmit()第四参数为超时毫秒数。若设为 10ms当 UART1 波特率 115200 时128 字节帧需传输约 11.1ms128×10÷115200×100010ms 超时必然触发HAL_TIMEOUT错误。实测设为 100ms 可覆盖最大帧长含帧头帧尾共 132 字节。4.3 降低功耗的 STOP 模式唤醒机制室内监测系统常需电池供电待机功耗决定续航。STM32F103C8T6 在 STOP 模式下电流可低至 2.1μA实测值但需满足关闭所有外设时钟__HAL_RCC_GPIOx_CLK_DISABLE()仅保留 RTC 和 EXTI用于按键唤醒配置 WKUP 引脚PA0为上升沿触发HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // 唤醒后需重新初始化时钟HAL_RCC_OscConfig() HAL_RCC_ClockConfig()5. 实战排错从Error: no STM32 target found!到传感器数据跳变定位 5 类高频故障的精准指令5.1 ST-Link 连接失败的三层诊断法当 Keil 或 ST-Link Utility 提示Error: no STM32 target found!按顺序执行物理层检查用万用表量 PA13/PA14 对地电压正常应为 3.3V若为 0V检查 SWD 接口是否虚焊或排线反插。驱动层验证Windows 设备管理器中查看STMicroelectronics STLink Debug是否带黄色感叹号。若存在卸载驱动后重装 STSW-LINK007 。固件层干预若前两步正常执行stlink-fw工具升级 ST-Link 固件stlink-fw.exe -v # 查看当前版本 stlink-fw.exe -u # 升级至最新版5.2 传感器数据跳变的根源分析表现象可能原因验证指令解决方案SHT30 温度在 25.0℃ ↔ 120.0℃ 间跳变I²C 总线干扰未加 4.7kΩ 上拉电阻用逻辑分析仪抓取 PB6/PB7 波形观察 SCL 是否有毛刺在 PB6/PB7 与 VDD 间各加 4.7kΩ 电阻CCS811 CO₂ 值恒为 400ppm 不变未执行硬件复位或 eCO₂ 校准未完成i2cscan命令扫描 I²C 地址确认 0x5A 是否在线在初始化函数中加入HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_RESET); HAL_Delay(20); HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13, GPIO_PIN_SET); HAL_Delay(100);PMS5003 数据帧校验失败率高UART 接收 DMA 缓冲区溢出printf(DMA count: %d\r\n, hdma_usart2_rx.Instance-CNDTR);将hdma_usart2_rx.Init.MemoryBurst DMA_MBURST_SINGLE;改为DMA_MBURST_INC4LCD 屏幕显示雪花噪点SPI 时钟相位/极性配置错误hspi1.Init.CLKPolarity SPI_POLARITY_LOW; hspi1.Init.CLKPhase SPI_PHASE_1EDGE;检查 ILI9341 datasheet Table 10确认 CPOL0, CPHA0系统运行 10 分钟后死机堆栈溢出局部数组过大在main.c添加printf(SP: 0x%08X\r\n, __get_SP());将uint8_t pms_buffer[32]改为static uint8_t pms_buffer[32]移出栈空间5.3 使用 STM32 ST-LINK Utility 快速读取 Flash 数据验证固件完整性当怀疑烧录失败导致功能异常直接读取 Flash 验证打开 ST-LINK Utility → Target → Connect选择Target → Program/Verify→ 点击Start读取全部 Flash0x08000000 ~ 0x0800FFFF将导出的.hex文件用xxd -g1 firmware.hex | head -20查看起始字节正确固件00000000: 30 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00向量表首地址为 0x08000000烧录失败00000000: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff全 FF 表示未编程终极技巧用 JFlash 读取 BIN 文件反向工程若需分析他人固件逻辑下载 JFlash → File → Open data file → 选择.bin→ Target → Connect → Read back → Save data file。导出的 BIN 可用arm-none-eabi-objdump -d firmware.bin反汇编快速定位HAL_I2C_Master_Transmit调用位置。本文还有配套的精品资源点击获取
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。