文章目录
-
- 一、项目概述
-
- [1.1 硬件清单](#1.1 硬件清单)
- [1.2 系统整体流程](#1.2 系统整体流程)
- 二、STM32CubeMX配置步骤
-
- [2.1 新建工程](#2.1 新建工程)
- [2.2 时钟配置](#2.2 时钟配置)
- [2.3 引脚配置](#2.3 引脚配置)
-
- [2.3.1 DHT11引脚配置(GPIO)](#2.3.1 DHT11引脚配置(GPIO))
- [2.3.2 OLED引脚配置(I2C)](#2.3.2 OLED引脚配置(I2C))
- [2.4 工程设置](#2.4 工程设置)
- 三、代码编写
-
- [3.1 代码文件结构说明](#3.1 代码文件结构说明)
- [3.2 DHT11驱动代码](#3.2 DHT11驱动代码)
- [3.3 OLED驱动代码](#3.3 OLED驱动代码)
- [3.4 延时函数代码(辅助DHT11)](#3.4 延时函数代码(辅助DHT11))
- [3.5 主函数代码](#3.5 主函数代码)
- 四、硬件接线说明
- 五、编译与下载
-
- [5.1 工程配置](#5.1 工程配置)
- [5.2 编译工程](#5.2 编译工程)
- [5.3 下载程序](#5.3 下载程序)
- 六、测试与调试
- 七、功能扩展建议
一、项目概述
本项目基于STM32F103C8T6单片机,通过HAL库驱动DHT11温湿度传感器采集环境温湿度数据,并将数据实时显示在0.96寸I2C接口的OLED屏幕上。整个系统实现了温湿度数据的采集、解析、显示全流程,适合零基础小白入门STM32 HAL库开发,所有代码和步骤均经过实测,可直接落地。
1.1 硬件清单
- 主控板:STM32F103C8T6最小系统板
- 传感器:DHT11温湿度传感器模块
- 显示模块:0.96寸I2C接口OLED屏幕(SSD1306驱动)
- 辅助配件:杜邦线若干、5V/3.3V电源、USB-TTL下载器
- 开发环境:STM32CubeMX 6.9.0 + Keil MDK-ARM 5.38a
1.2 系统整体流程
否
是
系统上电初始化
HAL库底层初始化
时钟/引脚/I2C
OLED屏幕初始化
DHT11传感器初始化
读取DHT11温湿度数据
数据校验是否通过?
解析温湿度数值
OLED屏幕刷新显示数据
延时1秒
二、STM32CubeMX配置步骤
2.1 新建工程
- 打开STM32CubeMX,点击
File -> New Project - 在搜索框输入
STM32F103C8T6,选择对应型号,点击Start Project - 弹出
MCU/MPU Selector窗口,确认型号后点击OK
2.2 时钟配置
- 点击左侧
Clock Configuration - 选择
HSE为Crystal/Ceramic Resonator(外部晶振) - 将
System Clock Mux设置为PLLCLK - PLL倍频系数设置为
9,最终系统时钟配置为72MHz - 点击
Configuration返回配置界面
2.3 引脚配置
2.3.1 DHT11引脚配置(GPIO)
DHT11采用单总线通信,本项目选择PA0作为DHT11的数据引脚:
- 点击
PA0,选择GPIO_Output(初始为输出,通信时切换输入) - 点击左侧
GPIO,找到PA0:- GPIO Output Level:
High - GPIO Mode:
Output Push Pull - GPIO Pull-up/Pull-down:
Pull-Up - Maximum output speed:
Low
- GPIO Output Level:
2.3.2 OLED引脚配置(I2C)
0.96寸OLED(I2C)选择I2C1:
- 点击
PB6,选择I2C1_SCL - 点击
PB7,选择I2C1_SDA - 点击左侧
I2C1:- I2C Mode:
I2C - I2C Clock Speed:
100 kHz(标准模式) - I2C Addressing Mode:
7-bit - 其余保持默认
- I2C Mode:
2.4 工程设置
- 点击左侧
Project Manager:- Project Name:
DHT11_OLED_Weather - Project Location:选择自定义路径
- Toolchain/IDE:
MDK-ARM v5
- Project Name:
- 点击
Code Generator:- 勾选
Generate peripheral initialization as a pair of '.c/.h' files per peripheral - 勾选
Copy all used libraries into the project folder
- 勾选
- 点击
Generate Code,等待代码生成完成后,点击Open Project打开Keil工程
三、代码编写
3.1 代码文件结构说明
生成的工程中,我们需要新增/修改以下文件:
dht11.c/dht11.h:DHT11驱动代码oled.c/oled.h:OLED屏幕驱动代码main.c:主函数,实现数据采集和显示逻辑
3.2 DHT11驱动代码
文件名:dht11.h
c
#ifndef __DHT11_H
#define __DHT11_H
#include "stm32f1xx_hal.h"
// 定义DHT11数据引脚(与CubeMX配置一致)
#define DHT11_PIN GPIO_PIN_0
#define DHT11_PORT GPIOA
// 函数声明
void DHT11_Set_Pin_Output(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin); // 设置引脚为输出模式
void DHT11_Set_Pin_Input(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin); // 设置引脚为输入模式
uint8_t DHT11_Init(void); // DHT11初始化
uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi); // 读取温湿度数据
uint8_t DHT11_Read_Byte(void); // 读取一个字节
uint8_t DHT11_Read_Bit(void); // 读取一个比特
void DHT11_Rst(void); // 复位DHT11
#endif
文件名:dht11.c
c
#include "dht11.h"
#include "delay.h"
// 设置引脚为输出模式
void DHT11_Set_Pin_Output(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
// 设置引脚为输入模式
void DHT11_Set_Pin_Input(GPIO_TypeDef *GPIOx, uint16_t GPIO_Pin)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.Pin = GPIO_Pin;
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
GPIO_InitStruct.Pull = GPIO_PULLUP;
HAL_GPIO_Init(GPIOx, &GPIO_InitStruct);
}
// 复位DHT11
void DHT11_Rst(void)
{
DHT11_Set_Pin_Output(DHT11_PORT, DHT11_PIN); // 设置为输出模式
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); // 拉低引脚
HAL_Delay(20); // 拉低至少18ms
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); // 拉高引脚
delay_us(30); // 拉高20~40us
}
// 等待DHT11的响应
// 返回值:0-响应成功,1-响应失败
uint8_t DHT11_Check(void)
{
uint8_t retry = 0;
DHT11_Set_Pin_Input(DHT11_PORT, DHT11_PIN); // 设置为输入模式
// 等待DHT11拉低引脚(响应信号)
while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET && retry < 100)
{
retry++;
delay_us(1);
}
if(retry >= 100) return 1; // 响应超时
else retry = 0;
// 等待DHT11拉高引脚(响应信号结束)
while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET && retry < 100)
{
retry++;
delay_us(1);
}
if(retry >= 100) return 1; // 响应超时
return 0;
}
// 读取一个比特
uint8_t DHT11_Read_Bit(void)
{
uint8_t retry = 0;
// 等待引脚拉低(开始传输数据)
while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET && retry < 100)
{
retry++;
delay_us(1);
}
retry = 0;
// 等待引脚拉高
while(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET && retry < 100)
{
retry++;
delay_us(1);
}
delay_us(40); // 等待40us
// 判断引脚电平:高电平为1,低电平为0
if(HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET) return 1;
else return 0;
}
// 读取一个字节
uint8_t DHT11_Read_Byte(void)
{
uint8_t i, dat = 0;
for(i=0; i<8; i++)
{
dat <<= 1; // 左移一位,准备接收下一位
dat |= DHT11_Read_Bit(); // 读取当前位
}
return dat;
}
// 读取温湿度数据
// temp:温度值(0~50℃)
// humi:湿度值(20%~90%RH)
// 返回值:0-读取成功,1-读取失败
uint8_t DHT11_Read_Data(uint8_t *temp, uint8_t *humi)
{
uint8_t buf[5];
uint8_t i;
DHT11_Rst(); // 复位DHT11
if(DHT11_Check() == 0) // 响应成功
{
// 读取40位数据(5个字节)
for(i=0; i<5; i++)
{
buf[i] = DHT11_Read_Byte();
}
// 校验数据(前4个字节之和等于第5个字节)
if((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
{
*humi = buf[0]; // 湿度整数部分
*temp = buf[2]; // 温度整数部分
}
}
else
{
return 1; // 响应失败
}
return 0;
}
// DHT11初始化
// 返回值:0-初始化成功,1-初始化失败
uint8_t DHT11_Init(void)
{
__HAL_RCC_GPIOA_CLK_ENABLE(); // 使能GPIOA时钟
DHT11_Rst(); // 复位DHT11
return DHT11_Check(); // 检查响应
}
3.3 OLED驱动代码
文件名:oled.h
c
#ifndef __OLED_H
#define __OLED_H
#include "stm32f1xx_hal.h"
#include "i2c.h"
// OLED I2C地址(根据模块配置,一般为0x78或0x7A)
#define OLED_I2C_ADDR 0x78
// 屏幕分辨率
#define OLED_WIDTH 128
#define OLED_HEIGHT 64
// 函数声明
void OLED_Write_Command(uint8_t cmd); // 写命令
void OLED_Write_Data(uint8_t data); // 写数据
void OLED_Init(void); // OLED初始化
void OLED_Clear(void); // 清屏
void OLED_Set_Pos(uint8_t x, uint8_t y); // 设置光标位置
void OLED_Show_Char(uint8_t x, uint8_t y, uint8_t chr); // 显示单个字符
void OLED_Show_String(uint8_t x, uint8_t y, uint8_t *str); // 显示字符串
void OLED_Show_Num(uint8_t x, uint8_t y, uint32_t num, uint8_t len); // 显示数字
#endif
文件名:oled.c
c
#include "oled.h"
#include "stdlib.h"
#include "string.h"
// I2C写数据(底层调用HAL库)
static void I2C_Write(uint8_t addr, uint8_t data)
{
HAL_I2C_Mem_Write(&hi2c1, OLED_I2C_ADDR, addr, I2C_MEMADD_SIZE_8BIT, &data, 1, 100);
}
// 写命令
void OLED_Write_Command(uint8_t cmd)
{
I2C_Write(0x00, cmd); // 0x00表示命令
}
// 写数据
void OLED_Write_Data(uint8_t data)
{
I2C_Write(0x40, data); // 0x40表示数据
}
// 设置光标位置
void OLED_Set_Pos(uint8_t x, uint8_t y)
{
OLED_Write_Command(0xb0 + y); // 设置页地址(Y轴)
OLED_Write_Command(((x & 0xf0) >> 4) | 0x10); // 设置列地址高4位
OLED_Write_Command(x & 0x0f); // 设置列地址低4位
}
// 清屏
void OLED_Clear(void)
{
uint8_t i, j;
for(i=0; i<8; i++)
{
OLED_Write_Command(0xb0 + i); // 选择第i页
OLED_Write_Command(0x00); // 列地址低4位
OLED_Write_Command(0x10); // 列地址高4位
for(j=0; j<128; j++)
{
OLED_Write_Data(0x00); // 写入空数据
}
}
}
// OLED初始化
void OLED_Init(void)
{
HAL_Delay(100); // 上电延时
// 初始化命令
OLED_Write_Command(0xAE); // 关闭显示
OLED_Write_Command(0x00); // 设置低列地址
OLED_Write_Command(0x10); // 设置高列地址
OLED_Write_Command(0x40); // 设置起始行地址
OLED_Write_Command(0xB0); // 设置页地址
OLED_Write_Command(0x81); // 对比度设置
OLED_Write_Command(0xFF); // 对比度值(0x00~0xFF)
OLED_Write_Command(0xA1); // 段重定义设置,SEG0->127
OLED_Write_Command(0xA6); // 正常显示(0xA7为反显)
OLED_Write_Command(0xA8); // 设置多路复用率
OLED_Write_Command(0x3F); // 1/64 Duty
OLED_Write_Command(0xC8); // 扫描方向,COM0->63
OLED_Write_Command(0xD3); // 设置显示偏移
OLED_Write_Command(0x00); // 偏移0
OLED_Write_Command(0xD5); // 设置显示时钟分频比/振荡器频率
OLED_Write_Command(0x80); // 默认值
OLED_Write_Command(0xD9); // 设置预充电周期
OLED_Write_Command(0xF1); // 预充电周期
OLED_Write_Command(0xDA); // 设置COM引脚硬件配置
OLED_Write_Command(0x12);
OLED_Write_Command(0xDB); // 设置VCOMH
OLED_Write_Command(0x40);
OLED_Write_Command(0x8D); // 设置电荷泵
OLED_Write_Command(0x14); // 开启电荷泵
OLED_Write_Command(0xAF); // 开启显示
OLED_Clear(); // 清屏
}
// 显示单个字符
void OLED_Show_Char(uint8_t x, uint8_t y, uint8_t chr)
{
uint8_t c = 0, i = 0;
c = chr - ' '; // 偏移量,对应字库起始位置
if(x > 127) // 列地址超出范围,切换到下一页
{
x = 0;
y += 2;
}
OLED_Set_Pos(x, y);
for(i=0; i<8; i++) // 显示字符上半部分
{
OLED_Write_Data(F8X16[c*16 + i]);
}
OLED_Set_Pos(x, y+1);
for(i=0; i<8; i++) // 显示字符下半部分
{
OLED_Write_Data(F8X16[c*16 + i + 8]);
}
}
// 显示字符串
void OLED_Show_String(uint8_t x, uint8_t y, uint8_t *str)
{
uint8_t i = 0;
while(str[i] != '\0')
{
OLED_Show_Char(x, y, str[i]);
x += 8;
if(x > 120) // 一行显示满,换行
{
x = 0;
y += 2;
}
i++;
}
}
// 显示数字
void OLED_Show_Num(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_Show_Char(x + t*8, y, ' ');
continue;
}
else enshow = 1;
}
OLED_Show_Char(x + t*8, y, temp + '0');
}
}
// 8x16 ASCII字库(部分)
const unsigned char F8X16[] = {
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,// 空格
0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,0x00,0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,0x00,// 0
0x00,0x00,0x00,0x10,0x10,0x10,0x10,0x00,0x00,0x00,0x00,0x10,0x10,0x10,0x10,0x00,// 1
0x00,0x00,0x7C,0x02,0x01,0x02,0x7C,0x00,0x00,0x00,0x7C,0x02,0x01,0x02,0x7C,0x00,// 2
0x00,0x00,0x7C,0x02,0x31,0x02,0x7C,0x00,0x00,0x00,0x7C,0x02,0x31,0x02,0x7C,0x00,// 3
0x00,0x00,0x02,0x02,0x7F,0x02,0x02,0x00,0x00,0x00,0x02,0x02,0x7F,0x02,0x02,0x00,// 4
0x00,0x00,0x7C,0x01,0x02,0x02,0x7C,0x00,0x00,0x00,0x7C,0x01,0x02,0x02,0x7C,0x00,// 5
0x00,0x00,0x7C,0x01,0x32,0x12,0x7C,0x00,0x00,0x00,0x7C,0x01,0x32,0x12,0x7C,0x00,// 6
0x00,0x00,0x7C,0x02,0x01,0x01,0x01,0x00,0x00,0x00,0x7C,0x02,0x01,0x01,0x01,0x00,// 7
0x00,0x00,0x7C,0x12,0x31,0x12,0x7C,0x00,0x00,0x00,0x7C,0x12,0x31,0x12,0x7C,0x00,// 8
0x00,0x00,0x7C,0x12,0x31,0x02,0x7C,0x00,0x00,0x00,0x7C,0x12,0x31,0x02,0x7C,0x00,// 9
0x00,0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,0x00,0x00,0x00,0x7C,0x12,0x11,0x12,0x7C,// T
0x00,0x00,0x00,0x00,0x00,0x7C,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x7C,0x00,0x00,// H
0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x00,0x00,0x00,0x00,0x18,0x18,0x18,0x18,0x00,// %
};
3.4 延时函数代码(辅助DHT11)
文件名:delay.h
c
#ifndef __DELAY_H
#define __DELAY_H
#include "stm32f1xx_hal.h"
void delay_us(uint32_t us); // 微秒延时
#endif
文件名:delay.c
c
#include "delay.h"
// 微秒延时函数(基于系统时钟72MHz)
void delay_us(uint32_t us)
{
uint32_t ticks;
uint32_t told, tnow, tcnt = 0;
uint32_t reload = SysTick->LOAD; // SysTick重装值
ticks = us * (SystemCoreClock / 1000000); // 需要的节拍数
told = SysTick->VAL; // 读取当前计数器值
while(1)
{
tnow = SysTick->VAL;
if(tnow != told)
{
if(tnow < told) tcnt += told - tnow;
else tcnt += reload - tnow + told;
told = tnow;
if(tcnt >= ticks) break; // 延时时间到
}
}
}
3.5 主函数代码
文件名:main.c
c
#include "main.h"
#include "i2c.h"
#include "gpio.h"
#include "dht11.h"
#include "oled.h"
#include "delay.h"
/* 函数声明 */
void SystemClock_Config(void);
int main(void)
{
uint8_t temp = 0; // 温度值
uint8_t humi = 0; // 湿度值
uint8_t ret = 0; // 读取状态
/* 初始化HAL库 */
HAL_Init();
/* 配置系统时钟为72MHz */
SystemClock_Config();
/* 初始化GPIO、I2C等外设 */
MX_GPIO_Init();
MX_I2C1_Init();
/* 初始化OLED屏幕 */
OLED_Init();
OLED_Show_String(0, 0, "Weather Monitor"); // 显示标题
OLED_Show_String(0, 2, "Temp: C"); // 温度显示位置
OLED_Show_String(0, 4, "Humi: %"); // 湿度显示位置
/* 初始化DHT11 */
if(DHT11_Init() == 0)
{
OLED_Show_String(0, 6, "DHT11 OK"); // DHT11初始化成功
}
else
{
OLED_Show_String(0, 6, "DHT11 Error"); // DHT11初始化失败
while(1); // 初始化失败则死循环
}
/* 主循环 */
while (1)
{
ret = DHT11_Read_Data(&temp, &humi); // 读取温湿度数据
if(ret == 0) // 读取成功
{
// 显示温度值(位置:Temp:后第4列)
OLED_Show_Num(40, 2, temp, 2);
// 显示湿度值(位置:Humi:后第4列)
OLED_Show_Num(40, 4, humi, 2);
}
else // 读取失败
{
OLED_Show_String(40, 2, "Err");
OLED_Show_String(40, 4, "Err");
}
HAL_Delay(1000); // 1秒刷新一次
}
}
/**
* @brief 系统时钟配置函数
* @param 无
* @retval 无
*/
void SystemClock_Config(void)
{
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
/** 配置外部高速晶振(HSE)
*/
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
RCC_OscInitStruct.HSIState = RCC_HSI_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
{
Error_Handler();
}
/** 配置系统时钟源、AHB/APB分频
*/
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;
if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
{
Error_Handler();
}
}
/**
* @brief 错误处理函数
* @param 无
* @retval 无
*/
void Error_Handler(void)
{
__disable_irq();
while (1)
{
// 错误时可以添加LED闪烁等提示
}
}
#ifdef USE_FULL_ASSERT
/**
* @brief 断言失败处理函数
* @param file: 源文件名
* @param line: 行号
* @retval 无
*/
void assert_failed(uint8_t *file, uint32_t line)
{
// 可以添加断言失败提示信息
}
#endif /* USE_FULL_ASSERT */
四、硬件接线说明
| STM32F103C8T6 | DHT11模块 | OLED模块 |
|---|---|---|
| 3.3V | VCC | VCC |
| GND | GND | GND |
| PA0 | DATA | - |
| PB6 | - | SCL |
| PB7 | - | SDA |
注意:部分DHT11模块支持5V供电,若使用5V供电,DATA引脚需串联1kΩ电阻后再连接到STM32的PA0引脚,避免电平过高损坏单片机。
五、编译与下载
5.1 工程配置
- 在Keil MDK中,点击
魔法棒图标(Options for Target) - 选择
Target选项卡:- 设置
Stack Size为0x400 - 设置
Heap Size为0x200
- 设置
- 选择
Output选项卡:- 勾选
Create HEX File - 设置输出路径
- 勾选
5.2 编译工程
点击Build或Rebuild按钮,确保工程无错误、无警告编译通过。
5.3 下载程序
- 连接USB-TTL下载器到STM32(TX->PA10,RX->PA9,GND->GND,5V->5V)
- 打开串口下载工具(如FlyMcu):
- 选择生成的HEX文件
- 波特率设置为115200
- 点击
开始编程,然后复位STM32单片机
六、测试与调试
- 下载完成后,OLED屏幕会显示:
- 第一行:Weather Monitor
- 第三行:Temp: XX C
- 第五行:Humi: XX %
- 第七行:DHT11 OK
- 若显示
DHT11 Error,检查:- DHT11接线是否正确
- DHT11模块是否正常供电
- PA0引脚配置是否正确
- 若温湿度显示为0或固定值,检查:
- DHT11数据引脚是否接触良好
- 延时函数是否准确
- 数据校验逻辑是否正确
七、功能扩展建议
- 添加按键功能,实现手动刷新/单位切换
- 增加SD卡模块,实现温湿度数据存储
- 增加WiFi模块(如ESP8266),实现数据上传到云平台
- 添加报警功能,温湿度超出阈值时LED闪烁/蜂鸣器报警
总结
- 本项目基于STM32 HAL库实现了DHT11温湿度采集和OLED显示,核心流程为:系统初始化→OLED初始化→DHT11初始化→数据采集→数据校验→OLED显示→循环刷新。
- 关键代码分为DHT11驱动(单总线通信、数据解析)、OLED驱动(I2C通信、字符显示)、主函数(逻辑调度)三部分,所有代码均可直接复用。
- 硬件接线需严格对应引脚,DHT11供电需注意电平匹配,OLED I2C地址需根据实际模块调整(0x78或0x7A)。