使用HAL库驱动DS18B20温度传感器在STM32单总线通信

 代码:

 !!!只要代码的就复制走,需要搞懂原理的看代码后面的

 DS18B20.h

#ifndef __DS18B20_H
#define __DS18B20_H


//修改引脚在这
#define DS18B20_PIN         GPIO_PIN_1          //DS18B20引脚
#define DS18B20_PORT        GPIOA               //DS18B20端口

#define DS18B20_LOW      HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET)       //引脚输出低电平
#define DS18B20_HIGH     HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET)         //引脚输出高电平


#define DS18B20_IN        HAL_GPIO_ReadPin(DS18B20_PORT, DS18B20_PIN)                       //读取引脚电平



void DS18B20_Tem_Convert(void);
void DS18B20_Read_Tem(float* tem);
void DS18B20_MspInit(void);

#endif

 DS18B20.c

#include "stm32f1xx_hal.h"
#include "DS18B20.h"
#include "Delay.h"
#include "UART.h"

#define DS18B20_SKIP_ROM          0xCC
#define DS18B20_CONVERT_T         0x44
#define DS18B20_READ_SCRATCHPAD   0xBE

void DS18B20_MspInit(void)
{	
	__HAL_RCC_GPIOA_CLK_ENABLE();
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.Pin = DS18B20_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;
    GPIO_InitStruct.Pull = GPIO_NOPULL; // 添加这行以确保没有上拉或下拉
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct);
}

uint8_t DS18B20_Init(void)
{
    uint8_t AckBit;
    DS18B20_HIGH;

    DS18B20_LOW;
    Delay_us(480);

    DS18B20_HIGH;
    Delay_us(60);

    AckBit = DS18B20_IN;
    Delay_us(420);

    if(AckBit)
    {
        /*打印错误信息*/
        return 1;
    }
    return 0;
}

void DS18B20_WriteBit(uint8_t bit)
{
    DS18B20_LOW;
    Delay_us(15);
    if(bit)
    {
        DS18B20_HIGH;
    }
    else
    {
        DS18B20_LOW;    /*可省略*/
    }
    Delay_us(45);
    DS18B20_HIGH;
}

void DS18B20_WriteByte(uint8_t byte)
{
    uint8_t i;
    for(i = 0; i < 8; i++)
    {
        DS18B20_WriteBit(byte & (0x01 << i));
    }
}

uint8_t DS18B20_ReadBit(void)
{
    uint8_t bit;

    DS18B20_LOW;
    Delay_us(5);

    DS18B20_HIGH;
    Delay_us(10);
    
    if(DS18B20_IN)
    {
        bit = 1;
    }
    else
    {
        bit = 0;
    }
    Delay_us(45);
    return bit;
}

void DS18B20_ReadByte(uint8_t *byte)
{
    uint8_t i;
    for(i = 0; i < 8; i++)
    {
        *byte >>= 1; // 右移一位
        if(DS18B20_ReadBit())
        {
            *byte |= 0x80; // 设置最高位
        }
    }
}

void DS18B20_Tem_Convert(void)
{
    
    if(DS18B20_Init() == 0)
    {
        DS18B20_WriteByte(DS18B20_SKIP_ROM);
        DS18B20_WriteByte(DS18B20_CONVERT_T);
    }
    else
    {
        /*打印错误信息*/
        Printf("DS18B20_Init ERROR");
    }
}

void DS18B20_Read_Tem(float* tem)
{
    uint16_t temValue;
    uint8_t tem_LSB, tem_MSB;
    if(DS18B20_Init() == 0)
    {
        DS18B20_WriteByte(DS18B20_SKIP_ROM);
        DS18B20_WriteByte(DS18B20_READ_SCRATCHPAD);

        DS18B20_ReadByte(&tem_LSB);
        DS18B20_ReadByte(&tem_MSB);

        temValue = (tem_MSB << 8) | tem_LSB;
        
        *tem = (float)temValue / 16;
    }
    else
    {
        /*打印错误信息*/
        Printf("DS18B20_Init ERROR");
    }
}



结合代码+数据手册时序图讲解:

头文件DS18B20.h

#ifndef __DS18B20_H
#define __DS18B20_H


//修改引脚在这
#define DS18B20_PIN         GPIO_PIN_1          //DS18B20引脚
#define DS18B20_PORT        GPIOA               //DS18B20端口

#define DS18B20_LOW      HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_RESET)       //引脚输出低电平
#define DS18B20_HIGH     HAL_GPIO_WritePin(DS18B20_PORT, DS18B20_PIN, GPIO_PIN_SET)         //引脚输出高电平


