打印
[STM32F1]

步进电机编码器适配的分享(步进电机闭环准备)

[复制链接]
999|4
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主

      本例我们选用了THB6064MQ集成步进电机驱动模块使用很简单,仅有3个输入端口分别是使能端口、方向控制端口和脉冲输入端口,在不细分的模式下每输入一个脉冲步进电机就转动一个步进角,本例使用的是32细分所以就是每32个脉冲步进电机转动一个步进角。








驱动步进电机的方法有很多,不同的驱动器可能使用的方法不一样,使用同一种驱动器控制的方法也可能不一样,这里我们使用的是利用STM32定时器的输出比较反转模式产生驱动电机所需的脉冲。

/**

  * 函数功能: 驱动器定时器初始化

  * 输入参数: 无

  * 返 回 值: 无

  * 说    明: 无

  */

void STEPMOTOR_TIMx_Init(void)

{

  TIM_ClockConfigTypeDef sClockSourceConfig;             // 定时器时钟

  TIM_MasterConfigTypeDef sMasterConfig;                 // 定时器主模式配置

  TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig;   // 刹车和死区时间配置

  TIM_OC_InitTypeDef sConfigOC;                          // 定时器通道比较输出

  

  /* 定时器基本环境配置 */

  htimx_STEPMOTOR.Instance = STEPMOTOR_TIMx;                                 // 定时器编号

  htimx_STEPMOTOR.Init.Prescaler = STEPMOTOR_TIM_PRESCALER;                  // 定时器预分频器

  htimx_STEPMOTOR.Init.CounterMode = TIM_COUNTERMODE_UP;                     // 计数方向:向上计数

  htimx_STEPMOTOR.Init.Period = STEPMOTOR_TIM_PERIOD;                        // 定时器周期

  htimx_STEPMOTOR.Init.ClockDivision=TIM_CLOCKDIVISION_DIV1;                 // 时钟分频

  htimx_STEPMOTOR.Init.RepetitionCounter = STEPMOTOR_TIM_REPETITIONCOUNTER;  // 重复计数器

  HAL_TIM_Base_Init(&htimx_STEPMOTOR);



  /* 定时器时钟源配置 */

  sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;                 // 使用内部时钟源

  HAL_TIM_ConfigClockSource(&htimx_STEPMOTOR, &sClockSourceConfig);



  /* 初始化定时器比较输出环境 */

  HAL_TIM_OC_Init(&htimx_STEPMOTOR);

  

  /* 定时器主输出模式 */

  sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;

  sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;

  HAL_TIMEx_MasterConfigSynchronization(&htimx_STEPMOTOR, &sMasterConfig);

  

  /* 刹车和死区时间配置 */

  sBreakDeadTimeConfig.OffStateRunMode = TIM_OSSR_DISABLE;

  sBreakDeadTimeConfig.OffStateIDLEMode = TIM_OSSI_DISABLE;

  sBreakDeadTimeConfig.LockLevel = TIM_LOCKLEVEL_OFF;

  sBreakDeadTimeConfig.DeadTime = 0;

  sBreakDeadTimeConfig.BreakState = TIM_BREAK_DISABLE;

  sBreakDeadTimeConfig.BreakPolarity = TIM_BREAKPOLARITY_HIGH;

  sBreakDeadTimeConfig.AutomaticOutput = TIM_AUTOMATICOUTPUT_DISABLE;

  HAL_TIMEx_ConfigBreakDeadTime(&htimx_STEPMOTOR, &sBreakDeadTimeConfig);



  /* 定时器比较输出配置 */

  sConfigOC.OCMode = TIM_OCMODE_TOGGLE;                // 比较输出模式:反转输出

  sConfigOC.Pulse = Toggle_Pulse;                      // 脉冲数

  sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;          // 输出极性

  sConfigOC.OCNPolarity = TIM_OCNPOLARITY_LOW;         // 互补通道输出极性

  sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;           // 快速模式

  sConfigOC.OCIdleState = TIM_OCIDLESTATE_RESET;       // 空闲电平

  sConfigOC.OCNIdleState = TIM_OCNIDLESTATE_RESET;     // 互补通道空闲电平

  HAL_TIM_OC_ConfigChannel(&htimx_STEPMOTOR, &sConfigOC, STEPMOTOR_TIM_CHANNEL_x);



  /* STEPMOTOR相关GPIO初始化配置 */

  STEPMOTOR_GPIO_Init();

  

  /* 配置定时器中断优先级并使能 */

  HAL_NVIC_SetPriority(STEPMOTOR_TIMx_IRQn, 0, 0);

  HAL_NVIC_EnableIRQ(STEPMOTOR_TIMx_IRQn);



}




/**

  * 函数功能: 步进电机运动控制

  * 输入参数: Dir:步进电机运动方向 0:反转 1正转

  *           Speed:步进电机速度  

  * 返 回 值: void

  * 说    明: 无

  */

void STEPMOTOR_Motion_Ctrl(uint8_t Dir , uint16_t Speed)

{

  uint16_t Step_Delay;  //步进延时



  if(Speed == 0)

    STEPMOTOR_OUTPUT_DISABLE();

  else

  {

    if(Dir==CCW)

    {

      STEPMOTOR_DIR_REVERSAL();

    }

    else STEPMOTOR_DIR_FORWARD();//方向控制

   

    //步进电机在丝杠上的单步距离

    //x = (MPR*α)/2π;  MPR:单圈距离 α:步距角

    //单步距离的周期

    //T = C/Ft;  C:定时器计数周期 Ft:定时器频率

    //∴ V = x/T =(MPR*α*Ft)/(2π*C);

    //得 C = (MPR*α*Ft)/(2π*V);

    //其中 α = (2π)/spr;     spr:步进电机旋转一圈需要的脉冲数

    //∴ C= (MPR*Ft)/(spr*V);

    //Step_Delay = C/2; 步进电机脉宽

    Step_Delay = (uint16_t)((MPR_FREQ_SPR_X_10/Speed)>>1);

   

    STEPMOTOR_OUTPUT_ENABLE();

    Toggle_Pulse = Step_Delay;

  }

}





