红外接收头很常见,具体就不细说了,这里记录重点:

NEC的特征

1:使用38 kHz 载波频率
2:引导码间隔是9 ms + 4.5 ms
3:使用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_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMxTIM_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为标准值,560usRmtRec<<=1;                   //左移一位.RmtRec|=0;                  //接收到0} else if(Dval>NEC_ONE_MIN && Dval<NEC_ONE_MAX){    //1680为标准值,1680usRmtRec<<=1;                 //左移一位.RmtRec|=1;                  //接收到1} else if(Dval>NEC_CONTINUE_MIN && Dval<NEC_CONTINUE_MAX){  //得到按键键值增加的信息 2500为标准值2.5msRmtCnt++;                  //按键次数增加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_timTIM_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);         //发送地址码HNEC_Send_BYTE(~addr);           //发送地址码LNEC_Send_BYTE(value);           //发送命令码HNEC_Send_BYTE(~value);          //发送命令码LNEC_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则重装载值要*9SysTick->VAL = 0;        //计数器归零while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //等待计数完成
}void Delay_ms(u32 xms)
{SysTick->LOAD = 9000; //计9次为1us,1000次为1msSysTick->VAL = 0;     //计数器归零while (xms--){while (!(SysTick->CTRL & SysTick_CTRL_COUNTFLAG_Msk)); //等待单次计数完成}
}

这样的话中断依然打开,但是中断里是空的,其次延时函数也因为不调用获取时基直接判断寄存器而更加精确!

By Urien

2019年12月4日 11:43:25

【单片记笔记】基于STM32F103的NEC红外发送接收使用同一个定时器的一体设计相关推荐

  1. Qt5学习笔记之串口助手二:发送接收实现

    这里写目录标题 一级目录 显示接收内容 实现发送功能 一级目录 1.定义一个串口端口的对象并实例化 2.打开按钮自动关联槽函数 #include 显示接收内容 需要手动关联槽函数 查看应该使用的信号 ...

  2. HNU小学期计算机系统设计与创新基础训练——基于STC学习板的加密信息存储与游戏操作系统(第一部分设计思路+基础原理)

    HNU小学期计算机系统设计与创新基础训练--加密信息存储与游戏操作系统 一. 选题名称 二. 选题背景 三. 实现功能 1. 主要功能 2. 细节设计 四. 设计思路 五. 基本原理 1. 数码管与发 ...

  3. 基于STM32的利用红外收发机制的人体感应设计

    因为网上资料很多,本人在这里只是记录自己学习的过程.具体内容可以参考其他大神的文章. 红外的收发其实和光耦原理差不多.发射端收到数据,发红外光,接收端收到光信号也开始导通,采集Rx信号即可知道发来的数 ...

  4. Java学习笔记,面向猴子记笔记2021/5/29更新

    如何在 3 天内学会 Java? https://www.zhihu.com/question/66535555/answer/1799868707 (手动滑稽) 在编辑器中鼠标右键source可以快 ...

  5. 嵌入式学习笔记——基于Cortex-M的单片机介绍

    基于Cortex-M的单片机介绍 前言 1生产厂商及其产品线 1.1ARM单片机的产品线 1.2命名规则 作业1 2习单片机的资料准备 2.1STM32开发所需手册 2.1.1芯片的数据手册 芯片基本 ...

  6. K8S——单master节点和基于单master节点的双master节点二进制部署(本机实验,防止卡顿,所以多master就不做3台了)

    K8S--单master节点和基于单master节点的双master节点二进制部署 一.准备 二.ETCD集群 1.master节点 2.node节点 三.Flannel网络部署 四.测试容器间互通 ...

  7. 机智云代码移植_IoT开发者 | 基于STM32F103的机智云宠物屋外加4路继电器开源教程...

    [ 写在前面 ] 自智云社区开辟IoT开源项目专区以来,一直有IoT开发者在贡献案例.玛莉甄选了一些具有代表性的案例分享给IoT爱好者们,本文亦如此. 若你有好的案例,想和IoT爱好者们分享,欢迎投稿 ...

  8. 用emacs做笔记_3种用于记笔记的Emacs模式

    用emacs做笔记 无论您从事什么工作,都不可避免地需要记笔记. 通常,不止几个. 如果您在当今时代像许多人一样,可以使用数字方式记笔记. 开源爱好者有多种选择可以记下他们的电子格式的想法,思想和研究 ...

  9. 计算机应用苹果笔记,使用感受 篇一:为什么我不推荐ipad+apple pencil记笔记(一反主流)...

    使用感受 篇一:为什么我不推荐ipad+apple pencil记笔记(一反主流) 2019-04-06 20:27:44 38点赞 51收藏 14评论 创作立场声明:看到过的文章都是在推荐使用ipa ...

最新文章

  1. 如何同步更新 Github 上 Fork 的项目?
  2. 台达asda-b2伺服驱动器说明书_REXROTH力士乐DKC系列伺服放大器LED灯都
  3. 03|复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?
  4. uni-app单个页面的生命周期函数
  5. logback 配置详解
  6. 计算机九大核心课程,九大变化,透析IB(国际文凭)课程发展趋势
  7. 简单的学习一下node吧——还在学习中~~~
  8. 学python能干什么工作-什么是Python?学完之后能做哪些工作?
  9. HBase MapReduce实例分析
  10. librtmp读包阻塞问题修复
  11. git 拉取最新代码覆盖本地
  12. 国庆节怎么少得了国旗:国旗头像
  13. 《数据结构》课程介绍
  14. JAVA程序设计实战(1-9章)
  15. 用ffmpeg进行音频格式转换、剪切、合并、音量调整等
  16. Java正则表达式提取字符的方法实例
  17. Windows--从dos下进入D盘,切换盘符
  18. 大数据培训:Spark 性能调优详解
  19. 图像处理-Opencv入门(3)-图像的基本运算(1)-代数运算
  20. Python王者荣耀皮肤批量下载

热门文章

  1. 最新迪恩电影/美剧DiscuzV3.2商业版模板源码
  2. 小生意汽车配件销售管理软件选型
  3. 一起来找茬:下面这段代码是让计算机在屏幕上输出“hi”。其中有三个错误,快来改正吧
  4. excel仪表盘制作,商业智能仪表盘的作用
  5. 智能视频分析技术与被动红外技术的整合应用
  6. 关于功耗芯片那些事(四)
  7. 路由器重温——WAN接入/互联-DCC配置管理2
  8. [10.96.0.1]:443/apis/crd.projectcalico.org/v1/clusterinformations/default: timeout
  9. 管理学原理期末复习笔记
  10. 110 李俊民 新庵