STM32 定时器输入捕获实现红外遥控数据接收

2024-06-04 05:58

本文主要是介绍STM32 定时器输入捕获实现红外遥控数据接收,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

之前已经写过了一个使用定时器普通计时功能来识别红外遥控数据的文章。本次是使用定时器输入捕获来实现,这种方法比起定时器普通计数来说要更加复杂一些,不过效果会更好。

一、原理

1、红外发射协议

  • 红外发射协议已经在之前的文章中写过,在此就不赘述。

2、定时器计数和输入捕获

  • 定时器就是按照一个特定的频率对计数值进行加一或减一操作,当数值溢出时则产生一个标志或中断。

  • 定时器的输入捕获就是可以测量输入信号的脉冲宽度。

  • 本次就是通过普通计数和输入捕获的结合来实现的。

3、实现方法

  • 利用定时器记录输入信号高脉冲的时间,通过该时间来判断数据是否是同步头信息、数据 1 或者数据 0。

二、实现

1、配置 定时器2 输入捕获通道

  • 示例代码中使用 PA1 管脚,配置为上拉输入模式,复用功能为定时器2的通道2。

  • 定时器采用普通定时器,定时器2,该定时器具有输入捕获功能。

  • 配置定时器的两种工作模式,一个是普通计数器TIM_TimeBaseInit,一个是输入捕获模式TIM_ICInit

  • 配置定时器2的中断源,有两个中断源,一个是更新中断TIM_IT_Update,一个是输入捕获中断TIM_IT_CC2

  • 配置代码如下:

/*
* Ir input pin
* mode:floating input
* pin: PA1
* GPIO_AF: TIM2_CH2
*/
void Ir_Pin_Init()
{GPIO_InitTypeDef GPIO_InitStructure;GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_2);GPIO_InitStructure.GPIO_Pin  =  GPIO_Pin_1;GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOA, &GPIO_InitStructure);GPIO_SetBits(GPIOA,GPIO_Pin_1); //output 1
}//PA1 TIM2_CH2
//使用GPIO输入捕获实现红外接收
void Remote_Init()
{NVIC_InitTypeDef                NVIC_InitStructure;TIM_TimeBaseInitTypeDef         TIM_TimeBaseStructure;TIM_ICInitTypeDef               TIM_ICInitStructure;/*使能TIM1时钟,默认时钟源为PCLK1(PCLK1未分频时不倍频,否则由PCLK1倍频输出),可选其它时钟源*/RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);Ir_Pin_Init();TIM_ClearITPendingBit(TIM2,TIM_IT_Update|TIM_IT_CC2); //清除中断和捕获标志位TIM_TimeBaseStructure.TIM_Period = 1000; //设定计数器自动重装值 最大10ms溢出TIM_TimeBaseStructure.TIM_Prescaler = 480-1;   //预分频器,0.1M的计数频率,10us加1.TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割:TDTS = Tck_timTIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  //TIM向上计数模式TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); //根据指定的参数初始化TIMxTIM_ICInitStructure.TIM_Channel = TIM_Channel_2;  // 选择输入端 IC2映射到TI2上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(TIM2, &TIM_ICInitStructure);//初始化定时器输入捕获通道NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;  //TIM2中断NVIC_InitStructure.NVIC_IRQChannelPriority = 2;  //优先级0级NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能NVIC_Init(&NVIC_InitStructure);  //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器TIM_ITConfig(TIM2,TIM_IT_Update|TIM_IT_CC2,ENABLE);//允许更新中断 ,允许CC2IE捕获中断TIM_Cmd(TIM2,ENABLE);    //使能定时器2
}

2、添加定时器2的中断服务函数

  • 使用了两种定时器中断源,分别为计数溢出中断和输入捕获中断。但是这两种方式触发中断的中断服务函数是同一个,即void TIM2_IRQHandler(void)

  • 定时器使用的是 TIM2 通用定时器,模式为向上计数。在该模式中,计数器从 0 计数到自动加载值 (TIMx_ARR计数器的内容) ,然后重新从 0 开始计数并且产生一个计数器溢出事件。定时器计数溢出的周期为10ms,该中断的产生说明在10ms内都没有输入捕获来清空计数值,也就是输入信号没有发生变化,说明 10ms 没有收到红外信号了,因此可判断为接收完成。

  • 输入捕获是为了测量高电平的持续时间,因此采用上升沿触发中断,对计数值清零,切换下一次为下降沿触发;在下降沿触发中断时,记下计数值,切换下一次为上升沿触发。因此在下降沿记下的时间即为高电平的时序时间。记录高电平持续时间的原因,是因为红外信号在表示逻辑0、逻辑1时低电平的持续时间的相同的,而高电平的持续时间不同的。

  • 示例代码如下:

