前言

参考文章/博主

单总线

参考:

github工程

onewire(单总线)抽象

完美实现STM32单总线挂多个DS18B20

详解

onewire(单总线) 是DALLAS公司推出的外围串行扩展总线技术总线,顾名思义,它是采用 根信号线进行通信,既传输时钟信号又传输数据,而且能够进行双向通信,具有节省I/O口线、资源结构简单、成本低廉、便于总线扩展和维护等诸多优点。常用到单总线的器件,一般是稳定传感器、EEPROM、唯一序列号芯片等,如 DS18B20、DS2431

在使用单总线时, 往往很少CPU会提供硬件单总线几乎都是根据单总线标准的时序图,通过普通IO翻转模拟实现单总线。而在模式实现时序图的过程中,需要根据CPU时钟频率等条件进行时序时间计算,如果更换CPU后,需要重新计算时序时间

甚至供电线也可以不用,直接通过寄生方式从信号线上取电,可只连接 信号线(DQ)GND 即可使用,而且一般在信号线那加一个上拉电阻

图里说明, DS18B20 在输出时,是靠拉低 DQ 通信线实现低电平输出,靠释放 DQ 通信线(被上拉电阻拉高)来实现高电平输出

也就是说它可以实现 “线与”。多个 DS18B20 同时输出时,只要有一个低电平,则 DQ 通信线就会被拉低,只有低电平能被识别,只有都为高电平时,总线上才是高电平。单总线上识别不同的 DS18B20 就是靠“线与”来解决通信冲突的

时序

以下的时序是基于DS18B20的,它是单总线的经典应用

数据先低再高

还有就是 us 延时是非常重要的,一般来说如果时序没问题但是读不出数据或者经常读取到0,大概率就是 us 延时不够精准问题

初始化(复位脉冲 + 存在脉冲)

在初始化序列期间,总线控制器拉低总线并保持 480us 以发出 (TX) 一个复位脉冲,然后释放总线,进入接收状态 (RX) ,单总线由上拉电阻拉到高电平,当 DS18B20 探测到 I/O 引脚上的上升沿后,等待 15-60us ,然后发出一个由 60-240us 低电平信号构成的存在脉冲

cpp
/*
* @function: OneWire_Reset
* @param: dev -- 设备
* @retval: None
* @brief: Onewire复位
*/
static uint8_t OneWire_Reset(OneWire_Hardware_st *dev)
{
    uint8_t ret = 0;

    Set_SDO(dev,1); // 置高
    Delay_us(50);
    Set_SDO(dev, 0);    // 置低
    Delay_us(480);
    Set_SDO(dev,1); // 置高
    Delay_us(40);
    ret = Get_SDO(dev); // 获取
    Delay_us(480);
    Set_SDO(dev,1); // 置高

    return ret;
}

读时序

主机把总线拉低至少 1us (读时序开始),在总线控制器发出读时序后, DS18B20 通过拉高或拉低总线上来传输 1 或 0,当传输逻辑 0 结束后,总线将被释放,通过上拉电阻回到上升沿状态,从 DS18B20 输出的数据在读时序的下降沿出现后 15us 内有效,必须在 15us 内读取数据,传输完一位后要等待至少65us(因为低电平它持续时间是60us)

cpp
/*
* @function: OneWire_Read
* @param: dev -- 设备  buff -- 存放数据缓存  size -- 数据大小
* @retval: 读取到的数据大小
* @brief: Onewire读多个字节
*/
static uint8_t OneWire_Read(OneWire_Hardware_st *dev, void *buff, int size)
{
    uint8_t i = 0;
    char *p = (char*)buff;

    for (i = 0; i < size; i++)
    {
        p[i++] = OneWire_Read_Byte(dev);
    }

    return i;
}

/*
* @function: OneWire_Read_Byte
* @param: dev -- 设备
* @retval: 读取的数据
* @brief: Onewire读一个字节
*/
static uint8_t OneWire_Read_Byte(OneWire_Hardware_st *dev)
{
    uint8_t data = 0;
    
    for (uint8_t i = 0; i < 8; i++)
    {
        data >>= 1;
        Set_SDO(dev, 0);    // 置低
        Delay_us(5);
        Set_SDO(dev, 1);    // 置高
        Delay_us(5);

        if (Get_SDO(dev))
        {
            data |= 0x80;
        }
        else
        {
            data &= 0x7F;
        }
        Delay_us(65);
        Set_SDO(dev, 1);    // 置高
    }

    return data;
}

写时序

