STM32开发——传感器

一、DHT11温湿度传感器

1.基本概念

传感器参数
参数 范围/精度
温度测量范围 0°C ~ 50°C
湿度测量范围 20%RH ~ 90%RH
温度精度 ±2°C
湿度精度 ±5%RH
通信接口 单总线(GPIO)
DHT11电路特性
  • 线与特性:多个设备可以共享同一总线,通过开漏输出实现

  • 总线状态真值表

DHT11状态 STM32状态 总线实际电平
1(释放) 0(拉低) 0
0(拉低) 1(释放) 0
1(释放) 1(释放) 1(上拉)
DHT11通信时序
(1) 起始和应答信号
起始信号(主机发送):
总线默认高电平(上拉)
    ↓
主机拉低总线至少18ms
    ↓
主机拉高总线20-40us(释放总线)
    ↓
代表主机发送了一个起始信号

应答信号(DHT11回复):
DHT11检测到起始信号后
    ↓
DHT11拉低总线80us左右
    ↓
DHT11拉高总线80us左右(释放总线)
    ↓
代表回复了一个应答信号
(2) 数据传输
发送bit '0':
DHT11拉低总线50us
    ↓
DHT11拉高总线26-28us
    ↓
代表发送了一个bit '0'

发送bit '1':
DHT11拉低总线50us
    ↓
DHT11拉高总线70us左右
    ↓
代表发送了一个bit '1'
(3) 结束信号
  • 数据传输完毕后,主机将总线拉为高电平

DHT11数据格式
总共40bit,5个字节:

字节0:湿度整数部分(8bit)
字节1:湿度小数部分(8bit,通常为0)
字节2:温度整数部分(8bit)
字节3:温度小数部分(8bit,通常为0)
字节4:校验和(8bit)= 字节0+字节1+字节2+字节3

2.具体编译过程

CubeMX配置引脚
  • 将连接DHT11的GPIO引脚配置为开漏输出模式

  • 使能上拉电阻

  • 在代码中需要切换输入/输出模式

keil实现dht11.c
设置为输出模式
void set_gpio_output(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = GPIO_PIN_10;        // PB10引脚
    GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  // 推挽输出模式
    GPIO_InitStruct.Pull = GPIO_NOPULL;       // 无上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW; // 低速
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    return;
}

参数说明

  • GPIO_PIN_10:使用PB10引脚

  • GPIO_MODE_OUTPUT_PP:推挽输出模式(能输出高电平和低电平)

  • GPIO_NOPULL:无上下拉电阻

  • GPIO_SPEED_FREQ_LOW:低速模式(降低功耗和噪声)

设置为输入模式
void set_gpio_input(void)
{
    GPIO_InitTypeDef GPIO_InitStruct = {0};

    GPIO_InitStruct.Pin = GPIO_PIN_10;
    GPIO_InitStruct.Mode = GPIO_MODE_INPUT;   // 输入模式
    GPIO_InitStruct.Pull = GPIO_NOPULL;       // 无上下拉
    GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
    HAL_GPIO_Init(GPIOB, &GPIO_InitStruct);

    return;
}
DHT11数据读取函数
int read_dht11(char *phum, char *ptemp)
{
    int timeout = 0;
    int i = 0;
    int n = 0;
    int cnt = 0;
    unsigned char data[5] = {0};

    // 1. 开启TIM3定时器开始计数
    HAL_TIM_Base_Start(&htim3);

    // 2. 等待传感器工作稳定
    HAL_Delay(100);

    // 3. 发送起始信号:拉低至少18ms
    set_gpio_output();                    // 设置为输出模式
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, 0);  // 拉低
    HAL_Delay(25);                         // 保持低电平25ms

    // 4. 拉高总线,准备接收响应
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, 1);  // 拉高
    set_gpio_input();                       // 切换为输入模式

    // 5. 等待DHT11拉低总线(响应信号开始)
    timeout = 0;
    while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10) && timeout < 5)
    {
        HAL_Delay(10);  // 延时10us
        timeout++;
    }

    // 6. 超时判断
    if (timeout >= 5)
    {
        return -1;  // -1代表DHT11没有应答
    }

    // 7. 等待高电平到来(数据开始)
    while (!HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10));

    // 8. 循环接收5个字节数据(40位)
    for (i = 0; i < 5; i++)
    {
        data[i] = 0;
        for (n = 7; n >= 0; n--)  // 从高位到低位接收
        {
            cnt = 0;
            
            // 等待高电平到来(数据位开始)
            while (!HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10));

            // 测量高电平持续时间
            while (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_10))
            {
                HAL_Delayus(10);  // 每10us计数一次
                cnt++;
            }

            // 判断数据位:高电平时间大于50us代表bit'1'
            if (cnt > 5)  // 5*10us = 50us
            {
                data[i] |= (0x1 << n);  // 对应位置1
            }
            // 否则bit'0'(data[i]该位保持0)
        }
    }

    // 9. 校验数据
    if (data[0] + data[1] + data[2] + data[3] != data[4])
    {
        return -2;  // -2代表数据校验出错
    }

    // 10. GPIO设置为输出,拉高总线(结束)
    set_gpio_output();
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_10, 1);

    // 11. 格式化温湿度数据
    sprintf(phum, "%d.%d\n", data[0], data[1]);   // 湿度
    sprintf(ptemp, "%d.%d\n", data[2], data[3]);  // 温度

    return 0;  // 读取成功
}
keil实现主函数
int main(void)
{
    /* 初始化HAL库 */
    HAL_Init();
    
    /* 配置系统时钟 */
    SystemClock_Config();
    
    /* 初始化外设 */
    MX_GPIO_Init();      // GPIO初始化
    MX_TIM3_Init();      // 定时器3初始化(用于微秒延时)
    MX_UART1_UART_Init(); // 串口1初始化

    char hum[32] = {0};   // 湿度字符串缓冲区
    char temp[32] = {0};  // 温度字符串缓冲区
    char s[32] = {0};     // 状态字符串缓冲区
    int ret = 0;

    while (1)
    {
        // 读取DHT11数据
        ret = read_dht11(hum, temp);
        
        // 延时2秒(DHT11读取间隔至少1秒)
        HAL_Delay(2000);

        // 通过串口发送状态信息
        sprintf(s, "ret = %d\r\n", ret);
        HAL_UART_Transmit(&huart1, (uint8_t *)s, strlen(s), 1000);
        
        // 发送湿度数据
        HAL_UART_Transmit(&huart1, (uint8_t *)hum, strlen(hum), 1000);
        
        // 发送温度数据
        HAL_UART_Transmit(&huart1, (uint8_t *)temp, strlen(temp), 1000);
    }
}