#define DS18B20_IN        HAL_GPIO_ReadPin(DS18B20_PORT, DS18B20_PIN)                       //读取引脚电平



void DS18B20_Tem_Convert(void);
void DS18B20_Read_Tem(float* tem);
void DS18B20_MspInit(void);

#endif

1、在DS18B20的头文件,定义了引脚端口,方便修改。

2、定义了输出高低电平和读取电平的宏定义

C文件DS18B20.c

#include "stm32f1xx_hal.h"
#include "DS18B20.h"
#include "Delay.h"
#include "UART.h"

#define DS18B20_SKIP_ROM          0xCC
#define DS18B20_CONVERT_T         0x44
#define DS18B20_READ_SCRATCHPAD   0xBE

void DS18B20_MspInit(void)
{	
	__HAL_RCC_GPIOA_CLK_ENABLE();
	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.Pin = DS18B20_PIN;
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_OD;     //开漏输出模式
    GPIO_InitStruct.Pull = GPIO_NOPULL;             // 添加这行以确保没有上拉或下拉
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
	HAL_GPIO_Init(DS18B20_PORT, &GPIO_InitStruct);
}

1、包含的头文件:HAL库头文件 DS18B20头文件 Delay延时函数头文件 串口UART打印头文件

2、宏定义了三个DS1820 命令

        (1)DS18B20_SKIP_ROM 

        (2)DS18B20_CONVERT_T

        (3)DS18B20_READ_SCRATCHPAD

3、底层GPIO口初始化(看注释够了)

DS18B20初始化函数

uint8_t DS18B20_Init(void)
{
    uint8_t AckBit;     //判断DS18B20是否存在
    DS18B20_HIGH;

    DS18B20_LOW;
    Delay_us(480);

    DS18B20_HIGH;
    Delay_us(60);

    AckBit = DS18B20_IN;
    Delay_us(420);

    if(AckBit)
    {
        /*打印错误信息*/
        return 1;
    }
    return 0;
}

1、图11为初始化时序图,看着代码的延时时间一一对应,很好理解,在延时60微妙后读取引脚电平状态给AckBit,再判断AckBit,如果为低电平,DS18B20存在,如果为高电平DS18B20不存在或者未响应。

向DS18B20写入一个位的函数

void DS18B20_WriteBit(uint8_t bit)
{
    DS18B20_LOW;
    Delay_us(15);
    if(bit)
    {
        DS18B20_HIGH;
    }
    else
    {
        DS18B20_LOW;    /*可省略*/
    }
    Delay_us(45);
    DS18B20_HIGH;
}

1、下图为写入DS18B20一位数据的时序图,看着代码的延时时间一一对应,很好理解。设置低电平15微妙后,判断传入的bit值,设置相应的电平。(注:写入0的时候,低电平至少保持60微妙,所以总的延时事件加起来为60微妙,一一对应,很好理解)

向DS18B20写入一个字节的函数

void DS18B20_WriteByte(uint8_t byte)
{
    uint8_t i;
    for(i = 0; i < 8; i++)
    {
        DS18B20_WriteBit(byte & (0x01 << i));
    }
}

