从红外接收头接收到的编码可以看出,-帧编码以引导码开始,然后跟随32位的系统和数据码。引导码是以下降沿开始,按照输入捕获的原理,在引导码的第一个下降沿到来时,将此时的计数值捕获,第二个下降沿到来时,再次捕获计数值,计算第二次 的计数值和第一次计数值的差值,如果差值时间在13.5ms附近(编码时间上会有一定误差),则认为是引导码,例如时间差Δt在13ms-14ms之间,则认为是引导码。同理,如果时间差Δt在1ms-1.3ms之间,则认为时编码‘0’,如果时间差Δt在2ms-2.5ms之间,则认为时编码‘1’。 还要注意的是,计数差值单位是个数,而两次红外编码两次下降沿时间间隔单位为ms,它们相比较需要单位统一,因此要将计数差值转换为ms或者将ms转换成差值个数,转换时需要用到定时器驱动时钟的周期,如果将ms转换为个数,则只需要将两次下降沿时间差除以时钟周期即可得到个数,如果将个数转换为ms,则只需将差值个数乘以时钟周期即可得到ms的单位时间差。
接收端接收到码除引导码外有32位二进制码组成,其中前16位为用户识别码,能区别不同的电器设备,防止不同机种遥控码互相干扰。后16位为8位操作码(功能码)及其反码,可以进行编码的校验。发射端发射的码和接收端的码波形是相反的。遥控器在按键按下后,周期性地发出同一种32位二进制码,周期约为108ms。一组码本身的持续时间随它包含的二进制“0”和“1”的个数不同而不同,大约在45-63ms之间, 下图为接收波形图。
当一个键按下超过36ms,振荡器使芯片激活,将发射一组108ms的编码脉冲,这108ms发射代码由一个引导码(9ms) ,一个结果码(4. 5ms) ,低8位地址码(9ms-18ms) ,高8位地址码(9ms-18ms) , 8位数据码(9ms-18ms)和这8位数据的反码(9ms-18ms) 组成。如果键按下超过108ms仍未松开,接下来发射的代码(连发码)将仅由起始码(9ms) 和结束码(2. 25ms)组成。
STM32F4除了定时5和6不能捕获外,其他的定时器都可一进行捕获,即捕获脉冲波形的下降沿或者是上升沿。 下图是定时器捕获红外编码原理,其中定时器先进行(168-1)的分频,时钟频率为84MHz,即2us定时器加1,定时器自动重装为0xFFFF,即溢出时间约为65536*2us = 131ms,以使得定时器可以在1-2个定时器溢出周期获得红外编码。 定时器捕获共有两种情况,第一种情况是在一个定时器溢出周期能够采集完,即两次捕获的下降沿时间为t2 - t1,第二种情况是在第二个定时器溢出周期获得红外编码,那么他的捕获下降沿的时间差为0xFFFF - t3 + t4。
下面是定时器的的配置函数 Remote_Init.采用定时器3的channel3进行捕获,并对IO口进行复用,系统时钟为84MHz,溢出时间为2us。
//红外遥控初始化 //设置IO以及TIM3_CH3的输入捕获 void Remote_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; NVIC_InitTypeDef NVIC_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB2PeriphClockCmd(RCC_AHB1Periph_GPIOB,ENABLE); //使能PORTB时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //TIM3 时钟使能 GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; // PB0 输入 GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; // 上拉输入 GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP; GPIO_Init(GPIOB, &GPIO_InitStructure); GPIO_ResetBits(GPIOB, GPIO_Pin_0); //初始化GPIOB0 GPIO_PinAFConfig(GPIOB, GPIO_PinSource0, GPIO_AF_TIM3); //GPIOB0复用为TIM3 TIM_TimeBaseStructure.TIM_Period = 0xffff; //设定计数器自动重装值 最大131ms溢出 TIM_TimeBaseStructure.TIM_Prescaler = (176 - 1); //预分频器,2us加1.t = (arr+1)*(psc+1)/84000000 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_3; // 选择输入端 IC3映射到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 = 0x00; //IC4F=0011 配置输入滤波器 8个定时器时钟周期滤波 TIM_ICInit(TIM3, &TIM_ICInitStructure); //初始化定时器输入捕获通道 TIM_ITConfig( TIM3,TIM_IT_Update|TIM_IT_CC3,ENABLE);//允许更新中断 ,允许CC4IE捕获中断 TIM_Cmd(TIM3, ENABLE ); //使能定时器3 TIM_ClearFlag(TIM3, TIM_IT_CC3|TIM_IT_Update); NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断 NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1; //先占优先级1级 NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级 NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能 NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器 }下面是定时器3的回调函数 TIM3_IRQHandler.分别进入定时器3的溢出中断和捕获中断对红外遥控器进行解码。
void TIM3_IRQHandler(void) { if(TIM_GetITStatus(TIM3, TIM_IT_CC3) != RESET) { TIM_ClearITPendingBit(TIM3, TIM_IT_CC3); IR_Up = 0; // 每次进入捕获中断就清除溢出次数计数值 IR_ThisPulse = TIM_GetCapture3(TIM3); // 获取通道3的捕获值 if(IR_ThisPulse > IR_LastPulse) // 这次捕获的值大于上次捕获的值 { IR_PulseSub = IR_ThisPulse - IR_LastPulse; // 得到时间差 } else // 小于时要加上0xffff { IR_PulseSub = 0xffff - IR_LastPulse + IR_ThisPulse; // 得到时间差 } IR_LastPulse = IR_ThisPulse; // 将本次得到值作为下一次编码的前一个值 IR_PulseCnt++; // 解码位数加1 if(IR_PulseCnt == 2) { if((IR_PulseSub > 6000) && (IR_PulseSub < 8000)) // 引导码范围,13.5ms 13.5ms/2us = 6750 { IR_Sta = 0x01; // 标志位为1, 表示引导码已经收到 } else IR_PulseCnt = 0; // 如果时间差没有在引导码范围内,中断次数清零,重新编码 } if((IR_PulseCnt > 2) && (IR_Sta == 0x01)) // 中断次数大于2,并且引导码已经解完 { IR_Code <<= 1; // 存储红外编码的寄存器向左移一位, 以便于存储下一位放到最低位 if((IR_PulseSub > 450) && (IR_PulseSub < 700)) // 编码‘0’范围, 1.125ms 1.125/2us = 562.5 { IR_Code |= 0x00; // 存储0 } else if((IR_PulseSub > 800) && (IR_PulseSub) < 1300) // 编码‘1’范围, 2.25ms 2.25/2us = 1125 { IR_Code |= 0x01; // 存储1 } else // 如果不是0码也不是1码, 变量清零,重新解码 { IR_Sta = 0; IR_Code = 0; IR_PulseCnt = 0; } } if(IR_PulseCnt == 34) // 如果解完码, 第34次进入中断正好解完一帧码 { IR_Key = IR_Code; // 存放解出的码 IR_Sta = 0x02; // 进入连发码的状态 flag = 1; // 解完码标志位1,进行编码处理 } if((IR_PulseCnt == 36) && (IR_Sta == 0x02)) // 2位引导码,32位数据码 { IR_PulseCnt = 34; // 按键不松手便认为他在34 if((IR_PulseSub > 4500) && (IR_PulseSub < 6000)) // 进入连发状态, 11.5ms/2us = 5525 { LianfaCnt++; IR_Key = IR_Code; flag = 1; } } } if(TIM_GetITStatus(TIM3,TIM_IT_Update) != RESET) { TIM_ClearITPendingBit(TIM3,TIM_IT_Update); if(RDATA == 1) { IR_Up++; if(IR_Up >= 2) { IR_Code = 0; IR_Sta = 0; IR_PulseCnt = 0; LianfaCnt = 0; } } } }解码的关键是如何识别“0”和“1”,解码方法也有很多种方法,第一种使用读取GPIO端口的输入电平的方法:从位的定义我们可以发现“0”“1”均以0. 56ms的低电平开始。不同的是高电平的宽度不同,“0”为0. 56ms,“1”为1. 68ms,所以必须根据高电平的宽度区别“0”和“1”。如果从0.56ms低电平过后,开始延时,0. 56ms以后,若读到的电平为低,说明该位为“0”,反之则为“1”,为了可靠起见,延时必须比0. 56ms长些,但又不能超过1.12ms,否则如果该位为“0”,读到的已是下一位的高电平,因此取(1. 12ms+0. 56ms )/2=0. 84ms最为可靠,一般取0. 84ms左右均可;第二种采用单片机的外部中断功能:由于编码0和编码1的周期不同,只要计算两次下降沿之间的时间差,即可判断是编码0还是编码1;第三种采用单片机的输入捕获功能,在红外编码的下降沿捕捉计数器的值,记录两次下降沿捕捉到的计数器的值,然后后- -次减前一次即可得到时间差,从而判断是编码0还是编码1.
源码如下: 链接: 红外遥控.