写时序分为写 1 时序和写 0 时序,总线控制器通过写 1 时序写逻辑 1DS18B20,写 0时序写逻辑 0DS18B20,所有写时序必须最少持续 60us,包括两个写周期之间至少 1us 的恢复时间。当总线控制器把数据线从高电平拉到低电平的时候,写时序开始

写1:在写时序开始后的 15us 释放总线。当总线被释放的时候,上拉电阻将拉高总线

写0:写时序开始,把数据线拉到低电平并持续保持 (至少 60us),然后释放总线

cpp
/*
* @function: OneWire_Write
* @param: dev -- 设备  buff -- 待写入数据地址  size -- 数据大小
* @retval: 写入的数据大小
* @brief: Onewire写多个字节
*/
static uint8_t OneWire_Write(OneWire_Hardware_st *dev, void *buff, int size)
{
    uint8_t i = 0;
    char *p = (char*)buff;

    for (i = 0; i < size; i++)
    {
        OneWire_Write_Byte(dev, p[i]);
    }

    return i;
}

/*
* @function: OneWire_Write_Byte
* @param: dev -- 设备 data -- 待写入的字节
* @retval: None
* @brief: Onewire写一个字节
*/
static void OneWire_Write_Byte(OneWire_Hardware_st *dev, uint8_t data)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        Set_SDO(dev, 0);    // 置低
        Delay_us(5);

        if (data & 0x01)
        {
            Set_SDO(dev, 1);    // 置高
        }
        else
        {
            Set_SDO(dev, 0);    // 置低
        }  
        Delay_us(65);
        Set_SDO(dev, 1);    // 置高 
        Delay_us(2);
        data >>= 1;   
    }
}

编程示例1

当单总线上挂载多个 DS18B20 时,MCU必须知道每个 DS18B20 内部固化的 64bit 序列号,才能分别和它们通信

  • 程序编写
OneWire.h
cpp
#ifndef __ONEWIRE_H
#define __ONEWIRE_H
#include "main.h"
#include "dma.h"
#include "spi.h"
#include "gpio.h"
#include "usart.h"
#include "stdio.h"
#include "string.h"
// 注册设备部分
typedef struct
{
    GPIO_TypeDef *onewire_Port; // 端口组
    uint16_t onewire_Pin;   // 引脚
} OneWire_Hardware_st;


typedef struct
{
    void (*OneWire_Registered)(OneWire_Hardware_st *, GPIO_TypeDef*, uint16_t); // Onewire设备注册及初始化
    uint8_t (*OneWire_Reset)(OneWire_Hardware_st *);   // 复位初始化
    uint8_t (*OneWire_Read)(OneWire_Hardware_st *, void *, int);    // Onewire读多个字节
    uint8_t (*OneWire_Write)(OneWire_Hardware_st *, void *, int);   // Onewire写多个字节      
} OneWire_ops_st;


extern OneWire_ops_st OneWire_ops;
#endif
OneWire.c
cpp
/***************************************************************************
 * File: OneWire.c
 * Author: Luckys.
 * Date: 2023/11/01
 * description: 单总线实现
****************************************************************************/
#include "OneWire.h"
#include "DS18B20.h"
#define MaxSensorNum 8
unsigned char DS18B20_ID[MaxSensorNum][8];	// 存检测到的传感器DS18B20_ID的数组,前面的维数代表单根线传感器数量上限
unsigned char DS18B20_SensorNum;	

/* Private function prototypes===============================================*/
static void OneWire_Registered(OneWire_Hardware_st *, GPIO_TypeDef*, uint16_t);
static uint8_t OneWire_Read(OneWire_Hardware_st *, void *, int);
static uint8_t OneWire_Write(OneWire_Hardware_st *, void *, int);
static uint8_t OneWire_Reset(OneWire_Hardware_st *);

static uint8_t OneWire_Read_Byte(OneWire_Hardware_st *);
static void OneWire_Write_Byte(OneWire_Hardware_st *, uint8_t);

static void Set_SDO(OneWire_Hardware_st *, uint8_t);
static uint8_t Get_SDO(OneWire_Hardware_st *);
static void Delay_us(uint32_t);
/* Public variables==========================================================*/
OneWire_ops_st OneWire_ops = 
{
    .OneWire_Registered = &OneWire_Registered,
    .OneWire_Reset = &OneWire_Reset,
    .OneWire_Read = &OneWire_Read,
    .OneWire_Write = &OneWire_Write
};

//**************************************   对外接口层  **************************************

