一、环境介绍
MCU: STM32F103ZET6
编程软件环境: keil5
红外线传输协议: NEC 协议---38KHZ 载波。NEC 协议是红外遥控协议中常见的一种。
解码思路: 外部中断 + 定时器方式
代码风格: 模块化编程,寄存器直接操作方式
二、NEC 协议与解码思路介绍
2.1 采用的相关硬件
图 1: 这是 NEC 协议的红外线遥控器: 如果自己手机没有红外线遥控器的功能,可以淘宝上买一个小遥控器来学习测试,成本不高,这个遥控器也可以自己做,能解码当然也可以编码发送,只需要一个红外光发射管即可。
图 2: 这是红外线接收头模块。如果自己的开发板没有自带这个接收头,那就单独买一个接收头模块,使用杜邦线接到开发板的 IO 口上即可用来测试学习,接线很方便。
图 3: 这是红外线发射管,如果自己想做遥控器的发射端,自己做遥控器,那么就可以直接购买这种模块即可。
2.2 红外线协议介绍
在光谱中波长自 760nm 至 400um 的电磁波称为红外线,它是一种不可见光。红外线通信的例子我们每个人应该都很熟悉,目前常用的家电设备几乎都可以通过红外遥控的方式进行遥控,比如电视机、空调、投影仪等,都可以见到红外遥控的影子。这种技术应用广泛,相应的应用器件都十分廉价,因此红外遥控是我们日常设备控制的理想方式。
红外线的通讯原理: 红外光是以特定的频率脉冲形式发射,接收端收到到信号后,按照约定的协议进行解码,完成数据传输,在消费类电子产品里,脉冲频率普遍采用 30KHz 到 60KHz 这个频段,NEC 协议的频率就是 38KHZ。 这个以特定的频率发射其实就可以理解为点灯,不要被复杂的词汇难住了,就是控制灯的闪烁频率(亮灭),和刚学单片机完成闪光灯一样的意思,只不过是灯换了一种类型,都是灯。 接收端的原理: 接收端的芯片对这个红外光比较敏感,可以根据有没有光输出高低电平,如果发送端的闪烁频率是有规律的,接收端收到后输出的高电平和低电平也是有规律对应的,这样发送端和接收端只要约定好,那就可以做数据传输了。
红外线传输协议可以说是所有无线传输协议里成本最低,最方便的传输协议了,但是也有缺点,距离不够长,速度不够快;当然,每个传输协议应用的环境不一样,定位不一样,好坏没法比较,具体要看自己的实际场景选择合适的通信方式。
2.3 NEC 协议介绍
NEC 协议是众多红外线协议中的一种(这里说的协议就是他们数据帧格式定义不一样,数据传输原理都是一样的),我们购买的外能遥控器、淘宝买的 mini 遥控器、电视机、投影仪几乎都是 NEC 协议。 像格力空调、美的空调这些设备使用的就是其他协议格式,不是 NEC 协议,但是只要学会一种协议解析方式,明白了红外线传输原理,其他遥控器协议都可以解出来。
下图是 NEC 协议传输一次数据的完整格式:
NEC 协议一次完整的传输包含: 引导码、8 位用户码、8 位用户反码、8 位数据码、8 位数据反码。
(注意:下面的解释都是站在红外线接收端的角度来进行说明的,就是解码端的角度)
引导码: 由 9ms 的高电平+4.5ms 的低电平组成。
4 个字节的数据: 用户码+用户反码+数据码+数据反码。 这里的反码可以用来校验数据是否传输正确,有没有丢包。
重点: NEC 协议传输数据位的时候,0 和 1 的区分是依靠收到的高、低电平的持续时间来进行区分的---这是解码关键。
标准间隔时间:0.56ms
收到数据位 0: 0.56ms
收到位 1: 1.68ms
所以,收到一个数据位的完整时间表示方法是这样的:
收到数据位 0: 0.56m 低电平+ 0.56ms 的高电平
收到数据位 1: 0.56ms 低电平+1.68ms 的高电平
红外线接收头模块输出电平的原理: 红外线接收头感应到有红外光就输出低电平,没有感应到红外光就输出高电平。
这是使用逻辑分析采集红外线接收头输出的信号:
这是采集红外线遥控器上的 LED 灯输出电平时序图,刚好和接收端相反:
单片机编写解码程序的时候,常见的方式就是采用外部中断+定时器的方式进行解析,中断可以设置为低电平触发,因为接收头没有感应到红外光默认是输出高电平,如果收到 NEC 引导码,就会输出低电平,进入到中断服务函数,完成解码,解码过程中开启定时器记录每一段的高电平、低电平的持续时间,按照 NEC 协议进行判断,完成最终解码。
STM32 可以使用输入捕获方式完成解码,其实输入捕获就是外部中断+定时器的组合,只不过是 STM32 内部封装了一层。
外部中断服务器里的解码程序如下(这个在其他单片机上思路是一样的):
/*
函数功能: 外部中断线9_5服务函数
*/
void EXTI9_5_IRQHandler(void)
{
u32 time;
u8 i,j,data=0;
//清除中断线9上的中断请求
EXTI->PR|=1<<9;
time=Infrared_GetTime_L(); //得到低电平时间
if(time<7000||time>10000)return; //标准时间: 9000us
time=Infrared_GetTime_H(); //得到高电平时间
if(time<3000||time>5500)return; //标准时间4500us
//正式解码NEC协议
for(i=0;i<4;i++)
{
for(j=0;j<8;j++)
{
time=Infrared_GetTime_L(); //得到低电平时间
if(time<400||time>700)return; //标准时间: 560us
time=Infrared_GetTime_H(); //得到高电平时间
if(time>1400&&time<1800) //数据1 1680us
{
data>>=1;
data|=0x80;
}
else if(time>400&&time<700) //数据0 560us
{
data>>=1;
}
else return;
}
InfraredRecvData[i]=data; //存放解码成功的值
}
//解码成功
InfraredRecvState=1;
}
复制代码
三、核心完整代码
本程序的解码思路是: 将红外线接收模块的输出脚接到 STM32 的 PB9 上,配置 STM32 的 PB9 为外部中断模式,下降沿电平触发;如果收到红外线信号就进入到中断服务函数里解码,如果解码过程中发现数据不符合要求就终止解码,如果数据全部符合要求就按照协议接收,直到解码完成,设置标志位,在 main 函数里打印解码得到的数据。
代码都是模块化编程,阅读起来也很方便。
3.1 红外线解码.c
#include "nec_Infrared.h"
u8 InfraredRecvData[4]; //存放红外线解码接收的数据
u8 InfraredRecvState=0; //0表示未接收到数据,1表示接收到数据
/*
函数功能: 红外线解码初始化(接收)
*/
void Infrared_RecvInit(void)
{
Infrared_Time6_Init(); //定时器初始化
/*1. 配置GPIO口*/
RCC->APB2ENR|=1<<3; //PB
GPIOB->CRH&=0xFFFFFF0F;
GPIOB->CRH|=0x00000080;
GPIOB->ODR|=1<<9;
/*2. 配置外部中断*/
EXTI->IMR|=1<<9; //外部中断线9,开放中断线的中断请求功能
EXTI->FTSR|=1<<9; //中断线9_下降沿
RCC->APB2ENR|=1<<0; //开启AFIO时钟
AFIO->EXTICR[2]&=~(0xF<<1*4);
AFIO->EXTICR[2]|=0x1<<1*4;
STM32_NVIC_SetPriority(EXTI9_5_IRQn,1,1);
}
/*
函数功能: 初始化定时器,用于红外线解码
*/
void Infrared_Time6_Init(void)
{
RCC->APB1ENR|=1<<4;
RCC->APB1RSTR|=1<<4;
RCC->APB1RSTR&=~(1<<4);
TIM6->PSC=72-1; //预分频器
TIM6->ARR=65535; //重装载寄存器
TIM6->CR1|=1<<7; //开启缓存功能
//TIMx->CR1|=1<<0; //开启定时器
}
/*
函数功能: 测量高电平持续的时间
*/
u32 Infrared_GetTime_H(void)
{
TIM6->CNT=0;
TIM6->CR1|=1<<0; //开启定时器
while(NEC_IR){} //等待高电平结束
TIM6->CR1&=~(1<<0); //关闭定时器
return TIM6->CNT;
}
/*
函数功能: 测量低电平持续的时间
*/
u32 Infrared_GetTime_L(void)
{
TIM6->CNT=0;
TIM6->CR1|=1<<0; //开启定时器
while(!NEC_IR){} //等待低电平结束
TIM6->CR1&=~(1<<0); //关闭定时器
return TIM6->CNT;
}
/*
函数功能: 外部中断线9_5服务函数
*/
void EXTI9_5_IRQHandler(void)
{
u32 time;
u8 i,j,data=0;
//清除中断线9上的中断请求
EXTI->PR|=1<<9;
time=Infrared_GetTime_L(); //得到低电平时间
if(time<7000||time>10000)return; //标准时间: 9000us
time=Infrared_GetTime_H(); //得到高电平时间
if(time<3000||time>5500)return; //标准时间4500us
//正式解码NEC协议
for(i=0;i<4;i++)
{
for(j=0;j<8;j++)
{
time=Infrared_GetTime_L(); //得到低电平时间
if(time<400||time>700)return; //标准时间: 560us
time=Infrared_GetTime_H(); //得到高电平时间
if(time>1400&&time<1800) //数据1 1680us
{
data>>=1;
data|=0x80;
}
else if(time>400&&time<700) //数据0 560us
{
data>>=1;
}
else return;
}
InfraredRecvData[i]=data; //存放解码成功的值
}
//解码成功
InfraredRecvState=1;
}
复制代码
3.2 主函数.c
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include "at24c02.h"
#include "W25Q64.h"
#include "spi.h"
#include "nec_Infrared.h"
int main()
{
LED_Init();
BEEP_Init();
KeyInit();
USARTx_Init(USART1,72,115200);
IIC_Init();
W25Q64_Init();
printf("芯片ID号:0x%X\n",W25Q64_ReadID());
Infrared_RecvInit();
while(1)
{
if(InfraredRecvState)
{
InfraredRecvState=0;
printf("用户码:%d,按键码:%d\n",InfraredRecvData[0],InfraredRecvData[2]);
printf("user反码:%d,key反码:%d\n",(~InfraredRecvData[1])&0xFF,(~InfraredRecvData[3])&0xFF);
BEEP=!BEEP;
LED0=!LED0;
}
}
}
复制代码
四、扩展提高
如果上面的 NEC 的解码思路已经看到,程序已经可以自己编写,就可以试着使用 STM32 的输入捕获+定时器方式写一版解码代码,既能更加熟悉 NEC 协议、也可以学习 STM32 定时器捕获捕获的用法;也可以做一些小东西来锻炼,比如:红外线遥控小车、音乐播放器支持红外线遥控器切歌,电机的开关、灯的开关等等。
搞定协议解码之后,我们下一步就是完成自定义的 NEC 协议红外线制作,采用 STM32 模拟一个万能红外线遥控器。
在光谱中波长自 760nm 至 400um 的电磁波称为红外线,它是一种不可见光。目前几乎所有的视频和音频设备都可以通过红外遥控的方式进行遥控,比如电视机、空调、影碟机等,都可以见到红外遥控的影子。这种技术应用广泛,相应的应用器件都十分廉价,因此红外遥控是我们日常设备控制的理想方式。
评论