STM32F4驱动NEC协议的红外接收头

    红外遥控是一种无线、非接触式控制技术,具有抗干扰能力强、传输可靠、价格便宜、功耗低、易实现等优点。被很多的家用电器所采用。目前常用的红外遥控器协议有NEC协议(PWM脉冲宽度调制)、Philips RC5协议(PPM脉冲位置调制)。红外遥控分为发射端与接收端,发射端是以调制的方式发射数据,就是把数据和一定频率的载波进行“与操作”。调制载波频率一般为30KHz到60KHz之间。

    NEC协议的特点:8位地址和8位命令长度

                               为提高可靠性每次传输有地址码、地址码反码,键码、键码反码等用来检验

                                通过脉冲之间的时间间隔来实现信号的调制

                                38KHz载波频率

                                逻辑0的周期为1.12ms,逻辑1的周期为2.25ms

            

      NOTE:一个逻辑1传输需要2.25ms(560us脉冲+1680us低电平),一个逻辑0传输需要1.125ms(560us脉冲+560us低电平),而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样我们在接收头接收到的信号为:逻辑1(560us的低电平+1680us高电平),逻辑0(560us低电平+560us高电平)。

                    

        NOTE:上图为NEC协议的数据格式:引导码、地址码(用户码)、地址反码(用户反码)、键码、键码反码。引导码由9ms高电平+4.5ms低电平组成。数据按照低位在前,高位在后的顺序发送,反码的作用就是用来校验数据的准确性。如果一帧数据发送完毕后,还没有松开按键,则遥控器发送重复码,不会再发送引导码、地址码、键码了。(重复码由9ms低电平+2.5ms高电平+0.56ms低电平+97.94ms高电平组成)。如果接收到重复码,可以使用一个变量来计算按键按下的次数。

         了解到这些俺们就可以用STM32来驱动红外接收头了,通过使用STM32的输入捕获功能来测量高电平的脉宽,从而来判断遥控器发送过来的数据。以下是硬件连接图:


   接下来就来看看我们的软件部分:                                                                                                                                  

       首先我们先初始化GPIO、定时器、捕获、NVIC中断优先级等功能:一定要记得将GPIO设置为复用映射模式

/*==========================================================================

 #
 # 函 数 名:void Remote_Init(void)
 # 功能描述:初始化红外遥控所使用到的GPIO和外设
 # 输入参数:无
 # 输出参数:无
 # 返 回 值:无
 # 说    明:用到多少个中断源就需要初始化多少个NVIC
 #
==========================================================================*/
void Remote_Init(void)

GPIO_InitTypeDef GPIO_InitStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_ICInitTypeDef TIM_ICInitStruct;
NVIC_InitTypeDef NVIC_InitStruct;

RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);

GPIO_InitStruct.GPIO_Pin   = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Mode  = GPIO_Mode_AF; //复用模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP;  //推挽输出
GPIO_InitStruct.GPIO_PuPd  = GPIO_PuPd_UP; //上拉
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);

GPIO_PinAFConfig(GPIOA, GPIO_PinSource8, GPIO_AF_TIM1); //将PA8映射到TIM1

TIM_TimeBaseInitStruct.TIM_Prescaler = 167; //预分频系数
TIM_TimeBaseInitStruct.TIM_Period    = 10000; //预装载值
TIM_TimeBaseInitStruct.TIM_CounterMode   = TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1; //不分频
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseInitStruct);

TIM_ICInitStruct.TIM_Channel  = TIM_Channel_1;
TIM_ICInitStruct.TIM_ICFilter = 0x03; //IC1F=0003 8个定时器时钟周期滤波,连续采集8次都是高电平才有效
TIM_ICInitStruct.TIM_ICPolarity  = TIM_ICPolarity_Rising;
TIM_ICInitStruct.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入不分频
TIM_ICInitStruct.TIM_ICSelection = TIM_ICSelection_DirectTI; //映射到TI1上
TIM_ICInit(TIM1, &TIM_ICInitStruct);

TIM_ITConfig(TIM1, TIM_IT_Update | TIM_IT_CC1, ENABLE); //使能定时器溢出和捕获中断
TIM_Cmd(TIM1, ENABLE); //使能定时器

NVIC_InitStruct.NVIC_IRQChannel = TIM1_CC_IRQn; 
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority =3;
NVIC_Init(&NVIC_InitStruct);

NVIC_InitStruct.NVIC_IRQChannel = TIM1_UP_TIM10_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority =2;
NVIC_Init(&NVIC_InitStruct);

      初始化有关外设之后,我们就可以在捕获中断里面获取高电平的脉宽了,通过获取高电平持续的时间从而来判断发送过来的是引导码还是重复码以及用户码、键码的信息。在捕获到高电平后要马上设置为低电平捕获并清空计数器,在捕获到低电平后读取计数器的值然后设置为高电平捕获。注意:我们只需要捕获高电平持续的时间就可以了,低电平持续的时间则忽略。

