CW32L012/F030灵眸X1智能小车——板载红外接收开发
红外通信介绍光谱中波长为760nm至400um的电磁波称为红外线,是一种不可见光。红外通信的例子大家应该都很熟悉。几乎所有常用的家用电器都可以通过红外遥控器进行控制,比如电视、空调、投影仪等,凡是都能看到红外遥控器的地方。该技术应用广泛,相应的应用设备也非常便宜。因此,红外遥控器是控制我们日常设备的理想方式。
红外通讯原理红外光以特定频率脉冲的形式发射。接收端收到信号后,按照约定的协议进行解码,完成数据传输。在消费类电子产品中,脉冲频率一般采用30KHz~60KHz频段,NEC协议的频率为38KHZ。这种特定频率的发射实际上可以理解为点亮一盏灯。不要被复杂的词汇所困惑。它是控制灯的闪烁频率(开和关)。和你刚学会用单片机闪光的意思是一样的。只是光线的类型不同而已。它们都是灯。
接收端的原理是:接收端的芯片对这种红外光比较敏感,可以根据有没有光来输出高低电平。如果发送端的闪烁频率有规律,则接收端收到后输出的高电平和低电平也有规律对应。这样,只要发送端和接收端同意,就可以进行数据传输。
红外传输协议可以说是所有无线传输协议中成本最低、最方便的传输协议。但它也有缺点,比如距离不够长、速度不够快等。当然,每种传输协议的应用环境不同,定位也不同。质量无法比较。根据您的实际场景选择合适的通信方式。
NEC协议介绍NEC协议是众多红外协议之一(这里所说的协议是指它们的数据帧格式定义不同,但数据传输原理是相同的)。几乎我们买的外电遥控器、淘宝上买的迷你遥控器、电视、投影仪都采用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的高电平组成。当连续发送红外信号时,可以通过发送重复码来快速发送。
电路设计红外接收头感应到红外光时输出低电平,未感应到红外光时输出高电平。因此,我们将红外引脚配置为外部中断下降沿触发方式。当红外引脚有下降沿时,我们立即进入中断处理并接收红外信号。
红外接收电路如图所示,连接到CW32L012/F030的PB10引脚。
工程代码:引脚配置如下:
//红外引脚初始化voidfrared_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); //初始化//清除PB0中断标志GPIOA_INTFLAG_CLR(EXTI_BV); //启用NVIC NVIC_EnableIRQ(EXTI_IRQ);}
红外信号的数据都是根据时间的长短来判断数据是0还是1,最小单位要求是560us,已经达到us级别的测量。
获取低电平时间的实现代码如下:
//获取红外低电平时间//Us微秒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;返回;延迟_我们(20);时间值++; } *low_time=time_val;}
当该引脚为低电平时,会进入while循环,直到不再为低电平时结束循环。循环中,时间变量time_val不断累加,每次加法耗时20us。当time_val变量累计时间大于500 * 20=10000us=10ms时,判断为超时,强制终止函数,防止妨碍系统运行。
获取高层时间的代码相同:
//获取红外高电平时间//Us微秒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;返回;延迟_我们(20);时间值++; } *high_time=time_val;}
引导码与重复码判断引导代码由9ms低电平和4.5ms高电平组成。每当接收到红外信号时,第一个数据就是导频码。我们通过判断红外信号的第一个数据是否为导频码来决定是否进行后续的数据接收处理。
重复码由9ms低电平和2.5ms高电平组成。当我们的红外遥控器持续按下按钮时,它会发出重复代码。我们可以检测重复码来判断是否连续触发重复动作,比如长按开机、长按加速等。
/******************************************************************** * 函数名称:guide_and_repeat_code_judgment * 函数描述:引导和重复码判断* 函数形参:无* 函数返回: 1:不是引导码2:重复码0:引导码* 作者:LC * 注:使用20 微秒us 作为时间基准引导码:由一个9ms 低电平和一个4.5ms 高电平Repeat 组成code:由9ms低电平和2.5ms高电平组成*************************************************************************/uint8_tguide_and_repeat_code_judgment(void){ uint32_t out_time=0; get_infrared_low_time(out_time); //time10ms 时间8ms if((out_time 500) || (out_time 400)) { return 1; } get_infrared_high_time(out_time); //x5ms 或x2ms if((out_time 250) || (out_time 100)) { return 1; } //如果是重复代码2ms 时间3ms if((out_time 100) (out_time 150)) { return 2; }返回0;}
完整红外数据接收具体接收流程:【判断是否接收到启动码】-【接收数据】-【判断数据是否正确】。
//接收红外数据void receive_infrared_data(void){ uint16_t group_num=0,data_num=0; uint32_t时间=0; uint8_t 位数据=0; uint8_t ir_value[5]={0}; uint8_tguide_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 ;时间=0; //接收高电平get_infrared_high_time(time); //如果在1200ust2000us范围内,则判断为1 if((time=60) (time 100)) { bit_data=1; } //如果在200ust1000us范围内则判断为0 else if((time=10) (time 50)) { bit_data=0; } //groupNum表示是哪一组数据ir_value[ group_num ]=1; //第一个收到的数字为高电平;在第二个for循环中,数据将右移8次ir_value[ group_num ] |=bit_data; //用完时间后重新赋值=0; } } //判断数据是否正确,如果正确则保存数据frared_data_true_judgment(ir_value);}
为了判断数据是否正确,可以将正常数据反转,并与反码进行比较。如果不一致,则数据不正确。
typedef struct INFRARED_DATA{ uint8_t 地址代码; //地址码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=值[1]; InfraredData.CommandCode=值[2]; InfraredData.CommandInverseCode=value[3];}//获取红外发送的命令uint8_t get_infrared_command(void){ return InfraredData.CommandCode;}//清除红外发送的数据voidclear_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) //如果为低电平{ //接收一次红外数据receive_infrared_data(); } GPIOA_INTFLAG_CLR(EXTI_BV); //清除标志}}
最后在main函数中写入如下代码(delay_us函数这里不再解释)
uint8_t val;int main(void){ OLED_Init();//初始化frared_goio_config();//红外初始化OLED_ShowString(1,1,'hello');//OLED显示字符串while(1) { val=get_infrared_command();//接收红外发送的数据OLED_ShowHexNum(2,1,val,2);//显示数据}}
工作现象将代码烧录到开发板后,可以观察到以下现象
OLED显示屏第一行第一列显示字符串hello
未按下遥控器时,第二行显示00
按下遥控器后,第二行显示接收到的数据