目录
前言
一、前期的准备
1. 结构体
2. 中断
1.定时中断
2.外部中断(下降沿中断)
二、功能实现
1.时序说明
2.逻辑实现
3.代码实现
总结
前言
简单介绍红外遥控器的使用,可以正常使用,但是部分功能未启用,给大家抛砖引玉介绍下思路。
环境:正常室内
芯片:STM32F103C8T6
Keil:V5.24.2.0
接收头:VS1838
信号源:配套的遥控器
一、前期的准备
1. 结构体
使用结构体可以方便我们使用全局变量,并且可以增加代码的可读性。
如果没了解的可以看下我之前对结构体的简单介绍:
学习记录6-结构体的应用https://mp.csdn.net/mp_blog/creation/editor/135315602
上代码:
typedef struct {
// uint32_t OLED_Count;
uint8_t ifr_state;//信号的状态
uint32_t time_ms;
uint32_t time_10us;//10us时间计算
uint8_t Trigger_cnt;//case的值计算
uint8_t jump_cnt;//在case里进行跳跃计数,跳过1,2,4阶段
uint8_t ifr_val;//信号值的计算
}USER;
extern USER user;
2. 中断
1.定时中断
为方便计算时间使用,我设计为10us进入一次中断。
频繁进入中断,不知道实际精度如何。有大佬能解释下就更好了,不过不影响我们这个实验
上代码:
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 72 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 10-1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
user.time_10us++;
if(user.time_10us>=100000)
{
// user.time_ms ++;
// user.time_10us = 0;
user.Trigger_cnt = 0;
}
}
}
注意点:1.待会还有外部中断,这个抢占优先级一定要高于外部的优先级。
2.user.Trigger_cnt 变量在下个中断会说明。
常规计数中断函数,没什么说的了
2.外部中断(下降沿中断)
不啰嗦,上代码:
void IFR_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_InitStructure;
EXTI_InitStructure.EXTI_Line = EXTI_Line1;
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
EXTI_Init(&EXTI_InitStructure);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2;
NVIC_Init(&NVIC_InitStructure);
}
注意点:1.我配置的引脚为A1脚,自己使用时记得和自己的匹配。
2. 中断触发设置为下降沿触发。
3.抢占优先级低于计时中断。
二、功能实现
1.时序说明
上时序图:
时序说明:1:第一次进入中断,进入起始码的开始
2:第二次进入中断,正式进入数据阶段
3:和2是一处,这边只是方便理解。
4:和5是一样的,一位数据只会有一次。就是说要么是4要么是5
2.逻辑实现
信号正确的逻辑:
引导码:第一次进入中断到第二次进入中断,时间应该是13.5ms(9+4.5)
数据码:第二次后的每次中断都是一位数据位,就是说第三次和第二次时间间隔,如果为1120us(560+560)就是数据0;如果时间是2520us(560+1690)就是数据1;
数据位数:地址码+地址反码+数据码+命令反码
每个是8位数据,总共是32位。
3.代码实现
上代码:
void EXTI1_IRQHandler(void)
{
if (EXTI_GetITStatus(EXTI_Line1) == SET)
{
EXTI_ClearITPendingBit(EXTI_Line1);
if (GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_1) == 0)
{
user.OLED_Count++;
switch(user.Trigger_cnt)
{
case 0:user.Trigger_cnt++;user.time_10us = 0;break;
case 1:
if(user.time_10us>1400)//9ms+4.5ms,时间是1350,这里给了余量
{
user.ifr_state = 3;//超时
}
else if(user.time_10us>1000)//800同上
{
user.ifr_state = 2; //正常
user.Trigger_cnt++;
user.time_10us = 0;
}
else
{
user.ifr_state = 1;//异常
user.Trigger_cnt = 0;
}
break;
case 2:
user.jump_cnt++;
if(user.jump_cnt >= 16)
{
user.Trigger_cnt++;
user.time_10us = 0;
user.jump_cnt = 0;
}
break;
case 3:
// OLED_ShowNum(2, 1, user.jump_cnt, 8);
user.jump_cnt++;
user.ifr_val <<= 1;
if(user.time_10us >= 200)//2000us
{
user.ifr_val |= 0x01;
}
user.time_10us = 0;
if(user.jump_cnt >= 8)
{
user.Trigger_cnt++;
user.jump_cnt = 0;
}
break;
case 4:
user.jump_cnt++;
if(user.jump_cnt >= 8)
{
user.Trigger_cnt = 0;
user.jump_cnt = 0;
}
break;
}
}
}
}
在运行前,验证下是否可以正常进入中断:
user.OLED_Count++;
在显示器上显示这个变量,看看是否能进入,如果值有变化,说明进入中断没有问题。
user.Trigger_cnt:为现在执行的阶段判断,方便进行操作
case 0:第一次进入中断,并对状态进行+1,下次进入第二阶段,对计时数据进行清零。
case 1:第二次进入中断,需要对时间进行计算,计算第一次到现在的时间,按正常时间计算,应该是13500us。如果不对并进行判断,这些代码有些啰嗦了,熟悉后可以精简。
如果正常,状态再次进行+1,进入地址码阶段。
case 2:
user.jump_cnt++:向大家问下,如何读取全部的32位数据?我是跳过前面的地址码和地址反码。所以用这个进行计数,如果大于16 则进入数据码的读取。
如果大于16,则对状态进行+1,并对一些数据进行清零。
case 3:
user.ifr_val <<= 1;
if(user.time_10us >= 200)//2000us
{
user.ifr_val |= 0x01;
}
本文的数据读取
计入8次后进行下一阶段,就是数据反码。我也是直接跳过了。
最后,说下定时中断中进行清零操作:
user.Trigger_cnt = 0;因为理想状态下,接收完一次完整信号后。下次信号就是引导码的开始。但是实际情况比较复杂,造成外部中断状态可能不是在引导码待命。所以我对它进行清零。0.1S没有操作,对状态进行清零。它就在引导码待命了。
总结
本文中可完善地方很多,让大家了解了原汁原味的思考方法。也给大家留了不少坑,后期我尽量把它补全。