单片机传感器实验

单片机 :STM32F407

开发板:DMF407电机开发板

平台:keil V5.31

HSE 为8MHZ

HSI为16MHZ

原理图:

一、DS18B20数字温度传感器实验

传感器资料:

DS18B20的信号波形遵循单总线(1-Wire)协议,核心分为复位、写、读三类典型波形,所有时序都要求微秒级精度‌。以下是各类波形的关键参数和特征说明:

1. 初始化复位波形(通信第一步)

初始化是通信启动的必备环节,由主机发起复位、从机回应应答,完整波形分为三个阶段:

  • 主机复位脉冲 ‌:主机拉低总线,保持 ‌**480~960μs(典型值500μs)**‌,之后释放总线(拉高)
  • 从机存在脉冲 ‌:总线释放后,DS18B20等待 ‌16~60μs ‌,随后主动拉低总线 ‌60~240μs‌ 作为应答,最后释放总线
  • 总线恢复期 ‌:应答结束后需要至少 ‌480μs‌ 的恢复时间,才能开始后续操作

常见异常波形:无应答脉冲(总线持续高电平,一般是硬件断路或上拉电阻异常)、应答脉冲过短(电源不稳定,可加去耦电容解决)。

2. 写操作波形(写0/写1)

写操作按位传输,每一位对应一个时间隙,通过低电平持续时间区分写0和写1:

  • 写1波形 ‌:主机拉低总线后,‌15μs内必须释放总线‌(典型保持低电平5μs),之后保持高电平直到该时隙结束(整个时隙至少60μs,两位之间至少1μs恢复时间)
  • 写0波形 ‌:主机拉低总线后,保持 ‌**60~120μs(典型值60μs)**‌ 低电平,再释放总线,同样需要至少1μs的恢复时间

3. 读操作波形(读0/读1)

读操作由主机发起读时隙,DS18B20输出数据位:

  • 主机先拉低总线至少 ‌1μs ‌ 发起读请求,随后必须在 ‌15μs内完成总线状态采样
  • 若DS18B20输出‌0‌:总线会被持续拉低到整个时隙结束,采样得到低电平
  • 若DS18B20输出‌1‌:DS18B20不拉低总线,采样得到高电平
  • 单个读时隙至少需要60μs,两位之间保留至少1μs恢复时间

典型时序参数汇总

表格

操作类型 关键时间参数 允许范围(μs) 典型值(μs)
主机复位脉冲 总线拉低持续时间 480~960 500
从机应答脉冲 应答低电平持续时间 60~240 110
写1 总线拉低持续时间 1~15 5
写0 总线拉低持续时间 60~120 60
读操作采样点 主机发起后延迟采样 1~15

主函数:

复制代码
int main(void)
{
    uint8_t t = 0;
    short temperature;

    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
    delay_init(168);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    lcd_init();                         /* 初始化LCD */
    
    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "DS18B20 TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);

    while (ds18b20_init())              /* DS18B20初始化 */
    {
        lcd_show_string(30, 110, 200, 16, 16, "DS18B20 Error", RED);
        delay_ms(200);
        lcd_fill(30, 110, 239, 130 + 16, WHITE);
        delay_ms(200);
    }

    lcd_show_string(30, 110, 200, 16, 16, "DS18B20 OK", RED);
    lcd_show_string(30, 130, 200, 16, 16, "Temp:   . C", BLUE);

    while (1)
    {
        if (t % 10 == 0)                /* 每100ms读取一次 */
        {
            temperature = ds18b20_get_temperature();

            if (temperature < 0)
            {
                lcd_show_char(30 + 40, 130, '-', 16, 0, BLUE); /* 显示负号 */
                temperature = -temperature;                    /* 转为正数 */
            }
            else
            {
                lcd_show_char(30 + 40, 130, ' ', 16, 0, BLUE); /* 去掉负号 */
            }

            lcd_show_num(30 + 40 + 8, 130, temperature / 10, 2, 16, BLUE);  /* 显示正数部分 */
            lcd_show_num(30 + 40 + 32, 130, temperature % 10, 1, 16, BLUE); /* 显示小数部分 */
        }

        delay_ms(10);
        t++;

        if (t == 20)
        {
            t = 0;
            LED0_TOGGLE(); /* LED0闪烁 */
        }
    }
}

配置:

复制代码
/* DS18B20引脚 定义 */

#define DS18B20_DQ_GPIO_PORT                GPIOG
#define DS18B20_DQ_GPIO_PIN                 GPIO_PIN_15
#define DS18B20_DQ_GPIO_CLK_ENABLE()        do{ __HAL_RCC_GPIOG_CLK_ENABLE(); }while(0)   /* PG口时钟使能 */

