//例Y15.3
//该程序的功能是演示当单总线上连有多片18B20时,对它们进行以下与搜索有关德操作:
//搜索1--搜索单总线上指定的芯片家族(需提供1B家族号);
//搜索2--搜索单总线上的所有器件(本例假定网络中只存在18B20数字测温芯片);
//演示程序先搜索家族号为0x28的器件(即18B20),然后启动所有18B20进行测温转换,并延时750ms等待转换完成。
//之后,一一搜索总线上的器件，每搜到一个则安排2种输出:
//输出1--数码管输出,格式为"[-]xxx.xx",为了让用户看到不同器件的测温数据,程序每秒只搜索1个器件。
//输出2--用printf()实现的串口输出,硬件仿真时数据将出现在串口窗口1,数据内容如下:
//8字节ID(MSB位于最左端),位置1(事前写入TH单元),位置2(事前写入TL单元),当前温度(12位精度)。
//该程序假定总线上所有18B20事先已经设置了位置1,位置2和12位的转换精度。
//Program Size: data=64.1 xdata=0 code=3661(Small)
//Program Size: data=15.1 xdata=49 code=3741(compact)
//Program Size: data=15.1 xdata=74 code=3854(Large)
#include <reg51.h>
#include <absacc.h>
#include <intrins.h>
#include <stdio.h>
#define	uchar unsigned char
#define	uint unsigned int
#define	ON	0
#define	OFF	1
#define	TRUE	1
#define	FALSE	0
#define	skiprom_cmd1820();		wr_byte1820(0xcc);		//跳过ROM操作(芯片识别)
#define	rdrom_cmd1820();		wr_byte1820(0x33);		//拟读ROM(器件ID)
#define	searchrom_cmd1820();	wr_byte1820(0xf0);		//拟搜索总线上的18B20
#define	matchrom_cmd1820();	wr_byte1820(0x55);			//给出ID以匹配总线上指定的18B20
#define	convert_cmd1820();		wr_byte1820(0x44);		//启动测温转换
#define	wrscrat_cmd1820();		wr_byte1820(0x4e);		//拟写高速暂存
#define	rdscrat_cmd1820();		wr_byte1820(0xbe);		//拟读高速暂存
#define	copyscrat_cmd1820();	wr_byte1820(0x48);		//将高速暂存复制到EEPROM保存
#define	callscrat_cmd1820();	wr_byte1820(0xb8);		//从EEPROM调出保存数据到高速暂存

sbit DQ_1820=P1^0;									//18B20的DQ端
sbit DAT_164=P3^4;									//74HC164的串入端
sbit CLK_164=P3^5;									//74HC164的时钟端
sbit BEEP=P1^3;										//连接有源蜂鸣器
uchar code seg[]={									//七段码表
0x3f,0x06,0x5b,0x4f,0x66,							//0~4
0x6d,0x7d,0x07,0x7f,0x6f,							//5~9
0x77,0x7c,0x39,0x5e,0x79,							//A~E
0x71,0x40,0x46,0x00,0x76,							//F,-,-1,全灭,H
0xbf,0x86,0xdb,0xcf,0xe6,							//0.~4.
0xed,0xfd,0x87,0xff,0xef							//5.~9.
};
uchar code crc_tab[256]={							//8位CRC校验数据表
  0, 94,188,226, 97, 63,221,131,194,156,126, 32,163,253, 31, 65,
157,195, 33,127,252,162, 64, 30, 95,  1,227,189, 62, 96,130,220,
 35,125,159,193, 66, 28,254,160,225,191, 93,  3,128,222, 60, 98,
190,224,  2, 92,223,129, 99, 61,124, 34,192,158, 29, 67,161,255,
 70, 24,250,164, 39,121,155,197,132,218, 56,102,229,187, 89,  7,
219,133,103, 57,186,228,  6, 88, 25, 71,165,251,120, 38,196,154,
101, 59,217,135,  4, 90,184,230,167,249, 27, 69,198,152,122, 36,
248,166, 68, 26,153,199, 37,123, 58,100,134,216, 91,  5,231,185,
140,210, 48,110,237,179, 81, 15, 78, 16,242,172, 47,113,147,205,
 17, 79,173,243,112, 46,204,146,211,141,111, 49,178,236, 14, 80,
175,241, 19, 77,206,144,114, 44,109, 51,209,143, 12, 82,176,238,
 50,108,142,208, 83, 13,239,177,240,174, 76, 18,145,207, 45,115,
202,148,118, 40,171,245, 23, 73,  8, 86,180,234,105, 55,213,139,
 87,  9,235,181, 54,104,138,212,149,203, 41,119,244,170, 72, 22,
233,183, 85, 11,136,214, 52,106, 43,117,151,201, 74, 20,246,168,
116, 42,200,150, 21, 75,169,247,182,232, 10, 84,215,137,107, 53
};
uchar disbuf[6];					//数码管显示缓冲区
union								//利用该结构来分离测温数据中的高/低8位
{ int i;
  uchar c[2];
}temp;								//存温度数据
uchar scratbuf[9];					//存从18B20高速缓存中读取的9B数据
uchar ROM_NO[8];
uchar last_discr_index;				//最近一次发生分支搜索的位置,初值=0,一般=1~64
uchar last_fam_discr_index;			//在器件家族码中最近一次发生分支搜索的位置,初值=0,一般=1~8
bit is_last_device;					//搜索到最后一个器件了吗,初值=FALSE
uchar crc8;							//进行CRC8校验用

