[其他ST产品] 方向位置控制模式 主从定时器 实现PWM脉冲数精确控制

[复制链接]
2296|18
 楼主| 米多0036 发表于 2023-11-24 01:14 | 显示全部楼层 |阅读模式
STM32 伺服电机 指令脉冲+方向位置控制模式 主从定时器 实现PWM脉冲数精确控制
实验室有个项目涉及到多个步进电机以及伺服电机的控制,需要电机得到一个触发信号后精确移动一段距离。

下面以单个伺服电机控制为例,利用伺服电机的指令脉冲加方向位置控制模式实现精确位置控制。



关于伺服电机
所谓指令脉冲+方向控制模式 就是由一路PWM波控制电机转动,一个IO控制电机方向。PWM的占空比控制电机转速,PWM的脉冲数控制电机的转动距离。

计算方法如下:

电机转动一圈所需脉冲数=电机驱动内置编码器线数*4/电子齿轮数                                                   

4为编码器对AB相计数模式     若仅对AB相上升沿或者下降沿计数则取2  若对AB相上升沿和下降沿都计数则取4                                                                                                                  电子齿轮即为电机驱动对脉冲的细分数,使控制更加精确。

 楼主| 米多0036 发表于 2023-11-24 01:14 | 显示全部楼层
例:                                

伺服电机编码器为2500线 驱动细分为4:1                                                                                        则一圈脉冲数=2500*4/4=2500                                                                                                            指令脉冲频率=(需要电机运行的转速 r/min/60)*一圈的脉冲数   此次操作不涉及到电机速度控制 不做介绍

一圈所需脉冲数为2500 则一个脉冲转动1/2500圈 设电机转动一圈移动距离为C,则一个脉冲移动C/2500 所以通过对脉冲计数可以实现非常精确的位置控制。
 楼主| 米多0036 发表于 2023-11-24 01:15 | 显示全部楼层
关于PWM脉冲数精确控制
PWM脉冲数精确控制有以下三种方案:

利用延时   在开始发出脉冲的同时开始延时         时长=PWM周期*PWM脉冲数                        这是非常土的方法 且弊端显而易见       一是延时使系统的实时性降低                    二是精确度不高
 楼主| 米多0036 发表于 2023-11-24 01:15 | 显示全部楼层
两个定时器同步,一个发PWM波另外一个计数      即将两个定时器时基参数配置相同且同时开启      定时器在1个period中产生一个PWM脉冲 定时器2在一个period更新时产生一个中断,通过中断使计数变量加一,至设定脉冲数后关闭定时器1。  这个方法可以实现精确的脉冲数量控制,但是需要系统频繁的进入中断,如果需要20个PWM脉冲就要触发20次中断进行计数,如果需要20000个PWM脉冲则要触发20000次中断,太浪费系统资源,并且过于频繁的中断会让系统出现跑飞或者其他一些莫名其妙的错误,只有在数量比较小时才适合这种方法。
 楼主| 米多0036 发表于 2023-11-24 01:15 | 显示全部楼层
利用STM32自带的定时器主从模式进行计数       将主定时器的PWM输出脉冲作为从定时器的时钟,同时将从定时器的period设置为所需脉冲数,当从定时器产生溢出中断时则表示到达了所需脉冲数,即可在中断中关闭主定时器的PWM输出,达到精确的位移控制的目的。这种方法只需要触发一次中断,并且计数非常精确。
 楼主| 米多0036 发表于 2023-11-24 01:15 | 显示全部楼层
权衡利弊,本次使用了方法三,需要两个定时器资源,主控为STM32F103ZET6,选用TIM3的CH2作为PWM输出通道即主定时器为TIM3,从定时器选用TIM4。
 楼主| 米多0036 发表于 2023-11-24 01:15 | 显示全部楼层
关于STM32主从定时器
在stm32中文参考手册”定时器同步“那一小节中有详细介绍

选择使用模式1 使用一个定时器(主)作为另一个定时器(从)的预分频器

其实就是将stm32的72MHz的时钟由主定时器分为一个周期为主定时器period的新脉冲(可理解为从定时器的新时钟),在主定时器的每个period溢出的那一刻会产生更新事件,由更新事件作为从定时器的时钟。
 楼主| 米多0036 发表于 2023-11-24 01:16 | 显示全部楼层
 楼主| 米多0036 发表于 2023-11-24 01:16 | 显示全部楼层
由下表可以看到STM32 TIMX的内部连接,在程序中通过选择ITRX将主从定时器联系起来,如本程序从定时器为TIM4,主定时器选择TIM3,则应选择ITR2. 18205655f88e688d4b.png
 楼主| 米多0036 发表于 2023-11-24 01:16 | 显示全部楼层
