OLED显示红外遥控键码

基本原理

本遥控器的编码是NEC编码,为PWM(脉冲宽度调制)。
发射红外载波的时间固定,通过改变不发射载波的时间来改变占空比。

  • 逻辑“0”是由0.56ms的38KHZ载波和0.560ms的无载波间隔组成;
  • 逻辑“1”是由0.56ms的38KHZ载波和1.685m 的无载波间隔组成;
  • 结束位是0.56ms的38K载波。
  • 重复码由9ms红外脉冲和2.25ms的无红外脉冲以及560us的红外脉冲组成。

若发了一次命令码之后,一直按住遥控器按键并未松手,遥控器发射端将不会再发送命令码,而是每隔 110ms 时间,发送一段重复码。
image.png

过程分析

初始化部分

通用配置过程:

  1. 将相关的gpio和硬件挂载到RCC
  2. 初始化GPIO
  3. 初始化TIM时基单元
  4. 初始化IC输入捕获
  5. 初始化NVIC中断管理
  6. 实现中断处理函数
  7. 启用中断
  8. 启动定时器

image.png
TIM3的CH1通道位于PA6引脚。

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode =GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStructure.GPIO_Speed =GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStructure);

配置时基单元。arr和psc作为函数参数传入,支持动态调整。
在本文中,分频系数为72,这样标准周期就是1us,方便后续计算。

TIM_InternalClockConfig(TIM3);
TIM_TimeBaseInitTypeDef	TIM_TimeBaseStructure;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseStructure.TIM_Period = arr - 1;
TIM_TimeBaseStructure.TIM_Prescaler = psc - 1;
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);

配置输入捕获。将TIM_ICInitStructure.TIM_ICFilter设置为0xf是为了对输入捕获信号进行滤波,滤波时间为8个时钟周期。

  • 对应16个采样点的平均,意味着最终捕获值是16个相邻采样值的平均值。
  • 这相当于对输入信号进行8个时钟周期的滤波,因为每个采样周期占用一个时钟周期。
TIM_ICInitTypeDef	TIM_ICInitStructure;
TIM_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM_ICInitStructure.TIM_ICFilter = 0xf;
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling;
TIM_ICInitStructure.TIM_ICPrescaler = TIM_CKD_DIV1;
TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM_ICInit(TIM3,&TIM_ICInitStructure);

配置中断优先级。中断优先级的配置方案需要在主函数中设置:NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);

NVIC_InitTypeDef	NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority =1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_Init(&NVIC_InitStructure);

在之前的《PWM输入/输出》中,输入捕获采用的是事件方式:

TIM_SelectInputTrigger(TIM3,TIM_TS_TI1FP1);
TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_Reset);

当时没有设置中断,设置了事件。事件由硬件自动完成,不需要我们实现中断处理函数,只需要指定处理方式即可。
我们这次用不到事件。
涉及各种码的判断,以及信号处理阶段的判断,硬件事件难以处理这种复杂任务,需要通过中断实现。
启用中断标志位,在TIM3的CC1通道出现下降沿时,自动触发TIM_IT_CC1中断:

TIM_ITConfig(TIM3,TIM_IT_CC1,ENABLE);

配置完毕,启动TIM定时器:

TIM_Cmd(TIM3,ENABLE);

中断处理函数

通过输入捕获,判断两个下降沿之间的时间间隔:

  • 引导码:13.5ms
  • 重复码:11.25ms
  • 逻辑0:1.12ms
  • 逻辑1:2.245ms

将输入捕获通道的GPIO设置为上拉输入。在没有外界信号时会自动上拉到高电平。
时间间隔通过TIM_GetCounter(TIM3)获取。TIM定时器设置的标准周期为1us,返回值即为下降沿之间的时间间隔,单位为us。
获取之后通过TIM_SetCounter(TIM3,0);设置寄存器值为0,相当于重新开始计数。由于一个周期的开始和结束都是下降沿,所以上一个周期的结束接着就是下一个周期的开始,计数值误差可以接受。
操作类似于一个状态机:

  • 初始处于空闲状态,TIM定时器的值是无意义的。在第一个下降沿到来时,设置TIM寄存器值为0,开始计数,进入准备状态。
  • 准备状态下,第二个下降沿到来时,根据时长判断是引导码还是重复码。重复码则回到空闲状态。引导码还需要读取具体的指令,进入接受状态。
  • 接收状态下,逐位接收,共32位。接收结束后回到空闲状态。等待下一个响应。

为什么引导码和重复码在同一个状态下判断?

