一、模块来源
模块实物展示:
资料链接:https://pan.baidu.com/s/1dEWVMIFDWb7k1NcsRy5hHA
资料提取码:uucv
二、规格参数
1.CR2025环保纽扣电池,容量160mah
2.发射距离:8m以上(具体和周围环境、接收端的灵敏度等因素有关)
3.有效角度:60度
4.面贴材料:0.125mmPET,有效寿命2万次。
5.品质稳定,性价比高
6.静态电流3-5uA,动态电流3-5mA。
以上信息见厂家资料文件
三、移植过程
我们的目标是将例程移植至CW32F030C8T6开发板上【能够实现红外信号接收的功能】。首先要获取资料,查看数据手册应如何实现读取数据,再移植至我们的工程。
3.1查看资料
在光谱中波长自760nm至400um的电磁波称为红外线,它是一种不可见光。红外线通信的例子我们每个人应该都很熟悉,目前常用的家电设备几乎都可以通过红外遥控的方式进行遥控,比如电视机、空调、投影仪等,都可以见到红外遥控的影子。这种技术应用广泛,相应的应用器件都十分廉价,因此红外遥控是我们日常设备控制的理想方式。
红外线的通讯原理
红外光是以特定的频率脉冲形式发射,接收端收到到信号后,按照约定的协议进行解码,完成数据传输。在消费类电子产品里,脉冲频率普遍采用 30KHz 到 60KHz 这个频段,NEC协议的频率就是38KHZ。这个以特定的频率发射其实就可以理解为点灯,不要被复杂的词汇难住了,就是控制灯的闪烁频率(亮灭),和刚学单片机完成闪烁灯一样的意思,只不过是灯换了一种类型,都是灯。
接收端的原理: 接收端的芯片对这个红外光比较敏感,可以根据有没有光输出高低电平,如果发送端的闪烁频率是有规律的,接收端收到后输出的高电平和低电平也是有规律对应的,这样发送端和接收端只要约定好,那就可以做数据传输了。
红外线传输协议可以说是所有无线传输协议里成本最低,最方便的传输协议了,但是也有缺点,距离不够长,速度不够快;当然,每个传输协议应用的环境不一样,定位不一样,好坏没法比较,具体要看自己的实际场景选择合适的通信方式。
NEC协议介绍
NEC协议是众多红外线协议中的一种(这里说的协议就是他们数据帧格式定义不一样,数据传输原理都是一样的),我们购买的外能遥控器、淘宝买的mini遥控器、电视机、投影仪几乎都是NEC协议。像格力空调、美的空调这些设备使用的就是其他协议格式,不是NEC协议,但是只要学会一种协议解析方式,明白了红外线传输原理,其他遥控器协议都可以解出来。
NEC协议一次完整的传输包含: 引导码、8位地址码、8位地址反码、8位命令码、8位命令反码。这里我们主要讲解如何接收红外发送端发送的NEC协议内容。
引导码:由9ms的低电平+4.5ms的高电平组成。
4个字节的数据: 地址码+地址反码+命令码+命令反码。这里的反码可以用来校验数据是否传输正确,有没有丢包。
重点: NEC协议传输数据位的时候,0和1的区分是依靠收到的高、低电平的持续时间来进行区分的。这是解码关键。
数据发送0码:0.56ms低电平+ 0.56ms的高电平。
数据发送1码:0.56ms低电平+1.68ms的高电平
所以,收到一个数据位的完整时间表示方法是这样的:
收到数据位0: 0.56ms低电平+ 0.56ms的高电平 收到数据位1: 0.56ms低电平+1.68ms的高电平
还有一个重复码,它是由一个 9ms 的低电平和一个 2.5ms 的高电平组成。当一个红外信号连续发送时,可以通过发送重复码的方式快速发送。
3.2引脚选择
当红外线接收头感应到有红外光就输出低电平,没有感应到红外光就输出高电平。因此我们配置红外引脚为外部中断下降沿触发方式,当红外引脚有下降沿时,我们马上进入中断处理并接收红外信号。
模块接线图
3.3移植至工程
引脚配置如下:
//红外引脚初始化 void infrared_goio_config(void) { IR_RCC_GPIO_ENABLE(); // 使能GPIO时钟 GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化结构体 GPIO_InitStruct.Pins = IR_PIN; // GPIO引脚 GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP; // 上拉输入 GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 速度高 GPIO_InitStruct.IT = GPIO_IT_FALLING; // 下降沿触发中断 GPIO_Init(IR_PORT, &GPIO_InitStruct); // 初始化 // 清除PA0中断标志 GPIOA_INTFLAG_CLR(EXTI_BV); // 使能NVIC NVIC_EnableIRQ(EXTI_IRQ); }
红外信号的数据,全部是以时间长度来确定数据是0还是1,而最小的单位要求有560us,已经达到了us级的测量。
我们在 空白工程中已经为大家准备好了us延时,就在board 文件中。
获取高低电平时间
获取低电平时间的实现代码如下:
//获取红外低电平时间 //以微秒us作为时间参考 void get_infrared_low_time( uint32_t *low_time ) { uint32_t time_val = 0; while( GPIO_ReadPin(IR_PORT, IR_PIN) == 0 ) { if( time_val >= 500 ) { *low_time = time_val; return; } delay_us(20); time_val++; } *low_time = time_val; }
当引脚为低电平时,将进入 while 循环,直到不为低电平时就结束循环。在循环之中不断的让时间变量time_val累加, 每加一次需要经过20us。当time_val变量累加时间大于 500 * 20 = 10000us = 10ms时,判断为超时,强行结束该函数,防止阻碍系统运行。
获取高电平时间的代码同理:
//获取红外高电平时间 //以微秒us作为时间参考 void get_infrared_high_time(uint32_t *high_time) { uint32_t time_val = 0; while( GPIO_ReadPin(IR_PORT, IR_PIN) == 1 ) { if( time_val >= 250 ) { *high_time = time_val; return; } delay_us(20); time_val++; } *high_time = time_val; }
引导码与重复码判断
引导码是由一个 9ms 的低电平和一个 4.5ms 的高电平组成。每当接收到一个红外信号时,第一个数据就是引导码。我们通过判断红外信号的第一个数据是否是引导码,来决定是否要进行后面的数据接收处理。
重复码是由一个 9ms 的低电平和一个 2.5ms 的高电平组成。当我们的红外遥控一直按住按键时,就会发出重复码,我们可以检测重复码,来确定是否要连续触发重复动作,比如长按开机,长按加速等等。
/****************************************************************** * 函 数 名 称:guide_and_repeat_code_judgment * 函 数 说 明:引导 和 重复 码 判断 * 函 数 形 参:无 * 函 数 返 回:1:不是引导码 2:重复码 0:引导码 * 作 者:LC * 备 注:以20微秒us作为时间参考 引导码:由一个 9ms 的低电平和一个 4.5ms 的高电平组成 重复码:由一个 9ms 的低电平和一个 2.5ms 的高电平组成 ******************************************************************/ uint8_t guide_and_repeat_code_judgment(void) { uint32_t out_time=0; get_infrared_low_time(&out_time); //time >10ms time < 8ms if((out_time > 500) || (out_time < 400)) { return 1; } get_infrared_high_time(&out_time); // x >5ms 或者 x< 2ms if((out_time > 250) || (out_time < 100)) { return 1; } //如果是重复码 2ms < time < 3ms if((out_time > 100) && (out_time < 150)) { return 2; } return 0; }
完整红外数据接收
具体接收流程:【判断是否接收到引导码】->【接收数据】->【判断数据是否正确】。
//接收红外数据 void receiving_infrared_data(void) { uint16_t group_num = 0,data_num = 0; uint32_t time=0; uint8_t bit_data = 0; uint8_t ir_value[5] = {0}; uint8_t guide_and_repeat_code = 0; //等待引导码 guide_and_repeat_code = guide_and_repeat_code_judgment(); //如果不是引导码则结束解析 if( guide_and_repeat_code == 1 ) return; //共有4组数据 //地址码+地址反码+命令码+命令反码 for(group_num = 0; group_num < 4; group_num++ ) { //接收一组8位的数据 for( data_num = 0; data_num < 8; data_num++ ) { //接收低电平 get_infrared_low_time(&time); //如果不在0.56ms内的低电平,数据错误 if((time > 60) || (time < 20)) { return ; } time = 0; //接收高电平 get_infrared_high_time(&time); //如果是在1200us< t< 2000us范围内则判断为1 if((time >=60) && (time < 100)) { bit_data = 1; } //如果是在200us< t< 1000us范围内则判断为0 else if((time >=10) && (time < 50)) { bit_data = 0; } //groupNum表示第几组数据 ir_value[ group_num ] < <= 1; //接收的第1个数为高电平;在第二个for循环中,数据会向右移8次 ir_value[ group_num ] |= bit_data; //用完时间要重新赋值 time=0; } } //判断数据是否正确,正确则保存数据 infrared_data_true_judgment(ir_value); }
判断数据是否正确,可以通过将正常数据取反,与反码比较。如果不一致说明数据不对。
typedef struct INFRARED_DATA{ uint8_t AddressCode; //地址码 uint8_t AddressInverseCode; //地址反码 uint8_t CommandCode; //命令码 uint8_t CommandInverseCode; //命令反码 }_INFRARED_DATA_STRUCT_; _INFRARED_DATA_STRUCT_ InfraredData; //红外数据是否正确判断 uint8_t infrared_data_true_judgment(uint8_t *value) { //判断地址码是否正确 if( value[0] != (uint8_t)(~value[1]) ) return 0; //判断命令码是否正确 if( value[2] != (uint8_t)(~value[3]) ) return 1; //串口输出查看接收到的数据 printf("%x %x %x %xrn",value[0],value[1],value[2],value[3]); //保存正确数据 InfraredData.AddressCode = value[0]; InfraredData.AddressInverseCode = value[1]; InfraredData.CommandCode = value[2]; InfraredData.CommandInverseCode = value[3]; } //获取红外发送过来的命令 uint8_t get_infrared_command(void) { return InfraredData.CommandCode; } //清除红外发送过来的数据 void clear_infrared_command(void) { InfraredData.CommandCode = 0x00; }
最后,记得在外部中断服务函数中,调用红外接收函数。
void EXTI_HANDLER(void) { if(IR_PORT- >ISR_f.EXTI_PIN) // 中断标志位 { if(GPIO_ReadPin(IR_PORT, IR_PIN) == GPIO_Pin_RESET) // 如果是低电平 { //接收一次红外数据 receiving_infrared_data(); } GPIOA_INTFLAG_CLR(EXTI_BV); // 清除标志位 } }
移植步骤中的导入.c和.h文件与【CW32模块使用】DHT11温湿度传感器相同,只是将.c和.h文件更改为bsp_ir_receiver.c与bsp_ir_receiver.h。这里不再过多讲述,移植完成后面修改相关代码。
以下为完成红外接收代码:
bsp_ir_receiver.c
/* * Change Logs: * Date Author Notes * 2024-06-24 LCKFB-LP first version */ #include "bsp_ir_receiver.h" #include "stdio.h" #include "board.h" typedef struct INFRARED_DATA{ uint8_t AddressCode; //地址码 uint8_t AddressInverseCode; //地址反码 uint8_t CommandCode; //命令码 uint8_t CommandInverseCode; //命令反码 }_INFRARED_DATA_STRUCT_; _INFRARED_DATA_STRUCT_ InfraredData; //红外引脚初始化 void infrared_goio_config(void) { IR_RCC_GPIO_ENABLE(); // 使能GPIO时钟 GPIO_InitTypeDef GPIO_InitStruct; // GPIO初始化结构体 GPIO_InitStruct.Pins = IR_PIN; // GPIO引脚 GPIO_InitStruct.Mode = GPIO_MODE_INPUT_PULLUP; // 上拉输入 GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; // 速度高 GPIO_InitStruct.IT = GPIO_IT_FALLING; // 下降沿触发中断 GPIO_Init(IR_PORT, &GPIO_InitStruct); // 初始化 // 清除PA0中断标志 GPIOA_INTFLAG_CLR(EXTI_BV); // 使能NVIC NVIC_EnableIRQ(EXTI_IRQ); } //获取红外低电平时间 //以微秒us作为时间参考 void get_infrared_low_time( uint32_t *low_time ) { uint32_t time_val = 0; while( GPIO_ReadPin(IR_PORT, IR_PIN) == 0 ) { if( time_val >= 500 ) { *low_time = time_val; return; } delay_us(20); time_val++; } *low_time = time_val; } //获取红外高电平时间 //以微秒us作为时间参考 void get_infrared_high_time(uint32_t *high_time) { uint32_t time_val = 0; while( GPIO_ReadPin(IR_PORT, IR_PIN) == 1 ) { if( time_val >= 250 ) { *high_time = time_val; return; } delay_us(20); time_val++; } *high_time = time_val; } /****************************************************************** * 函 数 名 称:guide_and_repeat_code_judgment * 函 数 说 明:引导 和 重复 码 判断 * 函 数 形 参:无 * 函 数 返 回:1:不是引导码 2:重复码 0:引导码 * 作 者:LC * 备 注:以20微秒us作为时间参考 引导码:由一个 9ms 的低电平和一个 4.5ms 的高电平组成 重复码:由一个 9ms 的低电平和一个 2.5ms 的高电平组成 ******************************************************************/ uint8_t guide_and_repeat_code_judgment(void) { uint32_t out_time=0; get_infrared_low_time(&out_time); //time >10ms time < 8ms if((out_time > 500) || (out_time < 400)) { return 1; } get_infrared_high_time(&out_time); // x >5ms 或者 x< 2ms if((out_time > 250) || (out_time < 100)) { return 1; } //如果是重复码 2ms < time < 3ms if((out_time > 100) && (out_time < 150)) { return 2; } return 0; } //红外数据是否正确判断 uint8_t infrared_data_true_judgment(uint8_t *value) { //判断地址码是否正确 if( value[0] != (uint8_t)(~value[1]) ) return 0; //判断命令码是否正确 if( value[2] != (uint8_t)(~value[3]) ) return 1; printf("%x %x %x %xrn",value[0],value[1],value[2],value[3]); //保存正确数据 InfraredData.AddressCode = value[0]; InfraredData.AddressInverseCode = value[1]; InfraredData.CommandCode = value[2]; InfraredData.CommandInverseCode = value[3]; } //接收红外数据 void receiving_infrared_data(void) { uint16_t group_num = 0,data_num = 0; uint32_t time=0; uint8_t bit_data = 0; uint8_t ir_value[5] = {0}; uint8_t guide_and_repeat_code = 0; //等待引导码 guide_and_repeat_code = guide_and_repeat_code_judgment(); //如果不是引导码则结束解析 if( guide_and_repeat_code == 1 ) { printf("errrn"); return; } //共有4组数据 //地址码+地址反码+命令码+命令反码 for(group_num = 0; group_num < 4; group_num++ ) { //接收一组8位的数据 for( data_num = 0; data_num < 8; data_num++ ) { //接收低电平 get_infrared_low_time(&time); //如果不在0.56ms内的低电平,数据错误 if((time > 60) || (time < 20)) { return ; } time = 0; //接收高电平 get_infrared_high_time(&time); //如果是在1200us=60) && (time < 100)) { bit_data = 1; } //如果是在200us=10) && (time < 50)) { bit_data = 0; } //groupNum表示第几组数据 ir_value[ group_num ] <<= 1; //接收的第1个数为高电平;在第二个for循环中,数据会向右移8次 ir_value[ group_num ] |= bit_data; //用完时间要重新赋值 time=0; } } //判断数据是否正确,正确则保存数据 infrared_data_true_judgment(ir_value); } //获取红外发送过来的命令 uint8_t get_infrared_command(void) { return InfraredData.CommandCode; } //清除红外发送过来的数据 void clear_infrared_command(void) { InfraredData.CommandCode = 0x00; } void EXTI_HANDLER(void) { if(IR_PORT- >ISR_f.EXTI_PIN) // 中断标志位 { if(GPIO_ReadPin(IR_PORT, IR_PIN) == GPIO_Pin_RESET) // 如果是低电平 { //接收一次红外数据 receiving_infrared_data(); } GPIOA_INTFLAG_CLR(EXTI_BV); // 清除标志位 } } bsp_ir_receiver.h /* * Change Logs: * Date Author Notes * 2024-06-24 LCKFB-LP first version */ #ifndef _BSP_IR_RECEIVER_H__ #define _BSP_IR_RECEIVER_H__ #include "board.h" #define IR_RCC_GPIO_ENABLE() __RCC_GPIOA_CLK_ENABLE() #define IR_PORT CW_GPIOA #define IR_PIN GPIO_PIN_2 #define EXTI_PIN PIN2 #define EXTI_BV bv2 #define EXTI_IRQ GPIOA_IRQn #define EXTI_HANDLER GPIOA_IRQHandler void infrared_goio_config(void); uint8_t get_infrared_command(void); void clear_infrared_command(void); #endif
四、移植验证
在自己工程中的main主函数中,编写如下。
/* * Change Logs: * Date Author Notes * 2024-06-24 LCKFB-LP first version */ #include "board.h" #include "stdio.h" #include "bsp_uart.h" #include "bsp_ir_receiver.h" int32_t main(void) { board_init(); // 开发板初始化 uart1_init(115200); // 串口1波特率115200 //红外接收初始化 infrared_goio_config(); printf("Start!!!rn"); while(1) { //如果按下遥控的【1】键 if( get_infrared_command() == 0xA2 ) { clear_infrared_command(); printf("按下【1】按键! rn"); } } }
移植现象:
模块移植成功案例代码:
链接:https://pan.baidu.com/s/1Yln6MD82bPkgS2x-YMnfCQ?pwd=LCKF
提取码:LCKF
审核编辑 黄宇
全部0条评论
快来发表一下你的评论吧 !