STM32F4单定时器独立输出四路PWM脉冲

  本文以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脉冲的过程。

  1. 定时器的计数寄存器(CNT)不停地从0到65534计数,计数65535次后从0再计数到65534,循环往复。
  2. 当计数寄存器(CNT)中的数值等于捕获比较寄存器(CCRx)中的数值时,引脚电平翻转一次,同时进入相应的捕获比较中断服务程序。
  3. 在中断程序中,将捕获比较寄存器(CCRx)的值读出并加上一个数(与PWM的频率和占空比有关)后,再写入捕获比较寄存器(CCRx)。
  4. 这样当计数寄存器的数值再次等于捕获比较寄存器的数值时,重复 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++;
			//类似前面的代码,按实际需求写.
		}		
	}
}
  • 13
    点赞
  • 93
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值