//遥控器接收状态
//[7]:收到了引导码标志
//[6]:得到了一个按键的所有信息
//[5]:保留
//[4]:标记上升沿是否已经被捕获
//[3:0]:溢出计时器
uint8_t  RmtSta = 0;
uint16_t Dval;         //下降沿时计数器的值
uint32_t RmtRec = 0;   //红外接收到的数据
uint8_t  RmtCnt = 0;   //按键按下的次数//定时器2中断服务程序
void TIM2_IRQHandler(void)
{if(TIM_GetITStatus(TIM2,TIM_IT_Update) != RESET)  //计数溢出中断{if(RmtSta & 0x80) //上次有数据被接收到了{RmtSta &= ~0x10;  //取消上升沿已经被捕获标记if((RmtSta & 0x0f) == 0x00) RmtSta |= 1<<6; //电平没有变化后延时10ms,可标记已经完成一次按键的信息采集if((RmtSta & 0x0f) < 14) RmtSta++;  //若进入14,意味着定时器计数溢出了14次,即140mselse{RmtSta &= ~(1<<7); //清空引导标识RmtSta &= 0xf0;   //清空计数器 ,意味着可以下一次的检测//RmtCnt = 0;//RmtSta &= ~(1<<6);}}}if(TIM_GetITStatus(TIM2,TIM_IT_CC2) != RESET)    //输入捕获中断{if(RDATA){TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Falling); //设置下降沿触发捕获TIM_SetCounter(TIM2,0);    // 清零计数值RmtSta |= 0x10;}else{Dval = TIM_GetCapture2(TIM2);  // 获取计数值,该计数值代表高电平持续时间TIM_OC2PolarityConfig(TIM2,TIM_ICPolarity_Rising);  // 设置上升沿触发if(RmtSta & 0x10){if(RmtSta & 0x80){if((Dval > 30) && (Dval < 80)){RmtRec <<= 1;RmtRec |= 0;}else if((Dval > 140) && (Dval < 180)){RmtRec <<= 1;RmtRec |= 1;}else if((Dval > 200) && (Dval < 250)){RmtCnt++;    // 重复码RmtSta &= 0xf0;}}else if((Dval > 420) && (Dval < 470)){RmtSta |= 1<<7; //表示接收到同步头RmtCnt = 0;    //清零按键次数}}RmtSta &= ~0x10;}}TIM_ClearFlag(TIM2,TIM_IT_Update|TIM_IT_CC2);
}

3、红外按键扫描函数

  • 该函数放在主循环中,轮训判断按键是否接收完成。如果接收完成则开始分析键值。

  • 该函数返回一个16位的数值,其中低八位表示键值,高八位表示按下的次数,依次来分析长按键和短按键。这一点主要是通过红外协议中重复码的规定来实现的。

  • 红外协议中规定,若按下一个键后没有放开,则会以 108ms 为一个周期发送重复码。重复码表现为2.25ms的高电平。

  • 示例代码如下:

/*SystemKeybuf
*处理红外键盘
*返回值: keybuf
*   0xffff,没有任何按键按下
*   其他,按下的按键键值.
*   keybuf[15:8] : key cnt
*   keybuf[7:0]  : key code
*/
uint16_t Remote_Scan(void)
{uint16_t keybuf = 0xffff;uint8_t sta = 0xff;uint8_t t1,t2;if(RmtSta & (1<<6))//得到一个按键的所有信息了{t1 = RmtRec >> 24;            //得到地址码t2 = (RmtRec >> 16) & 0xff; //得到地址反码//printf("rmtrec = %#x\n",RmtRec);if((t1 == (uint8_t)~t2) && (t1 == REMOTE_ID))//检验遥控识别码(ID)及地址//if(t1 == (uint8_t)~t2)//检验遥控识别码(ID)及地址{//printf("t1 = %d\n",t1);t1 = RmtRec >> 8;t2 = RmtRec;if(t1 == (uint8_t)~t2) sta = t1;//键值正确}if((sta != 0xff))//按键数据正确{if(RmtCnt < 10) // short key{if((RmtSta & 0x80) == 0) // release{keybuf = RmtCnt;keybuf <<= 8;keybuf |= sta;RmtSta &= ~(1<<6); //清除接收到有效按键标识RmtCnt = 0;       //清除按键次数计数器}}#if 0else if(RmtCnt == 10)// && RmtCnt < 15)  // long key{keybuf = RmtCnt;keybuf <<= 8;keybuf |= sta;}else if(RmtCnt > 10){if(((RmtCnt - 10) % 2) == 0)   //每5次重复码处理一次按键 {keybuf = RmtCnt;keybuf <<= 8;keybuf |= sta;}}#elseelse if((sta == KEY_NEXT) || (sta == KEY_PREV) || (sta == KEY_MENU) || (sta == KEY_SELECT)) //仅这四个按键支持连续键功能{if(((RmtCnt - 10) % 2) == 0)   //每2次重复码处理一次按键{keybuf = RmtCnt;keybuf <<= 8;keybuf |= sta;}if((RmtSta & 0x80) == 0) // release{RmtSta &= ~(1<<6); //清除接收到有效按键标识RmtCnt = 0;       //清除按键次数计数器}}else //长按键{//if((RmtSta & 0x80) == 0)  //release ,并非等到按键释放才处理{keybuf = RmtCnt;keybuf <<= 8;keybuf |= sta;RmtSta &= ~(1<<6); //清除接收到有效按键标识RmtCnt = 0;       //清除按键次数计数器RmtRec = 0;       //清除接收值}}#endif}}return keybuf;
}

