好消息:请在手机淘宝或闲鱼上搜索“电子入门趣谈”,有惊喜哦 :) 我把全本电子入门趣谈的电子版(包括科技提升和理论升华部分,共计50余万字)放到上面开始兜售啦,如果您真的喜欢这本书,又想跟作者一起聊聊天的话 :) ... ...欢迎各界电子科技爱好者朋友们斧正收藏 。作者微信:zhangbolin1202 --- 2019.05.27注
3.2 常见传感器及其驱动电路
比较简单的传感器像光敏电阻,热敏电阻、光电二极管、光敏三极管大家稍微看看应该就能明白了,下面送给大家一些相对比较复杂的传感器案例,这几款传感器操作起来难易不均,但是包含的知识量都比较丰富,弄明白这几个传感器估计您再操作其他传感器也就不在话下了。另外,第六章的竞赛机器人里也会介绍一些传感器的使用方法,欢迎查阅。
3.2.1 DS18B20
测量温度是生活中一项常见的活动,通常我们利用的是各种温度计,温度计靠人来读,现在我们利用传感器进行温度测量,让CPU来读。在本案例中,我们利用温度传感器DS18B20、51单片机和LED数码管来完成温度的测量和显示任务。项目实现功能:利用DS18B20采集环境温度,LED数码管将显示得到的温度值。
注:事实上DS18B20属于传感器里比较难操作的传感器,要考虑的东西几乎囊括了其他芯片要考虑的所有的问题,包括指令码、时序图、数据寄存器、配置寄存器、发送指令、发送数据甚至还有CRC(循环冗余校验)的知识内容,所以这一节的内容相当重要,把这个传感器放在第一个讲就是让大家“欣赏”一下单片机控制芯片的整个流程,实在读不明白的地方先放一放,找个时间再读读,这个东西绝对不是看一遍就能精通的,但是后面的电路图和程序流程框图及其代码非常值钱,在类似的项目上基本上可以直接拿过来用。不要害怕,后面的会相对简单得多,咱们开始吧。
(1)DS18B20温度传感器简介
美国Dallas 半导体公司的数字化温度传感器DS1820 是世界上第一片支持 "一线总线"接口的温度传感器。全部传感元件及转换电路集成在形如一只三极管的集成电路内。一线总线独特而且经济的特点,使用户可轻松地组建传感器网络,为测量系统的构建引入全新概念。现在,新一代的DS18B20 体积更小、更经济、更灵活。使你可以充分发挥“一线总线”的优点。一线总线(1—Wire总线)是Dallas公司的专有技术,只需要一根导线就可以完成串行通信,该技术将设备的地址线、数据线、控制线合为一根线。单根信号线既可以传输时钟,又可以传输数据,数据传输可以是双向的。一线总线技术适合于单主机系统,可以由一个或多个从设备。
在传统的模拟信号远距离温度测量系统中,需要很好的解决引线误差补偿问题、多点测量切换误差问题和放大电路零点漂移误差问题等技术问题,才能够达到较高的测量精度。另外一般监控现场的电磁环境都非常恶劣,各种干扰信号较强,模拟温度信号容易受到干扰而产生测量误差,影响测量精度。因此,在温度测量系统中,采用抗干扰能力强的新型数字温度传感器是解决这些问题的最有效方案,新型数字温度传感器DS18B20 具有体积更小、精度更高、适用电压更宽、采用一线总线、可组网等优点,在实际应用中取得了良好的测温效果。
(2)DS18B20主要特点
以下这些特点可以直接从DS18B20的数据手册(datasheet)上查到,对于一款传感器,在阅读它的数据手册的时候一般来说都要注意它的供电电压、分辨率、测量范围、数据通信方式等。
Ø 适应电压范围更宽,电压范围:3.0~5.5V,在寄生电源方式下可由数据线供电
Ø 独特的单线接口方式,DS18B20在与微处理器连接时仅需要一条口线即可实现微处理器与DS18B20 的双向通讯
Ø DS18B20 支持多点组网功能,多个DS18B20可以并联在唯一的三线上,实现组网多点测温
Ø DS18B20 在使用中不需要任何外围元件,全部传感元件及转换电路集成在形如一只三极管的集成电路内
Ø 温范围-55℃~+125℃,在-10~+85℃时精度为±0.5℃
Ø 可编程的分辨率为9~12 位,对应的可分辨温度分别为0.5℃、0.25℃、0.125℃和0.0625℃,可实现高精度测温。
Ø 测量结果直接输出数字温度信号,以一条总线串行传送给CPU,同时可传送CRC校验码,具有很强的抗干扰和纠错能力。
Ø 负压特性,当电源接反时。芯片不会因为发热而烧毁,但芯片不能正常工作。
Ø 在9位分辨率时最多在 93.75ms内把温度转换为数字,12位分辨率时最多在750ms内把温度值转换为数字,速度更快。
(3)DS18B20内部结构
为了利用DS18B20进行温度测量,我们当然应该了解它的内部结构,只有准确把握了它的特性,我们才能设计出正确的温度测量程序。如果希望DS18B20协助我们测量温度,我们该如何表达我们的意愿呢?DS18B20当然听不懂我们的日常用语了,所以我们得学习可以和DS18B20交流的语言。DS18B20的操作在芯片控制领域非常典型,难度也很大,大家努努力,争取把它看懂了,以后再遇到其他芯片就会一帆风顺了。
DS18B20内部结构主要由64位ROM、温度传感器、温度触发器TH和TL及高速缓冲器等部分组成,如下图所示。注意,常说DS18B20是一个温度传感器,事实上它是一个小系统,温度传感器是它里面最主要的部分而已。
1、DS18B20 的指令:
指令就相当于暗号,单片机给芯片发出指令会促使芯片完成相应的功能,DS18B20的指令及其约定代码如下表所示。
例如某温度测量系统中只有一个DS18B20,我们可以直接使用跳过ROM指令:CCH(H表示前面的CC是十六进制数),该指令允许主设备不提供64位二进制ROM代码就使用存储器操作命令。
所以我们在程序设计中只需要按照上面的指令表写入特定的指令,就可以完成对DS18B20特定的操作。读到这,有没有疑问?啥叫64位ROM代码?
知识点:
光刻ROM 中的64 位序列号是出厂前被光刻好的,它可以看作是该DS18B20 的地址序列码,实际上就相当于我们的身份证号。
64位光刻ROM 的排列是:开始8 位(28H)是产品类型标号,接着的48 位是该DS18B20 自身的序列号,最后8 位是前面56 位的循环冗余校验码(CRC=X8+X5+X4+1)。光刻ROM 的作用是使每一个DS18B20都各不相同,这样就可以实现一根总线上挂接多个DS18B20 的目的,当单片机发送指令的时候先通过64位ROM确定使用哪个DS18B20,然后其他DS18B20就会进入类休眠状态,对单片机后来发的指令不在理会。如果一个系统只有一个DS18B20,当然就可以不用考虑“身份”的问题啦。
2、DS18B20 分辨率
以12位转换为例,用16位符号扩展的二进制补码形式读数,以0.0625℃/LSB 形式表达,其中S为符号位。
知识点:LSB( Least Significant Bit)称为最低有效位,类似于十进制中最右边的一个数字,“以0.0625℃/LSB 形式表达”的实际意思就是下面提到的RAM寄存器中的数字每变化“1”,对应温度就变化0.0625℃。对应的还有一个MSB,代表最高有效位。
| bit 7 | bit 6 | bit 5 | bit 4 | bit 3 | bit 2 | bit 1 | bit 0 |
LS Byte | 23 | 22 | 21 | 20 | 2-1 | 2-2 | 2-3 | 2-4 |
| bit 15 | bit 14 | bit 13 | bit 12 | bit 11 | bit 10 | bit 9 | bit 8 |
MS Byte | S | S | S | S | S | 26 | 25 | 24 |
这是12位转化后得到的12 位数据,存储在DS18B20 的两个8位的RAM中,二进制中的前面5 位是符号位,如果测得的温度大于0,这5位为0,只要将测到的数值乘于0.0625即可得到实际温度;如果温度小于0,这5位为1,测到的数值需要取反加1再乘于0.0625即可得到实际温度。例如+125℃的数字输出为07D0H(H表示前面的数字07D0为十六进制数),+25.0625℃的数字输出为0191H,-25.0625℃的数字输出为FF6FH,-55℃的数字输出为FC90H。
3、DS18B20 温度传感器的存储器
DS18B20 温度传感器的内部存储器包括一个高速暂存RAM 和一个非易失性的可电擦除的EEPRAM,后者存放高温度和低温度触发器TH、TL 和结构寄存器。
4、配置寄存器
该字节各位的意义如下:
TM | R1 | R0 | 1 | 1 | 1 | 1 | 1 |
低五位一直都是"1",TM 是测试模式位,用于设置DS18B20 在工作模式还是在测试模式。在DS18B20出厂时该位被设置为0,用户不要去改动。R1 和R0 用来设置分辨率,如下表所示, DS18B20 出厂时分辨率被默认设置为12 位,下面的程序中我们就使用默认的12位分辨率,所以无需操作R1、R0。
R1 | R0 | 分辨率 | 温度最大转换时间(ms) |
0 | 0 | 9 | 93.75 |
0 | 1 | 10 | 187.5 |
1 | 0 | 11 | 375 |
1 | 1 | 12 | 750 |
5、操作时序
一说起操作时序不知道大家还有没有印象,第一章中提到的串口通信就用到了时序,它就类似于一个口令,某些硬件之间互相通信有时候是需要对口令的。串口通信,I2C通信,SPI通信都有对应的时序,这些时序描述较为抽象,不易理解,建议大家先浏览掌握大概,再对照相应的程序阅读,就可以慢慢掌握了。下面是DS18B20的操作时序说明。
复位时序:首先我们必须对DS18B20芯片进行复位,复位就是由控制器(单片机)给DS18B20单总线至少480us的低电平信号。当18B20接到此复位信号后则会在15~60us后回发一个芯片的存在脉冲。
写时间隙:写时间隙分为写“0”和写“1”。在写数据时间隙的前15us总线需要是被控制器拉置低电平,而后则将是芯片对总线数据的采样时间,采样时间在15~60us,采样时间内如果控制器将总线拉高则表示写“1”,如果控制器将总线拉低则表示写“0”。每一位的发送都应该有一个至少15us的低电平起始位,随后的数据“0”或“1”应该在45us内完成。整个位的发送时间应该保持在60~120us,否则不能保证通信的正常。
读时间隙:读时间隙时控制时的采样时间应该更加的精确才行,读时间隙时也是必须先由主机产生至少1uS的低电平,表示读时间的起始。随后在总线被释放后的15uS中DS18B20会发送内部数据位,这时控制如果发现总线为高电平表示读出“1”,如果总线为低电平则表示读出数据“0”。每一位的读取之前都由控制器加一个起始信号。注意,必须在读间隙开始的15us内读取数据位才可以保证通信的正确
(4)DS18B20管脚介绍
DS18B20的外形和封装如上图所示,各个管脚功能说明如下:VCC/VDD接5V电源,GND 电源地,I/O 数据输入输出管脚,NC管脚无连接。当信号线DQ为高电平时,信号线DQ为芯片供电,并且DS18B20内部电容会存储电能;当信号线DQ为低电平时,内部电容供电,直至下一个高电平到来。
(5)硬件电路图
下图是DS18B20的典型连接电路,我们设计的硬件电路就是以此图为参考的。它的接法很简单,重点在于如何编程。
DS18B20典型连接电路
硬件原理图如下图所示,该电路图是在第二章介绍38译码器的基础上发展而来的,说是发展,实际上就加了一个P1插口,也就是传说中的DS18B20插头。
(6)程序实现
大家看了上面的内部结构介绍会不会很迷茫了,觉得DS18B20真的是太复杂了,难以交流啊。下面我们将结合上文的结构原理进行具体的程序设计,看看大家能理解多少。
下面先附上DS18B20读取温度的流程框图,然后给出该案例对应的代码。
程序实现分为两个c文件,分别是DS18B20驱动程序和主文件程序,主要程序代码及说明如下所示:
(1)DS18B20驱动程序
/*******此部分为18B20的驱动程序***************/
#include<reg52.H>
#include<intrins.h>
sbit D18B20=P3^7;
#define NOP() _nop_() /* 定义空指令 */
#define _Nop() _nop_() /*定义空指令*/
voidTempDelay (unsigned char idata us);
voidInit18b20 (void);
voidWriteByte (unsigned char idata wr); //单字节写入
voidread_bytes (unsigned char idata j);
unsignedchar CRC (unsigned char j);
voidGemTemp (void);
voidConfig18b20 (void);
voidReadID (void);
voidTemperatuerResult(void);
bit flag;
unsignedint idata Temperature,D[10];
unsignedchar idata temp_buff[9]; //存储读取的字节,read scratchpad为9字节,read rom ID为8字节
unsignedchar idata id_buff[8];
unsignedchar idata *p,TIM;
unsignedchar idata crc_data;
unsignedchar code CrcTable [256]={
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};
/************************************************************
*Function:延时处理
*parameter:unsigned char idata us
*Return:无
*************************************************************/
voidTempDelay (unsigned char idata us)
{
while(us--);
}
/************************************************************
*Function:18B20初始化
*parameter:无
*Return:无
*************************************************************/
voidInit18b20 (void)
{
D18B20=1;
_nop_();
D18B20=0;
TempDelay(50); //delay 530 uS//80
_nop_();
D18B20=1;
TempDelay(10); //delay 100 uS//14
_nop_();
_nop_();
_nop_();
if(D18B20==0)
flag = 1; //detect 1820 success!
else
flag = 0; //detect 1820 fail!
TempDelay(15); //20
_nop_();
_nop_();
D18B20 = 1;
}
/************************************************************
*Function:向18B20写入一个字节
*parameter:unsigned char idata wr
*Return:无
*************************************************************/
voidWriteByte (unsigned char idata wr) //单字节写入
{
unsigned char idata i;
for (i=0;i<8;i++)
{
D18B20 = 0;
_nop_();
D18B20=wr&0x01;
TempDelay(3); //delay 45 uS //5
_nop_();
_nop_();
D18B20=1;
wr >>= 1;
}
}
/************************************************************
*Function:读18B20的一个字节
*parameter:无
*Return:无
*************************************************************/
unsignedchar ReadByte (void) //读取单字节
{
unsigned char idata i,u=0;
for(i=0;i<8;i++)
{
D18B20 = 0;
u >>= 1;
D18B20 = 1;
if(D18B20==1)
u |= 0x80;
TempDelay (2);
_nop_();
}
return(u);
}
/************************************************************
*Function:读18B20
*parameter:unsigned char idata j
*Return:无
*************************************************************/
voidread_bytes (unsigned char idata j)
{
unsigned char idata i;
for(i=0;i<j;i++)
{
*p = ReadByte();
p++;
}
}
/************************************************************
*Function:CRC校验
*parameter:unsigned char j
*Return: crc_data
*************************************************************/
unsignedchar CRC (unsigned char j)
{
unsignedchar idata i,crc_data=0;
for(i=0;i<j;i++) //查表校验
crc_data= CrcTable[crc_data^temp_buff[i]];
return (crc_data);
}
/************************************************************
*Function:读取温度
*parameter:无
*Return:无
*************************************************************/
voidGemTemp (void)
{
read_bytes (9);
if (CRC(9)==0) //校验正确
{
Temperature = temp_buff[1]*0x100 + temp_buff[0];
Temperature /= 16;
TempDelay(1);
}
}
/************************************************************
*Function:内部配置
*parameter:无
*Return:无
*************************************************************/
voidConfig18b20 (void) //重新配置报警限定值和分辨率
{
Init18b20();
WriteByte(0xcc); //skip rom
WriteByte(0x4e); //write scratchpad
WriteByte(0x19); //上限
WriteByte(0x1a); //下限
WriteByte(0x7f); //set 11 bit (0.125)
Init18b20();
WriteByte(0xcc); //skip rom
WriteByte(0x48); //保存设定值
Init18b20();
WriteByte(0xcc); //skip rom
WriteByte(0xb8); //回调设定值
}
/************************************************************
*Function:读18B20ID
*parameter:无
*Return:无
*************************************************************/
voidReadID (void)//读取器件 id
{
Init18b20();
WriteByte(0x33); //read rom
read_bytes(8);
}
/************************************************************
*Function:18B20ID全处理
*parameter:无
*Return:无
*************************************************************/
voidTemperatuerResult(void)
{
p =id_buff;
ReadID();
Config18b20();
Init18b20 ();
WriteByte(0xcc); //skip rom
WriteByte(0x44); //Temperature convert
Init18b20 ();
WriteByte(0xcc); //skip rom
WriteByte(0xbe); //read Temperature
p = temp_buff;
GemTemp();
}
unsignedint TempTick=0;
voidGetTemp()
{
if(TIM==1)
{ TIM=0;
TemperatuerResult();
D[5]=Temperature;
}
}
/*************************************
[ t1 (0.5ms)中断]中断中做 PWM 输出
------------1000/(0.02ms*250)=200Hz
*************************************/
voidT1zd(void) interrupt 3
{
TH1 = 0xfe; //11.0592
TL1 = 0x33;
TIM++;
}
(2) 主程序部分
#include<reg52.H>
externGetTemp(); //声明引用外部函数
externunsigned int idata Temperature; // 声明引用外部变量
voiddelay(unsigned int i);
//else IO
sbit LS138A=P2^2; //管脚定义
sbit LS138B=P2^3;
sbit LS138C=P2^4;
//此表为 LED 的字模,共阴数码管 0-9
unsigned char code Disp_Tab[] ={0x3f,0x06,0x5b,0x4f,0x66,0x6d,0x7d,0x07,0x7f,0x6f,0x40};
unsignedlong LedOut[5],LedNumVal;
voidsystem_Ini()
{
TMOD|= 0x11;
TH1=0xDC; //11.0592M
TL1=0x00;
IE = 0x8A;
TR1 = 1;
}
main()
{unsigned char i;
system_Ini();
while(1)
{
GetTemp();
/********以下将读18b20的数据送到LED数码管显示*************/
LedNumVal=Temperature;
LedOut[0]=Disp_Tab[LedNumVal%10000/1000];
LedOut[1]=Disp_Tab[LedNumVal%1000/100];
LedOut[2]=Disp_Tab[LedNumVal%100/10]; //十位
LedOut[3]=Disp_Tab[LedNumVal%10]; //个位
for(i=0; i<4; i++)
{
P0 = LedOut[i] ;
switch(i)
{ //138译码
case 0:LS138A=0; LS138B=0;LS138C=0; break;
case 1:LS138A=1; LS138B=0;LS138C=0; break;
case 2:LS138A=0; LS138B=1;LS138C=0; break;
case 3:LS138A=1; LS138B=1;LS138C=0; break;
}
delay(150);
}
P0 = 0;
}
}
//延时程序
voiddelay(unsigned int i)
{
char j;
for(i; i > 0; i--)
for(j = 200; j > 0; j--);
}
未完待续。。。