对于上面四种码的时间间隔,很明显:引导码与重复码相近,逻辑0和逻辑1相近。
如果同一个阶段去判断时间间隔差距过大的码,可能造成数据遗漏。

如何按位接收?

需要用到指针,通过模运算、位运算实现。
指令一共有32位,需要一个char[4]类型的数组。
创建一个指针,其实是整型,

  • 对8做除法,用于对指定下标的char赋值
  • 对8取模之后,值介于[0,7],为指定位赋值

这种做法在存储器扩展、cache映射中都有应用,映射的常用方式就是取模。
如果学过,应该不会陌生。

数据校验

NEC编码的数据包括:
8位地址码+8位地址码的反码+8位命令码+8位命令码的反码
验证操作就是将反码部分按位取反,判断是否相等。

Addr_Temp = ~IR_DATA[1];
Cmd_Temp  = ~IR_DATA[3];
if (IR_DATA[0] == Addr_Temp && IR_DATA[2] == Cmd_Temp)  //数据验证
{IR_Address = IR_DATA[0];IR_Command = IR_DATA[2];IR_DataFlag = 1;
}

代码实现

到目前,需要创建如下变量:

  • uint8_t IR_State:记录当前状态:空闲/准备/接收
  • uint16_t IR_Time:下降沿间隔时间:引导/重复/0/1
  • uint8_t IR_RepeatFlag,IR_DataFlag:数据状态:重复/有数据
  • uint8_t IR_DATA[4]:数据:地址码+地址码反码+命令码+命令码反码
  • uint8_t IR_pData:指针:对IR_DATA进行段选和位选
void TIM3_IRQHandler()
{uint8_t Addr_Temp,Cmd_Temp;if (TIM_GetITStatus(TIM3,TIM_FLAG_CC1) != RESET){if (IR_State == 0)     //空闲态{TIM_SetCounter(TIM3,0);IR_State = 1;}else if (IR_State == 1) //准备态{IR_Time = TIM_GetCounter(TIM3);if (IR_Time > 13500 - 500 && IR_Time < 13500+500)  //Start信号{IR_State = 2;}else if (IR_Time > 11250 - 500 && IR_Time < 11250+500)  //Repeat信号{IR_State = 0;IR_RepeatFlag = 1;}else{}TIM_SetCounter(TIM3,0);}else if (IR_State == 2) //接收态{IR_Time = TIM_GetCounter(TIM3);if (IR_Time > 1120 -500 && IR_Time < 1120+500)   //逻辑0{IR_DATA[IR_pData/8] &= ~(0x01 << (IR_pData %8));IR_pData++;}else if (IR_Time > 2250 -500 && IR_Time < 2250 + 500) //逻辑1{IR_DATA[IR_pData/8] |= (0x01 << (IR_pData %8));IR_pData++;}else {IR_pData = 0;IR_State = 1;}if (IR_pData>=32)   //如果接收完32位数据{IR_pData = 0;Addr_Temp = ~IR_DATA[1];Cmd_Temp  = ~IR_DATA[3];if (IR_DATA[0] == Addr_Temp && IR_DATA[2] == Cmd_Temp)  //数据验证{IR_Address = IR_DATA[0];IR_Command = IR_DATA[2];IR_DataFlag = 1;}IR_State = 0;}TIM_SetCounter(TIM3,0);}else{}TIM_ClearITPendingBit(TIM3,TIM_IT_CC1);}
}

实验效果

飞线

红外部分并不直接与PA6相连,需要飞线。
用杜邦线连接IF和TIM3的CH1所在的PA6即可。
image.png

在主函数中调用

uint8_t Address;
uint8_t Command;
uint8_t Num;
int main(void)
{NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);Remote_Init(30000,72);OLED_Init();OLED_ShowString(1,1,"ADDR CMD NUM");OLED_ShowString(2,1,"00   00  000");while(1){if (IR_GetDataFlag() || IR_GetRepeatFlag()){Address = IR_GetAddress();Command = IR_GetCommand();OLED_ShowHexNum(2,1,Address,2);OLED_ShowHexNum(2,6,Command,2);if (Command == IR_VOL_ADD){Num++;}if(Command == IR_VOL_MINUS){Num--;}OLED_ShowNum(2,10,Num,3);}}
}

VID_20240215_014048

参考

  • STM32F103C8T6引脚定义.xlsx
  • stm32 使用说明+笔记(必读).pdf
  • 32版开发板原理图.pdf

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/470553.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【JAVA】计算机软件工程人工智能研究生复试资料整理

