一、单总线协议(1-wire)
1.定义:主机和从机用一根总线进行通信,是一种半双工的通信方式,单线=时钟线+数据线+控制线( +电源线)。理想状况下一条总线上的从器件数量几乎不受数量限制。
2.特点:这是由达拉斯半导体公司推出的一项通信技术。它采用单根信号线,既可传输时钟,又能传输数据,而且数据传输是双向的。
3.优点:单总线技术具有线路简单,硬件开销少,成本低廉,便于总线扩展和维护等。
二、单总线通信过程
所有的单总线器件要求采用严格的通信协议, 以保证数据的完整性。该协议定义了几种信号类型:复位脉冲、 应答脉冲、 写0、写1、 读0和读1。所有这些信号,除了应答脉冲以外,都由主机发出同步信号。并且发送所有的命令和数据都是字节的低位在前,这一点与多数串行通信格式不同(多数为字节的高位在前)。
(1)初始化序列:复位和应答脉冲
初始化过程 = 复位脉冲 + 从机应答脉冲。
主机通过拉低单总线480 ~ 960 us产生复位脉冲,然后释放总线,进入接收模式。主机释放总线时,会产生低电平跳变为高电平的上升沿,单总线器件检测到上升沿之后,延时15 ~ 60 us,单总线器件拉低总线60 ~ 240 us来产生应答脉冲。主机接收到从机的应答脉冲说明单总线器件就绪,初始化过程完成。
(1)初始化时序
(2)写间隙
写间隙有两种,包括写0的时间隙和写1的时间隙。当数据线拉低后,在15 ~ 60 us的时间窗口内对数据线进行采样。如果数据线为低电平,就是写0,如果数据线为高电平,就是写1。主机要产生一个写1时间隙,就必须把数据线拉低,在写时间隙开始后的15 us内允许数据线拉高。主机要产生一个写0时间隙,就必须把数据线拉低并保持60 us。
写时间隙时序图如下所示:
(3)读时间隙
当主机把总线拉低是,并保持至少1 us后释放总线,必须在15 us内读取数据。单总线器件仅在主机发出读时隙时,才向主机传输数, 所以, 在主机发出读数据命令后,必须马上产生读时隙,以便从机能够传输数据。所有读时隙至少需要 60us, 且在两次独立的读时隙之间至少需要 1us的恢复时间,每个读时隙都由主机发起, 至少拉低总线 1us (图5 所示)。在主机发起读时隙之后,单总线器件才开始在总线上发送 0 或1。 若从机发送1,则保持总线为高电平;若发送 0, 则拉低总线 。当发送 0 时,从机在该时隙结束后释放总线 。由上拉电阻将总线拉回至空闲高电平状态。从机发出的数据在起始时隙之后,保持有效时间 15us ,因而,主机在读时隙期间必须释放总线 ,并且在时隙起始后的 15 us 之内采样总线状态。
三、DS18B20温度传感器模块
它一共有3个角,分别是:
GND(接地)
DQ(数据总线,与单片机的一个IO口相连)
Vdd(电源供应)
DS18N20的电路原理图
DS18B20要想实现温度测量,必须先复位初始化:
复位初始化时序图
首先总线为高电平持续约几微秒,拉低总线,持续480us---960us再释放总线(拉高总线),等待15-60us,如果检测到DS18B20存在端口会返回一个低电平脉冲信号,持续60--240us,总的检测时间最小为480us,所以再检测低电平脉冲信号(240us)后,释放总线(拉高总线)240us(480-240=240);
复位代码实现
#include "reg52.h"
#include <intrins.h>
sbit DS = P3^7;
int DS_Reset()
{
unsigned char i;
DS = 1;
_nop_();
DS = 0;//拉低最少480us,480us----960us(最上边单总线协议的初始化时序图上有最大时间为960us)
i = 70;
while(i--);//循环一次需要9个周期,79*9=630 再加上i=0没计算的周期,630+12=642us
DS = 1;//拉高总线
i = 0;
i = 2;
while(i--);//等待18us,时序图上是等待15us -- 60us
if(DS){
i = 7;//7*9 = 63us
while(i--);//再多给63us如果还是高电平,则复位失败
if(DS){
return 0;
}
}
return 1;//如果为低电平则返回1,表示复位成功
}
写时序图
总线高电平维持一个_nop_(),然后拉低总线维持_nop_(),DS开始写数据,先取出最低位:temp&0x01,如果写入0的话最低位就为0,如果写1的话最低位就为1。然后延时60us(15+15+30 = 60)如果写1的话,15us后就写入1,如果写0的话60us就开始写0。
写数据代码实现
#include "reg52.h"
#include <intrins.h>
sbit DS = P3^7;
void Ds18b20WriteByte(uchar temp)
{
unsigned char i,j;
for(j=0;j<8;j++){
DS = 1;
_nop_();
DS = 0;
_nop_();
DS = 1;//释放总线
DS = temp & 0x01;//取出temp的最低位,如果temp的最低位为0,0&1=1,则DS存放0,如果temp最低位为1,1&1=1,DS,存放1
i = 6;
while(i--);//延时66us : 6*9+12=66us
temp >>= 1;//temp >>= 1 ==> temp = temp>>1移位,从低位向高位写入数据,所以,最低为写入后,向右移,原来的次低位代替原来的低位。
}
}
读时序图
总线高电平维持一个_nop()_,拉低总线,延时_nop_(),释放总线,开始读数据,45us后开始读下一个数据
#include "reg52.h"
#include <intrins.h>
sbit DS = P3^7;
char Ds18b20ReadByte()
{
unsigned char temp;//存放来自DS的数据
unsigned int i,j;
for(j=0;j<8;j++){
DS = 1;
_nop_();
DS = 0;
_nop_();
_nop_();
DS = 1;//释放总线
temp >>= 1;
if(DS){
temp = temp | 0x80;//如果temp写1,那么或一个0x80,就将1读出来
}
if(!DS){
temp = temp | 0x00;//如果temp写0,那么或一个0x00,就将0读出来
}
i=4;
while(i--);//延时48us再读取下一位数据
}
return temp;
}
此次实验所使用的是普中科技A251单片机
P3^7口作为I/O口来读写数据:
读出数据后乘以频率0.0625就的到了温度,高位全为1,则温度位负数
想要检测温度必须先复位然后发送CCH来跳过ROM指令,然后发44H(每次只能运行RAM指令表和暂存寄存器两个中的一个指令表,运行后需复位再运行另一个),复位,发CCH,再发送BEH来读暂存器,读温度的低8位和高8位
温度测量的代码实现
#include <reg52.h>
#include <intrins.h>
sbit LSA = P2^2;
sbit LSB = P2^3;
sbit LSC = P2^4;
sbit DS = P3^7;
//转换温度启动指令
void Ds18b20ChangTemp()
{
Ds18b20_init(); //初始化
delayms(1);
Ds18b20WriteByte(0xcc); //写入跳过ROM操作命令(cc)
Ds18b20WriteByte(0x44); //温度转换命令(44)
}
//读取温度命令
void Ds18b20ReadTempCom()
{
Ds18b20_init();
delayms(1);
Ds18b20WriteByte(0xcc); //跳过ROM操作命令
Ds18b20WriteByte(0xbe); //发送读取温度命令
}
int DS18b20ReadTemp()
{
float tp;
int i;
int temp = 0;
uchar tmh,tml;
Ds18b20ChangTemp(); //先写入转换命令
Ds18b20ReadTempCom(); //然后等待转换完后发送读取温度命令
tml = Ds18b20ReadByte(); //读取温度值共16位,先读低字节
tmh = Ds18b20ReadByte(); //再读高字节
temp = tmh; //用中间值来运算,防止测量温度的时候出现问题
temp <<= 8; //移位运算,高字节左移8位,变为16位
temp |= tml; //位运算中的或运算,组合
if(temp <0){
temp = temp-1;//因为得到的是补码,但是温度计算用原码计算,原码按位取反+1就是补码
tp = ~temp;
temp = temp*0.0625*100+0.5 //留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为
整型的时候把小数点后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
算加上0.5,还是在小数点后面。
}else{
tp=temp;//因为数据处理有小数点所以将温度赋给一个浮点型变量,如果温度是正的那么,那么正数的原码就是补码它本身
temp=tp*0.0625*100+0.5;
}
return temp;
}
数码管显示温度
本次项目使用的是普中A289c52单片机,数码管的驱动不是直接在端口,而是通过74HC138译码器进行位选
文章来源:https://www.toymoban.com/news/detail-498718.html
通过译码器实现数码管位选和显示的代码实现
#include <reg52.h>
#include <intrins.h>
sbit BEEP = P1^7;
sbit LSA = P2^2;
sbit LSB = P2^3;
sbit LSC = P2^4;
sbit DS = P3^7;
char code smgduan[13]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x00,0x80,0x40}; //共阴:0-9数码
char display_T[8];//存放温度的数组 不显示 . -
//毫秒延迟函数
void delayms(uint ms)
{
uchar i;
while(ms--)
for(i=0;i<123;i++);
}
//数码管显示温度
void ds20b18display(int temp)
{
display_T[2] = temp/10000; //显示百位
display_T[3] = temp%10000/1000; //显示十位
display_T[4] = temp%1000/100; //显示个位
display_T[6] = temp%10/10; //显示小数点后一位
display_T[7] = temp%10; //显示小数点后两位
display_T[1] = 10; //无显示
display_T[5] = 11; //显示“.”
}
//数码管显示函数
void Digdisplay()
{
char i;
for(i=0;i<8;i++)
{
switch(i)
{
case 0: LSC=1;LSB=1;LSA=1; break; //显示第1位数码管
case 1: LSC=1;LSB=1;LSA=0; break; //显示第2位数码管
case 2: LSC=1;LSB=0;LSA=1; break; //显示第3位数码管
case 3: LSC=1;LSB=0;LSA=0; break; //显示第4位数码管
case 4: LSC=0;LSB=1;LSA=1; break; //显示第5位数码管
case 5: LSC=0;LSB=1;LSA=0; break; //显示第6位数码管
case 6: LSC=0;LSB=0;LSA=1; break; //显示第7位数码管
case 7: LSC=0;LSB=0;LSA=0; break; //显示第8位数码管 LED1
}
P0=smgduan[display_T[i]]; //发送数据
delayms(1); //延时1ms,让数码管不断显示
P0=0x00; //数码管消影
}
}
所有程序展示
#include <reg52.h>
#include <intrins.h>
#define uint unsigned int
#define uchar unsigned char
typedef unsigned int u16;
sbit BEEP = P1^7;
sbit LSA = P2^2;
sbit LSB = P2^3;
sbit LSC = P2^4;
sbit DS = P3^7;
char code smgduan[13]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,
0x7f,0x6f,0x00,0x80,0x40}; //共阴:0-9数码
char display_T[8]; // 不显示 . -
//毫秒延迟函数
void delayms(uint ms)
{
uchar i;
while(ms--)
for(i=0;i<123;i++);
}
//数码管显示函数
void Digdisplay()
{
char i;
for(i=0;i<8;i++)
{
switch(i)
{
case 0: LSC=1;LSB=1;LSA=1; break; //显示第1位数码管
case 1: LSC=1;LSB=1;LSA=0; break; //显示第2位数码管
case 2: LSC=1;LSB=0;LSA=1; break; //显示第3位数码管
case 3: LSC=1;LSB=0;LSA=0; break; //显示第4位数码管
case 4: LSC=0;LSB=1;LSA=1; break; //显示第5位数码管
case 5: LSC=0;LSB=1;LSA=0; break; //显示第6位数码管
case 6: LSC=0;LSB=0;LSA=1; break; //显示第7位数码管
case 7: LSC=0;LSB=0;LSA=0; break; //显示第8位数码管
}
P0=smgduan[display_T[i]]; //发送数据
delayms(1); //延时1ms,让数码管不断显示
P0=0x00; //数码管消遣
}
}
//DS18B20初始化函数
uchar Ds18b20_init()
{
/*unsigned char i;
DS = 0; //将总线拉低480US~960us
i = 70;
while(i--); //延时642us,循环一次要9个周期,9x70=630 再加一次跳过没计算的12,就是642
DS = 1; //拉高总线,如果DS18B20做出反应会将在15us~60us后总线拉低
i = 0;
while(DS) //等待DS18B20拉低总线
{
delayms(1);
i++;
if(i>1) //等待大于1MS,说明总线没有被拉低,初始化失败
{
return 0; //初始化失败
}
}
return 1; //若为低电平,直接跳过while循环初始化成功
*/
unsigned char i;
DS = 1;
_nop_();
DS = 0;//拉低最少480us,480us----960us(最上边单总线协议的初始化时序图上有最大时间为960us)
i = 70;
while(i--);//循环一次需要9个周期,79*9=630 再加上i=0没计算的周期,630+12=642us
DS = 1;//拉高总线
i = 0;
i = 2;
while(i--);//等待18us
if(DS){
i = 7;//7*9 = 63us
while(i--);//再多给63us如果还是高电平,则复位失败
if(DS){
return 0;
}
}
return 1;//如果为低电平则返回1,表示复位成功
}
//写时序函数
void Ds18b20WriteByte(uchar temp)
{
/*int i,j;
for(j=0;j<8;j++) //8位数据
{
DS = 0; //每写入一位数据之前先把总线拉低1us
_nop_();
DS = tmp & 0x01; //然后写入一个数据,从最低位开始。位运算的与运算,只要数据中有0,则相与后为0,只有为1相与为1
i=6; //延时初值
while(i--); //延时66us,因为持续时间最少为60us 6*9+12 = 66
DS = 1; //然后释放总线,至少1us给总线恢复时间才能接着写入第二个数值
tmp >>= 1; //移位,从低位向高位写入数据,所以,最低为写入后,向右移,原来的次低位代替原来的低位。
}*/
unsigned char i,j;
for(j=0;j<8;j++){
DS = 1;
_nop_();
DS = 0;
_nop_();
DS = 1;//释放总线
DS = temp & 0x01;//取出temp的最低位,如果temp的最低位为0,0&1=1,则DS存放0,如果temp最低位为1,1&1=1,DS,存放1
i = 6;
while(i--);//延时66us : 6*9+12=66us
temp >>= 1;//temp >>= 1 ==> temp = temp>>1移位,从低位向高位写入数据,所以,最低为写入后,向右移,原来的次低位代替原来的低位。
}
}
//读时序函数
uchar Ds18b20ReadByte()
{
uchar tmp;
uint i,j;
for(j=8;j>0;j--)
{
DS = 0; //先将总线拉低1us
_nop_();
tmp = tmp >> 1;
DS = 1; //然后释放总线
_nop_();
_nop_(); //延迟几us等待数据稳定
if(DS){
tmp = tmp | 0x80;
}
else if(!DS){
tmp = tmp | 0x00;
}
i=4; //4*9+12 = 48
while(i--);//读取完之后等待48us再接着读取下一个数
}
return tmp;
}
//转换温度启动指令
void Ds18b20ChangTemp()
{
Ds18b20_init(); //初始化
delayms(1);
Ds18b20WriteByte(0xcc); //写入跳过ROM操作命令(cc)
Ds18b20WriteByte(0x44); //温度转换命令(44)
}
//读取温度命令
void Ds18b20ReadTempCom()
{
Ds18b20_init();
delayms(1);
Ds18b20WriteByte(0xcc); //跳过ROM操作命令
Ds18b20WriteByte(0xbe); //发送读取温度命令
}
//读取温度
int Ds18b20ReadTemp()
{
float tp;
int i , beep_reg;
int temp = 0;
uchar tmh,tml;
Ds18b20ChangTemp(); //先写入转换命令
Ds18b20ReadTempCom(); //然后等待转换完后发送读取温度命令
tml = Ds18b20ReadByte(); //读取温度值共16位,先读低字节
tmh = Ds18b20ReadByte(); //再读高字节
temp = tmh;
temp <<= 8; //移位运算,高字节左移8位,变为16位
temp |= tml; //位运算中的或运算,组合
if(temp < 0)
{
display_T[0] = 12; //显示“-”
temp=temp-1; //因为读取的温度是实际温度的补码,所以减1,再取反求出原码
temp=~temp;
tp=temp;
temp=tp*0.0625*100+0.5; //0.0625是精度
//留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为整型的时候把小数点
//后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
//算加上0.5,还是在小数点后面。
}
else
{
display_T[0] = 10; //无显示
tp=temp;//因为数据处理有小数点所以将温度赋给一个浮点型变量
//如果温度是正的那么,那么正数的原码就是补码它本身
temp=tp*0.0625*100+0.5;
//留两个小数点就*100,+0.5是四舍五入,因为C语言浮点数转换为整型的时候把小数点
//后面的数自动去掉,不管是否大于0.5,而+0.5之后大于0.5的就是进1了,小于0.5的就
//算加上0.5,还是在小数点后面。
beep_reg = temp;
if(beep_reg>3000) //超过30度报警,但是数值一定要写3000,因为传过来的数是不加小数点的数据4位:两位整数两位小数;其他温度同理
{
for(i=30;i>0;i--)
{
BEEP=~BEEP;
}
}
}
return temp; //高8位和低8位的组合
}
//数码管显示温度
void ds20b18display(int temp)
{
display_T[2] = temp/10000; //显示百位
display_T[3] = temp%10000/1000; //显示十位
display_T[4] = temp%1000/100; //显示个位
display_T[6] = temp%10/10; //显示小数点后一位
display_T[7] = temp%10; //显示小数点后两位
display_T[1] = 10; //无显示
display_T[5] = 11; //显示“.”
}
void main()
{
int temp_chek;
BEEP =1;
Ds18b20_init();
while(1)
{
ds20b18display(Ds18b20ReadTemp());
Digdisplay();
temp_chek = Ds18b20ReadTemp();
if(temp_chek >= 30 ){
BEEP = 0;
}else {
BEEP = 1;
delayms(30);
}
}
}
结果展示
文章来源地址https://www.toymoban.com/news/detail-498718.html
到了这里,关于[stc89c52] DS18B20基于单总线协议的温度测量的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!