文章目录
-
- 一、前言
-
- [1.1 项目背景](#1.1 项目背景)
- [1.2 DHT11传感器特点](#1.2 DHT11传感器特点)
- [1.3 应用场景](#1.3 应用场景)
- [1.4 技术栈](#1.4 技术栈)
- [1.5 学习目标](#1.5 学习目标)
- 二、系统架构设计
-
- [2.1 整体架构](#2.1 整体架构)
- [2.2 数据流程](#2.2 数据流程)
- 三、硬件准备
-
- [3.1 硬件清单](#3.1 硬件清单)
- [3.2 引脚分配](#3.2 引脚分配)
- [3.3 硬件连接图](#3.3 硬件连接图)
- 四、DHT11驱动开发
-
- [4.1 DHT11通信协议](#4.1 DHT11通信协议)
- [4.2 DHT11驱动代码](#4.2 DHT11驱动代码)
- 五、OLED显示驱动
-
- [5.1 SSD1306驱动代码](#5.1 SSD1306驱动代码)
- 六、主程序实现
-
- [6.1 主程序代码](#6.1 主程序代码)
- 七、常见问题
-
- [7.1 DHT11读取失败](#7.1 DHT11读取失败)
- [7.2 OLED显示异常](#7.2 OLED显示异常)
- 八、扩展功能
-
- [8.1 数据存储](#8.1 数据存储)
- [8.2 无线传输](#8.2 无线传输)
- 九、总结
-
- [9.1 核心知识点](#9.1 核心知识点)
- [9.2 扩展方向](#9.2 扩展方向)
一、前言
1.1 项目背景
温湿度监测是物联网应用中最基础也是最重要的功能之一。无论是智能家居、农业大棚、仓储管理还是工业生产,都需要实时监测环境温湿度。DHT11是一款性价比极高的数字温湿度传感器,配合OLED显示屏,可以快速构建一个完整的温湿度监测系统。
1.2 DHT11传感器特点
- 成本低:单价仅几元钱
- 使用简单:单总线数字接口
- 响应快:约2秒采样周期
- 稳定性好:已校准数字信号输出
- 免外围元件:内置电阻,直接连接
1.3 应用场景
- 智能家居:室内环境监测
- 农业大棚:作物生长环境监控
- 仓储物流:货物存储环境记录
- 机房监控:服务器运行环境监测
- 实验室:实验环境数据采集
1.4 技术栈
| 类别 | 技术/器件 | 说明 |
|---|---|---|
| 主控芯片 | STM32F103C8T6 | ARM Cortex-M3 |
| 传感器 | DHT11 | 温湿度传感器 |
| 显示屏 | SSD1306 0.96寸 | 128×64 OLED |
| 通信协议 | 单总线/I2C | DHT11/OLED |
| 开发环境 | Keil MDK-ARM | 集成开发环境 |
1.5 学习目标
通过本教程,你将学会:
- ✅ DHT11单总线通信协议
- ✅ OLED I2C驱动开发
- ✅ 中文显示实现
- ✅ 数据存储与历史曲线
- ✅ 报警阈值设置
二、系统架构设计
2.1 整体架构
单总线
I2C
GPIO
GPIO
USART
温湿度数据
显示数据
STM32F103主控
DHT11传感器
OLED显示屏
蜂鸣器
LED指示灯
串口调试
2.2 数据流程
是
否
是
否
系统启动
初始化外设
读取DHT11
数据有效?
更新显示
错误处理
检查报警
超限?
触发报警
正常状态
延时2秒
三、硬件准备
3.1 硬件清单
| 序号 | 名称 | 数量 | 参考价格 | 说明 |
|---|---|---|---|---|
| 1 | STM32F103C8T6核心板 | 1 | ¥12 | 主控芯片 |
| 2 | DHT11温湿度模块 | 1 | ¥6 | 传感器 |
| 3 | SSD1306 OLED 0.96寸 | 1 | ¥10 | I2C接口 |
| 4 | 有源蜂鸣器 | 1 | ¥2 | 报警提示 |
| 5 | LED灯 | 2 | ¥1 | 状态指示 |
| 6 | 电阻220Ω | 2 | ¥0.2 | LED限流 |
| 7 | 杜邦线 | 若干 | ¥3 | 连接线 |
3.2 引脚分配
| 功能 | STM32引脚 | 连接对象 | 说明 |
|---|---|---|---|
| DHT11_DATA | PA0 | DHT11 DATA | 单总线数据 |
| I2C_SCL | PB6 | OLED SCL | I2C时钟 |
| I2C_SDA | PB7 | OLED SDA | I2C数据 |
| BEEP | PA1 | 蜂鸣器 | 报警输出 |
| LED_RED | PA2 | 红灯 | 高温指示 |
| LED_BLUE | PA3 | 蓝灯 | 高湿指示 |
| USART_TX | PA9 | USB-TTL | 调试输出 |
| USART_RX | PA10 | USB-TTL | 调试输入 |
3.3 硬件连接图
STM32F103C8T6
┌─────────────────────────────────────┐
│ │
│ PA0 ────────────> DHT11 DATA │
│ │
│ PB6 ────────────> OLED SCL │
│ PB7 ────────────> OLED SDA │
│ │
│ PA1 ────────────> 蜂鸣器 │
│ PA2 ────────────> LED_RED │
│ PA3 ────────────> LED_BLUE │
│ │
│ PA9 ────────────> USB-TTL RX │
│ PA10 ───────────> USB-TTL TX │
│ │
└─────────────────────────────────────┘
四、DHT11驱动开发
4.1 DHT11通信协议
DHT11使用单总线串行通信,一次完整的数据传输为40bit:
- 湿度整数:8bit
- 湿度小数:8bit(DHT11固定为0)
- 温度整数:8bit
- 温度小数:8bit(DHT11固定为0)
- 校验和:8bit(前4字节之和)
时序要求:
- 主机启动信号:拉低18ms,然后拉高20-40us
- DHT11响应:拉低80us,然后拉高80us
- 数据位0:50us低电平 + 26-28us高电平
- 数据位1:50us低电平 + 70us高电平
4.2 DHT11驱动代码
📄 创建文件:
Hardware/dht11.h
c
#ifndef __DHT11_H
#define __DHT11_H
#include "sys.h"
// DHT11引脚定义
#define DHT11_PIN GPIO_Pin_0
#define DHT11_PORT GPIOA
// 控制宏
#define DHT11_HIGH() GPIO_SetBits(DHT11_PORT, DHT11_PIN)
#define DHT11_LOW() GPIO_ResetBits(DHT11_PORT, DHT11_PIN)
#define DHT11_READ() GPIO_ReadInputDataBit(DHT11_PORT, DHT11_PIN)
// 数据结构
typedef struct {
uint8_t humidity_int; // 湿度整数部分
uint8_t humidity_deci; // 湿度小数部分
uint8_t temperature_int; // 温度整数部分
uint8_t temperature_deci; // 温度小数部分
uint8_t checksum; // 校验和
} DHT11_Data;
// 函数声明
void DHT11_Init(void);
uint8_t DHT11_ReadData(DHT11_Data *data);
float DHT11_GetTemperature(DHT11_Data *data);
float DHT11_GetHumidity(DHT11_Data *data);
#endif
📄 创建文件:
Hardware/dht11.c
c
#include "dht11.h"
#include "delay.h"
/**
* DHT11初始化
* 配置为开漏输出,需要外部上拉电阻
*/
void DHT11_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置为开漏输出
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD; // 开漏输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
// 初始状态高电平
DHT11_HIGH();
}
/**
* 设置DHT11引脚为输出模式
*/
static void DHT11_SetOutput(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
}
/**
* 设置DHT11引脚为输入模式
*/
static void DHT11_SetInput(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Pin = DHT11_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
GPIO_Init(DHT11_PORT, &GPIO_InitStructure);
}
/**
* 读取一个bit
* @return: 0或1
*/
static uint8_t DHT11_ReadBit(void)
{
// 等待低电平结束
while (DHT11_READ() == Bit_RESET);
// 延时40us
delay_us(40);
// 读取电平
if (DHT11_READ() == Bit_SET)
{
// 等待高电平结束
while (DHT11_READ() == Bit_SET);
return 1;
}
else
{
return 0;
}
}
/**
* 读取一个字节
*/
static uint8_t DHT11_ReadByte(void)
{
uint8_t byte = 0;
for (int i = 0; i < 8; i++)
{
byte <<= 1;
byte |= DHT11_ReadBit();
}
return byte;
}
/**
* 读取DHT11数据
* @param data: 数据存储结构
* @return: 0-成功,1-失败
*/
uint8_t DHT11_ReadData(DHT11_Data *data)
{
uint8_t buf[5];
// 设置为输出模式
DHT11_SetOutput();
// 发送起始信号:拉低18ms
DHT11_LOW();
delay_ms(20);
// 拉高20-40us
DHT11_HIGH();
delay_us(30);
// 切换为输入模式
DHT11_SetInput();
// 等待DHT11响应(拉低80us)
uint16_t timeout = 100;
while (DHT11_READ() == Bit_SET)
{
if (--timeout == 0) return 1;
delay_us(1);
}
timeout = 100;
while (DHT11_READ() == Bit_RESET)
{
if (--timeout == 0) return 1;
delay_us(1);
}
// 等待响应结束(拉高80us)
timeout = 100;
while (DHT11_READ() == Bit_SET)
{
if (--timeout == 0) return 1;
delay_us(1);
}
// 读取40bit数据
for (int i = 0; i < 5; i++)
{
buf[i] = DHT11_ReadByte();
}
// 切换回输出模式
DHT11_SetOutput();
DHT11_HIGH();
// 校验数据
if ((buf[0] + buf[1] + buf[2] + buf[3]) != buf[4])
{
return 1; // 校验失败
}
// 保存数据
data->humidity_int = buf[0];
data->humidity_deci = buf[1];
data->temperature_int = buf[2];
data->temperature_deci = buf[3];
data->checksum = buf[4];
return 0;
}
/**
* 获取温度值
*/
float DHT11_GetTemperature(DHT11_Data *data)
{
float temp = data->temperature_int;
temp += data->temperature_deci / 10.0f;
// 处理负温度(最高位为1表示负温)
if (data->temperature_int & 0x80)
{
temp = -temp;
}
return temp;
}
/**
* 获取湿度值
*/
float DHT11_GetHumidity(DHT11_Data *data)
{
float humi = data->humidity_int;
humi += data->humidity_deci / 10.0f;
return humi;
}
五、OLED显示驱动
5.1 SSD1306驱动代码
📄 创建文件:
Hardware/oled.h
c
#ifndef __OLED_H
#define __OLED_H
#include "sys.h"
// OLED参数
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// I2C地址
#define OLED_ADDR 0x78
// 函数声明
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr);
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str);
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len);
void OLED_ShowFloat(uint8_t x, uint8_t y, float num, uint8_t int_len, uint8_t dec_len);
void OLED_DrawBMP(uint8_t x, uint8_t y, uint8_t width, uint8_t height, const uint8_t *bmp);
void OLED_SetPos(uint8_t x, uint8_t y);
#endif
📄 创建文件:
Hardware/oled.c
c
#include "oled.h"
#include "i2c.h"
#include "oled_font.h"
extern I2C_HandleTypeDef hi2c1;
/**
* 写命令
*/
static void OLED_WriteCommand(uint8_t cmd)
{
uint8_t buf[2];
buf[0] = 0x00; // Control byte: Co=0, D/C#=0
buf[1] = cmd;
HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, HAL_MAX_DELAY);
}
/**
* 写数据
*/
static void OLED_WriteData(uint8_t data)
{
uint8_t buf[2];
buf[0] = 0x40; // Control byte: Co=0, D/C#=1
buf[1] = data;
HAL_I2C_Master_Transmit(&hi2c1, OLED_ADDR, buf, 2, HAL_MAX_DELAY);
}
/**
* OLED初始化
*/
void OLED_Init(void)
{
delay_ms(100); // 等待OLED上电稳定
OLED_WriteCommand(0xAE); // Display OFF
OLED_WriteCommand(0x20); // Set Memory Addressing Mode
OLED_WriteCommand(0x10); // 00:Horizontal, 01:Vertical, 10:Page
OLED_WriteCommand(0xB0); // Set Page Start Address
OLED_WriteCommand(0xC8); // Set COM Output Scan Direction
OLED_WriteCommand(0x00); // Set Low Column Address
OLED_WriteCommand(0x10); // Set High Column Address
OLED_WriteCommand(0x40); // Set Start Line Address
OLED_WriteCommand(0x81); // Set Contrast Control
OLED_WriteCommand(0xFF); // Max contrast
OLED_WriteCommand(0xA1); // Set Segment Re-map
OLED_WriteCommand(0xA6); // Normal display
OLED_WriteCommand(0xA8); // Set Multiplex Ratio
OLED_WriteCommand(0x3F); // 1/64 duty
OLED_WriteCommand(0xA4); // Output follows RAM content
OLED_WriteCommand(0xD3); // Set Display Offset
OLED_WriteCommand(0x00); // No offset
OLED_WriteCommand(0xD5); // Set Display Clock Divide
OLED_WriteCommand(0xF0); // Max frequency
OLED_WriteCommand(0xD9); // Set Pre-charge Period
OLED_WriteCommand(0x22);
OLED_WriteCommand(0xDA); // Set COM Pins Hardware Config
OLED_WriteCommand(0x12); // Alternative COM pin
OLED_WriteCommand(0xDB); // Set VCOMH Deselect Level
OLED_WriteCommand(0x20); // 0.77x Vcc
OLED_WriteCommand(0x8D); // Charge Pump Setting
OLED_WriteCommand(0x14); // Enable charge pump
OLED_WriteCommand(0xAF); // Display ON
OLED_Clear();
}
/**
* 设置显示位置
*/
void OLED_SetPos(uint8_t x, uint8_t y)
{
OLED_WriteCommand(0xB0 + y);
OLED_WriteCommand(((x & 0xF0) >> 4) | 0x10);
OLED_WriteCommand(x & 0x0F);
}
/**
* 清屏
*/
void OLED_Clear(void)
{
for (uint8_t page = 0; page < 8; page++)
{
OLED_SetPos(0, page);
for (uint8_t col = 0; col < 128; col++)
{
OLED_WriteData(0x00);
}
}
}
/**
* 显示字符(8×16)
*/
void OLED_ShowChar(uint8_t x, uint8_t y, uint8_t chr)
{
uint8_t c = chr - ' ';
OLED_SetPos(x, y);
for (uint8_t i = 0; i < 8; i++)
{
OLED_WriteData(ascii_8x16[c][i]);
}
OLED_SetPos(x, y + 1);
for (uint8_t i = 0; i < 8; i++)
{
OLED_WriteData(ascii_8x16[c][i + 8]);
}
}
/**
* 显示字符串
*/
void OLED_ShowString(uint8_t x, uint8_t y, uint8_t *str)
{
while (*str != '\0')
{
OLED_ShowChar(x, y, *str);
x += 8;
if (x > 120)
{
x = 0;
y += 2;
}
str++;
}
}
/**
* 显示数字
*/
void OLED_ShowNum(uint8_t x, uint8_t y, uint32_t num, uint8_t len)
{
uint8_t t, temp;
uint8_t enshow = 0;
for (t = 0; t < len; t++)
{
temp = (num / (uint32_t)pow(10, len - t - 1)) % 10;
if (enshow == 0 && t < (len - 1))
{
if (temp == 0)
{
OLED_ShowChar(x + 8 * t, y, ' ');
continue;
}
else
{
enshow = 1;
}
}
OLED_ShowChar(x + 8 * t, y, temp + '0');
}
}
/**
* 显示浮点数
*/
void OLED_ShowFloat(uint8_t x, uint8_t y, float num, uint8_t int_len, uint8_t dec_len)
{
uint8_t i;
uint32_t int_part, dec_part;
// 处理负数
if (num < 0)
{
OLED_ShowChar(x, y, '-');
x += 8;
num = -num;
}
// 分离整数和小数
int_part = (uint32_t)num;
dec_part = (uint32_t)((num - int_part) * pow(10, dec_len));
// 显示整数部分
OLED_ShowNum(x, y, int_part, int_len);
x += int_len * 8;
// 显示小数点
OLED_ShowChar(x, y, '.');
x += 8;
// 显示小数部分
OLED_ShowNum(x, y, dec_part, dec_len);
}
六、主程序实现
6.1 主程序代码
📄 创建文件:
User/main.c
c
/**
* STM32 DHT11温湿度监测 + OLED显示
* 主程序
*/
#include "stm32f10x.h"
#include "sys.h"
#include "dht11.h"
#include "oled.h"
#include "delay.h"
#include <stdio.h>
#include <string.h>
// 报警阈值
typedef struct {
float temp_high; // 温度上限
float temp_low; // 温度下限
float humi_high; // 湿度上限
float humi_low; // 湿度下限
} Alarm_Threshold;
// 全局变量
DHT11_Data dht11_data;
Alarm_Threshold alarm_threshold = {
.temp_high = 30.0f,
.temp_low = 10.0f,
.humi_high = 80.0f,
.humi_low = 30.0f
};
// 历史数据缓存(用于绘制曲线)
#define HISTORY_SIZE 64
float temp_history[HISTORY_SIZE];
float humi_history[HISTORY_SIZE];
uint8_t history_index = 0;
// 函数声明
void System_Init(void);
void Display_Update(void);
void Alarm_Check(void);
void History_Update(float temp, float humi);
void Draw_Curve(void);
int main(void)
{
System_Init();
// 显示启动画面
OLED_ShowString(20, 2, (uint8_t *)"DHT11 Monitor");
OLED_ShowString(30, 4, (uint8_t *)"Starting...");
delay_ms(2000);
OLED_Clear();
printf("\r\n================================\r\n");
printf(" DHT11 Temperature & Humidity\r\n");
printf("================================\r\n");
while (1)
{
// 读取DHT11数据
if (DHT11_ReadData(&dht11_data) == 0)
{
float temperature = DHT11_GetTemperature(&dht11_data);
float humidity = DHT11_GetHumidity(&dht11_data);
// 更新历史数据
History_Update(temperature, humidity);
// 更新显示
Display_Update();
// 检查报警
Alarm_Check();
// 串口输出
printf("Temp: %.1f C, Humi: %.1f %%\r\n", temperature, humidity);
}
else
{
printf("DHT11 Read Error!\r\n");
OLED_ShowString(0, 6, (uint8_t *)"Sensor Error!");
}
// DHT11最小采样周期为2秒
delay_ms(2000);
}
}
/**
* 系统初始化
*/
void System_Init(void)
{
// 时钟配置
SystemClock_Config();
// 延时初始化
delay_init();
// 串口初始化
USART1_Init(115200);
// I2C初始化
I2C1_Init();
// OLED初始化
OLED_Init();
// DHT11初始化
DHT11_Init();
// GPIO初始化(蜂鸣器、LED)
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
// 初始状态
GPIO_ResetBits(GPIOA, GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3);
}
/**
* 更新OLED显示
*/
void Display_Update(void)
{
float temperature = DHT11_GetTemperature(&dht11_data);
float humidity = DHT11_GetHumidity(&dht11_data);
// 显示标题
OLED_ShowString(0, 0, (uint8_t *)"Temp:");
OLED_ShowString(0, 2, (uint8_t *)"Humi:");
// 显示温度
OLED_ShowFloat(40, 0, temperature, 2, 1);
OLED_ShowString(90, 0, (uint8_t *)"C");
// 显示湿度
OLED_ShowFloat(40, 2, humidity, 2, 1);
OLED_ShowString(90, 2, (uint8_t *)"%");
// 绘制历史曲线
Draw_Curve();
}
/**
* 报警检查
*/
void Alarm_Check(void)
{
float temperature = DHT11_GetTemperature(&dht11_data);
float humidity = DHT11_GetHumidity(&dht11_data);
uint8_t alarm = 0;
// 温度报警
if (temperature > alarm_threshold.temp_high)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); // 红灯亮
alarm = 1;
}
else if (temperature < alarm_threshold.temp_low)
{
GPIO_SetBits(GPIOA, GPIO_Pin_2); // 红灯亮
alarm = 1;
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_2); // 红灯灭
}
// 湿度报警
if (humidity > alarm_threshold.humi_high)
{
GPIO_SetBits(GPIOA, GPIO_Pin_3); // 蓝灯亮
alarm = 1;
}
else if (humidity < alarm_threshold.humi_low)
{
GPIO_SetBits(GPIOA, GPIO_Pin_3); // 蓝灯亮
alarm = 1;
}
else
{
GPIO_ResetBits(GPIOA, GPIO_Pin_3); // 蓝灯灭
}
// 蜂鸣器报警
if (alarm)
{
GPIO_SetBits(GPIOA, GPIO_Pin_1);
delay_ms(200);
GPIO_ResetBits(GPIOA, GPIO_Pin_1);
}
}
/**
* 更新历史数据
*/
void History_Update(float temp, float humi)
{
temp_history[history_index] = temp;
humi_history[history_index] = humi;
history_index++;
if (history_index >= HISTORY_SIZE)
{
history_index = 0;
}
}
/**
* 绘制历史曲线
*/
void Draw_Curve(void)
{
// 简化的曲线显示,实际应该绘制点阵图
// 这里仅显示趋势指示
uint8_t x = 0;
uint8_t y = 6;
OLED_SetPos(x, y);
for (int i = 0; i < 128; i++)
{
// 根据历史数据计算显示点
uint8_t data = 0;
// ... 曲线绘制算法
OLED_WriteData(data);
}
}
七、常见问题
7.1 DHT11读取失败
原因:
- 时序不精确
- 上拉电阻缺失
- 采样频率过高
解决:
- 检查延时函数精度
- 添加4.7K上拉电阻
- 确保采样间隔≥2秒
7.2 OLED显示异常
原因:
- I2C地址错误
- 初始化序列错误
- 电源不稳定
解决:
- 确认地址是0x78或0x7A
- 检查初始化命令
- 添加电源滤波电容
八、扩展功能
8.1 数据存储
使用STM32内部Flash存储历史数据:
c
void Save_Data_To_Flash(float temp, float humi)
{
// Flash写入实现
// ...
}
8.2 无线传输
添加ESP8266模块实现WiFi上传:
c
void Upload_To_Cloud(float temp, float humi)
{
// MQTT上传实现
// ...
}
九、总结
9.1 核心知识点
- DHT11单总线通信协议
- I2C OLED驱动开发
- 温湿度数据采集
- 报警系统设计
9.2 扩展方向
- 使用DHT22提高精度
- 添加SD卡数据记录
- 实现WiFi远程监控
- 开发手机APP
如果本教程对你有帮助,欢迎点赞、收藏、关注!