红外遥控模块
1.1红外通信介绍
电视,空调等
成本低,控制简单
遥控器:红外发射关发射红外光,3mm和5mm功率不同,通信距离不同。
接收端:红外接收管,把红外光信号转换为电信号。
原理图
当接收端接收到信号时,灯管导通,上拉电阻就不起作用,输出为0
当接收端没有接收到信号时,灯管不导通,由于上拉电阻的作用,输出为1
电源(VCC)通过上拉电阻(图中 “>20K” 的电阻)向输出端供电,使输出端被拉到高电平(1),上拉电阻的作用就是在 “灯管” 不导通时,为输出端提供高电平的逻辑信号。
1.2红外编码
红外通信时的一个协议。对0和1进行编码。编码格式NEC RC5
NEC:
1)38K的载波频率。光很容易受到干扰。消除其他光源的干扰
2)有引导码,通信之前先发送高9ms,低4.5ms
3)使用16位的客户代码。开,关。。。每个功能都是一个16位的数据
4)使用8位的数据代码和8位取反的数据代码
0x01 0x08
0000 0001 1111 1110 0000 1000 1111 0111
先数据码,再取反码
接收端波形图分析:
引导码:
发射端先发送9ms的高,接收端接收9ms的低,发射端再发送4.5ms的低,接收端接收4.5ms的高
逻辑0和1的编码格式:
逻辑0:是0.56ms高(载波)+0.56ms低 ----------接收端:0.56ms高+0.56ms低
逻辑1:是056ms高(载波)+1.68ms低 -----------接收端:1.68ms高+0.56ms低
数据的接收,由于低电平都一样,只看高电平就可以
0000 0000 1111 1111 1010 0010 0101 1101
0x00 0xFF 0XA2 0x5D
8位的数据代码和8位取反的数据代码
问题:为什么发送的38k的载波,收到的不是呢?
发射端:发1,并不是真正的1,而是红外LED 以 38kHz 的频率快速闪烁(人眼无法察觉),形成一个被 38kHz 载波调制的红外脉冲信号。
由于红外接收装置,采用VS1838B,能够实现38khz的过滤
检测逻辑:轮询和外部中断
采用外部中断进行触发检测
使用定时器进行计数,用于对逻辑0和1的检测
程序:
初始化红外接收器和定时器
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=0xFFFF;
TIM_TimeBaseInitStruct.TIM_Prescaler=71; //72/(71+1)=1MhzTIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);NVIC_EnableIRQ(TIM2_IRQn);
}
初始化定时器 TIM2。首先使能 TIM2 的时钟,然后配置定时器的基本参数,包括时钟分频、计数模式、周期和预分频器。这里设置预分频器为 71,使得定时器的计数频率为 1MHz,周期为 0xFFFF。最后使能定时器的更新中断,并配置 NVIC使能 TIM2 的中断。
void NEC_Init(void)
{RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);GPIO_InitTypeDef GPIO_InitStruct;GPIO_InitStruct.GPIO_Mode=GPIO_Mode_IPU;//上拉输入GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz;GPIO_Init(GPIOA,&GPIO_InitStruct);GPIO_EXTILineConfig(GPIO_PortSourceGPIOA,GPIO_PinSource1);EXTI_InitTypeDef EXTI_InitStruct;EXTI_InitStruct.EXTI_Line=EXTI_Line1;EXTI_InitStruct.EXTI_LineCmd=ENABLE;EXTI_InitStruct.EXTI_Mode=EXTI_Mode_Interrupt;//中断模式EXTI_InitStruct.EXTI_Trigger=EXTI_Trigger_Rising_Falling;EXTI_Init(&EXTI_InitStruct);NVIC_InitTypeDef NVIC_InitStruct;NVIC_InitStruct.NVIC_IRQChannel=EXTI1_IRQn;NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;NVIC_Init(&NVIC_InitStruct);NVIC_EnableIRQ(EXTI1_IRQn);}
首先使能 GPIOA 和 AFIO 的时钟,然后配置 GPIOA 的 Pin1 为上拉输入模式,用于接收红外信号。接着配置 GPIO 的外部中断线,将 PA1 与外部中断线 EXTI_Line1 关联。再配置 EXTI 的相关参数,包括中断线、使能、中断模式和触发方式(上升沿和下降沿触发)。最后配置 NVIC,设置 EXTI1 的中断优先级,并使能该中断。
外部中断处理
void EXTI1_IRQHandler(void)
{if (EXTI_GetITStatus(EXTI_Line1) != RESET){//引导switch (nec.trigger){case 0:Start_Count();nec.trigger++;break;case 1:Stop_Count();if (Get_count() > 8000 && Get_count() < 10000){nec.trigger++;Start_Count();}elsenec.trigger = 0;break;case 2:Stop_Count();if (Get_count() > 3800 && Get_count() < 5500){nec.trigger++;nec.cnt = 0;memset(nec.data, 0, sizeof(nec.data));}elsenec.trigger = 0;break;default://接收数据if (Nec_Read_Pin())Start_Count();else{Stop_Count();if (Get_count() > 380 && Get_count() < 750){}else if (Get_count() > 1350 && Get_count() < 2000){nec.data[nec.cnt / 8] |= 1 << (nec.cnt % 8);}elsenec.trigger = 0;nec.cnt++;if (nec.cnt >= 32){nec.receive_complete = 1;nec.trigger = 0;nec.cnt = 0;}}}EXTI_ClearITPendingBit(EXTI_Line1);}
}
触发外部中断时,打开定时器,trigger++,准备进入状态1;
进入状态1,停止定时器,并且进行判断,是否是低电平的9ms,由于定时器计数时存在误差,就把范围设置为8000-10000us。
如果是低电平的9ms,那么trigger++,并且开始定时器,准备进入状态2。否则trigger=0;重新开始。
进入状态2,停止定时器,并且进行判断,是否是高电平的4.5ms,由于定时器计数时存在误差,就把范围设置为3800-5500us。
如果是高电平的4.5ms,那么trigger++,cnt=0,并且data数组清零,准备进入状态3。否则trigger=0;重新开始。
状态3就是接收数据,当引导码(9ms 低 + 4.5ms 高)验证通过后,就进入32 位数据接收阶段(NEC 协议规定数据是 “8 位地址码 + 8 位地址反码 + 8 位数据码 + 8 位数据反码”),核心是通过 “电平跳变 + 定时器计时” 区分二进制的 0 和 1。
跳变到高电平→开定时器计时→跳变到低电平→关定时器→看时长判 0/1→存数据 / 只计数→直到收满 32 位,标记完成。
数据获取:
uint8_t NEC_GetData(NEC *nec, u8 *data)
{if (nec->receive_complete){if ((nec->data[0] == (uint8_t)~nec->data[1]) &&(nec->data[2] == (uint8_t)~nec->data[3])){memcpy(data, nec->data, sizeof(nec->data));nec->receive_complete = 0;return 1;}else{nec->receive_complete = 0;}}return 0;
}
就是确认接收到数据后,在进行对数据校验,验证数据的地址码与反码、数据码与反码是否符合 NEC 协议标准。如果验证通过,则将数据复制到输出缓冲区data
,重置接收完成标志,并返回 1 表示数据有效;否则,也重置接收完成标志并返回 0 表示数据无效。
在红外解码代码中,(uint8_t)
是 强制类型转换,用于将取反运算的结果转换为 8 位无符号整数,解决 C 语言中 “整数提升” 导致的校验错误问题
- 例如:
nec->data[1]
是u8
类型,值为0x00
执行~nec->data[1]
时,实际过程是:0x00
先被提升为int
类型的0x00000000
- 按位取反后得到
0xFFFFFFFF
(32 位,对应十进制-1
)