3. 代码执行流程

(1) 发送起始信号
set_gpio_output()           → 设置为输出模式
HAL_GPIO_WritePin(,0)       → 拉低总线
HAL_Delay(25)               → 保持低电平25ms(>18ms)
HAL_GPIO_WritePin(,1)       → 拉高总线
set_gpio_input()            → 切换为输入,准备接收
(2) 等待DHT11响应
timeout计数等待拉低
    ↓
超时(timeout>=5) → 返回-1(无应答)
    ↓
等待拉高(响应结束)
(3) 接收数据位原理
每个数据位开始:等待高电平
    ↓
测量高电平持续时间(每10us计数)
    ↓
cnt > 5(>50us)→ 判定为bit'1'
cnt ≤ 5(≤50us)→ 判定为bit'0'
    ↓
数据位存入对应字节的对应位
(4) 数据校验
data[0] + data[1] + data[2] + data[3] == data[4] → 数据正确
否则返回-2(校验错误)

4. 超时和错误码说明

返回值 含义
0 读取成功
-1 DHT11无应答(未检测到拉低信号)
-2 数据校验失败

5. 逻辑分析仪使用

  • 作用:观察DHT11通信时序,验证时序是否正确

  • 观察点:起始信号长度、应答信号、数据位0和1的波形差异

二、LM75温度传感器

1.具体概念

I2C时序基础
(1) 起始信号
当SCL为高电平时
    ↓
SDA由高电平变为低电平
    ↓
代表主机发送了一个起始信号
(2) 从机地址 + 读写位
主机发送:7位从机地址 + 读写位
读写位:0表示写,1表示读
    ↓
从机应答:ACK(低电平)或 NACK(高电平)
(3) 数据传输规则
SCL状态 SDA操作 说明
SCL高电平 SDA要保持稳定 主机采样数据
SCL低电平 从机可以进行数据传输 SDA可以变化
(4) 结束信号
当SCL为高电平时
    ↓
SDA由低电平变为高电平
    ↓
代表发送了一个结束信号
I2C写时序(主机向从机写入数据)
1. 发送起始信号
2. 发送7位从机地址 + 写命令(0)
3. 等待从机应答(ACK)
4. 主机发送数据
5. 等待从机应答(ACK)
6. 发送结束信号
I2C读时序(主机从从机读取数据)
1. 发送起始信号
2. 发送7位从机地址 + 写命令(0)
3. 等待从机应答(ACK)
4. 发送寄存器地址
5. 等待从机应答(ACK)
6. 发送起始信号(重新起始)
7. 发送7位从机地址 + 读命令(1)
8. 等待从机应答(ACK)
9. 接收数据并发送应答(ACK)
10. 发送结束信号
LM75从机地址
  • 默认地址:0x48

  • 地址组成:7位地址(A2、A1、A0默认为0)

  • HAL库使用:需要将7位地址左移1位(变成8位地址)

LM75温度寄存器
  • 寄存器地址:0x00

  • 数据格式

    • 总共16bit数据

    • 先发高位,再发低位

    • 需要舍弃掉低7bit(右移7位)

    • 分辨率:0.5°C

2.具体编译过程

CubeMX配置
  • 配置I2C引脚(SCL、SDA)

  • 使能I2C外设

  • 设置I2C速度模式(标准模式100KHz或快速模式400KHz)