1、这个函数需要传入一个byte参数,八次循环的操作是将 byte 的每一位写入 DS18B20 传感器。

  • byte & (0x01 << i):
  • 0x01 是一个二进制数 00000001。
  • 0x01 << i 表示将 0x01 左移 i 位。
  • byte & (0x01 << i) 通过按位与操作提取 byte 的第 i 位。
  • DS18B20_WriteBit:
  • 这个函数将提取出的第 i 位写入 DS18B20 传感器。
  • 跟时序无关,纯纯C语言用法,不深入讲解。

    从DS18B20读取一个位的函数

    uint8_t DS18B20_ReadBit(void)
    {
        uint8_t bit;
    
        DS18B20_LOW;
        Delay_us(5);
    
        DS18B20_HIGH;
        Delay_us(10);
        
        if(DS18B20_IN)
        {
            bit = 1;
        }
        else
        {
            bit = 0;
        }
        Delay_us(45);
        return bit;
    }

    1、下图为写入DS18B20一位数据的时序图,看着代码的延时时间一一对应,很好理解。也是总共60微妙,先拉低5微妙,再拉高(释放)需要10微妙,之后再读取数据。(注:写入0的时候,低电平至少保持60微妙,所以总的延时事件加起来为60微妙,一一对应,很好理解)

    从DS18B20读取一个字节的函数

    void DS18B20_ReadByte(uint8_t *byte)
    {
        uint8_t i;
        for(i = 0; i < 8; i++)
        {
            *byte >>= 1; // 右移一位
            if(DS18B20_ReadBit())
            {
                *byte |= 0x80; // 设置最高位
            }
        }
    }

    1、这个函数需要传入一个byte参数,用来储存读取的字节。跟时序无关,纯纯C语言用法,不深入讲解。

  • for(i = 0; i < 8; i++): 循环 8 次,每次读取 1 位数据。
  • *byte >>= 1;: 将 byte 的值右移一位。右移操作会将当前最低位丢弃,并在最高位填充 0。
  • if(DS18B20_ReadBit()): 调用 DS18B20_ReadBit 函数读取一位数据。如果读取到的位是 1,则进入 if 语句块。
  • *byte |= 0x80;: 将 byte 的最高位置为 1。0x80 是一个 8 位二进制数,最高位为 1,其余位为 0(即 10000000)。
  • 该函数通过循环 8 次,每次读取 1 位数据,并将其存储到 byte 中。每次读取到的位会被放置在 byte 的最高位,然后通过右移操作将其移到正确的位置。最终,byte 中将包含从 DS18B20 传感器读取的一个完整字节的数据。

    DS18B20开始温度变换

    void DS18B20_Tem_Convert(void)
    {
        
        if(DS18B20_Init() == 0)
        {
            DS18B20_WriteByte(DS18B20_SKIP_ROM);
            DS18B20_WriteByte(DS18B20_CONVERT_T);
        }
        else
        {
            /*打印错误信息*/
            Printf("DS18B20_Init ERROR");
        }
    }

    1、DS18B20_SKIP_ROM :跳过ROM命令(0xCC)用于在单总线系统中跳过ROM选择步骤,直接对所连接的DS18B20传感器进行操作。这在只有一个传感器或不需要区分多个传感器时非常有用。DS18B20_CONVERT_T :温度转换命令。都是通过向DS18B20写入一个字节的函数写入命令。

    DS18B20读取温度

    void DS18B20_Read_Tem(float* tem)
    {
        uint16_t temValue;
        uint8_t tem_LSB, tem_MSB;
        if(DS18B20_Init() == 0)
        {
            DS18B20_WriteByte(DS18B20_SKIP_ROM);
            DS18B20_WriteByte(DS18B20_READ_SCRATCHPAD);
    
            DS18B20_ReadByte(&tem_LSB);
            DS18B20_ReadByte(&tem_MSB);
    
            temValue = (tem_MSB << 8) | tem_LSB;
            
            *tem = (float)temValue / 16;
        }
        else
        {
            /*打印错误信息*/
            Printf("DS18B20_Init ERROR");
        }
    }

    1、DS18B20_READ_SCRATCHPAD :读取DS18B20传感器的暂存器(Scratchpad)的命令。

    2、下面这两行代码分别从DS18B20传感器读取温度值的低字节(tem_LSB)和高字节(tem_MSB)。

    DS18B20_ReadByte(&tem_LSB);
    DS18B20_ReadByte(&tem_MSB);

    3、这行代码将高字节左移8位,然后与低字节进行按位或操作,合并成一个16位的温度值(temValue)。

    temValue = (tem_MSB << 8) | tem_LSB;

    4、这行代码将16位的温度值除以16,转换为实际的温度值(单位:摄氏度),并存储在tem指针指向的变量中。

    *tem = (float)temValue / 16;

    不理解在评论问!!!

    HAL库的Delay_us代码

    void Delay_us(uint32_t us)
    {
        // 初始化延迟时间总数为0
        uint32_t total = 0;
    
        // 计算需要延迟的时间(以微秒为单位)
        uint32_t target = (SystemCoreClock/1000000U) * us;
    
        // 获取初始的SysTick计数值
        int last = SysTick->VAL;
        int now = last;
        int diff = 0;
    
        // 循环直到累计的延迟时间达到目标时间
        while(1)
        {
            // 获取当前的SysTick计数值
            now = SysTick->VAL;
    
            // 计算两次读取之间的差值
            diff = last - now;
    
            // 如果差值大于0,表示时间有增加
            if(diff > 0)
            {
                // 增加总延迟时间
                total += diff;
            }
            // 如果差值小于0,表示时间有减少,需要加上最大计数值
            else
            {
                total += diff + SysTick->LOAD;
            }
    
            // 如果总延迟时间大于目标时间,则退出循环
            if(total > target)
            {
                return;
            }
    
            // 更新上次读取的SysTick计数值
            last = now;
        }
    }

    作者:肖誉誉誉誉誉

    物联沃分享整理
    物联沃-IOTWORD物联网 » 使用HAL库驱动DS18B20温度传感器在STM32单总线通信

    发表回复