下面是一个基于STM32的智能环境监测站设计方案,使用Keil MDK-ARM开发环境。这个系统集成了多种传感器,并通过OLED显示数据,同时具备数据存储和报警功能。
[STM32F4系列MCU]
├── I2C总线
│ ├── SHT30温湿度传感器
│ ├── BMP280气压传感器
│ ├── BH1750光照传感器
│ └── OLED显示屏
├── UART接口
│ ├── USB转串口(调试输出)
│ └── ESP8266 WiFi模块
├── SPI接口
│ └── W25Q128 Flash存储芯片
└── GPIO
├── 蜂鸣器(报警)
└── LED指示灯
graph TD
A[系统初始化] --> B[外设初始化]
B --> C[定时器配置]
C --> D{主循环}
D --> E[传感器数据采集]
E --> F[数据处理]
F --> G[OLED显示]
F --> H[数据存储]
F --> I[报警检测]
I --> J[触发报警]
F --> K[数据上传]
核心代码实现
传感器驱动与数据采集
// sensor.h
#ifndef __SENSOR_H
#define __SENSOR_H
#include "stm32f4xx_hal.h"
// 传感器I2C地址
#define SHT30_ADDR 0x44
#define BMP280_ADDR 0x76
#define BH1750_ADDR 0x23
// 传感器数据结构
typedef struct {
float temperature;
float humidity;
float pressure;
float altitude;
uint16_t light;
} SensorData;
// 初始化传感器
void Sensors_Init(I2C_HandleTypeDef *hi2c);
// 读取所有传感器数据
void Sensors_ReadAll(SensorData *data);
#endif
// sensor.c
#include "sensor.h"
#include "sht30.h"
#include "bmp280.h"
#include "bh1750.h"
static I2C_HandleTypeDef *hi2c_ptr;
void Sensors_Init(I2C_HandleTypeDef *hi2c) {
hi2c_ptr = hi2c;
SHT30_Init(hi2c_ptr);
BMP280_Init(hi2c_ptr);
BH1750_Init(hi2c_ptr);
}
void Sensors_ReadAll(SensorData *data) {
SHT30_Read(hi2c_ptr, &data->temperature, &data->humidity);
BMP280_Read(hi2c_ptr, &data->pressure, &data->altitude);
data->light = BH1750_ReadLight(hi2c_ptr);
}
主程序逻辑
// main.c
#include "stm32f4xx_hal.h"
#include "sensor.h"
#include "oled.h"
#include "w25qxx.h"
#include "esp8266.h"
// 全局变量
SensorData env_data;
uint32_t last_sensor_read = 0;
uint32_t last_data_save = 0;
uint32_t last_data_send = 0;
// 报警阈值
#define TEMP_ALARM_THRESHOLD 35.0f
#define HUMI_ALARM_THRESHOLD 80.0f
// 外设句柄
I2C_HandleTypeDef hi2c1;
UART_HandleTypeDef huart1; // 调试串口
UART_HandleTypeDef huart2; // ESP8266
SPI_HandleTypeDef hspi1; // Flash存储器
int main(void) {
HAL_Init();
SystemClock_Config();
// 初始化外设
MX_GPIO_Init();
MX_I2C1_Init();
MX_USART1_UART_Init();
MX_USART2_UART_Init();
MX_SPI1_Init();
// 初始化传感器
Sensors_Init(&hi2c1);
// 初始化OLED
OLED_Init(&hi2c1);
// 初始化Flash
W25Qxx_Init(&hspi1);
// 初始化WiFi模块
ESP8266_Init(&huart2);
// 启动定时器
HAL_TIM_Base_Start_IT(&htim2);
while (1) {
uint32_t current_time = HAL_GetTick();
// 每2秒读取一次传感器数据
if (current_time - last_sensor_read >= 2000) {
last_sensor_read = current_time;
Sensors_ReadAll(&env_data);
// 显示数据
OLED_DisplayData(&env_data);
// 检测报警条件
if (env_data.temperature > TEMP_ALARM_THRESHOLD ||
env_data.humidity > HUMI_ALARM_THRESHOLD) {
// 触发报警
HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_SET);
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_SET);
} else {
HAL_GPIO_WritePin(BUZZER_GPIO_Port, BUZZER_Pin, GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_GPIO_Port, LED_Pin, GPIO_PIN_RESET);
}
}
// 每10秒保存一次数据到Flash
if (current_time - last_data_save >= 10000) {
last_data_save = current_time;
W25Qxx_SaveData(&env_data);
}
// 每30秒发送一次数据到云平台
if (current_time - last_data_send >= 30000) {
last_data_send = current_time;
ESP8266_SendData(&env_data);
}
}
}
// 定时器中断处理
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) {
if (htim->Instance == TIM2) {
// 定时器任务可以在这里处理
}
}
OLED显示实现
// oled.c
#include "oled.h"
#include "oled_font.h"
void OLED_DisplayData(SensorData *data) {
OLED_Clear();
// 显示温度
OLED_ShowString(0, 0, "Temp:", 16);
OLED_ShowFloat(50, 0, data->temperature, 2, 16);
OLED_ShowString(100, 0, "C", 16);
// 显示湿度
OLED_ShowString(0, 2, "Humi:", 16);
OLED_ShowFloat(50, 2, data->humidity, 2, 16);
OLED_ShowString(100, 2, "%", 16);
// 显示气压
OLED_ShowString(0, 4, "Press:", 16);
OLED_ShowFloat(50, 4, data->pressure, 2, 16);
OLED_ShowString(100, 4, "hPa", 16);
// 显示光照
OLED_ShowString(0, 6, "Light:", 16);
OLED_ShowNum(50, 6, data->light, 5, 16);
OLED_ShowString(100, 6, "lux", 16);
// 显示报警状态
if (data->temperature > TEMP_ALARM_THRESHOLD) {
OLED_ShowString(0, 7, "HIGH TEMP!", 16);
} else if (data->humidity > HUMI_ALARM_THRESHOLD) {
OLED_ShowString(0, 7, "HIGH HUMI!", 16);
}
}
数据存储实现
// w25qxx.c
#include "w25qxx.h"
#define DATA_START_ADDR 0x000000 // Flash存储起始地址
#define MAX_RECORDS 1000 // 最大存储记录数
static uint32_t current_addr = DATA_START_ADDR;
static uint16_t record_count = 0;
void W25Qxx_SaveData(SensorData *data) {
if (record_count >= MAX_RECORDS) {
// 循环覆盖最早的数据
current_addr = DATA_START_ADDR;
record_count = 0;
}
// 擦除扇区(如果需要)
if ((current_addr % W25QXX_SECTOR_SIZE) == 0) {
W25Qxx_EraseSector(current_addr);
}
// 写入数据
W25Qxx_Write((uint8_t *)data, current_addr, sizeof(SensorData));
// 更新地址和计数
current_addr += sizeof(SensorData);
record_count++;
}
学习重点与实现技巧
1. GPIO配置
使用STM32CubeMX进行可视化配置
合理分配引脚资源,避免冲突
配置上拉/下拉电阻,提高抗干扰能力
2. ADC采样
配置ADC时钟和采样时间
使用DMA传输减少CPU开销
添加软件滤波算法(如移动平均)
// ADC采样示例
#define ADC_SAMPLES 10
uint16_t adc_buffer[ADC_SAMPLES];
void ADC_Init(void) {
ADC_ChannelConfTypeDef sConfig = {0};
hadc1.Instance = ADC1;
hadc1.Init.ClockPrescaler = ADC_CLOCK_SYNC_PCLK_DIV4;
hadc1.Init.Resolution = ADC_RESOLUTION_12B;
hadc1.Init.ScanConvMode = ENABLE;
hadc1.Init.ContinuousConvMode = ENABLE;
hadc1.Init.DiscontinuousConvMode = DISABLE;
hadc1.Init.ExternalTrigConvEdge = ADC_EXTERNALTRIGCONVEDGE_NONE;
hadc1.Init.ExternalTrigConv = ADC_SOFTWARE_START;
hadc1.Init.DataAlign = ADC_DATAALIGN_RIGHT;
hadc1.Init.NbrOfConversion = 1;
hadc1.Init.DMAContinuousRequests = ENABLE;
hadc1.Init.EOCSelection = ADC_EOC_SINGLE_CONV;
HAL_ADC_Init(&hadc1);
sConfig.Channel = ADC_CHANNEL_0;
sConfig.Rank = 1;
sConfig.SamplingTime = ADC_SAMPLETIME_480CYCLES;
HAL_ADC_ConfigChannel(&hadc1, &sConfig);
// 启动DMA传输
HAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_SAMPLES);
}
3. I2C总线通信
使用HAL库的I2C函数
处理总线忙状态和错误恢复
添加重试机制提高稳定性
HAL_StatusTypeDef I2C_Write(I2C_HandleTypeDef *hi2c, uint16_t DevAddress, uint8_t *pData, uint16_t Size) {
HAL_StatusTypeDef status;
uint8_t retry = 0;
while (retry < 3) {
status = HAL_I2C_Master_Transmit(hi2c, DevAddress, pData, Size, 100);
if (status == HAL_OK) {
return HAL_OK;
}
retry++;
HAL_Delay(1);
}
return status;
}
4. 定时器应用
配置定时器中断进行周期性任务
使用PWM驱动蜂鸣器
实现精确延时
// 定时器配置示例
void TIM_Init(void) {
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim2.Instance = TIM2;
htim2.Init.Prescaler = 8400 - 1; // 84MHz/8400 = 10kHz
htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
htim2.Init.Period = 10000 - 1; // 1s中断
htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&htim2);
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
// 使能定时器中断
HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
}
5. 传感器数据解析
理解传感器数据手册
正确处理原始数据到物理量的转换
添加校验机制确保数据有效性
// SHT30数据解析示例
void SHT30_ParseData(uint8_t *data, float *temp, float *humi) {
uint16_t rawTemp = (data[0] << 8) | data[1];
uint16_t rawHumi = (data[3] << 8) | data[4];
// 计算温度(℃)
*temp = -45 + 175 * (float)rawTemp / 65535.0f;
// 计算湿度(%RH)
*humi = 100 * (float)rawHumi / 65535.0f;
}
项目优化建议
低功耗设计:
使用睡眠模式
传感器间歇工作
降低主频
数据安全:
添加CRC校验
数据加密存储
异常恢复机制
用户界面:
OLED菜单系统
按键控制
报警阈值可配置
云平台集成:
MQTT协议
HTTP API
数据可视化
固件升级:
Bootloader设计
OTA(Over-The-Air)升级
版本管理
调试技巧
串口调试:
使用printf重定向
分模块调试
添加调试宏
#ifdef DEBUG
#define DEBUG_PRINT(fmt, ...) printf(fmt, ##__VA_ARGS__)
#else
#define DEBUG_PRINT(fmt, ...)
#endif
逻辑分析仪:
分析I2C/SPI波形
测量时序
检测信号质量
断点调试:
变量监控
调用栈分析
内存检查
性能优化:
使用DMA减少CPU负载
优化算法
减少不必要的延时
这个智能环境监测站项目涵盖了嵌入式系统开发的核心技术点,通过实践可以深入理解传感器应用、通信协议和外设控制。项目具有很好的扩展性,可以根据需求添加更多功能模块。