
简介这是一套基于STM32F103ZET6开发的嵌入式环境监测系统完整工程面向嵌入式初学者与课程设计实践者解决温湿度、气压、可燃气体浓度等多参数实时采集与本地显示问题适用于智能农业、实验室环境监控等典型应用场景。资源包共156个文件包含39个头文件.h定义外设驱动与功能接口、38个C源码.c实现传感器通信、串口调试及LCD显示逻辑辅以编译中间文件.o/.d/.crf、Keil工程配置.uvprojx/.uvoptx、烧录镜像.hex及调试辅助脚本.bat整体压缩后仅3.05MB结构规范便于理解STM32标准外设库开发流程。已有6399人学习下载提供完整可运行工程、B站实机演示视频及配套博文详解涵盖DHT11温湿度、BMP280气压、MQ2气体传感器的I²C/单总线驱动适配以及1602液晶的底层显示控制是掌握多传感器融合与嵌入式人机交互的优质实践范例。1. 为什么用 STM32 做环境检测系统不是“能跑就行”而是要稳在传感器融合、低功耗与实时串口反馈上很多初学者拿到“基于STM32的环境检测系统”这个题目第一反应是接几个温湿度传感器串口打印数据烧录成功就算完成。但实际交付场景中——比如农业大棚监测节点、实验室温控终端、或毕业设计答辩现场——常出现DHT22读数跳变超±5℃、BH1750光照值卡死不更新、USART发送中断频繁丢帧、Keil编译报错error: no stm32 target found!或stm32f10x.h(298): error、电池供电下连续运行48小时后系统休眠唤醒失败。这些问题根源不在代码逻辑而在于芯片级外设配置与物理层协同被严重低估。本方案聚焦 STM32F103C8T6主流入门型号 多传感器融合 USART 虚拟串口稳定输出 Keil MDK-ARM 环境零错误构建覆盖从硬件连接、寄存器级初始化、传感器驱动健壮性处理到 Keil 工程配置避坑的全链路。适合已掌握 C 语言基础、正搭建第一个 STM32 实战项目的开发者也给有经验者提供可复用的传感器校准模板和低功耗唤醒调试技巧。2. 选型与硬件连接为什么 STM32F103 DHT22 BH1750 BMP280 是环境检测的黄金组合2.1 为什么锁定 STM32F103C8T6 而非其他型号在stm32f10x系列中F103C8T6 是性价比与资源平衡的典型代表资源匹配度高64KB Flash / 20KB RAM足够容纳多传感器驱动 数据滤波 USART 协议栈外设完备性3 个 USART其中 USART1 可复用为虚拟 COM 口、2 个 I²C支持 BH1750/BMP280 共用总线、1 个 SPI预留扩展 SD 卡或 LoRa、12 位 ADC可接土壤湿度模拟传感器开发生态成熟Keil 官网Keil.STM32F1xx_DFP.2.3.0.pack支持完善.\libraries\cmsis\cm3\devicesupport\st\stm32f10x\stm32f10x.h文件路径明确避免error: no stm32 target found!类芯片包缺失问题硬件兼容性强ST-Link V2 下载器即插即用无需额外驱动Windows 10/11 自带 WinUSB规避stm32 virtual com port 叹号设备管理器异常。提示若使用keil uvision5 设备不匹配请确认 Project → Options for Target → Device 标签页中已正确选择STMicroelectronics → STM32F103C8且勾选Use MicroLIB减少 printf 占用栈空间。2.2 传感器选型依据与物理连接规范传感器接口类型关键参数连接引脚F103C8T6注意事项DHT22单总线GPIO 模拟温度 ±0.5℃湿度 ±2%RH响应时间 2sPA0任意 GPIO需软件时序严禁与其他 I²C 设备共用上拉电阻单总线需 5.1kΩ 上拉至 3.3VBH1750I²C标准模式光照 1–65535 lux精度 ±10%PB6(SCL)、PB7(SDA)4.7kΩ 上拉SCL/SDA 必须接相同阻值上拉电阻否则I²C ACK 失败地址默认0x23ADDR 引脚接地BMP280I²C高速模式温度 ±0.5℃气压 ±0.12hPa支持海拔计算PB6(SCL)、PB7(SDA)与 BH1750 共用总线BMP280 地址0x76SDO 接 GND需在 I²C 初始化中启用I2C_ACK_Enable否则BMP280 init failed2.2.1 硬件连接实操要点晶振电容计算外部 8MHz HSE 晶振必须配 22pF 贴片电容非“按经验选20–30pF”实测stm32 晶振电容计算公式为C 2 × (C1 ∥ C2) Cstray其中Cstray ≈ 3–5pF故选用 22pF 可确保起振稳定性避免Keil 错误target DLL has been cancelled电源去耦VDD/VDDA 引脚旁必须放置 100nF 10μF 陶瓷电解电容否则 ADC 读数波动 10LSBUSART 虚拟串口接法PA9(TX) → CH340 TXPA10(RX) ← CH340 RXGND 必须共地否则 Keil 无法下载或串口乱码。3. Keil MDK 工程构建与 USART 配置绕过stm32f10x.h(298): error和no target found的完整流程3.1 Keil 环境搭建零错误步骤3.1.1 安装与芯片包配置下载 Keil MDK-ARM v5.36避开keil 5.36下载中捆绑广告软件的第三方站安装时取消勾选“Install Pack Installer”避免keil mdk 打开自动弹出 pack installer 怎么关闭干扰启动 Keil → Pack Installer → 搜索STM32F1xx→ 安装Keil.STM32F1xx_DFP.2.3.0.pack勿用 2.4.0 版本其stm32f10x.h第 298 行定义有变更引发error新建工程 → Target 页选择STM32F103C8→ Output 页勾选Create HEX File和Browse Information在Project → Options for Target → C/C中添加预定义宏USE_STDPERIPH_DRIVER, STM32F10X_MD缺一不可否则stm32f10x.h报错。3.1.2 USART1 初始化代码与关键参数说明// usart.c #include stm32f10x.h #include stdio.h void USART1_Config(void) { GPIO_InitTypeDef GPIO_InitStruct; USART_InitTypeDef USART_InitStruct; // 1. 使能时钟APB2 为 GPIOA 和 USART1 提供时钟 RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_USART1, ENABLE); // 2. 配置 PA9(TX) 为复用推挽输出PA10(RX) 为浮空输入 GPIO_InitStruct.GPIO_Pin GPIO_Pin_9; GPIO_InitStruct.GPIO_Mode GPIO_Mode_AF_PP; // 复用推挽非普通推挽 GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStruct); GPIO_InitStruct.GPIO_Pin GPIO_Pin_10; GPIO_InitStruct.GPIO_Mode GPIO_Mode_IN_FLOATING; // 浮空输入非上拉 GPIO_Init(GPIOA, GPIO_InitStruct); // 3. USART1 参数115200bps, 8N1, 无硬件流控 USART_InitStruct.USART_BaudRate 115200; // 波特率对应 CH340 默认速率 USART_InitStruct.USART_WordLength USART_WordLength_8b; // 8位数据位 USART_InitStruct.USART_StopBits USART_StopBits_1; // 1位停止位 USART_InitStruct.USART_Parity USART_Parity_No; // 无校验 USART_InitStruct.USART_HardwareFlowControl USART_HardwareFlowControl_None; // 关闭RTS/CTS USART_InitStruct.USART_Mode USART_Mode_Tx | USART_Mode_Rx; // 发送接收 USART_Init(USART1, USART_InitStruct); // 4. 使能 USART1 和接收中断用于调试命令交互 USART_ITConfig(USART1, USART_IT_RXNE, ENABLE); // 接收非空中断 USART_Cmd(USART1, ENABLE); }注意GPIO_Mode_AF_PP是关键若误设为GPIO_Mode_Out_PPTX 将无法输出信号GPIO_Mode_IN_FLOATING对应 RX若设为GPIO_Mode_IPU上拉可能引入干扰噪声导致接收误判。3.1.3 重定向 printf 到 USART1 的底层实现// syscalls.c必须添加到工程中 #include stm32f10x.h #include usart.h int fputc(int ch, FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); // 等待发送完成 USART_SendData(USART1, (uint8_t) ch); return ch; } int fgetc(FILE *f) { while (USART_GetFlagStatus(USART1, USART_FLAG_RXNE) RESET); // 等待接收就绪 return (int) USART_ReceiveData(USART1); }此实现替代了printf底层输出避免keil 缺少 axf因未链接标准库while (USART_GetFlagStatus(...))循环比HAL_Delay()更可靠杜绝发送缓冲区溢出若需支持scanf必须启用USART_IT_RXNE并在USART1_IRQHandler中清中断标志。4. 多传感器驱动与数据融合解决 DHT22 时序抖动、I²C 地址冲突与 BMP280 校准偏差4.1 DHT22 单总线驱动的抗干扰实现DHT22 对时序敏感标准库Delay_us()易受编译器优化影响。采用SysTick 定时器精准延时// dht22.c #include stm32f10x.h #include dht22.h #define DHT22_PORT GPIOA #define DHT22_PIN GPIO_Pin_0 void DHT22_Init(void) { GPIO_InitTypeDef GPIO_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); GPIO_InitStruct.GPIO_Pin DHT22_PIN; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_PP; // 初始设为输出 GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(DHT22_PORT, GPIO_InitStruct); GPIO_SetBits(DHT22_PORT, DHT22_PIN); // 上拉 } uint8_t DHT22_ReadData(uint16_t *humidity, uint16_t *temperature) { uint8_t data[5] {0}; uint8_t i, j; // 1. 主机启动信号拉低至少 18ms GPIO_ResetBits(DHT22_PORT, DHT22_PIN); Delay_us(20000); // 精确 20ms // 2. 拉高 20–40us等待 DHT22 响应 GPIO_SetBits(DHT22_PORT, DHT22_PIN); Delay_us(30); // 3. 检测 DHT22 响应80us 低 80us 高 if (!GPIO_ReadInputDataBit(DHT22_PORT, DHT22_PIN)) { Delay_us(80); if (GPIO_ReadInputDataBit(DHT22_PORT, DHT22_PIN)) { Delay_us(80); // 4. 读取 40 位数据5 字节 for (i 0; i 5; i) { data[i] 0; for (j 0; j 8; j) { while (!GPIO_ReadInputDataBit(DHT22_PORT, DHT22_PIN)); // 等待高电平开始 Delay_us(30); // 测量高电平持续时间 if (GPIO_ReadInputDataBit(DHT22_PORT, DHT22_PIN)) { data[i] | (1 (7 - j)); } while (GPIO_ReadInputDataBit(DHT22_PORT, DHT22_PIN)); // 等待低电平 } } // 5. 校验和验证 if (data[0] data[1] data[2] data[3] data[4]) { *humidity (data[0] 8) | data[1]; *temperature (data[2] 8) | data[3]; return 0; // 成功 } } } return 1; // 失败 }Delay_us()基于 SysTick精度 ±1us规避keil c51风格的__nop()不可靠问题关键防抖逻辑每次读取前执行GPIO_SetBits()拉高总线避免残留低电平干扰若返回1需在主循环中加入if (DHT22_ReadData(...) ! 0) { Delay_ms(2000); continue; }强制 2 秒重试。4.2 I²C 多设备共存与 BMP280 校准补偿4.2.1 I²C 初始化与地址仲裁// i2c.c void I2C1_Config(void) { GPIO_InitTypeDef GPIO_InitStruct; I2C_InitTypeDef I2C_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_I2C1, ENABLE); // PB6/SCL, PB7/SDA 设为开漏输出 GPIO_InitStruct.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStruct.GPIO_Mode GPIO_Mode_Out_OD; // 开漏非推挽 GPIO_InitStruct.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStruct); // 上拉电阻4.7kΩ 至 3.3V硬件必须 GPIO_SetBits(GPIOB, GPIO_Pin_6 | GPIO_Pin_7); I2C_InitStruct.I2C_Ack I2C_Ack_Enable; // 必须开启 ACK否则 BMP280 不响应 I2C_InitStruct.I2C_AcknowledgedAddress I2C_AcknowledgedAddress_7bit; // 7位地址 I2C_InitStruct.I2C_ClockSpeed 100000; // 标准模式 100kHz I2C_InitStruct.I2C_DutyCycle I2C_DutyCycle_16_9; I2C_InitStruct.I2C_Mode I2C_Mode_Sm; // 标准模式 I2C_InitStruct.I2C_OwnAddress1 0x00; // 主机无地址 I2C_Init(I2C1, I2C_InitStruct); I2C_Cmd(I2C1, ENABLE); }GPIO_Mode_Out_OD是 I²C 硬件要求若误设为AF_OD或AF_PP将导致usart、uart、i2c、spi区别中 I²C 通信失败I2C_Ack_Enable决定从机是否返回 ACKBMP280/BH1750 均需此设置。4.2.2 BMP280 温度补偿与海拔计算BMP280 原始温度值存在系统性偏差±0.8℃需用出厂校准参数修正// bmp280.c简化核心逻辑 typedef struct { int16_t dig_T1; uint16_t dig_T2; int16_t dig_T3; } bmp280_calib_t; bmp280_calib_t calib; void BMP280_ReadCalibration(void) { // 读取 0x88–0x9F 寄存器填充 calib 结构体具体寄存器地址见 datasheet // 此处省略 I²C 读取代码重点在补偿公式 } float BMP280_CompensateTemperature(int32_t adc_T) { int32_t var1, var2, T; var1 ((((adc_T 3) - ((int32_t)calib.dig_T1 1))) * ((int32_t)calib.dig_T2)) 11; var2 (((((adc_T 4) - ((int32_t)calib.dig_T1)) * ((adc_T 4) - ((int32_t)calib.dig_T1))) 12) * ((int32_t)calib.dig_T3)) 14; T var1 var2; return (T * 5 128) 8; // 返回 ℃精度 0.01℃ }dig_T1/T2/T3为出厂校准系数必须在初始化时读取并缓存否则温度误差 1℃海拔计算公式altitude 44330 * (1.0 - pow(pressure / seaLevelPressure, 0.1903))其中seaLevelPressure取本地气象站数据如 1013.25 hPa。5. 低功耗运行与数据上报技巧让电池供电系统连续工作 72 小时以上5.1 STOP 模式唤醒与传感器轮询策略环境检测系统多数时间处于空闲应启用 STOP 模式降低功耗// main.c 片段 void EnterStopMode(void) { // 1. 配置 WKUP 引脚PA0为上升沿唤醒 EXTI_InitTypeDef EXTI_InitStruct; NVIC_InitTypeDef NVIC_InitStruct; RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE); GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0); EXTI_InitStruct.EXTI_Line EXTI_Line0; EXTI_InitStruct.EXTI_Mode EXTI_Mode_Interrupt; EXTI_InitStruct.EXTI_Trigger EXTI_Trigger_Rising; EXTI_InitStruct.EXTI_LineCmd ENABLE; EXTI_Init(EXTI_InitStruct); NVIC_InitStruct.NVIC_IRQChannel EXTI0_IRQn; NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority 0; NVIC_InitStruct.NVIC_IRQChannelSubPriority 0; NVIC_InitStruct.NVIC_IRQChannelCmd ENABLE; NVIC_Init(NVIC_InitStruct); // 2. 进入 STOP 模式所有时钟关闭仅 LSE/LSI 运行 PWR_EnterSTOPMode(PWR_Regulator_LowPower, PWR_STOPEntry_WFI); } void EXTI0_IRQHandler(void) { if (EXTI_GetITStatus(EXTI_Line0) ! RESET) { EXTI_ClearITPendingBit(EXTI_Line0); // 唤醒后重新初始化传感器、读取数据、发送 USART DHT22_ReadData(hum, temp); printf(T:%d.%02d H:%d.%02d\r\n, temp/100, temp%100, hum/100, hum%100); Delay_ms(1000); // 给传感器稳定时间 EnterStopMode(); // 再次进入低功耗 } }PWR_EnterSTOPMode()功耗约 2μA远低于SLEEP模式的 100μA唤醒源必须为外部中断如按键、光照突变不可依赖 SysTickSTOP 模式下停振若使用stm32 鱼缸场景可将水位传感器接入 PA0实现“水位异常→唤醒→报警”。5.2 USART 数据上报的可靠性增强为应对stm32 和变频器通讯等工业场景的干扰增加数据帧校验// 添加帧头、长度、CRC16、帧尾 #define FRAME_HEADER 0xAA #define FRAME_FOOTER 0x55 typedef struct { uint8_t header; uint8_t len; uint8_t data[32]; uint16_t crc; uint8_t footer; } frame_t; uint16_t CRC16(uint8_t *data, uint8_t len) { uint16_t crc 0xFFFF; for (uint8_t i 0; i len; i) { crc ^ data[i]; for (uint8_t j 0; j 8; j) { if (crc 0x0001) crc (crc 1) ^ 0xA001; else crc 1; } } return crc; } void SendFrame(uint8_t *payload, uint8_t plen) { frame_t frame; frame.header FRAME_HEADER; frame.len plen; memcpy(frame.data, payload, plen); frame.crc CRC16((uint8_t*)frame.len, plen 2); // 校验 len data frame.footer FRAME_FOOTER; for (uint8_t i 0; i sizeof(frame_t); i) { USART_SendData(USART1, ((uint8_t*)frame)[i]); while (USART_GetFlagStatus(USART1, USART_FLAG_TC) RESET); } }CRC16使用 Modbus 标准多项式0xA001比简单累加更抗突发干扰帧结构强制header/len/crc/footer接收端可快速丢弃乱码帧避免keil 调试时因数据错位导致逻辑崩溃。5.3 Keil 工程优化生成 BIN 文件用于 OTA 升级毕业设计或产品化需支持远程升级Keil 必须输出纯二进制Project → Options for Target → Output → 勾选Create HEX File在User标签页Run #1 输入fromelf --bin --output .\Objects\env_monitor.bin .\Objects\env_monitor.axfenv_monitor.bin即可直接烧录到 Flash 0x08000000 地址若需keil 生成 bin 文件后校验可用md5sum env_monitor.bin记录指纹避免keil 下载时文件损坏。本文还有配套的精品资源点击获取