【单片记笔记】基于STM32F103的NEC红外发送接收使用同一个定时器的一体设计
红外接收头很常见,具体就不细说了,这里记录重点:
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红外发送接收使用同一个定时器的一体设计相关推荐
- Qt5学习笔记之串口助手二:发送接收实现
这里写目录标题 一级目录 显示接收内容 实现发送功能 一级目录 1.定义一个串口端口的对象并实例化 2.打开按钮自动关联槽函数 #include 显示接收内容 需要手动关联槽函数 查看应该使用的信号 ...
- HNU小学期计算机系统设计与创新基础训练——基于STC学习板的加密信息存储与游戏操作系统(第一部分设计思路+基础原理)
HNU小学期计算机系统设计与创新基础训练--加密信息存储与游戏操作系统 一. 选题名称 二. 选题背景 三. 实现功能 1. 主要功能 2. 细节设计 四. 设计思路 五. 基本原理 1. 数码管与发 ...
- 基于STM32的利用红外收发机制的人体感应设计
因为网上资料很多,本人在这里只是记录自己学习的过程.具体内容可以参考其他大神的文章. 红外的收发其实和光耦原理差不多.发射端收到数据,发红外光,接收端收到光信号也开始导通,采集Rx信号即可知道发来的数 ...
- Java学习笔记,面向猴子记笔记2021/5/29更新
如何在 3 天内学会 Java? https://www.zhihu.com/question/66535555/answer/1799868707 (手动滑稽) 在编辑器中鼠标右键source可以快 ...
- 嵌入式学习笔记——基于Cortex-M的单片机介绍
基于Cortex-M的单片机介绍 前言 1生产厂商及其产品线 1.1ARM单片机的产品线 1.2命名规则 作业1 2习单片机的资料准备 2.1STM32开发所需手册 2.1.1芯片的数据手册 芯片基本 ...
- K8S——单master节点和基于单master节点的双master节点二进制部署(本机实验,防止卡顿,所以多master就不做3台了)
K8S--单master节点和基于单master节点的双master节点二进制部署 一.准备 二.ETCD集群 1.master节点 2.node节点 三.Flannel网络部署 四.测试容器间互通 ...
- 机智云代码移植_IoT开发者 | 基于STM32F103的机智云宠物屋外加4路继电器开源教程...
[ 写在前面 ] 自智云社区开辟IoT开源项目专区以来,一直有IoT开发者在贡献案例.玛莉甄选了一些具有代表性的案例分享给IoT爱好者们,本文亦如此. 若你有好的案例,想和IoT爱好者们分享,欢迎投稿 ...
- 用emacs做笔记_3种用于记笔记的Emacs模式
用emacs做笔记 无论您从事什么工作,都不可避免地需要记笔记. 通常,不止几个. 如果您在当今时代像许多人一样,可以使用数字方式记笔记. 开源爱好者有多种选择可以记下他们的电子格式的想法,思想和研究 ...
- 计算机应用苹果笔记,使用感受 篇一:为什么我不推荐ipad+apple pencil记笔记(一反主流)...
使用感受 篇一:为什么我不推荐ipad+apple pencil记笔记(一反主流) 2019-04-06 20:27:44 38点赞 51收藏 14评论 创作立场声明:看到过的文章都是在推荐使用ipa ...
最新文章
- 如何同步更新 Github 上 Fork 的项目?
- 台达asda-b2伺服驱动器说明书_REXROTH力士乐DKC系列伺服放大器LED灯都
- 03|复杂度分析(上):如何分析、统计算法的执行效率和资源消耗?
- uni-app单个页面的生命周期函数
- logback 配置详解
- 计算机九大核心课程,九大变化,透析IB(国际文凭)课程发展趋势
- 简单的学习一下node吧——还在学习中~~~
- 学python能干什么工作-什么是Python?学完之后能做哪些工作?
- HBase MapReduce实例分析
- librtmp读包阻塞问题修复
- git 拉取最新代码覆盖本地
- 国庆节怎么少得了国旗:国旗头像
- 《数据结构》课程介绍
- JAVA程序设计实战(1-9章)
- 用ffmpeg进行音频格式转换、剪切、合并、音量调整等
- Java正则表达式提取字符的方法实例
- Windows--从dos下进入D盘,切换盘符
- 大数据培训:Spark 性能调优详解
- 图像处理-Opencv入门(3)-图像的基本运算(1)-代数运算
- Python王者荣耀皮肤批量下载
热门文章
- 最新迪恩电影/美剧DiscuzV3.2商业版模板源码
- 小生意汽车配件销售管理软件选型
- 一起来找茬:下面这段代码是让计算机在屏幕上输出“hi”。其中有三个错误,快来改正吧
- excel仪表盘制作,商业智能仪表盘的作用
- 智能视频分析技术与被动红外技术的整合应用
- 关于功耗芯片那些事(四)
- 路由器重温——WAN接入/互联-DCC配置管理2
- [10.96.0.1]:443/apis/crd.projectcalico.org/v1/clusterinformations/default: timeout
- 管理学原理期末复习笔记
- 110 李俊民 新庵