/******************************************************************************************/

/* IO操作函数 */
#define DS18B20_DQ_OUT(x)   do{ x ? \
                                HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN, GPIO_PIN_SET) : \
                                HAL_GPIO_WritePin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN, GPIO_PIN_RESET); \
                            }while(0)                                                       /* 数据端口输出 */
#define DS18B20_DQ_IN       HAL_GPIO_ReadPin(DS18B20_DQ_GPIO_PORT, DS18B20_DQ_GPIO_PIN)     /* 数据端口输入 */

初始化:

复制代码
uint8_t ds18b20_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;
    
    DS18B20_DQ_GPIO_CLK_ENABLE();   /* 开启DQ引脚时钟 */

    gpio_init_struct.Pin = DS18B20_DQ_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;            /* 开漏输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(DS18B20_DQ_GPIO_PORT, &gpio_init_struct); /* 初始化DS18B20_DQ引脚 */
    /* DS18B20_DQ引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */

    ds18b20_reset();
    return ds18b20_check();
}

传感器检查:

复制代码
uint8_t ds18b20_check(void)
{
    uint8_t retry = 0;
    uint8_t rval = 0;

    while (DS18B20_DQ_IN && retry < 200)        /* 等待DQ变低, 等待200us */
    {
        retry++;
        delay_us(1);
    }

    if (retry >= 200)
    {
        rval = 1;
    }
    else
    {
        retry = 0;

        while (!DS18B20_DQ_IN && retry < 240)   /* 等待DQ变高, 等待240us */
        {
            retry++;
            delay_us(1);
        }

        if (retry >= 240) rval = 1;
    }

    return rval;
}

没有外接传感器,检测失败。

读数据:

复制代码
short ds18b20_get_temperature(void)
{
    uint8_t flag = 1;           /* 默认温度为正数 */
    uint8_t TL, TH;
    short temp;
    
    ds18b20_start();            /*  ds1820 start convert */
    ds18b20_reset();
    ds18b20_check();
    ds18b20_write_byte(0xcc);   /*  skip rom */
    ds18b20_write_byte(0xbe);   /*  convert */
    TL = ds18b20_read_byte();   /*  LSB */
    TH = ds18b20_read_byte();   /*  MSB */

    if (TH > 7)
    {/* 温度为负,查看DS18B20的温度表示法与计算机存储正负数据的原理一致:
                正数补码为寄存器存储的数据自身,负数补码为寄存器存储值按位取反后+1
                所以我们直接取它实际的负数部分,但负数的补码为取反后加一,但考虑到低位可能+1后有进位和代码冗余,
                我们这里先暂时没有作+1的处理,这里需要留意	*/
        TH = ~TH;
        TL = ~TL;
        flag = 0;   
    }

    temp = TH;      /* 获得高八位 */
    temp <<= 8;
    temp += TL;     /* 获得底八位 */

    /* 转换成实际温度 */
    if (flag == 0)
    {/* 将温度转换成负温度,这里的+1参考前面的说明 */
        temp = (double)(temp+1) * 0.625;
        temp = -temp;   
    }
    else
    {
        temp = (double)temp * 0.625;
    }
    
    return temp;
}

写操作:

复制代码
static void ds18b20_write_byte(uint8_t data)
{
    uint8_t j;

    for (j = 1; j <= 8; j++)
    {
        if (data & 0x01)
        {
            DS18B20_DQ_OUT(0);  /*  Write 1 */
            delay_us(2);
            DS18B20_DQ_OUT(1);
            delay_us(60);
        }
        else
        {
            DS18B20_DQ_OUT(0);  /*  Write 0 */
            delay_us(60);
            DS18B20_DQ_OUT(1);
            delay_us(2);
        }

        data >>= 1;             /* 右移,获取高一位数据 */
    }
}

读操作:

复制代码
static uint8_t ds18b20_read_byte(void)
{
    uint8_t i, b, data = 0;

    for (i = 0; i < 8; i++)
    {
        b = ds18b20_read_bit(); /* DS18B20先输出低位数据 ,高位数据后输出 */
        
        data |= b << i;         /* 填充data的每一位 */ 
    }

    return data;
}

测试结果:

二、DHT11数字温湿度传感器实验

DHT11数字温湿度传感器资料:

DHT11采用单总线通信协议,信号波形分为四个核心阶段:起始信号、应答信号、数据传输,所有时序要求严格,微秒级误差就可能导致通信失败‌。结合公开资料整理的各阶段波形特征如下:

  1. 起始信号波形(主机发起,单片机→DHT11)

