1.TIM6基本定时器功能简介
2.HAL库函数开发定时器配置的步骤
3.图形化配置中定时器相关函数调用关系
1.TIM6基本定时器简介
基本定时器TIM6包含一个16位自动装载计数器,由各自的可编程预分频器驱动。它们可以作为通用定时器提供时间基准,特别地可以为数模转换器(DAC)提供时钟。实际上,它们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC。
1.1主要特性
● 16位自动重装载累加计数器
● 16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
● 触发DAC的同步电路
● 在更新事件(计数器溢出)时产生中断/DMA请求
1.2 定时器内部原理简介
如图1所示, 内部时钟 CK_INT信号,是从 APB1 倍频的来的,其频率一般都比较高;如图2所示,由于APB1 的时钟分频数设置为 2,外接定时器的时钟是72MHz,所以图中的CK_INT=72MHz;体现在16位的定时器上的效果就是从0计数到65535上溢只需要0.9毫秒。
如图1所示,如果我们需要更长时间的定时间隔,需要预分频器对时钟进行分频处理,以降低定时器时钟(CK_CNT)的频率。
除此之外,也可以通过配置预分频器,来获取想要的定时器时钟频率。如果我们想获取一个精确的1ms中断,如果不分频,72MHz的时钟对应每周期1/72us,十分不利于计算。这时候使用预分频器将其72分频后为1MHz,每周期1us,1000个计时周期即为1ms,这样既便于计算,定时也更加精确。(预分频器的作用详解https://zhuanlan.zhihu.com/p/82590576)
PSC预分频器输出CK_INT信号,作为CNT计数器的输入信号,于此同时,自动重装载寄存器将数值存入到CNT计数器中;
如图1所示,经过定时器控制器(TIM6_CR1),输出CK_PSC=72MHz,经过预分频器PSC,如果想定时1秒;如图公式1所示,
如果我们想定时其他时间,单位是秒,参考公式2:
在CubeMX图形化配置中,CKCNT的数值等同于 Prescaler的配置值;TIMxARR的数值等于Counter Preiod 的数值;
2.HAL库函数开发定时器配置的步骤:
1)TIM6 时钟使能;
2)初始化定时器参数,设置自动重装值,分频系数,计数方式等:
3)使能定时器更新中断,使能定时器;
4)TIM6中断优先级设置;
5)编写中断服务函数。
在图形化配置的HAL库中,我们依然可以按照这个思路找到对应函数功能:
1)TIM6 时钟使能
HAL 中定时器使能是通过宏定义标识符来实现对相关寄存器操作的__HAL_RCC_TIM6_CLK_ENABLE()被函数HAL_TIM_Base_MspInit()调用;
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM6)
{
/* TIM6 clock enable */
__HAL_RCC_TIM6_CLK_ENABLE();
/* TIM6 interrupt Init */
HAL_NVIC_SetPriority(TIM6_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(TIM6_IRQn);
}
}
2)初始化定时器参数,设置自动重装值,分频系数,计数方式等。在TIM_Base_InitTypeDef结构体成员变量中,对相关参数都进行了申明;
typedef struct
{
uint32_t Prescaler; \\预分频系数
uint32_t CounterMode; \\计数模式
uint32_t Period; \\自动装载值ARR
uint32_t ClockDivision; \\时钟分频因子
uint32_t RepetitionCounter;
uint32_t AutoReloadPreload;
} TIM_Base_InitTypeDef;
参数 Prescaler 是用来设置分频系数的;
参数CounterMode 是用来设置计数方式, 常 用 的 是 向 上 计 数 模 式 TIM_CounterMode_Up 和 向 下 计 数 模 式TIM_CounterMode_Down;
参数 Period 是设置自动重载计数周期值;就是TIMx_ARR的数值
参数 ClockDivision 是用来设置时钟分频因子,也就是定时器时钟频率 CK_INT 与数字滤波器所使用的采样时钟之间的分频比。
参数 RepetitionCounter 用来设置重复计数器寄存器的值,用在高级定时器中。
在图形化配置工具中,如图3所示,我们可以找到相关参数的配置;从这里我们可以得出,任何图形化配置的参数都会最终在函数代码中找到对应关系;
3)使能定时器更新中断,使能定时器
HAL 库 中 , 使 能 定 时 器 更 新 中 断 和 使 能 定 时 器 两 个 操 作 可 以 在 函 数HAL_TIM_Base_Start_IT()中一次完成的,该函数声明如下:
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim);
该 函 数只 有 一 个 入 口 参 数 。 调 用 该 定 时 器 之 后 , 会 首 先 调 用
__HAL_TIM_ENABLE_IT 宏定义使能更新中断,然后调用宏定义__HAL_TIM_ENABLE 使能相应的定时器。
/**
* @brief Starts the TIM Base generation in interrupt mode.
* @param htim TIM Base handle
* @retval HAL status
*/
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
uint32_t tmpsmcr;
/* Check the parameters */
assert_param(IS_TIM_INSTANCE(htim->Instance));
/* Check the TIM state */
if (htim->State != HAL_TIM_STATE_READY)
{
return HAL_ERROR;
}
/* Set the TIM state */
htim->State = HAL_TIM_STATE_BUSY;
/* Enable the TIM Update interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
/* Enable the Peripheral, except in trigger mode where enable is automatically done with trigger */
if (IS_TIM_SLAVE_INSTANCE(htim->Instance))
{
tmpsmcr = htim->Instance->SMCR & TIM_SMCR_SMS;
if (!IS_TIM_SLAVEMODE_TRIGGER_ENABLED(tmpsmcr))
{
__HAL_TIM_ENABLE(htim);
}
}
else
{
__HAL_TIM_ENABLE(htim);
}
/* Return function status */
return HAL_OK;
}
4)TIM6中断优先级设置
HAL_NVIC_SetPriority(TIM6_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(TIM6_IRQn);
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM6)
{
/* TIM6 clock enable */
__HAL_RCC_TIM6_CLK_ENABLE();
/* TIM6 interrupt Init */
HAL_NVIC_SetPriority(TIM6_IRQn, 2, 0);
HAL_NVIC_EnableIRQ(TIM6_IRQn);
}
5)编写中断服务函数
首先,定时器6的中断服务函数为:
void TIM6_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim6);
}
一般情况下我们是在中断服务函数内部编写中断控制逻辑。但是 HAL 库为我们定义了 新的定时器中断共用处理函数 HAL_TIM_IRQHandler,在每个定时器的中断服务函数内部,我们会调用该函数。该函数声明如下:
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim);
/* TIM Update event */
if (__HAL_TIM_GET_FLAG(htim, TIM_FLAG_UPDATE) != RESET)
{
if (__HAL_TIM_GET_IT_SOURCE(htim, TIM_IT_UPDATE) != RESET)
{
__HAL_TIM_CLEAR_IT(htim, TIM_IT_UPDATE);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->PeriodElapsedCallback(htim);
#else
HAL_TIM_PeriodElapsedCallback(htim);
而函数 HAL_TIM_IRQHandler 内部,会对相应的中断标志位进行详细判断,判断确定中断来源后,会自动清掉该中断标志位,同时调用不同类型中断的回调函数。所以我们的中断控制逻辑只用编写在中断回调函数中,并且中断回调函数中不需要清中断标志位。
比如定时器更新中断回调函数为:
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim);
我们只需要在main.c中重写该函数即可。
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (TIM6 == htim ->Instance)
{
num=num>>1;
if (num==0)
num=0x80;
HAL_GPIO_WritePin(GPIOE,0xff,GPIO_PIN_SET);
HAL_GPIO_WritePin(GPIOE,num,GPIO_PIN_RESET);
}
}
在基于图形化配置生成的KEIL5工程中,我们从自动生成的main.c函数中,可以找到MX_TIM6_Init()函数,关于定时器的 时钟使能,初始参数配置,就在这个函数中完成;如图8所示;
如图9所示:
MX_TIM6_Init()调用了HAL_TIM_Base_Init(),
HAL_TIM_Base_Init()调用了HAL_TIM_Base_MspInit()
通过这三个函数,就实现了定时器时钟使能,定时器参数配置,定时器中断优先级配置;也就是上文的第1步,第2步和第4步;
如图10所示,在mian.c中补充使能定时器中断,就实现了上文的第3步,函数使能定时器更新中断,使能定时器;
如图11所示,在main.c中补充回调函数,就实现了第5步;
总结:依据定时器配置的步骤,我们找到了相关函数的定义和功能;在图形化配置中,这些功能函数都已经自动生成且帮我们封装好了,对于初学者,需要结合步骤,将每一个函数的功能进行理解,才能够有效运用;