void delay_10us(uchar n)			//10微秒级延时
{ do
  { _nop_();
    _nop_();
    _nop_();
    _nop_();
    _nop_();
  }while(--n); 
}
void delay_ms(uint n)					//毫秒级延时
{ do delay_10us(131);
  while(--n);
}
void sendchar(uchar ch)					//向数码管送1位显示数码
{ uchar i;
  for(i=0;i<8;i++)
  { DAT_164=ch&0x01;
    CLK_164=0;	
    CLK_164=1;
    ch>>=1;
  }
}
void display()							//将缓冲区数据送数码管显示
{ uchar i;
  for(i=0;i<6;i++) sendchar(seg[disbuf[i]]);
}
void reset_1820()							//复位18B20
{ DQ_1820=0;
  delay_10us(60);							//延时约500uS(要求>480us)
  DQ_1820=1;								//主机上拉DQ_1820
  delay_10us(7);							//延时约60uS
  while(!DQ_1820);							//等18B20送出0
}
void wr_bit1820(bit b)						//1数据写入18B20
{ DQ_1820=0;
  delay_10us(2);							//延时约20uS
  if(b) DQ_1820=1;							//发送1否则发送0
  delay_10us(7);							//延时约60uS
  DQ_1820=1;								//主机上拉DQ_1820
}
void wr_byte1820(uchar dat)					//1字节数据写入18B20
{ uchar i;
  for(i=0;i<8;i++)							//依次发送8位数据
  { wr_bit1820(dat&0x01);
    dat>>=1;
  }
}
bit rd_bit1820()							//从18B20读1位数据
{ DQ_1820=0;								//主机下拉DQ_1820
  DQ_1820=1;								//主机上拉DQ_1820
  delay_10us(2);							//满足延时要求约20uS
  return DQ_1820;							//返回读取的位数据
}
uchar rd_byte1820()						//从18B20读1字节数据
{ uchar i,dat=0;
  for(i=0;i<8;i++)							//依次接收8位数据
  { dat>>=1;
    if(rd_bit1820()) dat|=0x80;				//读取位数据置字节高位
    delay_10us(7);							//满足延时要求58us
  }
  return dat; 								//返回读取的字节数据
}
void res1820_to_disbuf()					//处理转换结果并送显示缓冲区
{ uchar i;
  uchar tint;								//用它处理温度数据的整数部分
  uint tdec;								//用它处理温度数据的小数部分
  disbuf[0]=18;								//正数时显示空格
  if(temp.i<0)
  { disbuf[0]=16;							//显示"-"
    temp.i=-temp.i;							//负数取绝对值
  }
  tint=temp.i>>4;							//右移4位后得8位整数(3位十进制)
  disbuf[1]=tint/100;						//取百位上数据
  disbuf[2]=tint/10%10;						//取十位上数据
  disbuf[3]=tint%10+20;						//取个位上数据并加小数点
  i=1;										//消隐数码显示前导0
  while(disbuf[i]==0) disbuf[i++]=18;
  tdec=temp.c[1]&0x0f;						//得4位小数，最低位权重=0.0625
  tdec=tdec*625+50;							//小数作整数处理避免浮点运算(保留2位小数)
  disbuf[4]=tdec/1000;						//取十分之一位数据
  disbuf[5]=tdec/100%10;					//取百分之一位数据
}
uchar do_crc8(uchar dat)					//8位CRC校验,p指向校验数据,n为字节数
{ crc8=crc_tab[crc8^dat];					//连续查表计算CRC
  return crc8;
}
bit search_1wire()							//在给定条件下搜索总线上的1个器件,得到其ID
{ uchar cur_bit_index;						//当前搜索位的位置(1~64)
  uchar last_zero_index;					//最近一次搜索0分支的位置
  uchar rom_byte_index;						//当前搜索的字节位置(0~7)
  uchar rom_byte_mask;						//当前使用的字节掩码
  bit search_secc;							//搜索成败标志
  bit id_bit;								//主机第1次读总线上的位数据(从机第1次送位数据)
  bit cmp_id_bit;							//主机第2次读总线上的位数据(从机第2次送反码位数据)
  bit search_dir;							//主机写搜索方向(符合方向的从机响应,其余脱离搜索)

  if(!is_last_device)						//若未搜索到最后一个器件则继续新一轮搜索
  { cur_bit_index=1;						//搜索初值从第1位开始
    last_zero_index=0;						//最近一次搜索0分支的位置,初值=0
    rom_byte_index=0;						//当前搜索的字节位置(0~7),初值=0
    rom_byte_mask=1;						//当前使用的字节掩码,初值=1
    crc8=0;									//CRC8校验初值=0
    reset_1820();							//复位
    searchrom_cmd1820();					//发命令开始搜索作业

    do										//每次循环搜索1个器件
    { id_bit=rd_bit1820();					//读从机发来的第1位数据
      cmp_id_bit=rd_bit1820();					//读从机发来的第2位数据(第1位数据的反码)
      if(id_bit==1&&cmp_id_bit==1) break;		//收到11,说明总线上无设备
      else
      { if(id_bit!=cmp_id_bit)
           search_dir=id_bit;					//收到01/10,说明从机该位相同搜索方向为id_bit
        else									//收到00,说明该处有分支
        { if(cur_bit_index<last_zero_index)		//分支发生在前次走0分支之前
             search_dir=((ROM_NO[rom_byte_index]&rom_byte_mask)>0);	//搜索走原路线
          else									//分支发生在前次走0分支处或发生在前次走0分支之后
          { if(cur_bit_index==last_discr_index)	//若为前者则搜索改走1分支
               search_dir=1;
            else search_dir=0;					//若为后者则搜索走0分支
          }
          if(search_dir==0)						//若搜索走0分支
          { last_zero_index=cur_bit_index;			//更新最后1次走0分支的位置
            if(last_zero_index<9)					//若搜索走0分支的位置在家族码区(1~8)
            last_fam_discr_index=last_zero_index;	//更新家族码最近1次发生分支的位置
          }
        }
        if(search_dir==1)							//若搜索走1分支,
           ROM_NO[rom_byte_index]|=rom_byte_mask;	//在ROM_NO的相应位置记录1
        else										//若搜索走0分支,
           ROM_NO[rom_byte_index]&=~rom_byte_mask;	//在ROM_NO的相应位置记录0
        wr_bit1820(search_dir);						//主机向从机写搜索方向位数据
        cur_bit_index++;							//搜索位置加1
        rom_byte_mask<<=1;							//字节掩码左移1位
        if(rom_byte_mask==0)						//若字节掩码为0,说明已搜索1个字节
        { crc8=do_crc8(ROM_NO[rom_byte_index]);
          rom_byte_index++;						//搜索字节数加1 
          rom_byte_mask=1;						//字节掩码设置到初始位置
        }
      }
    }while(rom_byte_index<8);					//不到8字节则继续搜索

    if(!(cur_bit_index<65||crc8!=0))				//该轮搜索结束校验正确
    { last_discr_index=last_zero_index;			//最后分支发生处=最后走0分支的位置
      if(last_discr_index==0)					//若最后分支发生处等于0
         is_last_device=TRUE;						//已搜索完所有器件
      if(last_fam_discr_index==last_discr_index)	//若最后分支发生处等于家族码最后分支发生处
         last_fam_discr_index=0;					//说明总线上器件家族搜索完
      search_secc=TRUE;							//搜索成功
    }
  }
  if(!search_secc||!ROM_NO[0])					//若搜索失败或器件家族码为0
  { last_discr_index=0;							//最后分支发生处=0
    is_last_device=FALSE;						//不到最后1个器件
    last_fam_discr_index=0;						//器件家族码最后分支处=0
    search_secc=FALSE;							//搜索失败
  }
  return  search_secc;							//返回本次搜索成败标志
}

