IRDA红外通信协议是一种基于850-900nm波长红外线的短距离无线传输技术,由红外数据协会(IRDA)于1993年成立后制定标准,采用脉宽调制(PWM)和脉位调制(PPM)实现二进制信号传输。该协议通过发送端38KHz调制光脉冲、接收端解调电信号完成通信,典型传输距离为1-100厘米,具有定向角30度的直线传输特性 ,但由于红外线对非透明物体的透过性较差,实际传输距离常受环境物体阻隔影响。
本次就来研究一下IRDA红外遥控协议,板上有一个IRDA红外接收头,用于红外遥控控制输入。这里通过GPIO中断和定时器,测量脉冲的宽度和间隔,根据时序还原出二进制数据。
首先打开STM32CubeMX设置IRDA接收头GPIO边沿中断模式,以及定时器TIM6定时100us周期。

接下来编写获取红外接收脉冲时间捕获。具体代码如下:
#define IR_TICK_BUFF_LEN 256
union _IRDA_Data
{
uint32_t ir_code;
struct
{
uint8_t ir_data_l;
uint8_t ir_data_h;
uint8_t ir_addr_l;
uint8_t ir_addr_h;
}ir_data;
uint8_t ir_data_buff[8];
};
struct _IRDA
{
volatile uint16_t ir_tick_low : 10;
volatile uint16_t ir_tick_flag : 1;
volatile uint16_t ir_tick_ok : 1;
volatile uint16_t ir_tick_print : 1;
volatile uint16_t ir_press_print : 1;
volatile uint16_t ir_tick_index;
volatile uint16_t ir_tick_time[IR_TICK_BUFF_LEN];
volatile uint16_t ir_timetick;
volatile uint16_t ir_timetick_old;
volatile uint8_t ir_step : 3;
volatile uint8_t ir_status : 1;
volatile uint8_t ir_press;
volatile uint8_t ir_data_index;
union _IRDA_Data ir_buff;
}g_IrDA;
//定时器回调
//100us timetick
void IRDA_Timer100us_Handler(void)
{
g_IrDA.ir_timetick ++ ;
if((g_IrDA.ir_timetick - g_IrDA.ir_timetick_old) > 10000)
{
g_IrDA.ir_timetick = 0;
g_IrDA.ir_timetick_old = 0;
}else if((g_IrDA.ir_timetick - g_IrDA.ir_timetick_old) > 1000)
{
if(g_IrDA.ir_tick_index)
{
if(g_IrDA.ir_tick_ok == 0)
{
g_IrDA.ir_tick_ok = 1;
g_IrDA.ir_tick_print = 1;
}
}
g_IrDA.ir_tick_flag = 0;
g_IrDA.ir_step = 0;
g_IrDA.ir_press = 0;
}
}
//GPIO边沿中断回调
//IO外部下降沿中断调用
uint32_t IRDA_IOFallingIntReceive_Handler(void)
{
uint32_t Interval_time;
if(0 == g_IrDA.ir_tick_flag)
{
g_IrDA.ir_tick_flag = 1;
g_IrDA.ir_tick_ok = 0;
g_IrDA.ir_tick_index = 0;
g_IrDA.ir_tick_low = 0;
//g_IrDA.ir_timetick_old = g_IrDA.ir_timetick;
}
Interval_time = g_IrDA.ir_timetick - g_IrDA.ir_timetick_old;
g_IrDA.ir_timetick_old = g_IrDA.ir_timetick;
g_IrDA.ir_tick_time[g_IrDA.ir_tick_index % IR_TICK_BUFF_LEN] = Interval_time;
g_IrDA.ir_tick_index++;
return Interval_time;
}
//IO外部上升沿沿中断调用
uint32_t IRDA_IORisingIntReceive_Handler(void)
{
uint32_t Interval_time;
Interval_time = g_IrDA.ir_timetick - g_IrDA.ir_timetick_old;
g_IrDA.ir_tick_low = Interval_time;
g_IrDA.ir_tick_time[g_IrDA.ir_tick_index % IR_TICK_BUFF_LEN] = Interval_time;
g_IrDA.ir_tick_index++;
return Interval_time;
}
//中断处理
void HAL_GPIO_EXTI_Falling_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == IR_IN_Pin)
{
IRDA_IOIntReceivePraseCode_Handler(IRDA_IOFallingIntReceive_Handler()); //Falling edge
}
}
void HAL_GPIO_EXTI_Rising_Callback(uint16_t GPIO_Pin)
{
if(GPIO_Pin == IR_IN_Pin)
{
IRDA_IORisingIntReceive_Handler();
}
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
IRDA_Timer100us_Handler(); //100us
}
}
接下来通过shell命令测试红外接收捕获时间:
void IRDA_Init(void)
{
g_IrDA.ir_buff.ir_code = 0;
g_IrDA.ir_tick_low = 0;
g_IrDA.ir_tick_flag = 0;
g_IrDA.ir_tick_ok = 0;
g_IrDA.ir_tick_print = 0;
g_IrDA.ir_press_print = 0;
g_IrDA.ir_timetick = 0;
g_IrDA.ir_timetick_old = 0;
g_IrDA.ir_tick_index = 0;
g_IrDA.ir_data_index = 0;
g_IrDA.ir_press = 0;
g_IrDA.ir_status = 0;
g_IrDA.ir_step = 0;
}
//读红外按键编码
uint32_t IRDA_GetCode(uint32_t *Ir_num)
{
*Ir_num = 0;
if(g_IrDA.ir_status) //recvive ok
{
g_IrDA.ir_status = 0;
// if(
// (g_IrDA.ir_buff.ir_data.ir_addr_h + g_IrDA.ir_buff.ir_data.ir_addr_l) == 0xff
// (g_IrDA.ir_buff.ir_data.ir_data_h + g_IrDA.ir_buff.ir_data.ir_data_l) == 0xff
// )
{
*Ir_num = g_IrDA.ir_buff.ir_code;
return 1;
}
}
if(g_IrDA.ir_press & g_IrDA.ir_press_print)
{
*Ir_num = g_IrDA.ir_press;
g_IrDA.ir_press_print = 0;
return 2;
}
return 0;
}
#if UART_SHELL == 3
#include "lsy_shell_core.h"
//LSY_SHELL
int cmd_irda_control(int argc, char **argv)
{
uint32_t time,re;
uint32_t key;
printf("irda test 20s timeout.\r\n");
IRDA_Init();
HAL_TIM_Base_Start_IT(&htim6);
time = HAL_GetTick();
while(1)
{
if(g_IrDA.ir_tick_print)
{
g_IrDA.ir_tick_print = 0;
printf("irda tick: %d\r\n",g_IrDA.ir_tick_index);
for(int i=0;i<(g_IrDA.ir_tick_index%IR_TICK_BUFF_LEN);i++)
{
printf("%4d ",g_IrDA.ir_tick_time[i]);
}
printf("\r\n");
time = HAL_GetTick();
}
re = IRDA_GetCode(&key);
if(re)
{
printf("irda key = 0x%08X,re=%d\r\n",key,re);
}
if((HAL_GetTick() - time) > 20000)
{
printf("timeout.\r\n");
break;
}
}
return SHELL_OK;
}
LSY_SHELL_CMD_EXPORT(irda, cmd_irda_control, "test irda key data.");
#else
#endif
使用手头上风扇遥控器测试一下,输出接收的低电平脉冲时间和周期脉冲时间。
可以看到这个遥控器不是NEC协议,这里有6个字节。根据周期脉冲30100us和15100us左右时间区分0和1。前面有178*100us的前导标志。
通过获取时间间隔就可以解析出红外数据编码了。
//IO通过脉冲时间解析红外编码
void IRDA_IOIntReceivePraseCode_Handler(uint16_t plus_time)
{
//解析编码数据
switch(g_IrDA.ir_step)
{
case 0:
g_IrDA.ir_step = 1;
g_IrDA.ir_press = 0;
break;
case 1:
if( (plus_time >= 100)&&(plus_time <= 200))
{
g_IrDA.ir_step = 2;
}else
{
g_IrDA.ir_step = 0;
g_IrDA.ir_data_index = 0;
}
break;
case 2:
if( (plus_time >= 8)&&(plus_time <= 20))//1.5ms ->0
{
g_IrDA.ir_buff.ir_data_buff[g_IrDA.ir_data_index>>3] = g_IrDA.ir_buff.ir_data_buff[g_IrDA.ir_data_index>>3]<<1;
g_IrDA.ir_data_index++;
}else if( (plus_time>=21)&&(plus_time<=36) ) //3.0ms ->1
{
g_IrDA.ir_buff.ir_data_buff[g_IrDA.ir_data_index>>3] = g_IrDA.ir_buff.ir_data_buff[g_IrDA.ir_data_index>>3] << 1;
g_IrDA.ir_buff.ir_data_buff[g_IrDA.ir_data_index>>3] = g_IrDA.ir_buff.ir_data_buff[g_IrDA.ir_data_index>>3] | 0x01;
g_IrDA.ir_data_index++;
}else//error recv
{
g_IrDA.ir_step = 0;
g_IrDA.ir_buff.ir_code = 0;
g_IrDA.ir_data_index = 0;
}
if(g_IrDA.ir_data_index == 48)
{
g_IrDA.ir_status = 1; //recv ok
g_IrDA.ir_press = 1; //press key
g_IrDA.ir_press_print = 1;
g_IrDA.ir_step = 3;
g_IrDA.ir_data_index = 0;
}
break;
case 3:
if(plus_time < 1000) //100ms
{
if(g_IrDA.ir_press) g_IrDA.ir_press ++; //press
g_IrDA.ir_press_print = 1;
}else
{
g_IrDA.ir_step = 0;
}
break;
default :
g_IrDA.ir_step = 0;
break;
}
}

|