由下表可以看到STM32 TIMX的内部连接,在程序中通过选择ITRX将主从定时器联系起来,如本程序从定时器为TIM4,主定时器选择TIM3,则应选择ITR2. 3887655f88ff5ab47.png
 楼主| 米多0036 发表于 2023-11-24 01:17 | 显示全部楼层
下面直接上代码:

下为伺服电机控制头文件代码
  1. <MS_Counter.h>


  2. #ifndef ms_counter_h
  3. #define ms_counter_h

  4. #include "sys.h"

  5. #define PWM_CLK RCC_APB2Periph_GPIOC                  //定义PWM输出时钟

  6. #define Motor_CCW  GPIO_ResetBits(GPIOE,GPIO_Pin_5)   //电机逆时针旋转
  7. #define Motor_CW   GPIO_SetBits(GPIOE,GPIO_Pin_5)     //电机顺时针旋转

  8. void Master_TIM(u16 period,u16 prescaler,u16 pulse);   //主定时器配置函数
  9. void Slave_TIM(u16 period);                            //从定时器配置函数
  10. void DIR_Crl(void);                                    //方向控制IO配置函数

  11. #endif
 楼主| 米多0036 发表于 2023-11-24 01:19 | 显示全部楼层
下面直接上代码:

下为伺服电机控制头文件代码
  1. <MS_Counter.h>


  2. #ifndef ms_counter_h
  3. #define ms_counter_h

  4. #include "sys.h"

  5. #define PWM_CLK RCC_APB2Periph_GPIOC                  //定义PWM输出时钟

  6. #define Motor_CCW  GPIO_ResetBits(GPIOE,GPIO_Pin_5)   //电机逆时针旋转
  7. #define Motor_CW   GPIO_SetBits(GPIOE,GPIO_Pin_5)     //电机顺时针旋转

  8. void Master_TIM(u16 period,u16 prescaler,u16 pulse);   //主定时器配置函数
  9. void Slave_TIM(u16 period);                            //从定时器配置函数
  10. void DIR_Crl(void);                                    //方向控制IO配置函数

  11. #endif
 楼主| 米多0036 发表于 2023-11-24 01:19 | 显示全部楼层
