利用STM32F4系列基本定时器写延时程序
前言
在一些需要的情况下软件延时十分必要,有时为了测试方便大都直接用了while(–i)或者for循环大致延时下看看就可以了。当需要精确延时情况下一般需要定时器来定时,当然对于STM32系列单片机都有SysTick,一般都是用这个作为延时定时器。这两天突然想着用个基本定时器实现一个延时程序,想着几十分钟的解决的事情结果搞了一整天,所以写个博客纪念一下调试经历。
必须了解
想要正确使用定时器就不得不先了解两个必要内容:定时器的时钟频率和影子寄存器这两个内容。不止针对基本定时器其它定时器也是一样的。
定时器时钟频率
本着遇到问题就查数据手册的精神,首先来查阅《STM32F4xx中文参考手册》关于定时器章节发现基本上就说计数原理以及寄存器说明,对于时钟没说什么。没关系往上一层看,直接看STM32的时钟树,如下图:
先大致看了下有关于定时器时钟描述的,从图上看大致也就是如果APBx不分频(也就是APBx presc = /1),定时器时钟频率也就是APBx的时钟频率,反之就是对APBx时钟的×2倍频。然后继续往下看会看到这样的话:
可以看出跟我们的理解差不多。那么就到了下一步对于APBx的时钟怎么确定呢?那么一般情况下如果事自己配置的时钟自己应该直接就知道了,如果不是自己配置的我们会直接调用库函数的时钟初始化函数,那么此时有两种方法可以确定我们的APBx的时钟:
法一:还是这个图,首先一个前提我们都知道使用库函数时钟配置,SYSCLK为168MHZ,那么如果知道AHB和APBx的分频值,根据上述描述直接就可以得到定时器频率了。
问题来了,在哪里可以看到这个两个分频值勒,很简单库函数时钟配置在system_stm32f4xx.c,当然在这里找了。很简单不用看代码直接看@brief里面的第5条,找到自己F4的型号,以STM32F40xxx/41xxx devices为例,如下图:
可以看到AHB与APBx的分频值,根据确定上述判定,到APBx的时钟为168MHz,APBx的分频值也不是1,所以定时器时钟也就是APBx时钟的2倍数。以上就可以得到了定时器频率(这种方式可能有些人不清楚定时器挂载在哪一个APB上,简单看法就是直接看rcc里面开启外设时钟里面的参数可选值可以看到或者直接查看数据手册)。
法二:不说废话直接看手册,外设挂载图以及相应时钟(只对使用库函初始化的时钟配置可以,自己配置不可以)
直接看到APB1是42MHz,APB2是84MHz。分频值也不是1所以定时器6、7的时钟频率也就是84MHz。
影子寄存器
关于影子寄存器,我大致说下所谓影子寄存器就是定时器实际工作寄存器,而给与用户的操作的寄存器就是影子寄存器的本体,那么当我们操作寄存器时,需要一个条件去将写入寄存器值更新到影子寄存器中或者“直接操作”影子寄存器。(关于定时器的影子操作寄存器这个文章写的挺好:STM32定时器的预装寄存器及影子寄存器PSC—ARR-CCRx
)
代码设计
首先是初始化。
void tim7Init()
{
NVIC_InitTypeDef NVIC_InitStruct;
//开启相关时钟
RCC_APB1PeriphClockCmd(TIM_RCC_PERIPH, ENABLE);
//开启自动重装
TIM_ARRPreloadConfig(TIM_NAME, ENABLE);
//中断NVIC配置
NVIC_InitStruct.NVIC_IRQChannel = TIM7_IRQn;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 14;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStruct);
//中断条件设置
TIM_ITConfig(TIM_NAME, TIM_IT_Update, ENABLE);
//开启定时器
}
void TIM7_IRQHandler()
{
if(TIM_GetITStatus(TIM_NAME, TIM_IT_Update))//获取中断状态
{
TIM_ClearITPendingBit(TIM_NAME, TIM_IT_Update);
tim7ITCount++;
}
}
void delayMs(uint16_t ms)
{
tim7MsInit(ms);
}
然后时延时函数
static void tim7MsInit(uint32_t ms)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//定时器相关初始化设置-剩下两个参数对于基本定时6、7无影响
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = ms*10-1;//单位是1ms
TIM_TimeBaseInitStruct.TIM_Prescaler = 8400 - 1;//100us计数一次
TIM_TimeBaseInit(TIM_NAME, &TIM_TimeBaseInitStruct);
//开始计时
while(TIM_GetITStatus(TIM_NAME, TIM_IT_Update));//等待初始化事件更新,将设定值设置到寄存器
tim7ITCount = 0;
TIM_Cmd(TIM_NAME, ENABLE);
//等待结束
while(!tim7ITCount);//等待置1
TIM_Cmd(TIM_NAME, DISABLE);
TIM_SetCounter(TIM_NAME, 0);//清空已经计数数值
}
我当时调试了很久一直没有想到,当调用TIM_TimeBaseInit后会产生一个事件更新,之所以产生事件更新也就是因为要将写入的寄存器数值更新到影子寄存器中,不管你开不开是自动预装,当调用调用TIM_TimeBaseInit后会产生一个事件更新,可以查看库函数源码:
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct)
{
uint16_t tmpcr1 = 0;
/* Check the parameters */
assert_param(IS_TIM_ALL_PERIPH(TIMx));
assert_param(IS_TIM_COUNTER_MODE(TIM_TimeBaseInitStruct->TIM_CounterMode));
assert_param(IS_TIM_CKD_DIV(TIM_TimeBaseInitStruct->TIM_ClockDivision));
tmpcr1 = TIMx->CR1;
if((TIMx == TIM1) || (TIMx == TIM8)||
(TIMx == TIM2) || (TIMx == TIM3)||
(TIMx == TIM4) || (TIMx == TIM5))
{
/* Select the Counter Mode */
tmpcr1 &= (uint16_t)(~(TIM_CR1_DIR | TIM_CR1_CMS));
tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_CounterMode;
}
if((TIMx != TIM6) && (TIMx != TIM7))
{
/* Set the clock division */
tmpcr1 &= (uint16_t)(~TIM_CR1_CKD);
tmpcr1 |= (uint32_t)TIM_TimeBaseInitStruct->TIM_ClockDivision;
}
TIMx->CR1 = tmpcr1;
/* Set the Autoreload value */
TIMx->ARR = TIM_TimeBaseInitStruct->TIM_Period ;
/* Set the Prescaler value */
TIMx->PSC = TIM_TimeBaseInitStruct->TIM_Prescaler;
if ((TIMx == TIM1) || (TIMx == TIM8))
{
/* Set the Repetition Counter value */
TIMx->RCR = TIM_TimeBaseInitStruct->TIM_RepetitionCounter;
}
/* Generate an update event to reload the Prescaler
and the repetition counter(only for TIM1 and TIM8) value immediatly */
TIMx->EGR = TIM_PSCReloadMode_Immediate;
}
从最后一句可以看书,会产生了一个更新事件,所以,在初始化完成后,需要等待这个更新事件,并清除标志位,才打开定时器并等待定时结束,才是所需要的定时值。以下是我定时1s时操作结果(因为中间有初始化函数以及等待了一个更新事件所有花费了点时间,小于1ms)
进入函数内部计算就可以看出基本就是1s了
参考
STM32F4xx中文参考手册.pdf
stm32f407Datasheet.pdf