1、JAVA 2、计算机网络 3、计算机体系结构 4、数据库 5、计算机租场原理 6、软件工程 7、大数据 8、英文 自我介绍 1. Java 1. == 和 equals的区别 比较基本数据类型是比较的值,引用数据类型是比较两个是不是同一个对象,也就是引用是否指向同 一个对象,地址是否相同,equ…

代码随想录算法训练营第51天 | 139.单词拆分 + 多重背包理论基础 + 背包问题总结

今日任务 139.单词拆分 关于多重背包&#xff0c;你该了解这些&#xff01; 背包问题总结篇&#xff01; 139.单词拆分 - Medium 题目链接&#xff1a;力扣&#xff08;LeetCode&#xff09;官网 - 全球极客挚爱的技术成长平台 给你一个字符串 s 和一个字符串列表 wordDict …

Linux中sigaction函数和SIGCHLD信号的使用

sigaction函数&#xff1a; 函数说明&#xff1a;注册一个信号处理函数 函数原型&#xff1a;int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 函数参数&#xff1a; signum:捕捉的信号act:传入参数&#xff0c;…

Flutter Android开发 梳理Google Material Design颜色体系

前言 做安卓开发&#xff08;Kotlin语言&#xff09;&#xff0c;Flutter开发的人员应该都听说过谷歌一直推崇的Material Design&#xff0c;而Material Design Color是其推崇的颜色体系&#xff0c;具体来说&#xff0c;Material Design Color是一套旨在帮助设计师和开发者创…

洛谷_P1781 宇宙总统_python写法

这道题如果去比较数字的大小是不行的&#xff0c;因为位数太多了&#xff0c;所以就想到用字符串来比较&#xff0c;字符串比较也有讲究&#xff0c;比如说数字长度越长说明这个数字越大&#xff0c;对于相等长度的数字&#xff0c;我们只需要去比较每一位的数字&#xff0c;只…

Windows11通过SMB映射NAS网络驱动磁盘

环境 NAS&#xff1a;威联通TS-416 操作系统&#xff1a;Windows11 第一步 连接NAS winr 打开运行&#xff0c;输入NAS局域网IP地址&#xff0c;按照如下的格式输入 然后输入NAS的账号和密码就可以通过SMB连接到NAS了 第二步 映射网络驱动器 举个栗子&#xff1a;右键Stora…

【Linux系统编程】25.mmap、munmap

目录 mmap 参数addr 参数length 参数prot 参数flags 参数fd 参数offset 返回值 保险使用方式 munmap 参数addr 参数length 返回值 测试代码1 测试结果 使用注意事项 测试代码2 测试结果 测试代码3 测试结果 匿名映射 测试代码4 测试结果 mmap 将一个指…

可变参数(c/c++)

目录 一、C语言版本 二、C的实现方法 2.1数据包 2.2sizeof...运算符 2.3可变参数模板的使用 2.4emplace_back() 有时候我们在编写函数时&#xff0c;可能不知道要传入的参数个数&#xff0c;类型 。比如我们要实现一个叠加函数&#xff0c;再比如c语言中的printf,c中的emp…

Go语言每日一练——链表篇(九)

传送门 牛客面试笔试必刷101题 ----------------链表相加(二) 题目以及解析 题目 解题代码及解析 解析 这一道题主要是要对链表相加的过程进行模拟&#xff0c;虽然思路不难但是细节出比较多&#xff0c;这里博主的思路主要是先将两个链表反转过来然后以Head1为基础来模拟…

C语言:指针的基础详解

目录 1. 内存 2. 取地址& 3. 指针变量 4. 解引用 4.1 *解引用 4.2 []解引用 4.3 ->解引用 5. 指针变量的大小 5.1 结论 6. 指针运算 7. void* 指针 8. const修饰指针 8.1 const修饰变量 8.2 const修饰指针变量 8.3 结论 9. 野指针 9.1 为什么会出现野指…

[嵌入式系统-15]:RT-Thread -1- 简介与技术架构

目录 一、RT-Thread简介 1.1 简介 1.2 功能特点 1.3 发展历史 1.4 应用场合 1.5 与Linux的比较 1.6 ​​​​​​​RT-Thread优缺点 二、技术架构 2.1 分层架构​编辑 2.2 功能组件 2.3 应用程序接口RT-Thread API 2.4 应用程序接口&#xff1a;RT-Thread API、POS…

鸿蒙(HarmonyOS)项目方舟框架(ArkUI)之Navigation组件

鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#xff08;ArkUI&#xff09;之Navigation组件 一、操作环境 操作系统: Windows 10 专业版、IDE:DevEco Studio 3.1、SDK:HarmonyOS 3.1 二、Navigation组件 鸿蒙&#xff08;HarmonyOS&#xff09;项目方舟框架&#…