目录
一、STM32的定时器资源
1、STM3 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器功能
2、定时器计数模式
3、计数器时钟选择
二、使用STM32CubeMX创建工程
1、设置RCC
2、时钟树配置
3、配置TIM3
4、设置工程文件等等
三、程序设计
四、HAL库中定时器相关的函数与其用法
STM32 的定时器功能十分强大,有 TIME1 和 TIME8 等高级定时器,也有 TIME2~TIME5 等通用定时器,还有 TIME6 和
TIME7 等基本定时器。
本文将介绍STM32的通用定时器TIME2~TIME5。
1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。
2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535 之间的任意数值。
3)4 个独立通道(TIMx_CH1~4),这些通道可以用来作为:
A.输入捕获
B.输出比较
C.PWM 生成(边缘或中间对齐模式)
D.单脉冲模式输出
4)可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可以用 1 个定时器控制另外一个定时器)的同步电路。
5)如下事件发生时产生中断/DMA:
A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
C.输入捕获
D.输出比较
E.支持针对定位的增量(正交)编码器和霍尔传感器电路
F.触发输入作为外部时钟或者按周期的电流管理
STM32的通用定时器有三种计数模式。
向上计数:计数器从0计数到自动加载值(TIMx_ARR计数器的内容),然后重新从0开始计数并且产生一个计数器溢出事件。
向下计数:计数器从自动装入的值(TIMx_ARR计数器的值)开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出事件。
中央对齐模式( 向上/向下计数):计数器从0开始计数到自动加载的值(TIMx_ARR寄存器)−1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器下溢事件;然后再从0开始重新计数。
用一个表格来说明三种计数模式溢出值与重装值的关系
计数模式 | 计数器溢出值 | 计数器重装值 |
向上计数 | CNT = ARR | CNT = 0 |
向下计数 | CNT = 0 | CNT = ARR |
中心对齐计数 | CNT = ARR - 1 | CNT = ARR |
CNT = 1 | CNT = 0 |
在《STM32参考手册》中有图形来形象的描述这三种计数方式。
计数器时钟可由下列时钟源提供:
● 内部时钟(CK_INT)
● 外部时钟模式1:外部输入脚(TIx)
● 外部时钟模式2:外部触发输入(ETR)
● 内部触发输入(ITRx):使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
接下来以STM32L475VET6开发板为例,使用STM32CubeMX软件创建一个定时器3的工程,控制LED闪烁。
因为这里我们只使用到 HSE,所以我们设置选项 High Speed Clock(HSE)的值为Crystal/Ceramic Resonator(使用晶振/陶瓷振荡器)即可。
根据参考手册上面的时钟树,我们可以知道通用定时器是挂载在APB1总线上面的。
在本次实验中,我想让系统全速运行,而且我的外部晶振为8MHz,设置HCLK为最大频率为80MHz,APB1总线频率也为80MHz。
在TIM3的配置界面,由于在本次实验中仅仅使用到定时器功能,所以只需要设置计数器时钟源为内部时钟即可。
接下来进入详细配置。
定时器的溢出时间为:
在这里,我需要定时器定时1ms,所以可以设置PSC=79,ARR=999,设置为向上计数模式
这里我们只需要设置以上选项即可。
当然不要忘记开启中断,否则定时器是不会进入中断的。
至此,CubeMX的配置完成,点击生成代码。
在CubeMX生成的代码中,外设初始化已经给写好了
TIM_HandleTypeDef htim3;
/* TIM3 init function */
void MX_TIM3_Init(void)
{
TIM_ClockConfigTypeDef sClockSourceConfig = {0};
TIM_MasterConfigTypeDef sMasterConfig = {0};
htim3.Instance = TIM3;
htim3.Init.Prescaler = 79;
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 999;
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
if (HAL_TIM_ConfigClockSource(&htim3, &sClockSourceConfig) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim3, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM3)
{
/* USER CODE BEGIN TIM3_MspInit 0 */
/* USER CODE END TIM3_MspInit 0 */
/* TIM3 clock enable */
__HAL_RCC_TIM3_CLK_ENABLE();
/* TIM3 interrupt Init */
HAL_NVIC_SetPriority(TIM3_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM3_IRQn);
/* USER CODE BEGIN TIM3_MspInit 1 */
/* USER CODE END TIM3_MspInit 1 */
}
}
以及初始化函数的调用
和中断函数都已经写好了
关于STM32的HAL库的中断系统,稍后会详细讲解。
我们只需要编写定时器的中断回调函数,该函数在stm32l4xx_hal_tim.c文件中也有定义。
/**
* @brief Output Compare callback in non-blocking mode
* @param htim TIM OC handle
* @retval None
*/
__weak void HAL_TIM_OC_DelayElapsedCallback(TIM_HandleTypeDef *htim)
{
/* Prevent unused argument(s) compilation warning */
UNUSED(htim);
/* NOTE : This function should not be modified, when the callback is needed,
the HAL_TIM_OC_DelayElapsedCallback could be implemented in the user file
*/
}
注意,在stm32l4xx_hal_tim.c文件中定义的这个回调函数,前面有一个__weak修饰符,关于这个关键字的用法如下
weak 顾名思义是“弱”的意思,所以如果函数名称前面加上__weak 修饰符,我们一般称这个函数为“弱函数”。加上了__weak 修饰符的函数,用户可以在用户文件中重新定义一个同名函数,最终编译器编译的时候,会选择用户定义的函数,如果用户没有重新定义这个函数,那么编译器就会执行__weak 声明的函数,并且编译器不会报错。
__weak 在回调函数的时候经常用到。这样的好处是,系统默认定义了一个空的回调函数,保证编译器不会报错。同时,如果用户自己要定义用户回调函数,那么只需要重新定义即可,不需要考虑函数重复定义的问题,使用非常方便,在 HAL 库中__weak 关键字被广泛使用。
所以我们重新定义一个中断回调函数,然后在回调函数中判断是哪个定时器发生了溢出中断,执行相应的操作即可。
注意:自己添加的代码请在用户代码段之间,防止使用CubeMX软件更新配置的时候将用户自定义代码擦除覆盖。
/* USER CODE BEGIN 1 */
// 请在这里添加代码,防止被擦除覆盖
/* USER CODE END 1 */
自定义定时器中断回调函数,这里我放在了tim.c文件中的用户代码段里面
// 定时器更新中断回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint16_t TIM3_Cnt1 = 0;
// 判断是定时器3发生的中断
if(htim->Instance == TIM3)
{
// 定时器3每中断一次,计数器自加1
TIM3_Cnt1 ++;
// 计数1000次,定时1s
if(TIM3_Cnt1 >= 1000)
{
// 清除计数器
TIM3_Cnt1 = 0;
// 翻转LED_R的电平状态
HAL_GPIO_TogglePin(LED_R_GPIO_Port, LED_R_Pin);
}
}
}
然后在main.c中,在定时器初始化命令之后加入以下代码
// 清除定时器初始化过程中的更新中断标志,避免定时器一启动就中断
__HAL_TIM_CLEAR_IT(&htim3, TIM_IT_UPDATE);
// 使能定时器3更新中断并启动定时器
HAL_TIM_Base_Start_IT(&htim3);
最终结果如下所示
然后编译,并烧录到单片机中,观察发现红灯每个1s亮灭一次,实验成功。
未完待续......