STM32使用通用定时器中断服务程序实现ms级延时

使用通用定时器的原因

定时器和中断的关系:定时器不受中断函数的影响,不会因为进入中断服务程序就停止计时。例如:定时器计时2s,中断函数执行需要0.1s,第二次继续计时就从上次的2s继续,而不是从2.1s再开始定时。它并不等待中断函数直接计时,相当于它计时它的,中断函数自己执行。

利用这个特点,我们可以通过定时器进入中断函数的次数,和定时器进入中断服务程序的时间来进行延时。延时时间=进入中断的次数*定时器进入中断时间。具体实现可看代码。

使用定时器延时的注意问题

通过查阅网络上的资料,有建议:
“中断服务子程序是不要调用延时子程序,这样会增加中断处理的时间,如果有其他低级中断,就会延误响应中断了。
中断子程序不要过长,处理多的任务,要尽快返回,如果中断一次执行很多任务,可以在中断中设置一个标志位,当标志位为1时,就在主程序中完成这些任务,这样就不会影响中断源中断,也不会使中断产生混乱。”

这样的说法,我们可以这样思考:

  1. 如果定时器进入中断服务子程序的时间是2s,而中断服务子程序的任务太多,执行时间超过两秒,中断的执行可能就会产生混乱。
  2. 我们使用的程序中,中断服务程序只是用来计次,记录进入中断子程序的次数,具体延时的实现借助延时函数,不会影响其他中断。

定时时长的确定

  1. 定时器的定时时长由预装载值预分频系数确定。
  2. 预分频系数:将频率分割,比如常用的STM32F103C8T6的时钟频率一般是72MHz,可以理解为单片机一秒钟数72M次。如果分频系数是72(设置的时候数值应该是72-1),则该时钟的频率会变成72MHz/72=1MHz,可以理解为单片机一秒钟数1M次,即1us数1次。
  3. 预装载值:单片机在预分频后需要计数的值。比如分频系数是上面的72,也就是单片机1us数1次。需要定时1ms,1ms=1us*1000,那么预装载值就是1000(设置的时候数值应该是1000-1)。所以在分频系数确定的情况下,定时的时长由预装载值确定。
  4. 定时器的周期计算公式:((arr+1)*(psc+1))/Tclk
    其中arr是预分频系数,psc是预装载值,Tclk是定时器的时钟频率。
    如下图所示STM32F10x系列的TIM2-TIM7时钟受APB1控制,TIM2-TIM7时钟是从 APB1 倍频的来的,TIMx 的时钟 是 APB1 时钟的 2 倍,也就是72MHz。Tclk一般为72MHz。
    在这里插入图片描述

目前测试实现的结果

  1. 1ms的精准延时和1s的精准延时,使用示波器测量显示。
    在这里插入图片描述
    在这里插入图片描述

使用程序需要注意

  1. 尝试继续实现1us的延时测试时有些问题暂未解决,后续实现会再做总结。
  2. 为实现主要逻辑使用的是库函数的方法,后续可以尝试使用C语言寄存器的方法和汇编程序实现,提升程序效率。

程序执行流程图

在这里插入图片描述

程序示例

#include "stm32f10x.h"

uint32_t TimingDelay;	//定义进入中断服务程序次数

/*初始化LED灯*/
void LED_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);	//开启外设时钟
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz ;
	GPIO_Init(GPIOB,&GPIO_InitStructure);
	
	GPIO_SetBits(GPIOB,GPIO_Pin_15);	//先熄灭LED
}

/*LED灯状态转换*/
void LED1_Turn(void)
{
	if (GPIO_ReadOutputDataBit(GPIOB, GPIO_Pin_15) == 0)
	{
		GPIO_SetBits(GPIOB, GPIO_Pin_15);
	}
	else
	{
		GPIO_ResetBits(GPIOB, GPIO_Pin_15);
	}
}

//延时函数
void Delay(uint32_t nTime)
{ 
	//也就是延时时间=进入定时器的次数*定时器每次进入中断的时间
	TimingDelay = nTime;
	while(TimingDelay != 0);
}

//通用定时器3配置
void TIM3_Init(u16 arr,u16 psc)
{
	//结构体初始化
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	NVIC_InitTypeDef NVIC_InitStructure; 
	
	//分配时钟
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
	//初始化定时器的相关配置
	TIM_TimeBaseStructure.TIM_Period = arr;		//重装载的值
	TIM_TimeBaseStructure.TIM_Prescaler = psc;	//分频系数
	
	//TIM_ClockDivision 是设置与进行输入捕获相关的分频
	//设置这个值不会影响定时器的时钟频率,我们一般设置为TIM_CKD_DIV1,也就是不分频
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //向上计数
	TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);	  													
	NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn;	  
	NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
	NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;	
	NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
	NVIC_Init(&NVIC_InitStructure);
	
	//允许更新中断
	TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);

	//打开定时器
	TIM_Cmd(TIM3,ENABLE);
}

//定时器时间到了之后自动跳转到中断函数当中
void TIM3_IRQHandler(void)
{
	if ( TIM_GetITStatus(TIM3 , TIM_IT_Update) != RESET ) 
	{
		TIM_ClearITPendingBit(TIM3 , TIM_FLAG_Update);    
		//这里的TimingDelay相当于计时次数,计算进入定时器的次数
		if(TimingDelay!=0x00){TimingDelay--;}
	}		 	
}

int main(void)
{
	/*这里定时器进入中断的时间是1s,如果TIM3_Init(7199,9)就是1ms。
	当然也可以定时ms,再写一个1s的延时函数
	*/
	TIM3_Init(7199,9999);	//初始化定时器 
	LED_Init();
	
	while(1){
		LED1_Turn();
		Delay(1);			//延时1s
	}
}

  • 4
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
STM32中,可以使用通用定时器来实现精确的延时。在非中断方式下,我们需要使用定时器的计数器来实现精确的计时。 下面是一个使用TIM2定时器实现精确延时的例子: ```c #include "stm32f10x.h" void TIM2_Delay_Init(u16 arr,u16 psc) { TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //使能TIM2时钟 TIM_TimeBaseInitStruct.TIM_Period=arr; //自动重装载值 TIM_TimeBaseInitStruct.TIM_Prescaler=psc; //分频器 TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分割 TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式 TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct); TIM_Cmd(TIM2,DISABLE); //关闭定时器2 } void TIM2_Delay_us(u32 us) { TIM_SetCounter(TIM2,0); //计数器清0 TIM_Cmd(TIM2,ENABLE); //使能定时器2 while(TIM_GetCounter(TIM2)<us); //等待计数器计满us TIM_Cmd(TIM2,DISABLE); //关闭定时器2 } int main(void) { TIM2_Delay_Init(72-1,72-1); //初始化定时器2,分频系数为72,则计数器时钟为1MHz TIM2_Delay_us(1000); //延时1ms while(1); return 0; } ``` 在上述代码中,我们使用TIM2定时器来实现延时。在初始化函数`TIM2_Delay_Init()`中,我们配置了定时器的自动重装载值和分频器,以及向上计数模式。在延时函数`TIM2_Delay_us()`中,我们首先将计数器清零,然后使能定时器开始计数。接着,我们不断检查计数器是否达到了指定的延时时间,如果没有,则继续等待。当计数器计满指定的时间后,我们关闭定时器,延时结束。 需要注意的是,这种方式并不是非常精确,因为延时过程中可能会有其他中断干扰计数器的计数。如果需要更精确的延时,可以考虑使用硬件定时器中断方式实现
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值