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

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于MySQL Binlog的Elasticsearch数据同步实践

一、为什么要做 随着马蜂窝的逐渐发展,我们的业务数据越来越多,单纯使用 MySQL 已经不能满足我们的数据查询需求,例如对于商品、订单等数据的多维度检索。 使用 Elasticsearch 存储业务数据可以很好的解决我们业务中的搜索需求。而数据进行异构存储后,随之而来的就是数据同步的问题。 二、现有方法及问题 对于数据同步,我们目前的解决方案是建立数据中间表。把需要检索的业务数据,统一放到一张M

关于数据埋点,你需要了解这些基本知识

产品汪每天都在和数据打交道,你知道数据来自哪里吗? 移动app端内的用户行为数据大多来自埋点,了解一些埋点知识,能和数据分析师、技术侃大山,参与到前期的数据采集,更重要是让最终的埋点数据能为我所用,否则可怜巴巴等上几个月是常有的事。   埋点类型 根据埋点方式,可以区分为: 手动埋点半自动埋点全自动埋点 秉承“任何事物都有两面性”的道理:自动程度高的,能解决通用统计,便于统一化管理,但个性化定

使用SecondaryNameNode恢复NameNode的数据

1)需求: NameNode进程挂了并且存储的数据也丢失了,如何恢复NameNode 此种方式恢复的数据可能存在小部分数据的丢失。 2)故障模拟 (1)kill -9 NameNode进程 [lytfly@hadoop102 current]$ kill -9 19886 (2)删除NameNode存储的数据(/opt/module/hadoop-3.1.4/data/tmp/dfs/na

异构存储(冷热数据分离)

异构存储主要解决不同的数据,存储在不同类型的硬盘中,达到最佳性能的问题。 异构存储Shell操作 (1)查看当前有哪些存储策略可以用 [lytfly@hadoop102 hadoop-3.1.4]$ hdfs storagepolicies -listPolicies (2)为指定路径(数据存储目录)设置指定的存储策略 hdfs storagepolicies -setStoragePo

Hadoop集群数据均衡之磁盘间数据均衡

生产环境,由于硬盘空间不足,往往需要增加一块硬盘。刚加载的硬盘没有数据时,可以执行磁盘数据均衡命令。(Hadoop3.x新特性) plan后面带的节点的名字必须是已经存在的,并且是需要均衡的节点。 如果节点不存在,会报如下错误: 如果节点只有一个硬盘的话,不会创建均衡计划: (1)生成均衡计划 hdfs diskbalancer -plan hadoop102 (2)执行均衡计划 hd

hdu1043(八数码问题,广搜 + hash(实现状态压缩) )

利用康拓展开将一个排列映射成一个自然数,然后就变成了普通的广搜题。 #include<iostream>#include<algorithm>#include<string>#include<stack>#include<queue>#include<map>#include<stdio.h>#include<stdlib.h>#include<ctype.h>#inclu

【C++】_list常用方法解析及模拟实现

相信自己的力量,只要对自己始终保持信心,尽自己最大努力去完成任何事,就算事情最终结果是失败了,努力了也不留遗憾。💓💓💓 目录   ✨说在前面 🍋知识点一:什么是list? •🌰1.list的定义 •🌰2.list的基本特性 •🌰3.常用接口介绍 🍋知识点二:list常用接口 •🌰1.默认成员函数 🔥构造函数(⭐) 🔥析构函数 •🌰2.list对象

【Prometheus】PromQL向量匹配实现不同标签的向量数据进行运算

✨✨ 欢迎大家来到景天科技苑✨✨ 🎈🎈 养成好习惯,先赞后看哦~🎈🎈 🏆 作者简介:景天科技苑 🏆《头衔》:大厂架构师,华为云开发者社区专家博主,阿里云开发者社区专家博主,CSDN全栈领域优质创作者,掘金优秀博主,51CTO博客专家等。 🏆《博客》:Python全栈,前后端开发,小程序开发,人工智能,js逆向,App逆向,网络系统安全,数据分析,Django,fastapi

让树莓派智能语音助手实现定时提醒功能

最初的时候是想直接在rasa 的chatbot上实现,因为rasa本身是带有remindschedule模块的。不过经过一番折腾后,忽然发现,chatbot上实现的定时,语音助手不一定会有响应。因为,我目前语音助手的代码设置了长时间无应答会结束对话,这样一来,chatbot定时提醒的触发就不会被语音助手获悉。那怎么让语音助手也具有定时提醒功能呢? 我最后选择的方法是用threading.Time