一般都是使用编码器读取在转动时产生的脉冲值根据采集时间计算电机轴的运转速度,本例使用的是STM32定时器模式来做编码器AB脉冲的读取。

/**

  * 函数功能: 基本定时器硬件初始化配置

  * 输入参数: htim_base:基本定时器句柄类型指针

  * 返 回 值: 无

  * 说    明: 该函数被HAL库内部调用

  */

void HAL_TIM_Encoder_MspInit(TIM_HandleTypeDef* htim_base)

{

  GPIO_InitTypeDef GPIO_InitStruct;

  if(htim_base->Instance==ENCODER_TIMx)

  {

    /* 基本定时器外设时钟使能 */

    ENCODER_TIM_RCC_CLK_ENABLE();

    ENCODER_TIM_GPIO_CLK_ENABLE();



    /* 定时器通道1功能引脚IO初始化 */

    GPIO_InitStruct.Pin = ENCODER_TIM_CH1_PIN;

    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;

    GPIO_InitStruct.Pull=GPIO_PULLUP;

    GPIO_InitStruct.Alternate = GPIO_CH1_AFx_TIMx;

    HAL_GPIO_Init(ENCODER_TIM_CH1_GPIO, &GPIO_InitStruct);

   

    /* 定时器通道2功能引脚IO初始化 */

    GPIO_InitStruct.Pin = ENCODER_TIM_CH2_PIN;

    GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;

    GPIO_InitStruct.Pull = GPIO_PULLUP;

    GPIO_InitStruct.Alternate = GPIO_CH2_AFx_TIMx;

    HAL_GPIO_Init(ENCODER_TIM_CH2_GPIO, &GPIO_InitStruct);

  }

}

      // 采样和控制周期为20ms

    if(Time_Flag & SAMPLING)

    {

      // 获得编码器的脉冲值

      CaptureNumber = OverflowCount*65535 + __HAL_TIM_GET_COUNTER(&htimx_Encoder);



      // M法 测速度 M法是测量单位时间内的脉数,然后换算成频率,这里不算频率

      MSF = CaptureNumber  - Last_CaptureNumber;

      Last_CaptureNumber = CaptureNumber;

      // 对速度进行累计,得到1s内的脉冲数

      MSF = abs(MSF);

      SUM_Pulse += MSF;

      MMPS = MSF*SPEED_CONSTANT;// 由脉冲数转换为 mm/s, MMPS = (MSF*(TXDCYCLE/SAMPLING_PERIOD))/PSPM;



      if(Speed >= MAX_SPEED)    // 限制最大速度

        Speed = MAX_SPEED;

      if(Speed <= MIN_SPEED)

        Speed = MIN_SPEED;

   

      STEPMOTOR_Motion_Ctrl(CW,Speed);

      Time_Flag &= ~SAMPLING;

    }

//    数据发送周期为1s

    if(Time_Flag & TXD)

    {


      //printf("OverflowCount:%d \n",OverflowCount);



      sprintf((char *)aTxBuffer,"捕获值: %d -- 速度: %.2f mm/s\n",CaptureNumber,(float)MMPS);

      sprintf((char *)aTxBuffer+strlen((const char*)aTxBuffer),"1s内编码器计数值: %5d \n",SUM_Pulse);

      HAL_USART_Transmit_DMA(&husartx,aTxBuffer,strlen((const char*)aTxBuffer));

      SUM_Pulse = 0;

      Time_Flag &= ~TXD;

    }


使用特权

评论回复
评论
王栋春 2022-8-18 22:33 回复TA
楼主的技术非常厉害呀! 
沙发
yiy| | 2022-8-18 17:16 | 只看该作者
谢谢分享

使用特权

评论回复
板凳
Uriah| | 2022-10-1 14:14 | 只看该作者

在实际的项目应用当中,单片机引脚的复用相当厉害

使用特权

评论回复
地板
Pulitzer| | 2023-1-20 07:13 | 只看该作者

对于没有else的场景,使用ifPresent即可

使用特权

评论回复
5
周半梅| | 2023-1-20 08:16 | 只看该作者

每个Strategy交由Spring管理,并在构造后注册

使用特权

评论回复
6
童雨竹| | 2023-1-20 09:09 | 只看该作者

这是个再正常不过的coding习惯

使用特权

评论回复
7
Wordsworth| | 2023-1-20 10:12 | 只看该作者

使用Optional简化if判空

使用特权

评论回复
8
Clyde011| | 2023-1-20 11:15 | 只看该作者

不同的代码逻辑就代表了不同的策略

使用特权

评论回复
9
万图| | 2023-1-20 13:11 | 只看该作者

通过对判断条件取反,代码在逻辑表达上会更加清晰

使用特权

评论回复
10
Uriah| | 2023-1-20 14:14 | 只看该作者

会以switch-case的方式出现

使用特权

评论回复
11
帛灿灿| | 2023-1-20 16:10 | 只看该作者

对于优秀程序员来说,这不是好代码

使用特权

评论回复
12
Bblythe| | 2023-1-20 17:13 | 只看该作者

不要根据不同的参数类型走不同的代码逻辑

使用特权

评论回复
13
周半梅| | 2023-1-20 19:09 | 只看该作者

代码量小的时候用来做条件判断

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

58

主题

113

帖子

1

粉丝