通信协议学习-单总线
前言
参考文章/博主
单总线
参考:
详解
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 低电平信号构成的存在脉冲
/*
* @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)
/*
* @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 时序写逻辑 1 到 DS18B20,写 0时序写逻辑 0 到 DS18B20,所有写时序必须最少持续 60us,包括两个写周期之间至少 1us 的恢复时间。当总线控制器把数据线从高电平拉到低电平的时候,写时序开始
写1:在写时序开始后的 15us 释放总线。当总线被释放的时候,上拉电阻将拉高总线
写0:写时序开始,把数据线拉到低电平并持续保持 (至少 60us),然后释放总线
/*
* @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
#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;
#endifOneWire.c
/***************************************************************************
* 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
#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;
#endifDS18B20.c
/***************************************************************************
* 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, ®, 1);
reg = 0x44; // 温度转换指令
OneWire_ops.OneWire_Write(dev, ®, 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, ®, 1);
reg = 0xBE; // 读取指令
OneWire_ops.OneWire_Write(dev, ®, 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, ®, 1);
for (uint8_t i = 0; i < 8; i++)
{
OneWire_ops.OneWire_Read(dev, &rom[i], 1);
}
}- 实验现象
正常读取单个DS18B20
待解决
单总线挂载多个DS18B20实验还是不太行,无法正确读出ID — 2023.11.2




