[讨论] DS18B20 测温程序完全解读

fsddd   2008-6-18 08:51 楼主
硬件: 51板
    (1)单线ds18b20接 P2.2
    (2)使用外部电源给ds18b20供电,没有使用寄生电源奥
软件:
     Kei uVision 2
    刚开始对时序把握不好,可是在论坛里没找到比较详细的解释,所以俺倒塌了这个东东,就把俺的经验贴上来,供大家参考,呵呵……
    如有错误请指正

#include "reg52.h"
#include "intrins.h"
#define uchar unsigned char
#define uint unsigned int
sbit ds=P2^2;
sbit dula=P2^6;
sbit wela=P2^7;
uchar flag ;
uint temp;               //参数temp一定要声明为 int 型
uchar code table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,
0x07,0x7f,0x6f,0x77,0x7c,0x39,0x5e,0x79,0x71};     //不带小数点数字编码

uchar code table1[]={0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,
0x87,0xff,0xef};        //带小数点数字编码

/*延时函数*/
void TempDelay (uchar us)
{
   while(us--);
}

void delay(uint count) //延时子函数
{
uint i;
while(count)
{
i=200;
while(i>0)
i--;
count--;
}
}

/*串口初始化,波特率9600,方式1 */
void init_com()
{
   TMOD=0x20;      //设置定时器1为模式2
   TH1=0xfd;       //装初值设定波特率
   TL1=0xfd;
   TR1=1;          //启动定时器
   SM0=0;          //串口通信模式设置
   SM1=1;
// REN=1;          //串口允许接收数据
   PCON=0;         //波特率不倍频
//  SMOD=0;       //波特率不倍频
  // EA=1;          //开总中断
  //ES=1;          //开串行中断
}

/*数码管的显示 */
void display(uint temp)
{
    uchar bai,shi,ge;
    bai=temp/100;
    shi=temp%100/10;
    ge=temp%100%10;

dula=0;
    P0=table[bai];  //显示百位
dula=1;         //从0到1,有个上升沿,解除锁存,显示相应段
dula=0;         //从1到0再次锁存
         
    wela=0;
    P0=0xfe;
wela=1;
wela=0;
delay(1);    //延时约2ms

P0=table1[shi];  //显示十位
dula=1;
    dula=0;
   
P0=0xfd;
wela=1;
    wela=0;
    delay(1);

    P0=table[ge];  //显示个位
dula=1;
    dula=0;
   
P0=0xfb;
wela=1;
    wela=0;
    delay(1);
}
/*****************************************
时序:初始化时序、读时序、写时序。
所有时序都是将主机(单片机)作为主设备,单总
线器件作为从设备。而每一次命令和数据的传输
都是从主机主动启动写时序开始,如果要求单总
线器件回送数据,在进行写命令后,主机需启动
读时序完成数据接收。数据和命令的传输都是低
位在先。  
初始化时序:复位脉冲 存在脉冲
        读;1 或 0时序
        写;1 或 0时序
只有存在脉冲信号是从18b20(从机)发出的,其
它信号都是由主机发出的。
存在脉冲:让主机(总线)知道从机(18b20)已
经做好了准备。
******************************************/


/*--------------------------------------------------------------------------------------------------------------------
初始化:检测总线控制器发出的复位脉冲
和ds18b20的任何通讯都要从初始化开始

初始化序列包括一个由总线控制器发出的复位脉冲
和跟在其后由从机发出的存在脉冲。

初始化:复位脉冲+存在脉冲

具体操作:
  总线控制器发出(TX)一个复位脉冲 (一个最少保持480μs 的低电平信号),然后释放总线,
进入接收状态(RX)。单线总线由5K 上拉电阻拉到高电平。探测到I/O 引脚上的上升沿后
DS1820 等待15~60μs,然后发出存在脉冲(一个60~240μs 的低电平信号)。

  具体看"倒塌 18b20"文档里的 " 单线复位脉冲时序和1-wire presence detect "的时序图
-------------------------------------------------------------------------------------------------------------------*/
void ds_reset(void)
{
   ds=1;
   _nop_();        //1us
   ds=0;
   TempDelay(80);  //当总线停留在低电平超过480us,总线上所以器件都将被复位,这里//延时约530us总线停留在低电平超过480μs,总线上的所有器件都
//将被复位。
   _nop_();
   ds=1;           //产生复位脉冲后,微处理器释放总线,让总线处于空闲状态,原因查//18b20中文资料

   TempDelay(5);  //释放总线后,以便从机18b20通过拉低总线来指示其是否在线,
                  //存在检测高电平时间:15~60us, 所以延时44us,进行            1-wire presence //detect(单线存在检测)
   _nop_();
   _nop_();
   _nop_();
   if(ds==0)
        flag=1;       //detect 18b20 success
   else
        flag=0;       //detect 18b20 fail
  TempDelay(20);    //存在检测低电平时间:60~240us,所以延时约140us
   _nop_();
   _nop_();
   ds=1;          //再次拉高总线,让总线处于空闲状态
/**/
}