4、主函数

  • 在 main 函数中,对 IO 口和 定时器进行初始化。

  • 主循环中,通过判断接收完成标志位,对接收完成的按键控制码进行打印。

  • SystemKeyHandle()函数处理每一个按键的操作逻辑。

  • 示例代码如下:

void main()
{uint16_t SystemKeybuf;...//Ir RemoteRemote_Init();while(1){SystemKeybuf = Remote_Scan();    //红外按键扫描if(SystemKeybuf != 0xffff) //如果有按键未处理{//main_printf("SystemKeyBuf = %#x\n",SystemKeybuf);SystemKeyHandle();SystemKeybuf = 0xffff;}}
}

三、演示

如下图为串口打印出接收的红外按键值信息:
这里写图片描述

说明1:这篇文章所使用的方法主要参考自这篇文章,代码仅做了部分修改,并在源代码基础上添加了部分代码,用于实现连续键。

本文档基于 STM32 F1 系列 MCU,固件库版本 3.5。其他 MCU 及固件库仅需要对库函数略作修改。

参考链接1:cortex_m3_stm32嵌入式学习笔记(二十三):红外遥控实验(输入捕捉+解码)

参考链接2:STM32 红外线实验

参考链接3:STM32 红外线实验

更多关于 STM32 的文章

这篇关于STM32 定时器输入捕获实现红外遥控数据接收的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1029255

相关文章

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整

Java枚举类实现Key-Value映射的多种实现方式

《Java枚举类实现Key-Value映射的多种实现方式》在Java开发中,枚举(Enum)是一种特殊的类,本文将详细介绍Java枚举类实现key-value映射的多种方式,有需要的小伙伴可以根据需要... 目录前言一、基础实现方式1.1 为枚举添加属性和构造方法二、http://www.cppcns.co

使用Python实现快速搭建本地HTTP服务器

《使用Python实现快速搭建本地HTTP服务器》:本文主要介绍如何使用Python快速搭建本地HTTP服务器,轻松实现一键HTTP文件共享,同时结合二维码技术,让访问更简单,感兴趣的小伙伴可以了... 目录1. 概述2. 快速搭建 HTTP 文件共享服务2.1 核心思路2.2 代码实现2.3 代码解读3.

MySQL双主搭建+keepalived高可用的实现

《MySQL双主搭建+keepalived高可用的实现》本文主要介绍了MySQL双主搭建+keepalived高可用的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,... 目录一、测试环境准备二、主从搭建1.创建复制用户2.创建复制关系3.开启复制,确认复制是否成功4.同

Java实现文件图片的预览和下载功能

《Java实现文件图片的预览和下载功能》这篇文章主要为大家详细介绍了如何使用Java实现文件图片的预览和下载功能,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... Java实现文件(图片)的预览和下载 @ApiOperation("访问文件") @GetMapping("

使用Sentinel自定义返回和实现区分来源方式

《使用Sentinel自定义返回和实现区分来源方式》:本文主要介绍使用Sentinel自定义返回和实现区分来源方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Sentinel自定义返回和实现区分来源1. 自定义错误返回2. 实现区分来源总结Sentinel自定

Java实现时间与字符串互相转换详解

《Java实现时间与字符串互相转换详解》这篇文章主要为大家详细介绍了Java中实现时间与字符串互相转换的相关方法,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下... 目录一、日期格式化为字符串(一)使用预定义格式(二)自定义格式二、字符串解析为日期(一)解析ISO格式字符串(二)解析自定义

opencv图像处理之指纹验证的实现

《opencv图像处理之指纹验证的实现》本文主要介绍了opencv图像处理之指纹验证的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学... 目录一、简介二、具体案例实现1. 图像显示函数2. 指纹验证函数3. 主函数4、运行结果三、总结一、