下为主/从定时器配置函数代码:
  1. #include"stm32f10x.h"
  2. #include "MS_Counter.h"

  3. /***************
  4. 主定时器配置函数
  5. period:PWM周期
  6. prescaler:预分频系数
  7. pulse:占空比控制变量 也就是PWM有效电平的宽度
  8. PWM输出IO为GPIOC_7
  9. 完全重映射至 TIM3_CH2
  10. ***************/

  11. void Master_TIM(u16 period,u16 prescaler,u16 pulse)
  12. {
  13.        
  14.         TIM_OCInitTypeDef TIM_OCInitStructure;                        //OC2初始化结构体
  15.         TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;            //TIM3时基初始化结构体
  16.         GPIO_InitTypeDef GPIO_InitTypeStructure;                      //GPIO初始化结构体
  17.        
  18.         RCC_APB2PeriphClockCmd(PWM_CLK|RCC_APB2Periph_GPIOB|RCC_APB2Periph_AFIO,ENABLE);   
  19.                                                            //使能PWM输出的时钟和复用IO时钟
  20.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);        //使能主定时器时钟
  21.        
  22.         GPIO_InitTypeStructure.GPIO_Mode=GPIO_Mode_Out_PP;           
  23.                                //沙雕核心板默认蜂鸣器开启状态,每次还要专门写代码关蜂鸣器...
  24.         GPIO_InitTypeStructure.GPIO_Pin=GPIO_Pin_8;
  25.         GPIO_InitTypeStructure.GPIO_Speed=GPIO_Speed_50MHz;
  26.         GPIO_Init(GPIOB,&GPIO_InitTypeStructure);
  27.         GPIO_ResetBits(GPIOB,GPIO_Pin_8);
  28.        
  29.         GPIO_InitTypeStructure.GPIO_Mode=GPIO_Mode_AF_PP;             //PWM输出IO设为复用推挽
  30.         GPIO_InitTypeStructure.GPIO_Pin=GPIO_Pin_7;
  31.         GPIO_InitTypeStructure.GPIO_Speed=GPIO_Speed_50MHz;
  32.         GPIO_Init(GPIOC,&GPIO_InitTypeStructure);
  33.         GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);             //完全重映射至TIM3_CH2
  34.        
  35.         /*********
  36.         初始化TIM3的时基
  37.         设定PWM周期和预分频系数
  38.         *********/
  39.        
  40.         TIM_TimeBaseInitStructure.TIM_ClockDivision=1;                //时钟分频为1即为72MHz
  41.         TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
  42.         TIM_TimeBaseInitStructure.TIM_Period=period;
  43.         TIM_TimeBaseInitStructure.TIM_Prescaler=prescaler;
  44.         TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
  45.         TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
  46.        
  47.         /***********
  48.         初始化PWM输出通道OC2
  49.         设定PWM占空比
  50.         ***********/
  51.         TIM_OCInitStructure.TIM_OCIdleState=ENABLE;
  52.         TIM_OCInitStructure.TIM_OCMode=TIM_OCMode_PWM1;              //PWM模式1
  53.         TIM_OCInitStructure.TIM_OCPolarity=TIM_OCPolarity_High;      //高电平为有效位
  54.         TIM_OCInitStructure.TIM_OutputState=ENABLE;
  55.         TIM_OCInitStructure.TIM_Pulse=pulse;
  56.         TIM_OC2Init(TIM3,&TIM_OCInitStructure);
  57.        
  58.         TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);             //预装载TIM3的pulse值
  59.         TIM_Cmd(TIM3,DISABLE);                                       //初始化过程中不开启TIM3
  60.                                                  //中断响应后再开启,保证主从定时器同步工作
  61.        
  62.         TIM_SelectMasterSlaveMode(TIM3,TIM_MasterSlaveMode_Enable);  //TIM3选择使能主从模式主定时器
  63.         TIM_SelectOutputTrigger(TIM3,TIM_TRGOSource_Update);         //TIM3选择更新事件作为
  64.                                                                             trgo触发源
  65.        
  66. }



  67. /************
  68. 从定时器配置函数
  69. period:TIM4的溢出值 即设定的PWM脉冲数
  70. ***********/

  71. void Slave_TIM(u16 period)
  72. {
  73.        
  74.         NVIC_InitTypeDef NVIC_InitStructure;                         //中断初始化结构体
  75.         TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;           //TIM4时基初始化结构体
  76.        
  77.         RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4,ENABLE);          //使能TIM4时钟
  78.        
  79.         /********
  80.         初始化TIM4时基
  81.         设定所需PWM脉冲数
  82.         ********/
  83.        
  84.         TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;    //时钟不分频即直接使用
  85.                                                                  //TIM3脉冲
  86.         TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
  87.         TIM_TimeBaseInitStructure.TIM_Period=period;
  88.         TIM_TimeBaseInitStructure.TIM_Prescaler=0;                   //PWM不分频
  89.         TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
  90.         TIM_TimeBaseInit(TIM4,&TIM_TimeBaseInitStructure);
  91.        
  92.         TIM_SelectSlaveMode(TIM4,TIM_SlaveMode_External1);           //TIM4选择从定时器模式
  93.        
  94.   TIM_SelectInputTrigger(TIM4,TIM_TS_ITR2);                    //TIM4选择内部触发来源为
  95.                                                                //TIM3
  96.        
  97.         TIM_Cmd(TIM4,DISABLE);                                       //同样初始化中不使能TIM4
  98.        
  99.         /***************
  100.         配置TIM4的中断
  101.         利用TIM4计数溢出作为中断事件关闭TIM3
  102.         可达到精确计数的目的
  103.         ****************/
  104.         NVIC_InitStructure.NVIC_IRQChannel=TIM4_IRQn;
  105.         NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
  106.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=2;
  107.         NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
  108.         NVIC_Init(&NVIC_InitStructure);
  109.        
  110.         TIM_ITConfig(TIM4,TIM_IT_Update,ENABLE);                    //TIM4中断触发事件为update
  111.        
  112. }

  113. /*************
  114. 方向控制IO配置函数
  115. GPIOE_5
  116. 高电平顺时针
  117. 低电平逆时针
  118. *************/

  119. void DIR_Crl(void)
  120. {
  121.        
  122.         GPIO_InitTypeDef GPIO_InitStructure;
  123.        
  124.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE,ENABLE);
  125.        
  126.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_Out_PP;
  127.         GPIO_InitStructure.GPIO_Pin=GPIO_Pin_5;
  128.         GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  129.         GPIO_Init(GPIOE,&GPIO_InitStructure);
  130.        
  131. }
 楼主| 米多0036 发表于 2023-11-24 01:20 | 显示全部楼层
使用按键信号来模拟外部触发信号,通过外部中断开启TIM3进行PWM输出,开启TIM4进行计数

