STM32使用 通用定时器中断服务程序 实现ms级延时
使用通用定时器的原因
定时器和中断的关系:定时器不受中断函数的影响,不会因为进入中断服务程序就停止计时。例如:定时器计时2s,中断函数执行需要0.1s,第二次继续计时就从上次的2s继续,而不是从2.1s再开始定时。它并不等待中断函数直接计时,相当于它计时它的,中断函数自己执行。
利用这个特点,我们可以通过定时器进入中断函数的次数,和定时器进入中断服务程序的时间来进行延时。延时时间=进入中断的次数*定时器进入中断时间。具体实现可看代码。
使用定时器延时的注意问题
通过查阅网络上的资料,有建议:
“中断服务子程序是不要调用延时子程序,这样会增加中断处理的时间,如果有其他低级中断,就会延误响应中断了。
中断子程序不要过长,处理多的任务,要尽快返回,如果中断一次执行很多任务,可以在中断中设置一个标志位,当标志位为1时,就在主程序中完成这些任务,这样就不会影响中断源中断,也不会使中断产生混乱。”
这样的说法,我们可以这样思考:
- 如果定时器进入中断服务子程序的时间是2s,而中断服务子程序的任务太多,执行时间超过两秒,中断的执行可能就会产生混乱。
- 我们使用的程序中,中断服务程序只是用来计次,记录进入中断子程序的次数,具体延时的实现借助延时函数,不会影响其他中断。
定时时长的确定
- 定时器的定时时长由预装载值和预分频系数确定。
- 预分频系数:将频率分割,比如常用的STM32F103C8T6的时钟频率一般是72MHz,可以理解为单片机一秒钟数72M次。如果分频系数是72(设置的时候数值应该是72-1),则该时钟的频率会变成72MHz/72=1MHz,可以理解为单片机一秒钟数1M次,即1us数1次。
- 预装载值:单片机在预分频后需要计数的值。比如分频系数是上面的72,也就是单片机1us数1次。需要定时1ms,1ms=1us*1000,那么预装载值就是1000(设置的时候数值应该是1000-1)。所以在分频系数确定的情况下,定时的时长由预装载值确定。
- 定时器的周期计算公式:((arr+1)*(psc+1))/Tclk
其中arr是预分频系数,psc是预装载值,Tclk是定时器的时钟频率。
如下图所示STM32F10x系列的TIM2-TIM7时钟受APB1控制,TIM2-TIM7时钟是从 APB1 倍频的来的,TIMx 的时钟 是 APB1 时钟的 2 倍,也就是72MHz。Tclk一般为72MHz。
目前测试实现的结果
- 1ms的精准延时和1s的精准延时,使用示波器测量显示。
使用程序需要注意
- 尝试继续实现1us的延时测试时有些问题暂未解决,后续实现会再做总结。
- 为实现主要逻辑使用的是库函数的方法,后续可以尝试使用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
}
}