1. 项目概述
温湿度检测与控制是工业自动化、农业大棚、仓储管理等场景中的基础需求。基于单片机的解决方案因其成本低廉、开发周期短、可靠性高等特点,成为中小型温湿度监控系统的首选方案。这个项目我们将使用常见的DHT11传感器和STM32单片机,构建一个具备实时显示、阈值报警和简单控制功能的完整系统。
在实际工程中,这类系统往往需要7×24小时稳定运行,因此除了基础功能实现外,我们还需要特别关注电源管理、抗干扰设计等工程细节。通过这个项目,不仅能掌握传感器数据采集的基本方法,还能学习到嵌入式系统开发的完整流程。
2. 硬件选型与电路设计
2.1 核心器件选型
单片机选择:
STM32F103C8T6是最具性价比的选择,72MHz主频、64KB Flash、20KB RAM的性能完全满足需求,且开发资源丰富。相比51单片机,STM32具有更丰富的外设和更强的处理能力,能够轻松处理传感器数据并实现复杂的控制算法。
温湿度传感器:
DHT11是入门级数字温湿度传感器,虽然精度一般(温度±2℃,湿度±5%RH),但价格低廉、接口简单。对于要求更高的场景,可以考虑DHT22或SHT30系列,它们的精度更高但价格也相应提升。
显示模块:
0.96寸OLED(SSD1306驱动)是最佳选择,它功耗低、显示效果好,且支持I2C接口节省IO资源。如果预算有限,也可以使用1602液晶屏,但显示效果和用户体验会打折扣。
2.2 电路设计要点
电源部分:
- 采用AMS1117-3.3V稳压芯片为系统供电
- 输入电容10μF,输出电容22μF提高稳定性
- 建议增加100nF去耦电容靠近单片机电源引脚
传感器接口:
DHT11使用单总线协议,只需要一个GPIO引脚:
code复制VCC - 3.3V
DATA - PA1(需接4.7K上拉电阻)
GND - GND
抗干扰设计:
- 传感器信号线尽量短(<20cm)
- 在电源入口处增加TVS二极管防止浪涌
- 敏感信号线远离电源走线
注意:DHT11的供电电压不能超过3.6V,直接连接5V系统会损坏传感器。如果系统使用5V供电,需要在数据线上串联1K电阻进行电平匹配。
3. 软件设计与实现
3.1 开发环境搭建
使用Keil MDK作为开发环境,配置步骤:
- 安装Keil MDK和STM32支持包
- 新建工程,选择STM32F103C8系列
- 配置系统时钟为72MHz
- 启用SWD调试接口
- 添加必要的库文件(DHT11驱动、OLED驱动等)
对于习惯使用开源工具的开发者,也可以选择PlatformIO + VSCode的开发方式,但需要自行移植相关驱动库。
3.2 DHT11驱动实现
DHT11的通信时序是关键,具体实现如下:
c复制#define DHT11_PIN GPIO_PIN_1
#define DHT11_PORT GPIOA
void DHT11_Start(void) {
GPIO_InitTypeDef GPIO_InitStruct = {0};
// 配置为输出模式
GPIO_InitStruct.Pin = DHT11_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct);
// 主机拉低18ms
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET);
delay_ms(18);
// 拉高20-40us
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET);
delay_us(30);
// 切换为输入模式
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_NOPULL;
HAL_GPIO_Init(DHT11_PORT, &GPIO_InitStruct);
}
uint8_t DHT11_ReadByte(void) {
uint8_t data = 0;
for(int i=0; i<8; i++) {
// 等待低电平结束
while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET);
// 延时40us判断高低电平
delay_us(40);
if(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET) {
data |= (1 << (7-i));
}
// 等待高电平结束
while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET);
}
return data;
}
3.3 主程序逻辑设计
系统主循环采用状态机设计,提高代码可维护性:
c复制typedef enum {
STATE_INIT,
STATE_READ_SENSOR,
STATE_UPDATE_DISPLAY,
STATE_CHECK_THRESHOLD,
STATE_CONTROL_OUTPUT,
STATE_ERROR_HANDLE
} SystemState;
void main(void) {
SystemState state = STATE_INIT;
DHT11_Data dhtData;
uint32_t lastReadTime = 0;
while(1) {
switch(state) {
case STATE_INIT:
OLED_Init();
Relay_Init();
state = STATE_READ_SENSOR;
break;
case STATE_READ_SENSOR:
if(HAL_GetTick() - lastReadTime > 2000) { // 2秒读取一次
if(DHT11_ReadData(&dhtData) == DHT11_OK) {
state = STATE_UPDATE_DISPLAY;
} else {
state = STATE_ERROR_HANDLE;
}
lastReadTime = HAL_GetTick();
}
break;
case STATE_UPDATE_DISPLAY:
OLED_ShowTempHum(dhtData.temperature, dhtData.humidity);
state = STATE_CHECK_THRESHOLD;
break;
case STATE_CHECK_THRESHOLD:
if(dhtData.temperature > TEMP_THRESHOLD) {
Relay_On(RELAY_FAN);
} else {
Relay_Off(RELAY_FAN);
}
if(dhtData.humidity < HUMIDITY_THRESHOLD) {
Relay_On(RELAY_HUMIDIFIER);
} else {
Relay_Off(RELAY_HUMIDIFIER);
}
state = STATE_READ_SENSOR;
break;
case STATE_ERROR_HANDLE:
OLED_ShowError("Sensor Error!");
delay_ms(1000);
state = STATE_READ_SENSOR;
break;
}
}
}
4. 系统优化与功能扩展
4.1 数据滤波处理
原始传感器数据往往存在波动,需要适当的滤波算法:
c复制#define FILTER_WINDOW_SIZE 5
typedef struct {
float tempBuffer[FILTER_WINDOW_SIZE];
float humBuffer[FILTER_WINDOW_SIZE];
uint8_t index;
} FilterData;
float MovingAverage_Filter(float newValue, float *buffer, uint8_t *index) {
buffer[(*index) % FILTER_WINDOW_SIZE] = newValue;
(*index)++;
float sum = 0;
for(int i=0; i<FILTER_WINDOW_SIZE; i++) {
sum += buffer[i];
}
return sum / FILTER_WINDOW_SIZE;
}
void Filter_Update(FilterData *filter, float temp, float hum) {
float filteredTemp = MovingAverage_Filter(temp, filter->tempBuffer, &filter->index);
float filteredHum = MovingAverage_Filter(hum, filter->humBuffer, &filter->index);
// 使用滤波后的数据
OLED_ShowTempHum(filteredTemp, filteredHum);
}
4.2 报警功能实现
增加声光报警功能,当温湿度超出设定范围时触发:
c复制void Buzzer_Beep(uint16_t freq, uint16_t duration) {
TIM_HandleTypeDef htim;
// 配置PWM输出
htim.Instance = TIM2;
htim.Init.Prescaler = 72-1; // 1MHz
htim.Init.CounterMode = TIM_COUNTERMODE_UP;
htim.Init.Period = 1000000/freq - 1;
HAL_TIM_PWM_Start(&htim, TIM_CHANNEL_1);
delay_ms(duration);
HAL_TIM_PWM_Stop(&htim, TIM_CHANNEL_1);
}
void CheckThreshold(float temp, float hum) {
static uint8_t alarmState = 0;
if(temp > TEMP_MAX || temp < TEMP_MIN ||
hum > HUM_MAX || hum < HUM_MIN) {
if(!alarmState) {
Buzzer_Beep(2000, 200);
LED_On(ALARM_LED);
alarmState = 1;
}
} else {
if(alarmState) {
LED_Off(ALARM_LED);
alarmState = 0;
}
}
}
4.3 上位机通信扩展
通过串口将数据发送到PC端,便于长期记录和分析:
c复制void USART_SendData(float temp, float hum) {
char buffer[64];
int len = sprintf(buffer, "Temp:%.1fC,Hum:%.1f%%\r\n", temp, hum);
HAL_UART_Transmit(&huart1, (uint8_t*)buffer, len, 100);
}
// 在状态机的STATE_UPDATE_DISPLAY中调用
USART_SendData(dhtData.temperature, dhtData.humidity);
5. 常见问题与解决方案
5.1 DHT11读取失败
现象:传感器经常返回校验错误或超时
- 检查接线是否正确,特别是上拉电阻
- 确保供电电压稳定(3.3V±10%)
- 适当增加两次读取之间的间隔(建议≥2秒)
- 在恶劣环境中,考虑增加防潮处理
5.2 显示闪烁或不稳定
解决方案:
- 优化OLED刷新逻辑,避免全屏刷新
- 增加显示缓冲区,只更新变化的部分
- 检查电源稳定性,必要时增加电容
- 降低I2C通信速率(400kHz→100kHz)
5.3 继电器误动作
处理措施:
- 在控制逻辑中加入回差控制(Hysteresis)
c复制// 温度控制示例
#define TEMP_THRESHOLD_HIGH 28.0
#define TEMP_THRESHOLD_LOW 26.0
if(temp > TEMP_THRESHOLD_HIGH) {
Relay_On(RELAY_FAN);
} else if(temp < TEMP_THRESHOLD_LOW) {
Relay_Off(RELAY_FAN);
}
- 为继电器线圈增加续流二极管
- 避免频繁切换(增加最小开启/关闭时间)
5.4 系统功耗优化
对于电池供电的应用,可采取以下措施:
- 使用STM32的低功耗模式(Stop模式)
- 间歇性唤醒采集数据(比如每分钟唤醒一次)
- 关闭不必要的外设时钟
- 选择低功耗的显示器件(如电子墨水屏)
6. 项目进阶方向
6.1 多节点组网监测
通过RS485或LoRa实现多个监测点的组网:
- 每个节点分配唯一地址
- 主机轮询或节点定时上报数据
- 统一显示和存储所有节点数据
6.2 物联网云端接入
将数据上传到云平台(如阿里云IoT):
- 增加ESP8266 WiFi模块
- 实现MQTT协议通信
- 配置云平台数据解析脚本
- 开发手机APP远程监控
6.3 本地数据存储
添加SD卡模块记录历史数据:
- 使用FAT32文件系统
- 按日期创建CSV文件
- 定期归档旧数据
- 支持USB导出分析
6.4 PID控制算法
对于精密控制场景,替换简单的阈值控制为PID算法:
c复制typedef struct {
float Kp, Ki, Kd;
float integral;
float prev_error;
} PIDController;
float PID_Update(PIDController *pid, float setpoint, float input) {
float error = setpoint - input;
pid->integral += error;
if(pid->integral > INTEGRAL_MAX) pid->integral = INTEGRAL_MAX;
if(pid->integral < INTEGRAL_MIN) pid->integral = INTEGRAL_MIN;
float derivative = error - pid->prev_error;
pid->prev_error = error;
return pid->Kp * error +
pid->Ki * pid->integral +
pid->Kd * derivative;
}
在实际部署中,我发现环境温湿度监测系统最关键的三个要素是:传感器校准、控制逻辑的鲁棒性和电源稳定性。特别是在农业大棚应用中,早晚温差大、湿度变化剧烈,简单的阈值控制往往会导致执行机构频繁动作。这时采用带有回差控制的算法,或者更高级的预测控制算法,能显著提高系统稳定性和设备寿命。
