一个项目需要使用PWM调制四路激光,要求四路PWM的频率和占空比均可调(频率1~50000Hz,占空比0~100%)。如果是频率固定,只是要求占空比可调的话,使用一个定时器四路输出通道即可,但该项目要求频率也可调,因此定时器的时钟频率和计数周期均需调整,就需要用到四个独立的定时器。
我项目中使用的单片机为STM32F103RCT6,设计使用四个通用定时器输出四路PWM,定时器及通道分别为TIM2_CH3、TIM3_CH4、TIM4_CH3、TIM5_CH2。定时器的程序如下(HAL库):
1、定时器GPIO外设配置
/**
* @brief 定时器GPIO外设配置
* @param 无
* @retval 无
*/
static void TIMx_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
/* 通用定时器GPIO外设时钟使能 */
__HAL_RCC_GPIOA_CLK_ENABLE();
__HAL_RCC_GPIOB_CLK_ENABLE();
/*TIM2_CH3功能引脚IO初始化*/
GPIO_InitStruct.Pin = GPIO_PIN_2; //引脚
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; //模式为复用推挽输出
GPIO_InitStruct.Pull = GPIO_PULLDOWN; //下拉
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH; //高速
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); //PA2初始化
/*TIM3_CH4功能引脚IO初始化*/
GPIO_InitStruct.Pin = GPIO_PIN_1; //引脚
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); //PB1初始化
/*TIM4_CH3功能引脚IO初始化*/
GPIO_InitStruct.Pin = GPIO_PIN_8; //引脚
HAL_GPIO_Init(GPIOB, &GPIO_InitStruct); //PB8初始化
/*TIM5_CH2功能引脚IO初始化*/
GPIO_InitStruct.Pin = GPIO_PIN_1; //引脚
HAL_GPIO_Init(GPIOA, &GPIO_InitStruct); //PA1初始化
}
2、定时器配置
TIM_HandleTypeDef TIM2_TimeBaseStructure;
TIM_HandleTypeDef TIM3_TimeBaseStructure;
TIM_HandleTypeDef TIM4_TimeBaseStructure;
TIM_HandleTypeDef TIM5_TimeBaseStructure;
static void TIM_PWMOUTPUT_Config(void)
{
TIM_OC_InitTypeDef TIM_OCInitStructure;
int tim_per=10000;//定时器周期
/*使能定时器*/
__HAL_RCC_TIM2_CLK_ENABLE();
__HAL_RCC_TIM3_CLK_ENABLE();
__HAL_RCC_TIM4_CLK_ENABLE();
__HAL_RCC_TIM5_CLK_ENABLE();
/*TIM2时基配置*/
TIM2_TimeBaseStructure.Instance = TIM2;
/* 累计 TIM_Period个后产生一个更新或者中断*/
//当定时器从0计数到10000,即为10000次,为一个定时周期(默认初始配置)
TIM2_TimeBaseStructure.Init.Period = tim_per-1;
// 通用控制定时器时钟源TIMxCLK = 72MHz
// 设定定时器频率为=TIMxCLK/(TIM_Prescaler+1)=1MHz(默认初始配置)
TIM2_TimeBaseStructure.Init.Prescaler = 72-1;
/*计数方式*/
TIM2_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
/*采样时钟分频,时钟分频因子 ,配置死区时间时需要用到*/
TIM2_TimeBaseStructure.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
/*初始化定时器*/
HAL_TIM_Base_Init(&TIM2_TimeBaseStructure);
/*TIM3时基配置*/
TIM3_TimeBaseStructure.Instance = TIM3;
TIM3_TimeBaseStructure.Init.Period = tim_per-1;
TIM3_TimeBaseStructure.Init.Prescaler = 72-1;
TIM3_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM3_TimeBaseStructure.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&TIM3_TimeBaseStructure);
/*TIM4时基配置*/
TIM4_TimeBaseStructure.Instance = TIM4;
TIM4_TimeBaseStructure.Init.Period = tim_per-1;
TIM4_TimeBaseStructure.Init.Prescaler = 72-1;
TIM4_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM4_TimeBaseStructure.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&TIM4_TimeBaseStructure);
/*TIM5时基配置*/
TIM5_TimeBaseStructure.Instance = TIM5;
TIM5_TimeBaseStructure.Init.Period = tim_per-1;
TIM5_TimeBaseStructure.Init.Prescaler = 72-1;
TIM5_TimeBaseStructure.Init.CounterMode = TIM_COUNTERMODE_UP;
TIM5_TimeBaseStructure.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;
HAL_TIM_Base_Init(&TIM5_TimeBaseStructure);
/*PWM模式配置*/
TIM_OCInitStructure.OCMode = TIM_OCMODE_PWM1;//配置为PWM模式1
TIM_OCInitStructure.Pulse = tim_per;//默认占空比为100%
TIM_OCInitStructure.OCFastMode = TIM_OCFAST_DISABLE;
/*当定时器计数值小于CCR1_Val时为低电平,
因为我的硬件上经过三极管反了一下,因此选择低电平,如直接输出可配置为高电平*/
TIM_OCInitStructure.OCPolarity = TIM_OCPOLARITY_LOW;
/*配置PWM通道*/
HAL_TIM_PWM_ConfigChannel(&TIM2_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_3);
HAL_TIM_PWM_ConfigChannel(&TIM3_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_4);
HAL_TIM_PWM_ConfigChannel(&TIM4_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_3);
HAL_TIM_PWM_ConfigChannel(&TIM5_TimeBaseStructure, &TIM_OCInitStructure, TIM_CHANNEL_2);
/*开始输出PWM*/
HAL_TIM_PWM_Start(&TIM2_TimeBaseStructure,TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&TIM3_TimeBaseStructure,TIM_CHANNEL_4);
HAL_TIM_PWM_Start(&TIM4_TimeBaseStructure,TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&TIM5_TimeBaseStructure,TIM_CHANNEL_2);
}
3、定时器初始化程序
在主程序内调用该函数进行定时器初始化配置。
/**
* @brief 初始化控制通用定时器
* @param 无
* @retval 无
*/
void TIMx_Configuration(void)
{
TIMx_GPIO_Config();
TIM_PWMOUTPUT_Config();
}
4、PWM频率和占空比设置函数
可在其他功能函数中调用该函数进行四路PWM频率和占空比配置。
程序中的定时器时钟预分频和计数周期等参数计算参考自:STM32输出 PWM频率 占空比 连续可调~~
/**
* @brief 设置PWM频率和占空比
* @param htim 定时器句柄结构体
* Channel 通道 TIM_CHANNEL_x x=1..4
* FQ 频率 1..50000(Hz)
* Duty 占空比 0..100(%)
* @retval 无
*/
void TIMx_SetPWM_FQ_Duty(TIM_HandleTypeDef *htim,uint32_t Channel,uint32_t FQ,uint8_t Duty)
{
uint32_t temp32;
uint32_t uhTimerfrequency;
uint16_t uhTimerPeriod;
uint16_t uhTimerPrescaler;
uint16_t uhTimerPulse;
if (FQ >= 4000)
{
uhTimerfrequency = FQ; /* 定时器计时频率和PWM频率相等 */
uhTimerPrescaler = 1; /* 实际频率FQ较大时,TIMx设置分频为1(不分频) */
}
else
{
uhTimerfrequency = 4000; /* 实际频率FQ较小时,和4000Hz的频率比较,看相差几倍 */
uhTimerPrescaler = 4000 / FQ; /* 实际频率和4000相差的倍数作为TIM2的分频设置值 */
uhTimerfrequency = uhTimerPrescaler * FQ; /* TIMx分频后,计时周期变长,需要将uhTimerfrequency倍频 */
}
/* TIMx的周期要通过倍频uhTimerfrequency来计算,uhTimerPeriod = 72MHz / uhTimerfrequency */
temp32 = SystemCoreClock / uhTimerfrequency;
if (temp32 > 65535) temp32 = 65535;
uhTimerPeriod = (uint16_t) temp32;
if (Duty > 100) Duty = 100;
uhTimerPulse = uhTimerPeriod * Duty / 100;
__HAL_TIM_SET_PRESCALER(htim, uhTimerPrescaler-1);
__HAL_TIM_SET_AUTORELOAD(htim, uhTimerPeriod-1);
__HAL_TIM_SET_COMPARE(htim, Channel, uhTimerPulse);
}