STM32单片机温湿度监测实战:DHT11传感器与OLED/LCD1602双屏显示系统

1. 项目概述与环境搭建

最近在做一个温湿度监测的小项目,用STM32驱动DHT11传感器采集数据,同时在OLED和LCD1602两种屏幕上显示。这个方案特别适合需要双屏对比显示的场合,比如实验室环境监测或者智能家居控制面板。下面我就把整个实现过程详细分享一下。

先说说硬件选型。我用的主控是STM32F103C8T6,也就是常说的"蓝莓派",性价比超高。传感器选用DHT11,虽然精度比不上DHT22,但对于一般室内监测完全够用。显示部分选了两种屏:OLED用的是0.96寸I2C接口的SSD1306,LCD1602是经典的16x2字符液晶屏。

开发环境方面,我习惯用STM32CubeMX配合Keil MDK。CubeMX可以快速完成引脚配置和初始化代码生成,特别适合我这种懒人。硬件连接时要注意:

  • DHT11的数据线接在PB5
  • LCD1602的RS、RW、EN分别接PB0、PB1、PB2,数据线PA0-PA7
  • OLED的SCL和SDA接PB6和PB7

接线时有个小技巧:给DHT11的数据线加个4.7K上拉电阻,这样信号更稳定。LCD1602的对比度调节端VL记得接电位器,不然可能显示不清。

2. DHT11传感器驱动开发

DHT11是单总线通信的温湿度传感器,虽然数据手册看起来简单,但实际调试时发现时序要求很严格。传感器每次传输40bit数据,包含湿度整数、湿度小数、温度整数、温度小数和校验和。

关键是要实现精确的微秒级延时。我用的是SysTick定时器实现的延时函数:

void delay_us(uint16_t cnt) {
    uint8_t i;
    while(cnt--){
        for(i=0;i<10;i++){}
    }
}

传感器通信分三步:主机发送开始信号、传感器响应、数据传输。开始信号要保持至少18ms低电平:

void dht11Start() {
    DHT_GPIO_PB5_INIT(GPIO_MODE_OUTPUT_PP);
    DHT_HIGH;
    DHT_LOW;
    HAL_Delay(20);  // 保持低电平18ms以上
    DHT_HIGH;
    DHT_GPIO_PB5_INIT(GPIO_MODE_INPUT);
    
    // 等待传感器响应
    while(DHT_VALUE);  // 等待传感器拉低
    while(!DHT_VALUE); // 等待传感器拉高
    while(DHT_VALUE);  // 等待传感器准备数据
}

数据读取时要特别注意时序判断。'0'和'1'的区别在于高电平持续时间不同:

for(i=0; i<5; i++){  // 读取5字节
    for(j=0; j<8; j++){  // 每字节8位
        while(!DHT_VALUE);  // 等待上升沿
        delay_us(40);       // 延时40us后检测电平
        if(DHT_VALUE == 1){ // 高电平表示'1'
            while(DHT_VALUE); // 等待高电平结束
            tmp = (tmp << 1) | 1;
        } else {            // 低电平表示'0'
            tmp = tmp << 1;
        }
    }
    dht11Data[i] = tmp;
}

实际测试中发现,环境电磁干扰可能导致数据错误,所以我在代码中增加了校验和检查:

if(dht11Data[0] + dht11Data[1] + dht11Data[2] + dht11Data[3] != dht11Data[4]) {
    return ERROR_CODE;  // 校验失败返回错误
}

3. LCD1602显示实现

LCD1602是并行接口的字符液晶,有8位和4位两种工作模式。我选择8位模式,虽然多用几个IO口,但时序更简单。首先定义控制引脚:

#define RS_GPIO_Port GPIOB
#define RW_GPIO_Port GPIOB
#define EN_GPIO_Port GPIOB
#define RS_GPIO_PIN GPIO_PIN_0
#define RW_GPIO_PIN GPIO_PIN_1
#define EN_GPIO_PIN GPIO_PIN_2

初始化过程要严格按照时序进行,特别注意延时时间:

void lcd1602Init() {
    HAL_Delay(15);       // 上电延时15ms
    writeCmd(0x38);      // 功能设置:8位接口,2行显示
    HAL_Delay(5);
    writeCmd(0x38);      // 再次发送确保稳定
    writeCmd(0x08);      // 显示关闭
    writeCmd(0x01);      // 清屏
    HAL_Delay(2);        // 清屏需要较长时间
    writeCmd(0x06);      // 入口模式:地址递增,不移位
    writeCmd(0x0C);      // 显示开,无光标
}

显示字符串的函数要注意地址设置。LCD1602第一行地址从0x80开始,第二行从0xC0开始:

void lcd1602ShowData(char row, char col, char *str) {
    if(row == 1) {
        writeCmd(0x80 + col - 1);
    } else {
        writeCmd(0xC0 + col - 1);
    }
    while(*str) {
        writeData(*str++);
    }
}

调试时遇到一个坑:如果直接快速连续发送指令,LCD可能来不及处理。解决方法是在每个指令后加5ms延时,或者检查忙标志。我选择了简单粗暴的延时方式,虽然效率低但代码简单。

4. OLED显示驱动开发

