中断内使用定时器进行延迟方法

最近在做毕设,使用的STM32f103C8T。有一个功能被我设计成使用按键中断实现,当按下按键时执行此功能,不影响主程序循环。

那么,在就需要使用EXTI中断来实现。可是问题来了,在EXTI中断内使用延迟函数时,发现主程序死机了;最明显的就是UNIX时间戳,本来应该每秒++的,直接彻底不动了,这是不可以的,是一个很严重的Bug,必须要解决。

经过我的搜索,终于找到了解决这个问题的核心思路:“在外部中断服务函数内,除了必要的判断和清除,仅开启定时器。使用定时器延时10-20ms,然后在定时器中断服务函数内读取gpio电平。”

这段话是论坛某位前辈所留的,那么核心解决解决思路,开始实践。

开始实践却并不像说说这么简单,一行短短的汉字背后要隐藏了多少硬件和软件上的风云变幻。

那么好,路在脚下,还是要迎难而上的。

我一开始的思路是想把这个定时函数封装在Timer.c模块中的

后来由于能力有限,各种不知名Bug满天飞,故而放弃。

后来又问了AI,AI给出的解决方法是定义一个

volatile uint32_t msTicks = 0;//该变量随时可能改变,每次都从地址读取

volatile这个修饰符我是第一次见,原谅我才疏学浅,不过以后就不是第一次见了。查询资料以后,发现这个定义的意思是,告诉编译器,被这个修饰的变量随时可能改变,要每次从地址重新读取。

也就是说,我们的msTicks会跟随定时器溢出而改变,我们设置好溢出频率,就可以设置msTicks的计时单位了(us/ms/s)。

虽然最终AI提供的代码我没有调试通,但是极大的为我们指引了方向。

这里我们设置溢出时间为1ms

此附:定时器溢出频率计算公式

\frac{1}{T}=f=72Mhz/ARR+1/PSC+1

ARR(16位0~65535):自动重装计数器        PSC(16位0~65535):预分频计数器

这里注意,配置好后是溢出的频率,就是溢出时间的倒数!

想要每1ms溢出一次,1ms=0.001s,那么:f=\frac{1}{1000}

代入公式,72*10^6 *\frac{1}{1000}=72000

也就是说ARR*PSC = 72000

以下是TIM3配置代码,配置好后每1ms溢出一次

void Timer3_Init(void)
{
		//开启APB1时钟
		RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
		
		//选择内部时钟输入
		TIM_InternalClockConfig(TIM3);
		
		//配置时基单元 定时1 ms
		//CK_PSC = 72MHz / ARR+1 / PSC+1 
		TIM_TimeBaseInitTypeDef TimBaseInitStructure;
		TimBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//滤波分频
		TimBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//向上计数
	
		TimBaseInitStructure.TIM_Period = 72 - 1;//ARR自动重装器值 0~65535 
		TimBaseInitStructure.TIM_Prescaler = 1000 - 1;//PSC预分频计时器 0~65535
		TimBaseInitStructure.TIM_RepetitionCounter = 0;
		
		TIM_TimeBaseInit(TIM3, &TimBaseInitStructure);
		
		TIM_ClearFlag(TIM3, TIM_IT_Update);
		//使能中断,开启中断到NVIC
		TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
		
		//配置NVIC
		NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
		
		NVIC_InitTypeDef NVIC_InitStructure;
		NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;//中断通道
		NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
		NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
		NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
		NVIC_Init(&NVIC_InitStructure);
		
		//启动定时器
		TIM_Cmd(TIM3,ENABLE); //启动定时器
}

设置好定时器后,我们就要编写main.c了。

逻辑是这样的,在系统初始化时不初始化TIM3;当按键按下时才初始化TIM3,TIM3设置好的溢出频率就是天然的延迟。这里由于设置的溢出频率比较小,所以我们由于得到了AI的启发,直接在main.c里定义一个TImer变量,手动创造一个虚拟计数器。

uint16_t Timer = 0; //计数器

这里的Timer要设置成uint16_t,定时器溢出时间为1ms一次,uint8_t范围是0~255,最多也就是计时0.255s(255ms),这明显是不够的。所以定义为uint16_t,范围0~65535,最多计时65.535s,这就富裕多了。

好,接下来编写中断内部的代码:

按下按键后进入EXTI中断,在中断内初始化定时器,定时器每1ms溢出一次,溢出一次Timer++一次,Timer=1就是经过了1ms,Timer=20就是经过了20ms。嗯,非常好!来吧!

先对按键消抖,按下后20ms内不允许执行任何代码,进入死循环,也就是