主函数实现
int main(void)
{
    /* 初始化HAL库 */
    HAL_Init();
    
    /* 配置系统时钟 */
    SystemClock_Config();
    
    /* 初始化外设 */
    MX_GPIO_Init();      // GPIO初始化
    MX_I2C1_Init();      // I2C1初始化(连接LM75)
    MX_USART1_UART_Init(); // 串口1初始化

    uint8_t tmpbuff[2] = {0};  // 存放读取到的2字节温度数据
    float temp = 0;             // 计算后的温度值
    char send_buff[32] = {0};   // 发送缓冲区

    while (1)
    {
        // 1. 读取LM75温度寄存器
        HAL_I2C_Mem_Read(
            &hi2c1,              // I2C控制器
            (0x48 << 1),         // LM75地址(7位0x48左移1位)
            0x00,                // 温度寄存器地址
            1,                   // 寄存器地址长度(1字节)
            (uint8_t *)tmpbuff,  // 存放读取数据的缓冲区
            sizeof(tmpbuff),     // 读取2字节
            1000                 // 超时1000ms
        );

        // 2. 温度数据转换
        // tmpbuff[0]是高8位,tmpbuff[1]是低8位
        // 合并后右移7位,舍弃低7位,再乘以0.5
        temp = ((tmpbuff[0] << 8 | tmpbuff[1]) >> 7) * 0.5;

        // 3. 格式化温度数据
        sprintf(send_buff, "lm75_temp = %.2f\r\n", temp);

        // 4. 通过串口发送
        HAL_UART_Transmit(&huart1, (uint8_t *)send_buff, strlen(send_buff), 1000);

        // 5. 延时1秒
        HAL_Delay(1000);
    }
}
I2C读取函数详解
HAL_I2C_Mem_Read(
    &hi2c1,              // I2C1外设
    (0x48 << 1),         // 地址:0x48左移1位 = 0x90(HAL库需要8位地址)
    0x00,                // 寄存器地址:LM75温度寄存器
    1,                   // 寄存器地址长度:1字节
    (uint8_t *)tmpbuff,  // 数据缓冲区指针
    2,                   // 要读取的字节数:2字节
    1000                 // 超时时间:1000ms
);
温度数据转换
// 原始数据:tmpbuff[0] = 0x1A, tmpbuff[1] = 0xC0
// 二进制:   00011010       11000000

// 1. 合并2字节
uint16_t raw = tmpbuff[0] << 8 | tmpbuff[1];
// 0x1A << 8 = 0x1A00
// 0x1A00 | 0xC0 = 0x1AC0
// 二进制:00011010 11000000

// 2. 右移7位(舍弃低7位)
uint16_t value = raw >> 7;
// 0x1AC0 >> 7 = 0x0358
// 二进制:00000011 01011000
// 十进制:856

// 3. 乘以分辨率0.5
float temp = value * 0.5;
// 856 * 0.5 = 428.0°C?(实际温度应在此范围内)
// 注意:LM75实际温度范围-55°C~125°C,这里示例数据需要根据实际调整

3. LM75数据格式说明

16位温度数据寄存器(地址0x00):

D15 D14 D13 D12 D11 D10 D9 D8 D7 D6 D5 D4 D3 D2 D1 D0
温度数据(11位有效)                    未使用(5位)
         ↓
右移7位后得到11位温度数据
         ↓
乘以0.5得到实际温度

三、两个传感器代码对比

1. 通信方式对比

传感器 通信方式 代码特点
DHT11 GPIO单总线 需要精确时序,微秒级延时,手动切换输入输出
LM75 I2C总线 使用HAL库函数,时序由硬件处理

2. 数据处理对比

传感器 数据读取 数据转换
DHT11 5字节原始数据 data[0]=湿度整数,data[1]=湿度小数,data[2]=温度整数,data[3]=温度小数
LM75 2字节原始数据 ((high<<8 low) >> 7) * 0.5

3. 错误处理对比

传感器 错误码 含义
DHT11 -1 无应答
-2 校验错误
LM75 HAL_OK=0 成功
HAL_TIMEOUT 超时

四、关键函数总结

DHT11关键函数

函数 作用
set_gpio_output() 设置PB10为输出模式
set_gpio_input() 设置PB10为输入模式
HAL_Delayus() 微秒级延时(基于TIM3)
read_dht11() 读取温湿度数据

LM75关键函数

函数 作用
HAL_I2C_Mem_Read() 读取I2C设备寄存器
HAL_UART_Transmit() 串口发送数据

共用函数

函数 作用
HAL_Delay() 毫秒级延时
sprintf() 格式化字符串

总结

  1. DHT11驱动:需要精确时序控制,通过测量高电平持续时间判断0/1

  2. LM75驱动:使用HAL库I2C函数,简化通信过程

  3. 数据校验:DHT11有校验和验证,LM75无校验

  4. 错误处理:DHT11返回自定义错误码,LM75返回HAL库状态

  5. 数据格式:DHT11返回整数和小数,LM75需计算得到浮点数

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