TIM主从同步之主定时器多通道PWM独立驱动多个从定时器

研究目标

        目标任务为使用TIM输出PWM来驱动步进电机(一共三路PWM),其中的难点即为在利用尽可能少的资源的情况下,精确控制PWM脉冲数。之前尝试了最基础的即使用三个TIM,每个TIM使用一个CH输出PWM,且开启每个TIM的中断,在中断内作步数的自减,减到零即停止PWM输出。这样操作,能针对精确的步数来作一些梯形加减速等算法处理,但是使用下来发现,TIM的中断占用的资源太多,且TIM中断的打断导致整个程序的时序有些混乱。
        为了节省资源,首先想到的是使用DMA加TIM的方案,当时参考了这篇文章:https://www.amobbs.com/thread-5646984-1-1.html。但是在后续发现,我的步进电机的驱动器细分系数调的比较高,这样一来步数就会很大,这种方法会导致内存占用太大,详细的内容就不再介绍,因为最终也没有采用这种方法。
        所以继续换一种方法,即只使用一个定时器作为MASTER TIM,利用他的三个通道的PWM,独立驱动三个SLAVE TIM。

实现过程

        选取TIM2为MASTER TIM,先写出其三通道输出PWM的相关代码(使用STM32C8T6):

        【T2C2-PA1-X  T2C3-PA2-Y  T2C4-PA3-Z】

