STM32-CubeMX学习使用记录5-基础定时器的使用
1.STM32定时器介绍
1.stm32的定时器分为高级定时器、通用定时器、基础定时器三种类型
2.基础定时器:TIM6、TIM7
基础定时器的功能包括:
触发DAC的同步电路、在更新事件(计数器溢出)时产生中断/DMA请求
2.1 基本定时器是连接在APB1总线上的,CK_INT为内部时钟源,CK_INT = CK_PSC
2.2 基本定时器的时基单元有:
计数器寄存器(TIMx_CNT) 、预分频寄存器(TIMx_PSC)、 自动重装载寄存器(TIMx_ARR)且都是16位寄存器。
工作流程:内部时钟CK_INT 经过控制器(主要控制计数器的复位、使能、计数)后名字改为CK_PSC(频率与CK_INT 相同)通过PSC预分频器进行分频,名字改为CK_CNT到达计数器,为计数器提供脉冲开始计数,当计数值到达自动重装载寄存器的数值后,触发一次更新中断,根据工作模式的不同,停止或者不停循环计数触发更新中断。
2.3 软件可以读写计数器、自动重装载寄存器和预分频寄存器,即使计数器运行时也可以操作
在计时器运行过程中修改寄存器:
1.预分频器
预分频可以以系数介于1至65536之间的任意数值对计数器时钟分频,因为TIMx_PSC控制寄存器具有缓冲,可以在运行过程中改变它 的数值,新的预分频数值将在下一个更新事件时起作用.
2.自动重装载寄存器:
在计数器运行过程中修改自动重装载寄存器的值分为两种情况,这两种情况受到TIMx_CR1 控制寄存器中的ARPE位决定,如果ARPE = 0,则会直接修改自动重装载寄存器。如果ARPE = 1 ,则会将修改后的值存入影子寄存器中,等待更新事件触发后影子寄存器的数据更新到自动重装载寄存器,所以自动重装载寄存器是有预加载的。
3. 计数器寄存器:
计数器寄存器也可以修改其数据,不过一般是在计数器停止或者重新计数时修改。
2.4 定时器的工作模式有两种,一种是触发一次更新中断后就停止计数。另一种是在触发一次更新中断后,触发控制器重新进行新一轮的计数。两种模式的选择由TIMx_CR1寄存器中的OPM位来控制。
2.STM_HAL库基础定时器函数
定时器初始化的函数(cubemx自动生成好了):
Ctrl + F 搜索 HAL_TIM_Base找到有关基础定时器的函数
上述函数中的结构体有:
TIM_HandleTypeDef
TIM_TypeDef
TIM_Base_InitTypeDef
HAL_TIM_StateTypeDef
1. TIM_HandleTypeDef :定时器句柄,包含的东西有点多,基础定时器看前两个
2. TIM_TypeDef : 就是有关定时器寄存器的结构体,这样就可以操作寄存器了。
比如:
TIM_TypeDef *TIM6 = (TIM_TypeDef *) TIM6_BASE;
TIM6->CR1 |= TIM_CR1_CEN; // 启用定时器6
3. TIM_Base_InitTypeDef :对定时器进行参数配置
4. HAL_TIM_StateTypeDef :返回定时器的状态
简单看一下HAL_TIM_Base_Init函数,可以直接将这段代码复制到ChatGpt中查看讲解。其他的函数一样的做法。
HAL_StatusTypeDef HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)
{
/* Check the TIM handle allocation */
if (htim == NULL)
{
return HAL_ERROR;
}
/* Check the parameters */
assert_param(IS_TIM_INSTANCE(htim->Instance));
assert_param(IS_TIM_COUNTER_MODE(htim->Init.CounterMode));
assert_param(IS_TIM_CLOCKDIVISION_DIV(htim->Init.ClockDivision));
assert_param(IS_TIM_PERIOD(htim, htim->Init.Period));
assert_param(IS_TIM_AUTORELOAD_PRELOAD(htim->Init.AutoReloadPreload));
if (htim->State == HAL_TIM_STATE_RESET)
{
/* Allocate lock resource and initialize it */
htim->Lock = HAL_UNLOCKED;
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
/* Reset interrupt callbacks to legacy weak callbacks */
TIM_ResetCallback(htim);
if (htim->Base_MspInitCallback == NULL)
{
htim->Base_MspInitCallback = HAL_TIM_Base_MspInit;
}
/* Init the low level hardware : GPIO, CLOCK, NVIC */
htim->Base_MspInitCallback(htim);
#else
/* Init the low level hardware : GPIO, CLOCK, NVIC */
HAL_TIM_Base_MspInit(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
/* Set the TIM state */
htim->State = HAL_TIM_STATE_BUSY;
/* Set the Time Base configuration */
TIM_Base_SetConfig(htim->Instance, &htim->Init);
/* Initialize the DMA burst operation state */
htim->DMABurstState = HAL_DMA_BURST_STATE_READY;
/* Initialize the TIM channels state */
TIM_CHANNEL_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
TIM_CHANNEL_N_STATE_SET_ALL(htim, HAL_TIM_CHANNEL_STATE_READY);
/* Initialize the TIM state*/
htim->State = HAL_TIM_STATE_READY;
return HAL_OK;
}
可以知道 USE_HAL_TIM_REGISTER_CALLBACKS != 1 所以调用HAL_TIM_Base_MspInit(htim) 函数来配置时钟、NVIC优先级、中断开启,也就是CubeMX生成的函数。然后TIM_Base_SetConfig(htim->Instance, &htim->Init)这个函数会将配置好的参数赋值给对应的寄存器。HAL_TIM_Base_Init(TIM_HandleTypeDef *htim)这个函数会在main中的MX_TIM6_Init()中调用,MX_TIM6_Init()这个函数也是CubeMX自动生成的,所以初始化只要知道流程就好了。初始化流程和标准库是一样的。
打开main中的MX_TIM6_Init()函数:这里我是使用的168Mhz的STM32F407VET6单片机,主要是记录学习过程,这段代码是cubemx生成的。
/* TIM6 init function */
void MX_TIM6_Init(void)
{
/* USER CODE BEGIN TIM6_Init 0 */
/* USER CODE END TIM6_Init 0 */
TIM_MasterConfigTypeDef sMasterConfig = {0};
/* USER CODE BEGIN TIM6_Init 1 */
/* USER CODE END TIM6_Init 1 */
htim6.Instance = TIM6;
htim6.Init.Prescaler = 83;
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 999;
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
if (HAL_TIMEx_MasterConfigSynchronization(&htim6, &sMasterConfig) != HAL_OK)
{
Error_Handler();
}
/* USER CODE BEGIN TIM6_Init 2 */
/* USER CODE END TIM6_Init 2 */
}
可以看到在这个函数中调用了 HAL_TIM_Base_Init 函数来进行初始化基础定时器。
打开生成的HAL_TIM_Base_MspInit(htim)函数:
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* tim_baseHandle)
{
if(tim_baseHandle->Instance==TIM6)
{
/* USER CODE BEGIN TIM6_MspInit 0 */
/* USER CODE END TIM6_MspInit 0 */
/* TIM6 clock enable */
__HAL_RCC_TIM6_CLK_ENABLE();
/* TIM6 interrupt Init */
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 1, 0);
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
/* USER CODE BEGIN TIM6_MspInit 1 */
/* USER CODE END TIM6_MspInit 1 */
}
}
可以看到配置了定时器的时钟以及中断优先级和打开中断。
初始化配置好后,再就是看中断的开启与中断回调流程:
使用cubemx生成的代码中,定时器中断放到了it . c文件中。打开stm32f4xx_it.c文件。可以看到
/**
* @brief This function handles TIM6 global interrupt, DAC1 and DAC2 underrun error interrupts.
*/
void TIM6_DAC_IRQHandler(void)
{
/* USER CODE BEGIN TIM6_DAC_IRQn 0 */
/* USER CODE END TIM6_DAC_IRQn 0 */
HAL_TIM_IRQHandler(&htim6);
/* USER CODE BEGIN TIM6_DAC_IRQn 1 */
/* USER CODE END TIM6_DAC_IRQn 1 */
}
打开HAL_TIM_IRQHandler(&htim6):
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
{
uint32_t itsource = htim->Instance->DIER;
uint32_t itflag = htim->Instance->SR;
/* Capture compare 1 event */
if ((itflag & (TIM_FLAG_CC1)) == (TIM_FLAG_CC1))
{
if ((itsource & (TIM_IT_CC1)) == (TIM_IT_CC1))
{
{
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC1);
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_1;
/* Input capture event */
if ((htim->Instance->CCMR1 & TIM_CCMR1_CC1S) != 0x00U)
{
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->IC_CaptureCallback(htim);
#else
HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
/* Output compare event */
else
{
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->OC_DelayElapsedCallback(htim);
htim->PWM_PulseFinishedCallback(htim);
#else
HAL_TIM_OC_DelayElapsedCallback(htim);
HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
}
}
}
/* Capture compare 2 event */
if ((itflag & (TIM_FLAG_CC2)) == (TIM_FLAG_CC2))
{
if ((itsource & (TIM_IT_CC2)) == (TIM_IT_CC2))
{
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC2);
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_2;
/* Input capture event */
if ((htim->Instance->CCMR1 & TIM_CCMR1_CC2S) != 0x00U)
{
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->IC_CaptureCallback(htim);
#else
HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
/* Output compare event */
else
{
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->OC_DelayElapsedCallback(htim);
htim->PWM_PulseFinishedCallback(htim);
#else
HAL_TIM_OC_DelayElapsedCallback(htim);
HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
}
}
/* Capture compare 3 event */
if ((itflag & (TIM_FLAG_CC3)) == (TIM_FLAG_CC3))
{
if ((itsource & (TIM_IT_CC3)) == (TIM_IT_CC3))
{
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC3);
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_3;
/* Input capture event */
if ((htim->Instance->CCMR2 & TIM_CCMR2_CC3S) != 0x00U)
{
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->IC_CaptureCallback(htim);
#else
HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
/* Output compare event */
else
{
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->OC_DelayElapsedCallback(htim);
htim->PWM_PulseFinishedCallback(htim);
#else
HAL_TIM_OC_DelayElapsedCallback(htim);
HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
}
}
/* Capture compare 4 event */
if ((itflag & (TIM_FLAG_CC4)) == (TIM_FLAG_CC4))
{
if ((itsource & (TIM_IT_CC4)) == (TIM_IT_CC4))
{
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_CC4);
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_4;
/* Input capture event */
if ((htim->Instance->CCMR2 & TIM_CCMR2_CC4S) != 0x00U)
{
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->IC_CaptureCallback(htim);
#else
HAL_TIM_IC_CaptureCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
/* Output compare event */
else
{
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->OC_DelayElapsedCallback(htim);
htim->PWM_PulseFinishedCallback(htim);
#else
HAL_TIM_OC_DelayElapsedCallback(htim);
HAL_TIM_PWM_PulseFinishedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
htim->Channel = HAL_TIM_ACTIVE_CHANNEL_CLEARED;
}
}
/* TIM Update event */
if ((itflag & (TIM_FLAG_UPDATE)) == (TIM_FLAG_UPDATE))
{
if ((itsource & (TIM_IT_UPDATE)) == (TIM_IT_UPDATE))
{
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->PeriodElapsedCallback(htim);
#else
HAL_TIM_PeriodElapsedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
}
/* TIM Break input event */
if ((itflag & (TIM_FLAG_BREAK)) == (TIM_FLAG_BREAK))
{
if ((itsource & (TIM_IT_BREAK)) == (TIM_IT_BREAK))
{
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_BREAK);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->BreakCallback(htim);
#else
HAL_TIMEx_BreakCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
}
/* TIM Trigger detection event */
if ((itflag & (TIM_FLAG_TRIGGER)) == (TIM_FLAG_TRIGGER))
{
if ((itsource & (TIM_IT_TRIGGER)) == (TIM_IT_TRIGGER))
{
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_TRIGGER);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->TriggerCallback(htim);
#else
HAL_TIM_TriggerCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
}
/* TIM commutation event */
if ((itflag & (TIM_FLAG_COM)) == (TIM_FLAG_COM))
{
if ((itsource & (TIM_IT_COM)) == (TIM_IT_COM))
{
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_COM);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->CommutationCallback(htim);
#else
HAL_TIMEx_CommutCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
}
}
看到代码很多,但是基础定时器一般只用中断更新的功能,所以找到更新相关的函数:
/* TIM Update event */
if ((itflag & (TIM_FLAG_UPDATE)) == (TIM_FLAG_UPDATE))
{
if ((itsource & (TIM_IT_UPDATE)) == (TIM_IT_UPDATE))
{
__HAL_TIM_CLEAR_FLAG(htim, TIM_FLAG_UPDATE);
#if (USE_HAL_TIM_REGISTER_CALLBACKS == 1)
htim->PeriodElapsedCallback(htim);
#else
HAL_TIM_PeriodElapsedCallback(htim);
#endif /* USE_HAL_TIM_REGISTER_CALLBACKS */
}
}
和外部中断一样都是先检查再清楚标志位,然后进入一个函数HAL_TIM_PeriodElapsedCallback(htim);
打开此函数:
__weak void HAL_TIM_PeriodElapsedCallback(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_PeriodElapsedCallback could be implemented in the user file
*/
}
发现是一个弱函数,所以和外部中断一样,进行复写。
我选择在tim.c中进行复写:
/* USER CODE BEGIN 1 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
static uint16_t num;
if (htim->Instance == TIM6)
{
num++;
if (num == 500)
{
HAL_GPIO_TogglePin(LED_USER_GPIO_Port, LED_USER_Pin); // 1s翻转一次电平
num = 0;
}
}
}
/* USER CODE END 1 */
在main中加入中断启动定时器的函数:
int main(void)
{
/* USER CODE BEGIN 1 */
/* USER CODE END 1 */
/* MCU Configuration--------------------------------------------------------*/
/* Reset of all peripherals, Initializes the Flash interface and the Systick. */
HAL_Init();
/* USER CODE BEGIN Init */
/* USER CODE END Init */
/* Configure the system clock */
SystemClock_Config();
/* USER CODE BEGIN SysInit */
/* USER CODE END SysInit */
/* Initialize all configured peripherals */
MX_GPIO_Init();
MX_TIM6_Init();
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim6);
/* USER CODE END 2 */
/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */
}
这样就可以实现1ms进入一次更新中断,500ms翻转一次电平。
一般基础定时器用来作为中断使用。
除此之外,如果想要改变正在运行时定时器的配置,则可以使用宏定义函数来修改:
在stm32f4xx_hal_tim.h中有关于定时器的宏定义函数:
1. __HAL_TIM_ENABLE(__HANDLE__) 启动定时器
2.__HAL_TIM_DISABLE(__HANDLE__) 关闭定时器
3.__HAL_TIM_ENABLE_IT(__HANDLE__, __INTERRUPT__) 使能定时器中断
4.__HAL_TIM_DISABLE_IT(__HANDLE__, __INTERRUPT__) 失能定时器中断
5. __HAL_TIM_ENABLE_DMA(__HANDLE__, __DMA__) 使能更新触发DMA
6.__HAL_TIM_DISABLE_DMA(__HANDLE__, __DMA__) 失能更新触发DMA
7. __HAL_TIM_GET_FLAG(__HANDLE__, __FLAG__)获取当前定时器的中断状态
8. __HAL_TIM_SET_PRESCALER(__HANDLE__, __PRESC__) 设置定时器的预分频值
9. __HAL_TIM_SET_COUNTER(__HANDLE__, __COUNTER__) 设置计数器的计数值
10. __HAL_TIM_SET_COUNTER(__HANDLE__, __COUNTER__) 获取当前计数器的计数值
11. __HAL_TIM_SET_AUTORELOAD(__HANDLE__, __AUTORELOAD__) 设置重装载寄存器的值
12. __HAL_TIM_GET_AUTORELOAD(__HANDLE__) 获取重装载寄存器的值
除了以上宏定义函数外还有很多。
3.STM_HAL库 基础定时器cubemx 的配置
作者:吼吼吼欧亚