Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
DIY综合交流区
红外_IR_infrared
[RealTouch例程]红外遥控器的使用
发布于 2012-09-16 18:25:07 浏览:11150
订阅该版
[tocm] - [使用红外遥控器.pdf](https://oss-club.rt-thread.org/uploads/3857_dfe117560e8c16a847acddb4105ce43b.pdf) - [RealTouch_irda.zip](https://oss-club.rt-thread.org/uploads/3857_983780965fc5920f7ac483e941ab249c.zip) ## 红外接收 红外常用在短距离的遥控设备中,它造价低制造工艺简单因此应用非常广泛。然而红外 数据传输格式众多,甚至各个厂商都可以自定一套协议。在简单的应用中,比如只使用 四个方向键和两个控制键(确定、返回)的情况下,如果对具体某种制式解码,当更换 另一种制式的遥控器后又得编写另一种解码程序,这样将耗费大量时间。本文利用 STM32的PWM输入功能非常容易地实现了一种自学习型红外识别方法。通过简单的学习过 程后可适应多种遥控器。 ### 1. STM32 PWM输入简介 图一 定时9结构图 PWM输入是输入捕获的一个特殊应用,输入捕获就是当连接到定时器的引脚上产生电平 变化时对应的捕获装置会立即将当前计数值复制到另一个寄存器中。你可以开启捕获中 断然后在中断处理函数中读出保存的计数值。 与输入捕获不同的是PWM输入模式会将同一个输入信号(TI1或TI2)连接到两个捕获装 置(IC1和IC2)。这两个捕获装置一个捕获上升沿一个捕获下降沿。TI1FP1、TI2FP2它 们中的一个被选择为触发输入且从模式控制器被配置为复位模式。 下面将演示以TI1作为输入端,TIMx_CCR1作为周期测量TIMx_CCR2作为脉宽测量的设置 过程。 1) 为TIMx_CCR1选择激活输入:写TIMx_CCMR1寄存器的CC1S位域为’01’(TI1被 选择)。 2) 为TI1FP1选择激活极性:设置CC1P和CC1NP位域为’00’(上升沿激活)。 3) 为TIMx_CCR2选择激活输入:设置TIMx_CCMR1寄存器的CC2S位域为’10’(TI1 被选择)。 4) 为TI1FP2选择激活极性:设置CC2P与CC2NP位域为’11’(下降沿激活)。 5) 选择有效的触发输入:设置TImx_SMCR寄存器的TS位域为’101’(TI1FP1被选 择)。 6) 配置从模式控制器为复位模式:设置TIMx_SMCR寄存器的SMS位域为’100’。 7) 使能捕获:设置TIMx_CCER寄存器的CC1E位和CC2E位为1。 图二. PWM输入模式时序 1) 当第一次上升沿到达时IC1捕获TIMx_CCR1的值为当前计数值4,IC2不会捕获 TIMx_CCR2保持不变,计数器复位从0开始计数。 2) 第一个下降沿到达时IC2捕获TIMx_CCR2的值为2表示脉冲宽度。当上升再次到 达时TIMx_CCR1的值就表示脉冲周期了(注意:第一次上升沿捕获的是个随机值)。 2. 本例程所需硬件资源 1) IO扩展板,使用上面的红外接收模块KS0338。 2) GPIO_PE6,连接到红外接收模块的输出端。 3) 定时器9,捕获脉冲周期和脉冲宽度。 4) 红外遥控器(需自备),不能使用空调遥控器。因为空调遥控器按同一个键后 发出的脉冲序列不是固定的,它发出的脉冲与当前温度、风速等具体设置有关。 3. 例程 代码一 初始化 ```c /* irda.c */ static volatile key_t _key[NUM_KEY + 1]; static void key_init(void) { int i; for (i = 0; i < NUM_KEY + 1; i++) { memset((uint8_t*)&_key*, 0, sizeof(key_t)); _key*.flag = KEY_FLAG_DISABLE; } } void ir_init(void) { key_init(); /* GPIO初始化 */ { GPIO_InitTypeDef GPIO_InitStructure; RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); /* TIM9 通道2:PE6 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; GPIO_InitStructure.GPIO_OType = GPIO_OType_PP; GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP ; GPIO_Init(GPIOE, &GPIO_InitStructure); /* 连接TIM9到复用功能引脚 */ GPIO_PinAFConfig(GPIOE, GPIO_PinSource6, GPIO_AF_TIM9); } /* TIM9 NVIC设置 */ { NVIC_InitTypeDef NVIC_InitStructure; NVIC_InitStructure.NVIC_IRQChannel = TIM1_BRK_TIM9_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); } /* TIM9初始化 */ { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM9, ENABLE); /* APB2上的定时器输入时钟为168M,840000为计数时钟 */ TIM_TimeBaseStructure.TIM_Prescaler = 168000000 / 840000 - 1; TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; TIM_TimeBaseStructure.TIM_Period = 0xFFFF; TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; TIM_TimeBaseInit(TIM9, &TIM_TimeBaseStructure); /* PWM输入设置 */ TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; /* 通道2下降沿捕获,通道1上升沿捕获 */ TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Falling; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0x8; TIM_PWMIConfig(TIM9, &TIM_ICInitStructure); /* 选择输入触发: TI2FP2 */ TIM_SelectInputTrigger(TIM9, TIM_TS_TI2FP2); /* 选择从模式: Reset Mode */ TIM_SelectSlaveMode(TIM9, TIM_SlaveMode_Reset); TIM_SelectMasterSlaveMode(TIM9,TIM_MasterSlaveMode_Enable); /* 使能定时器 */ TIM_Cmd(TIM9, ENABLE); /* 上升沿和定时器溢出产生中断 */ TIM_ITConfig(TIM9, TIM_IT_CC1 | TIM_IT_Update, ENABLE); } } ``` 首先是按键初始化,这里定义了6个按键:上、下、左、右、确定、返回。初始化后, 按键是不可用的。因此这里指定了按键标识为KEY_FLAG_DISABLE(参见工程)。接下来 就是对GPIO和定时器以及NVIC的初始化,例程中用到的PE6是连接到TIM9的TI2的。通道 2下降沿捕获、通道1上升沿捕获,产生中断是在上升沿。因此,通道2的值为周期、通 道1的值为低电平宽度。同时也开启了定时器溢出中断,溢出中断会周期性的产生并对 溢出中断的个数进行了计数,这里用作时间基准以确定各个脉冲是在哪个时刻产生的。 ### 代码二 按键等待学习 ```c /* main.c */ int main(void) { int key; uart_init(); ir_init(); wait_for_keystudy(); /* 省略其它代码 */ } /************************************************/ /* irda.c */ void wait_for_keystudy(void) { debug("need key study,please press any key "); while (_ir_status == remote_mode_disable); for (_key_in_study = 0; _key_in_study < NUM_KEY; _key_in_study ++) { _key[_key_in_study].key = _key_in_study; switch (_key_in_study) { case 0: /* up */ debug("press up "); _key[_key_in_study].flag = KEY_FLAG_READY; while (_key[_key_in_study].flag != KEY_FLAG_ENABLE); break; case 1: /* down */ debug("press down "); _key[_key_in_study].flag = KEY_FLAG_READY; while (_key[_key_in_study].flag != KEY_FLAG_ENABLE); break; case 2: /* left */ debug("press left "); _key[_key_in_study].flag = KEY_FLAG_READY; while (_key[_key_in_study].flag != KEY_FLAG_ENABLE); break; case 3: /* right */ debug("press right "); _key[_key_in_study].flag = KEY_FLAG_READY; while (_key[_key_in_study].flag != KEY_FLAG_ENABLE); break; case 4: /* enter */ debug("press enter "); _key[_key_in_study].flag = KEY_FLAG_READY; while (_key[_key_in_study].flag != KEY_FLAG_ENABLE); break; case 5: /* back */ debug("press back "); _key[_key_in_study].flag = KEY_FLAG_READY; while (_key[_key_in_study].flag != KEY_FLAG_ENABLE); break; } } _key_in_study = 0; _key[NUM_KEY].flag = KEY_FLAG_READY; _ir_status = remote_mode_enable; debug("study over "); } ``` 当程序启动时会提示按任意键,当按任意键后会提示“press up”等,按完六个不同的 键后学习完成。 ### 代码二 脉冲采样 ```c /* irda.c */ void TIM1_BRK_TIM9_IRQHandler(void) { static uint32_t Pulse_Count =0; /* 统计脉冲个数 */ static uint32_t Tick = 0; /* 定时器溢出中断计数 */ uint16_t ccr1 = 0; uint16_t ccr2 = 0; /* 下降沿 */ if ((TIM9->SR & TIM_IT_CC2) && !(TIM9->SR & TIM_IT_Update)) { /* 周期 */ ccr2 = TIM_GetCapture2(TIM9); } /* 上升沿 */ if (TIM9->SR & TIM_IT_CC1) { /* 低电平宽度 */ ccr1 = TIM_GetCapture1(TIM9); /* 按键准备好学习 */ if ((_ir_status == remote_mode_study) && (_key[_key_in_study].flag & KEY_FLAG_READY)) { if (Pulse_Count < CODE_SAMPLING_MAX) { _key[_key_in_study].period[Pulse_Count] = ccr2; _key[_key_in_study].width[Pulse_Count] = ccr1; } Pulse_Count ++; /* 学习中 */ _key[_key_in_study].flag |= KEY_FLAG_STUDY; } /* 键值被读取后才可接受下一次按键信号 */ if ((_ir_status == remote_mode_enable) && (_key[NUM_KEY].flag & KEY_FLAG_READY)) { if (Pulse_Count < CODE_SAMPLING_MAX) { _key[NUM_KEY].period[Pulse_Count] = ccr2; _key[NUM_KEY].width[Pulse_Count] = ccr1; } /* 读到按键信号 */ _key[NUM_KEY].flag |= KEY_FLAG_ENABLE; Pulse_Count ++; } /* 脉冲的当前时间 */ Tick = _timer_tick; } /* 计数器溢出 */ if (TIM9->SR & TIM_IT_Update) { TIM_ClearITPendingBit(TIM9, TIM_IT_Update); /* 记录时间 */ _timer_tick ++; /* 有任意键按下后才开始学习 */ if ((_ir_status == remote_mode_disable) && (((_timer_tick - Tick) == CODE_SAMPLING_END) && Tick != 0)) { _ir_status = remote_mode_study; } /* 学习完成 */ if ((_key[_key_in_study].flag & KEY_FLAG_STUDY) && ((_timer_tick - Tick) == CODE_SAMPLING_END)) { _key[_key_in_study].flag = KEY_FLAG_ENABLE; _key[_key_in_study].pulse_count = Pulse_Count; /* 清除脉冲计数 */ Pulse_Count = 0; } /* 学习完成后有键被按下 */ if ((_key[NUM_KEY].flag & KEY_FLAG_ENABLE) && ((_timer_tick - Tick) == CODE_SAMPLING_END)) { _key[NUM_KEY].flag |= KEY_FLAG_PRESS; _key[NUM_KEY].flag &= ~KEY_FLAG_READY; _key[NUM_KEY].pulse_count = Pulse_Count; Pulse_Count = 0; } } } ``` 脉冲采样是放在中断处理函数中执行的,主要对脉冲周期、脉冲宽度、脉冲个数进行收 集。按键的学习过程以及学习完成后对按键脉冲的采集都是放在上升沿中断处理中的。 判断学习完成以及按键脉冲结束是放在定时器溢出中断中处理的。 ### 代码三 读取键值 ```c /* irda.c */ int32_t ir_readkey(void) { int k,n,s; if (_key[NUM_KEY].flag & KEY_FLAG_PRESS) { for(k = 0; k < NUM_KEY; k ++) { /* 应对比的脉冲个数 */ s = (_key[k].pulse_count < CODE_SAMPLING_MAX) ? _key [k].pulse_count : CODE_SAMPLING_MAX; /* period[0]是个随机数,忽略 */ for (n = 1; n < s; n++) { /* 对脉冲宽度和脉冲周期进行比较 */ if ((abs(_key[k].period[n] - _key[NUM_KEY].period[n]) > CODE_TOLERANCE_MAX) || (abs(_key[k].width[n] - _key[NUM_KEY].width[n]) > CODE_TOLERANCE_MAX)) { //debug("<%d,%d,%d>",abs(_key[k].cycle[n] - _key [NUM_KEY].cycle[n]),n,k); //debug("<%d,%d,%d> ",abs(_key[k].width[n] - _key [NUM_KEY].width[n]),n,k); break; } /* 完全匹配 */ if (n == (s - 1)) goto _ok; } } /* 清除标志 */ _key[NUM_KEY].flag &= ~KEY_FLAG_PRESS; /* 可以接受下次按键 */ _key[NUM_KEY].flag |= KEY_FLAG_READY; /* 匹配不成功 */ return (-1); _ok: _key[NUM_KEY].flag &= ~KEY_FLAG_PRESS; _key[NUM_KEY].flag |= KEY_FLAG_READY; /* 成功读取 */ return (k); } /* 没有键被按下 */ return (-1); } ``` 当学习完成后,按键的脉冲信息放在了`_key[NUM_KEY]`中。读取键值时将`_key[NUM_KEY]` 中的信息与`_key[0…5]`依次进行比较。 需要注意的是对脉冲的采样误差需要计算好,不 然就很难识别到按键。 ### 4. 下载运行 ![图像 1.png](https://oss-club.rt-thread.org/uploads/3857_f036060be0bb4e62d0ee53d3a21631f5.png) 下载程序后首先会提示`“press any key”`。 这时程序正在等待按键学习,按遥控器任 意键后会提示`“press up”`,按`“上”`键后依次按完其它键学习完成。 这时你可以按学习过的键以及没学习过的键进行测试了。
查看更多
9
个回答
默认排序
按发布时间排序
grissiom
2012-09-16
这家伙很懒,什么也没写!
牛! 也就是说红外按键是靠脉冲的长度来区分按键的?
heyuanjie87
2012-09-16
这家伙很懒,什么也没写!
依靠脉冲的周期、低电平宽度、脉冲个数这三个参数来区分不同按键的。
lujun723
2013-02-21
这家伙很懒,什么也没写!
这么好的程序没人学习么?还有王法吗,还有法律吗? [s:154]
lixupeng
2013-07-02
这家伙很懒,什么也没写!
可以参考 mark
lifan_cn
2013-09-17
这家伙很懒,什么也没写!
是一个很好的实验
ta00at
2014-07-21
这家伙很懒,什么也没写!
好帖,学习
weiyuliang
2024-06-17
这家伙很懒,什么也没写!
非常感谢,学习了
游走在01的海洋
2024-06-17
In the end, it's not the years in your life that count. It's the life in your years.
思路学习了,mark
撰写答案
登录
注册新账号
关注者
0
被浏览
11.2k
关于作者
heyuanjie87
这家伙很懒,什么也没写!
提问
34
回答
86
被采纳
1
关注TA
发私信
相关问题
1
[项目]搞个开源的硬件项目
2
硬件计划贴,及时更新,欢迎提意见
3
软件计划贴,及时更新,欢迎提意见::WMA,MOUNT,LWIP等问题急需解决.
4
MMS协议
5
定点的wma解压库-libwma
6
QQ群记录 [20090821]
7
STM32网络收音机PCB报名征集
8
第一版调试记录
9
第二版硬件讨论
10
RADIO项目相关模块规格--欢迎大家自己做板时规格与此兼容,减少重复劳动
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
最新文章
1
使用百度AI助手辅助编写一个rt-thread下的ONVIF设备发现功能的功能代码
2
RT-Thread 发布 EtherKit开源以太网硬件!
3
rt-thread使用cherryusb实现虚拟串口
4
《C++20 图形界面程序:速度与渲染效率的双重优化秘籍》
5
《原子操作:程序世界里的“最小魔法单位”解析》
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
WIZnet_W5500
ota在线升级
UART
PWM
cubemx
freemodbus
flash
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
Debug
编译报错
msh
SFUD
keil_MDK
rt_mq_消息队列_msg_queue
at_device
ulog
C++_cpp
本月问答贡献
踩姑娘的小蘑菇
7
个答案
3
次被采纳
a1012112796
13
个答案
2
次被采纳
张世争
9
个答案
2
次被采纳
rv666
5
个答案
2
次被采纳
用户名由3_15位
11
个答案
1
次被采纳
本月文章贡献
程序员阿伟
7
篇文章
2
次点赞
hhart
3
篇文章
4
次点赞
大龄码农
1
篇文章
2
次点赞
ThinkCode
1
篇文章
1
次点赞
Betrayer
1
篇文章
1
次点赞
回到
顶部
发布
问题
分享
好友
手机
浏览
扫码手机浏览
投诉
建议
回到
底部