目录
前言
由于日常工作需要,简略写一篇关于红外遥控的文章,方便日后理解和随时移植。本文通过野火指南者(STM32F103VET6单片机)实现红外遥控接收过程。
一、原理
1、简述
红外遥控一般分为发送和接收两种电路。红外遥控的发射电路是采用红外发光二极管来发出经过调制的红外光波,而不是可见光;红外接收电路由红外接收二极管、三极管或硅光电池组成,它们将红外发射器发射的红外光转换为相应的电信号,再送后置放大器。
简单来说,就是通过按下红外遥控器发送一段一连串经过调制的信号,在硬件端通过红外接收头接收这段信号并解调该段信号,形成一段脉冲信号,每一个红外按键都有不同的脉冲,让单片机去识别解码,下图为红外遥控工作流程图。
特点:
通信方式:单工,异步
红外LED波长:940nm
通信协议标准:NEC标准
2、协议
NEC协议采用PPM(Pulse Position Modulation,脉冲位置调制)的形式进行编码,也就是根据脉冲时间长短解码,数据的每一位(Bit)脉冲长度为560us,由38KHz的载波脉冲 (carrier burst) 进行调制,推荐的载波占空比为 1/3至 1/4。
逻辑位表示:
逻辑 "1" 脉冲时间为一个560us高电平+1690us的低电平,合计2.25ms。
逻辑 "0" 脉冲时间为一个560us高电平+560us的低电平,合计1.12ms。
数据格式:
地址码 +地址码反码 + 命令码 + 命令码反码
总格式:
引导码(13.5ms)+ 32位数据格式 + 重复码(11.25ms)
高低电平说明:
空闲状态:红外LED不亮,接收头输出高电平
发送低电平:红外LED以38kHz频率闪烁,接收头输出低电平
发送高电平:红外LED不亮,接收头输出高电平
二、硬件设备
1、红外发射设备
选用市面上比较常用的红外发光二极管,注意需要支持NEC协议,如下图所示,选用某厂商的红外遥控器,图中包含该遥控器的用户码和命令码。
2、红外接收设备
选用红外接收二极管vs1838b,工作电压2.7~5.5V,红外接收头只接收38K信号(误差范围内),我们把接收头看出一个转换器。遇到38K就输出低电平,没有遇到38K就被上拉成高电平。。
3、主控设备
选用野火指南者开发板,板载STM32F103VET6单片机,拥有两个基本定时器、两个高级定时器和4个通用定时器,可满足本文的需求。
本文使用该板子上TIM3定时器的通道CH2,拥有输入捕获和基本定时器功能,根据需要捕获红外接收头接收到解调后的脉冲。
引脚 | TIMx | 通道 |
PA7 | TIM3 | CH2 |
三、软件实现
主要要点:设置输入捕获为下降沿触发中断;编写中断函数,利用状态机的思维配合NEC协议来看就会通俗易懂;数据以大端模式存取,即低地址存放在高位、高地址存放在低位。
1、初始化GPIO和TIM外设
为了达到最长的计数次数13500次,设置ARR的值为20000,即20ms溢出
计数频率:72/72000000=0.000001s
溢出时间:0.000001*20000=20ms=0.02s
//红外遥控使用的GPIO及时钟
#define IR_RCC_APB RCC_APB2Periph_GPIOA
#define IR_PORT GPIOA
#define IR_PIN GPIO_Pin_7
void IR_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB2PeriphClockCmd(IR_RCC_APB, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //TIM3 时钟使能
GPIO_InitStructure.GPIO_Pin = IR_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(IR_PORT, &GPIO_InitStructure);
GPIO_SetBits(IR_PORT, IR_PIN);
TIM_DeInit(TIM3);
TIM_TimeBaseStructure.TIM_Period = 20000; //设定计数器自动重装值 最大20ms溢出
TIM_TimeBaseStructure.TIM_Prescaler =(72-1); //预分频器,1M的计数频率,1us加1.
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx
TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; // 选择输入端 IC2映射到TI3上
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; //下降沿捕获
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
TIM_ICInitStructure.TIM_ICFilter = 0x03;//IC2F=0011 配置输入滤波器 8个定时器时钟周期滤波
TIM_ICInit(TIM3, &TIM_ICInitStructure);//初始化定时器输入捕获通道
TIM_ITConfig( TIM3,TIM_IT_Update|TIM_IT_CC2,ENABLE);//允许更新中断 ,允许CC2IE捕获中断
TIM_Cmd(TIM3,ENABLE ); //使能定时器3
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //抢占优先级1级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级2级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
}
2、函数接口
/**
* @brief 红外遥控获取收到数据帧标志位
* @param 无
* @retval 是否收到数据帧,1为收到,0为未收到
*/
unsigned char IR_GetDataFlag(void)
{
if(IR_DataFlag)
{
IR_DataFlag=0;
return 1;
}
return 0;
}
/**
* @brief 红外遥控获取收到连发帧标志位
* @param 无
* @retval 是否收到连发帧,1为收到,0为未收到
*/
unsigned char IR_GetRepeatFlag(void)
{
if(IR_RepeatFlag)
{
IR_RepeatFlag=0;
return 1;
}
return 0;
}
/**
* @brief 红外遥控获取收到的地址数据
* @param 无
* @retval 收到的地址数据
*/
unsigned char IR_GetAddress(void)
{
return IR_Address;
}
/**
* @brief 红外遥控获取收到的命令数据
* @param 无
* @retval 收到的命令数据
*/
unsigned char IR_GetCommand(void)
{
return IR_Command;
}
/**
* @brief 红外扫描函数
* @param 无
* @retval 无
*/
void IR_Scan(void)
{
unsigned char Command;
if(IR_GetDataFlag() ) //||IR_GetRepeatFlag()
{
Command=IR_GetCommand();
printf("Command=%x\r\n",Command);
switch(Command)
{
case IR_CH_MINUS:
break;
case IR_CH_ADD:
break;
case IR_MODE:
break;
}
}
}
3、红外接收中断函数
void TIM3_IRQHandler(void)
{
//下降沿捕获触发中断
if(TIM_GetITStatus(TIM3,TIM_FLAG_CC2)!=RESET)
{
if(IR_State==0) //状态0,空闲状态
{
TIM_SetCounter(TIM3,0); //定时计数器清0
IR_State=1; //置状态为1
}
else if(IR_State==1) //状态1,等待Start信号或Repeat信号
{
IR_Time=TIM_GetCapture2(TIM3); //获取上一次中断到此次中断的时间
TIM_SetCounter(TIM3,0);
if(IR_Time>13500-500 && IR_Time<13500+500) //如果计时为13.5ms,则接收到了Start信号
{
IR_State=2; //置状态为2
}
else if(IR_Time>11250-300 && IR_Time<11250+300) //如果计时为11.25ms,则接收到了Repeat信号
{
IR_RepeatFlag=1; //置收到连发帧标志位为1
TIM_SetCounter(TIM3,0); //定时器计数清0
IR_State=0; //置状态为0
}
else //接收出错
{
IR_State=1;
}
}
else if(IR_State==2) //状态2,接收数据
{
IR_Time=TIM_GetCapture2(TIM3);
TIM_SetCounter(TIM3,0);
if(IR_Time>1120-500 && IR_Time<1120+500) //如果计时为1120us,则接收到了数据0
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //写0
IR_pData++; //数据位置指针自增
}
else if(IR_Time>2250-500 && IR_Time<2250+500) //如果计时为2250us,则接收到了数据1
{
IR_Data[IR_pData/8]|=(0x01<<(IR_pData%8)); //写1
IR_pData++;
}
else
{
IR_pData=0; //数据位置指针清0
IR_State=1; //置状态为1
}
if(IR_pData>=32) //如果接收到了32位数据
{
IR_pData=0;
if((IR_Data[0]==(uint8_t)~IR_Data[1]) && (IR_Data[2]==(uint8_t)~IR_Data[3])) //数据验证
{
IR_Address=IR_Data[0]; //转存数据
IR_Command=IR_Data[2];
if(IR_Address==0)IR_DataFlag=1; //置收到连发帧标志位为1
else IR_DataFlag=0;
// printf("IR_Address=%d\r\n",IR_Address); //注意华为盒子遥控器IR_Address是0x22
}
TIM_SetCounter(TIM3,0);
IR_State=0;
}
}
}
TIM_ClearITPendingBit(TIM3,TIM_IT_Update|TIM_IT_CC2);
}
4、主函数
/**
* @brief 主函数
* @param 无
* @retval 无
*/
int main(void)
{
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置中断优先级分组为组2:2位抢占优先级,2位响应优先级
USART_Config();
IR_Configuration();
for(;;)
{
IR_Scan();
}
}
四、实验现象
按下CH-键,串口打印显示十六进制的45,符合协议。
示波器分析:
注意大端读取数据
如果长按就会出现重复码,波长为11.25ms