void search_init()
{ last_discr_index=0;							//最后分支发生处=0
  is_last_device=FALSE;							//不到最后1个器件
  last_fam_discr_index=0;						//器件家族码最后分支处=0
}
bit search_next()								//在前次搜索的基础上接着搜索下一个器件
{ return search_1wire();
}
void set_fam_code(uchar fam_code)				//给出要搜索的器件的1B家族号
{ uchar i;
  ROM_NO[0]=fam_code;							//家族号置于ROM_NO[0]
  for(i=1;i<8;i++) ROM_NO[i]=0;
  last_discr_index=64;							//需要从头开始搜索
  last_fam_discr_index=0;						//器件家族码最后分支处=0
  is_last_device=FALSE;							//不到最后1个器件
}
void beep(uint t)								//鸣响有源蜂鸣器
{ BEEP=ON;
  delay_ms(t);
  BEEP=OFF;
}
void main()										//演示程序
{ char i;										//控制循环用
  uchar n=0;									//记录单总线上的器件个数
  bit res;										//记录搜索结果

  reset_1820();									//复位18b20
  search_init();								//搜索前对相关的全局变量进行初始化
  printf("\nFIND ONLY 0x28\n");					//提示只搜索单总线上器件号为0x28的家族
  set_fam_code(0x28);							//设置要搜索的器件家族的编号0x28
  while(search_next())							//不断搜索下一个,直到全部器件搜完
  { if(ROM_NO[0]!=0x28) break;					//不是0x28器件则跳过
    printf("n=%-3bu  ID(HEX)=MSB  ",++n);			//是0x28器件
    for(i=7;i>=0;i--)							//输出其8字节ID号(先输出MSB)
      printf("%02bx ",ROM_NO[i]);
    printf("  LSB\n");
  }

  reset_1820();									//复位18B20
  skiprom_cmd1820();							//跳过ROM操作,不识别器件
  convert_cmd1820();							//单总线上的全部18B20均启动转换
  delay_ms(750);								//延时等12位精度的测温转换完成

  printf("\nFIND ALL\n");						//搜索单总线上的所有器件(设总线器件均18B20)
  n=0;											//记录器件个数,初始为0
  res=search_next();							//搜索下个器件
  while(res)									//搜索结果为真就不断进行
  { printf("n=%-3bu  ID(HEX)=MSB  ",++n);		//输出器件序号
    for(i=7;i>=0;i--)							//从高字节到低字节输出ID号
      printf("%02bx ",ROM_NO[i]);
    printf("  LSB\n");
    reset_1820();								//复位18B20
    matchrom_cmd1820();							//通过匹配ROM操作,选定器件
    for(i=0;i<8;i++) wr_byte1820(ROM_NO[i]);	//连续写器件的ID号,则仅该器件可进行后续操作
    rdscrat_cmd1820();							//发命令拟读高速缓存
    for(i=0;i<9;i++)							//连续读高速缓存9B(最后字节为CRC8校验码)
    { scratbuf[i]=rd_byte1820();
      crc8=do_crc8(scratbuf[i]);					//进行CRC8校验
    }
    if(!crc8)										//如果校验正确
    { printf("  POS1=%3bu\n",scratbuf[2]);			//从串口#1窗口输出TH(常用作器件位置1)
      printf("  POS2=%3bu\n",scratbuf[3]);			//从串口#1窗口输出TL(常用作器件位置2)
      temp.c[0]=scratbuf[1];						//保存温度数据,有效数字12位(补码)
      temp.c[1]=scratbuf[0];
      printf(" TEMP=%8.3f\n",temp.i*0.0625);		//从串口#1窗口输出温度(0.0625度/LSB)
      res1820_to_disbuf();						//数据送显示缓冲区数据格式:[-]xxx.xx
      display();								//在数码管上显示温度
    }
    else beep(200);								//校验出错鸣笛告知
    delay_ms(1000);								//停顿1秒
    res=search_next();							//搜索下一个器件直到完成全部器件的搜索
  }
  while(1);										//程序原地踏步
}

