单片机学习小组
直播中

肖耳朵

12年用户 557经验值
私信 关注

如何用一个定时器的四个通道独立地输出四路PWM脉冲呢

输出比较Toggle模式的时序是什么样的?
如何用一个定时器的四个通道独立地输出四路PWM脉冲呢?

回帖(1)

池冰龄

2022-1-25 15:27:58
  本文以STM32F405单片机的TIM4为例,介绍如何用一个定时器的四个通道独立地输出四路PWM脉冲。
  STM32单片机的定时器的PWM模式和输出比较翻转(Toggle)模式都能输出PWM脉冲。PWM模式下,一个定时器的只能输出四路频率相同(占空比可以不同)的PWM波。然而在实际应用中可能需要单片机输出PWM控制多个电机,使用不同的定时器输出PWM脉冲比较浪费硬件资源,这时候就可以采用输出比较 Toggle 模式使一个定时器的四个通道独立(频率和占空比都可以不同)地输出四路PWM脉冲。
输出比较 Toggle 模式介绍

  将定时器配置成输出比较 Toggle 模式后,使定时器向上计数,当定时器的计数寄存器(CNT)中的数值等于捕获比较寄存器(CCRx,x对应通道编号)时,对应的引脚输出电平发生反转,同时状态寄存器中的CCxIF中断标志位置1,如果使能了捕获比较中断,则单片机进入中断服务程序。输出比较 Toggle 模式的时序请见下图:
输出比较 Toggle 模式输出PWM脉冲

  在定时器输出PWM脉冲的应用中,定时器是在不停地计数。如果需要引脚停止输出PWM脉冲,则失能对应通道的输出和捕获比较中断,详细方法请见下面代码中的TIM4CH1OutControl函数;如果需要引脚输出PWM脉冲,则使能对应通道的输出和捕获比较中断,并且在中断中改变捕获比较寄存器(CCRx)中的值。下面简要介绍定时器输出比较 Toggle 模式输出PWM脉冲的过程。


  • 定时器的计数寄存器(CNT)不停地从0到65534计数,计数65535次后从0再计数到65534,循环往复。
  • 当计数寄存器(CNT)中的数值等于捕获比较寄存器(CCRx)中的数值时,引脚电平翻转一次,同时进入相应的捕获比较中断服务程序。
  • 在中断程序中,将捕获比较寄存器(CCRx)的值读出并加上一个数(与PWM的频率和占空比有关)后,再写入捕获比较寄存器(CCRx)。
  • 这样当计数寄存器的数值再次等于捕获比较寄存器的数值时,重复 2、3 过程。这样引脚输出的电平不断翻转,就产生了PWM脉冲。

  需要注意的是,单片机进入两次捕获比较中断服务程序才输出一个PWM脉冲。如果通过驱动器控制步进电机时需要计算旋转角度,则进两次中断记一次脉冲,步进电机相应地走一个步进角,脉冲数乘以步进角就能得到步进电机旋转角度。
代码

下面的代码配置STM32F405单片机的TIM4的四个通道输出不同频率的PWM脉冲,占空比均为50%。