while(Timer <= 20);// 20ms延时消抖

Timer++到20以后才能执行其他代码。

以下是中断完整代码:

uint16_t Timer = 0;
void EXTI1_IRQHandler(void)
{
	if(EXTI_GetITStatus(EXTI_Line1) == SET)
	{
		Timer3_Init();//使能定时器TIM3,开始计时
		while(Timer <= 20);// 20ms延时消抖
		
        //在这里写你的代码吧

		Timer3_Disable();	//失能定时器
		EXTI_ClearITPendingBit(EXTI_Line1);//清除中断标志位
	}
}

//每0.001s Timer++
void TIM3_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM3, TIM_IT_Update) == SET)
	{
		Timer++;
		TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
	}
}

中断执行结束时要把定时器失能,进行下次定时。

不要忘了清楚中断标志位哦。

另附:EXTI中断配置代码:

void EXTI_Init(void)
{
	//配置AFIO
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);//打开AFIO
	GPIO_EXTILineConfig(GPIO_PortSourceGPIOB, GPIO_PinSource1);//选择引脚
	
	//配置EXTI
	EXTI_InitTypeDef EXTI_InitStructure;
	EXTI_InitStructure.EXTI_Line = EXTI_Line1;//1号线路
	EXTI_InitStructure.EXTI_LineCmd = ENABLE;//使能
	EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;//中断模式,0x04事件模式
	EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;//下降沿触发
	//EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;//上升沿触发
	EXTI_Init(&EXTI_InitStructure);
	
	//配置NVIC
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);//配置抢占和响应优先级 a,b a+b=4;4抢占0响应
	NVIC_InitTypeDef NVIC_InitStructure;
	NVIC_InitStructure.NVIC_IRQChannel = EXTI1_IRQn;
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 13;//抢占优先级
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;//响应优先级
	NVIC_Init(&NVIC_InitStructure);
}

  • 31
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
中断的概念和作用: 中断是一种机制,用于在微处理器执行程序的过程,暂停当前正在执行的任务,转而执行一个特定的代码块,然后再返回到原来的任务。中断可以用于响应外部事件(如按键、传感器信号等)或内部事件(如定时器溢出、串口接收等),实现实时响应和异步处理。 中断的作用: 1. 实时响应:中断可以立即响应外部或内部事件,使得系统能够及时处理紧急或重要的任务。 2. 异步处理:中断允许系统在执行主任务的同时,处理其他事件,提高系统的效率和灵活性。 3. 时间控制:通过定时器中断,可以实现精确的时间控制,例如定时触发任务、生成精确的时间延迟等。 4. 多任务管理:中断允许系统在多个任务之间进行切换,实现多任务管理和调度。 使用定时器进行延时操作: 定时器可以用来生成精确的时间延迟,以下是使用定时器进行延时操作的一般步骤: 1. 配置定时器使用STM32CubeMX或编写代码配置定时器的计数模式、预分频系数和重载值等参数。 2. 启动定时器使用HAL库提供的函数启动定时器,开始计数。 3. 等待定时器溢出:使用HAL库提供的函数或轮询定时器状态的方式,等待定时器溢出。 4. 定时器中断处理函数:在定时器溢出时,中断服务程序(ISR)会被执行,可以在ISR进行延时结束后的处理。 以下是一个基于定时器延时操作的示例代码: ```c #include "stm32f4xx_hal.h" TIM_HandleTypeDef htim2; void SysTick_Handler(void) { HAL_IncTick(); } void TIM2_IRQHandler(void) { HAL_TIM_IRQHandler(&htim2); } void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim) { if (htim->Instance == TIM2) { // 定时器溢出后的处理 } } void delay_ms(uint32_t ms) { HAL_Delay(ms); } int main(void) { HAL_Init(); SystemClock_Config(); // 初始化定时器 __HAL_RCC_TIM2_CLK_ENABLE(); htim2.Instance = TIM2; htim2.Init.Prescaler = 1000 - 1; // 设置预分频系数 htim2.Init.CounterMode = TIM_COUNTERMODE_UP; htim2.Init.Period = 10000 - 1; // 设置重载值 HAL_TIM_Base_Init(&htim2); // 启动定时器 HAL_TIM_Base_Start_IT(&htim2); while (1) { // 延时1秒 delay_ms(1000); // 延时结束后的处理 } } ``` 以上代码使用定时器TIM2生成1秒的延时定时器溢出后会调用`HAL_TIM_PeriodElapsedCallback()`函数进行延时结束后的处理。在主循环使用`delay_ms()`函数实现1秒的延时

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值