帕萨特b51.8t顶排汽管感应器多少钱

《手把手教你学51单片机》之十六----红外通信与DS18B20温度传感器_手把手教51单片机_ICKey电子工程师论坛-ickey电子工程师社区
发表于& 16:54:40
第16章&红外通信与DS18B20温度传感器
& & & 本章我们将学习到另外两种通信协议和使用它们的两种具体器件,分别是:使用NEC红外通信协议的遥控器和使用1-Wire总线协议的温度传感器——DS18B20。红外可以使单片机系统具备远距离的遥控能力,温度传感器则给我们提供了感知周围温度的手段,这都是非常实用且常用的扩展功能。
1.1&红外光的基本原理
& & & 红外线是波长介于微波和可见光之间的电磁波,波长在760纳米到1毫米之间,是波形比红光长的非可见光。自然界中的一切物体,只要它的温度高于绝对零度(-273)就存在分子和原子的无规则运动,其表面就会不停的辐射红外线。当然了,虽然是都辐射红外线,但是不同的物体辐射的强度是不一样的,而我们正是利用了这一点把红外技术应用到我们的实际开发中。
& & &&红外发射管很常用,在我们的遥控器上都可以看到,它类似发光二极管,但是它发射出来的是红外光,是我们肉眼所看不到的。第2章我们学过发光二极管的亮度会随着电流的增大而增加,同样的道理,红外发射管发射红外线的强度也会随着电流的增大而增强,常见的红外发射管如图16-1所示。
图16-1&&红外发射管
& & & 红外接收管内部是一个具有红外光敏感特征的PN节,属于光敏二极管,但是它只对红外光有反应。无红外光时,光敏管不导通,有红外光时,光敏管导通形成光电流,并且在一定范围内电流随着红外光的强度的增强而增大。典型的红外接收管如图16-2所示。
图16-2&&红外接收管
& & & 这种红外发射和接收对管在小车、机器人避障以及红外循迹小车中有所应用,这部分内容在我们的KST-51开发板上并没有实现,但是属于红外部分的内容,所以我提供一个原理图给大家作为学习之用,如图16-3所示。
图16-3&&红外避障、循迹原理图
& & & 在图16-3中,发射控制和接收检测都是接到单片机的IO口上的。
& & & 发射部分:当发射控制输出高电平时,三极管Q1不导通,红外发射管L1不会发射红外信号;当发射控制输出低电平的时候,通过三极管Q1导通让L1发出红外光。
& & &&接收部分:R4是一个电位器,我们通过调整电位器给LM393的2脚提供一个阈值电压,这个电压值的大小可以根据实际情况来调试确定。而红外光敏二极管L2收到红外光的时候,会产生电流,并且随着红外光的从弱变强,电流会从小变大。当没有红外光或者说红外光很弱的时候,3脚的电压就会接近VCC,如果3脚比2脚的电压高的话,通过LM393比较器后,接收检测引脚输出一个高电平。当随着光强变大,电流变大,3脚的电压值等于VCC-I*R3,电压就会越来越小,当小到一定程度,比2脚的电压还小的时候,接收检测引脚就会变为低电平。
& & &&这个电路用于避障的时候,发射管先发送红外信号,红外信号会随着传送距离的加大逐渐衰减,如果遇到障碍物,就会形成红外反射。当反射回来的信号比较弱时,光敏二极管L2接收的红外光较弱,比较器LM393的3脚电压高于2脚电压,接收检测引脚输出高电平,说明障碍物比较远;当反射回来的信号比较强,接收检测引脚输出低电平,说明障碍物比较近了。
& & & 用于小车循迹的时候,必须要有黑色和白色的轨道。当红外信号发送到黑色轨道时,黑色因为吸光能力比较强,红外信号发送出去后就会被吸收掉,反射部分很微弱。白色轨道就会把大部分红外信号反射回来。通常情况下的循迹小车,需要应用多个红外模块同时检测,从多个角度判断轨道,根据判断的结果来调整小车使其按照正常循迹前行。
1.2&红外遥控通信原理
& & & 在实际的通信领域,发出来的信号一般有较宽的频谱,而且都是在比较低的频率段分布大量的能量,所以称之为基带信号,这种信号是不适合直接在信道中传输的。为便于传输、提高抗干扰能力和有效的利用带宽,通常需要将信号调制到适合信道和噪声特性的频率范围内进行传输,这就叫做信号调制。在通信系统的接收端要对接收到的信号进行解调,恢复出原来的基带信号。这部分通信原理的内容,大家了解一下即可。
& & &&我们平时用到的红外遥控器里的红外通信,通常是使用38K左右的载波进行调制的,下面我把原理大概给大家介绍一下,先看发送部分原理。
& & & 调制:就是用待传送信号去控制某个高频信号的幅度、相位、频率等参量变化的过程,即用一个信号去装载另一个信号。比如我们的红外遥控信号要发送的时候,先经过38K调制,如图16-4所示。
图16-4&&红外信号调制
& & & 原始信号就是我们要发送的一个数据“0”位或者一位数据“1”位,而所谓38K载波就是频率为38K的方波信号,调制后信号就是最终我们发射出去的波形。我们使用原始信号来控制38K载波,当信号是数据“0”的时候,38K载波毫无保留的全部发送出去,当信号是数据“1”的时候,不发送任何载波信号。
那在原理上,我们如何从电路的角度去实现这个功能呢?如图16-5所示。
图16-5&&红外发射原理图
& & & 38K载波,我们可以用455K晶振,经过12分频得到37.91K,也可以由时基电路NE555来产生,或者使用单片机的PWM来产生。当信号输出引脚输出高电平时,Q2截止,不管38K载波信号如何控制Q1,右侧的竖向支路都不会导通,红外管L1不会发送任何信息。当信号输出是低电平的时候,那么38K载波就会通过Q1释放出来,在L1上产生38K的载波信号。这里要说明的是,大多数家电遥控器的38K的占空比是1/3,也有1/2的,但是相对少一些。
& & & 正常的通信来讲,接收端要首先对信号通过监测、放大、滤波、解调等等一系列电路处理,然后输出基带信号。但是红外通信的一体化接收头HS0038B,已经把这些电路全部集成到一起了,我们只需要把这个电路接上去,就可以直接输出我们所要的基带信号了,如图16-6所示。
图16-6&&红外接收原理图
& & & 由于红外接收头内部放大器的增益很大,很容易引起干扰,因此在接收头供电引脚上必须加上滤波电容,官方手册给的值是4.7uF,我们这里直接用的10uF,手册里还要求在供电引脚和电源之间串联100欧的电阻,进一步降低干扰。
& & & 图16-6所示的电路,用来接收图16-5电路发送出来的波形,当HS0038B监测到有38K的红外信号时,就会在OUT引脚输出低电平,当没有38K的时候,OUT引脚就会输出高电平。那我们把OUT引脚接到单片机的IO口上,通过编程,就可以获取红外通信发过来的数据了。
& & &&大家想想,OUT引脚输出的数据是不是又恢复成为基带信号数据了呢?那我们单片机在接收这个基带信号数据的时候,如何判断接收到的是什么数据,应该遵循什么协议呢?像我们前边学到的UART、I2C、SPI等通信协议都是基带通信的通信协议,而红外的38K仅仅是对基带信号进行调制解调,让信号更适合在信道中传输。
& & &&由于我们的红外调制信号是半双工的,而且同一时刻空间只能允许一个信号源,所以红外的基带信号不适合在I2C或者SPI通信协议中进行的,我们前边提到过UART虽然是2条线,但是通信的时候,实际上一条线即可,所以红外可以在UART中进行通信。当然,这个通信也不是没有限制的,比如在HS0038B的数据手册中标明,要想让HS0038B识别到38K的红外信号,那么这个38K的载波必须要大于10个周期,这就限定了红外通信的基带信号的比特率必须不能高于3800,那如果把串口输出的信号直接用38K调制的话,波特率也就不能高于3800。当然还有很多其它基带协议可以利用红外来调制,下面我们介绍一种遥控器常用的红外通信协议——NEC协议。
1.3&NEC协议红外遥控器
& & & 家电遥控器通信距离往往要求不高,而红外的成本比其它无线设备要低的多,所以家电遥控器应用中红外始终占据着一席之地。遥控器的基带通信协议很多,大概有几十种,常用的就有ITT协议、NEC协议、Sharp协议、Philips&RC-5协议、Sony&SIRC协议等。用的最多的就是NEC协议了,因此我们KST-51开发板配套的遥控器直接采用NEC协议,我们这节课也以NEC协议标准来讲解一下。
& & & NEC协议的数据格式包括了引导码、用户码、用户码(或者用户码反码)、按键键码和键码反码,最后一个停止位。停止位主要起隔离作用,一般不进行判断,编程时我们也不予理会。其中数据编码总共是4个字节32位,如图16-7所示。第一个字节是用户码,第二个字节可能也是用户码,或者是用户码的反码,具体由生产商决定,第三个字节就是当前按键的键数据码,而第四个字节是键数据码的反码,可用于对数据的纠错。
图16-7&&NEC协议数据格式
& & & 这个NEC协议,表示数据的方式不像我们之前学过的比如UART那样直观,而是每一位数据本身也需要进行编码,编码后再进行载波调制。
引导码:9ms的载波+4.5ms的空闲。
比特值“0”:560us的载波+560us的空闲。
比特值“1”:560us的载波+1.68ms的空闲。
& & &&结合图16-7我们就能看明白了,最前面黑乎乎的一段,是引导码的9ms载波,紧接着是引导码的4.5ms的空闲,而后边的数据码,是众多载波和空闲交叉,它们的长短就由其要传递的具体数据来决定。HS0038B这个红外一体化接收头,当收到有载波的信号的时候,会输出一个低电平,空闲的时候会输出高电平,我们用逻辑分析仪抓出来一个红外按键通过HS0038B解码后的图形来了解一下,如图16-8所示。
图16-8&&红外遥控器按键编码
& & & 从图上可以看出,先是9ms载波加4.5ms空闲的起始码,数据码是低位在前,高位在后,数据码第一个字节是8组560us的载波加560us的空闲,也就是0x00,第二个字节是8组560us的载波加1.68ms的空闲,可以看出来是0xFF,这两个字节就是用户码和用户码的反码。按键的键码二进制是0x0C,反码就是0xF3,最后跟了一个560us载波停止位。对于我们的遥控器来说,不同的按键,就是键码和键码反码的区分,用户码是一样的。这样我们就可以通过单片机的程序,把当前的按键的键码给解析出来。
& & &&我们前边学习中断的时候,学到51单片机有外部中断0和外部中断1这两个外部中断。我们的红外接收引脚接到了P3.3引脚上,这个引脚的第二功能就是外部中断1。在寄存器TCON中的bit3和bit2这两位,是和外部中断1相关的两位。其中IE1是外部中断标志位,当外部中断发生后,这一位被自动置1,和定时器中断标志位TF相似,进入中断后会自动清零,也可以软件清零。bit2是设置外部中断类型的,如果bit2为0,那么只要P3.3为低电平就可以触发中断,如果bit2为1,那么P3.3从高电平到低电平的下降沿发生才可以触发中断。此外,外部中断1使能位是EX1。那下面我们就把程序写出来,使用数码管把遥控器的用户码和键码显示出来。
& & &&Infrared.c文件主要是用来检测红外通信的,当发生外部中断后,进入外部中断,通过定时器1定时,首先对引导码判断,而后对数据码的每个位逐位获取高低电平的时间,从而得知每一位是0还是1,最终把数据码解出来。虽然最终实现的功能很简单,但因为编码本身的复杂性,使得红外接收的中断程序在逻辑上显得就比较复杂,那么我们首先提供出中断函数的程序流程图,大家可以对照流程图来理解程序代码,如图16-9所示。
图16-9&&红外接收程序流程图
/***************************Infrared.c文件程序源代码*****************************/
#include&&reg52.h&
sbit&IR_INPUT&=&P3^3;&&//红外接收引脚
bit&irflag&=&0;&&//红外接收标志,收到一帧正确数据后置1
unsigned&char&ircode[4];&&//红外代码接收缓冲区
/*&初始化红外接收功能&*/
void&InitInfrared()
&&&&IR_INPUT&=&1;&&//确保红外接收引脚被释放
&&&&TMOD&&=&0x0F;&&//清零T1的控制位
&&&&TMOD&|=&0x10;&&//配置T1为模式1
&&&&TR1&=&0;&&&&&&&&//停止T1计数
&&&&ET1&=&0;&&&&&&&//禁止T1中断
&&&&IT1&=&1;&&&&&&&//设置INT1为负边沿触发
&&&&EX1&=&1;&&&&&&&//使能INT1中断
/*&获取当前高电平的持续时间&*/
unsigned&int&GetHighTime()
&&&&TH1&=&0;&&//清零T1计数初值
&&&&TL1&=&0;
&&&&TR1&=&1;&&//启动T1计数
&&&&while&(IR_INPUT)&&//红外输入引脚为1时循环检测等待,变为0时则结束本循环
&&&&&&&&if&(TH1&&=&0x40)
&&&&&&&&{&&&&&&&&&&&&&//当T1计数值大于0x4000,即高电平持续时间超过约18ms时,
&&&&&&&&&&&&&&&//强制退出循环,是为了避免信号异常时,程序假死在这里。
&&&&TR1&=&0;&&//停止T1计数
&&&&return&(TH1*256&+&TL1);&&//T1计数值合成为16bit整型数,并返回该数
/*&获取当前低电平的持续时间&*/
unsigned&int&GetLowTime()
&&&&TH1&=&0;&&//清零T1计数初值
&&&&TL1&=&0;
&&&&TR1&=&1;&&//启动T1计数
&&&&while&(!IR_INPUT)&&//红外输入引脚为0时循环检测等待,变为1时则结束本循环
&&&&&&&&if&(TH1&&=&0x40)
&&&&&&&&{&&&&&&&&&&&&&//当T1计数值大于0x4000,即低电平持续时间超过约18ms时,
&&&&&&&&&&&&&&&//强制退出循环,是为了避免信号异常时,程序假死在这里。
&&&&TR1&=&0;&&//停止T1计数
&&&&return&(TH1*256&+&TL1);&&//T1计数值合成为16bit整型数,并返回该数
/*&INT1中断服务函数,执行红外接收及解码&*/
void&EXINT1_ISR()&interrupt&2
&&&&unsigned&char&i,&j;
&&&&unsigned&char&
&&&&unsigned&int&
&&&&//接收并判定引导码的9ms低电平
&&&&time&=&GetLowTime();
&&&&if&((time&7833)&||&(time&8755))&&//时间判定范围为8.5~9.5ms,
&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//超过此范围则说明为误码,直接退出
&&&&&&&&IE1&=&0;&&&//退出前清零INT1中断标志
&&&&//接收并判定引导码的4.5ms高电平
&&&&time&=&GetHighTime();
&&&&if&((time&3686)&||&(time&4608))&&//时间判定范围为4.0~5.0ms,
&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//超过此范围则说明为误码,直接退出
&&&&&&&&IE1&=&0;
&&&&//接收并判定后续的4字节数据
&&&&for&(i=0;&i&4;&i++)&&//循环接收4个字节
&&&&&&&&for&(j=0;&j&8;&j++)&&//循环接收判定每字节的8个bit
&&&&&&&&&&&&//接收判定每bit的560us低电平
&&&&&&&&&&&&time&=&GetLowTime();
&&&&&&&&&&&&if&((time&313)&||&(time&718))&//时间判定范围为340~780us,
&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//超过此范围则说明为误码,直接退出
&&&&&&&&&&&&&&&&IE1&=&0;
&&&&&&&&&&&&&&&&
&&&&&&&&&&&&}
&&&&&&&&&&&&//接收每bit高电平时间,判定该bit的值
&&&&&&&&&&&&time&=&GetHighTime();
&&&&&&&&&&&&if&((time&313)&&&&(time&718))&//时间判定范围为340~780us,
&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//在此范围内说明该bit值为0
&&&&&&&&&&&&&&&&byt&&&=&1;&&&//因低位在先,所以数据右移,高位为0
&&&&&&&&&&&&}
&&&&&&&&&&&&else&if&((time&1345)&&&&(time&1751))&//时间判定范围为1460~1900us,
&&&&&&&&&&&&{&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&//在此范围内说明该bit值为1
&&&&&&&&&&&&&&&&byt&&&=&1;&&&//因低位在先,所以数据右移,
&&&&&&&&&&&&&&&&byt&|=&0x80;&//高位置1
&&&&&&&&&&&&}
&&&&&&&&&&&&else&&//不在上述范围内则说明为误码,直接退出
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&IE1&=&0;
&&&&&&&&&&&&&&&&
&&&&&&&&&&&&}
&&&&&&&&ircode[i]&=&&&//接收完一个字节后保存到缓冲区
&&&&irflag&=&1;&&//接收完毕后设置标志
&&&&IE1&=&0;&&&&&//退出前清零INT1中断标志
& & & 大家在阅读这个程序时,会发现我们在获取高低电平时间的时候做了超时判断if&(TH1&&=&0x40),这个超时判断主要是为了应对输入信号异常(比如意外的干扰等)情况的,如果不做超时判断,当输入信号异常时,程序就有可能会一直等待一个无法到来的跳变沿,而造成程序假死。
& & & 另外补充一点,遥控器的单按按键和持续按住按键发出来的信号是不同的。我们先来对比一下两种按键方式的实测信号波形,如图16-10和16-11所示。
图16-10&&红外单次按键时序图
图16-11&&红外持续按键时序图
& & & 单次按键的结果16-9和我们之前的图16-8是一样的,这个不需要再解释。而持续按键,首先会发出一个和单次按键一样的波形出来,经过大概40ms后,会产生一个9ms载波加2.25ms空闲,再跟一个停止位的波形,这个叫做重复码,而后只要你还在按住按键,那么每隔约108ms就会产生一个重复码。对于这个重复码我们的程序并没有对它单独解析,而是直接忽略掉了,这并不影响对正常按键数据的接收。如果你日后做程序时需要用到这个重复码,那么只需要再把对重复码的解析添加进来就可以了。
/*****************************main.c文件程序源代码******************************/
#include&&reg52.h&
sbit&ADDR3&=&P1^3;
sbit&ENLED&=&P1^4;
unsigned&char&code&LedChar[]&=&{&&//数码管显示字符转换表
&&&&0xC0,&0xF9,&0xA4,&0xB0,&0x99,&0x92,&0x82,&0xF8,
&&&&0x80,&0x90,&0x88,&0x83,&0xC6,&0xA1,&0x86,&0x8E
unsigned&char&LedBuff[6]&=&{&&//数码管显示缓冲区
&&&&0xFF,&0xFF,&0xFF,&0xFF,&0xFF,&0xFF
unsigned&char&T0RH&=&0;&&//T0重载值的高字节
unsigned&char&T0RL&=&0;&&//T0重载值的低字节
extern&bit&
extern&unsigned&char&ircode[4];
extern&void&InitInfrared();
void&ConfigTimer0(unsigned&int&ms);
void&main()
&&&&EA&=&1;&&&&&&&//开总中断
&&&&ENLED&=&0;&&&//使能选择数码管
&&&&ADDR3&=&1;
&&&&InitInfrared();&&&//初始化红外功能
&&&&ConfigTimer0(1);&&//配置T0定时1ms
&&&&//PT0&=&1;&&&&&&&&&//配置T0中断为高优先级,启用本行可消除接收时的闪烁
&&&&while&(1)
&&&&&&&&if&(irflag)&&//接收到红外数据时刷新显示
&&&&&&&&&&&&irflag&=&0;
&&&&&&&&&&&&LedBuff[5]&=&LedChar[ircode[0]&&&&4];&&//用户码显示
&&&&&&&&&&&&LedBuff[4]&=&LedChar[ircode[0]&0x0F];
&&&&&&&&&&&&LedBuff[1]&=&LedChar[ircode[2]&&&&4];&&//键码显示
&&&&&&&&&&&&LedBuff[0]&=&LedChar[ircode[2]&0x0F];
/*&配置并启动T0,ms-T0定时时间&*/
void&ConfigTimer0(unsigned&int&ms)
&&&&unsigned&long&&&//临时变量
&&&&tmp&=&&/&12;&&&&&&&//定时器计数频率
&&&&tmp&=&(tmp&*&ms)&/&1000;&&//计算所需的计数值
&&&&tmp&=&65536&-&&&&&&&&&&//计算定时器重载值
&&&&tmp&=&tmp&+&13;&&&&&&&&&&&&//补偿中断响应延时造成的误差
&&&&T0RH&=&(unsigned&char)(tmp&&8);&&//定时器重载值拆分为高低字节
&&&&T0RL&=&(unsigned&char)
&&&&TMOD&&=&0xF0;&&&//清零T0的控制位
&&&&TMOD&|=&0x01;&&&//配置T0为模式1
&&&&TH0&=&T0RH;&&&&&//加载T0重载值
&&&&TL0&=&T0RL;
&&&&ET0&=&1;&&&&&&&&//使能T0中断
&&&&TR0&=&1;&&&&&&&&//启动T0
/*&LED动态扫描刷新函数,需在定时中断中调用&*/
void&LedScan()
&&&&static&unsigned&char&i&=&0;&&//动态扫描索引
&&&&P0&=&0xFF;&&&&&&&&&&&&&&&&&&&&&//关闭所有段选位,显示消隐
&&&&P1&=&(P1&&&0xF8)&|&i;&&&&&&&&//位选索引值赋值到P1口低3位
&&&&P0&=&LedBuff[i];&&&&&&&&&&&&&&//缓冲区中索引位置的数据送到P0口
&&&&if&(i&&&sizeof(LedBuff)-1)&&//索引递增循环,遍历整个缓冲区
&&&&&&&&i++;
&&&&&&&&i&=&0;
/*&T0中断服务函数,执行数码管扫描显示&*/
void&InterruptTimer0()&interrupt&1
&&&&TH0&=&T0RH;&&//重新加载重载值
&&&&TL0&=&T0RL;
&&&&LedScan();&&&//数码管扫描显示
& & & main.c文件的主要功能就是把获取到的红外遥控器的用户码和键码信息,传送到数码管上显示出来,并且通过定时器T0的1ms中断进行数码管的动态刷新。不知道大家经过试验发现没有,当我们按下遥控器按键的时候,数码管显示的数字会闪烁,这是什么原因呢?单片机的程序都是顺序执行的,一旦我们按下遥控器按键,单片机就会进入遥控器解码的中断程序内,而这个程序执行的时间又比较长,要几十个毫秒,而如果数码管动态刷新间隔超过10ms后就会感觉到闪烁,因此这个闪烁是由于程序执行红外解码时,延误了数码管动态刷新造成的。
& & & 如何解决?前边我们讲过中断优先级问题,如果设置了中断的抢占优先级,就会产生中断嵌套。中断嵌套的原理,我们在前边讲中断的时候已经讲过一次了,大家可以回头再复习一下。那么这个程序中,有2个中断程序,一个是外部中断程序负责接收红外数据,一个是定时器中断程序负责数码管扫描,要使红外接收不耽误数码管扫描的执行,那么就必须让定时器中断对外部中断实现嵌套,即把定时器中断设置为高抢占优先级。定时器中断程序,执行时间只有几十个us,即使打断了红外接收中断的执行,也最多是给每个位的时间测量附加了几十us的误差,而这个误差在最短560us的时间判断中完全是容许的,所以中断嵌套并不会影响红外数据的正常接收。在main函数中,大家把这行程序“//PT0&=&1;”的注释取消,也就是使这行代码生效,这样就设置了T0中断的高抢占优先级,再编译一下,下载到单片机里,然后按键试试,是不是没有任何闪烁了呢?而中断嵌套的意义也有所体会了吧。
这个家伙很懒,什么也没有留下
上一帖子:
下一帖子:
发表于& 16:54:51
1.1&&温度传感器DS18B20
& & & DS18B20是美信公司的一款温度传感器,单片机可以通过1-Wire协议与DS18B20进行通信,最终将温度读出。1-Wire总线的硬件接口很简单,只需要把DS18B20的数据引脚和单片机的一个IO口接上就可以了。硬件的简单,随之而来的,就是软件时序的复杂。1-Wire总线的时序比较复杂,很多同学在这里独立看时序图都看不明白,所以这里还要带着大家来研究DS18B20的时序图。我们先来看一下DS18B20的硬件原理图,如图16-12所示。
图16-12&&DS18B20电路原理图
& & & DS18B20通过编程,可以实现最高12位的温度存储值,在寄存器中,以补码的格式存储,如图16-13所示。
图16-13&&DS18B20温度数据格式
& & & 一共2个字节,LSB是低字节,MSB是高字节,其中MSb是字节的高位,LSb是字节的低位。大家可以看出来,二进制数字,每一位代表的温度的含义,都表示出来了。其中S表示的是符号位,低11位都是2的幂,用来表示最终的温度。DS18B20的温度测量范围是从-55度到+125度,而温度数据的表现形式,有正负温度,寄存器中每个数字如同卡尺的刻度一样分布,如图16-14所示。
图16-14&&DS18B20温度值
& & &二进制数字最低位变化1,代表温度变化0.0625度的映射关系。当0度的时候,那就是0x0000,当温度125度的时候,对应十六进制是0x07D0,当温度是零下55度的时候,对应的数字是0xFC90。反过来说,当数字是0x0001的时候,那温度就是0.0625度了。
首先,我先根据手册上DS18B20工作协议过程大概讲解一下。
& & & 1、初始化。和I2C的寻址类似,1-Wire总线开始也需要检测这条总线上是否存在DS18B20这个器件。如果这条总线上存在DS18B20,总线会根据时序要求返回一个低电平脉冲,如果不存在的话,也就不会返回脉冲,即总线保持为高电平,所以习惯上称之为检测存在脉冲。此外,获取存在脉冲不仅仅是检测是否存在DS18B20,还要通过这个脉冲过程通知DS18B20准备好,单片机要对它进行操作了,如图16-15所示。
图16-15&&检测存在脉冲
& & & 大家注意看图,实粗线是我们的单片机IO口拉低这个引脚,虚粗线是DS18B20拉低这个引脚,细线是单片机和DS18B20释放总线后,依靠上拉电阻的作用把IO口引脚拉上去。这个我们前边提到过了,51单片机释放总线就是给高电平。
& & & 存在脉冲检测过程,首先单片机要拉低这个引脚,持续大概480us到960us之间的时间即可,我们的程序中持续了500us。然后,单片机释放总线,就是给高电平,DS18B20等待大概15到60us后,会主动拉低这个引脚大概是60到240us,而后DS18B20会主动释放总线,这样IO口会被上拉电阻自动拉高。
有的同学还是不能够彻底理解,程序列出来逐句解释。首先,由于DS18B20时序要求非常严格,所以在操作时序的时候,为了防止中断干扰总线时序,先关闭总中断。然后第一步,拉低DS18B20这个引脚,持续500us;第二步,延时60us;第三步,读取存在脉冲,并且等待存在脉冲结束。
bit&Get18B20Ack()
&&&&EA&=&0;&&&//禁止总中断
&&&&IO_18B20&=&0;&&&&&&//产生500us复位脉冲
&&&&DelayX10us(50);
&&&&IO_18B20&=&1;
&&&&DelayX10us(6);&&&&&//延时60us
&&&&ack&=&IO_18B20;&&&&//读取存在脉冲
&&&&while(!IO_18B20);&//等待存在脉冲结束
&&&&EA&=&1;&&&//重新使能总中断
&&&&return&
& & &&很多同学对第二步不理解,时序图上明明是DS18B20等待15us到60us,为什么要延时60us呢?举个例子,妈妈在做饭,告诉你大概5分钟到10分钟饭就可以吃了,那么我们什么时候去吃,能够绝对保证吃上饭呢?很明显,10分钟以后去吃肯定可以吃上饭。同样的道理,DS18B20等待大概是15us到60us,我们要保证读到这个存在脉冲,那么60us以后去读肯定可以读到。当然,不能延时太久,太久,超过75us,就可能读不到了,为什么是75us,大家自己思考一下。
2、ROM操作指令。我们学I2C总线的时候就了解到,总线上可以挂多个器件,通过不同的器件地址来访问不同的器件。同样,1-Wire总线也可以挂多个器件,但是它只有一条线,如何区分不同的器件呢?
& & & 在每个DS18B20内部都有一个唯一的64位长的序列号,这个序列号值就存在DS18B20内部的ROM中。开始的8位是产品类型编码(DS18B20是0x10),接着的48位是每个器件唯一的序号,最后的8位是CRC校验码。DS18B20可以引出去很长的线,最长可以到几十米,测不同位置的温度。单片机可以通过和DS18B20之间的通信,获取每个传感器所采集到的温度信息,也可以同时给所有的DS18B20发送一些指令。这些指令相对来说比较复杂,而且应用很少,所以这里大家有兴趣的话就自己去查手册完成吧,我们这里只讲一条总线上只接一个器件的指令和程序。
Skip&ROM(跳过ROM):0xCC。当总线上只有一个器件的时候,可以跳过ROM,不进行ROM检测。
3、RAM存储器操作指令。
& & &&RAM读取指令,只讲2条,其它的大家有需要可以随时去查资料。
& & & Read&Scratchpad(读暂存寄存器):0xBE
& & & 这里要注意的是,DS18B20的温度数据是2个字节,我们读取数据的时候,先读取到的是低字节的低位,读完了第一个字节后,再读高字节的低位,直到两个字节全部读取完毕。
& & &&Convert&Temperature(启动温度转换):0x44
& & & &当我们发送一个启动温度转换的指令后,DS18B20开始进行转换。从转换开始到获取温度,DS18B20是需要时间的,而这个时间长短取决于DS18B20的精度。前边说DS18B20最高可以用12位来存储温度,但是也可以用11位,10位和9位一共四种格式。位数越高,精度越高,9位模式最低位变化1个数字温度变化0.5度,同时转换速度也要快一些,如图16-16所示。
图16-16&&DS18B20温度转换时间
& & & 其中寄存器R1和R0决定了转换的位数,出厂默认值就11,也就是12位表示温度,最大的转换时间是750ms。当启动转换后,至少要再等750ms之后才能读取温度,否则读到的温度有可能是错误的值。这就是为什么很多同学读DS18B20的时候,第一次读出来的是85度,这个值要么是没有启动转换,要么是启动转换了,但还没有等待一次转换彻底完成,读到的是一个错误的数据。
& & & 4、DS18B20的位读写时序。
DS18B20的时序图不是很好理解,大家对照时序图,结合我的解释,一定要把它学明白。写时序图如图16-17所示。
图16-17&&DS18B20位写入时序
& & & 当要给DS18B20写入0的时候,单片机直接将引脚拉低,持续时间大于60us小于120us就可以了。图上显示的意思是,单片机先拉低15us之后,DS18B20会在从15us到60us之间的时间来读取这一位,DS18B20最早会在15us的时刻读取,典型值是在30us的时刻读取,最多不会超过60us,DS18B20必然读取完毕,所以持续时间超过60us即可。
& & & 当要给DS18B20写入1的时候,单片机先将这个引脚拉低,拉低时间大于1us,然后马上释放总线,即拉高引脚,并且持续时间也要大于60us。和写0类似的是,DS18B20会在15us到60us之间来读取这个1。
& & & &可以看出来,DS18B20的时序比较严格,写的过程中最好不要有中断打断,但是在两个“位”之间的间隔,是大于1小于无穷的,那在这个时间段,我们是可以开中断来处理其它程序的。发送即写入一个字节的数据程序如下。
void&Write18B20(unsigned&char&dat)
&&&&unsigned&char&
&&&&EA&=&0;&&&//禁止总中断
&&&&for&(mask=0x01;&mask!=0;&mask&&=1)&&//低位在先,依次移出8个bit
&&&&&&&&IO_18B20&=&0;&&&&&&&&&&//产生2us低电平脉冲
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&&&&&if&((mask&dat)&==&0)&&//输出该bit值
&&&&&&&&&&&&IO_18B20&=&0;
&&&&&&&&else
&&&&&&&&&&&&IO_18B20&=&1;
&&&&&&&&DelayX10us(6);&&&&&&&&//延时60us
&&&&&&&&IO_18B20&=&1;&&&&&&&&&//拉高通信引脚
&&&&EA&=&1;&&&//重新使能总中断
读时序图如图16-18所示。
图16-18&&DS18B20位读取时序
& & & 当要读取DS18B20的数据的时候,我们的单片机首先要拉低这个引脚,并且至少保持1us的时间,然后释放引脚,释放完毕后要尽快读取。从拉低这个引脚到读取引脚状态,不能超过15us。大家从图16-18可以看出来,主机采样时间,也就是MASTER&SAMPLES,是在15us之内必须完成的,读取一个字节数据的程序如下。
unsigned&char&Read18B20()
&&&&unsigned&char&
&&&&unsigned&char&
&&&&EA&=&0;&&&//禁止总中断
&&&&for&(mask=0x01;&mask!=0;&mask&&=1)&&//低位在先,依次采集8个bit
&&&&&&&&IO_18B20&=&0;&&&&&&&&&//产生2us低电平脉冲
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&&&&&IO_18B20&=&1;&&&&&&&&&//结束低电平脉冲,等待18B20输出数据
&&&&&&&&_nop_();&&&&&&&&&&&&&&&//延时2us
&&&&&&&&_nop_();
&&&&&&&&if&(!IO_18B20)&&&&&&&&//读取通信引脚上的值
&&&&&&&&&&&&dat&&=&~
&&&&&&&&else
&&&&&&&&&&&&dat&|=&
&&&&&&&&DelayX10us(6);&&&&&&&&//再延时60us
&&&&EA&=&1;&&&//重新使能总中断
&&&&return&
& & & DS18B20所表示的温度值中,有小数和整数两部分。常用的带小数的数据处理方法有两种,一种是定义成浮点型直接处理,第二种是定义成整型,然后把小数和整数部分分离出来,在合适的位置点上小数点即可。我们在程序中使用的是第二种方法,下面我们就写一个程序,将读到的温度值显示在1602液晶上,并且保留一位小数位。
/***************************DS18B20.c文件程序源代码****************************/
#include&&reg52.h&
#include&&intrins.h&
sbit&IO_18B20&=&P3^2;&&//DS18B20通信引脚
/*&软件延时函数,延时时间(t*10)us&*/
void&DelayX10us(unsigned&char&t)
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&}&while&(--t);
/*&复位总线,获取存在脉冲,以启动一次读写操作&*/
bit&Get18B20Ack()
&&&&EA&=&0;&&&//禁止总中断
&&&&IO_18B20&=&0;&&&&&//产生500us复位脉冲
&&&&DelayX10us(50);
&&&&IO_18B20&=&1;
&&&&DelayX10us(6);&&&&//延时60us
&&&&ack&=&IO_18B20;&&&//读取存在脉冲
&&&&while(!IO_18B20);&//等待存在脉冲结束
&&&&EA&=&1;&&&//重新使能总中断
&&&&return&
/*&向DS18B20写入一个字节,dat-待写入字节&*/
void&Write18B20(unsigned&char&dat)
&&&&unsigned&char&
&&&&EA&=&0;&&&//禁止总中断
&&&&for&(mask=0x01;&mask!=0;&mask&&=1)&&//低位在先,依次移出8个bit
&&&&&&&&IO_18B20&=&0;&&&&&&&&&&//产生2us低电平脉冲
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&&&&&if&((mask&dat)&==&0)&&//输出该bit值
&&&&&&&&&&&&IO_18B20&=&0;
&&&&&&&&else
&&&&&&&&&&&&IO_18B20&=&1;
&&&&&&&&DelayX10us(6);&&&&&&&&//延时60us
&&&&&&&&IO_18B20&=&1;&&&&&&&&&//拉高通信引脚
&&&&EA&=&1;&&&//重新使能总中断
/*&从DS18B20读取一个字节,返回值-读到的字节&*/
unsigned&char&Read18B20()
&&&&unsigned&char&
&&&&unsigned&char&
&&&&EA&=&0;&&&//禁止总中断
&&&&for&(mask=0x01;&mask!=0;&mask&&=1)&&//低位在先,依次采集8个bit
&&&&&&&&IO_18B20&=&0;&&&&&&&&&//产生2us低电平脉冲
&&&&&&&&_nop_();
&&&&&&&&_nop_();
&&&&&&&&IO_18B20&=&1;&&&&&&&&&//结束低电平脉冲,等待18B20输出数据
&&&&&&&&_nop_();&&&&&&&&&&&&&&&//延时2us
&&&&&&&&_nop_();
&&&&&&&&if&(!IO_18B20)&&&&&&&&//读取通信引脚上的值
&&&&&&&&&&&&dat&&=&~
&&&&&&&&else
&&&&&&&&&&&&dat&|=&
&&&&&&&&DelayX10us(6);&&&&&&&&//再延时60us
&&&&EA&=&1;&&&//重新使能总中断
&&&&return&
/*&启动一次18B20温度转换,返回值-表示是否启动成功&*/
bit&Start18B20()
&&&&ack&=&Get18B20Ack();&&&//执行总线复位,并获取18B20应答
&&&&if&(ack&==&0)&&&&&&&&&&&//如18B20正确应答,则启动一次转换
&&&&&&&&Write18B20(0xCC);&&//跳过ROM操作
&&&&&&&&Write18B20(0x44);&&//启动一次温度转换
&&&&return&~&&&//ack==0表示操作成功,所以返回值对其取反
/*&读取DS18B20转换的温度值,返回值-表示是否读取成功&*/
bit&Get18B20Temp(int&*temp)
&&&&unsigned&char&LSB,&MSB;&//16bit温度值的低字节和高字节
&&&&ack&=&Get18B20Ack();&&&&//执行总线复位,并获取18B20应答
&&&&if&(ack&==&0)&&&&&&&&&&&&//如18B20正确应答,则读取温度值
&&&&&&&&Write18B20(0xCC);&&&//跳过ROM操作
&&&&&&&&Write18B20(0xBE);&&&//发送读命令
&&&&&&&&LSB&=&Read18B20();&&//读温度值的低字节
&&&&&&&&MSB&=&Read18B20();&&//读温度值的高字节
&&&&&&&&*temp&=&((int)MSB&&&&8)&+&LSB;&&//合成为16bit整型数
&&&&return&~&&//ack==0表示操作应答,所以返回值为其取反值
/***************************Lcd1602.c文件程序源代码*****************************/
(此处省略,可参考之前章节的代码)
/*****************************main.c文件程序源代码******************************/
#include&&reg52.h&
bit&flag1s&=&0;&&&&&&&&&&&//1s定时标志
unsigned&char&T0RH&=&0;&&//T0重载值的高字节
unsigned&char&T0RL&=&0;&&//T0重载值的低字节
void&ConfigTimer0(unsigned&int&ms);
unsigned&char&IntToString(unsigned&char&*str,&int&dat);
extern&bit&Start18B20();
extern&bit&Get18B20Temp(int&*temp);
extern&void&InitLcd1602();
extern&void&LcdShowStr(unsigned&char&x,&unsigned&char&y,&unsigned&char&*str);
void&main()
&&&&int&&&&&&&&&&//读取到的当前温度值
&&&&int&intT,&decT;&&//温度值的整数和小数部分
&&&&unsigned&char&
&&&&unsigned&char&str[12];
&&&&EA&=&1;&&&&&&&&&&&&&&//开总中断
&&&&ConfigTimer0(10);&&//T0定时10ms
&&&&Start18B20();&&&&&&//启动DS18B20
&&&&InitLcd1602();&&&&&//初始化液晶
&&&&while&(1)
&&&&&&&&if&(flag1s)&&//每秒更新一次温度
&&&&&&&&&&&&flag1s&=&0;
&&&&&&&&&&&&res&=&Get18B20Temp(&temp);&&//读取当前温度
&&&&&&&&&&&&if&(res)&&&&&&&&&&&&&&&&&&&&&&&//读取成功时,刷新当前温度显示
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&intT&=&temp&&&&4;&&&&&&&&&&&&&&&//分离出温度值整数部分
&&&&&&&&&&&&&&&&decT&=&temp&&&0xF;&&&&&&&&&&&&&&//分离出温度值小数部分
&&&&&&&&&&&&&&&&len&=&IntToString(str,&intT);&//整数部分转换为字符串
&&&&&&&&&&&&&&&&str[len++]&=&'.';&&&&&&&&&&&&&&&//添加小数点
&&&&&&&&&&&&&&&&decT&=&(decT*10)&/&16;&&&&&&&&&//二进制的小数部分转换为1位十进制位
&&&&&&&&&&&&&&&&str[len++]&=&decT&+&'0';&&&&&&//十进制小数位再转换为ASCII字符
&&&&&&&&&&&&&&&&while&(len&&&6)&&&&&&&&&&&&&&&&&//用空格补齐到6个字符长度
&&&&&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&&&&&str[len++]&=&'&';
&&&&&&&&&&&&&&&&}
&&&&&&&&&&&&&&&&str[len]&=&'';&&&&&&&&&&&&&&&//添加字符串结束符
&&&&&&&&&&&&&&&&LcdShowStr(0,&0,&str);&&&&&&&&//显示到液晶屏上
&&&&&&&&&&&&}
&&&&&&&&&&&&else&&&&&&&&&&&&&&&&&&&&&&&&//读取失败时,提示错误信息
&&&&&&&&&&&&{
&&&&&&&&&&&&&&&&LcdShowStr(0,&0,&"error!");
&&&&&&&&&&&&}
&&&&&&&&&&&&Start18B20();&&&&&&&&&&&&&//重新启动下一次转换
/*&整型数转换为字符串,str-字符串指针,dat-待转换数,返回值-字符串长度&*/
unsigned&char&IntToString(unsigned&char&*str,&int&dat)
&&&&signed&char&i&=&0;
&&&&unsigned&char&len&=&0;
&&&&unsigned&char&buf[6];
&&&&if&(dat&&&0)&&//如果为负数,首先取绝对值,并在指针上添加负号
&&&&&&&&dat&=&-
&&&&&&&&*str++&=&'-';
&&&&&&&&len++;
&&&&do&{&&&&&&&&&&&//先转换为低位在前的十进制数组
&&&&&&&&buf[i++]&=&dat&%&10;
&&&&&&&&dat&/=&10;
&&&&}&while&(dat&&&0);
&&&&len&+=&i;&&&&&//i最后的值就是有效字符的个数
&&&&while&(i--&&&0)&&&//将数组值转换为ASCII码反向拷贝到接收指针上
&&&&&&&&*str++&=&buf[i]&+&'0';
&&&&*str&=&'';&&//添加字符串结束符
&&&&return&&&&//返回字符串长度
/*&配置并启动T0,ms-T0定时时间&*/
void&ConfigTimer0(unsigned&int&ms)
&&&&unsigned&long&&&//临时变量
&&&&tmp&=&&/&12;&&&&&&&//定时器计数频率
&&&&tmp&=&(tmp&*&ms)&/&1000;&&//计算所需的计数值
&&&&tmp&=&65536&-&&&&&&&&&&//计算定时器重载值
&&&&tmp&=&tmp&+&12;&&&&&&&&&&&&//补偿中断响应延时造成的误差
&&&&T0RH&=&(unsigned&char)(tmp&&8);&&//定时器重载值拆分为高低字节
&&&&T0RL&=&(unsigned&char)
&&&&TMOD&&=&0xF0;&&&//清零T0的控制位
&&&&TMOD&|=&0x01;&&&//配置T0为模式1
&&&&TH0&=&T0RH;&&&&&//加载T0重载值
&&&&TL0&=&T0RL;
&&&&ET0&=&1;&&&&&&&&//使能T0中断
&&&&TR0&=&1;&&&&&&&&//启动T0
/*&T0中断服务函数,完成1秒定时&*/
void&InterruptTimer0()&interrupt&1
&&&&static&unsigned&char&tmr1s&=&0;
&&&&TH0&=&T0RH;&&//重新加载重载值
&&&&TL0&=&T0RL;
&&&&tmr1s++;
&&&&if&(tmr1s&&=&100)&&//定时1s
&&&&&&&&tmr1s&=&0;
&&&&&&&&flag1s&=&1;
1.2&练习题
1、理解红外通信调制解调的原理,掌握NEC红外通信编码的原理。
2、将显示跳线帽调到左侧控制步进电机,使用红外遥控器控制电机的正反转。
3、掌握DS18B20的时序过程,能够理解每一位读写的时序。
4、结合DS1302的电子钟实例,将温度显示加入进去,做一个带温度显示的万年历。
这个家伙很懒,什么也没有留下
发表于& 19:41:37
这几天也折腾了一下红外
现在接收头都直接一体化了,简单。
这个家伙很懒,什么也没有留下
发表于& 22:17:42
看看 &顶顶
这个家伙很懒,什么也没有留下
发表于& 08:29:35
感谢楼主分享。。期待楼主新作!
坚持自己的梦想,总有一天会成功!
发表于& 09:06:50
可接项目和毕设等
发表于& 15:28:32
这个家伙很懒,什么也没有留下
发表于& 22:48:04
跟踪到底 闭环反馈}

我要回帖

更多关于 燃气灶进汽管有些松动 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信