/*----------------------------------------
读/写时间隙:
DS1820 的数据读写是通过时间隙处理
位和命令字来确认信息交换。
------------------------------------------*/
bit  ds_read_bit(void)    //读一位
{
   bit dat;
   ds=0;         //单片机(微处理器)将总线拉低
  _nop_();       //读时隙起始于微处理器将总线拉低至少1us
   ds=1;        //拉低总线后接着释放总线,让从机18b20能够接管总线,输出有效数据
   _nop_();
   _nop_();          //小延时一下,读取18b20上的数据 ,因为从ds18b20上输出的数据
//在读"时间隙"下降沿出现15us内有效
   dat=ds;           //主机读从机18b20输出的数据,这些数据在读时隙的下降沿出现//15us内有效
   TempDelay(10);    //所有读"时间隙"必须60~120us,这里77us
   return(dat);       //返回有效数据
}
uchar ds_read_byte(void ) //读一字节
{

uchar value,i,j;
value=0;           //一定别忘了给初值
for(i=0;i<8;i++)
{
    j=ds_read_bit();
     value=(j<<7)|(value>>1);   //这一步的说明在一个word文档里面
}
return(value);        //返回一个字节的数据
}
void ds_write_byte(uchar dat) //写一个字节
{
  uchar i;
  bit onebit;        //一定不要忘了,onebit是一位
  for(i=1;i<=8;i++)
  {
    onebit=dat&0x01;
    dat=dat>>1;
if(onebit)      //写 1
{
ds=0;
_nop_();   
      _nop_();      //看时序图,至少延时1us,才产生写"时间隙"  
ds=1;       //写时间隙开始后的15μs内允许数据线拉到高电平
     TempDelay(5);  //所有写时间隙必须最少持续60us
}
else         //写 0
{
ds=0;
     TempDelay(8);    //主机要生成一个写0 时间隙,必须把数据线拉到低电平并保持至少60μs,这里64us
ds=1;
_nop_();
     _nop_();
}
  }
}

/*****************************************
主机(单片机)控制18B20完成温度转换要经过三个步骤:
每一次读写之前都要18B20进行复位操作,复位成功后发送
一条ROM指令,最后发送RAM指令,这样才能对DS18b20进行
预定的操作。
复位要求主CPU将数据线下拉500us,然后释放,当ds18B20
受到信号后等待16~60us,后发出60~240us的存在低脉冲,
主CPU收到此信号表示复位成功
******************************************/

/*----------------------------------------
进行温度转换:
先初始化
然后跳过ROM:跳过64位ROM地址,直接向ds18B20发温度转换命令,适合单片工作
发送温度转换命令
------------------------------------------*/

void tem_change()
{
  ds_reset();
  delay(1);              //约2ms
  ds_write_byte(0xcc);
  ds_write_byte(0x44);
}

/*----------------------------------------
获得温度:
------------------------------------------*/
uint get_temperature()
{
  float wendu;
  uchar a,b;
  ds_reset();
  delay(1);              //约2ms
  ds_write_byte(0xcc);
  ds_write_byte(0xbe);
  a=ds_read_byte();
  b=ds_read_byte();
  temp=b;
  temp<<=8;
  temp=temp|a;
  wendu=temp*0.0625;     //温度读取的解释我记录在 "倒塌 18B20"里面
  temp=wendu*10+0.5;
  return temp;
}
/*----------------------------------------
读ROM   
------------------------------------------*/
/*
void ds_read_rom()                  //这里没有用到
{
   uchar a,b;
   ds_reset();
   delay(30);
   ds_write_byte(0x33);
   a=ds_read_byte();
   b=ds_read_byte();
}
*/
void main()
{
   uint a;
   init_com();
   while(1)
   {
tem_change();          //12位转换时间最大为750ms
for(a=10;a>0;a--)
{
display( get_temperature());
}
   }
}

