写在前面:
在前面我们学习了一些通信总线,例如:II2、UART等,这些通信均为有线通信(即通信双方有实际的线路连接)。今天我们学习一种无线通信技术——红外遥控通信 。我们利用红外遥控通信能够实现一些简单的功能,所涉及的知识包括:NEC编码以及51单片机的外部中断、LCD1602液晶显示屏;
目录
一、红外遥控介绍
1.1红外线简介
人的眼睛能看到的可见光按波长从长到短排列,依次为红、橙、黄、绿、青、 蓝、紫。其中红光的波长范围为 0.62~0.76μm;紫光的波长范围为 0.38~0.46 μm。比紫光波长还短的光叫紫外线,比红光波长还长的光叫红外线。红外线遥 控就是利用波长为 0.76~1.5μm 之间的近红外线来传送控制信号的。下图为常见的电磁波谱图。
1.2红外遥控通信简介
红外遥控:是利用红外线进行通信的设备,由红外LED调制后的信号发出,由专用的红外接头进行解调;
通信方式:单工、异步;
红外LED波长:94nm;
通信协议标准:NEC标准;
通信双方需要完成的内容:调制与解调;
由于红外线遥控不具有像无线电遥控那样穿过障碍物去控制被控对象的能力,所以,在设计红外线遥控器时,不必要像无线电遥控器那样,每套(发射器和接收器)要有不同的遥控频率或编码(否则,就会隔墙控制或干扰邻居的家用电器),所以同类产品的红外线遥控器,可以有相同的遥控频率或编码,而不会出现遥控信号“串门”的情况。这对于大批量生产以及在家用电器上普及红外线遥控提供了极大的方便。由于红外线为不可见光,因此对环境影响很小,再由红外光波动波长远小于无线电波的波长,所以红外线遥控不会影响其他家用电器,也不会影响临近的无线电设备。
调制与解调
对于红外发送设备与红外接收设备来说,最重要的进行调制与解调;对于发送设备来说,需要将发送的信息通过电平信号的高低进行传递,对于发送设备来说,只需要将对应的信号按照某种协议进行读取即可,那为什么还要进行调制和解调呢?
原因在于,如果对于有线设备来说,这种做法(仅依靠某种协议)进行通信毫无问题,但是对于无线通信来说,通信双方中间空气中可能夹杂着许多其他的红外波(例如太阳的辐射),这样就对有用的信号产生了一定的干扰;为了抗除这种干扰,我们对发送的信号在发送前进行调制,再接收后进行解调,这样就可以很大程度上防止其他的干扰;那么如何进行调制呢?
调制的原理就是将发送的信号电平,搭载在频率为38Khz的方波(称为载波)上,经过调制的信号进过发送设备进行发送;接收设备接收到信号后,先将38Khz信号进行解调,再按照协议进行读取;
1.3发送与接收设备
红外发射装置,也就是通常我们说的红外遥控器是由键盘电路、红外编码电 路、电源电路和红外发射电路组成。红外发射电路的主要元件为红外发光二极管。 它实际上是一只特殊的发光二极管;由于其内部材料不同于普通发光二极管,因 而在其两端施加一定电压时,它便发出的是红外线而不是可见光。目前大量的使 用的红外发光二极管发出的红外线波长为 940nm 左右,外形与普通发光二极管 相同。红外发光二极管有透明的,还有不透明的,在我们的红外遥控器上可以看 到这个红外发光二极管。
红外接收设备是由红外接收电路、红外解码、电源和应用电路组成。红外遥 控接收器的主要作用是将遥控发射器发来的红外光信好转换成电信号,再放大、 限幅、检波、整形,形成遥控指令脉冲,输出至遥控微处理器。
1.4 NEC编码
我们所使用的红外遥控器使用的使NEC协议,即遥控器发出来的信号电平是按照NEC协议进行发送的;
NEC协议特征:
1、基本发送与接收:
(发送设备)空闲状态:红外LED不亮,输出高电平(默认就是发送高电平);
发送低电平:红外LED以38KHZ发光,输出低电平;
发送高电平:红外LED不亮,输出高电平;
2、载波频率为38Khz;
3、发送的数据格式为:地址码+地址码反码+键码+键码反码;(共32位);数据低位在前,高位在后;
4、位时间为:1.125ms或2.25ms;
NEC时序图:
由上图可知,当我们按下一个按键后, 发送设备就会发送一串上述信号:开始信号+数据+连续重发;NEC 遥控指令的数据格式为:引导码、地址码、地址反码、控制码、控制反码。引导码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反 码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性(可用于校验)。
NEC 码还规定了连发码(由 9ms 低电平+2.5m 高电平+0.56ms ),如果在一帧数据发送完毕之后,红外遥控器按键仍然没有放开,则发射连发码,可以通过统计连发码的次数来标记按键按下的长短或次数;
1.5使用思维
如何让检测设备对于不同的信号进行检测呢?
其关键在于利用,信号中,每两个下降沿之间的间隔时序(时间长短)来判断其为引导码、数据位、以及重发位并且判断数据是0或是1;两者的区别也是在控制下降沿之间的时间;
但是每两个下降沿的时间是十分短的,如果依靠软件来进行计时,那么时间的准确性没有把握,也会导致收到的信号出错,因此,我们采用单片机的计数器进行计数,计数器计数的准确性高,能够准备记录两个下降沿的时间差,从而判断是什么信号;
此外,还需要定义三种状态:
当没有信号时持续高电平为状态0,接收到下降沿信号为状态1,检测到信号为起始信号,进入状态2(数据一定是跟在起始信号后面;)检测到连续按下信号返回状态0;起始信号检测完后,返回状态1,之后返回状态0;
二、硬件设计
本次涉及的硬件包括:
1、红外接收;
2、外部中断;
3、定时/计数器;
4、LCD1602;
红外接收电路图:
红外接收的输出引脚,刚好接入外部中断1,我们将中断的触发方式设置为下降沿触发,则触发中断后,进入中断,从而进入计数器进行计数。
三、软件设计
3.1 项目说明:
通过按下红外遥控器按键,在LCD1602液晶屏幕上显示对应的数字;
3.2 项目源码
main.c
#include <REGX52.H>//包含51头文件
#include "LCD1602.h"//包含液晶显示屏头文件
#include "IR.h"//包含红外模块头文件
unsigned char Command;//定义接收到的数据变量
void main()
{
LCD_Init();//LCD1602初始化
LCD_ShowString(1,1,"key_number:");
IR_Iint();//红外模块初始化
while(1)
{
if(IR_GetDataFlag() || IR_GetRepeatFlag())//判断数据接收完毕,并且重复按键也完毕
{
Command=IR_GetCommand();//显示接收到的数字
LCD_ShowHexNum(2,5,Command,2);
}
}
}
IR.c
#include <REGX52.H>//包含51头文件
#include "Time0.h"//包含定时器文头件
#include "Int0.h"//包含中断头文件
unsigned int IR_Time;//定义两个下降沿之间的距离变量
unsigned char IR_State;//定义状态变量
unsigned char IR_Data[4];//定义长度为4的数组,用于存放32位数据
unsigned char IR_PData;
unsigned char IR_DataFlag;//定义数据传送结束变量
unsigned char IR_RepeatFlag;//定义重复按键结束变量
unsigned char IR_Address;//定义地址变量
unsigned char IR_Command;//定义数据变量
void IR_Iint(void)//初始化函数
{
Timer0_Init();//定时器初始化
Int0_Init();//中断初始化
}
unsigned char IR_GetDataFlag()//返回数据结束标志函数
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
unsigned char IR_GetRepeatFlag()//返回重复按键结束标志
{
if(IR_RepeatFlag)
{
IR_RepeatFlag=0;
return 1;
}
return 0;
}
unsigned char IR_GetAddress(void)//返回地址函数
{
return IR_Address;
}
unsigned char IR_GetCommand(void)//返回数据函数
{
return IR_Command;
}
void Int0_Routine (void) interrupt 0//外部中断函数,当有下降沿时,进入中断
{
if(IR_State==0)//状态为0
{
Timer0_SetCounter(0);//定时器初值为0
Timer0_Run(1);//打开定时器
IR_State=1;//转变状态为1
}
else if(IR_State==1)//状态为1
{
IR_Time=Timer0_GetCounter();//获取定时器时间
Timer0_SetCounter(0);//定时器初值为0
if(IR_Time>(12442-500) && IR_Time<(12442+500))//判断时间为初始信号还是重发信号
{
IR_State=2;//若为初始信号,即将进入数据接收,即状态2
}
else if(IR_Time>10368-500&&IR_Time<10368+500)
{
IR_RepeatFlag=1;//若为重发信号,退回状态0
Timer0_Run(0);
IR_State=0;
}
else
{
IR_State=1;
}
}
else if(IR_State==2)状态为2
{
IR_Time=Timer0_GetCounter();//获取定时器时间
Timer0_SetCounter(0);//关闭定时器
if(IR_Time>1032-500 && IR_Time<1032+500)//判断是0还是1
{
IR_Data[IR_PData/8]&=~(0x01<<(IR_PData%8));
IR_PData++;
}
else if(IR_Time>2074-500 && IR_Time<2074+500)
{
IR_Data[IR_PData/8]|=(0x01<<(IR_PData%8));
IR_PData++;
}
else
{
IR_PData=0;
IR_State=1;
}
if(IR_PData>=32)//判断数据位数,满不满32位
{
IR_PData=0;
if((IR_Data[0]==~IR_Data[1])&&(IR_Data[2]==~IR_Data[3]))//判断数据是否正确
{
IR_DataFlag=1;
IR_Address=IR_Data[0];
IR_Command=IR_Data[2];
}
Timer0_Run(0);//关闭定时器
IR_State=0;//状态返回0;
}
}
}
Int0.c
#include <REGX52.H>
void Int0_Init(void)//中断初始化函数
{
IT0=1;
IE0=0;
EX0=1;
EA=1;
PX0=1;
}
Time0.c
#include <REGX52.H>
void Timer0_Init(void)//定时器/计数初始化函数
{
TMOD &=0XF0;
TMOD |=0X01;
TL0=0;
TH0=0;
TF0=0;
TR0=0;
}
void Timer0_SetCounter(unsigned int Value)//定义定时/计数器初始值
{
TH0=Value/256;
TL0=Value%256;
}
unsigned int Timer0_GetCounter(void)//返回定时/计数器值
{
return (TH0<<8)|TL0;
}
void Timer0_Run(unsigned char Flag)
{
TR0=Flag;
}
LCD1602.c
#include <REGX52.H>
//引脚配置:
sbit LCD_RS=P2^6; // RS引脚为数据/指令选择 1为数据,0为指令
sbit LCD_RW=P2^5; // RW引脚为读/写选择 1为读,0为写
sbit LCD_EN=P2^7; // EN引脚为使能 1为数据有效,下降沿执行命令
#define LCD_DataPort P0 //定义P0引脚为数据端口
//延迟函数的定义;LCD1602延时函数,12MHz调用可延时xms;
void LCD_Delay(unsigned int xms) //@12.000MHz
{
unsigned char i, j;
while(xms--)
{
i = 2;
j = 239;
do
{
while (--j);
} while (--i);
}
}
//写指令函数定义: LCD1602写指令函数
void LCD_WriteCommand(unsigned char Command)
{
LCD_RS=0;//选择为指令,1为数据,0为指令
LCD_RW=0;//选择为写, 1为读,0为写
LCD_DataPort=Command;//写入指令的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
// 写数据函数定义: LCD1602写数据函数
void LCD_WriteData(unsigned char Data)
{
LCD_RS=1; //选择为数据,1为数据,0为指令
LCD_RW=0; //选择为写, 1为读,0为写
LCD_DataPort=Data;//写入指数据的内容
LCD_EN=1; //使能脚E先上升沿写入
LCD_Delay(1);
LCD_EN=0; //使能脚E后负跳变完成写入
LCD_Delay(1);
}
//初始化函数定义: LCD1602屏幕初始化
void LCD_Init()
{
LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
LCD_WriteCommand(0x01);//光标复位,清屏
}
//LCD1602 进行清屏
void LCD_clear()
{
LCD_WriteCommand(0x01);
}
//设置光标位置
void LCD_SetCursor(unsigned char Line,unsigned char Column)//(行数1-2,列数1-16)
{
if(Line==1)
{
LCD_WriteCommand(0x80|(Column-1));
}
else if(Line==2)
{
LCD_WriteCommand(0x80|(Column-1+0x40));
}
}
// 字符串函数定义: LCD1602显示字符串
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
unsigned char i;
LCD_SetCursor(Line,Column);
for(i=0;String[i]!='\0';i++)
{
LCD_WriteData(String[i]);
}
}
// 字符数字函数定义: LCD1602显示数字
int Pow(int x,int y)
{
unsigned char i;
int result = 1;
for(i = 0; i < y; i++)
{
result *= x;
}
return result;
}
void LCD_ShowNumber(unsigned char Line,unsigned char Column,unsigned int number,unsigned char length)
{
unsigned char i;
unsigned char temp;
LCD_SetCursor(Line,Column);
for(i =length ; i > 0 ; i--)
{
temp = number/Pow(10,i-1)%10 + '0'; //循环将每一位都提取出来并转换为字符
LCD_WriteData(temp);
}
}
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
unsigned char i;
unsigned int Number1;
LCD_SetCursor(Line,Column);
if(Number>=0)
{
LCD_WriteData('+');
Number1=Number;
}
else
{
LCD_WriteData('-');
Number1=-Number;
}
for(i=Length;i>0;i--)
{
LCD_WriteData(Number1/Pow(10,i-1)%10+'0');
}
}
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
unsigned char i,SingleNumber;
LCD_SetCursor(Line,Column);
for(i=Length;i>0;i--)
{
SingleNumber=Number/Pow(16,i-1)%16;
if(SingleNumber<10)
{
LCD_WriteData(SingleNumber+'0');
}
else
{
LCD_WriteData(SingleNumber-10+'A');
}
}
}
3.3 项目现象
红外遥控视频
链接:https://pan.baidu.com/s/1GelVrbFc_9ojRLlI_Y9g8w
提取码:1022
总结:本节我们学习了红外遥控模块,通过红外遥控器,实现对应的数值在LCD1602屏幕的显示,主要内容包括:红外遥控的原理、调制与解调,NEC编码,对应电路设计等等;大家学习完后一定一定要自己练习练习;
创作不易,还望大家点赞支持!!!👍