我们要想在stm32上使用红外遥控器,那么肯定需要红外接收头,可以在淘宝搜到,红外遥控器和接收头配套也才几块钱出头。但是我们没办法直接使用它,单片机无法直接对红外信号进行解码,这就需要我们手动配置解码文件,今天跟大家一起来给红外遥控器所发出的红外信号进行解析。
一、NEC协议
要想对红外信号进行解析,我们就必须了解红外协议,本次的红外遥控器和接收头采用的是NEC协议,
1. 协议特征:
· 使用双向编码(又称曼彻斯特编码);
· 使用38K载波对编码后的波形进行调制;
· 位时间 1.12ms 或 2.25ms
2. 调制:
如图所示:
这边我在网上找了一张比较好理解的图给大家讲解NEC协议调制过程,我们没按下一次按键,就会发送红外信号,红外信号是由:地址码+地址码反码+指令码+指令码反码, 一共是32个字节组成。
值得注意的是NEC协议中规定
“1”:是由560us低电平+1690us的高电平代表“1”
“0”:是由560us低电平+560us的高电平代表“0”
开始信号:先给一个下降沿,加上9ms的低电平,在给个上升沿,加上4.5ms的高电平,就可以组成NEC协议的开始信号了。
重复指令:如果我们按下一个按键不松开,红外遥控器就会发送9ms低电平和2.25ms高电平的信号,每隔100ms重复发送一次,起到了连发的作用。
因此我们就可以通过时间对红外信号进行解析,相信不少的伙伴在这里已经有了思路,可以通过判断下降沿作为中断触发的条件,以高低电平的时间为解码方式。
二、硬件配置
本次采用的stm32f103c8t6型号的微控芯片,选择了0.96寸的OLED屏幕作为调试工具,红外接收头加上遥控器用来发射接收红外信号。
OLED显示是采用江科大stm32教程OLED屏幕那节课的接线方式,OLED代码也是用的江科大的,大家可以去网上找江科大OLED屏幕相关代码。
红外接收头正负极正常连接3.3v和GND,OUT口连接GPIO0
三、代码配置
1、中断配置
#include "stm32f10x.h"
void MyEXTI_Init(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 开启GPIOA和AFIO时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE);
// 配置PA0为输入模式
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; // 上拉输入
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 连接PA0到外部中断线0
GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);
// 配置外部中断线0
EXTI_InitStructure.EXTI_Line = EXTI_Line0;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; // 设置为中断模式
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; // 设置为下降沿触发
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
// 配置NVIC中断优先级
NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}//中断配置代码,配置GPIOA_pin0的外部中断
2、计数器配置
#include "stm32f10x.h" // Device header
void Counter_Init(void)//初始化TIM2计数器
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_InitStructure;
TIM_InitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_InitStructure.TIM_CounterMode=TIM_CounterMode_Up;
TIM_InitStructure.TIM_Period=65536-1;
TIM_InitStructure.TIM_Prescaler=72-1;
TIM_InitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_InitStructure);
TIM_Cmd(TIM2, DISABLE);
}
uint16_t Read_MyCounter(void)
{
return TIM_GetCounter(TIM2);
}
void Counter_Start(uint8_t command)//开始计数
{
if(command==0)
{
TIM_Cmd(TIM2, DISABLE);
}
else
{
TIM_Cmd(TIM2, ENABLE);
}
}
void Set_MyCounter(uint16_t counter)//置计数器存的数为counter
{
TIM_SetCounter(TIM2,counter);
}
3、NEC解码文件
#include "stm32f10x.h" // Device header
#include "Counter.h"
#include "MyEXTI.h"
uint16_t NECTime;//获取计数器值的变量
uint16_t NECState=0;//状态位
uint16_t RepetFlag;//重复位
uint8_t Data[4];//数据数组
uint8_t pData,A1,A2,D1,D2;//A1A2代表地址码和反码 D1D2代表指令码和反码
uint8_t NEC_Address;//地址
uint8_t NEC_Command;//指令
uint8_t NEC_DataFlag;
//初始化计数器和外部中断
void NEC_Init(void)
{
MyEXTI_Init();
Counter_Init();
}
//获取地址码
uint8_t Get_Address(void)
{
return NEC_Address;
}
//获取红外指令
uint8_t Get_Command(void)
{
return NEC_Command;
}
//返回按键值
uint16_t Get_Key(void)
{
u16 key;
key=NEC_Command;
switch(key)
{
case 0x16:
return 0;
case 0x0c:
return 1;
case 0x18:
return 2;
case 0x5e:
return 3;
case 0x08:
return 4;
case 0x1c:
return 5;
case 0x5a:
return 6;
case 0x42:
return 7;
case 0x52:
return 8;
case 0x4a:
return 9;
case 0x07:
return 10;
case 0x15:
return 11;
case 0x19:
return 12;
case 0x0d:
return 13;
default:
// 如果没有任何匹配的情况,返回一个默认值,比如 -1 表示无效键值
return 999;
}
}
//解码过程 NECTState状态标识: 为0的时候代表准备接收 为1代表判断开始状态
//为3开始接收数据
void EXTI0_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line0) == 1) // 检查是否为EXTI0触发的中断
{
if(NECState==0)//NECTState状态标识,当为0的时候会做准备接收工作
{
Set_MyCounter(0);//清空计数器
Counter_Start(1);//开启定时器
NECState=1;//将接收状态设置为1
}
else if(NECState==1)//接收开始信号
{
NECTime=Read_MyCounter();
Set_MyCounter(0);
if(NECTime<13500+500 && NECTime>13500-500)//正常开始
{
NECState=2;//准备接收信号
}
else if(NECTime<11250+500 && NECTime>11250-500) //连按
{
RepetFlag=1;
Counter_Start(0);
NECState=0;
}
else
{
NECState=1;//接收异常,继续接收
}
}
else if(NECState==2)//开始接收数据
{
NECTime=Read_MyCounter();
Set_MyCounter(0);
if(NECTime<1120+500 && NECTime>1120-500)//接收到数据0
{
Data[pData/8]&=~(0x01<<(pData%8));
pData++;
}
else if(NECTime<2250+500 && NECTime>2250-500)//接收到数据1
{
Data[pData/8]|=(0x01<<(pData%8));
pData++;
}
else
{
pData=0;
NECState=1;
}
if(pData>=32) //如果接收到了32位数据
{
pData=0;//数据位置指针清0
A1=Data[0],A2=Data[1],D1=Data[2],D2=Data[3];
NEC_Address=Data[0];//转存数据
NEC_Command=Data[2];
NEC_DataFlag=1; //置收到连发帧标志位为1
Counter_Start(0);//定时器停止
NECState=0; //置状态为0
}
}
EXTI_ClearFlag(EXTI_Line0);
}
}
4.main函数
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Delay.h"
#include "NEC.h"
#include "led.h"
uint16_t address;
uint16_t data;
uint16_t command;
uint8_t A[4];
uint8_t B,i,a;
void OLED_ON (void)
{
GPIO_ResetBits(GPIOA,GPIO_Pin_1);
}
void OLED_OFF (void)
{
GPIO_SetBits(GPIOA,GPIO_Pin_1);
}
int main()
{
LED_Init();
OLED_Init();
NEC_Init();
uint16_t key;
while(1)
{
command=Get_Key();
address=Get_Address();
data=Get_Command();
OLED_ShowBinNum(1,1,D1,8);//D1是在NEC.h文件声明外部可调用的D
OLED_ShowBinNum(2,1,D2,8);//它两都在NEC.c中定义的
OLED_ShowNum(3,1,command,2);
key=command;
if(key==1)
{
OLED_ON();
}
else
{
OLED_OFF();
}
}
}
调试完成后,就可以进行红外控制了,我上面演示的是红外遥控器控制小灯的亮灭,还是很丝滑的,大家可以用来控制其他设备,用起来挺方便的,但传输距离有限,但只要有遥控器,按一下按键就可以完成想要的功能,相比较蓝牙控制、wifi控制,红外的优势还是较为方便的。
补充:
红外遥控器的按键返回值实在NEC文件中,代码内容很简单,很多小伙伴有可能不知道自己红外遥控器的键码值对应的是什么指令信号,我们可以让指令码在在屏幕上面显示,通过按下每个按键看屏幕的值,这种方式确定自己的按键码。
我的红外遥控器也是随便找的,之前那个配套的丢了,然后就通过这种方式确定自己按键码,通过记录二进制数据来确定对应的按键