单总线one-Wire
概述
One-Wire总线是
DALLAS
公司研制开发的一种协议
特点:
它是由一个总线主节点,一个或多个从节点组成系统,通过一根信号线对从芯片进行数据的读取
每一个符合One-Wire协议的从芯片都有一个唯一的地址,包括48位的序列号8位的家族代码和8位的CRC代码。主芯片对各个从芯片的寻址依据这64位的不同来进行。
One-Wire总线利用一根线实现双向通信(异步半双工)
。因此其协议对时序的要求较严格,如应答等时序都有明确的时间要求。
工作原理
由一根数据线,系统中的数据交换,控制都由这跟线完成。
单总线通常要求外接一个约为4.7k的上拉电阻
,这样,当总线闲置时,其状态为高电平1
由于它们是主从结构,只有主机呼叫从机时,从机才能应答,因此主机访问One-Wire器件都必须严格遵循单总线命令序列,即初始化、ROM命令、功能命令。
如果出现序列混乱,One-Wire器件将不响应主机(搜索ROM命令、报警搜索命令除外)。
信号方式
复位和应答
- 将DQ主线拉低持续500us DQ=0
- 释放DQ主线的控制权 DQ=1
- 持续20us判断DQ状态
– 1.250us没有处于高电平 复位异常
– 2.250us没有处于低电平 复位异常
one-Wire读写原理图
代码实现
#include "one_wire.h"
#include "intrins.h"//内核用来进行低延时的头文件_nop_();
static u8 _ReadBit(void);//定义一个个读取字节的函数
void OneWire_Init(void)//初始化 因为有一个上拉电阻所以默认是高电平初步初始化无所谓
{
DQ = 1;
}
u8 OneWire_Reset(void)//返回0:成功,返回1:失败
{
u8 u8timeout = 0;
//主机低位复位
DQ = 0;
delay_10us(50);//主机发送复位信号500us
DQ = 1; //主机释放DQ线,等待从机回应
delay_10us(2);//主机等待20us后读取DQ状态
//两个250us处于高低电平来判断是否复位成功
//主机判断DQ是否被从机拉低250us
while(DQ)
{
u8timeout++;
if(u8timeout>25)return 1;//复位异常
delay_10us(1);
}
//主机判断从机有没有释放DQ线
u8timeout = 0;
while(!DQ)
{
u8timeout++;
if(u8timeout>25)return 1;//复位异常
delay_10us(1);
}
return 0;//复位成功
}
//低位先读
void OneWire_WriteByte(u8 u8data)//LSB
{
u8 i = 0;
u8 u8temp = 0;//当前读取的字节
for(i=0;i<8;i++)
{
u8temp = u8data & 0x01;//取低位
u8data>>=1;//读取低位
if(u8temp)//写1当前位写1的话 拉低电平持续2us,在拉高电平持续60us
{
DQ = 0;
_nop_();_nop_();
DQ = 1;
delay_10us(6);
}
else//写0当前位写0的话 拉低电平持续60us,在拉高电平持续2us
{
DQ = 0;
delay_10us(6);
DQ = 1;
_nop_();_nop_();
}
}
}
//读取字节
u8 OneWire_ReadByte(void)//LSB
{
u8 i;
u8 u8val = 0,u8temp = 0;
for(i=0;i<8;i++)
{
//因为是一个个字节读取
u8temp = _ReadBit(); //每次读取一个bit
u8val += (u8temp<<i);//总数据
}
return u8val;
}
//单个比特读取
static u8 _ReadBit(void)
{
u8 u8val = 0;
DQ = 0;
_nop_();_nop_();
DQ = 1;
_nop_();_nop_();
if(DQ)
{
u8val = 1;
}
else
{
u8val = 0;
}
delay_10us(6);
return u8val;
}
ROM命令
在主机检测到应答脉冲后,就可以发出 ROM 命令。这些命令与各个从机设备的唯一64位ROM代码相关,允许主机在单总线上连接多个从机设备时,指定操作某个从机设备。
常走是一对1也就是一号线
搜索ROM[F0h]
当系统初始上电时,主机必须找出总线上所有从机设备的ROM代码,这样主机就能够判断出从机的数目和类型。主机通过重复执行搜索ROM 循环(搜索ROM命令跟随着位数据交换),以找出总线上所有的从机设备。如果总线只有一个从机设备则可以采用读ROM命令来替代搜索ROM命令。在每次执行完搜索ROM循环后,主机必须返回至命令序列的第一步(初始化)
读ROM[33h](仅适合于单节点)
该命令仅适用于总线上只有一个从机设备。它允许主机直接读出从机的64位ROM 代码,而无须执行搜索ROM过程。
如果该命令用于多节点系统,则必然发生数据冲突,因为每个从机设备都会响应该命令。
匹配ROM[55h]
匹配ROM命令跟随64位ROM 码,从而允许主机访问多节点系统中某个指定的从机设备。仅当从机完全匹配64位ROM代码时,才会响应主机随后发出的功能命令;其它设备将处于等待复位脉冲状态。
跳越ROM[CCh] (仅适合于单节点)
主机能够采用该命令同时访问总线上的所有从机设备,而无须发出任何ROM代码信息。例如,主机通过在发出跳越ROM命令后跟随转换温度命令[44h],就可以同时命令总线上所有的DS18B20 开始转换温度,这样大大节省了主机的时间。值得注意,如果跳越ROM命令跟随的是读暂存器[BEh]的命令(包括其它读操作命令),则该命令只能应用于单节点系统,否则将由于多个节点都响应该命令而引起数据冲突。
报警搜索[ECh](仅少数1-wire 器件支持)
除那些设置了报警标志的从机响应外,该命令的工作方式完全等同于搜索ROM命令。该命令允许主机设备判断那些从机设备发生了报警(如最近的测量温度过高或过低等)。同搜索ROM命令
DS18B20功能命令
DS18B20 是由 DALLAS 半导体公司推出的一种的“一线总线(单总线)”接口的温度传感器。与传统的热敏电阻等测温元件相比,它是一种新型的体积小、 适用电压宽、与微处理器接口简单的数字化温度传感器
命令 | 描述 | 命令代码 | 响应消息 | 注释 |
---|---|---|---|---|
转换温度 | 启动温度转换 | 0x44 | 无 | 1 |
读暂存器 | 读全部的暂存器内容,包括CRC字节 | 0xBE | DS18B20传输至多9个字节 | 2 |
写暂存器 | 写暂存器第2、3和4个字节的数据(即TH、TL和配置寄存器) | 0x4E | 主机传输3个字节数据 | 3 |
复制暂存器 | 将暂存器中的TH、TL和配置字节复制到EEPROM中 | 0x48 | 无 | 1 |
回读EEPROM | 将TH、TL和配置字节从EEPROM回读至暂存器中 | 0xB8 | DS18B20传送回读状态至主机 | |
读取供电方式 | 0xB4 | 1bit:0 = 寄生电源,1 = 提供外部电源 |
注释:
- 在温度转换和复制暂存器数据至EEPROM期间,主机必须在单总线上允许强上拉。并且在此期间,总线上不能进行其它数据传输;
- 通过发出复位脉冲,主机能够在任何时候中断数据传输;
- 在复位脉冲发出前,必须写入全部的三个字节。
DS18B20特点
- 适应电压范围更宽,电压范围3-5.5v
- 独特的单线接口方式
- DS18B20支持多点组网功能
- DS18B20在使用中不需要任何外围元件
- 温范围 -55℃ ~ 125℃,在10~85时的精度为±0.5
- 可编程的分辨率在9~12位,
- 在9位分辨率时最多在93.75ms内把温度转换为数字,12位分辨率时最多在750ms内把温度值转换成数字,速度更快
- 负压特性,不会因为发热而烧毁,只是不能正常工作
DS18B20的引脚
只需要 1 个数据传输引脚 (DQ) 。是 1-Wire 总线模式。
单总线模式没有时钟信号是怎么传输数据的呢????
单总线模式,即可传输时钟又可传输数据!
从 DS18B20 外观图可以看到,当我们正对传感器切面(传感器型号字符那 一面)时,传感器的管脚顺序是从左到右排列。管脚 1 为 GND,管脚 2 为数据 DQ,管脚 3 为 VDD。
内部结构
ROM
DS18B20温度传感器的内部存储器包括一个高速的暂存器RAM和一个非易失性的可电擦除的EEPROM,后者存放高温度和低温度触发器TH,TL和配置寄存器
可电擦除的EEPROM
配置寄存器是配置不同的位数来确定温度和数字的转化,配置寄存器结构如下
低五位一直都是“1”,TM是测试模式位,用于设置DS18B20在工作模式还是在测试模式,R1和R0是用来设置DS18B20的精度,可设置为9,10,11,12位,对应分辨率温度是0.5,0.25,0.125,0.0625℃
在初始状态下默认的精度是 12 位,即 R0=1、R1=1。
高速的暂存器RAM
发出温度转换命令44H发布后,经转换所得的温度值以二字节补码形式存放在高速暂存存储器的第0和第一字节
温度为正数时: 如果测得的温度大于 0,这 5 位为‘ 0’,只要将测到的数值乘以 0.0625 (默认精度是 12 位)即可得到实际温度;
温度为负数时:如果温度小于 0,这 5 位为‘ 1’, 测到的数值需要取反加 1 再乘以 0.0625 即可得到实际温度。
DS18B20 时序包括如下几种:初始化时序、写(0 和 1)时序、 读(0 和 1)时序。 DS18B20 发送所有的命令和数据都是字节的低位在前(LSB)。
初始化时序
单总线上的所有通信都是以初始化序列开始。
a. 主机输出低电平,保持低电平时间至少480us(该时间的时间范围可以从480到960微妙),以产生复位脉冲。
b. 接着主机释放总线,外部的上拉电阻将单总线拉高,延时15~60us,并进入接收模式。
c. 接着 DS18B20拉低总线60~240us,以产生低电平应答脉冲,若为低电平,还要做延时,其延时的时间从外部上拉电阻将单总线拉高算起最少要 480 微妙
写时序
写时序包括写 0 时序和写 1 时序。
所有写时序至少需要 60us,且在 2 次 独立的写时序之间至少需要 1us 的恢复时间,两种写时序均起始于主机拉低总线。
写 0 时序:主机输出低电平,延时 60us,然后释放总线,延时 2us。
写 1 时序:主机输出低电平,延时 2us,然后释放总线,延时 60us。
读时序
单总线器件仅在主机发出读时序时,才向主机传输数据,所以,在主机发出读数据命令后,必须马上产生读时序,以便从机能够传输数据。所有读时序至少需要 60us,且在 2 次独立的读时序之间至少需要 1us 的恢复时间。
每个读时序都由主机发起,至少拉低总线1us。主机在读时序期间必须释放总线,并且在时序起始后的15us 之内采样总线状态。
读 0 时序:主机输出低电平,延时 1us,然后释放总线,在15us内读取DQ的状态,18B20这时输出是0
读 1 时序:主机输出低电平,延时 至少1us,然后释放总线,在15us内读取DQ的状态,18B20这时输出是1
典型的温度读取过程为:
主机输出低电平延时 2us,然后主机转入输入模式延 时12us,然后读取单总线当前的电平,然后延时 50us。
在了解了单总线时序之后,我们来看看 DS18B20 的典型温度读取过程, DS18B20 的典型温度读取过程为:
复位→发 SKIP ROM 命令(0XCC)→发开始转换命令(0X44)→延时→复位→发送 SKIP ROM 命令(0XCC)→发读存储器命令 (0XBE)→连续读出两个字节数据(即温度)→结束。
代码实现
实现的功能是:插上 DS18B20 温度传感器,数码管显示检测的温度值。
程序框架如下:
(1)编写数码管显示功能
(2)编写 DS18B20 读取温度功能
(3)编写主函数
//one-wire.h
#ifndef __ONE_WIRE_H
#define __ONE_WIRE_H
#include "public.h"
sbit DQ = P3^7; //定义单总线IO
void OneWire_Init(void);
u8 OneWire_Reset(void); //返回0:成功,返回1:失败
void OneWire_WriteByte(u8 u8data);
u8 OneWire_ReadByte(void);
#endif
//wire.c
//功能:将DS18B20监测到的温度显示在数码管上,数值精确到小数点后一位。
#include "one_wire.h"
#include "intrins.h"
static u8 _ReadBit(void);
void OneWire_Init(void)
{
DQ = 1;
}
u8 OneWire_Reset(void)//返回0:成功,返回1:失败
{
u8 u8timeout = 0;
//主机低位复位
DQ = 0;
delay_10us(50);//主机发送复位信号500us
DQ = 1; //主机释放DQ线,等待从机回应
delay_10us(2);//主机等待20us后读取DQ状态
//主机判断DQ是否被从机拉低250us
while(DQ)
{
u8timeout++;
if(u8timeout>25)return 1;//复位异常
delay_10us(1);
}
//主机判断从机有没有释放DQ线
u8timeout = 0;
while(!DQ)
{
u8timeout++;
if(u8timeout>25)return 1;//复位异常
delay_10us(1);
}
return 0;//复位成功
}
void OneWire_WriteByte(u8 u8data)//LSB
{
u8 i = 0;
u8 u8temp = 0;
for(i=0;i<8;i++)
{
u8temp = u8data & 0x01;
u8data>>=1;
if(u8temp)//写1
{
DQ = 0;
_nop_();_nop_();
DQ = 1;
delay_10us(6);
}
else//写0
{
DQ = 0;
delay_10us(6);
DQ = 1;
_nop_();_nop_();
}
}
}
u8 OneWire_ReadByte(void)//LSB
{
u8 i;
u8 u8val = 0,u8temp = 0;
for(i=0;i<8;i++)
{
u8temp = _ReadBit(); //每次读取一个bit
u8val += (u8temp<<i);
}
return u8val;
}
static u8 _ReadBit(void)
{
u8 u8val = 0;
DQ = 0;
_nop_();_nop_();
DQ = 1;
_nop_();_nop_();
if(DQ)
{
u8val = 1;
}
else
{
u8val = 0;
}
delay_10us(6);
return u8val;
}
//ds18b20驱动
#ifndef __DS18B20_H
#define __DS18B20_H
#include "public.h"
#include "one_wire.h"
void DS18B20_Init(void);
u16 DS18B20_GetTemperture(void);
#endif
//ds18b20.c
#include "ds18b20.h"
void DS18B20_Init(void)
{
//todo
OneWire_Init();
}
u16 DS18B20_GetTemperture(void)
{
u16 u16val = 0;
u8 u8DL = 0,u8DH = 0;
if(OneWire_Reset()!=0)return u16val;
OneWire_WriteByte(0xCC);//SKIP ROM命令
OneWire_WriteByte(0x44);//发送温度转换命令
delay_10us(1);
if(OneWire_Reset()!=0)return u16val;
OneWire_WriteByte(0xCC);//SKIP ROM命令
OneWire_WriteByte(0xBE);//读取存储器命令
//连续读出两个字节数据
u8DL = OneWire_ReadByte();
u8DH = OneWire_ReadByte();
u16val = (u16)((u8DH<<8) | u8DL);
return u16val;
}
//main.c
/*
功能要求:
将DS18B20监测的温度实时显示在数码管上,数值精确到小数点后一位。
*/
#include "public.h"
#include "smg.h"
#include "ds18b20.h"
#include "uart.h"
void main(void)
{
u8 u8code[8] = {0};
u8 i = 0;//计时器
u16 u16data = 0;
float tempter = 0.0;
SMG_Init();
UART_Init();
DS18B20_Init();
while(1)
{
//温度模块
i++;
if(i == 100)//每秒读一次温度
{
i = 0;
u16data = DS18B20_GetTemperture();
if(u16data & 0xf800)//零下温度
{
u8code[0] = 0x40;//温度符号
u16data = (~u16data) +1;
}
else//零上温度
{
u8code[0] = 0;
}
tempter = u16data * 0.0625;//DS18B20温度计算
tempter*=10;//把温度放大十倍
u16data = (u16)tempter;
}
//数码管模块
u8code[1] = smg_code[u16data/100]; //温度十位
u8code[2] = smg_code[u16data/10%10];//温度个位
u8code[2] |= 0x80;//小数点
u8code[3] = smg_code[u16data%10]; //温度的小数位
u8code[4] = 0x39;//C
SMG_Display(u8code);
}
}
void Uart_Isp(void) interrupt 4
{
u8 u8RecData = 0;
if(RI) //检测串口接收完成中断
{
UART_RecvData();
RI = 0; //用户清除接收数据完成标志
}
}
void Int0(void) interrupt 0
{
}
void Int1(void) interrupt 2
{
}
void Time0(void) interrupt 1
{
}
void Time1(void) interrupt 3
{
}