目录
前言
本文主要介绍如何利用外部中断和定时器实现红外遥控功能代码,在上一篇文章已经简述过原理了,所以本文着重于应用,不讲原理。
原理部分可跳转http://t.csdnimg.cn/I28Md。
硬件
选用野火指南者开发板,本文用到的IO只需要带有外部中断功能的就行,另外配有一个基础定时器,本文为了方便写代码改为通用定时器。
引脚 | TIMx |
PE5 | TIM2 |
软件
本例程参考了江协科技的51红外遥控代码,利用状态机的思维,用4个8位字节来在存数据,响应外部中断后用定时器来捕获数据 ,注意配合NEC协议阅读代码会通俗易懂。
注:软件部分在看了网上很多例程,包括野火和正点的,野火采用的是外部中断+滴答定时器,在CPU足够的情况下当然没问题,但是移植到一些频率较低的芯片可能就会有掉包的情况。正点的代码看起来没问题,但是既然里面用到了位操作,为什么还会和野火一样不注意大小端,高低位反着来读。
1、初始化外部中断IO和定时器
参考时钟(pclock) | 自动重装载值(ARR) | 分频系数(PSC) |
72000000 | 20000-1 | 72-1 |
计数频率:CK_CNT=pclock/(PSC+1)=72000000/72=1000000次/s(计数一次用时1us)
计数器溢出频率:CK_CNT_OV=CK_CNT/(ARR+1)=50(1s溢出50次,每次溢出中断需要20ms)
void IR_Configuration(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
/* config the extiline clock and AFIO clock */
RCC_APB2PeriphClockCmd(IR_RCC_APB | RCC_APB2Periph_AFIO,ENABLE);
/* config the NVIC */
NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级2级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; //从优先级0级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
/* EXTI line gpio config */
GPIO_InitStructure.GPIO_Pin = IR_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_Init(IR_PORT, &GPIO_InitStructure);
/* EXTI line mode config */
GPIO_EXTILineConfig(IRDA_GPIO_PORT_SOURCE, IRDA_GPIO_PIN_SOURCE);
EXTI_InitStructure.EXTI_Line = IRDA_EXTI_LINE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿中断
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
}
void Timer_Init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
// 72/72000000=0.000001s,0.000001*20000=20ms=0.02s
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
TIM_TimeBaseInitStructure.TIM_Period = 20000 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 72 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
/*NVIC配置*/
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
2、外部接口
unsigned int IR_Time; //用于记录两次中断之间的时间
unsigned char IR_State; //记录状态机状态
unsigned char IR_Data[4]; //红外数据,4组
unsigned char IR_pData; //指向的数据地址,一般32位
unsigned char IR_DataFlag; //接收到数据
unsigned char IR_RepeatFlag; //接收到重复数据
unsigned char IR_Address; //用户码
unsigned char IR_Command; //命令码
/**
* @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;
}
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、红外接收中断,基本定时中断
uint16_t timer2_cnt;
void TIM2_IRQHandler(void) //通用定时器实现基本定时
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
timer2_cnt++;
}
void EXTI9_5_IRQHandler(void)
{
if(EXTI_GetITStatus(IRDA_EXTI_LINE) != RESET)
{
if(IR_State==0) //状态0,空闲状态
{
TIM_SetCounter(TIM2,0); //定时计数器清0
IR_State=1; //置状态为1
}
else if(IR_State==1) //状态1,等待Start信号或Repeat信号
{
IR_Time=TIM_GetCounter(TIM2); //获取上一次中断到此次中断的时间
TIM_SetCounter(TIM2,0); //定时计数器清0
if(IR_Time>13500-500 && IR_Time<13500+500)
{
IR_State=2; //置状态为2
}
else if(IR_Time>11250-500 && IR_Time<11250+500)
{
IR_RepeatFlag=1; //置收到连发帧标志位为1
TIM_SetCounter(TIM2,0); //定时器停止
IR_State=0; //置状态为0
}
else //接收出错
{
IR_State=1; //置状态为1
}
}
else if(IR_State==2) //状态2,接收数据
{
IR_Time=TIM_GetCounter(TIM2); //获取上一次中断到此次中断的时间
TIM_SetCounter(TIM2,0); //定时计数器清0
if(IR_Time>1120-500 && IR_Time<1120+500)
{
IR_Data[IR_pData/8]&=~(0x01<<(IR_pData%8)); //数据对应位清0
IR_pData++; //数据位置指针自增
}
else if(IR_Time>2250-500 && IR_Time<2250+500)
{
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; //数据位置指针清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];
IR_DataFlag=1; //置收到连发帧标志位为1
}
TIM_SetCounter(TIM2,0); //定时器停止
IR_State=0; //置状态为0
}
}
EXTI_ClearITPendingBit(IRDA_EXTI_LINE); //清除中断标志位
}
}
实验结果
分别按下按键打印出对应数据
总结
在前文和本文中,红外遥控代码分为两种,一种是输入捕获中断实现红外遥控,本文是用外部中断+定时器来实现,两种方式用到的方法是同样的,不同的是用到的外设不同,下面将会列举编写代码过程遇到的问题。
注意:
1、在多任务系统里:vTaskDelay(50); //200 时间太长了会导致数据还没找到就又来了一个数据,会导致按下红外按键就出现按下两次的情况。
2、在计数器计数时,需要注意最大计数值是多少,两次中断进来之间的数据不能超过最大计数值的值。
3、在需要及时把数据清除,不然会误以为有数据,出现连按两次的现象。
4、正点||野火:用1个32位data储存数据帧,注意原本是以小端模式存数据,在改回大端模式存数据后已正常,不然会导致数据高位和低位对换,可能会出现10-30分钟内自己按下红外按键的现象。
引用
本文代码参考江协科技51红外遥控代码,模板用的是野火STM32标准库模板。
Gitee资源下载链接(本人):https://gitee.com/huang_ze_rong/stm32_-code.git