通信由主机主动发起,波形特征:

总线空闲时为高电平,主机先拉低总线,保持‌至少18ms‌(典型值20ms),保证DHT11能稳定检测到起始请求

之后主机释放总线(拉高),等待DHT11回应,拉高后需等待‌20-40μs‌让DHT11响应

常见异常:拉低时间小于18ms会导致DHT11不响应,没有应答信号。

  1. 应答信号波形(DHT11→主机)

DHT11检测到起始信号后,会发送固定的应答波形:

先拉低总线,保持‌80μs‌作为响应信号

再拉高总线,保持‌80μs‌,准备开始发送40位数据

异常特征:应答脉冲超时或持续时间不对,大概率是接线错误或上拉电阻异常(DHT11的DATA线必须接4.7kΩ~10kΩ上拉电阻)。

  1. 数据传输波形(DHT11→主机,共40位)

每一位数据都以固定低电平开头,通过高电平的持续时间区分0和1:

表格

数据位 低电平时长 高电平时长 单比特总时长

逻辑0 固定50μs 26~28μs ~76μs

逻辑1 固定50μs 约70μs ~120μs

完整的40位数据格式为:8位湿度整数+8位湿度小数+8位温度整数+8位温度小数+8位校验和,高位先传输,DHT11实际输出的小数部分始终为0。

完整通信总时序

主机起始→2.DHT11应答→3.逐位输出40位数据→4.传输完成,DHT11自动返回低功耗模式,等待下一次起始信号

实际调试中,超过60%的读取失败都是时序偏差导致:如果出现数据校验错误、间歇性读取失败,优先检查延时函数精度,建议使用定时器或汇编级精确延时来保证时序准确。

主函数:

复制代码
int main(void)
{
    uint8_t t = 0;
    uint8_t temperature;
    uint8_t humidity;
    
    HAL_Init();                         /* 初始化HAL库 */
    sys_stm32_clock_init(336, 8, 2, 7); /* 设置时钟, 168Mhz */
    delay_init(168);                    /* 延时初始化 */
    usart_init(115200);                 /* 串口初始化为115200 */
    led_init();                         /* 初始化LED */
    lcd_init();                         /* 初始化LCD */
    
    lcd_show_string(30, 50, 200, 16, 16, "STM32", RED);
    lcd_show_string(30, 70, 200, 16, 16, "DHT11 TEST", RED);
    lcd_show_string(30, 90, 200, 16, 16, "ATOM@ALIENTEK", RED);

    while (dht11_init())    /* DHT11初始化 */
    {
        lcd_show_string(30, 110, 200, 16, 16, "DHT11 Error", RED);
        delay_ms(200);
        lcd_fill(30, 110, 239, 130 + 16, WHITE);
        delay_ms(200);
    }

    lcd_show_string(30, 110, 200, 16, 16, "DHT11 OK", RED);
    lcd_show_string(30, 130, 200, 16, 16, "Temp:  C", BLUE);
    lcd_show_string(30, 150, 200, 16, 16, "Humi:  %", BLUE);

    while (1)
    {
        if (t % 10 == 0)    /* 每100ms读取一次 */
        {
            dht11_read_data(&temperature, &humidity);             /* 读取温湿度值 */
            lcd_show_num(30 + 40, 130, temperature, 2, 16, BLUE); /* 显示温度 */
            lcd_show_num(30 + 40, 150, humidity, 2, 16, BLUE);    /* 显示湿度 */
        }

        delay_ms(10);
        t++;

        if (t == 20)
        {
            t = 0;
            LED0_TOGGLE();  /* LED0闪烁 */
        }
    }
}

配置:

复制代码
/* DHT11 引脚 定义 */

#define DHT11_DQ_GPIO_PORT                  GPIOG
#define DHT11_DQ_GPIO_PIN                   GPIO_PIN_15
#define DHT11_DQ_GPIO_CLK_ENABLE()          do{ __HAL_RCC_GPIOG_CLK_ENABLE(); }while(0)     /* PG口时钟使能 */

/******************************************************************************************/

/* IO操作函数 */
#define DHT11_DQ_OUT(x)     do{ x ? \
                                HAL_GPIO_WritePin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN, GPIO_PIN_SET) : \
                                HAL_GPIO_WritePin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN, GPIO_PIN_RESET); \
                            }while(0)                                                       /* 数据端口输出 */
#define DHT11_DQ_IN         HAL_GPIO_ReadPin(DHT11_DQ_GPIO_PORT, DHT11_DQ_GPIO_PIN)         /* 数据端口输入 */

初始化:

