STM32 定时器实现红外遥控数据接收

2024-06-04 05:58

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

一、原理

1、红外发射协议

  • 红外通信的协议有很多种。这个实验使用的是NEC协议。这个协议采用PWM的方法进行调制,利用脉冲宽度来表示 0 和 1 。

  • NEC 遥控指令的数据格式为:同步码头、地址码、地址反码、控制码、控制反码。同步码由一个 9ms 的低电平和一个 4.5ms 的高电平组成,地址码、地址反码、控制码、控制反码均是 8 位数据格式。按照低位在前,高位在后的顺序发送。采用反码是为了增加传输的可靠性。因此,每帧的数据为 32 位,包括地址码,地址反码,控制码,控制反码。反码可用于解码时进行校验比对。

  • NEC码的位定义:一个脉冲对应 560us 的连续载波,一个逻辑 1 传输需要 2.25ms(560us 脉冲+1680us 低电平),一个逻辑 0 的传输需要 1.125ms(560us 脉冲+560us 低电平)。而遥控接收头在收到脉冲的时候为低电平,在没有脉冲的时候为高电平,这样,在接收头端收到的信号为:逻辑 1 应该是 560us 低+1680us 高,逻辑 0 应该是 560us 低+560us 高。

  • 红外数据的波形如下图:包括一个同步头和 32 帧数据。
    这里写图片描述

  • 下图可看出,同步头为 9ms 低电平加上 4.5ms 高电平,控制码为 8 个 0,控制反码为 8 个 1。
    这里写图片描述

2、定时器计数

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

3、实现方法

  • 利用定时器记录两个下降沿之间的时间,通过该时间判断是否是同步头信息、数据 1 或者数据 0。当检测到同步头,开始记录 32 个数据的时间值。
    这里写图片描述

二、实现

1、配置 GPIO 口下降沿触发中断

  • 示例代码中使用 PA7 管脚,配置为上拉输入模式。

  • 选择下降沿触发,是因为红外接收管默认情况下保持高电平,接收到数据时从高电平转变为低电平。

  • 中断源选择为 EXTI_Line7 ,在库函数中对该中断源定义的服务函数为 EXTI9_5_IRQHandler(),也就是说外部中断 5 到 9 是 共用一个中断服务函数的。

  • 配置代码如下:

    void IR_Pin_init()
    {GPIO_InitTypeDef GPIO_InitStructure;EXTI_InitTypeDef EXTI_InitStructure;NVIC_InitTypeDef NVIC_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);GPIO_InitStructure.GPIO_Pin=GPIO_Pin_7;GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;GPIO_Init(GPIOA,&GPIO_InitStructure);EXTI_ClearITPendingBit(EXTI_Line7);GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource7); EXTI_InitStructure.EXTI_Line=EXTI_Line7;EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;EXTI_InitStructure.EXTI_LineCmd=ENABLE;EXTI_Init(&EXTI_InitStructure); NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); NVIC_InitStructure.NVIC_IRQChannel = EXTI9_5_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;     NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;  NVIC_Init(&NVIC_InitStructure);
    }

2、配置定时器计数值

  • 定时器使用的是 TIM2 通用定时器,模式为向上计数。在该模式中,计数器从 0 计数到自动加载值 (TIMx_ARR计数器的内容) ,然后重新从 0 开始计数并且产生一个计数器溢出事件。

  • 示例函数接收两个参数,分别为预分频器的值和自动加载值。通过调整这两个参数,可以灵活地改变定时器的计数周期。例如在 TIM2 的默认时钟源 PCLK1 为96MHz时,使用语句 Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1); //0.1ms 进行初始化,可以每 0.1ms 产生一次中断。

  • 示例代码如下:

    void Tim2_UPCount_Init(u16 Prescaler,u16 Period)
    {TIM_TimeBaseInitTypeDef TIM_StructInit;NVIC_InitTypeDef NVIC_StructInit;RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);TIM_StructInit.TIM_Period=Period;TIM_StructInit.TIM_Prescaler=Prescaler;TIM_StructInit.TIM_ClockDivision=TIM_CKD_DIV1;TIM_StructInit.TIM_CounterMode=TIM_CounterMode_Up;TIM_StructInit.TIM_RepetitionCounter=0;TIM_TimeBaseInit(TIM2, &TIM_StructInit);TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);TIM_Cmd(TIM2, ENABLE);    TIM_ClearFlag(TIM2, TIM_FLAG_Update);NVIC_StructInit.NVIC_IRQChannel=TIM2_IRQn;NVIC_StructInit.NVIC_IRQChannelCmd=ENABLE;NVIC_StructInit.NVIC_IRQChannelPreemptionPriority=0;NVIC_StructInit.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_StructInit);
    }