/*
* @function: OneWire_Registered
* @param: dev -- 设备 onewire_port -- 端口组  onewire_pin -- 引脚
* @retval: None
* @brief: Onewire设备注册及初始化
*/
static void OneWire_Registered(OneWire_Hardware_st *dev, GPIO_TypeDef* onewire_port, uint16_t onewire_pin)
{
    // 初始化引脚
    dev->onewire_Port = onewire_port;
    dev->onewire_Pin = onewire_pin;
}

/*
* @function: OneWire_Reset
* @param: dev -- 设备
* @retval: None
* @brief: Onewire复位
*/
static uint8_t OneWire_Reset(OneWire_Hardware_st *dev)
{
    uint8_t ret = 0;

    Set_SDO(dev,1); // 置高
    Delay_us(50);
    Set_SDO(dev, 0);    // 置低
    Delay_us(480);
    Set_SDO(dev,1); // 置高
    Delay_us(40);
    ret = Get_SDO(dev); // 获取
    Delay_us(480);
    Set_SDO(dev,1); // 置高

    return ret;
}

/*
* @function: OneWire_Read
* @param: dev -- 设备  buff -- 存放数据缓存  size -- 数据大小
* @retval: 读取到的数据大小
* @brief: Onewire读多个字节
*/
static uint8_t OneWire_Read(OneWire_Hardware_st *dev, void *buff, int size)
{
    uint8_t i = 0;
    char *p = (char*)buff;

    for (i = 0; i < size; i++)
    {
        p[i++] = OneWire_Read_Byte(dev);
    }

    return i;
}

/*
* @function: OneWire_Write
* @param: dev -- 设备  buff -- 待写入数据地址  size -- 数据大小
* @retval: 写入的数据大小
* @brief: Onewire写多个字节
*/
static uint8_t OneWire_Write(OneWire_Hardware_st *dev, void *buff, int size)
{
    uint8_t i = 0;
    char *p = (char*)buff;

    for (i = 0; i < size; i++)
    {
        OneWire_Write_Byte(dev, p[i]);
    }

    return i;
}

//**************************************   应用层  **************************************

/*
* @function: OneWire_Read_Byte
* @param: dev -- 设备
* @retval: 读取的数据
* @brief: Onewire读一个字节
*/
static uint8_t OneWire_Read_Byte(OneWire_Hardware_st *dev)
{
    uint8_t data = 0;
    
    for (uint8_t i = 0; i < 8; i++)
    {
        data >>= 1;
        Set_SDO(dev, 0);    // 置低
        Delay_us(5);
        Set_SDO(dev, 1);    // 置高
        Delay_us(5);

        if (Get_SDO(dev))
        {
            data |= 0x80;
        }
        else
        {
            data &= 0x7F;
        }
        Delay_us(65);
        Set_SDO(dev, 1);    // 置高
    }

    return data;
}

/*
* @function: OneWire_Write_Byte
* @param: dev -- 设备 data -- 待写入的字节
* @retval: None
* @brief: Onewire写一个字节
*/
static void OneWire_Write_Byte(OneWire_Hardware_st *dev, uint8_t data)
{
    for (uint8_t i = 0; i < 8; i++)
    {
        Set_SDO(dev, 0);    // 置低
        Delay_us(5);

        if (data & 0x01)
        {
            Set_SDO(dev, 1);    // 置高
        }
        else
        {
            Set_SDO(dev, 0);    // 置低
        }  
        Delay_us(65);
        Set_SDO(dev, 1);    // 置高 
        Delay_us(2);
        data >>= 1;   
    }
}

//**************************************   抽象层  **************************************

/*
* @function: Set_SDO
* @param: dev -- 设备 state -- 状态 
* @retval: None
* @brief: IO输出高低电平
*/
static void Set_SDO(OneWire_Hardware_st *dev, uint8_t state)
{
    if (state)
    {
        HAL_GPIO_WritePin(dev->onewire_Port, dev->onewire_Pin, GPIO_PIN_SET);
    }
    else
    {
        HAL_GPIO_WritePin(dev->onewire_Port, dev->onewire_Pin, GPIO_PIN_RESET);
    }
}

/*
* @function: Get_SDO
* @param: dev -- 设备
* @retval: 状态(0/1)
* @brief: IO获取状态
*/
static uint8_t Get_SDO(OneWire_Hardware_st *dev)
{
    return HAL_GPIO_ReadPin(dev->onewire_Port, dev->onewire_Pin);
}

