STM32实现利用一个定时器通道控制任意个IO输出相同PWM波
摘要:本博客介绍了一种STM32利用一个定时器通道控制任意个IO输出相同PWM波的方法,本文所使用的硬件平台为STM32F407VET6,其他型号的平台实际使用时代码或许有所出入,但原理都是通用的。
实现原理:我们都知道STM32可以利用定时器通道从硬件上直接输出PWM波,一个定时器有四个通道,其中高级定时器每个通道有正反两个输出控制端口,也就是说一个定时器从硬件上最多可以同时输出8路PWM波(高级定时器是这样,其他的看具体定时器的结构框图),虽然这已经很多了,但笔者觉得还是不够啊,而且最重要的是这些硬件PWM波输出IO口都被硬件上定死了,从控制上自由度不够高啊,于是就有了本篇博客以及如下的代码。咳咳,回到实现原理上,因为一个定时器通道从硬件上实现输出占空比可调PWM波利用的就是CNT计数器以及捕获/比较寄存器(这个不清楚的好好看参考手册上的定时器框图),当CNT计数器计数到设定的自动重装的值时就会开始重新计数,而捕获/比较寄存器则是当CNT计数到设定的值后就会自动从硬件上进行电平翻转。本博客利用的就是这一点,因为实际上硬件输出PWM波进行电平翻转的时候也会产生对应的中断(使能对应中断的话),然后只要在中断服务函数中自行进行电平翻转,就可实现控制任意个IO输出相同PWM波的效果了(当然为了不让定时器通道向硬件上对应的IO口也输出PWM波要禁止定时器通道向绑定的硬件IO口真实输出PWM波)。
本篇博客以利用TIM1的通道1实现控制任意个IO输出相同PWM波为例,在正式展示代码前先非常感谢安富莱在我学习嵌入式上的帮助,其良好的代码风格也深深的影响着我,而且接下来的代码很大程度上也是在安富莱的相关源码基础上改变而来。
高级定时器TIM1和TIM8的结构框图如下
TIM定时器配置函数代码如下,实际使用时如果要实现本博客所示功能,传入的TIMx参数请使用硬件上具有捕获/比较寄存器的定时器
/*
*********************************************************************************************************
* 函 数 名: bsp_SetTIM_CC_ForInt
* 功能说明: 配置TIM和NVIC以及CCR,即可用于简单的定时中断,又可以对指定通道的捕获比较中断进行配置实现PWM的效果(实际并不会直接对引脚输出PWM,
说白了,利用该函数再配合bsp_TIM_PWM_ChangePulse函数以及中断服务函数,可以实现一个定时器通道控制任意个IO输出任意PWM的效果)。 中断服务程序由应用程序实现。
* 形 参: TIMx : 定时器
* _ucChannel : 通道1~4。
* _ulFreq : 定时频率 (Hz)。 0 表示关闭。
* _usPeriod : 自动重装的值,实际值应为次数减一。
* _PreemptionPriority : 中断优先级分组
* _SubPriority : 子优先级
* 返 回 值: 无
*********************************************************************************************************
*/
void bsp_SetTIM_CC_ForInt(TIM_TypeDef* TIMx, uint8_t _ucChannel, uint32_t _ulFreq, uint16_t _usPeriod, uint8_t _PreemptionPriority, uint8_t _SubPriority)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_OCInitTypeDef TIM_OCInitStructure;
uint16_t usPeriod;
uint16_t usPrescaler;
uint32_t uiTIMxCLK;
/* 使能TIM时钟 */
if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM9) || (TIMx == TIM10) || (TIMx == TIM11))
{
RCC_APB2PeriphClockCmd(bsp_GetRCCofTIM(TIMx), ENABLE);
}
else
{
RCC_APB1PeriphClockCmd(bsp_GetRCCofTIM(TIMx), ENABLE);
}
if (_ulFreq == 0)
{
TIM_Cmd(TIMx, DISABLE); /* 关闭定时输出 */
return;
}
/*-----------------------------------------------------------------------
system_stm32f4xx.c 文件中 void SetSysClock(void) 函数对时钟的配置如下:
HCLK = SYSCLK / 1 (AHB1Periph)
PCLK2 = HCLK / 2 (APB2Periph)
PCLK1 = HCLK / 4 (APB1Periph)
因为APB1 prescaler != 1, 所以 APB1上的TIMxCLK = PCLK1 x 2 = SystemCoreClock / 2;
因为APB2 prescaler != 1, 所以 APB2上的TIMxCLK = PCLK2 x 2 = SystemCoreClock;
APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13,TIM14
APB2 定时器有 TIM1, TIM8 ,TIM9, TIM10, TIM11
----------------------------------------------------------------------- */
if ((TIMx == TIM1) || (TIMx == TIM8) || (TIMx == TIM9) || (TIMx == TIM10) || (TIMx == TIM11))
{
/* APB2 定时器 */
uiTIMxCLK = SystemCoreClock;
}
else /* APB1 定时器 */
{
uiTIMxCLK = SystemCoreClock / 2;
}
usPrescaler = (uiTIMxCLK / _ulFreq) / (_usPeriod + 1) - 1; /* 分频系数 */
/* Time base configuration */
TIM_TimeBaseStructure.TIM_Period = usPeriod;
TIM_TimeBaseStructure.TIM_Prescaler = usPrescaler;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseStructure);
/*PWM模式配置*/
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;//配置为PWM模式1,先输出高电平,达到比较值的时候再改变电平
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Disable;//主输出失能
TIM_OCInitStructure.TIM_OutputNState = TIM_OutputNState_Disable;//互补输出失能
TIM_OCInitStructure.TIM_Pulse = 0;//设置占空比大小
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;//主输出有效值为高电平
if (_ucChannel == 1)
{
TIM_OC1Init(TIMx, &TIM_OCInitStructure);
// TIM_OC1PreloadConfig(TIMx, TIM_OCPreload_Enable);
/* TIM Interrupts enable */
TIM_ITConfig(TIMx, TIM_IT_CC1, ENABLE);
}
else if (_ucChannel == 2)
{
TIM_OC2Init(TIMx, &TIM_OCInitStructure);
// TIM_OC2PreloadConfig(TIMx, TIM_OCPreload_Enable);
/* TIM Interrupts enable */
TIM_ITConfig(TIMx, TIM_IT_CC2, ENABLE);
}
else if (_ucChannel == 3)
{
TIM_OC3Init(TIMx, &TIM_OCInitStructure);
// TIM_OC3PreloadConfig(TIMx, TIM_OCPreload_Enable);
TIM_ITConfig(TIMx, TIM_IT_CC3, ENABLE);
}
else if (_ucChannel == 4)
{
TIM_OC4Init(TIMx, &TIM_OCInitStructure);
// TIM_OC4PreloadConfig(TIMx, TIM_OCPreload_Enable);
TIM_ITConfig(TIMx, TIM_IT_CC4, ENABLE);
}
/* TIM Interrupts enable */
TIM_ITConfig(TIMx, TIM_IT_Update, ENABLE);
TIM_ARRPreloadConfig(TIMx, ENABLE);
/* TIMx enable counter */
TIM_Cmd(TIMx, ENABLE);
/* 配置TIM定时更新以及捕获/比较中断 (Update) */
{
NVIC_InitTypeDef NVIC_InitStructure; /* 中断结构体在 misc.h 中定义 */
uint8_t irq = 0; /* 中断号, 定义在 stm32f4xx.h */
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = _PreemptionPriority;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = _SubPriority;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
if ((TIMx == TIM1) || (TIMx == TIM10))
irq = TIM1_UP_TIM10_IRQn;
else if (TIMx == TIM2)
irq = TIM2_IRQn;
else if (TIMx == TIM3)
irq = TIM3_IRQn;
else if (TIMx == TIM4)
irq = TIM4_IRQn;
else if (TIMx == TIM5)
irq = TIM5_IRQn;
else if (TIMx == TIM6)
irq = TIM6_DAC_IRQn;
else if (TIMx == TIM7)
irq = TIM7_IRQn;
else if ((TIMx == TIM8) || (TIMx == TIM13))
irq = TIM8_UP_TIM13_IRQn;
else if (TIMx == TIM9)
irq = TIM1_BRK_TIM9_IRQn;
else if (TIMx == TIM11)
irq = TIM1_TRG_COM_TIM11_IRQn;
else if (TIMx == TIM12)
irq = TIM8_BRK_TIM12_IRQn;
else if (TIMx == TIM14)
irq = TIM8_TRG_COM_TIM14_IRQn;
NVIC_InitStructure.NVIC_IRQChannel = irq;
NVIC_Init(&NVIC_InitStructure);
/* 高级定时器TIM1,TIM8需要单独配置捕获/比较中断函数 */
if (TIMx == TIM1) {
irq = TIM1_CC_IRQn;
NVIC_InitStructure.NVIC_IRQChannel = irq;
NVIC_Init(&NVIC_InitStructure);
}
else if (TIMx == TIM8) {
irq = TIM8_CC_IRQn;
NVIC_InitStructure.NVIC_IRQChannel = irq;
NVIC_Init(&NVIC_InitStructure);
}
}
}
补上上示代码依赖的函数
/*
*********************************************************************************************************
* 函 数 名: bsp_GetRCCofTIM
* 功能说明: 根据TIM 得到RCC寄存器
* 形 参:无
* 返 回 值: TIM外设时钟名
*********************************************************************************************************
*/
uint32_t bsp_GetRCCofTIM(TIM_TypeDef* TIMx)
{
uint32_t rcc = 0;
/*
APB1 定时器有 TIM2, TIM3 ,TIM4, TIM5, TIM6, TIM7, TIM12, TIM13, TIM14
APB2 定时器有 TIM1, TIM8 ,TIM9, TIM10, TIM11
*/
if (TIMx == TIM1)
{
rcc = RCC_APB2Periph_TIM1;
}
else if (TIMx == TIM8)
{
rcc = RCC_APB2Periph_TIM8;
}
else if (TIMx == TIM9)
{
rcc = RCC_APB2Periph_TIM9;
}
else if (TIMx == TIM10)
{
rcc = RCC_APB2Periph_TIM10;
}
else if (TIMx == TIM11)
{
rcc = RCC_APB2Periph_TIM11;
}
/* 下面是 APB1时钟 */
else if (TIMx == TIM2)
{
rcc = RCC_APB1Periph_TIM2;
}
else if (TIMx == TIM3)
{
rcc = RCC_APB1Periph_TIM3;
}
else if (TIMx == TIM4)
{
rcc = RCC_APB1Periph_TIM4;
}
else if (TIMx == TIM5)
{
rcc = RCC_APB1Periph_TIM5;
}
else if (TIMx == TIM6)
{
rcc = RCC_APB1Periph_TIM6;
}
else if (TIMx == TIM7)
{
rcc = RCC_APB1Periph_TIM7;
}
else if (TIMx == TIM12)
{
rcc = RCC_APB1Periph_TIM12;
}
else if (TIMx == TIM13)
{
rcc = RCC_APB1Periph_TIM13;
}
else if (TIMx == TIM14)
{
rcc = RCC_APB1Periph_TIM14;
}
return rcc;
}
调整占空比的函数如下所示
/**
* @brief 改变PWM输出高电平的时间(占空比)
* @param 定时器,通道及输出PWM的高电平的时间(并非真实时间而是电平翻转时对应的CNT值)
* @retval 无
*/
void bsp_TIM_PWM_ChangePulse(TIM_TypeDef* TIMx, uint8_t _ucChannel, uint16_t _usPulse)
{
if (_ucChannel == 1) {
TIM_SetCompare1(TIMx, _usPulse);
}
else if (_ucChannel == 2) {
TIM_SetCompare2(TIMx, _usPulse);
}
else if (_ucChannel == 3) {
TIM_SetCompare3(TIMx, _usPulse);
}
else if (_ucChannel == 4) {
TIM_SetCompare4(TIMx, _usPulse);
}
}
中断服务函数如下,因为本篇博客以STM32F407VET6平台上的TIM1的通道1为示例,如果使用其他型号平台及定时器通道的需要改成相对应的中断服务函数
/**
* @brief TIM1定时中断服务程序
* @param 无
* @retval 无
*/
void TIM1_UP_TIM10_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1, TIM_IT_Update) != RESET) {
/* 清除标志 */
TIM_ClearITPendingBit(TIM1, TIM_IT_Update);
/* 用户程序 */
//此处可以设置指定的IO输出高电平
}
}
void TIM1_CC_IRQHandler(void)
{
if(TIM_GetITStatus(TIM1, TIM_IT_CC1) != RESET) {
/* 清除标志 */
TIM_ClearITPendingBit(TIM1, TIM_IT_CC1);
/* 用户程序 */
//此处可以设置指定的IO输出低电平
}
}
实际使用Demo示例
int main(void)
{
bsp_SetTIM_CC_ForInt(TIM1, 1, 1000, 1999, 2, 0); /* 配置TIM1的通道1,定时中断频率为1kHz,计数的重装值为1999,中断优先级分组2,子优先级0 */
for(;;) {
bsp_TIM_PWM_ChangePulse(TIM1, 1, 999) /* 将占空比改为50% */
DelayMs(1000);
bsp_TIM_PWM_ChangePulse(TIM1, 1, 199) /* 将占空比改为10% */
DelayMs(1000);
/* 输出PWM波的IO口由中断服务函数中的用户程序部分自行决定 */
}
}