OLED我选用的是SSD1306驱动的0.96寸屏,通过I2C接口通信。相比LCD1602,OLED显示效果更好,但驱动也更复杂。

首先配置I2C接口。在CubeMX中启用I2C1,PB6(SCL)和PB7(SDA):

void MX_I2C1_Init(void) {
    hi2c1.Instance = I2C1;
    hi2c1.Init.ClockSpeed = 100000;  // 100kHz
    hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2;
    hi2c1.Init.OwnAddress1 = 0;
    hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT;
    hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE;
    hi2c1.Init.OwnAddress2 = 0;
    hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE;
    hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE;
}

OLED初始化命令序列比较长,建议直接使用厂家提供的初始化代码:

void oledInit() {
    oledWriteCmd(0xAE); // 关闭显示
    oledWriteCmd(0xD5); // 设置时钟分频
    oledWriteCmd(0x80); // 建议值
    oledWriteCmd(0xA8); // 多路复用比例
    oledWriteCmd(0x3F); // 63
    // ... 其他初始化命令
    oledWriteCmd(0xAF); // 开启显示
}

显示字符需要先定义字模。我使用PCtoLCD2003软件生成了ASCII字符集点阵数据:

const unsigned char oledFont[] = {
    0x00,0x00,0x00,0x00,0x00, // 空格
    0x00,0x00,0x5F,0x00,0x00, // !
    // ... 其他字符点阵数据
};

字符显示函数要处理分页和列地址:

void oledShowChar(char row, char col, char ch) {
    unsigned int i;
    // 上半部分
    oledWriteCmd(0xB0 + row*2 - 2);  // 设置页地址
    oledWriteCmd(((col*8) & 0x0F) | 0x00);  // 列地址低4位
    oledWriteCmd((((col*8)>>4) & 0x0F) | 0x10); // 列地址高4位
    
    for(i=(ch-32)*10; i<(ch-32)*10+5; i++) {
        oledWriteData(oledFont[i]);
    }
    // 下半部分
    oledWriteCmd(0xB0 + row*2 - 1);
    // ... 同上
    for(i=(ch-32)*10+5; i<(ch-32)*10+10; i++) {
        oledWriteData(oledFont[i]);
    }
}

5. 系统整合与优化

把三个模块整合起来,主循环中每隔1秒读取一次传感器数据,然后刷新两个屏幕:

while(1) {
    if(HAL_GetTick() - lastRead > 1000) {
        lastRead = HAL_GetTick();
        
        if(readDht11Data() == SUCCESS) {
            formatDisplayString();
            
            // LCD1602显示
            lcd1602ShowData(1, 1, tempStr);
            lcd1602ShowData(2, 1, humiStr);
            
            // OLED显示
            oledClean();
            oledShowString(1, 1, "Temp:");
            oledShowString(1, 6, tempStr);
            oledShowString(3, 1, "Humi:");
            oledShowString(3, 6, humiStr);
        }
    }
}

为了提高稳定性,我做了几点优化:

  1. 增加传感器数据校验,连续3次读取失败则重置硬件
  2. 显示内容加入单位符号和分隔线,提高可读性
  3. 为OLED添加了简单的动画效果,数据更新时有刷新提示
  4. 使用RTOS的任务调度管理传感器读取和显示更新

电源管理方面,发现DHT11和LCD1602工作时电流较大,于是增加了软件开关控制:

void powerOnSensors() {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_SET); // 打开电源MOSFET
    HAL_Delay(50);  // 等待电源稳定
}

void powerOffSensors() {
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_8, GPIO_PIN_RESET);
}

这个项目花了我差不多一个周末的时间,主要卡在DHT11的时序调试上。后来用逻辑分析仪抓波形才发现,我的延时函数在72MHz主频下不够精确。调整后一切正常,现在设备已经连续运行一周多,数据稳定可靠。

内容概要:本文档是一份针对2025-2026年Java后端大厂面试的高频考点全面梳理,涵盖Java基础、集合框架、并发编程、JVM、Spring全家桶、MySQL、Redis、消息队列、分布式微服务等核心技术模块。内容不仅包括经典概念辨析(如StringStringBuilder区别、HashMap底层结构),还深入源码机制设计原理(如Spring三级缓存解决循环依赖、AOP动态代理实现),并结合实际场景探讨问题排查技术选型(如GC调优、缓存穿透解决方案)。特别强调从“背八股”向源码理解、线上排障和设计权衡的能力转变,体现当前面试趋势的深度化实战化。; 适合人群:具备1-3年工作经验,准备冲击中高级Java岗位的研发人员,尤其适合希望系统提升面试竞争力、深入理解主流技术底层原理的开发者。; 使用场景及目标:①应对大厂Java后端技术面试,掌握高频考点最新趋势;②深入理解核心技术的设计动机实现细节,如ConcurrentHashMap的线程安全机制、分布式ID生成方案对比;③提升实际问题分析解决能力,如Full GC排查、事务失效定位等。; 阅读建议:此资源以面试为导向,兼具广度深度,建议结合自身项目经验进行对照学习,注重理解“为什么”而非仅仅记忆结论,对关键知识点应动手验证(如ThreadLocal内存泄漏实验),并在模拟面试中强化表达逻辑。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值