复制代码
uint8_t dht11_init(void)
{
    GPIO_InitTypeDef gpio_init_struct;

    DHT11_DQ_GPIO_CLK_ENABLE();     /* 开启DQ引脚时钟 */

    gpio_init_struct.Pin = DHT11_DQ_GPIO_PIN;
    gpio_init_struct.Mode = GPIO_MODE_OUTPUT_OD;            /* 开漏输出 */
    gpio_init_struct.Pull = GPIO_PULLUP;                    /* 上拉 */
    gpio_init_struct.Speed = GPIO_SPEED_FREQ_HIGH;          /* 高速 */
    HAL_GPIO_Init(DHT11_DQ_GPIO_PORT, &gpio_init_struct);   /* 初始化DHT11_DQ引脚 */
    /* DHT11_DQ引脚模式设置,开漏输出,上拉, 这样就不用再设置IO方向了, 开漏输出的时候(=1), 也可以读取外部信号的高低电平 */

    dht11_reset();
    return dht11_check();
}

硬件检测:

复制代码
uint8_t dht11_check(void)
{
    uint8_t retry = 0;
    uint8_t rval = 0;

    while (DHT11_DQ_IN && retry < 100)  /* DHT11会拉低40~80us */
    {
        retry++;
        delay_us(1);
    }

    if (retry >= 100)
    {
        rval = 1;
    }
    else
    {
        retry = 0;

        while (!DHT11_DQ_IN && retry < 100) /* DHT11拉低后会再次拉高40~80us */
        {
            retry++;
            delay_us(1);
        }
        if (retry >= 100) rval = 1;
    }
    
    return rval;
}

不接传感器,检测失败!

读数据:

复制代码
uint8_t dht11_read_data(uint8_t *temp, uint8_t *humi)
{
    uint8_t buf[5];
    uint8_t i;
    dht11_reset();

    if (dht11_check() == 0)
    {
        for (i = 0; i < 5; i++)     /* 读取40位数据 */
        {
            buf[i] = dht11_read_byte();
        }

        if ((buf[0] + buf[1] + buf[2] + buf[3]) == buf[4])
        {
            *humi = buf[0];
            *temp = buf[2];
        }
    }
    else
    {
        return 1;
    }
    
    return 0;
}

读操作:

复制代码
uint8_t dht11_read_bit(void)
{
    uint8_t retry = 0;

    while (DHT11_DQ_IN && retry < 100)  /* 等待变为低电平 */
    {
        retry++;
        delay_us(1);
    }

    retry = 0;

    while (!DHT11_DQ_IN && retry < 100) /* 等待变高电平 */
    {
        retry++;
        delay_us(1);
    }

    delay_us(40);       /* 等待40us */

    if (DHT11_DQ_IN)    /* 根据引脚状态返回 bit */
    {
        return 1;
    }
    else 
    {
        return 0;
    }
}

/**
 * @brief       从DHT11读取一个字节
 * @param       无
 * @retval      读到的数据
 */
static uint8_t dht11_read_byte(void)
{
    uint8_t i, data = 0;

    for (i = 0; i < 8; i++)         /* 循环读取8位数据 */
    {
        data <<= 1;                 /* 高位数据先输出, 先左移一位 */
        data |= dht11_read_bit();   /* 读取1bit数据 */
    }

    return data;
}

测试结果:

相关推荐
芯岭技术4 小时前
PY32F030国产32位MCU,应用场景广泛,宽工作电压、丰富外设
单片机·嵌入式硬件·物联网
FreakStudio8 小时前
大话电容传感器和电容SOC芯片,看这一篇就够了
python·单片机·嵌入式·面向对象·并行计算·电子diy·电子计算机
信看9 小时前
常见通信接口
单片机·嵌入式硬件
Rsingstarzengjx10 小时前
STM32-F103ZET6开发板
stm32·单片机·嵌入式硬件
我先去打把游戏先10 小时前
VMware NAT 模式 Ubuntu 虚拟机「宿主机能上网、虚拟机 ping 不通外网 + apt 更新卡死」全故障复盘
linux·运维·vscode·单片机·嵌入式硬件·ubuntu·keil5
aini_lovee11 小时前
STM32 串口转CAN + WiFi模块实现WiFi转CAN网关
stm32·单片机·嵌入式硬件
zlinear数据采集卡11 小时前
输出短路保护电路深度解析:从电源的“最后一道防线”到ZLinear采集卡的硬核守护实战
开发语言·嵌入式硬件·持续集成
都在酒里11 小时前
FreeRTOS 手动移植教程(七):软件定时器 —— 不占硬件 Timer 的定时回调
stm32·单片机·嵌入式·rtos·嵌入式软件