单总线协议意思就是一个线就可以完成数据的发送和接收。IIc通信有两根线:SCL时钟线和SDA数据线,通过两根线的完美配合完成读写操作。具体可以参考第一篇文章。SPI通信有四根线:片选线,时钟线,主机输入线,从机写入线。单总线就是只有一根线。这跟线需要完成读和写的操作,通过模拟时序来完成单线读写,通过这根线的电平高低以及延时时间的情况来告知从机下一步的动作。这里结合DS18B20温度传感器来介绍单线协议
DS18b20必懂知识点():
DS18B20内部主要包括,64位ROM(只读储存器)、SCRATCHPAD(暂存寄存器)。其中暂存寄存器包括五部分,详细请看下图:
64位ROM(只读寄存器)
rom中的64位序列号在出厂前就被刻好,独一无二,可以看作器件的身份证,主机通过这个独特的信息决定当多个DS181B20构成多节点系统时通信的对象。
暂存寄存器,顾名思义信息被暂时存储在这里边,等待主机的读取,传感器温度转换完毕后就存储在暂存器的前两个字节(温度寄存器),寄存器的第二字节和第三字节分别存储高温限值和低温限值。第四字节是配置寄存器,用来设定传感器的精度。另外需要注意一点就是第二字节(TH),第三字节(TL)以及第四字节(配置寄存器)可以与可擦除EEPROM交换数据
温度寄存器
其中,高八位的前五位是符号位,如果在配置寄存器中设置精度为9位,那么第0位,第1位,第2位无效。如果设置精度为10位,那么第1位,第二位无效,以此类推一直到12位精度。DS18B20在读取温度值并转换为数字量之后就把数据储存在这两个字节里边。等待主机发送储存器指令来读取。
TH和TL限温报警寄存器(注意是有两个)
TH和TL中分别存储温度的限位,一个是高温度一个是低温,当温度处于这这个范围之外的时候就会报警。这两个温度需要主机来写入,首先主机需要在数据线上写4EH,就是写寄存器的命令,从机检测到这个信号后就知道主机开始往内部的RAM(高速暂存寄存器)的第三字节和第四字节写数据了,从机做好准备接受数据,主机也在发送外这条命令之后就开始发送两个字节(就是高温限位和低温限位)。
S也是一个符号位,如果被测温度处于这两个温度之外就会出现报警条件。主机可以通过发送ECH指令检查所有温度转换器的报警标志状态。当设备断电之后这两个字节会保留下来。
-配置寄存器
通过这个寄存器的R1和R2可以设置分辨率,DS18B20在上电后默认是12位分辨率。
下面介绍主机发生的命令库
-发送ROM指令
这里做个说明,每一个DS18B20都有自己独特的64位光刻ROM,主机为了确定与谁通信就用到了ROM指令。如下:
1)发送33H命令,把所有的器件ROM都读到
2)发送55H,这个指令发送之后,接着发送一个64位编码,从机开始与自己的身份对比。
如果只用到了一个器件,那么就不需要这个过程,直接跳过ROM指令过程。
准备工作完毕,我们来说说主机与DS18B20的数据转化的全过程
2.发送ROM命令
3.发送储存器命令
下面问题又来了,我们一直在说往总线发送ROM指令,发送RAM指令。主机是怎么发送指令的呐?RAM指令中还有读取暂存器的命令,暂存器是怎么样放送数据到总线的呐?
下边我们来看看读/写时许
-写时许图
芯片资料说到不管是写1还是写0还是写1,总线在每一位数据写入之前都是高电平,从上边可以看出来。左半部分是写0时许,右半部反是写1时许。如果主机往总线写0,主机把总线拉低到低电平最少60微秒。如果要写1,主机把总线拉低15微秒后拉高总线。这里注意一下,DS18B20就是通过主机每一个写位周期的15-45微秒这段时间的总线状态知道主机要给它传送的数据。(你可以想成每一个周期的前15微秒是主机起始信号的时间,当从机检测到主机总线拉低了15微秒这个信号之后就知道主机允许从机通过总线接受数据了。)
下面是写一个字节的程序,供参考
void tempwritebyte(uchar dat)//向传感器写一个字节数据函数
{
uint i;
uchar j;
bit testb;
for(j=1;j<=8;j++)
{
testb=dat&0x01;
dat=dat>>1;
if(testb)
{
ds=0;
i++;i++;
ds=1;
i=8;
while(i>0)
i--;
}
else
{
ds=0;
i=8;
while(i>0)
i--;
ds=1;//写0之后,上边的延时已经让从机接受到了主机发来的数据,这里就是为下一次写循环做准备,从时许图上可以看出每一次主线都是从高电平开始的。
i++;i++;
}
}
}
与前边一样,这是两个循环,分别是读0和读1;不同的是在从机检测到主机把总线电平下拉数微秒之后,不是主机来操作总线而是从机来操作总线。
这个图我刚看到的时候一头雾水怎么样也想不通。告诉从机可以往总线发数据的信号是主机把总线拉低>1微秒之后释放总线。从机在检测主机把总线拉低大于1微秒之后就开始往总线发送数据,这个时候主动权就交给了从机,如果从机想发送1,就把总线拉高然后延时到循环结束。反之就是拉低到循环结束。
下面有一个跟IIc一样,容易忽视的点。
-读一个字节的代码
it tempreadbit(void)//读一位数据函数
{
uint i;
bit dat;
ds=0;i++;
ds=1;i++;i++;
dat=ds;//上边那行代码是把总线拉高,那只是一个释放总线的操作,在释之
//后延时的这段时间,没有其他代码指令,因为是在等从机操作总线。然后结
//束,从机已经把数据送到了总线上,然后这句代码就是读取从机送来的数据特
//别像IIC中主机读数据的逻辑。IIc中读数据时,我们实现把数据线拉高,让机
//释放了数据线(把主动权交给从机),然后延时了5微秒,其实这是从机已把
//数据送给了数据线SDA,所以这是在for循环中就会判断数据线的状态。可以看
//IIc那篇文章小标(9)读数据代码的第6行和第12行
i=8;
while(i>0)
i--;
return(dat);
}
uchar tempread(void)
{
uchar i,j,dat;
dat=0;
for(i=1;i<=8;i++)
{
j=tempreadbit();
dat=(j<<7)|(dat>>1);
}
return(dat);
}
好了终于编辑完了,今天比较忙,中午想清楚之后就开始编辑,一直到现在,边想边写,写了有3个小时,里边把我昨天所有的疑惑都做了解答,我以后在看到的时候肯定会明白,不知道对网友有没有帮助。
这里贴上郭天祥的温度传感器报警装置的代码(我的班子是QX-MSC51,所以略有改动)
#include
#include
#define uchar unsigned char
#define uint unsigned int
sbit ds=P2^2;
sbit dula=P2^6;
sbit wela=P2^7;
sbit beep=P1^7;
uint temp;
float f_temp;
uint warn_l1=270;
uint warn_l2=250;
uint warn_h1=300;
uint warn_h2=320;
sbit led0=P1^0;
sbit led1=P1^1;
sbit led2=P1^2;
sbit led3=P1^3;
uchar code table[]={
0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,//带小数点0-9
0xbf,0x86,0xdb,0xcf,0xe6,0xed,0xfd,0x87,0xff,0xef};//不带小数0-9
void delay(uint num_time)
{
uint i,j;
for(i=num_time;i>0;i--)
for(j=110;j>0;j--);
}
void dereset(void)//DS18B20复位。初始化函数
{
uint i;
ds=0;
i=30;
while(i>0)
i--;
ds=1;
i=4;
while(i>0)
i--;
}
bit tempreadbit(void)//读一位数据函数
{
uint i;
bit dat;
ds=0;i++;
ds=1;i++;i++;
dat=ds;
i=8;
while(i>0)
i--;
return(dat);
}
uchar tempread(void)
{
uchar i,j,dat;
dat=0;
for(i=1;i<=8;i++)
{
j=tempreadbit();
dat=(j<<7)|(dat>>1);
}
return(dat);
}
void tempwritebyte(uchar dat)//向传感器写一个字节数据函数
{
uint i;
uchar j;
bit testb;
for(j=1;j<=8;j++)
{
testb=dat&0x01;
dat=dat>>1;
if(testb)
{
ds=0;
i++;i++;
ds=1;
i=8;
while(i>0)
i--;
}
else
{
ds=0;
i=8;
while(i>0)
i--;
ds=1;
i++;i++;
}
}
}
void tempchange(void)//DS18B20开始获取温度并转换
{
dereset();
delay(1);
tempwritebyte(0xcc);
tempwritebyte(0x44);
}
uint get_temp()
{
uchar a,b;
dereset();
delay(1);
tempwritebyte(0xcc);//跳过ROM
tempwritebyte(0xbe);//读暂存器
a=tempread();
b=tempread();
temp=b;
temp<<=8;
temp=temp|a;
f_temp=temp*0.0625;
temp=f_temp*10+0.5;
f_temp=f_temp+0.05;
return temp;
}
void display(uchar num,uchar dat)
{
uchar i;
dula=0;
P0=table[dat];
dula=1;
dula=0;
wela=0;
i=0xFF;
i=i&(~((0x01)<<(num)));
P0=i;
wela=1;
wela=0;
delay(1);
}
/*void display(uint num_tem)
{
uint shi,ge,xiaoshu;
shi=num_tem/100;
ge=(num_tem%100)/10;
xiaoshu=num_tem%10;
dula=1;
P0=table[shi+10];
dula=0;
P0=0xff;
wela=1;
P0=0xfe;
wela=0;
delay(5);
dula=1;
P0=table[ge];
dula=0;
P0=0xff;
wela=1;
P0=0xfd;
wela=0;
delay(5);
dula=1;
P0=table[xiaoshu+10];
dula=0;
P0=0xff;
wela=1;
P0=0xfb;
wela=0;
delay(5);
}
void dangwei(uint rank_num)//在数码管上显示0-5挡
{
dula=1;
P0=table[rank_num+10];
dula=0;
P0=0xff;
wela=1;
P0=0xef;
wela=0;
delay(5);
}
*/
void dis_temp(uint t)
{
uchar i;
i=t/100;
display(0,i);
i=t%100/10;
display(1,i+10);
i=t%100%10;
display(2,i);
}
void warn(uint s,uchar led)
{
uchar i;i=s;
beep=0;
P1=~(led);
while(i--)
{
dis_temp(get_temp());
}
beep=1;
P1=0xFF;
i=s;
while(i--)
{
dis_temp(get_temp());
}
}
void deal(uint t)//温度处理
{
uchar i;
if((t>warn_l2)&&(t<=warn_l1))
{
warn(40,0x01);
}
else if(t<warn_l2)
{
warn(10,0x03);
}
else if(t<warn_h2&&t>warn_h1)
{
warn(10,0x04);
}
else if(t>=warn_h2)
{
warn(10,0x0c);
}
else
{
i=40;
while(i--)
{
dis_temp(get_temp());
}
}
}
void init_com(void)//串口初始化
{
TMOD=0x20;
PCON=0x00;
SCON=0x50;
TH1=0xFD;
TL1=0xfd;
TR1=1;
}
void comm(char *parr)
{
do
{
SBUF=*parr++;
while(!TI)
{
TI=0;
}
}while(*parr);
}
void main()
{
uchar i;
uchar buff[4];
dula=0;
wela=0;
init_com();
while(1)
{
tempchange();
for(i=10;i>0;i--)
{
dis_temp(get_temp());
}
deal(temp);
sprintf(buff,"%f",f_temp);
for(i=10;i>0;i--)
{
dis_temp(get_temp());
}
comm(buff);
for(i=10;i>0;i--)
{
dis_temp(get_temp());
}
}
}
这里还有串口通讯的东西,就是传感器读到的温度转换之后传给了上位机。
串口通信以及SPI通信我们交给下几次总结。