下为按键配置代码:
  1. #include"stm32f10x.h"
  2. #include "key.h"

  3. /**********
  4. 按键配置函数
  5. 通过外部按键触发中断
  6. 使主定时器发出设定数量的PWM脉冲
  7. 也可将按键改为其他触发信号接入
  8. 按键IO为 GPIOE_4
  9. **********/

  10. void KEY_Config(void)
  11. {
  12.        
  13.         NVIC_InitTypeDef NVIC_InitStructure;        //中断初始化结构体
  14.         EXTI_InitTypeDef EXTI_InitStructure;        //外部中断通道初始化结构体
  15.         GPIO_InitTypeDef GPIO_InitStructure;        //按键IO初始化结构体
  16.        
  17.         RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOE|RCC_APB2Periph_AFIO,ENABLE);  
  18.                                                //使能按键IO时钟和复用时钟
  19.        
  20.         GPIO_InitStructure.GPIO_Mode=GPIO_Mode_IPU;  
  21.         GPIO_InitStructure.GPIO_Pin=GPIO_Pin_4;
  22.         GPIO_InitStructure.GPIO_Speed=GPIO_Speed_50MHz;
  23.         GPIO_Init(GPIOE,&GPIO_InitStructure);
  24.        
  25.         GPIO_EXTILineConfig(GPIO_PortSourceGPIOE,GPIO_PinSource4);    //选择外部中断通道
  26.        
  27.         EXTI_InitStructure.EXTI_Line=EXTI_Line4;                                   
  28.         EXTI_InitStructure.EXTI_LineCmd=ENABLE;
  29.         EXTI_InitStructure.EXTI_Mode=EXTI_Mode_Interrupt;
  30.         EXTI_InitStructure.EXTI_Trigger=EXTI_Trigger_Falling;                  
  31.     EXTI_Init(&EXTI_InitStructure);
  32.        
  33.         NVIC_InitStructure.NVIC_IRQChannel=EXTI4_IRQn;              
  34.         NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
  35.         NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
  36.         NVIC_InitStructure.NVIC_IRQChannelSubPriority=2;
  37.         NVIC_Init(&NVIC_InitStructure);
  38.        
  39.        
  40. }
 楼主| 米多0036 发表于 2023-11-24 01:20 | 显示全部楼层
下为中断服务函数:

外部按键按下后触发外部中断 在外部中断中将主从定时器同时开启 主定时器开始发出设定数量的PWM脉冲
  1. void TIM4_IRQHandler (void)                         //TIM4中断服务函数
  2. {
  3.        
  4.         if (TIM_GetITStatus(TIM4,TIM_IT_Update)!=RESET)
  5.         {
  6.                 TIM_Cmd(TIM3,DISABLE);                          //TIM4产生中断后关闭TIM3
  7.                 TIM_ClearITPendingBit(TIM4,TIM_IT_Update);
  8.         }
  9. }

  10. void EXTI4_IRQHandler(void)                        //按键外部中断服务函数
  11. {
  12.        
  13.         if (EXTI_GetITStatus(EXTI_Line4)!=RESET)         //按键按下后TIM3/4开启,电机动作      
  14.         {
  15.                 TIM_Cmd(TIM3,ENABLE);
  16.                 TIM_Cmd(TIM4,ENABLE);
  17.         }
  18.         EXTI_ClearITPendingBit(EXTI_Line4);
  19. }
 楼主| 米多0036 发表于 2023-11-24 01:20 | 显示全部楼层
下为主函数:
  1. include "stm32f10x.h"
  2. #include "MS_Counter.h"
  3. #include "key.h"
  4. #include "delay.h"

  5. int main()
  6. {
  7.         NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);      //配置中断优先级
  8.        
  9.         delay_init();
  10.         DIR_Crl();                                           //电机方向接口初始化
  11.         Motor_CCW;                                           //控制电机逆时针旋转
  12.         Master_TIM(999,719,399);          //主定时器预分频为720,周期为1000则PWM频率为100Hz
  13.         Slave_TIM(20);                                   //设定脉冲数为20,小数量便于验证
  14.         KEY_Config();
  15.        
  16.         delay_ms(10);                                 
  17.        
  18.         while(1);
  19.        
  20. }
 楼主| 米多0036 发表于 2023-11-24 01:21 | 显示全部楼层
通过MDK5自带的逻辑分析仪进行仿真如下

50338655f89f50edad.png
不多不少 正好20个脉冲
udaidfa002 发表于 2023-11-29 16:18 | 显示全部楼层
电机控制方面,还是不错的。
zzzlt422 发表于 2025-5-5 14:53 | 显示全部楼层
米多0036 发表于 2023-11-24 01:21
通过MDK5自带的逻辑分析仪进行仿真如下

楼主你好,我设置主定时器的TRGO为OCxREF,例如TIM_SelectOutputTrigger(TIM3,TIM_TRGOSource_OC3Ref);其他跟您设置的都一样,我现在发现keil软彷跑的时候也能够实现精准计数,但是烧录到板子上发现对应通道有输出PWM但是对应从定时器的CNT并没有增加,您能看看是什么原因吗?
我写了篇博文,具体代码您可以参考,感谢!
https://blog.csdn.net/zhouletian0422/article/details/140587227?spm=1001.2014.3001.5502
您需要登录后才可以回帖 登录 | 注册

本版积分规则

138

主题

1431

帖子

2

粉丝
快速回复 在线客服 返回列表 返回顶部