此篇博客记录的是自己通过CubeMX学习F7系列定时器功能的过程,献给有过标准库开发经验的同学。
基本计时功能
最简单的,定时器嘛,基本的定时器就是定时功能,简单来说就是TIMx->CNT会跟随着输入时钟的脉冲而计数。
初始化定时器的参数,大家都好理解,因为TIM2的输入时钟是108Mhz,这里进行10800分频,输入频率为10K,重装载值设置为20K,每2秒溢出一次。
在HAL_TIM_Base_Init
的执行过程中,会先调用HAL_TIM_Base_MspInit
再进行其他参数的配置,即先开时钟。
TIM_HandleTypeDef TIM2_Handler;
static void MX_TIM2_Init(void)
{
TIM2_Handler.Instance = TIM2;
TIM2_Handler.Init.Prescaler = 10800;
TIM2_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM2_Handler.Init.Period = 20000;
TIM2_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&TIM2_Handler);
HAL_TIM_Base_Start(&TIM2_Handler);
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
__HAL_RCC_TIM2_CLK_ENABLE(); //使能TIM3时钟
}
主函数中每秒打印一次定时器的值:
while (1)
{
printf("cnt:%d\r\n",TIM2->CNT);
delay_ms(1000);
}
显示效果如下:
定时器中断
通过HAL_TIM_Base_Start
可以开启基本计时功能,但要实现定时器中断功能,就需要开启相应的标志位,即使用HAL_TIM_Base_Start_IT
进行定时的开启;
在配置定时器之前,除了要开启时钟,还需要先设置中断优先级,和使能中断向量;
在定时器中断TIM2_IRQHandler
服务函数中调用HAL库提供的定时器中断处理函数HAL_TIM_IRQHandler
,解析到的定时器超时中断会自动跳转到HAL_TIM_PeriodElapsedCallback
;
TIM_HandleTypeDef TIM2_Handler;
static void MX_TIM2_Init(void)
{
TIM2_Handler.Instance = TIM2;
TIM2_Handler.Init.Prescaler = 10800;
TIM2_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM2_Handler.Init.Period = 20000;
TIM2_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&TIM2_Handler);
//HAL_TIM_Base_Start(&TIM2_Handler);
HAL_TIM_Base_Start_IT(&TIM2_Handler);
}
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef *htim)
{
__HAL_RCC_TIM2_CLK_ENABLE(); //使能TIM2时钟
HAL_NVIC_SetPriority(TIM2_IRQn, 1, 3); //设置中断优先级,抢占优先级1,子优先级3
HAL_NVIC_EnableIRQ(TIM2_IRQn); //开启ITM2中断
}
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIM2_Handler);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == (&TIM2_Handler))
{
printf("enter irq\r\n");
}
}
显示效果如下:
PWM输出
硬件PWM输出是不需要使用定时器中断的,但同样需要基本的定时参数配置,初始化也不再是使用HAL_TIM_Base_Init
而是使用HAL_TIM_PWM_Init
进行初始化了;
配置完定时器为PWM模式,那么相应的输出通道也需要通过HAL_TIM_PWM_ConfigChannel
进行配置;
同样的启动也不是HAL_TIM_Base_Start
或者HAL_TIM_Base_Start_IT
了,而是HAL_TIM_PWM_Start
了;
TIM_HandleTypeDef TIM2_Handler;
TIM_OC_InitTypeDef TIM2_CH2Handler;
static void MX_TIM2_Init(void)
{
TIM2_Handler.Instance = TIM2;
TIM2_Handler.Init.Prescaler = 10800;
TIM2_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM2_Handler.Init.Period = 20000;
TIM2_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&TIM2_Handler);
TIM2_CH2Handler.OCMode = TIM_OCMODE_PWM1;
TIM2_CH2Handler.Pulse = 10000;
TIM2_CH2Handler.OCPolarity = TIM_OCPOLARITY_HIGH;
TIM2_CH2Handler.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&TIM2_Handler, &TIM2_CH2Handler, TIM_CHANNEL_2) ;
HAL_TIM_PWM_Start(&TIM2_Handler, TIM_CHANNEL_2); //开启PWM通道2
}
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
通过万用表可以发现PA1口的电压在0和3.3V中大概每秒变动一次(疫情期间在家学习,没有示波器真是太惨了……为了确定确实有PWM波形,只能出此下策了T_T),串口打印数据如下:
PWM + 定时器溢出中断
我们都知道,PWM模式是在比较值处翻转,在溢出的时候再次翻转,而溢出的时候,我们也是可以产生中断的;所以同一个定时器,在这种定时时长和周期相同的时候,是可以即做硬件PWM输出,又做溢出中断的。
这里我们观察一下两种初始化函数:
/**
* @brief Initializes the TIM Time base Unit according to the specified
* parameters in the TIM_HandleTypeDef and create the associated handle.
* @param htim: pointer to a TIM_HandleTypeDef structure that contains
* the configuration information for TIM module.
* @retval HAL status
*/
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));
if(htim->State == HAL_TIM_STATE_RESET)
{
/* Init the low level hardware : GPIO, CLOCK, NVIC */
HAL_TIM_Base_MspInit(htim);
}
/* Set the TIM state */
htim->State= HAL_TIM_STATE_BUSY;
/* Set the Time Base configuration */
TIM_Base_SetConfig(htim->Instance, &htim->Init);
/* Initialize the TIM state*/
htim->State= HAL_TIM_STATE_READY;
return HAL_OK;
}
HAL_StatusTypeDef HAL_TIM_PWM_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));
if(htim->State == HAL_TIM_STATE_RESET)
{
/* Allocate lock resource and initialize it */
htim->Lock = HAL_UNLOCKED;
/* Init the low level hardware : GPIO, CLOCK, NVIC and DMA */
HAL_TIM_PWM_MspInit(htim);
}
/* Set the TIM state */
htim->State= HAL_TIM_STATE_BUSY;
/* Init the base time for the PWM */
TIM_Base_SetConfig(htim->Instance, &htim->Init);
/* Initialize the TIM state*/
htim->State= HAL_TIM_STATE_READY;
return HAL_OK;
}
两者除了底层的HAL_TIM_Base_MspInit
和HAL_TIM_PWM_MspInit
,其他地方一模一样,所以我们随便调用一个初始化就可以了,但是要注意,我们在HAL_TIM_Base_MspInit
里配置了NVIC,在HAL_TIM_PWM_MspInit
里配置了GPIO,所以只调用一个的话,必须手动把底层的粘贴到另一个函数里去,如果是想两个都调用一次,这里就需要考虑htim->State
这个变量了,因为在调用了一次之后,其值就从HAL_TIM_STATE_RESET
变成了HAL_TIM_STATE_READY
,后面的一个就得不到执行了。
再来看启动代码:
/**
* @brief Starts the TIM Base generation.
* @param htim: pointer to a TIM_HandleTypeDef structure that contains
* the configuration information for TIM module.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_TIM_Base_Start(TIM_HandleTypeDef *htim)
{
/* Check the parameters */
assert_param(IS_TIM_INSTANCE(htim->Instance));
/* Set the TIM state */
htim->State= HAL_TIM_STATE_BUSY;
/* Enable the Peripheral */
__HAL_TIM_ENABLE(htim);
/* Change the TIM state*/
htim->State= HAL_TIM_STATE_READY;
/* Return function status */
return HAL_OK;
}
/**
* @brief Starts the TIM Base generation in interrupt mode.
* @param htim: pointer to a TIM_HandleTypeDef structure that contains
* the configuration information for TIM module.
* @retval HAL status
*/
HAL_StatusTypeDef HAL_TIM_Base_Start_IT(TIM_HandleTypeDef *htim)
{
/* Check the parameters */
assert_param(IS_TIM_INSTANCE(htim->Instance));
/* Enable the TIM Update interrupt */
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
/* Enable the Peripheral */
__HAL_TIM_ENABLE(htim);
/* Return function status */
return HAL_OK;
}
/**
* @brief Starts the PWM signal generation.
* @param htim: pointer to a TIM_HandleTypeDef structure that contains
* the configuration information for TIM module.
* @param Channel: TIM Channels to be enabled.
* This parameter can be one of the following values:
* @arg TIM_CHANNEL_1: TIM Channel 1 selected
* @arg TIM_CHANNEL_2: TIM Channel 2 selected
* @arg TIM_CHANNEL_3: TIM Channel 3 selected
* @arg TIM_CHANNEL_4: TIM Channel 4 selected
* @retval HAL status
*/
HAL_StatusTypeDef HAL_TIM_PWM_Start(TIM_HandleTypeDef *htim, uint32_t Channel)
{
/* Check the parameters */
assert_param(IS_TIM_CCX_INSTANCE(htim->Instance, Channel));
/* Enable the Capture compare channel */
TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
if(IS_TIM_ADVANCED_INSTANCE(htim->Instance) != RESET)
{
/* Enable the main output */
__HAL_TIM_MOE_ENABLE(htim);
}
/* Enable the Peripheral */
__HAL_TIM_ENABLE(htim);
/* Return function status */
return HAL_OK;
}
其中的核心代码分别是:
__HAL_TIM_ENABLE(htim);
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
__HAL_TIM_ENABLE(htim);
TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE(htim);
那么我们可以不用hal库提供给我们的启动函数,直接写三句话,就实现了定时器中断+PWM功能了。
__HAL_TIM_ENABLE_IT(htim, TIM_IT_UPDATE);
TIM_CCxChannelCmd(htim->Instance, Channel, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE(htim);
完整代码如下:
TIM_HandleTypeDef TIM2_Handler;
TIM_OC_InitTypeDef TIM2_CH2Handler;
static void MX_TIM2_Init(void)
{
__HAL_RCC_TIM2_CLK_ENABLE();
HAL_NVIC_SetPriority(TIM2_IRQn, 1, 3);
HAL_NVIC_EnableIRQ(TIM2_IRQn);
TIM2_Handler.Instance = TIM2;
TIM2_Handler.Init.Prescaler = 10800;
TIM2_Handler.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM2_Handler.Init.Period = 20000;
TIM2_Handler.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
HAL_TIM_PWM_Init(&TIM2_Handler);
TIM2_CH2Handler.OCMode = TIM_OCMODE_PWM1;
TIM2_CH2Handler.Pulse = 10000;
TIM2_CH2Handler.OCPolarity = TIM_OCPOLARITY_HIGH;
TIM2_CH2Handler.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&TIM2_Handler, &TIM2_CH2Handler, TIM_CHANNEL_2) ;
__HAL_TIM_ENABLE_IT(&TIM2_Handler, TIM_IT_UPDATE);
TIM_CCxChannelCmd(TIM2_Handler.Instance, TIM_CHANNEL_2, TIM_CCx_ENABLE);
__HAL_TIM_ENABLE(&TIM2_Handler);
}
void TIM2_IRQHandler(void)
{
HAL_TIM_IRQHandler(&TIM2_Handler);
}
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if (htim == (&TIM2_Handler))
{
printf("enter irq\r\n");
}
}
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef *htim)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
GPIO_InitStruct.Pin = GPIO_PIN_1;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}