hx1838 红外遥控(1):接收时序的解码
本文开发环境:
- MCU型号:STM32F103C8T6
- IDE环境: MDK 5.27
- 代码生成工具:STM32CubeMx 5.6.1
- HAL库版本:STM32Cube_FW_F1_V1.8.0
本文内容:
- STM32 使用 定时器捕获功能捕获红外时序
- 解码 hx1838 时序
附件:
- MDK5 示例工程
红外遥控器 + 红外接收头 :
测试过程请注意遥控和接收头的距离不要过远,本文遥控在1m以上会有不稳定现象,实际操作可以使用示波器或逻辑分析仪捕获波形,保证接收头收到的遥控数据是完整的。
一、接收头的滤波输出
hx1838 红外接收头自带了滤波的功能,本文使用的接收头中,当接收到38Khz的PWM 时,输出低电平,否则输出高电平:
由于接收头的滤波功能,所以程序只需要检测高低电平的时长既可。
二、NEC编码
本文使用的 红外遥控器采用了NEC编码规则
:
- NEC 载波频率为 38Khz
- 引导码:9ms 高电平 + 4.5ms 低电平
- 1 码 :0.56 ms 高电平 + 0.56 ms 低电平
- 0 码 : 0.56ms 高电平 + 1.68 ms 低电平
- 结束码 :0.56ms 高电平
- 数据帧格式:引导码 + 地址 + 地址反码 + 键值 + 键值反码 + 结束码
- 重复帧格式:9ms 高电平 + 2.25ms低电平 + 结束位 + 结束码
- 高位在前,即首先收到的是高位的数据
注:本文使用的接收头,电平极性与协议相反。所以,当捕获到一个 9ms 低电平 + 4.5ms 高电平时,即收到一个引导码。
三、时序图
这是逻辑分析仪捕获到当遥控上按键 [1] 被按下一次的时序:
根据 NEC 的编码格式,可以实际看出这一帧数据的含义:
由于红外接收头输出的极性相反,所以高电平宽的波形为1码,高电平窄的波形为0码。
四、解码
只需要获取每个波形高低电平时长,通过然后将对应的数据置1或清0,最后读取整个数据即可。本文将时序的解码分为三个部分:
- 时序捕获
这部分程序负责捕获高低电平的之间变化,并记录其持续的值记录到数组中,初始化时,将定时器通道配置为下降沿捕获,当捕获到一个下降沿,就将捕获极性改为上升沿,并记录此时时间,下一次捕获时,说明获得了一个低电平,将两次时间作差,既可以得到低电平的时间。 - 时序解码
这部分程序负责读取时序捕获的值,并根据时长判断码值,最后合并成数据,如读取数组第一个值和第二个值分为为 9ms 和 4.5 ms,那么将认为捕获到一个引导码,读取到一个0.56ms低电平和0.56ms高电平,则认为读取到一个 0 码。 - 执行程序
这部分根据解析到的键值执行相应的代码
五、STM32CubeMx 输入捕获配置
这里只列出关键配置,关于 Mx 配置定时器捕获原理及完整的示例,详见:Mx 配置定时器 的输入捕获部分
- 本文将 TIM1 的 CH1 设置为输入捕获通道:
- 并打开了定时器溢出中断和捕获中断:
将定时器配置为72分频,即计数频率为 1M (TIM1 时钟源为 72M),可以捕获us级别的时间间隔。
六、程序
程序主要分为4个部分:
- 调试语句
- 波形捕获
- 波形解码
- 程序执行
- 使能捕获
空闲状态:本文使用变量sta_idle
来标志空闲状态,空闲状态只能是没有数据正在捕获中。
1. 调试部分
当 宏定义 RX_DBG_EN 为 1 时,RX_DGB 等效于 printf ,可以正常的打印数据,当 RX_DBG_EN 为0 时 RX_DGB 为空语句,程序不打印数据。
#define RX_DBG_EN 0#if RX_DBG_EN
#define RX_DBG(format, ...) printf(format, ##__VA_ARGS__)
#else
#define RX_DBG(format, ...) ;
#endif
这样,可通过RX_DBG_EN 宏,来控制程序是否打印调试信息。
本文示例工程中,宏被关闭,如果需要查看调试信息,将值改为1即可。由于打印数据较多,有一定概率打印不完整,一般重复3-4次即可获取一次完整数据。打开调试宏请避免使用连按功能。
2. 波形捕获
本文设置了TIM1的CH1为捕获通道,波形的捕获需要2个回调函数:
- void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
极性捕获函数用来捕获 IO 口的电平跳变,并记录当时计时的值 - void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
因为定时器溢出以后,计数会清0(本文配置决定),所以当捕获的电平在计数周期末时比如(9999),下一个电平来时候,可能捕获的值为550,所以计算时间差需要 550 + 10000(1次溢出)- 9999
2.1 捕获中断回调函数
rx_rcv_init()
是 HAL_TIM_IC_CaptureCallback()
子函数:
/* 电平捕获中断回调 */
void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
{static uint16_t tmp_cnt_l,tmp_cnt_h;if(TIM1 == htim->Instance){switch(cap_pol) //根据极性标志位判断捕获是低电平还是高电平{ /* 捕获到下降沿 */case 0:tmp_cnt_l = HAL_TIM_ReadCapturedValue(&htim1,TIM_CHANNEL_1); //记录当前时刻TIM_RESET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1); //复位极性配置TIM_SET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1, TIM_INPUTCHANNELPOLARITY_RISING); //改变极性cap_pol = 1; //极性标志位改为上升沿if(sta_idle) //如果当前为空闲状态,空闲捕获到的时序,为第一个下降沿{rx_rcv_init();break; //返回}rx_frame[cap_pulse_cnt] = tim_udt_cnt * 10000 + tmp_cnt_l - tmp_cnt_h; //与上次捕获的计时作差,记录值tim_udt_cnt = 0; //溢出次数清0RX_DBG("(%2d)%4d us:H\r\n",cap_pulse_cnt,rx_frame[cap_pulse_cnt]); //DBG:打印捕获到的电平及其时长cap_pulse_cnt++; //计数++break;/* 捕获到上升沿 */case 1:tmp_cnt_h = HAL_TIM_ReadCapturedValue(&htim1,TIM_CHANNEL_1);TIM_RESET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1); TIM_SET_CAPTUREPOLARITY(&htim1, TIM_CHANNEL_1, TIM_ICPOLARITY_FALLING);cap_pol = 0; if(sta_idle){rx_rcv_init();break;}rx_frame[cap_pulse_cnt] = tim_udt_cnt * 10000 + tmp_cnt_h - tmp_cnt_l;tim_udt_cnt = 0;RX_DBG("(%2d)%4d us:L\r\n",cap_pulse_cnt,rx_frame[cap_pulse_cnt]);cap_pulse_cnt++;break;default:break;}}
}
第38行:这里需要判断一次,是否为第一个边缘,如果是就执行初始化相关内容,然后退出,第一个边缘是没有上一次边缘可以与之作差的,此后的每一个边缘触发,都是一个电平时长的捕获。
cap_pulse_cnt
,是方波个数的统计,也用来做方波数组的下标值,因为他们存在一一对应的关系。
tim_udt_cnt
在非空闲状态定时器溢出一次,将累加1,每一次捕获到电平,计算时长以后,都需要将变量清零。所以,当这个值在一定未被清零时候,说明一定时间没有数据产生,可借助它来判断MCU是否进入空闲状态。
2.2 定时器1 溢出回调函数
/* 溢出中断回调函数 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{ if(TIM1 == htim->Instance){if(sta_idle) //空闲状态,不作任何处理{return;}tim_udt_cnt++; //溢出一次if(tim_udt_cnt == 3) //溢出3次{tim_udt_cnt = 0; //溢出次数清零sta_idle = 1; //这是为空闲状态cap_frame = 1; //标记捕获到新的数据}}
}
当 tim_udt_cnt
变量累计到3时候,证明至少20ms,至多30ms未捕获到新的电平,固判断为空闲状态
3. 时序解码
波形捕获部分,已经将捕获到的电平宽度一一保存在数组rx_frame
中,所以这部分程序需要分析数组的各个值。这部分需要一个数据结构:
#define RX_SEQ_NUM 33struct {uint16_t src_data[RX_SEQ_NUM*2];uint16_t repet_cnt;union{uint32_t rev;struct{uint32_t key_val_n:8;uint32_t key_val :8;uint32_t addr_n :8;uint32_t addr :8;}_rev;}data;
}rx;
RX_SEQ_NUM
:定义了波形(1高电平+1低电平的组合)的个数,此处不考虑结束码,所以是 33 个 波形,
src_data
:用存储捕获到的值,由于一个波形有一个高电平和一个低电平,所以数组的长度需要 RX_SEQ_NUM * 2
data
:是一个共同体,其中变量 rev 和 结构体_rev 处于同一内存中,程序可以直接操作rev,达到改变 结构体_rev 成员值的方法。
uint8_t appro(int num1,int num2)
{return (abs(num1-num2) < 300);
}uint8_t hx1838_data_decode(void)
{memcpy(rx.src_data,rx_frame,RX_SEQ_NUM*4);memset(rx_frame,0x00,RX_SEQ_NUM*4); RX_DBG("========= rx.src[] =================\r\n");for(uint8_t i = 0;i<=(RX_SEQ_NUM*2);i++){RX_DBG("[%d]%d\r\n",i,rx.src_data[i]);}RX_DBG("========= rx.rec =================\r\n");if(appro(rx.src_data[0],9000) && appro(rx.src_data[1],4500)) //#1. 检测前导码{uint8_t tmp_idx = 0;rx.repet_cnt = 0; //新按键开始,按键重复个数清0for(uint8_t i = 2;i<(RX_SEQ_NUM*2);i++) //#2. 检测数据{if(!appro(rx.src_data[i],560)){RX_DBG("%d,err:%d != 560\r\n",i,rx.src_data[i]);return 0;}i++;if(appro(rx.src_data[i],1680)){rx.data.rev |= (0x80000000 >> tmp_idx); //第 tmp_idx 为置1tmp_idx++;}else if(appro(rx.src_data[i],560)){rx.data.rev &= ~(0x80000000 >> tmp_idx); //第 tmp_idx 位清0tmp_idx++;}else{RX_DBG("%d,err:%d != 560||1680\r\n",i,rx.src_data[i+1]);return 0;}}}else if(appro(rx.src_data[0],9000) && appro(rx.src_data[1],2250) && appro(rx.src_data[2],560)){rx.repet_cnt++;return 2;}else{RX_DBG("前导码检测错误\r\n");return 0;}return 1;
}
第3,4行:拷贝捕获电平的数组到新数组中,并将捕获电平的数组清零,由于数组元素为2个字节所以长度需要 x2,即为波形个数的4倍。
注意到,由于捕获到的时序是有误差的,所以程序不能直接判断值是否相等,所以appro(int num1,int num2)
用来判断两个值是否接近,只需要接近标准值即可,本文设置的范围为 ± 300 us。
程序首先是判断前2个值,若为 9500us 和 4500 us,就是前导码,可以进一步判断后续的值,若前3个值是 9500us + 2250us + 560us 那就是重复码,rx.repet_cnt ++
累加1,若都不是,则数据出错。
4. 执行部分
当解码程序将捕获的电平解析以后,就可以根据键值来执行对应的程序了:
void hx1838_proc(uint8_t res)
{if(res == 0){printf("error \r\n");return;}if(res == 2){printf("repet(%d)\r\n",rx.repet_cnt);return;} switch(rx.data._rev.key_val){case 162:printf("detected code [%s] \r\n","1");break;case 98:printf("detected code [%s] \r\n","2");break;case 226:printf("detected code [%s] \r\n","3");break;case 34:printf("detected code [%s] \r\n","4");break;case 2:printf("detected code [%s] \r\n","5");break;case 194:printf("detected code [%s] \r\n","6");break;case 224:printf("detected code [%s] \r\n","7");break;case 168:printf("detected code [%s] \r\n","8");break;case 144:printf("detected code [%s] \r\n","9");break;case 152:printf("detected code [%s] \r\n","0");break;case 104:printf("detected code [%s] \r\n","*");break;case 176:printf("detected code [%s] \r\n","#");break;case 24:printf("detected code [%s] \r\n","↑");break;case 16:printf("detected code [%s] \r\n","←");break;case 74:printf("detected code [%s] \r\n","↓");break;case 90:printf("detected code [%s] \r\n","→");break;case 56:printf("detected code [%s] \r\n","OK");break;default:printf("detected unknow code \r\n");break;}
}
程序直接判断了rx.data._rev.key_val
的值,这是因为在对rev
操作时,已经改变了它的值,这体现了共同体的便利性,否则需要程序进行拆解。这里执行程序比较简单,只是简单把值打印出来。其中,参数为 解码函数的返回值,0表示数据错误,1表示新的按键值,2表示重复码。
5. 使能捕获
最后需要注意,HAL库默认是不开启捕获的,所以需要用户使能:
void hx1838_cap_start(void)
{HAL_TIM_Base_Start_IT(&htim1);HAL_TIM_IC_Start_IT(&htim1,TIM_CHANNEL_1);
}
hx1838_cap_start()
分别打开了溢出中断和捕获中断
6. 测试
void HX1838_demo(void)
{hx1838_cap_start(); //使能捕获while(1) { if(cap_frame) //如果捕获到新的数据{ hx1838_proc(hx1838_data_decode()); //处理解码后的数据cap_frame = 0; //标记未捕获到数据}};
}
上文代码写在新建立文件HX1838.c
中,在main
函数调用需要声明外部函数:
int main(void)
{...HAL_Init();
...while (1){... ...extern void HX1838_demo(void);HX1838_demo();}/* USER CODE END 3 */
}
本文使用ST-LINk的调试打印功能,运行程序后MDK的调试窗口中可以观察到数据情况:
七、附件
IR 接收 示例工程
提取码:1234
hx1838 红外遥控(1):接收时序的解码相关推荐
- Arduino VS/HX1838红外遥控接收和发送DEMO
Arduino VS/HX1838红外遥控接收和发送DEMO
- NEC红外协议编码,38K红外遥控编码,红外遥控发射接收电路选型设计
NEC为红外遥控最常用的编码,红外载波频率为38KHz,其协议小巧简单,非常适合家电设备的控制.其他的还有 Phillips(RCA)的RC-5和RC-6,但那只是IR协议的少数. 本篇博文参照国外博 ...
- 基于51单片机的红外遥控信号的发射和接收
本讲内容: 介绍红外遥控的知识,通过例程展示红外遥控程序的编写方法. 红外线简介: 在光谱中波长自760nm至400um的电磁波称为红外线,它是一种不可见光.目前几乎所有的视频和音频设备都可以通过红外 ...
- 红外遥控C语言程序设计,光电红外遥控开关设计(光电系统课程设计)【PCB图仿真图单片机C语言分工心得】..doc...
光电红外遥控开关设计(光电系统课程设计)[PCB图仿真图单片机C语言分工心得]. 本科生课程论文 论文题目光电红外遥控开关设计课程名称光电系统设计学生姓名学号所在学院所在班级指导教师 目 录 摘要3 ...
- arduino学习笔记十八--红外遥控检测
介绍 远程遥控技术又称为遥控技术,是指实现对被控目标的遥远控制,在工业控制.航空航天.家电领域应用广泛.红外遥控是一种无线.非接触控制技术,具有抗干扰能力强,信息传输可靠,功耗低,成本低,易实现等显著 ...
- 基于FPGA的红外遥控解码与PC串口通信
基于FPGA的红外遥控解码与PC串口通信 zouxy09@qq.com http://blog.csdn.net/zouxy09 这是我的<电子设计EDA>的课程设计作业(呵呵,这个月都拿 ...
- 万能遥控程序c语言,51单片机万能红外遥控解码程序
51hei单片机论坛里流传的遥控解码程序现在都弱爆了根本解不了现在的遥控自己写个万能红外遥控解码 本程序中需要用的头文件下载:http://www.51hei.com/mcu/2564.html // ...
- arduino 的红外遥控解码
测试发现 遥控器的角度不一样 或者有反射 导致解析的码也不一样 程序 /** IRrecvDemo-LED* =====================功能说明================ ...
- FPGA 24 工程模块 红外遥控(NEC协议)解码
FPGA 24 红外遥控(NEC协议)解码 主要功能 :设计了一个红外 NEC协议的解码模块 实现(设计)流程:通过遥控器发送的红外信号,外围红外信号接收传感器对数据进行接收,得到一个在基频上的高低电 ...
最新文章
- 今天收到上海某公司的全英文笔试题(some question of interview )
- 系统诊断概述-如何通过windbg来dump特定process的memory.
- 【学习笔记】生产订单实际价格差异计算
- 使用adb查看数据库的一些命令
- java excel自动保存_java读取excel的内容(可保存到数据库中)
- cad相对坐标快捷键_CAD里面绝对、相对、极坐标是什么?如何区别
- .NET 4.5 MEF 基于约定的编程模型
- 系统辨识(一):相关概念
- Tomcat 修改启动端口号
- Idea打包jar 及jar包反编译为代码的多种方法
- 手写汉字识别的发展综述
- CE8301与自激振荡
- WKWebView - 1
- 算术左、右移位与逻辑左、右移位,右移一位和除二的区别、算术溢出
- ftl模板导出excel_freemarker导出Excel
- 意识理论综述:众多竞争的意识理论如何相互关联?
- HBase批量写入数据
- 测试小故事6:术业有专攻
- 嵌入式系统设计电子书
- 设计中的设计-设计的意义