/**
* @description: Configure TIM4 CH1-CH4 with toggle mode.
* @param: none.
* @return: none.
*/
void BoardController::Timer4FourChannelPWMInit(void)
{
        GPIO_InitTypeDef GPIO_InitStructure;
        TIM_TimeBaseInitTypeDef  TIM_TimeBaseStructure;
        TIM_OCInitTypeDef  TIM_OCInitStructure;
        NVIC_InitTypeDef NVIC_InitStructure;
       
        RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);            
        RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);
       
        GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
        GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
        GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
        GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
        //TIM4_CH1 CH2 CH3 CH4
        GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7|GPIO_Pin_8|GPIO_Pin_9;
        GPIO_Init(GPIOB, &GPIO_InitStructure);       
        // 输出比较复用引脚映射到TIM4
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource6, GPIO_AF_TIM4);// CH1
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource7, GPIO_AF_TIM4);// CH2
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource8, GPIO_AF_TIM4);// CH3
        GPIO_PinAFConfig(GPIOB, GPIO_PinSource9, GPIO_AF_TIM4);// CH4
       
        TIM_TimeBaseStructure.TIM_Prescaler = 84-1;   // 定时器计数频率 1MHz
        TIM_TimeBaseStructure.TIM_Period = 65535-1;   // 自动从装载值,定时器从0到65534计数65535次      
        TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
        TIM_TimeBaseStructure.TIM_ClockDivision = 0;
        TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
       
        TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle; // 定时器配置成输出比较翻转(Toggle)模式
        TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
        TIM_OCInitStructure.TIM_Pulse = 0;       
        TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;// 通道引脚默认输出高电平
        TIM_OC1Init(TIM4, &TIM_OCInitStructure);
        TIM_OC2Init(TIM4, &TIM_OCInitStructure);
        TIM_OC3Init(TIM4, &TIM_OCInitStructure);
        TIM_OC4Init(TIM4, &TIM_OCInitStructure);       
       
        TIM_OC1PreloadConfig(TIM4,TIM_OCPreload_Disable);
        TIM_OC2PreloadConfig(TIM4,TIM_OCPreload_Disable);
        TIM_OC3PreloadConfig(TIM4,TIM_OCPreload_Disable);
        TIM_OC4PreloadConfig(TIM4,TIM_OCPreload_Disable);
        // 先失能输出比较通道,失能捕获比较中断,默认不输出PWM,后面有函数使能通道和中断,使单片机输出PWM
        TIM_ITConfig(TIM4,TIM_IT_CC1|TIM_IT_CC2|TIM_IT_CC3|TIM_IT_CC4,DISABLE);
        TIM_CCxCmd(TIM4,TIM_Channel_1,TIM_CCx_Disable);
        TIM_CCxCmd(TIM4,TIM_Channel_2,TIM_CCx_Disable);
        TIM_CCxCmd(TIM4,TIM_Channel_3,TIM_CCx_Disable);
        TIM_CCxCmd(TIM4,TIM_Channel_4,TIM_CCx_Disable);
        TIM_Cmd(TIM4,ENABLE);
       
        NVIC_InitStructure.NVIC_IRQChannel = TIM4_IRQn;
        NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
        NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
        NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
        NVIC_Init(&NVIC_InitStructure);               
}
/**
* @description: Control TIM4 output PWM.
* @param: operation 1 enable output, 0 disable output.
* @return: none.
*/
void BoardController::TIM4CH1OutControl(unsigned char operation)
{// 还有三个函数控制CH2、CH3、CH4,它们和此函数的区别仅在于调用库函数的参数的通道号,不再赘述.
        if(!operation)
        {// operation = 0 时,失能输出比较通道,失能捕获比较中断.
                TIM_CCxCmd(TIM4,TIM_Channel_1,TIM_CCx_Disable);
                TIM_ITConfig(TIM4,TIM_IT_CC1,DISABLE);       
        }
        else
        {// operation = 1 时,使能输出比较通道,使能捕获比较中断.
                TIM_CCxCmd(TIM4,TIM_Channel_1,TIM_CCx_Enable);
                TIM_ITConfig(TIM4,TIM_IT_CC1,ENABLE);               
        }
}
#define TIM4_CNT_FREQ 1000000 // 定时器计数频率 1MHz.
#define TIM4_CH1_PWM_FREQ 400 // CH1 输出频率 400Hz.
#define TIM4_CH2_PWM_FREQ 300 // CH2 输出频率 300Hz.
#define TIM4_CH3_PWM_FREQ 200 // CH3 输出频率 200Hz.
#define TIM4_CH4_PWM_FREQ 100 // CH4 输出频率 100Hz.
// 计算CCRx寄存器的步进值,占空比都是50%,若要改变占空比,则设置不同的步进值.
#define TIM4_CH1_CCR_INC TIM4_CNT_FREQ/TIM4_CH1_PWM_FREQ/2
#define TIM4_CH2_CCR_INC TIM4_CNT_FREQ/TIM4_CH2_PWM_FREQ/2
#define TIM4_CH3_CCR_INC TIM4_CNT_FREQ/TIM4_CH3_PWM_FREQ/2
#define TIM4_CH4_CCR_INC TIM4_CNT_FREQ/TIM4_CH4_PWM_FREQ/2
/**
* @description: TIM4 interrupt service routine.
* @param: none.
* @return: none.
*/
void TIM4_IRQHandler(void)
{
        unsigned short ccr;// 因为CCRx寄存器只有低16位有用,所以定义unsigned short型变量.
        if(TIM_GetITStatus(TIM4,TIM_IT_CC1) == SET)
        {
                static unsigned char cnt1 = 0x00;// 记录进入中断的次数.
                static unsigned int ch1_pulse_number =0x00;// 记录输出脉冲个数.
                TIM_ClearITPendingBit(TIM4,TIM_IT_CC1);
                cnt1++;
                ccr = TIM4->CCR1;
                TIM4->CCR1 = ccr+TIM4_CH1_CCR_INC;// 一直累加就行,让变量自动溢出.
                if(cnt1&0x01)//进两次中断完成一个PWM周期.
                {
                        ch1_pulse_number++; // 计算输出脉冲个数.
                        if(ch1_pulse_number>= target_pulse)
                        {// 如果输出脉冲个数达到目标脉冲个数,则关闭CH1的PWM输出.
                         // 变量target_pulse需要根据实际需求计算,这里代码中没有定义.
                                ch1_pulse_number= 0;
                                board.TIM4CH1OutControl(0);
                        }
                }
        }
        if(TIM_GetITStatus(TIM4,TIM_IT_CC2) == SET)
        {
                static unsigned char cnt2 = 0x00;// 记录进入中断的次数.
                static unsigned int ch2_pulse_number =0x00;// 记录输出脉冲个数.
                TIM_ClearITPendingBit(TIM4,TIM_IT_CC2);
                cnt2++;
                ccr = TIM4->CCR2;
                TIM4->CCR2 = ccr+TIM4_CH2_CCR_INC;
                if(cnt2&0x01)
                {
                        ch2_pulse_number++;
                        // 计算步进电机旋转角度,可以根据旋转方向再加上正负的定义.
                        // 代码中 angle 和 step_angle 未定义。
                        angle = ch2_pulse_number*step_angle;
                }
        }
        if(TIM_GetITStatus(TIM4,TIM_IT_CC3) == SET)
        {
                static unsigned char cnt3 = 0x00;// 记录进入中断的次数.
                static unsigned int ch3_pulse_number =0x00;// 记录输出脉冲个数.
                TIM_ClearITPendingBit(TIM4,TIM_IT_CC3);
                cnt3++;
                ccr = TIM4->CCR3;
                TIM4->CCR3 = ccr+TIM4_CH3_CCR_INC;
                if(cnt3&0x01)
                {
                        ch3_pulse_number++;
                        //类似前面的代码,按实际需求写.
                }
        }
        if(TIM_GetITStatus(TIM4,TIM_IT_CC4) == SET)
        {
                static unsigned char cnt4 = 0x00;// 记录进入中断的次数
                static unsigned int ch4_pulse_number =0x00;// 记录输出脉冲个数.
                TIM_ClearITPendingBit(TIM4,TIM_IT_CC4);
                cnt4++;
                ccr = TIM4->CCR4;
                TIM4->CCR4 = ccr+TIM4_CH4_CCR_INC;
                if(cnt4&0x01)//进两次中断完成一个PWM周期
                {
                        ch4_pulse_number++;
                        //类似前面的代码,按实际需求写.
                }               
        }
}
举报

更多回帖

发帖
×
20
完善资料,
赚取积分