在嵌入式开发领域,定时器是最基础也最核心的外设之一。STM32F407作为STMicroelectronics推出的高性能Cortex-M4内核微控制器,其定时器功能尤为强大。今天我们就来深入探讨如何配置STM32F407的基本定时器,以及如何实现PWM输出功能。
对于嵌入式开发者来说,掌握定时器的配置是基本功。无论是简单的延时功能,还是复杂的PWM波形生成,都需要对定时器有深入理解。STM32F407提供了多达17个定时器,包括基本定时器(TIM6/TIM7)、通用定时器(TIM2-TIM5)和高级定时器(TIM1/TIM8)等。本文将重点介绍基本定时器的配置方法,并延伸讲解PWM的配置过程。
STM32F407的定时器系统相当复杂,我们先来了解其基本架构。基本定时器TIM6和TIM7是16位定时器,只能向上计数,没有外部输入/输出通道,主要用于产生基础时基或DAC触发信号。
每个定时器都包含以下关键组件:
在配置定时器前,必须先正确配置系统时钟。STM32F407默认使用内部16MHz RC振荡器(HSI),但为了获得更精确的定时,我们通常会使用外部8MHz晶振(HSE)并通过PLL倍频到168MHz系统时钟。
c复制// 系统时钟配置示例
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};
// 启用HSE振荡器
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 336;
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2;
RCC_OscInitStruct.PLL.PLLQ = 7;
HAL_RCC_OscConfig(&RCC_OscInitStruct);
// 配置系统时钟
RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
|RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV4;
RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV2;
HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_5);
注意:APB1定时器时钟是APB1总线时钟的2倍,当APB1预分频系数不为1时,定时器时钟频率会翻倍。例如APB1时钟为42MHz(168MHz/4),定时器时钟实际为84MHz。
配置基本定时器(TIM6/TIM7)的一般步骤如下:
c复制// 定时器初始化代码示例
TIM_HandleTypeDef htim6;
void MX_TIM6_Init(void)
{
htim6.Instance = TIM6;
htim6.Init.Prescaler = 8399; // 预分频值
htim6.Init.CounterMode = TIM_COUNTERMODE_UP;
htim6.Init.Period = 9999; // 自动重装载值
htim6.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
if (HAL_TIM_Base_Init(&htim6) != HAL_OK)
{
Error_Handler();
}
}
定时器的时间计算公式为:
定时时间 = (Prescaler + 1) * (Period + 1) / TimerClockFrequency
例如,我们需要配置一个1ms的定时中断:
c复制// 1ms定时中断配置
htim6.Init.Prescaler = 8399; // 84000000/(8399+1) = 10kHz
htim6.Init.Period = 9; // (9+1)/10kHz = 1ms
要使用定时器中断,还需要配置NVIC:
c复制// 定时器中断配置
void HAL_TIM_Base_MspInit(TIM_HandleTypeDef* htim_base)
{
if(htim_base->Instance==TIM6)
{
// 使能TIM6时钟
__HAL_RCC_TIM6_CLK_ENABLE();
// 配置TIM6中断
HAL_NVIC_SetPriority(TIM6_DAC_IRQn, 0, 0);
HAL_NVIC_EnableIRQ(TIM6_DAC_IRQn);
}
}
// 中断服务函数
void TIM6_DAC_IRQHandler(void)
{
HAL_TIM_IRQHandler(&htim6);
}
// 回调函数
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
if(htim->Instance == TIM6)
{
// 定时中断处理代码
}
}
PWM(Pulse Width Modulation)即脉冲宽度调制,通过调节脉冲的占空比来控制输出功率。STM32的通用定时器和高级定时器都支持PWM输出。
PWM的关键参数:
以TIM3_CH1(PA6)为例,配置PWM输出的步骤如下:
c复制TIM_HandleTypeDef htim3;
TIM_OC_InitTypeDef sConfigOC = {0};
void MX_TIM3_Init(void)
{
// 定时器基础配置
htim3.Instance = TIM3;
htim3.Init.Prescaler = 83; // 84MHz/84 = 1MHz
htim3.Init.CounterMode = TIM_COUNTERMODE_UP;
htim3.Init.Period = 999; // 1MHz/1000 = 1kHz PWM频率
htim3.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
htim3.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_ENABLE;
if (HAL_TIM_PWM_Init(&htim3) != HAL_OK)
{
Error_Handler();
}
// PWM通道配置
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500; // 初始占空比50%
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1) != HAL_OK)
{
Error_Handler();
}
// GPIO配置
HAL_TIM_MspPostInit(&htim3);
}
// GPIO和时钟配置
void HAL_TIM_PWM_MspInit(TIM_HandleTypeDef* htim_pwm)
{
if(htim_pwm->Instance==TIM3)
{
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_GPIOA_CLK_ENABLE();
}
}
void HAL_TIM_MspPostInit(TIM_HandleTypeDef* htim)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(htim->Instance==TIM3)
{
// PA6配置为TIM3_CH1
GPIO_InitStruct.Pin = GPIO_PIN_6;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_NOPULL;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = GPIO_AF2_TIM3;
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
}
}
// 启动PWM
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
在实际应用中,我们经常需要动态调整PWM参数:
c复制// 改变PWM频率
__HAL_TIM_SET_AUTORELOAD(&htim3, new_period);
// 改变占空比
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, new_pulse);
// 或者使用HAL库函数
TIM_OC_InitTypeDef sConfigOC = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = new_pulse;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
HAL_TIM_PWM_ConfigChannel(&htim3, &sConfigOC, TIM_CHANNEL_1);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1);
检查时钟是否使能
检查GPIO配置
检查中断配置
检查定时器参数
无输出
频率不正确
占空比不正确
使用逻辑分析仪
使用调试器查看寄存器
使用HAL库错误回调
c复制void HAL_TIM_ErrorCallback(TIM_HandleTypeDef *htim)
{
// 定时器错误处理
}
对于高频PWM应用,可以使用DMA来动态更新PWM参数,减轻CPU负担:
c复制// 配置DMA从内存传输到TIMx_CCR1
hdma_tim3_ch1.Instance = DMA1_Stream4;
hdma_tim3_ch1.Init.Channel = DMA_CHANNEL_5;
hdma_tim3_ch1.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_tim3_ch1.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_tim3_ch1.Init.MemInc = DMA_MINC_ENABLE;
hdma_tim3_ch1.Init.PeriphDataAlignment = DMA_PDATAALIGN_HALFWORD;
hdma_tim3_ch1.Init.MemDataAlignment = DMA_MDATAALIGN_HALFWORD;
hdma_tim3_ch1.Init.Mode = DMA_CIRCULAR;
hdma_tim3_ch1.Init.Priority = DMA_PRIORITY_HIGH;
hdma_tim3_ch1.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
HAL_DMA_Init(&hdma_tim3_ch1);
// 关联DMA到TIM3
__HAL_LINKDMA(&htim3, hdma[TIM_DMA_ID_CC1], hdma_tim3_ch1);
// 启动DMA传输
HAL_TIM_PWM_Start_DMA(&htim3, TIM_CHANNEL_1, (uint32_t*)pwm_buffer, buffer_length);
对于电机控制等应用,可以使用高级定时器(TIM1/TIM8)的互补PWM输出功能:
c复制// 配置互补PWM
TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig = {0};
sConfigOC.OCMode = TIM_OCMODE_PWM1;
sConfigOC.Pulse = 500;
sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
sConfigOC.OCNPolarity = TIM_OCNPOLARITY_HIGH;
sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;
sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;
HAL_TIM_PWM_ConfigChannel(&htim1, &sConfigOC, TIM_CHANNEL_1);
// 配置死区时间
sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;
sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;
sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;
sBreakDeadTimeConfig.DeadTime = 54; // 约1us死区时间(假设定时器时钟为108MHz)
sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;
sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;
sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;
HAL_TIMEx_ConfigBreakDeadTime(&htim1, &sBreakDeadTimeConfig);
// 启动PWM
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);
对于需要超长定时的应用,可以将多个定时器级联使用:
c复制// 配置TIM2作为主定时器,TIM3作为从定时器
// TIM2更新事件触发TIM3
// TIM2配置
htim2.Instance = TIM2;
htim2.Init.Prescaler = 8399; // 84MHz/8400 = 10kHz
htim2.Init.Period = 9999; // 1秒
HAL_TIM_Base_Init(&htim2);
// TIM3配置
htim3.Instance = TIM3;
htim3.Init.Prescaler = 0;
htim3.Init.Period = 0xFFFF;
HAL_TIM_Base_Init(&htim3);
// 配置TIM3为从模式,由TIM2触发
sSlaveConfig.SlaveMode = TIM_SLAVEMODE_EXTERNAL1;
sSlaveConfig.InputTrigger = TIM_TS_ITR1; // TIM2触发TIM3
HAL_TIM_SlaveConfigSynchronization(&htim3, &sSlaveConfig);
// 启动定时器
HAL_TIM_Base_Start(&htim2);
HAL_TIM_Base_Start(&htim3);
在实际项目中,我发现合理使用定时器的各种高级功能可以大幅提升系统性能。比如使用DMA更新PWM参数可以实现无CPU干预的复杂波形生成,互补PWM配合死区控制可以安全驱动H桥电路,而定时器级联则能实现超长时间的精确定时。