回复评论 (13)

回复 楼主 的帖子

虽然一直在做ARM,可是俺也喜欢51,希望尽快把ARM和51都倒塌掉,呵呵
点赞  2008-6-18 08:51

回复 沙发 的帖子

DS18B20 测温+LCD1602显示
上个程序俺用数码管显示温度,这次就贴个用LCD1602显示的吧,呵呵……
lcd1602硬件连接:
       /*----------------------------------------------------------------------------------------------------
TC1602A(16*2)模拟口线接线方式
连接线图:  
       ---------------------------------------------------
       |LCM-----51   | LCM-----51   | LCM------51      |
       -------------------------------------------------------------------
       |D0-----P0.0 | D4-----P0.4 | RW------接地 (只能写) |
       |D1-----P0.1 | D5-----P0.5 | RS-------P3.5           |
       |D2-----P0.2 | D6-----P0.6 | E---------P3.4            |
       |D3-----P0.3 | D7-----P0.7 | VLCD接1K电阻到GND|
       --------------------------------------------------- ----------------
[注:AT89c52使用11.0592M晶振]
---------------------------------------------------------------------------------------------------------*/
DS18B20的硬件连接:
       这个没有变,和上一个程序一样

程序如下:
   #include  "reg52.h"
#include  "intrins.h"

#define uchar unsigned char
#define uint unsigned int

sbit DS=P2^2;
sbit dula=P2^6;
sbit wela=P2^7;
sbit LCD1602_E=P3^4;
sbit LCD1602_RS=P3^5;

uchar flag;
uint temp;
uchar tflag;       //温度正负标志位
uint tvalue;         //呵呵 刚开始又犯了那个错误,就是tvalue的类型要声明为int型
uchar data disdata[5];

uchar table1[]="temperature:";      //lcd第一行要显示的字符
uchar table2[]="            ";      //lcd第二行初始化时使用

/***********************************************
LCD1602 的操作
***********************************************/
void delay(uint x)    //延时子函数
{
    uint a,b;
    for(a=x;a>0;a--)
      for(b=10;b>0;b--);
}

/*---------------------------------------------
写指令,看着时序图编写就可以了
------------------------------------------------  */
void lcd_write_cmd(uchar cmd)
{
   P0=cmd;
   LCD1602_RS=0;
   LCD1602_E=0;
   delay(10);
   LCD1602_E=1;
   delay(10);
   LCD1602_E=0;
}
/*-----------------------------------
读指令,同样要看着时序图写
-------------------------------------*/
void lcd_write_data(uchar data1)
{
   P0=data1;
   LCD1602_RS=1;
   LCD1602_E=0;
   delay(10);
   LCD1602_E=1;
   delay(10);
   LCD1602_E=0;
}
/*-----------------------------------
lcd1602 的初始化设置
-------------------------------------*/
void init_lcd1602(void)
{
    dula=0;
    wela=0;
    lcd_write_cmd(0x38);
    delay(20);
    lcd_write_cmd(0x0e);
    delay(20);
    lcd_write_cmd(0x06);
    delay(20);
    lcd_write_cmd(0x01);
    delay(20);
}
/*-----------------------------------
初始化lcd1602的显示时用到的显示函数
-------------------------------------*/
void display(unsigned char *p)
{
    while(*p!='\0')
    {
        lcd_write_data(*p);
        p++;
        delay(20);
    }
}
/*-----------------------------------
初始化lcd1602的显示
-------------------------------------*/
void  init_lcd_display(void)
{

  init_lcd1602();
  lcd_write_cmd(0x80); //设定第一行显示初始地址
  delay(20);
  display(table1);

  lcd_write_cmd(0xc0); //设定第而行显示的初地址
  delay(20);
  display(table2);
}