3、定时器中断函数统计时间

  • 如上所说,定时器每 0.1ms 计数完成,产生中断,在中断函数中对标志位 ucTim2Flag 加 1,意味着时间过去了 0.1ms。

  • 时间标志位原型为 uint16_t ucTim2Flag;

  • 示例代码如下:

    void TIM2_IRQHandler(void)
    {TIM_ClearITPendingBit(TIM2, TIM_IT_Update);ucTim2Flag++;
    }

4、GPIO 中断函数中接收 32 位数据

  • 在下降沿触发的 IO 口中断函数中,需要实现统计两个下降沿之间的时间,并将其记录在数组中。

  • 下降沿第一次触发时,清除当前定时器中的计数值,以便统计时间。之后每一次下降沿触发就记录下当前计数值,然后再对其清零。如果该时间在同步头的时间区间内,对索引进行清零,表示重新开始接收数据。

  • 完整接收同步头和 32 个数据之后,表示接收完成。

  • 示例代码如下:

    uint8_t irdata[33];   //用于记录两个下降沿之间的时间
    bool receiveComplete; //接收完成标志位
    uint8_t idx;          //用于索引接收到的数值
    bool startflag;       //表示开始接收void EXTI9_5_IRQHandler(void)
    {uint16_t ir_time;if(startflag){ir_time = ucTim2Flag; if(ucTim2Flag < 150 && ucTim2Flag >= 50 ) // 接收到同步头{idx=0;  // 数组下标清零}irdata[idx] = ucTim2Flag;  // 获取计数时间ucTim2Flag = 0;            // 清零计数时间,以便下次统计idx++;                     // 接收到一个数据,索引加1if(idx==33)       // 如果接收到33个数据,包括32位数和以一个同步头{idx=0;ucTim2Flag = 0;receiveComplete = TRUE;}}else   // 下降沿第一次触发{idx = 0;ucTim2Flag = 0;startflag = TRUE;}EXTI_ClearITPendingBit(EXTI_Line7);  // 清除中断标志
    }

5、判断控制码值

  • 由于中断函数中接收并记录下的数据是两个下降沿之间的时间,并不是红外所发送的数据。因此需要根据红外协议,对 32 个时间进行判断,从而获得红外真正发送的数据。

  • 下面这个函数需要在红外完整接收数据后执行,可通过判断接收完成标志位 receiveComplete 来实现。

  • 示例代码如下:

    uint8_t Ir_Server()
    {uint8_t i,j,idx=1; //idx 从1 开始表示对同步头的时间不处理uint8_t temp;for(i=0; i<4; i++){for(j=0; j<8; j++){if(irdata[idx] >=8 && irdata[idx] < 15)   //表示 0{temp = 0;}else if(irdata[idx] >=18 && irdata[idx]<25) //表示 1{temp = 1;}remote_code[i] <<= 1;remote_code[i] |= temp;idx++;}}return remote_code[2];  // 该数组中记录的是控制码,每个按键不一样//for(idx=0; idx<4; idx++)//{//    printf("remote_code[%d] = %#x\n",idx,remote_code[idx]);//}
    }

6、主函数

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

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

  • 示例代码如下:

    void main()
    {...IR_Pin_init();Tim2_UPCount_Init(SystemCoreClock/1000000-1,100-1);while(1){if(repeatEnable){repeatEnable = FALSE;Ir_Server();printf("key_code = %#x\n",remote_code[2]);}}}

三、演示

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

说明1:这只是实现红外接收的其中一种方法,网上还有一种比较常见的方法是利用下降沿触发,在中断中进行延迟,判断高电平持续时间以此来判断信号类别。个人感觉这不是一种很好的方法,因为在中断中进行延时会导致主函数得不到及时的处理。

说明2:在调试时,不要在中断处理中加入过多无关语句,例如打印语句,这会导致结果出错。

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

参考链接:STM32 红外线实验

更多关于 STM32 的文章

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



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

相关文章

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

在人工智能(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