26.1.1 红外线简介
人的眼睛能看到的可见光按波长从长到短排列,依次为红、橙、黄、绿、青、蓝、紫。其中红光的波长范围为 0.62~0.76μm;紫光的波长范围为 0.38~0.46μm。比紫光波长还短的光叫紫外线,比红光波长还长的光叫红外线。红外线遥控就是利用波长为 0.76~1.5μm 之间的近红外线来传送控制信号的。
红外遥控的原理
红外遥控是一种无线、非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著优点,被诸多电子设备特别是家用电器广泛采用,并越来越多的应用到计算机系统中。
由于红外线遥控不具有像无线电遥控那样穿过障碍物去控制被控对象的能力,所以,在设计红外线遥控器时,不必要像无线电遥控器那样,每套(发射器和接收器)要有不同的遥控频率或编码(否则,就会隔墙控制或干扰邻居的家用电器),所以同类产品的红外线遥控器,可以有相同的遥控频率或编码,而不会出现遥控信号“串门”的情况。这对于大批量生产以及在家用电器上普及红外线遥控提供了极大的方便。由于红外线为不可见光,因此对环境影响很小,再由红外光波动波长远小于无线电波的波长,所以红外线遥控不会影响其他家用电器,也不会影响临近的无线电设备。
红外遥控通信系统一般由红外发射装置和红外接收设备两大部分组成。
红外发射装置
红外发射装置,也就是通常我们说的红外遥控器是由键盘电路、红外编码电路、电源电路和红外发射电路组成。红外发射电路的主要元件为红外发光二极管。它实际上是一只特殊的发光二极管;由于其内部材料不同于普通发光二极管,因而在其两端施加一定电压时,它便发出的是红外线而不是可见光。目前大量的使用的红外发光二极管发出的红外线波长为 940nm 左右,外形与普通发光二极管相同。红外发光二极管有透明的,还有不透明的,在我们的红外遥控器上可以看到这个红外发光二极管。红外遥控器和红外发光二极管如下图所示:
通常红外遥控为了提高抗干扰性能和降低电源消耗,红外遥控器常用载波的方式传送二进制编码,常用的载波频率为 38kHz,这是由发射端所使用的 455kHz晶振来决定的。在发射端要对晶振进行整数分频,分频系数一般取 12,所以455kHz÷12≈37.9kHz≈38kHz。也有一些遥控系统采用 36kHz、 40 kHz、 56 kHz等,一般由发射端晶振的振荡频率来决定。所以,通常的红外遥控器是将遥控信号(二进制脉冲码)调制在 38KHz 的载波上,经缓冲放大后送至红外发光二极管,转化为红外信号发射出去的。
二进制脉冲码的形式有多种,其中最为常用的是 NEC Protocol 的 PWM 码
(脉冲宽度调制)和 Philips RC-5 Protocol 的 PPM 码(脉冲位置调制码,脉冲串之间的时间间隔来实现信号调制)。如果要开发红外接收设备,一定要知道红外遥控器的编码方式和载波频率,我们才可以选取一体化红外接收头和制定解码方案。我们配套的红外遥控器使用的是 NEC 协议,其特征如下:
- 8 位地址和 8 位指令长度;
- 地址和命令 2 次传输(确保可靠性)
- PWM 脉冲位置调制,以发射红外载波的占空比代表“0”和“1”;
- 载波频率为 38Khz;
- 位时间为 1.125ms 或 2.25ms;
NEC 码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要2.25ms(560us 脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us脉冲+560us 低电平)。而红外接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,我们在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。所以可以通过计算高电平时间判断接收到的数据是 0 还是 1。NEC 码位定义时序图如下图所示:
NEC 遥控指令的数据格式为:引导码、地址码、地址反码、控制码、控制反码。引导码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可用于校验)。数据格式如下:
NEC 码还规定了连发码(由 9ms 低电平+2.5m 高电平+0.56ms 低电平
+97.94ms 高电平组成),如果在一帧数据发送完毕之后,红外遥控器按键仍然没
有放开,则发射连发码,可以通过统计连发码的次数来标记按键按下的长短或次
数。
红外接收设备
红外接收设备是由红外接收电路、红外解码、电源和应用电路组成。红外遥控接收器的主要作用是将遥控发射器发来的红外光信好转换成电信号,再放大、限幅、检波、整形,形成遥控指令脉冲,输出至遥控微处理器。近几年不论是业余制作还是正式产品,大多都采用成品红外接收头。成品红外接收头的封装大致有两种:一种采用铁皮屏蔽;一种是塑料封装。均有三只引脚,即电源正( VDD)、电源负(GND)和数据输出(VOUT)。其外观实物图如下图所示:
正对接收头的凸起处看,从左至右,管脚依次是 1:VOUT,2:GND,3:VDD。由于红外接收头在没有脉冲的时候为高电平,当收到脉冲的时候为低电平,所以可以通过外部中断的下降沿触发中断,在中断内通过计算高电平时间来判断接收到的数据是 0 还是 1。
LCD1602 介绍
LCD1602 简介
1602 液晶也叫 1602 字符型液晶,它能显示 2 行字符信息,每行又能显示 16个字符。它是一种专门用来显示字母、数字、符号的点阵型液晶模块。它是由若干个 5x7 或者 5x10 的点阵字符位组成,每个点阵字符位都可以用显示一个字符,每位之间有一个点距的间隔,每行之间也有间隔,起到了字符间距和行间距的作用,正因为如此,所以它不能很好的显示图片。其实物图如下所示:
在上图中可以看到有 16 个管脚孔,从左至右管脚编号顺序是 1-16,其功能定义如下所示:
编号 | 符号 | 引脚说明 |
---|---|---|
1 | VSS | 电源地 |
2 | VDD | 电源正极 |
3 | VL | 液晶显示偏压信号 |
4 | RS | 数据/命令选择端(H/L) |
5 | R/W | 读/写选择端(H/L) |
6 | E | 使能信号 |
7 | D0 | Data 1/0 |
8 | D1 | Data 1/0 |
9 | D2 | Data 1/0 |
10 | D3 | Data 1/0 |
11 | D4 | Data 1/0 |
12 | D5 | Data 1/0 |
13 | D6 | Data 1/0 |
14 | D7 | Data 1/0 |
15 | BLA | 背光源正极 |
16 | BLK | 背光源负极 |
下面对几个管脚做下说明:
3 脚:VL,液晶显示偏压信号,用于调整 LCD1602 的显示对比度,一般会外接电位器用以调整偏压信号,注意此脚电压为 0 时可以得到最强的对比度。
4 脚:RS,数据/命令选择端,当此脚为高电平时,可以对 1602 进行数据字节的传输操作,而为电平时,则是进行命令字节的传输操作。命令字节,即是用来对 LCD1602 的一些工作方式作设置的字节;数据字节,即使用以在 1602 上显示的字节。值得一提的是,LCD1602 的数据是 8 位的。
5 脚:R/W,读写选择端。当此脚为高电平可对 LCD1602 进行读数据操作,反之进行写数据操作。
6 脚:E,使能信号,其实是 LCD1602 的数据控制时钟信号,利用该信号的上升沿实现对 LCD1602 的数据传输。
7~14 脚:8 位并行数据口,而 51 单片机一组 IO 也是 8 位,使得对 LCD1602的数据读写大为方便。
在 LCD1602 内部含有 80 个字节的 DDRAM,它是用来寄存显示字符的。其地址和屏幕的对应关系如下表:
从上图可知,不是所有的地址都可以直接用来显示字符数据,只有第一行中的 00-0F,第二行中的 40-4F 才能显示,其他地址只能用于存储。要显示字符时要先输入显示字符地址,也就是告诉模块在哪里显示字符,例如第二行第一个字符的地址是 40H,那么是否直接写入 40H 就可以将光标定位在第二行第一个字符的位置呢?这样不行,因为写入显示地址时要求最高位 D7 恒定为高电平 1所以实际写入的数据应该是 01000000B(40H)
+10000000B(80H)=11000000B(C0H)。在 1602 中我们就用前 16 个就行了。第二
行也一样用前 16 个地址。
LCD1602 常用指令
在使用 LCD1602 时,我们需要掌握一些常用的指令,这些指令对于 LCD1602初始化是必须的。
清屏指令
- 清除液晶显示器,即将 DDRAM 的内容全部填入"空白"的 ASCII 码 20H;
- 光标归位,即将光标撤回液晶显示屏的左上方;
- 将地址计数器(AC)的值设为 0。
模式设置指令
设定每次写入 1 位数据后光标的移位方向,并且设定每次写入的一个字符是否移动。
I/D:0=写入新数据后光标左移 1=写入新数据后光标右移
S:0=写入新数据后显示屏不移动 1=写入新数据后显示屏整体右移 1 个字符
显示开关控制指令
控制显示器开/关、光标显示/关闭以及光标是否闪烁。
D:0=显示功能关 1=显示功能开
C:0=无光标 1=有光标
B:0=光标闪烁 1=光标不闪烁
功能设定指令
设定数据总线位数、显示的行数及字型。
DL:0=数据总线为 4 位 1=数据总线为 8 位
N:0=显示 1 行 1=显示 2 行
F:0=5×7 点阵/每字符 1=5×10 点阵/每字符
LCD1602 使用
要使用 LCD1602,首先需要对其初始化,即通过写入一些特定的指令实现。
然后选择要在 LCD1602 的哪个位置显示并将所要显示的数据发送到 LCD 的DDRAM。使用 LCD1602 通常都是用于写数据进去,很少使用读功能。LCD1602 操
作步骤如下所示:
- 初始化
- 写命令(RS=L),设置显示坐标
- 写数据(RS=H)
在此,不需要读出它的数据的状态或者数据本身。所以只需要看两个写时序:
①当要写指令字,设置 LCD1602 的工作方式时:需要把 RS 置为低电平,RW置为低电平,然后将数据送到数据口 D0~D7,最后 E 引脚一个高脉冲将数据写入。
②当要写入数据字,在 1602 上实现显示时:需要把 RS 置为高电平,RW 置为低电平,然后将数据送到数据口 D0~D7,最后 E 引脚一个高脉冲将数据写入。写指令和写数据,差别仅仅在于 RS 的电平不一样而已。以下是 LCD1602 的时序图:
从上图可以看到,以上给的时间参数全部是 ns 级别的,而 51 单片机的机器周期是 1us,指令周期是 2-4 个机器周期,所以即便在程序里不加延时程序,也可以很好的配合 LCD1602 的时序要求了。
当要写命令字节的时候,时间由左往右,RS 变为低电平,R/W 变为低平,注意看是 RS 的状态先变化完成。然后这时,DB0~DB7 上数据进入有效阶段,接着 E 引脚有一个整脉冲的跳变,接着要维持时间最小值为 tpw=400ns 的 E 脉冲宽度。然后E引脚负跳变,RS电平变化,R/W 电平变化。这样便是一个完整的LCD1602写命令的时序。
注意:这里介绍的是 8 位 LCD1602。
LCD1602.c
#include <REGX52.H>
#include <stdio.h>
#include "LCD1602.h"
#include "delay.h"
sbit LCD1602_RS=P2^6;
sbit LCD1602_RW=P2^5;
sbit LCD1602_EN=P2^7;
#define LCD_DataPort P0
//дָÁî
void LCD1602_Write_Cmd(unsigned char cmd)
{
LCD1602_RS=0; //Ñ¡ÔñÃüÁî
LCD1602_RW=0; //Ñ¡Ôñд
LCD1602_EN=0;
LCD_DataPort=cmd; //×¼±¸ÃüÁî
delay_ms(1);
LCD1602_EN=1; //ʹÄܽÅEÏÈÉÏÉýÑØдÈë
delay_ms(1);
LCD1602_EN=0; //ʹÄܽÅE¸ºÌø±äÍê³ÉдÈë
}
//дÊý¾Ý
void LCD1602_Write_Data(unsigned char dat)
{
LCD1602_RS=1; //Ñ¡ÔñÊý¾Ý
LCD1602_RW=0; //Ñ¡Ôñд
LCD1602_EN=0;
LCD_DataPort=dat; //×¼±¸Êý¾Ý
delay_ms(1);
LCD1602_EN=1;
delay_ms(1);
LCD1602_EN=0;
}
//³õʼ»¯LCD1602
void LCD1602_Init(void)
{
LCD1602_Write_Cmd(0x38); //Êý¾Ý×ÜÏß8룬ÏÔʾ2ÐÐ
LCD1602_Write_Cmd(0x38);
LCD1602_Write_Cmd(0x38);
LCD1602_Write_Cmd(0x38);
LCD1602_Write_Cmd(0x0c); //ÏÔʾ¹¦ÄÜ¿ª£¬ÎÞ¹â±ê£¬¹â±êÉÁ˸
LCD1602_Write_Cmd(0x06); //дÈëÐÂÊý¾Ýºó¹â±êÓÒÒÆ£¬ÏÔʾÆÁ²»Òƶ¯
LCD1602_Write_Cmd(0x01); //ÇåÆÁ
}
//ÇåÆÁ
void LCD1602_Clear(void)
{
LCD1602_Write_Cmd(0x01);
}
//ÉèÖùâ±êλÖÃ
void LCD1602_Set_Cursor(unsigned char Line,unsigned char Column)
{
if(Line==1)
{
LCD1602_Write_Cmd(0x80|(Column-1));
}
else if(Line==2)
{
LCD1602_Write_Cmd(0x80|(Column-1+0x40));
}
}
//ÏÔʾ×Ö·û
void LCD1602_Show_Char(unsigned char Line,unsigned char Column,char Char)
{
LCD1602_Set_Cursor(Line,Column);
LCD1602_Write_Data(Char);
}
//ÏÔʾ×Ö·û´®
void LCD1602_Show_String(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD1602_Set_Cursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD1602_Write_Data(String[i]);
}
}
int LCD1602_Pow(int X,int Y)
{
unsigned char i;
int Result=1;
for(i=0;i<Y;i++)
{
Result*=X;
}
return Result;
}
void LCD1602_Show_Num(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i;
LCD1602_Set_Cursor(Line,Column);
for(i=Length;i>0;i--)
{
LCD1602_Write_Data(Number/LCD1602_Pow(10,i-1)%10+'0');
}
}
void LCD1602_Show_Hex_Num(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD1602_Set_Cursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/LCD1602_Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD1602_Write_Data(SingleNumber+'0');
}
else
{
LCD1602_Write_Data(SingleNumber-10+'A');
}
}
}
void LCD1602_Show_Float_Num(unsigned char Line,unsigned char Column,float sum)
{
char a[16];
sprintf(a,"%.2f",sum);
LCD1602_Show_String(Line,Column,a);
}