完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
|
|
相关推荐
1个回答
|
|
本文开发环境:红外遥控器 + 红外接收头 : 测试过程请注意遥控和接收头的距离不要过远,本文遥控在1m以上会有不稳定现象,实际操作可以使用示波器或逻辑分析仪捕获波形,保证接收头收到的遥控数据是完整的。一、接收头的滤波输出 hx1838 红外接收头自带了滤波的功能,本文使用的接收头中,当接收到38Khz的PWM 时,输出低电平,否则输出高电平: 由于接收头的滤波功能,所以程序只需要检测高低电平的时长既可。 二、NEC编码 本文使用的 红外遥控器采用了NEC编码规则:
三、时序图 这是逻辑分析仪捕获到当遥控上按键 [1] 被按下一次的时序: 根据 NEC 的编码格式,可以实际看出这一帧数据的含义: 由于红外接收头输出的极性相反,所以高电平宽的波形为1码,高电平窄的波形为0码。 四、解码 只需要获取每个波形高低电平时长,通过然后将对应的数据置1或清0,最后读取整个数据即可。本文将时序的解码分为三个部分:
这里只列出关键配置,关于 Mx 配置定时器捕获原理及完整的示例,详见:Mx 配置定时器 的输入捕获部分
六、程序 程序主要分为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; //溢出次数清0 RX_DBG("(%2d)%4d us:Hrn",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:Lrn",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 33 struct { 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[] =================rn"); for(uint8_t i = 0;i<=(RX_SEQ_NUM*2);i++) { RX_DBG("[%d]%drn",i,rx.src_data); } RX_DBG("========= rx.rec =================rn"); if(appro(rx.src_data[0],9000) && appro(rx.src_data[1],4500)) //#1. 检测前导码 { uint8_t tmp_idx = 0; rx.repet_cnt = 0; //新按键开始,按键重复个数清0 for(uint8_t i = 2;i<(RX_SEQ_NUM*2);i++) //#2. 检测数据 { if(!appro(rx.src_data,560)) { RX_DBG("%d,err:%d != 560rn",i,rx.src_data); return 0; } i++; if(appro(rx.src_data,1680)) { rx.data.rev |= (0x80000000 >> tmp_idx); //第 tmp_idx 为置1 tmp_idx++; } else if(appro(rx.src_data,560)) { rx.data.rev &= ~(0x80000000 >> tmp_idx); //第 tmp_idx 位清0 tmp_idx++; } else { RX_DBG("%d,err:%d != 560||1680rn",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("前导码检测错误rn"); 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 rn"); return; } if(res == 2) { printf("repet(%d)rn",rx.repet_cnt); return; } switch(rx.data._rev.key_val) { case 162: printf("detected code [%s] rn","1"); break; case 98: printf("detected code [%s] rn","2"); break; case 226: printf("detected code [%s] rn","3"); break; case 34: printf("detected code [%s] rn","4"); break; case 2: printf("detected code [%s] rn","5"); break; case 194: printf("detected code [%s] rn","6"); break; case 224: printf("detected code [%s] rn","7"); break; case 168: printf("detected code [%s] rn","8"); break; case 144: printf("detected code [%s] rn","9"); break; case 152: printf("detected code [%s] rn","0"); break; case 104: printf("detected code [%s] rn","*"); break; case 176: printf("detected code [%s] rn","#"); break; case 24: printf("detected code [%s] rn","↑"); break; case 16: printf("detected code [%s] rn","←"); break; case 74: printf("detected code [%s] rn","↓"); break; case 90: printf("detected code [%s] rn","→"); break; case 56: printf("detected code [%s] rn","OK"); break; default: printf("detected unknow code rn"); 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的调试窗口中可以观察到数据情况: |
|
|
|
只有小组成员才能发言,加入小组>>
3320 浏览 9 评论
2999 浏览 16 评论
3496 浏览 1 评论
9069 浏览 16 评论
4089 浏览 18 评论
1190浏览 3评论
612浏览 2评论
const uint16_t Tab[10]={0}; const uint16_t *p; p = Tab;//报错是怎么回事?
602浏览 2评论
用NUC131单片机UART3作为打印口,但printf没有输出东西是什么原因?
2339浏览 2评论
NUC980DK61YC启动随机性出现Err-DDR是为什么?
1899浏览 2评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-12-27 03:07 , Processed in 1.445662 second(s), Total 79, Slave 60 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (威廉希尔官方网站 图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号