void Motor_PWM_Init(void)	//使用TIM2输出PWM  T2C2-PA1-X  T2C3-PA2-Y  T2C4-PA3-Z
{
	/* 
		MASTER TIM INIT BEGIN 
	*/
	/* MASTER TIM BASE */
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
	TIM_TimeBaseInitTypeDef TIM_InitType;
	TIM_InitType.TIM_Prescaler = PSC_DEFAULT;		//72M/144 
	TIM_InitType.TIM_Period    = ARR_DEFAULT;		//10KHZ(ARR 50)
	TIM_InitType.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_InitType.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInit(TIM2,&TIM_InitType);
	
	/* MASTER PWM GPIO */ //T2C2-PA1   T2C3-PA2   T2C4-PA3
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA|RCC_APB2Periph_AFIO,ENABLE);
	//直接复用即可,无需重映射
    GPIO_InitTypeDef GPIO_InitType;
	GPIO_InitType.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitType.GPIO_Mode  = GPIO_Mode_AF_PP;
	GPIO_InitType.GPIO_Pin   = GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3;
	GPIO_Init(GPIOA,&GPIO_InitType);
	GPIO_ResetBits(GPIOA,GPIO_Pin_1|GPIO_Pin_2|GPIO_Pin_3);
	
	/* MASTER TIM PWM*/
	TIM_OCInitTypeDef TIM_OCInitType;
	TIM_OCInitType.TIM_OCMode = TIM_OCMode_PWM1;
	TIM_OCInitType.TIM_Pulse  = ARR_DEFAULT/2;			//Duty 50
	TIM_OCInitType.TIM_OCPolarity  = TIM_OCPolarity_High;	//有效电平:高电平
	TIM_OCInitType.TIM_OutputState = TIM_OutputState_Enable;//比较输出使能
	TIM_OCInitType.TIM_OCIdleState = TIM_OCIdleState_Reset;	//空闲状态低电平
	TIM_OC2Init(TIM2,&TIM_OCInitType); //T2C2
	TIM_OC3Init(TIM2,&TIM_OCInitType); //T2C3
	TIM_OC4Init(TIM2,&TIM_OCInitType); //T2C4
	TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable);
	TIM_OC3PreloadConfig(TIM2, TIM_OCPreload_Enable);
	TIM_OC4PreloadConfig(TIM2, TIM_OCPreload_Enable);
	TIM_ARRPreloadConfig(TIM2, ENABLE);	
	
	//TIM_Cmd(TIM2,ENABLE);

 软仿结果成功:

        如此一来即可将其配置成MASTER TIM了,添加代码(该部分代码有问题,文章后续有分析,请勿直接使用):

	 TIM_SelectOutputTrigger(TIM2,TIM_TRGOSource_OC2Ref|TIM_TRGOSource_OC3Ref|TIM_TRGOSource_OC4Ref); 
	 TIM_SelectMasterSlaveMode(TIM2,TIM_MasterSlaveMode_Enable);	

        再来配置SLAVE TIM相关(TIM1\3\4):

	/* 
		SLAVE TIM INIT BEGIN 
	*/	
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3|RCC_APB1Periph_TIM4,ENABLE);
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
	TIM_InitType.TIM_Prescaler =  0;
	TIM_InitType.TIM_Period    =  0;
	TIM_InitType.TIM_CounterMode = TIM_CounterMode_Up;
	TIM_InitType.TIM_ClockDivision = TIM_CKD_DIV1;
	TIM_TimeBaseInit(TIM1,&TIM_InitType);TIM_TimeBaseInit(TIM3,&TIM_InitType);TIM_TimeBaseInit(TIM4,&TIM_InitType);	
	TIM_ARRPreloadConfig(TIM1, ENABLE);	TIM_ARRPreloadConfig(TIM3, ENABLE);	TIM_ARRPreloadConfig(TIM4, ENABLE);	
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);
	NVIC_InitTypeDef TIM_NVIC_InitType;
	TIM_NVIC_InitType.NVIC_IRQChannelPreemptionPriority = 0;	//确保及时关闭
	TIM_NVIC_InitType.NVIC_IRQChannelSubPriority = 0;
	TIM_NVIC_InitType.NVIC_IRQChannelCmd = ENABLE;
	TIM_NVIC_InitType.NVIC_IRQChannel = TIM1_UP_IRQn;	NVIC_Init(&TIM_NVIC_InitType); 
	TIM_ITConfig(TIM1,TIM_IT_Update,ENABLE);			TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
	TIM_NVIC_InitType.NVIC_IRQChannel = TIM3_IRQn;		NVIC_Init(&TIM_NVIC_InitType);
	TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);			TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
	TIM_NVIC_InitType.NVIC_IRQChannel = TIM4_IRQn;		NVIC_Init(&TIM_NVIC_InitType);
	TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);			TIM_ClearITPendingBit(TIM4,TIM_IT_Update);	
	
	/* 配置触发源 */
	TIM_SelectInputTrigger(TIM1,TIM_TS_ITR1);TIM_SelectInputTrigger(TIM3,TIM_TS_ITR1);TIM_SelectInputTrigger(TIM4,TIM_TS_ITR1);
	TIM_SelectSlaveMode(TIM1,TIM_SlaveMode_External1);TIM_SelectSlaveMode(TIM3,TIM_SlaveMode_External1);TIM_SelectSlaveMode(TIM4,TIM_SlaveMode_External1);
	TIM_SelectMasterSlaveMode(TIM1,TIM_MasterSlaveMode_Enable);TIM_SelectMasterSlaveMode(TIM3,TIM_MasterSlaveMode_Enable);TIM_SelectMasterSlaveMode(TIM4,TIM_MasterSlaveMode_Enable);
	TIM1->CNT=0;TIM3->CNT=0;TIM4->CNT=0; 
	
	/* 更新为单脉冲模式 */
	TIM1->CR1 |= TIM_CR1_OPM;TIM3->CR1 |= TIM_CR1_OPM;TIM4->CR1 |= TIM_CR1_OPM;
	
	TIM_Cmd(TIM1,ENABLE);TIM_Cmd(TIM3,ENABLE);TIM_Cmd(TIM4,ENABLE);

        触发源表如下(以TIM1为例)

        原理即,把从定时器的CNT的触发源选择为对应的TIM的PWM输出通道,此番一来,通过设置TIM1\3\4的ARR,当PWM脉冲数触发了CNT达到ARR的值时,就进入中断来停止对应通道的PWM输出,下面展示TIM1的中断:

#define X_NO_OUTPUT {TIM2->CCR2 = 0;}
void TIM1_UP_IRQHandler(void)
{
	if(TIM_GetITStatus(TIM1,TIM_IT_Update) != RESET)
	{
		X_NO_OUTPUT;
		
		TIM_ClearITPendingBit(TIM1,TIM_IT_Update);
	}
}

        所以设置CNT的值就是来设置对应通道的PWM的脉冲的个数。

        其中注意的是,对从定时器我使用了单脉冲模式:

        即CNT达到ARR值后CNT计数直接停止,即TIM的使能位CEN清零。所以在启动PWM输出时记得也把对应从定时器的计数使能:

#define X_OUTPUT 	{TIM2->CCR2 = ARR_DEFAULT/2; TIM1->CR1 |= TIM_CR1_CEN;}

        软彷看一下效果:                

        结果实验成功。




        就当我认为逻辑无误时,我在之后多种情况的软彷过程中发现,有时候TIM1和TIM3的CNT并不会因为TIM2 CH2 CH3在输出PWM而增加,而且当TIM1和TIM3的CNT与ARR一致时,还需要等CH4(TIM2)的上升沿到达才会触发。

        这时通过观察TIM1\TIM3的CNT的变化情况,分析出的可能原因就是,实际上TIM1\3并没有被TIM2的CH2\3驱动而是被CH4驱动。这时回头看SLAVE TIM初始化的阶段,其实触发源的选择并没有具体到TIM2的哪个通道,而是只指向,连接到了TIM2。

        然后又看到了TIM2初始化的过程中的这行代码:

TIM_SelectOutputTrigger(TIM2,TIM_TRGOSource_OC2Ref|TIM_TRGOSource_OC3Ref|TIM_TRGOSource_OC4Ref); 

         我的本意是把TIM2的OC2\3\4都作为TRGO,但是再次带着疑问翻看寄存器手册,发现CR2 MMS看样子只能选择一个通道的信号作为输出的驱动触发信号

#define TIM_TRGOSource_OC1Ref              ((uint16_t)0x0040)
#define TIM_TRGOSource_OC2Ref              ((uint16_t)0x0050)
#define TIM_TRGOSource_OC3Ref              ((uint16_t)0x0060)
#define TIM_TRGOSource_OC4Ref              ((uint16_t)0x0070)
void TIM_SelectOutputTrigger(TIM_TypeDef* TIMx, uint16_t TIM_TRGOSource)
{
  /* Check the parameters */
  assert_param(IS_TIM_LIST7_PERIPH(TIMx));
  assert_param(IS_TIM_TRGO_SOURCE(TIM_TRGOSource));
  /* Reset the MMS Bits */
  TIMx->CR2 &= (uint16_t)~((uint16_t)TIM_CR2_MMS);
  /* Select the TRGO source */
  TIMx->CR2 |=  TIM_TRGOSource;
}

        所以我的TRGO选择的代码中,或运算后,TIM2->CR2的MMS只是被设置成了111,即OC4REF作为TRGO,所以在仿真时,TIM1\3的CNT只会在CH4的上升沿到达才会触发,所以符合实际现象,问题解决。




        那么难道就不能用这种方法吗?当然不是。

        根据实际控制情况来看,Z轴运动和XY运动肯定不能同时进行,所以Z轴运动时,可以将TIM2的输出的触发信号选择为CH4,且CH4开始输出PWM,此时TIM4的ARR就设置成需要的脉冲数,而TIM1\3的ARR保持为0,CH2\3不输出PWM即可。

        在XY轴运动时,首先CH4不输出PWM,且TIM4的ARR设置为0。假设X轴需要的脉冲数大于Y轴需要的脉冲数,那么就把CH2作为TIM2输出的触发信号,因为若以脉冲数小的一方Y轴对应的CH3作为触发信号,当进入中断后,会把对应的PWM信号停止,那么此时X轴的CNT不再会被触发增长,即不会进入X轴对应的中断,那么X就不会及时停下。

        修改代码如下:

/* 选择通道输出 */
#define X_NO_OUTPUT {TIM2->CCR2 = 0;TIM1->ARR = 0;}
#define Y_NO_OUTPUT {TIM2->CCR3 = 0;TIM3->ARR = 0;}
#define Z_NO_OUTPUT {TIM2->CCR4 = 0;TIM4->ARR = 0;}
#define X_OUTPUT 	{TIM2->CCR2 = ARR_DEFAULT/2; TIM1->CR1 |= TIM_CR1_CEN;	Z_NO_OUTPUT;}
#define Y_OUTPUT 	{TIM2->CCR3 = ARR_DEFAULT/2; TIM3->CR1 |= TIM_CR1_CEN;	Z_NO_OUTPUT;}
#define Z_OUTPUT 	{TIM2->CCR4 = ARR_DEFAULT/2; TIM4->CR1 |= TIM_CR1_CEN;	X_NO_OUTPUT;Y_NO_OUTPUT;}

        测试任务如下:

        软彷结果为:             成功实现目标:主定时器多通道PWM独立驱动多个从定时器

 

 

  • 27
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,下面是代码实现: ```c #include "stm32f10x.h" void TIM2_Config(void); void TIM5_Config(void); int main(void) { TIM2_Config(); // 配置主定时器 TIM2 TIM5_Config(); // 配置从定时器 TIM5 while (1) { // 程序主循环 } } void TIM2_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 使能 TIM2 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); // 配置 TIM2 基本参数 TIM_TimeBaseStructure.TIM_Period = 999; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频器 TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分割 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数器模式 TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure); // 配置 TIM2 通道 1 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM 模式 1 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能 TIM_OCInitStructure.TIM_Pulse = 499; // 占空比 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性为高电平 TIM_OC1Init(TIM2, &TIM_OCInitStructure); // 配置 TIM2 通道 2 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 249; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC2Init(TIM2, &TIM_OCInitStructure); // 使能 TIM2 TIM_Cmd(TIM2, ENABLE); } void TIM5_Config(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; TIM_ICInitTypeDef TIM_ICInitStructure; // 使能 TIM5 时钟 RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE); // 配置 TIM5 基本参数 TIM_TimeBaseStructure.TIM_Period = 999; // 自动重装载值 TIM_TimeBaseStructure.TIM_Prescaler = 71; // 预分频器 TIM_TimeBaseStructure.TIM_ClockDivision = 0; // 时钟分割 TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; // 计数器模式 TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure); // 配置 TIM5 通道 1 为输入捕获模式 TIM_ICInitStructure.TIM_Channel = TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; // 上升沿触发 TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; // 映射到 TI1 TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; // 输入分频器 TIM_ICInitStructure.TIM_ICFilter = 0; // 输入滤波器 TIM_ICInit(TIM5, &TIM_ICInitStructure); // 配置 TIM5 通道 2 为输入捕获模式 TIM_ICInitStructure.TIM_Channel = TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter = 0; TIM_ICInit(TIM5, &TIM_ICInitStructure); // 配置 TIM5 通道 3 为输出比较模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; // PWM 模式 1 TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; // 输出使能 TIM_OCInitStructure.TIM_Pulse = 499; // 占空比 TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; // 输出极性为高电平 TIM_OC3Init(TIM5, &TIM_OCInitStructure); // 配置 TIM5 通道 4 为输出比较模式 TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse = 249; TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High; TIM_OC4Init(TIM5, &TIM_OCInitStructure); // 配置 TIM5 从模式 TIM_SelectInputTrigger(TIM5, TIM_TS_ITR0); // 触发源为 ITR0 TIM_SelectSlaveMode(TIM5, TIM_SlaveMode_Gated); // 从模式为 Gated TIM_SelectMasterSlaveMode(TIM5, TIM_MasterSlaveMode_Enable); // 开启主从模式 TIM_Cmd(TIM5, ENABLE); // 使能 TIM5 } ``` 在上面的代码中,我们使用了主定时器 TIM2 来控制 PWM 信号的输出,从定时器 TIM5 则用来控制输入捕获和输出比较。TIM5 的通道 1 和 2 配置为输入捕获模式,用来捕获外部引脚的信号;通道 3 和 4 则配置为输出比较模式,用来控制 PWM 信号的输出占空比。 在 TIM5 的配置中,我们使用了主从模式,即 TIM5 作为从定时器,由 TIM2 作为主定时器来控制。这样,TIM2 的输出信号就可以被 TIM5 的各个通道控制了。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值