/***********************************************
DS18B20 的操作
***********************************************/
/*-----------------------------------
延时函数
-------------------------------------*/
void TempDelay (uchar us)
{
   while(us--);
}
/*ds18b20 的初始化*/
void ds_reset(void)
{
   DS=1;
   _nop_();        //1us
   DS=0;
   TempDelay(80);  //当总线停留在低电平超过480us,总线上所以器件都将被复位,这里延时约530us
                   //总线停留在低电平超过480μs,总线上的所有器件都将被复位。
   _nop_();
   DS=1;           //产生复位脉冲后,微处理器释放总线,让总线处于空闲状态,原因查18b20中文资料

   TempDelay(5);  //释放总线后,以便从机18b20通过拉低总线来指示其是否在线,
                  //存在检测高电平时间:15~60us, 所以延时44us,进行1-wire presence detect(单线存在检测)
   _nop_();
   _nop_();
   _nop_();
  if(DS==0)
      flag=1;       //detect 18b20 success
   else
      flag=0;       //detect 18b20 fail
  TempDelay(20);    //存在检测低电平时间:60~240us,所以延时约140us
   _nop_();
   _nop_();
   DS=1;          //再次拉高总线,让总线处于空闲状态
/**/
}
/*-----------------------------------------------------------
读/写时间隙:
            DS1820 的数据读写是通过时间隙处理
            位和命令字来确认信息交换。
-------------------------------------------------------------*/
bit  ds_read_bit(void)    //读一位
{
   bit dat;
   DS=0;          //单片机(微处理器)将总线拉低
  _nop_();       //读时隙起始于微处理器将总线拉低至少1us
   DS=1;          //拉低总线后接着释放总线,让从机18b20能够接管总线,输出有效数据
   _nop_();
   _nop_();          //小延时一下,读取18b20上的数据 ,因为从ds18b20上输出的数据在读"时间隙"下降沿出现15us内有效
   dat=DS;           //主机读从机18b20输出的数据,这些数据在读时隙的下降沿出现15us内有效
   TempDelay(10);    //所有读"时间隙"必须60~120us,这里77us
   return(dat);     //返回有效数据
}
uchar ds_read_byte(void ) //读一字节
{

    uchar value,i,j;
    value=0;          //一定别忘了给初值
    for(i=0;i<8;i++)
    {
    j=ds_read_bit();
     value=(j<<7)|(value>>1); //这一步的说明在一个word文档里面
    }
    return(value); //返回一个字节的数据
}
void ds_write_byte(uchar dat) //写一个字节
{
  uchar i;
  bit onebit;        //一定不要忘了,onebit是一位
  for(i=1;i<=8;i++)
  {
    onebit=dat&0x01;
    dat=dat>>1;
    if(onebit)      //写 1
    {
      DS=0;
      _nop_();   
      _nop_();      //看时序图,至少延时1us,才产生写"时间隙"  
      DS=1;       //写时间隙开始后的15μs内允许数据线拉到高电平
     TempDelay(5);  //所有写时间隙必须最少持续60us
    }
    else         //写 0
    {
      DS=0;
     TempDelay(8);    //主机要生成一个写0 时间隙,必须把数据线拉到低电平并保持至少60μs,这里64us
     DS=1;
      _nop_();
     _nop_();
    }
  }
}
/*--------------------------------------------------------------------------------------
进行温度转换:
             先初始化
             然后跳过ROM:跳过64位ROM地址,直接向ds18B20发温度转换命令,适合单片工作
             发送温度转换命令
----------------------------------------------------------------------------------------*/

