关于STM32F051C8T6某项目总述
3. 模块总述
3. VS1838B
1. 功能说明
首先VS1838B接收头接收的是载波频率为38K的红外方波信号,遵守的是NEC协议。那么VS1838B可以用来做什么呢?市面上的大部分红外遥控器,都使用的是红外38K的NEC协议,也就是说VS1838B可以接收来自大部分遥控器的信号。所以要想给项目添加遥控功能,红外发射器和红外接收头便是最简便的配置。那么红外发射器指什么?当然指遥控器。遥控器可以定制,暂不赘述,所以先介绍VS1838B接收头。
2. 思路解析
首先红外发射器发射了一个38K红外信号,红外接收头在硬件层面接收到该38K红外信号,并产生相应的电信号。电信号即数字方波信号。那么该数字方波信号是一个特定的信号,试想一下为什么同样是38K红外载波遥控器为什么电视遥控器不能遥控空调呢?之所以叫特定,是因为遥控器不同按键产生的每个方波信号都是固定且不同的(同一遥控器),因此红外接收头接收到该方波信号传输给CPU就知道当前收到的信号是由哪个按键产生的。那么特定的方波信号到底有什么不同呢?可以这么理解,每个信号之所以不同是因为它们携带的“信息”不同。那么到底是什么信息呢?
这就要看NEC协议了。
图像来自此博客:(https://blog.csdn.net/STATEABC/article/details/131857097)
从上图可看出红外发射头发出的红外信号翻转后就是红外接收头接收的信号。浅浅分析一下此协议。NEC的协议格式是:
起始位(同步码(9ms低电平)+ 4.5ms高电平)+地址码+地址码反码+控制码+控制码反码
起始拉低9ms的电平之后,拉高4.5ms的电平,之后就是数据位。也就是说,在接收头那个引脚,CPU只要感受到电平被拉低9ms之后,然后有一段时间的高电平,就代表着我收到了一个红外信号。此后对该信号的数据解析,NEC的数据中地址码和控制码就是红外信号携带的信息,通过这个8字节(地址码)+8字节(控制码,一般以控制码作为唯一标识)数据对该红外信号唯一标识。
理一下思路:首先。我们在红外接收头out对应引脚接收到一个9ms的低电平和一个至少4.5ms的高电平那么就代表应该继续处理数据否则就丢弃该信号,然后在4.5ms的高电平之后,开始解析数据位(解析数据位的步骤是重点和难点)。解析完数据位,得到一个32字节的数据,该32字节数据构成如下:
地址码(4字节)+地址码反码(4字节)+控制码(4字节)+控制反码(4字节)
低字节先发送,意味着,我们先收到的是低字节,后收到的是高字节。
接下来分析:NEC协议的数据位结构,当我们接受完9ms的低电平和4.5ms的高电平时,继续读取电平信号会先读取到0.56ms的高电平,在0.56ms高电平之后会电平跳变此时是重点,我们需要记录低电平的持续时间如果低电平持续时间低于1125us-560us那么该数据位是0,如果低电平信号持续时间高于1125us-560us低于2250us-560us那么该数据位是1,然后又会继续读取到0.56ms的高电平,然后再记录低电平信号,如此循环往复。我们一共需要接收32位,接收后一般取控制码即可。然后用一个swich做分支处理。
3. 代码详解
void Vs1838bInsInit(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
EXTI_InitTypeDef EXTI_InitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
// 使能外部中断时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_SYSCFG, ENABLE);
RCC_AHBPeriphClockCmd(VS1838B_INPUT_CLK, ENABLE);
// 配置 PD0 引脚为输入模式
GPIO_InitStructure.GPIO_Pin = VS1838B_INPUT_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(VS1838B_INPUT_PORT, &GPIO_InitStructure);
// 配置外部中断 EXTI8
SYSCFG_EXTILineConfig(EXTI_PortSourceGPIOA, EXTI_PinSource8);
EXTI_InitStructure.EXTI_Line = EXTI_Line8;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_Init(&EXTI_InitStructure);
NVIC_EnableIRQ(EXTI4_15_IRQn);
// 配置外部中断 EXTI8 的中断优先级
NVIC_InitStructure.NVIC_IRQChannel = EXTI4_15_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPriority =3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
GPIO引脚(OUT连接引脚)可采用浮空不上拉或浮空上拉模式,项目采用的是浮空上拉模式。外部中断触发是上升沿和下降沿都触发
// 配置 TIM2 为计时器
void initTimer(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
NVIC_InitTypeDef NVIC_CFG;
// 使能 TIM2 时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 配置 TIM2 为计时器模式
TIM_DeInit(TIM2);
TIM_TimeBaseStructure.TIM_Period = 0xffff;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_Prescaler = 47; // 分频系数,使 TIM2 计数器时钟为 1MHz
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
// 清除计数器
TIM_SetCounter(TIM2, 0);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// // 配置中断优先级
// NVIC_CFG.NVIC_IRQChannel = TIM2_IRQn;
// NVIC_CFG.NVIC_IRQChannelPriority = 2;
// NVIC_CFG.NVIC_IRQChannelCmd = ENABLE;
// NVIC_Init(&NVIC_CFG);
NVIC_EnableIRQ(TIM2_IRQn);
TIM_Cmd(TIM2,ENABLE);
}
该定时器1TICK位1us。不使用其中断功能定时器只用来计时。
// 外部中断 EXTI0 的中断处理程序
void EXTI4_15_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line8) != RESET) {
switch (NEC_Trigger_cnt) {
case 0:
START_COUNT;
NEC_Trigger_cnt++;
break;
case 1:
STOP_COUNT;
if (GET_COUNT > 8000 && GET_COUNT < 10000) {
NEC_Trigger_cnt++;
START_COUNT;
} else {
bit_count = 0;
TIM2->CNT = 0;
memset((void *)receive_code, 0, sizeof(receive_code));
NEC_Trigger_cnt = 0;
}
break;
case 2:
STOP_COUNT;
if (GET_COUNT > 3800 && GET_COUNT < 5500) {
NEC_Trigger_cnt++;
bit_count = 0;
TIM2->CNT = 0;
memset((void *)receive_code, 0, sizeof(receive_code));
}
else {
bit_count = 0;
TIM2->CNT = 0;
memset((void *)receive_code, 0, sizeof(receive_code));
NEC_Trigger_cnt = 0;
}
break;
default: {
if (NEC_TRIGGER)
START_COUNT;
else {
STOP_COUNT;
if (GET_COUNT > 420 && GET_COUNT < 680) {
}
else if (GET_COUNT > 1450 && GET_COUNT < 1880)
{
receive_code[bit_count / 8] |= 1 << (bit_count % 8);
} else
{
bit_count = 0;
TIM2->CNT = 0;
memset((void *)receive_code, 0, sizeof(receive_code));
NEC_Trigger_cnt = 0;
}
bit_count++;
if (bit_count >= 31)
{
long_count++;
StateValue.NEC_KEY_STATE.value = receive_code[2];
if(receive_code[2] == START_NEC||receive_code[2] == STOP_NEC)
{
long_count = 0;
StateValue.NEC_KEY_STATE.isNeedUpdate = 1;
StateValue.NEC_KEY_STATE.isExcute = 0;
}
vs1838b_time_cnt = 0;
bit_count = 0;
NEC_Trigger_cnt = 0;
nec_time_cnt = 0;
vs1838b_time_cnt = 0;
memset((void *)receive_code, 0,sizeof(receive_code));
}
}
}
}
EXTI_ClearITPendingBit(EXTI_Line8); // clear interrupt flag
}
if (EXTI_GetITStatus(EXTI_Line6) != RESET){
Reset_Error9 = true;
EXTI_ClearITPendingBit(EXTI_Line6);
}
}
上面代码的大概思路是每次信号跳变会触发中断,每次中断会记录上次到本次信号跳变的时间,如果是9ms的低电平,NEC_Trigger_cnt++,代表下一次电平信号保持时长应该为4.5ms,依次类推,每次NEC_Trigger_cnt++都代表NEC信号解析到下一个电平信号跳变,如果不符合NEC协议信号的时长要求NEC_Trigger_cnt立即清0。
至此VS1838B总结完毕。