目录
1 温度传感器DS18B20的工作原理
DS18B20 可编程分辨率的单总线数字温度计
特征:
- 独特的单线接口仅需一个端口引脚进行通讯
- 每个器件有唯一的 64 位的序列号存储在内部存储器中
- 简单的多点分布式测温应用
- 无需外部器件
- 可通过数据线供电。供电范围为 3.0V到 5.5V。
- 测温范围为-55~+125℃(-67~+257℉)
- 在-10~+85℃范围内精确度为±5℃
- 温度计分辨率可以被使用者选择为9~12 位
- 最多在 750ms 内将温度转换为 12 位数字
- 用户可定义的非易失性温度报警设置
- 报警搜索命令识别并标志超过程序限定温度(温度报警条件)的器件
- 与 DS1822 兼容的软件
- 应用包括温度控制、工业系统、消费品、温度计或任何热感测系统
说明:
DS18B20 数字温度计提供 9-12 位摄氏温度测量而且有一个由高低电平触发的可编程的不因电源消失而改变的报警功能。DS18B20 通过一个单线接口发送或接受信息,因此在中央处理器和 DS18B20 之间仅需一条连接线(加上地线)。它的测温范围为-55~+125℃,并且在-10~+85℃精度为±5℃。除此之外,DS18B20能直接从单线通讯线上汲取能量,除去了对外部电源的需求。
每个 DS18B20 都有一个独特的 64 位序列号,从而允许多只 DS18B20 同时连在一根单线总线上;因此,很简单就可以用一个微控制器去控制很多覆盖在一大片区域的 DS18B20。这一特性在 HVAC 环境控制、探测建筑物、仪器或机器的温度以及过程监测和控制等方面非常有用。
引脚说明:
- GND -地
- DQ -数据I/O
- VDD -可选电源电压
- NC -无连接
板载DS18B20位置 位号U5
2 一线通信接口的使用(单总线)
通信线只有一根,且可以挂载多个DS18B20
2.1 单总线系统
单总线系统包括一个总线控制器和一个或多个从机。DS18B20 总是充当从机。当只有一只从机挂在总线上时,系统被称为“单点”系统;如果由多只从机挂在总线上,系统被称为“多点”。
所有的数据和指令的传递都是从最低有效位开始通过单总线。
2.2 执行序列
通过单线总线端口访问 DS18B20 的协议如下:
- 步骤1. 初始化
- 步骤2. ROM 操作指令
- 步骤3. DS18B20 功能指令
具体操作下面见代码
每一次 DS18B20 的操作都必须满足以上步骤,若是缺少步骤或是顺序混乱,器件将不会返回值。
例如这样的顺序:发起 ROM 搜索指令[F0h]和报警搜索指令[ECh]之后,总线控制器必须返回步骤 1。
2.3 单总线信号
DS18B20 需要严格的单总线协议以确保数据的完整性。协议包括集中单总线信号类型:复位脉冲、存在脉冲、写 0、写 1、读 0 和读 1。所有这些信号,除存在脉冲外,都是由总线控制器发出的。
3 DS18B20寄存器的配置
配置寄存器:
存储器的第 4 位为配置寄存器,其组织见图 8。用户可以通过按表 3 所示设置 R0和 R1 位来设定 DS18B20 的精度。上电默认设置:R0=1,R1=1(12 位精度)。注意:精度和转换时间之间有直接的关系。暂存器的位 7 和位 0-4 被器件保留,禁止写入;在读回数据时,它们全部表现为逻辑 1。
温度寄存器格式
4 根据传感器使用说明书时序图编写驱动程序
给DS18B20新建.c.h文件
本开发板DS18B20的数据引脚接到了P14
sbit OneWire_DQ = P1^4;
DS18B20 需要严格的单总线协议以确保数据的完整性。
必要时可以在操作前关闭中断,操作结束后再开启中断!!!
4.1 根据时序图写代码
4.1.1 初始化
复位序列:复位和存在脉冲
和 DS18B20 间的任何通讯都需要以初始化序列开始,初始化序列见下图13。一个复位脉冲跟着一个存在脉冲表明 DS18B20 已经准备好发送和接收数据。
在初始化序列期间,总线控制器拉低总线并保持 480us 以发出(TX)一个复位脉冲,然后释放总线,进入接收状态(RX)。单总线由 5K 上拉电阻拉到高电平。当DS18B20 探测到 I/O 引脚上的上升沿后,等待 15-60us,然后发出一个由 60-240us低电平信号构成的存在脉冲。
单总线初始化程序
void Delay480us() //@11.0592MHz
{
unsigned char i, j;
i = 6; j = 38;
do
{
while (--j);
} while (--i);
}
/*初始化
主机 将总线拉低至少480us,然后释放总线,
等待15~60us后,存在的 从机 会拉低总线60~240us以响应 主机
之后 从机 将释放总线
*/
//单总线初始化时序
void OneWire_Init(void)
{
unsigned char ackbit;
unsigned char i;
OneWire_DQ = 1;
OneWire_DQ = 0; Delay480us(); //总线拉低480us
OneWire_DQ = 1; //释放总线
i = 138; while (--i); //延时等待50us
ackbit = OneWire_DQ; Delay480us(); //应答位:读取一下是否为0,并且延时等待480us
}
最后的ackbit可以返回出去,验证初始化是否成功,这里我验证的没有成功,但是不影响后续程序
延时50us的程序
只需要赋值程序主题部分即可,上面用到的是这样的,差不多是50us
i = 135; while (--i);
4.1.2 读写时序
读/写时序
DS18B20 的数据读写是通过时序处理位来确认信息交换的
写时序
由两种写时序:写 1 时序和写 0 时序。总线控制器通过写 1 时序写逻辑 1 到DS18B20,写 0 时序写逻辑 0 到 DS18B20。所有写时序必须最少持续 60us,包括两个写周期之间至少 1us 的恢复时间。当总线控制器把数据线从逻辑高电平拉到低电平的时候,写时序开始(下图)
总线控制器要生产一个写时序,必须把数据线拉到低电平然后释放,在写时序开始后的 15us 释放总线。当总线被释放的时候,5K 的上拉电阻将拉高总线。总控制器要生成一个写 0 时序,必须把数据线拉到低电平并持续保持(至少 60us)。
总线控制器初始化写时序后,DS18B20 在一个 15us 到 60us 的窗口内对 I/O 线采样。如果线上是高电平,就是写 1。如果线上是低电平,就是写 0。
发送(写)一位
发送一位,即发送0或1,非0即1,下面有用到!
发送0和1时序的区别是在15us之后电平不同,我们可以根据这个不同点写这一段程序
先把总线拉低10us,然后把要发送的数据放在总线上,发0即保持低电平,发1即变成高电平,然后再延时50us满足时序长度,最终释放总线
/*发送一位
主机将总线拉低60~120us,然后释放总线,表示发送0;
主机将总线拉低1~15us,然后释放总线,表示发送1。
从机 将在总线拉低30us后读取电平,整个时间片应大于60us
*/
//发送一位数据,0或1
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
OneWire_DQ = 1;
OneWire_DQ = 0; i = 28; while (--i); //总线拉低10us
OneWire_DQ = Bit; i = 138; while (--i); //将Bit送回总线上再延时50us,检测Bit看变化赋值,比较巧妙的思想
OneWire_DQ = 1; //最终释放总线
}
发送一个字节
从低位到高位开始发送,只需要发送8次即可
发送的内容是 Byte & (0x01<<i) i每次都++
这样就会把数据由低位到高位发送出去,在草稿纸上写一写就明白了
//发送一字节数据,从低位到高位依次发送
void OneWire_SendByte(unsigned char Byte)
{
unsigned char i;
for (i = 0; i < 8; i ++)
OneWire_SendBit(Byte & (0x01<<i));
}
举个例子,假如要发送0x05
0x05 = 0000 0101
第一次循环 0000 0101 & 0000 0001 = 0000 0001 ≠ 0,发送1
第二次循环 0000 0101 & 0000 0010 = 0000 0000 = 0,发送0
第三次循环 0000 0101 & 0000 0100 = 0000 0100 ≠ 0,发送1
第四次循环 0000 0101 & 0000 1000 = 0000 0000 = 0,发送0
第五次循环 0000 0101 & 0001 0000 = 0000 0000 = 0,发送0
第六次循环 0000 0101 & 0010 0000 = 0000 0000 = 0,发送0
第七次循环 0000 0101 & 0100 0000 = 0000 0000 = 0,发送0
第八次循环 0000 0101 & 1000 0000 = 0000 0000 = 0,发送0
可以看到从数据由低位到高位依次发送出去了
读时序
总线控制器发起读时序时,DS18B20 仅被用来传输数据给控制器。因此,总线控制器在发出读暂存器指令[BEh]或读电源模式指令[B4H]后必须立刻开始读时序,DS18B20可以提供请求信息。除此之外,总线控制器在发出发送温度转换指令[44h]或召回 EEPROM 指令[B8h]之后读时序,详见 DS18B20 功能指令节。
所有读时序必须最少 60us,包括两个读周期间至少 1us 的恢复时间。当总线控制器把数据线从高电平拉到低电平时,读时序开始,数据线必须至少保持 1us,然后总线被释放(见图14)。在总线控制器发出读时序后,DS18B20 通过拉高或拉低总线上来传输 1 或 0。当传输逻辑 0 结束后,总线将被释放,通过上拉电阻回到上升沿状态。从 DS18B20 输出的数据在读时序的下降沿出现后 15us 内有效。因此,总线控制器在读时序开始后必须停止把 I/O 脚驱动为低电平 15us,以读取I/O 脚状态。
图 15 标识 TINIT,TRC 和 TSAMPLE 之和必须小于 15us。图 16 指出,系统时间可以用下面办法达到最大:TINIT 和 TRC 保持时间尽可能校;把控制器采样时间放到 15us 周期的最后。
接收(读)一位
主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us末尾) 读取为低电平则为接收0,读取为高电平则为接收1,整个时间片应大于60us
/*接收一位
主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us末尾)
读取为低电平则为接收0,读取为高电平则为接收1,整个时间片应大于60us
*/
//接收一位数据 0或1
unsigned char OneWire_ReceiveBit(void)
{
unsigned char i;
unsigned char Bit;
OneWire_DQ = 0; i = 14; while (--i); //先把总线拉低5us
OneWire_DQ = 1; i = 14; while (--i); //释放总线5us
Bit = OneWire_DQ; //此时读取电平,数据0会把电平拉低,数据1仍然置高
i = 138; while (--i); //延时50us,满足时序要求
return Bit;
}
接收一个字节
//接收一字节数据,从低位到高位依次接收
unsigned char OneWire_ReceiveByte(void)
{
unsigned Byte = 0x00;
unsigned char i;
for (i = 0; i < 8; i ++)
{
//这个if判断,判断为0的时候,Byte当前位还是0,但是变量i正常++
if( OneWire_ReceiveBit() ) //只有接收1才会执行下面的语句
Byte |= 0x01<<i; //从低位到高位依次取出
}
return Byte;
}
举个例子,假如接收的数据是0xF0
0xF0 = 1111 0000
前四此循环,发送的数据都是0,不进入if判断,直接跳出,但是i++了,所以Byte变量后四位都是0
第五次循环 1111 0000 | 0001 0000 = 0001 0000 = 1, 发送1
第六次循环 1111 0000 | 0010 0000 = 0010 0000 = 1, 发送1
第七次循环 1111 0000 | 0100 0000 = 0100 0000 = 1, 发送1
第八次循环 1111 0000 | 1000 0000 = 1000 0000 = 1, 发送1
所以Byte变量 = 1111 0000 = 0xF0
可以看到从数据由低位到高位依次接收,验证正确
4.1.3 单总线时序代码封装
将STC15单片机 单总线底层代码封装成头文件,供给DS18B20调用
建 OneWire.c 和 OneWire.h
将4.1时序图代码整合成OneWire.c,直接复制即可
#include "OneWire.h"
#include "intrins.h"
sbit OneWire_DQ = P1^4;
void Delay480us() //@11.0592MHz
{
unsigned char i, j;
i = 6; j = 38;
do
{
while (--j);
} while (--i);
}
/*初始化
主机 将总线拉低至少480us,然后释放总线,
等待15~60us后,存在的 从机 会拉低总线60~240us以响应 主机
之后 从机 将释放总线
*/
//单总线初始化时序
void OneWire_Init(void)
{
unsigned char ackbit;
unsigned char i;
OneWire_DQ = 1;
OneWire_DQ = 0; Delay480us(); //总线拉低480us
OneWire_DQ = 1; //释放总线
i = 138; while (--i); //延时等待50us
ackbit = OneWire_DQ; Delay480us(); //读取一下是否为0,并且延时等待480us
}
/*发送一位
主机将总线拉低60~120us,然后释放总线,表示发送0;
主机将总线拉低1~15us,然后释放总线,表示发送1。
从机 将在总线拉低30us后读取电平,整个时间片应大于60us
*/
//发送一位数据,0或1
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
OneWire_DQ = 1;
OneWire_DQ = 0; i = 28; while (--i); //总线拉低10us
OneWire_DQ = Bit; i = 138; while (--i); //将Bit送回总线上再延时50us,检测Bit看变化赋值,比较巧妙的思想
OneWire_DQ = 1; //最终释放总线
}
/*接收一位
主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us末尾)
读取为低电平则为接收0,读取为高电平则为接收1,整个时间片应大于60us
*/
//接收一位数据 0或1
unsigned char OneWire_ReceiveBit(void)
{
unsigned char i;
unsigned char Bit;
OneWire_DQ = 0; i = 14; while (--i); //先把总线拉低5us
OneWire_DQ = 1; i = 14; while (--i); //释放总线5us
Bit = OneWire_DQ; //此时读取电平,数据0会把电平拉低,数据1仍然置高
i = 138; while (--i); //延时50us,满足时序要求
return Bit;
}
//发送一字节数据,从低位到高位依次发送
void OneWire_SendByte(unsigned char Byte)
{
unsigned char i;
for (i = 0; i < 8; i ++)
OneWire_SendBit(Byte & (0x01<<i));
}
//接收一字节数据,从低位到高位依次接收
unsigned char OneWire_ReceiveByte(void)
{
unsigned Byte = 0x00;
unsigned char i;
for (i = 0; i < 8; i ++)
{
//这个if判断,判断为0的时候,Byte当前位还是0,但是变量i正常++
if( OneWire_ReceiveBit() ) //只有接收1才会执行下面的语句
Byte |= 0x01<<i; //从低位到高位依次取出
}
return Byte;
}
OneWire.h
#ifndef __ONEWIRE_H
#define __ONEWIRE_H
#include <STC15F2K60S2.H>
void OneWire_Init(void);//单总线初始化时序
void OneWire_SendBit(unsigned char Bit);//发送一位数据
unsigned char OneWire_ReceiveBit(void);//接收一位数据
void OneWire_SendByte(unsigned char Byte);//发送一字节数据
unsigned char OneWire_ReceiveByte(void);//接收一字节数据
#endif
4.2 15单片机DS18B20代码
温度变换:初始化-->跳过ROM-->开始温度变换
温度读取:初始化-->跳过ROM-->读暂存器-->连续的读操作
具体操作看代码就明白了
建DS18B20.c
#include "DS18B20.h"
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCRATCHPAD 0xBE
//温度转化:初始化-->跳过ROM-->开始温度变换
void DS18B20_ConvertT(void)
{
OneWire_Init();//初始化
OneWire_SendByte(DS18B20_SKIP_ROM);//跳过ROM
OneWire_SendByte(DS18B20_CONVERT_T);//温度变换
}
//温度读取:初始化-->跳过ROM-->读暂存器-->连续的读操作
float DS18B20_ReadT(void)
{
unsigned char TMSB,TLSB;
int temp;//16位数据
float T;
OneWire_Init();//初始化
OneWire_SendByte(DS18B20_SKIP_ROM);//跳过ROM
OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//读暂存器
//一旦发送完指令,控制权交给 从机
TLSB = OneWire_ReceiveByte();//读的Byte0,低8位数据
TMSB = OneWire_ReceiveByte();//读的Byte1,高8位数据
temp = (TMSB << 8) | TLSB;//整合数据
/*温度存储格式整体左移了四位,数据扩大了16倍
bit0是2的-4次幂,bit4才是2的0次幂 = 1*/
T = temp / 16.0; //强制类型转换
return T;
}
温度寄存器格式
解释:
读取数据 读取低八位高八位 整合数据之后,相当于bit0 = 1,实际bit0 = 2的-4次方,所以整合之后的数据比实际数据大了16倍,所以上面的代码才有 temp/16.0
DS18B20.h
#ifndef __DS18B20_H
#define __DS18B20_H
#include <STC15F2K60S2.H>
#include "OneWire.h" //调用子头文件
void DS18B20_ConvertT(void);
float DS18B20_ReadT(void);
#endif
由于转换时间比较长,更新的速率没有刷新的速率快,导致上电第一个数值是默认值25或85
如果在初始化部分先开启一次转换,延时个750ms以上,这样第一次显示的数据就不会出现默认值了
main.c 代码
#include <STC15F2K60S2.H>
#include "smg.h"
#include "DS18B20.h"
#include "OneWire.h"
float T;
void main()
{
All_Init();
DS18B20_ConvertT();//先转换一次,延时1s,覆盖默认值
Delay_ms(1000);
while(1)
{
DS18B20_ConvertT(); //温度转换
T = DS18B20_ReadT(); //温度读取
SMG_ShowFloatNum(T); //数码管显示
}
}
最后还有一个数码管显示浮点数的函数,手敲一个即可
smg.c
#include "smg.h"
//15单片机延时函数 @11.0592MHz
void Delay_ms(int xms)
{
unsigned char i, j;
while(xms--){
_nop_(); _nop_(); _nop_();
i = 11; j = 190;
do{
while (--j);
} while (--i);
}
}
/*共阳极码表 ABCDEF都是大写*/
//0 1 2 3 4 5 6 7 8 9 A B C D E F - 灭
unsigned char NixieTable[] = {0xC0, 0xF9, 0xA4, 0xB0, 0x99, 0x92, 0x82, 0xF8, 0x80, 0x90, 0x88, 0x80, 0xC6, 0xC0, 0x86, 0x8E, 0xBF, 0xFF, \
0xC0 & 0x7F, \
0xF9 & 0x7F, \
0xA4 & 0x7F, \
0xB0 & 0x7F, \
0x99 & 0x7F, \
0x92 & 0x7F, \
0x82 & 0x7F, \
0xF8 & 0x7F, \
0x80 & 0x7F, \
0x90 & 0x7F
};
//如果想显示小数点,就 &0x7F
void Nixie(unsigned char location, unsigned char number)
{
P2 = 0xC0; //Y6=0;Y6C=1; 使能锁存器U8 P27 = 1; P26 = 1; P25 = 0;
switch(location)//选中LED位置,位选
{
case 1:P0 = 0x01;break;
case 2:P0 = 0x02;break;
case 3:P0 = 0x04;break;
case 4:P0 = 0x08;break;
case 5:P0 = 0x10;break;
case 6:P0 = 0x20;break;
case 7:P0 = 0x40;break;
case 8:P0 = 0x80;break;
}
P2 = 0xFF; //Y7=0;Y7C=1; 使能锁存器U7 P27 = 1; P26 = 1; P25 = 1;
P0=NixieTable[number];//数字
Delay_ms(1);
P0=0xFF;//消影清零
}
/*数码管显示浮点数,支持0-1000以内的浮点数*/
void SMG_ShowFloatNum(float num)
{
unsigned char Sep_Num[8]; //一次存放 千百十个...
long temp;
if (num >= 0) //正数
temp = (long)(num*1000);
else
temp = (long)(-num*1000);
Sep_Num[0] = temp / 1000000 % 10; //千位
Sep_Num[1] = temp / 100000 % 10; //百位
Sep_Num[2] = temp / 10000 % 10; //十位
Sep_Num[3] = temp / 1000 % 10; //个位
Sep_Num[4] = temp / 100 % 10; //小数点后第一位
Sep_Num[5] = temp / 10 % 10; //小数点后第二位
Sep_Num[6] = temp % 10; //小数点后第三位
Nixie(4, Sep_Num[3] + 18); //下标+18代表显示小数点
Nixie(5, Sep_Num[4]);
Nixie(6, Sep_Num[5]);
Nixie(7, Sep_Num[6]);
if (num >=10 && num < 100 ) {Nixie(3, Sep_Num[2]);} //显示十位
else if (num >=100 && num < 1000 ) {Nixie(3, Sep_Num[2]); Nixie(2, Sep_Num[1]);} //显示十位百位
else if (num >=1000 && num <= 9999) {Nixie(3, Sep_Num[2]); Nixie(2, Sep_Num[1]); Nixie(1, Sep_Num[0]);} //显示十位百位千位
else if (num > -100 && num <= -10 ) {Nixie(3, Sep_Num[2]); Nixie(2,16);} //显示十位,并且显示负号
else if (num > -1000 && num <= -100) {Nixie(3, Sep_Num[2]); Nixie(2, Sep_Num[1]); Nixie(1,16);} //显示十位百位,并且显示负号
else if (num >= -9999 && num <=-1000) {Nixie(3, Sep_Num[2]); Nixie(2, Sep_Num[1]); Nixie(1, Sep_Num[0]);} //显示十位百位千位,但是数码管不够了后续想显示出来,就把数码管显示的内容整体往后挪动一个位置即可
else if (num > -10 && num < 0 ) {Nixie(3,16);} //大于-10小于0的负数
}
//关闭 蜂鸣器继电器LED数码管
void All_Init(void)
{
P2 = 0xA0; // 1010 0000
P0 = 0x00; //off蜂鸣器继电器
P2 = 0x80; // 1000 0000
P0 = 0xFF; //offLED
P2 = 0xC0; //使能锁存器U8 1100 0000
P0 = 0xFF; //选择所有数码管
P2 = 0xFF; //使能锁存器U7 1111 1111
P0 = 0xFF; //关闭所有数码管
}
smg.h
#ifndef __SMG_H__
#define __SMG_H__
#include <STC15F2K60S2.H>
#include "intrins.h"
extern unsigned char NixieTable[];
void Delay_ms(int xms); //15单片机延时函数 @11.0592MHz
void All_Init(void); //关闭 蜂鸣器继电器LED数码管
void Nixie(unsigned char location, unsigned char number);
void SMG_ShowFloatNum(float num);
//void SMG_ShowFloat(float num);
#endif
4.3 51单片机DS18B20代码
51单片机的代码和15单片机代码逻辑是一致的,只有延时时间代码不同
单总线底层代码
DS18B20 需要严格的单总线协议以确保数据的完整性。
在这个底层代码中初始化、发送一位、读取一位的函数都有一些相同的改动
即在函数开头写EA = 0; 函数结尾写EA = 1;
这样程序就不会被中断打断了,能保证数据完整!
上面15单片机的代码也可以加这两句
#include <REGX52.H>
#include "OneWire.h"
sbit OneWire_DQ = P3^7; //端口
/*初始化
主机 将总线拉低至少480us,然后释放总线,
等待15~60us后,存在的 从机 会拉低总线60~240us以响应 主机
之后 从机 将释放总线 */
//单总线
unsigned char OneWire_Init(void)
{
unsigned char i;
unsigned char AckBit;
EA = 0;
OneWire_DQ = 1; //先给总线置1
OneWire_DQ = 0; //再拉低总线
i = 230; while (--i); //延时500us(至少拉低总线480us)
OneWire_DQ = 1; //然后释放总线
i = 32; while (--i); //延时70us
AckBit = OneWire_DQ; //读出I/O口电平,存放在AckBit
i = 230; while (--i); //延时500us(也是至少480us)
EA = 1;
return AckBit;
}
/*发送一位
主机将总线拉低60~120us,然后释放总线,表示发送0;
主机将总线拉低1~15us,然后释放总线,表示发送1。
从机 将在总线拉低30us后读取电平,整个时间片应大于60us
*/
void OneWire_SendBit(unsigned char Bit)
{
unsigned char i;
EA = 0;
OneWire_DQ = 0; //先直接拉低,初始化之后总线是1,
i = 4; while (--i); //延时10us
OneWire_DQ = Bit; //10us后将Bit送回总线
i = 23; while (--i); //延时50us
OneWire_DQ = 1; //最后释放总线
EA = 1;
}
/*接收一位
主机将总线拉低1~15us,然后释放总线,并在拉低后15us内读取总线电平(尽量贴近15us末尾)
读取为低电平则为接收0,读取为高电平则为接收1,整个时间片应大于60us */
unsigned char OneWire_ReceiveBit(void)
{
unsigned char i;
unsigned char Bit;
EA = 0;
OneWire_DQ = 0; //主机将总线拉低1~15us
i = 2; while (--i); //延时5us
OneWire_DQ = 1; //释放总线
i = 2; while (--i); //延时5us
Bit = OneWire_DQ; //采样、读取电平(读取到0就是从机给拉低了)
i = 23; while (--i); //延时50us
//结束后 从机 会自动释放总线
EA = 1;
return Bit;
}
//发送一个字节
void OneWire_SendByte(unsigned char Byte)
{
unsigned char i;
for(i=0;i<8;i++)
{
OneWire_SendBit(Byte&(0x01<<i));
}
}
//接收一个字节
unsigned char OneWire_ReceiveByte(void)
{
unsigned char i;
unsigned char Byte = 0x00;
for(i=0;i<8;i++)
{
if(OneWire_ReceiveBit()) {Byte |= (0x01<<i);} //从低位到高位依次取出
//这个if判断,判断为0的时候,Byte当前位还是0
}
return Byte;
}
#ifndef __ONEWIRE_H__
#define __ONEWIRE_H__
unsigned char OneWire_Init(void);
void OneWire_SendBit(unsigned char Bit);
unsigned char OneWire_ReceiveBit(void);
void OneWire_SendByte(unsigned char Byte);
unsigned char OneWire_ReceiveByte(void);
#endif
DS18B20代码
#include <REGX52.H>
#include "OneWire.h"
//#include "LCD1602.h"
#define DS18B20_SKIP_ROM 0xCC
#define DS18B20_CONVERT_T 0x44
#define DS18B20_READ_SCRATCHPAD 0xBE
//温度变换:初始化-->跳过ROM-->开始温度变换
void DS18B20_ConvertT(void)
{
OneWire_Init();//初始化
OneWire_SendByte(DS18B20_SKIP_ROM);//跳过ROM
OneWire_SendByte(DS18B20_CONVERT_T);//温度变换
}
//温度读取:初始化-->跳过ROM-->读暂存器-->连续的读操作
float DS18B20_ReadT(void)//温度读取
{
unsigned char TLSB,TMSB;
int temp;//16位数据
float T;
OneWire_Init();
OneWire_SendByte(DS18B20_SKIP_ROM);
OneWire_SendByte(DS18B20_READ_SCRATCHPAD);//读暂存器
//一旦发送完指令,控制权交给 从机
TLSB = OneWire_ReceiveByte(); //读的Byte0,低8位数据
TMSB = OneWire_ReceiveByte(); //读的Byte1,高8位数据
//用LCD1602显示出来这16位二进制数(后四位是小数,再前边四位是整数部分)
// LCD_ShowBinNum(1,1,TMSB,8);
// LCD_ShowBinNum(1,9,TLSB,8);
temp = (TMSB << 8) | TLSB; //正好为int类型16位数据
/*温度存储格式整体左移了四位,数据扩大了16倍
bit0是2的-4次幂,bit4才是2的0次幂 = 1*/
T = temp/16.0; //强制类型转换
return T;
}
中间注释掉两行,可以用LCD1602显示温度读取到的16位数据
#ifndef __DS18B20_H__
#define __DS18B20_H__
void DS18B20_ConvertT(void);
float DS18B20_ReadT(void);//温度读取
#endif
main.c
#include <REGX52.H>
#include "LCD1602.h"
#include "Delay.h"
#include "OneWire.h"
#include "DS18B20.h"
//char ack;
float T; //全局变量
void main()
{
DS18B20_ConvertT(); //先转换1次,
Delay(1000); //再延时1s,就不会显示默认温度25了
LCD_Init();
// LCD_ShowString(1,1,"Temperature:");
// ack = OneWire_Init();
// LCD_ShowNum(2,1,ack,3);
LCD_ShowChar(2,9,0xDF);
LCD_ShowChar(2,10,'C');
while(1)
{
DS18B20_ConvertT();
T = DS18B20_ReadT();
if(T<0)
{
LCD_ShowChar(2,1,'-');//显示个负号
T=-T; //转化成正数
}
else {LCD_ShowChar(2,1,'+');}
LCD_ShowNum(2,2,T,3);//整数部分
LCD_ShowChar(2,5,'.');
LCD_ShowNum(2,6,(unsigned long)(T*10000)%10000,3);
//乘一万,小数部分移动到整数部分,再取余,再强制类型转换unsigned long
}
}
内容有点多呀,再敲敲吧~
学习视频链接