void tem_change()
{
  ds_reset();
  delay(1);              //约2ms
  ds_write_byte(0xcc);
  ds_write_byte(0x44);
}
/*----------------------------------------
获得温度:               
------------------------------------------*/
uint get_temperature()
{
  float wendu;
  uchar a,b;
  ds_reset();
  delay(1);              //约2ms
  ds_write_byte(0xcc);
  ds_write_byte(0xbe);
  a=ds_read_byte();
  b=ds_read_byte();
  temp=b;
  temp<<=8;
  temp=temp|a;
  if(temp<0x0fff)
    tflag=0; //正值
  else
   {
     tflag=1;
     temp=~temp+1;
   }
  wendu=temp*0.0625;     //温度读取的解释我记录在 "倒塌 18B20"里面
  temp=wendu*10+0.5;
  return temp;
}
/*-----------------------------------
显示读取的温度
------------------------------------------*/
void display_18b20(uint tvalue)    // 参数tvalue 的类型一定要声明为int型,如果声明为char型,数值会局限在0-255,超过255就会自动清零
{
   uint flagdata;
   disdata[0]=tvalue/1000+0x30;    //0x30在字符库里对应显示字符"0", 这里是显示温度的百位数
   disdata[1]=tvalue%1000/100+0x30; //十位数
   disdata[2]=tvalue%100/10+0x30;  //个位
   disdata[3]=tvalue%10+0x30;      //小数位

   if(tflag==0)
     flagdata=0x20;   //0x20在字符库里为空
    else
      flagdata=0x2d;  //0x2d在字符库里对应负号"-"

  if(disdata[0]==0x30)
   {
     disdata[0]=0x20;
     if(disdata[1]==0x30)
     {
       disdata[1]=0x20;
     }
   }
    lcd_write_cmd(0xc0);
    lcd_write_data(flagdata);//显示符号位

    lcd_write_cmd(0xc1);
    lcd_write_data(disdata[0]);//显示百位

    lcd_write_cmd(0xc2);
    lcd_write_data(disdata[1]);//显示十位

    lcd_write_cmd(0xc3);
   lcd_write_data(disdata[2]);//显示个位

   lcd_write_cmd(0xc4);
   lcd_write_data(0x2e);//显示小数点

    lcd_write_cmd(0xc5);
    lcd_write_data(disdata[3]);//显示小数位
}
void main()
{
init_lcd_display();          //lcd1602的初始化显示
while(1)
{
   tem_change();
   tvalue=get_temperature();
   display_18b20(tvalue);
}
}

大家多交流啊,希望能通过一起努力,把DS18B20彻底的倒塌掉,呵呵……
大家快跟帖啊……有经验一起分享,进步更快奥!
点赞  2008-6-18 08:52

回复 板凳 的帖子

看DS18B20的手册,似乎寄生电源特别简单,跟MCU连一根DQ,连一根地线就OK了
真这么简单?有人实践过吗?
另外如果用寄生电源,和用外接电源的程序应该一样吧,不用什么改动吧
点赞  2008-6-18 08:53

回复 4楼 的帖子

这个程序的最大问题在于延时,

其实 1 总线的每一位读写时序要求比较高,所以在读写每一位的时候,不能被其他程序中断。

楼主的两个程序都没有使用中断,而按照论坛高手 农讲所所长 的说法,没有中断的单片机不是一个完全的单片机。

即使这两个程序在线路板上运行的如何好。如程序要增加中断,所有函数都要重新测试一番,
点赞  2008-6-18 08:54

回复 5楼 的帖子

首先,要谢谢大家,这样一起交流才有意思啊,大家才会有进步!更要谢谢14楼的兄弟!

14楼: 最大问题

这个程序的最大问题在于延时,

其实 1 总线的每一位读写时序要求比较高,所以在读写每一位的时候,不能被其他程序中断。

楼主的两个程序都没有使用中断,而按照论坛高手 农讲所所长 的说法,没有中断的单片机不是一个完全的单片机。

即使这两个程序在线路板上运行的如何好。如程序要增加中断,所有函数都要重新测试一番,


说的很好,下次改进,呵呵……

希望大家继续批判,也希望大家能共享好的资料啊!
点赞  2008-6-18 08:55
LZ是好人!大好人!
点赞  2008-11-24 21:26
好经验,谢谢分享,
点赞  2008-11-27 13:34

Re: [分享] DS18B20 测温程序完全解读

好经验,急需,感谢分享,
点赞  2008-11-27 21:29

Re: [分享] DS18B20 测温程序完全解读

没有汇编的吗?:'( :'( :'(
点赞  2008-12-15 13:22

Re: [分享] DS18B20 测温程序完全解读

学习学习,最近可能要用到
点赞  2008-12-15 13:32

Re: [分享] DS18B20 测温程序完全解读

太感谢了!!!!!!!!!!!!
点赞  2009-1-24 10:23

回复 4楼 dz851210 的帖子

资料上有写,18B20在寄生电源模式对总线的占用时间较长,且在100摄氏度以上时漏电流较大,在一条总线上挂多个传感器时好像不太推荐.
点赞  2009-1-26 19:39
关注            学习
------------------------------------
点赞  2010-2-2 21:16
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复