红外接收头很常见,具体就不细说了,这里记录重点:NEC的特征1:使用38 kHz 载波频率2:引导码间隔是9 ms + 4.5 ms3:使用16 位客户代码4:使用8 位数据代码和8 位取反的数据代码当发射器按键按下后,即有遥控码发出,所按的键不同遥控编码也不同。这种遥控码具有以下特征:采用脉宽调制的串行码,以脉宽为0.565ms、间隔0.56ms、周期为1.125ms的组合表示二进制的“0”;以脉宽为0.565ms、间隔1.685ms、周期为2.25ms的组合表示二进制的“1”,其波形如图所示。
这里我采用同一个定时器TIM3的两个通道分别用来做红外的接收和发送,接收用通道3的输入捕获,发送用通道4的输出比较,程序采用分时复用的方式完成收发一体的实验效果。默认处于接收状态,在按键触发时切换成发射状态发送红外数据,发送完成后再次默认切换成接收。上接收部分的代码:- #define NEC_HEAD (u16)(4500)
- #define NEC_ZERO (u16)(560)
- #define NEC_ONE (u16)(1680)
- #define NEC_CONTINUE (u16)(2500)
- #define NEC_HEAD_MIN (u16)(NEC_HEAD*0.8f)
- #define NEC_HEAD_MAX (u16)(NEC_HEAD*1.2f)
- #define NEC_ZERO_MIN (u16)(NEC_ZERO*0.8f)
- #define NEC_ZERO_MAX (u16)(NEC_ZERO*1.2f)
- #define NEC_ONE_MIN (u16)(NEC_ONE*0.8f)
- #define NEC_ONE_MAX (u16)(NEC_ONE*1.2f)
- #define NEC_CONTINUE_MIN (u16)(NEC_CONTINUE*0.8f)
- #define NEC_CONTINUE_MAX (u16)(NEC_CONTINUE*1.2f)
- //红外遥控接收初始化
- void NEC_RX_Configuration(void)
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- NVIC_InitTypeDef NVIC_InitStructure;
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
- TIM_ICInitTypeDef TIM_ICInitStructure;//输入捕获
-
- RCC_APB2PeriphClockCmd(NEC_RX_RCC|NEC_TX_RCC,ENABLE); //使能PORT时钟
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //TIM3 时钟使能
-
- GPIO_InitStructure.GPIO_Pin = NEC_RX_PIN;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; // 上拉输入
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(NEC_RX_PORT, &GPIO_InitStructure);
- GPIO_InitStructure.GPIO_Pin = NEC_TX_PIN;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(NEC_TX_PORT, &GPIO_InitStructure);
- TIM_TimeBaseStructure.TIM_Period = 10000; //设定计数器自动重装值 最大10ms溢出
- TIM_TimeBaseStructure.TIM_Prescaler =71; //预分频器,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_3; // 选择输入端 IC3映射到TI3上
- TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; //上升沿捕获
- TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
- TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; //配置输入分频,不分频
- TIM_ICInitStructure.TIM_ICFilter = 0x03;//IC4F=0011 配置输入滤波器 8个定时器时钟周期滤波
- TIM_ICInit(TIM3, &TIM_ICInitStructure);//初始化定时器输入捕获通道
- NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //先占优先级2级
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
- NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
- TIM_ITConfig( TIM3,TIM_IT_Update|TIM_IT_CC3,ENABLE);//允许更新中断 ,允许CC3IE捕获中断
-
- TIM_ARRPreloadConfig(TIM3,ENABLE); //重装载
- TIM_Cmd(TIM3,ENABLE ); //使能定时器3
- }
- //遥控器接收状态
- //[7]:收到了引导码标志
- //[6]:得到了一个按键的所有信息
- //[5]:保留
- //[4]:标记上升沿是否已经被捕获
- //[3:0]:溢出计时器
- u8 RmtSta=0;
- u16 Dval; //下降沿时计数器的值
- u32 RmtRec=0; //红外接收到的数据
- u8 RmtCnt=0; //按键按下的次数
- //定时器4中断服务程序
- void TIM3_IRQHandler(void)
- {
- if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET) //更新中断
- {
- if(RmtSta&(1<<7)) //上次有数据被接收到了
- {
- RmtSta&=~(1<<4); //取消上升沿已经被捕获标记
- if((RmtSta&0X0F)==0X00)RmtSta|=1<<6; //标记已经完成一次按键的键值信息采集
- if((RmtSta&0X0F)<14)RmtSta++;
- else
- {
- RmtSta&=~(1<<7); //清空引导标识
- RmtSta&=0XF0; //清空计数器
- }
- }
- }
- if(TIM_GetITStatus(TIM3,TIM_IT_CC3)!=RESET)
- {
- if(NEC_READ_RX())//上升沿捕获
- {
- TIM_OC3PolarityConfig(TIM3,TIM_ICPolarity_Falling); //CC3P=1 设置为下降沿捕获
- TIM_SetCounter(TIM3,0); //清空定时器值
- RmtSta|=(1<<4); //标记上升沿已经被捕获
- }
- else //下降沿捕获
- {
- Dval=TIM_GetCapture3(TIM3); //读取CCR3也可以清CC3IF标志位
- TIM_OC3PolarityConfig(TIM3,TIM_ICPolarity_Rising); //CC3P=0 设置为上升沿捕获
- if(RmtSta&0X10) //完成一次高电平捕获
- {
- if(RmtSta&(1<<7))//接收到了引导码
- {
- if(Dval>NEC_ZERO_MIN && Dval<NEC_ZERO_MAX){ //560为标准值,560us
- RmtRec<<=1; //左移一位.
- RmtRec|=0; //接收到0
- }
- else if(Dval>NEC_ONE_MIN && Dval<NEC_ONE_MAX){ //1680为标准值,1680us
- RmtRec<<=1; //左移一位.
- RmtRec|=1; //接收到1
- }
- else if(Dval>NEC_CONTINUE_MIN && Dval<NEC_CONTINUE_MAX){ //得到按键键值增加的信息 2500为标准值2.5ms
- RmtCnt++; //按键次数增加1次
- RmtSta&=0XF0; //清空计时器
- }
- }
- else if(Dval>NEC_HEAD_MIN&&Dval<NEC_HEAD_MAX) //4500为标准值4.5ms
- {
- RmtSta|=1<<7; //标记成功接收到了引导码
- RmtCnt=0; //清除按键次数计数器
- }
- }
- RmtSta&=~(1<<4);
- }
- }
- TIM_ClearITPendingBit(TIM3,TIM_IT_Update|TIM_IT_CC3);
- }
- void NEC_GetValue(u16 *addr,u16 *value)
- {
- u8 t1,t2;
- *addr = 0;
- *value = 0;
- if(RmtSta&(1<<6)){ //得到一个按键的所有信息了
- t1=RmtRec>>24; //得到地址码
- t2=(RmtRec>>16)&0xff; //得到地址反码
- if(t1==(u8)~t2) { //检验遥控识别码(ID)及地址
- *addr = (t1<<8) | (t2);
- t1=RmtRec>>8;
- t2=RmtRec;
- if(t1==(u8)~t2){
- *value = (t1<<8) | (t2);
- }
- else{
- *addr = 0;
- *value=0;
- }
- }
- if((*value==0)||((RmtSta&(1<<7))==0)){//按键数据错误/遥控已经没有按下了
- RmtSta&=~(1<<6);//清除接收到有效按键标识
- RmtCnt=0; //清除按键次数计数器
- }
- }
- }
复制代码 接收部分在配置上比较简单,配置一个1us计数拼了,周期为10000(也就是10ms)的计数器,同时开启更新中断和捕获中断,在更新中断,和捕获中断里面分别对接数据进行处理,这里要提一下就是捕获中断分上升沿捕获和下降沿捕获,在捕获到某一个电平后需要更新一下捕获的中断源。在每一次捕获的同时对时间间隔进行判断,判断的结果有引导码、数据码、重复码等,这里就不多解释了。上发射部分代码:- /****************************************************************************************/
- #define CARRIER_38KHz() TIM_SetCompare4(TIM3,9)
- #define NO_CARRIER() TIM_SetCompare4(TIM3,0)
- void NEC_TX_Configuration(void) //红外传感器接收头引脚初始化
- {
- GPIO_InitTypeDef GPIO_InitStructure;
- TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
- TIM_OCInitTypeDef TIM_OCInitStructure; //输出比较
-
- RCC_APB2PeriphClockCmd(NEC_TX_RCC,ENABLE); //使能PORT时钟
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE); //TIM3 时钟使能
- GPIO_InitStructure.GPIO_Pin = NEC_TX_PIN;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(NEC_TX_PORT, &GPIO_InitStructure);
- TIM_Cmd(TIM3,DISABLE);
- TIM_ITConfig( TIM3,TIM_IT_Update|TIM_IT_CC3,DISABLE); //关闭TIM3中断
- TIM_TimeBaseStructure.TIM_Period = 25; //设定计数器自动重装值 最大10ms溢出
- TIM_TimeBaseStructure.TIM_Prescaler =71; //预分频器,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);
- TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //设置PWM1模式
- TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
- TIM_OCInitStructure.TIM_Pulse = 0; //设置捕获比较寄存器的值
- TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; //设置有效电平为高电平
- TIM_OC4Init(TIM3, &TIM_OCInitStructure); //生效初始化设置
- TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable); //使能输出比较预装载
-
- TIM_Cmd(TIM3,ENABLE);
- }
- static void NEC_Send_Head(void){
- CARRIER_38KHz(); //比较值为1/3载波
- Delay_us(9000);
- NO_CARRIER(); //不载波
- Delay_us(4500);
- }
- static void NEC_Send_BYTE(u8 value)
- {
- u8 i;
-
- for(i=0;i<8;i++)
- {
- if( value & 0x80 ){
- CARRIER_38KHz(); //比较值为1/3载波
- Delay_us(560);
- NO_CARRIER(); //不载波
- Delay_us(1680);
- }
- else{
- CARRIER_38KHz(); //比较值为1/3载波
- Delay_us(560);
- NO_CARRIER(); //不载波
- Delay_us(560);
- }
- value<<=1;
- }
- }
- static void NEC_Send_Repeat(u8 repeatcnt)
- {
- u8 i;
-
- if(repeatcnt==0) //如果没有重复码就直接设置无载波,发射管进行空闲状态
- {
- CARRIER_38KHz();
- Delay_us(560);
- NO_CARRIER();
- }
- else
- {
- for(i=0;i<repeatcnt;++i)
- {
- CARRIER_38KHz();
- Delay_us(560);
- NO_CARRIER();
- Delay_ms(98);
- CARRIER_38KHz();
- Delay_us(9000);
- NO_CARRIER();
- Delay_us(2250);
- }
- CARRIER_38KHz();
- Delay_us(560);
- NO_CARRIER();
- }
- }
- void NEC_Send(u8 addr,u8 value,u8 cnt){
- NEC_TX_Configuration();
-
- NEC_Send_Head(); //发送起始码
- NEC_Send_BYTE(addr); //发送地址码H
- NEC_Send_BYTE(~addr); //发送地址码L
- NEC_Send_BYTE(value); //发送命令码H
- NEC_Send_BYTE(~value); //发送命令码L
- NEC_Send_Repeat(cnt); //发送重复码
- NEC_RX_Configuration();
- }
复制代码 发送部分首先是重新配置定时器3,在配置里面计数频率依然是1us,也就是1MHz,计数周期修改成了25,也就是26个周期(0也算一个),这样算下来的话1MHz / 26 ≈ 38.4615KHz,这个跟红外接收差不多能匹配,当然红外接收头这里用的是38K的,所以配置成38K的,市面上也有不是38K的,比如36K,40K等。然后是载波,载波这里用的是差不多1/3的载波,从哪里可以体现呢?可以看到有这样一个宏定义:- #define CARRIER_38KHz() TIM_SetCompare4(TIM3,9)
- #define NO_CARRIER() TIM_SetCompare4(TIM3,0)
复制代码 载波的时候比较值设置的是9,没有载波的时候是0,9和26就约等于一个1/3的关系。最后就是发射部分,细节不说了,按照NEC协议进行发送,依次是引导码、地址码、地址反码、数据码、数据反码、重复码等。可以看到发射数据前后加了有NEC发送和NEC接收的配置,这里就是关键的分时部分,因为程序主要出于接收,在触发器(这里使用的按键)触发的时候才会发射红外数据。上述就是NEC底层部分。上层应用也贴出来以供参考- keyvalue = Key_Scan();
- if(keyvalue != KEY_NONE){
- NEC_Send(taddr,tvalue,0);
- taddr++;
- tvalue++;
- }
-
- NEC_GetValue(&addr,&value);
- if((value>>8)){
- printf(" %04x,%04x \r\n",addr,value);
- printf(" addr: %d,key: %d,cnt: %d\r\n",(addr>>8),(value>>8),RmtCnt);
- Oled_ShowNum(36,32,(addr>>8),3,8,16);
- Oled_ShowNum(96,32,(value>>8),3,8,16);
- Oled_ShowNum(36,48,RmtCnt,3,8,16);
- Oled_RefreshGram();
- }
- Delay_ms(10);
- if(++t==50){
- t=0;
- Led_Tog();
- }
复制代码 最后要说一点的就是关于系统的延时,延时部分需要慎重,此处我也是踩了一坑,也一便记录下来。原来的底层:- //u32 System_ms=0;
- //u32 usTicks;
- //void Systick_Configuration(void)
- //{
- // RCC_ClocksTypeDef RCC_ClocksStucture;
- // RCC_GetClocksFreq(&RCC_ClocksStucture);
- // usTicks = RCC_ClocksStucture.SYSCLK_Frequency/1000000;
- // SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
- // SysTick_Config(RCC_ClocksStucture.SYSCLK_Frequency/1000);
- //}
- //u32 GetSystick_us(void)
- //{
- // register u32 ms,cycle_cnt;
- // do{
- // ms = System_ms;
- // cycle_cnt = SysTick->VAL;
- // }while(ms != System_ms);
- // return (System_ms*1000) + (usTicks * 1000 - cycle_cnt) / usTicks;
- //}
- //u32 GetSystick_ms(void)
- //{
- // return System_ms;
- //}
- //void Delay_us(u32 nus)
- //{
- // u32 now = GetSystick_us();
- // while(GetSystick_us() - now < nus);
- //}
- //void Delay_ms(u32 nms)
- //{
- // while(nms--)
- // Delay_us(1000);
- //}
- void SysTick_Handler(void)
- {
- // System_ms++;
- }
复制代码 这里用到的延时us和ms函数都会调用获取时基函数,这样的话ms还好一点,到us级别就不是很准,再有就是滴答中断里面也有时基去计数,再低级的中断那也是中断,会打断前台程序的运行。改过之后的:- void Systick_Configuration(void)
- {
- SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //设置时钟源8分频
- SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk; //使能中断
- SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk; //开定时器
- SysTick->LOAD = 9; //随意设置一个重装载值
- }
- void Delay_us(u32 xus)
- {
- SysTick->LOAD = 9 * xus; //计9次为1us,xus则重装载值要*9
- SysTick->VAL = 0; //计数器归零
- while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //等待计数完成
- }
- void Delay_ms(u32 xms)
- {
- SysTick->LOAD = 9000; //计9次为1us,1000次为1ms
- SysTick->VAL = 0; //计数器归零
- while (xms--)
- {
- while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //等待单次计数完成
- }
- }
复制代码 这样的话中断依然打开,但是中断里是空的,其次延时函数也因为不调用获取时基直接判断寄存器而更加精确! |