/*
* @function: Delay_us
* @param: None
* @retval: None
* @brief: us延时
*/
static void Delay_us(uint32_t us)
{
    uint8_t i;
    // 通过示波器测量进行校准
    while (us--)
    {
        for (i = 0; i < 16; i++)
		{
			;
		}
    }
}
DS18B20.h
cpp
#ifndef __DS18B20_H
#define __DS18B20_H
#include "main.h"
#include "dma.h"
#include "spi.h"
#include "gpio.h"
#include "OneWire.h"

typedef struct
{
    void (*DS18B20_Init)(void); // DS18B20初始化
    void (*DS18B20_Start)(OneWire_Hardware_st *);   // DS18B20启动
    float (*DS18B20_Read_Temp)(OneWire_Hardware_st *);  // DS18B20读取温度
    void (*DS18B20_Read_Rom)(OneWire_Hardware_st *, char *);    // DS18B20读取唯一序列号
} DS18B20_st;


extern DS18B20_st DS18B20;
extern OneWire_Hardware_st Onewire_dev1;

#endif
DS18B20.c
cpp
/***************************************************************************
 * File: DS18B20.c
 * Author: Luckys.
 * Date: 2023/11/01
 * description: 
****************************************************************************/
#include "DS18B20.h"

/* Private function prototypes===============================================*/
static void DS18B20_Init(void);
static void DS18B20_Start(OneWire_Hardware_st *);
static float DS18B20_Read_Temp(OneWire_Hardware_st *);
static void DS18B20_Read_Rom(OneWire_Hardware_st *, char *);
/* Public variables==========================================================*/
OneWire_Hardware_st Onewire_dev1;

DS18B20_st DS18B20 = 
{
    .DS18B20_Init = &DS18B20_Init,
    .DS18B20_Start = &DS18B20_Start,
    .DS18B20_Read_Temp = &DS18B20_Read_Temp,
    .DS18B20_Read_Rom = &DS18B20_Read_Rom
};

/*
* @function: DS18B20_Init
* @param: None
* @retval: None
* @brief: DS18B20初始化
*/
static void DS18B20_Init(void)
{
    OneWire_ops.OneWire_Registered(&Onewire_dev1, DS18B20_GPIO_Port, DS18B20_Pin);  // 注册
}

/*
* @function: DS18B20_Start
* @param: None
* @retval: None
* @brief: DS18B20启动
*/
static void DS18B20_Start(OneWire_Hardware_st *dev)
{
    uint8_t reg;

    if (OneWire_ops.OneWire_Reset(dev) != 0)
	{
		return;
	}
    reg = 0xCC; // 跳过ID号指令
    OneWire_ops.OneWire_Write(dev, &reg, 1);
    reg = 0x44; // 温度转换指令
    OneWire_ops.OneWire_Write(dev, &reg, 1);
}

/*
* @function: DS18B20_Read_Temp
* @param: None
* @retval: 温度值
* @brief: DS18B20读取温度
*/
static float DS18B20_Read_Temp(OneWire_Hardware_st *dev)
{
    uint8_t TL,TH,sign;
    uint16_t reg_temp;
    uint8_t reg;
    float temp;

    DS18B20_Start(dev);
    if (OneWire_ops.OneWire_Reset(dev) != 0)
	{
		return 0;
	}
    reg = 0xCC; // 跳过ID号指令
    OneWire_ops.OneWire_Write(dev, &reg, 1);    
    reg = 0xBE; // 读取指令
    OneWire_ops.OneWire_Write(dev, &reg, 1); 
    OneWire_ops.OneWire_Read(dev, &TL, 1);  // 低8位数据
    OneWire_ops.OneWire_Read(dev, &TH, 1);  // 高8位数据

    if (TH > 7) // 负数
    {
        TH = ~TH;
        TL = ~TL + 1;
        sign = 0;
    }
    else    // 正数
    {
        sign = 1;
    }
    reg_temp = (TH << 8) | TL;
    temp = reg_temp * 0.0625f;

    if (sign)
    {
        return temp;
    }
    else
    {
        return -temp;
    }
}

/*
* @function: DS18B20_Read_Rom
* @param: None
* @retval: None
* @brief: DS18B20读取唯一序列号
*/
static void DS18B20_Read_Rom(OneWire_Hardware_st *dev, char *rom)
{
    uint8_t reg;

    OneWire_ops.OneWire_Reset(dev);
    reg = 0x33;
    OneWire_ops.OneWire_Write(dev, &reg, 1);

    for (uint8_t i = 0; i < 8; i++)
    {
        OneWire_ops.OneWire_Read(dev, &rom[i], 1);
    }
}
  • 实验现象

正常读取单个DS18B20

待解决

单总线挂载多个DS18B20实验还是不太行,无法正确读出ID — 2023.11.2