STM32红外遥控器(NEC协议)

目录

前言

一、前期的准备

1. 结构体

2. 中断

1.定时中断

2.外部中断(下降沿中断)

二、功能实现

1.时序说明

2.逻辑实现

3.代码实现

总结


前言

简单介绍红外遥控器的使用,可以正常使用,但是部分功能未启用,给大家抛砖引玉介绍下思路。

环境:正常室内

芯片:STM32F103C8T6

Keil:V5.24.2.0

接收头:VS1838

信号源:配套的遥控器


一、前期的准备

1. 结构体

使用结构体可以方便我们使用全局变量,并且可以增加代码的可读性。

如果没了解的可以看下我之前对结构体的简单介绍:

学习记录6-结构体的应用icon-default.png?t=N7T8https://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没有操作,对状态进行清零。它就在引导码待命了。


总结

本文中可完善地方很多,让大家了解了原汁原味的思考方法。也给大家留了不少坑,后期我尽量把它补全。

  • 25
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值