你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

【经验分享】基于STM32F103的NEC红外发送接收使用同一个定时器的一体设计

[复制链接]
STMCU小助手 发布时间:2022-3-16 10:53
红外接收头很常见,具体就不细说了,这里记录重点: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”,其波形如图所示。 AM]YYXBP7@ZX4]66N2G8`DT.png 这里我采用同一个定时器TIM3的两个通道分别用来做红外的接收和发送,接收用通道3的输入捕获,发送用通道4的输出比较,程序采用分时复用的方式完成收发一体的实验效果。默认处于接收状态,在按键触发时切换成发射状态发送红外数据,发送完成后再次默认切换成接收。上接收部分的代码:
  1. #define NEC_HEAD                (u16)(4500)
  2. #define NEC_ZERO                 (u16)(560)
  3. #define NEC_ONE                        (u16)(1680)
  4. #define NEC_CONTINUE         (u16)(2500)
  5. #define NEC_HEAD_MIN (u16)(NEC_HEAD*0.8f)
  6. #define NEC_HEAD_MAX (u16)(NEC_HEAD*1.2f)
  7. #define NEC_ZERO_MIN (u16)(NEC_ZERO*0.8f)
  8. #define NEC_ZERO_MAX (u16)(NEC_ZERO*1.2f)
  9. #define NEC_ONE_MIN (u16)(NEC_ONE*0.8f)
  10. #define NEC_ONE_MAX (u16)(NEC_ONE*1.2f)
  11. #define NEC_CONTINUE_MIN (u16)(NEC_CONTINUE*0.8f)
  12. #define NEC_CONTINUE_MAX (u16)(NEC_CONTINUE*1.2f)
  13. //红外遥控接收初始化
  14. void NEC_RX_Configuration(void)
  15. {
  16.     GPIO_InitTypeDef GPIO_InitStructure;
  17.     NVIC_InitTypeDef NVIC_InitStructure;
  18.     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  19.     TIM_ICInitTypeDef  TIM_ICInitStructure;//输入捕获
  20.         
  21.     RCC_APB2PeriphClockCmd(NEC_RX_RCC|NEC_TX_RCC,ENABLE); //使能PORT时钟
  22.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);         //TIM3 时钟使能
  23.         
  24.     GPIO_InitStructure.GPIO_Pin = NEC_RX_PIN;                        
  25.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;                  // 上拉输入
  26.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  27.     GPIO_Init(NEC_RX_PORT, &GPIO_InitStructure);
  28.     GPIO_InitStructure.GPIO_Pin = NEC_TX_PIN;                        
  29.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;          //复用推挽输出
  30.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  31.     GPIO_Init(NEC_TX_PORT, &GPIO_InitStructure);
  32.     TIM_TimeBaseStructure.TIM_Period = 10000; //设定计数器自动重装值 最大10ms溢出
  33.     TIM_TimeBaseStructure.TIM_Prescaler =71;         //预分频器,1M的计数频率,1us加1.
  34.     TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_tim
  35.     TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式
  36.     TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMx
  37.     TIM_ICInitStructure.TIM_Channel = TIM_Channel_3;  // 选择输入端 IC3映射到TI3上
  38.     TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;        //上升沿捕获
  39.     TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
  40.     TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;         //配置输入分频,不分频
  41.     TIM_ICInitStructure.TIM_ICFilter = 0x03;//IC4F=0011 配置输入滤波器 8个定时器时钟周期滤波
  42.     TIM_ICInit(TIM3, &TIM_ICInitStructure);//初始化定时器输入捕获通道
  43.     NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;  //TIM3中断
  44.     NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;  //先占优先级2级
  45.     NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;  //从优先级
  46.     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
  47.     NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
  48.     TIM_ITConfig( TIM3,TIM_IT_Update|TIM_IT_CC3,ENABLE);//允许更新中断 ,允许CC3IE捕获中断
  49.         
  50.         TIM_ARRPreloadConfig(TIM3,ENABLE);        //重装载
  51.     TIM_Cmd(TIM3,ENABLE );         //使能定时器3
  52. }
  53. //遥控器接收状态
  54. //[7]:收到了引导码标志
  55. //[6]:得到了一个按键的所有信息
  56. //[5]:保留
  57. //[4]:标记上升沿是否已经被捕获
  58. //[3:0]:溢出计时器
  59. u8         RmtSta=0;
  60. u16 Dval;                //下降沿时计数器的值
  61. u32 RmtRec=0;        //红外接收到的数据
  62. u8  RmtCnt=0;        //按键按下的次数
  63. //定时器4中断服务程序
  64. void TIM3_IRQHandler(void)
  65. {
  66.     if(TIM_GetITStatus(TIM3,TIM_IT_Update)!=RESET)        //更新中断
  67.     {
  68.         if(RmtSta&(1<<7))                                                                //上次有数据被接收到了
  69.         {
  70.             RmtSta&=~(1<<4);                                                        //取消上升沿已经被捕获标记
  71.             if((RmtSta&0X0F)==0X00)RmtSta|=1<<6;                //标记已经完成一次按键的键值信息采集
  72.             if((RmtSta&0X0F)<14)RmtSta++;
  73.             else
  74.             {
  75.                 RmtSta&=~(1<<7);                                        //清空引导标识
  76.                 RmtSta&=0XF0;                                                //清空计数器
  77.             }
  78.         }
  79.     }
  80.     if(TIM_GetITStatus(TIM3,TIM_IT_CC3)!=RESET)
  81.     {
  82.         if(NEC_READ_RX())//上升沿捕获
  83.         {
  84.             TIM_OC3PolarityConfig(TIM3,TIM_ICPolarity_Falling);                //CC3P=1        设置为下降沿捕获
  85.             TIM_SetCounter(TIM3,0);                                        //清空定时器值
  86.             RmtSta|=(1<<4);                                                        //标记上升沿已经被捕获
  87.         }
  88.                 else //下降沿捕获
  89.         {
  90.             Dval=TIM_GetCapture3(TIM3);                                //读取CCR3也可以清CC3IF标志位
  91.             TIM_OC3PolarityConfig(TIM3,TIM_ICPolarity_Rising);                                //CC3P=0        设置为上升沿捕获
  92.             if(RmtSta&0X10)                                                        //完成一次高电平捕获
  93.             {
  94.                 if(RmtSta&(1<<7))//接收到了引导码
  95.                 {
  96.                     if(Dval>NEC_ZERO_MIN && Dval<NEC_ZERO_MAX){                        //560为标准值,560us
  97.                         RmtRec<<=1;                                        //左移一位.
  98.                         RmtRec|=0;                                        //接收到0
  99.                     }
  100.                                         else if(Dval>NEC_ONE_MIN && Dval<NEC_ONE_MAX){        //1680为标准值,1680us
  101.                         RmtRec<<=1;                                        //左移一位.
  102.                         RmtRec|=1;                                        //接收到1
  103.                     }
  104.                                         else if(Dval>NEC_CONTINUE_MIN && Dval<NEC_CONTINUE_MAX){        //得到按键键值增加的信息 2500为标准值2.5ms
  105.                         RmtCnt++;                                         //按键次数增加1次
  106.                         RmtSta&=0XF0;                                //清空计时器
  107.                     }
  108.                 }
  109.                                 else if(Dval>NEC_HEAD_MIN&&Dval<NEC_HEAD_MAX)                //4500为标准值4.5ms
  110.                 {
  111.                     RmtSta|=1<<7;                                        //标记成功接收到了引导码
  112.                     RmtCnt=0;                                                //清除按键次数计数器
  113.                 }
  114.             }
  115.             RmtSta&=~(1<<4);
  116.         }
  117.     }
  118.     TIM_ClearITPendingBit(TIM3,TIM_IT_Update|TIM_IT_CC3);
  119. }
  120. void NEC_GetValue(u16 *addr,u16 *value)
  121. {
  122.     u8 t1,t2;
  123.         *addr = 0;
  124.         *value = 0;
  125.     if(RmtSta&(1<<6)){                        //得到一个按键的所有信息了
  126.         t1=RmtRec>>24;                        //得到地址码
  127.         t2=(RmtRec>>16)&0xff;        //得到地址反码
  128.         if(t1==(u8)~t2)        {                //检验遥控识别码(ID)及地址
  129.                         *addr = (t1<<8) | (t2);
  130.             t1=RmtRec>>8;
  131.             t2=RmtRec;
  132.             if(t1==(u8)~t2){                        
  133.                                 *value = (t1<<8) | (t2);
  134.                         }
  135.                         else{
  136.                                 *addr = 0;
  137.                                 *value=0;
  138.                         }
  139.         }
  140.         if((*value==0)||((RmtSta&(1<<7))==0)){//按键数据错误/遥控已经没有按下了
  141.             RmtSta&=~(1<<6);//清除接收到有效按键标识
  142.             RmtCnt=0;                //清除按键次数计数器
  143.         }
  144.     }
  145. }
复制代码
接收部分在配置上比较简单,配置一个1us计数拼了,周期为10000(也就是10ms)的计数器,同时开启更新中断和捕获中断,在更新中断,和捕获中断里面分别对接数据进行处理,这里要提一下就是捕获中断分上升沿捕获和下降沿捕获,在捕获到某一个电平后需要更新一下捕获的中断源。在每一次捕获的同时对时间间隔进行判断,判断的结果有引导码、数据码、重复码等,这里就不多解释了。上发射部分代码:
  1. /****************************************************************************************/
  2. #define CARRIER_38KHz() TIM_SetCompare4(TIM3,9)
  3. #define NO_CARRIER()        TIM_SetCompare4(TIM3,0)
  4. void NEC_TX_Configuration(void)            //红外传感器接收头引脚初始化
  5. {
  6.     GPIO_InitTypeDef GPIO_InitStructure;
  7.     TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
  8.         TIM_OCInitTypeDef  TIM_OCInitStructure;                                //输出比较        
  9.         
  10.     RCC_APB2PeriphClockCmd(NEC_TX_RCC,ENABLE);                         //使能PORT时钟
  11.     RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);        //TIM3 时钟使能
  12.     GPIO_InitStructure.GPIO_Pin = NEC_TX_PIN;                        
  13.     GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;         //复用推挽输出
  14.     GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
  15.     GPIO_Init(NEC_TX_PORT, &GPIO_InitStructure);
  16.         TIM_Cmd(TIM3,DISABLE);
  17.         TIM_ITConfig( TIM3,TIM_IT_Update|TIM_IT_CC3,DISABLE);                        //关闭TIM3中断
  18.     TIM_TimeBaseStructure.TIM_Period = 25;                                                         //设定计数器自动重装值 最大10ms溢出
  19.     TIM_TimeBaseStructure.TIM_Prescaler =71;                                                 //预分频器,1M的计数频率,1us加1.
  20.     TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;                 //设置时钟分割:TDTS = Tck_tim
  21.     TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;          //TIM向上计数模式
  22.     TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
  23.         TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;                          //设置PWM1模式
  24.         TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; //输出使能
  25.         TIM_OCInitStructure.TIM_Pulse = 0;                                                            //设置捕获比较寄存器的值
  26.         TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;          //设置有效电平为高电平
  27.         TIM_OC4Init(TIM3, &TIM_OCInitStructure);                                           //生效初始化设置
  28.         TIM_OC4PreloadConfig(TIM3, TIM_OCPreload_Enable);                          //使能输出比较预装载
  29.         
  30.         TIM_Cmd(TIM3,ENABLE);
  31. }
  32. static void NEC_Send_Head(void){
  33.         CARRIER_38KHz();        //比较值为1/3载波
  34.         Delay_us(9000);
  35.         NO_CARRIER();        //不载波
  36.         Delay_us(4500);
  37. }
  38. static void NEC_Send_BYTE(u8 value)
  39. {
  40.         u8 i;
  41.         
  42.         for(i=0;i<8;i++)
  43.         {
  44.                 if( value & 0x80 ){
  45.                         CARRIER_38KHz();        //比较值为1/3载波
  46.                         Delay_us(560);
  47.                         NO_CARRIER();        //不载波
  48.                         Delay_us(1680);
  49.                 }
  50.                 else{
  51.                         CARRIER_38KHz();        //比较值为1/3载波
  52.                         Delay_us(560);
  53.                         NO_CARRIER();        //不载波
  54.                         Delay_us(560);
  55.                 }
  56.                 value<<=1;
  57.         }
  58. }
  59. static void NEC_Send_Repeat(u8 repeatcnt)
  60. {
  61.         u8 i;
  62.         
  63.     if(repeatcnt==0)          //如果没有重复码就直接设置无载波,发射管进行空闲状态
  64.     {
  65.         CARRIER_38KHz();
  66.         Delay_us(560);
  67.         NO_CARRIER();     
  68.     }
  69.         else
  70.     {
  71.         for(i=0;i<repeatcnt;++i)
  72.         {
  73.             CARRIER_38KHz();
  74.             Delay_us(560);
  75.             NO_CARRIER();
  76.             Delay_ms(98);
  77.             CARRIER_38KHz();
  78.             Delay_us(9000);  
  79.             NO_CARRIER();
  80.             Delay_us(2250);                     
  81.         }
  82.                 CARRIER_38KHz();
  83.                 Delay_us(560);
  84.                 NO_CARRIER();
  85.     }
  86. }
  87. void NEC_Send(u8 addr,u8 value,u8 cnt){
  88.         NEC_TX_Configuration();
  89.         
  90.         NEC_Send_Head();                                //发送起始码
  91.         NEC_Send_BYTE(addr);                        //发送地址码H
  92.         NEC_Send_BYTE(~addr);                        //发送地址码L
  93.         NEC_Send_BYTE(value);                        //发送命令码H
  94.         NEC_Send_BYTE(~value);                        //发送命令码L
  95.         NEC_Send_Repeat(cnt);                        //发送重复码        
  96.         NEC_RX_Configuration();
  97. }
复制代码
发送部分首先是重新配置定时器3,在配置里面计数频率依然是1us,也就是1MHz,计数周期修改成了25,也就是26个周期(0也算一个),这样算下来的话1MHz / 26 ≈ 38.4615KHz,这个跟红外接收差不多能匹配,当然红外接收头这里用的是38K的,所以配置成38K的,市面上也有不是38K的,比如36K,40K等。然后是载波,载波这里用的是差不多1/3的载波,从哪里可以体现呢?可以看到有这样一个宏定义:
  1. #define CARRIER_38KHz() TIM_SetCompare4(TIM3,9)
  2. #define NO_CARRIER()        TIM_SetCompare4(TIM3,0)
复制代码
载波的时候比较值设置的是9,没有载波的时候是0,9和26就约等于一个1/3的关系。最后就是发射部分,细节不说了,按照NEC协议进行发送,依次是引导码、地址码、地址反码、数据码、数据反码、重复码等。可以看到发射数据前后加了有NEC发送和NEC接收的配置,这里就是关键的分时部分,因为程序主要出于接收,在触发器(这里使用的按键)触发的时候才会发射红外数据。上述就是NEC底层部分。上层应用也贴出来以供参考
  1.                 keyvalue = Key_Scan();
  2.                 if(keyvalue != KEY_NONE){
  3.                         NEC_Send(taddr,tvalue,0);
  4.                         taddr++;
  5.                         tvalue++;
  6.                 }
  7.         
  8.                 NEC_GetValue(&addr,&value);
  9.                 if((value>>8)){
  10.                         printf(" %04x,%04x \r\n",addr,value);
  11.                         printf(" addr: %d,key: %d,cnt: %d\r\n",(addr>>8),(value>>8),RmtCnt);
  12.                         Oled_ShowNum(36,32,(addr>>8),3,8,16);
  13.                         Oled_ShowNum(96,32,(value>>8),3,8,16);
  14.                         Oled_ShowNum(36,48,RmtCnt,3,8,16);
  15.                         Oled_RefreshGram();
  16.                 }
  17.                 Delay_ms(10);
  18.                 if(++t==50){
  19.                         t=0;
  20.                         Led_Tog();
  21.                 }
复制代码
最后要说一点的就是关于系统的延时,延时部分需要慎重,此处我也是踩了一坑,也一便记录下来。原来的底层:
  1. //u32 System_ms=0;
  2. //u32 usTicks;
  3. //void Systick_Configuration(void)
  4. //{
  5. //        RCC_ClocksTypeDef RCC_ClocksStucture;
  6. //        RCC_GetClocksFreq(&RCC_ClocksStucture);        
  7. //        usTicks = RCC_ClocksStucture.SYSCLK_Frequency/1000000;
  8. //        SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK);
  9. //        SysTick_Config(RCC_ClocksStucture.SYSCLK_Frequency/1000);
  10. //}
  11. //u32 GetSystick_us(void)
  12. //{
  13. //        register u32 ms,cycle_cnt;
  14. //        do{
  15. //                ms = System_ms;
  16. //                cycle_cnt = SysTick->VAL;
  17. //        }while(ms != System_ms);
  18. //        return (System_ms*1000) + (usTicks * 1000 - cycle_cnt) / usTicks;
  19. //}
  20. //u32 GetSystick_ms(void)
  21. //{
  22. //        return System_ms;
  23. //}
  24. //void Delay_us(u32 nus)
  25. //{
  26. //        u32 now = GetSystick_us();
  27. //        while(GetSystick_us() - now < nus);
  28. //}
  29. //void Delay_ms(u32 nms)
  30. //{
  31. //        while(nms--)
  32. //                Delay_us(1000);
  33. //}
  34. void SysTick_Handler(void)
  35. {
  36. //        System_ms++;
  37. }
复制代码
这里用到的延时us和ms函数都会调用获取时基函数,这样的话ms还好一点,到us级别就不是很准,再有就是滴答中断里面也有时基去计数,再低级的中断那也是中断,会打断前台程序的运行。改过之后的:
  1. void Systick_Configuration(void)
  2. {
  3.     SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8); //设置时钟源8分频
  4.     SysTick->CTRL |= SysTick_CTRL_TICKINT_Msk;            //使能中断
  5.     SysTick->CTRL |= SysTick_CTRL_ENABLE_Msk;             //开定时器
  6.     SysTick->LOAD = 9;                                    //随意设置一个重装载值
  7. }
  8. void Delay_us(u32 xus)
  9. {
  10.     SysTick->LOAD = 9 * xus; //计9次为1us,xus则重装载值要*9
  11.     SysTick->VAL = 0;        //计数器归零
  12.     while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //等待计数完成
  13. }
  14. void Delay_ms(u32 xms)
  15. {
  16.     SysTick->LOAD = 9000; //计9次为1us,1000次为1ms
  17.     SysTick->VAL = 0;     //计数器归零
  18.     while (xms--)
  19.     {
  20.         while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //等待单次计数完成
  21.     }
  22. }
复制代码
这样的话中断依然打开,但是中断里是空的,其次延时函数也因为不调用获取时基直接判断寄存器而更加精确!
收藏 评论0 发布时间:2022-3-16 10:53

举报

0个回答
关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版