//RmtStatu按键信息的标志位:[7]:引导码标志位,[6]:获取按键的所有信息标志位,
//[5]:保留,[4]:捕获到高电平标志位,[3:0]:定时器溢出的次数
u8 RmtStatu =0;   
u32 RmtSDA =0; //按键的地址码、键码
u16 RmtTime =0; //高电平持续时间
u8 RmtCnt =0; //按键连按的次数
/*==========================================================================
 #
 # 函 数 名:void TIM1_CC_IRQHandler(void) 
 # 功能描述:捕获中断服务函数,通过捕获到高电平的时间来得到按键引导码、地址码、键码、重复码等
 # 输入参数:无
 # 输出参数:无
 # 返 回 值:无
 # 说    明:无
 #
==========================================================================*/
void TIM1_CC_IRQHandler(void)
{
  if(TIM_GetITStatus(TIM1, TIM_IT_CC1) == SET)

if(REMOTE_IN)

TIM_OC1PolarityConfig(TIM1, TIM_OCPolarity_Low); //设置为低电平捕获
TIM_SetCounter(TIM1,0); //清空计数器
RmtStatu |=0x10;   //标记已经捕获到高电平

else 
{
RmtTime = TIM_GetCapture1(TIM1);  //完成一次捕获,获取高电平持续的时间
TIM_OC1PolarityConfig(TIM1, TIM_OCPolarity_High); //设置为高电平捕获

if(RmtStatu & 0x10)
{  
if(RmtStatu & 0x80)
{
if(RmtTime >300 && RmtTime <800)   //逻辑0,560us高电平
{
RmtSDA <<=1;
RmtSDA |=0;

}else if(RmtTime >1400 && RmtTime <1800) //逻辑1,1680us高电平
{
RmtSDA <<=1;
RmtSDA |=1;

}else if(RmtTime >2300 && RmtTime <2700) //重复码,2500us高电平
{
RmtCnt++;   //重复码,计算按键按下的次数
RmtStatu &=0xF0;
}
}
else if(RmtTime >4300 && RmtTime <4700) //引导码,4500us高电平

//printf("\r\n接收到引导码\r\n");
RmtStatu |=1<<7; //接收到引导码
RmtCnt =0; 
}
}
RmtStatu &= ~(1<<4); //清除捕获到高电平标志
}
}
TIM_ClearITPendingBit(TIM1,TIM_IT_CC1);
}

/*=========================================================================
 #
 # 函 数 名:void TIM1_UP_TIM10_IRQHandler(void) 
 # 功能描述:定时器中断服务函数,在中断里面判断是否完成按键信息的采集,每10ms溢出
 # 输入参数:无
 # 输出参数:无
 # 返 回 值:无
 # 说    明:在获取按键信息的的过程中不会产生计数器溢出中断,因为我们在捕获到高电平后都将计数器清0
 #
==========================================================================*/
void TIM1_UP_TIM10_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1,TIM_IT_Update) == SET) 

if(RmtStatu & 0x80)

RmtStatu &= ~0x10;     //清除捕获到高电平标志
if((RmtStatu & 0x0f) == 0x00) RmtStatu |= 1<<6;    //如果是第一次进入中断就标记完成按键信息的采集
    if((RmtStatu & 0x0f) <14) RmtStatu ++;
else 
{
RmtStatu &= ~(1<<7); //清除引导标识
RmtStatu &= 0xf0;  //清空计数器
}
  }
 }
  TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
}

/*==========================================================================
 #
 # 函 数 名:u8 Remote_Scan(void)
 # 功能描述:如果已经获取到了按键的全部信息,就校验地址码、地址反码和键码、键码反码,如果比
 # 对成功就返回键值,否则就认为没有按键按下
 # 输入参数:无
 # 输出参数:无
 # 返 回 值:KeyValue:0,没有按键按下
 # 其他,按键的键值
 # 说    明:无
 #
==========================================================================*/
u8 Remote_Scan(void)

u8 KeyValue =0;
u8 t1,t2;

if(RmtStatu & (1<<6))  //判断是否已经获取到了按键全部的信息

t1= RmtSDA >>24;
t2= RmtSDA >>16;
printf("%d\r\n",t1);   //打印用户码、用户码反码到串口助手显示
printf("%d\r\n",t2);
if(t1 == (u8)~t2) 

//printf("\r\n地址码比对正确\r\n");
t1 =RmtSDA >>8;
t2 =RmtSDA;

printf("%d\r\n",t1); //打印键码、键码反码到串口助手显示
printf("%d\r\n",t2);
if(t1 ==(u8)~t2) 
{
KeyValue = t1; 
//printf("\r\n键码比对正确\r\n");
}
}

if((KeyValue == 0) || ((RmtStatu & 0x80)==0))  //没有按键按下时
{
RmtStatu &= ~(1<<6); //清除按键全部信息标识
RmtCnt =0;
RmtSDA =0;
}
 }
 return KeyValue;

}

       在调试程序的过程中,由于是新建的工程,忘记了把System_stm32f4xx.c文件里的PLL第一级分频系数M修改为8(#define PLL_M 8)和stm32f4.h里面的外部时钟HSE_VALUE值修改为8MHz(#define HSE_VALUE((uint_32_t)8000000))了,导致捕获高电平脉宽的时候,计时器的值不准确,从而导致获取的信息不正确,在调了一上午之后才找出问题,心情是既高兴又坎坷啊,竟然犯了这么傻逼的错误。还有这是哥第一次写博客,感觉很新鲜、很好玩,也有很多不足之处,在以后写的时候会好好改正。希望能通过这种操作来加深一下记忆,分享下写程序过程中遇到的快乐&难题。

展开阅读全文

没有更多推荐了,返回首页