Learning PID – Implementation of Speed Control for Stepper Motors

Learning PID - Implementation of Speed Control for Stepper Motors

Overview of the Principle

A stepper motor is a motor driven by digital signals, one of its main advantages being its excellent open-loop control capability, where the control system does not require sensors and corresponding circuits to feedback motor information.

Under conditions where the load is not overloaded and the pulse frequency is appropriate, the number of pulses received by the stepper motor and the angular displacement of the rotor are strictly proportional.

Although stepper motors can perform well in open-loop control, in some open-loop systems, the stepper motor may lose steps, overshoot, or even stall during rapid start-stop or sudden load changes due to its own performance and system mechanical structure. The controller cannot know and correct these phenomena, which may lead to serious consequences in systems with high precision requirements.

By adding sensor feedback to form a closed-loop system, it can detect phenomena such as loss of steps and correct deviations in a timely manner.

There are many closed-loop control schemes for stepper motors, which can fully control the torque and position of the stepper motor, improve the torque frequency characteristics of the stepper motor, reduce heating and average power consumption, and enhance motor operating efficiency.

Some control terms we often hear, such as speed loop, position loop, and current loop, can also be applied to closed-loop control of stepper motors, and of course, these terms are similarly applicable to other motor closed-loop systems.

Below we will use the speed of the stepper motor as the controlled quantity (i.e., speed loop), using a rotary encoder as the feedback sensor, and the PID algorithm for the closed-loop control system.

Learning PID - Implementation of Speed Control for Stepper Motors

Speed Closed-Loop Control – Incremental PID

Next, we can create:

pid.h and pid.c files to store the relevant programs for the PID controller.

stepper_ctrl.c and stepper_ctrl.h files to store the stepper motor speed loop control program and related macro definitions.

Steps

• Timer IO configuration

• Initialization of relevant peripherals for stepper motor and encoder

• Implementation of speed closed-loop control

• PID parameter tuning

Testing Environment

STM32F103VE version or above, external high-speed crystal oscillator: 8MHz, RTC crystal oscillator: 32.768KHz.

All bus operating clocks: system clock = SYCCLK = AHB = 72MHz, APB2 = 72MHz, APB1 = 36MHz

Stepper Motor Driver Interface

Learning PID - Implementation of Speed Control for Stepper Motors

Wiring of the Encoder and Stepper Motor

Learning PID - Implementation of Speed Control for Stepper Motors

Code Analysis

The first step is to check the stepper_ctrl.h file, which initializes the GPIO for the stepper motor, etc.

#ifndef __STEP_MOTOR_INIT_H#define    __STEP_MOTOR_INIT_H#include "stm32f1xx_hal.h"//Motor Direction #define MOTOR_DIR_PIN                      GPIO_PIN_6  #define MOTOR_DIR_GPIO_PORT                GPIOE               #define MOTOR_DIR_GPIO_CLK_ENABLE()       __HAL_RCC_GPIOE_CLK_ENABLE()//Motor Enable #define MOTOR_EN_PIN                      GPIO_PIN_5#define MOTOR_EN_GPIO_PORT                GPIOE                       #define MOTOR_EN_GPIO_CLK_ENABLE()        __HAL_RCC_GPIOE_CLK_ENABLE()//Motor Pulse#define MOTOR_PUL_IRQn                  TIM8_CC_IRQn#define MOTOR_PUL_IRQHandler            TIM8_CC_IRQHandler#define MOTOR_PUL_TIM                   TIM8#define MOTOR_PUL_CLK_ENABLE()              __HAL_RCC_TIM8_CLK_ENABLE()#define MOTOR_PUL_PORT                         GPIOC#define MOTOR_PUL_PIN                       GPIO_PIN_6#define MOTOR_PUL_GPIO_CLK_ENABLE()          __HAL_RCC_GPIOC_CLK_ENABLE()#define MOTOR_PUL_CHANNEL_x             TIM_CHANNEL_1#define MOTOR_TIM_IT_CCx                TIM_IT_CC1#define MOTOR_TIM_FLAG_CCx              TIM_FLAG_CC1/*Frequency Related Parameters*///The actual timer clock frequency is: 72MHz/TIM_PRESCALER//The specific required frequency can be calculated#define TIM_PRESCALER                16  /*Note: For F103 routine testing, increasing the prescaler is beneficial for stabilizing the position loop*/ //Define the timer period, set the output compare mode period to 0xFFFF#define TIM_PERIOD                   0xFFFF/************************************************************/#define HIGH GPIO_PIN_SET       //High Level#define LOW  GPIO_PIN_RESET     //Low Level#define ON  LOW                 //On#define OFF HIGH                //Off#define CW  HIGH                //Clockwise#define CCW LOW                 //Counterclockwise//Control Enable Pin/* Macro with parameters can be used like inline functions */#define MOTOR_EN(x)                    HAL_GPIO_WritePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN,x)#define MOTOR_PUL(x)                HAL_GPIO_WritePin(MOTOR_PUL_GPIO_PORT,MOTOR_PUL_PIN,x)#define MOTOR_DIR(x)                HAL_GPIO_WritePin(MOTOR_DIR_GPIO_PORT,MOTOR_DIR_PIN,x) #define MOTOR_EN_TOGGLE     HAL_GPIO_TogglePin(MOTOR_EN_GPIO_PORT,MOTOR_EN_PIN)#define MOTOR_PUL_TOGGLE    HAL_GPIO_TogglePin(MOTOR_PUL_PORT,MOTOR_PUL_PIN)extern TIM_HandleTypeDef TIM_StepperHandle;extern void stepper_Init(void);#endif /* __STEP_MOTOR_INIT_H */

stepper_ctrl.h includes the step angle of the stepper motor, the subdivision number of the driver, and the target speed used for PID control.

The parameters of the stepper motor itself and the parameters needed for closed-loop control include the step angle of the stepper motor, the subdivision number of the driver, and the target speed used for PID control.

Among them, the macro PULSE_RATIO is the ratio of the number of pulses per revolution of the stepper motor to the number of pulses per revolution of the encoder, because in the entire speed closed-loop control system, the feedback and the PID calculated values are all based on the encoder’s pulse count.

#ifndef __STEP_MOTOR_CTRL_H#define    __STEP_MOTOR_CTRL_H#include "stepper_init.h"#include "encoder.h"/*Macro Definitions*/#define T1_FREQ           (SystemCoreClock/TIM_PRESCALER) // Frequency ft value/*Motor Single Circle Parameters*/#define STEP_ANGLE                1.8f                        // Step angle of the stepper motor in degrees#define FSPR              ((float)(360.0f/STEP_ANGLE))// Number of pulses required for one revolution of the stepper motor#define MICRO_STEP        32                                  // Subdivision number #define SPR               (FSPR*MICRO_STEP)           // Number of pulses required for one revolution after subdivision#define PULSE_RATIO       ((float)(SPR/ENCODER_TOTAL_RESOLUTION))// Ratio of the number of pulses per revolution of the stepper motor to the number of pulses per revolution of the encoder#define TARGET_DISP       2                    // Target number of revolutions for the stepper motor, in revolutions#define SPEED_LIMIT       10000                // Maximum startup speed limit#define SAMPLING_PERIOD   50                          // PID sampling frequency, in Hzvoid MSD_ENA(int NewState);void Set_Stepper_Stop(void);void Set_Stepper_Start(void);void Stepper_Speed_Ctrl(void);#endif /* __STEP_MOTOR_CTRL_H */

Defines a structure __SYS_STATUS to manage the state of the driver and motor.

typedef struct {  unsigned char stepper_dir : 1;               // Stepper motor direction  unsigned char stepper_running : 1;           // Stepper motor running status  unsigned char MSD_ENA : 1;                   // Driver enable status}__SYS_STATUS;

Incremental PID

The input parameters of the PID controller have been changed from the original target value to the actual value fed back, while the target value is assigned externally to the controller, and the return value of the controller becomes the incremental value calculated by the PID, with the accumulation of the actual value placed outside the controller.

The principle of the entire incremental PID controller has not changed; it has only adjusted part of the code organization logic, which makes it more convenient to call the PID controller in other parts of the program.

Learning PID - Implementation of Speed Control for Stepper Motors

Stepper Motor Closed-Loop Control System

/**  * @brief  Stepper Motor Braking  * @param  None  * @retval None  */void Set_Stepper_Stop(void){  /* Disable comparison channel */    TIM_CCxChannelCmd(MOTOR_PUL_TIM,MOTOR_PUL_CHANNEL_x,TIM_CCx_DISABLE);  sys_status.stepper_running = 0;}/*  * @brief  Start Stepper Motor  * @param  None  * @retval None  */void Set_Stepper_Start(void){  /* Enable driver */  MSD_ENA(0);  /* Enable comparison channel output */    TIM_CCxChannelCmd(MOTOR_PUL_TIM,MOTOR_PUL_CHANNEL_x,TIM_CCx_ENABLE);  sys_status.MSD_ENA = 1;  sys_status.stepper_running = 1;}/*  * @brief  Stepper Motor Incremental PID Control  * @retval None  * @note   Called in the basic timer interrupt  */void Stepper_Speed_Ctrl(void){  /* Encoder related variables */  static __IO int32_t last_count = 0;  __IO int32_t capture_count = 0;  __IO int32_t capture_per_unit = 0;  /* Expected value after pid calculation */  static __IO float cont_val = 0.0f;  __IO float timer_delay = 0.0f;  /* Start pid calculation only when the motor is moving */  if((sys_status.MSD_ENA == 1) && (sys_status.stepper_running == 1))  {    /* Calculate the number of encoder pulses within a single sampling time */    capture_count =(int)__HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (encoder_overflow_count * ENCODER_TIM_PERIOD);    /* The number of encoder pulses per unit time is passed as the actual value to the pid controller */    cont_val += PID_realize((float)capture_count);// Perform PID calculation    /* Determine speed direction */    cont_val > 0 ? (MOTOR_DIR(CW)) : (MOTOR_DIR(CCW));    /* Take the absolute value of the expected value calculated */    timer_delay = fabsf(cont_val);         /* Limit maximum startup speed */    timer_delay >= SPEED_LIMIT ? (timer_delay = SPEED_LIMIT) : timer_delay;    /* Calculate the value for the comparison counter */     OC_Pulse_num = ((uint16_t)(T1_FREQ / ((float)timer_delay * PULSE_RATIO))) >> 1;     printf("Actual Value: %d, Target Value: %.0f\r\n", capture_per_unit, pid.target_val);// Print actual and target values   }  else  {    /* Clear all parameters in the stopped state */    last_count = 0;    cont_val = 0;    pid.actual_val = 0;    pid.err = 0;    pid.err_last = 0;    pid.err_next = 0;  }}

Defines some intermediate variables used for encoder speed measurement and PID calculation.

Learning PID - Implementation of Speed Control for Stepper Motors

Judges the running status of the driver and motor; closed-loop control can only be executed when the driver is enabled and the motor is in motion.

if((sys_status.MSD_ENA == 1) && (sys_status.stepper_running == 1))

Reads the encoder count value and calculates the count value within a single sampling period, capture_per_unit, in pulses per millisecond, which actually represents the frequency of the encoder pulses. For convenience in subsequent calculations, it is not written in terms of speed in revolutions per second;

 capture_count =(int)__HAL_TIM_GET_COUNTER(&TIM_EncoderHandle) + (encoder_overflow_count * ENCODER_TIM_PERIOD);

When the motor stops or changes from running to stopping, it is necessary to clear the intermediate values of the encoder readings and the accumulated data in the PID controller to avoid affecting the control effect when the motor starts again.

/* Clear all parameters in the stopped state */last_count = 0;cont_val = 0;pid.actual_val = 0;pid.err = 0;pid.err_last = 0;pid.err_next = 0;

Execution Process

In the entire Stepper_Speed_Ctrl closed-loop control function, the parameters passed to PID and the output of PID are all based on the encoder’s data, which is the pulse frequency of the encoder. However, the actual controlled quantity is the speed of the stepper motor, which requires conversion.

The pulse frequency of the encoder, capture_per_unit, is multiplied by a coefficient PULSE_RATIO to obtain the required pulse frequency for the stepper motor. This coefficient is derived from the number of pulses required for the stepper motor to complete one revolution after subdivision, divided by the number of pulses emitted by the encoder in one revolution.

However, at this point, the frequency is still in milliseconds, and for convenience in subsequent calculations, it needs to be unified to seconds. Since the sampling period of this routine is 20ms, the unit conversion only requires multiplying by the number of samples in 1 second, which is 50.

After obtaining the required pulse frequency for the stepper motor, it is converted into a value that can be written into the capture compare register.

When the timer is configured in output compare mode, the pulse period of the stepper motor can be changed by modifying the value in the capture compare register, thereby changing the motor speed.

Timer Control

The basic timer TIM6 calls the closed-loop control program in a cyclic manner during its timing interrupt, with TIM6 configured to trigger an interrupt every 20ms, meaning that the sampling period for closed-loop control is 20ms.

void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){  /* Determine which timer triggered the interrupt */  if(htim->Instance == BASIC_TIM)  {    Stepper_Speed_Ctrl();  }  else if(htim->Instance == ENCODER_TIM)  {      /* Determine the current counting direction */    if(__HAL_TIM_IS_TIM_COUNTING_DOWN(htim))      /* Down overflow */      encoder_overflow_count--;    else      /* Up overflow */      encoder_overflow_count++;  }}

Main Function

#include "main.h"#include <stdio.h>#include <stdlib.h>#include "usart.h"#include "stepper_init.h"#include "key.h"#include "led.h"#include "pid.h"#include "tim.h"#include "stepper_ctrl.h"#include "encoder.h"#include "protocol.h"extern _pid pid;extern int pid_status;/**  * @brief  Main function  * @param  None  * @retval None  */int main(void) {    /* Initialize system clock to 72MHz */    SystemClock_Config();    /* Enable multiplex register clock */    __HAL_RCC_SYSCFG_CLK_ENABLE();    /* Set Interrupt Group Priority */    HAL_NVIC_SetPriorityGrouping(NVIC_PRIORITYGROUP_4);    /* Initialize USART configuration mode to 115200 8-N-1, interrupt receive */    DEBUG_USART_Config();    protocol_init();          /* Initialize serial communication protocol */    HAL_InitTick(5);    /* Key interrupt initialization */    Key_GPIO_Config();        /* LED initialization */    LED_GPIO_Config();    /* Initialize basic timer to generate an interrupt every 20ms */    TIMx_Configuration();    /* Encoder interface initialization */    Encoder_Init();    /* Stepper motor initialization */    stepper_Init();    /* Default stop the motor on power-up */    Set_Stepper_Stop();    /* PID algorithm parameter initialization */    PID_param_init();    /* Convert target speed to encoder pulse count as PID target value */    pid.target_val = TARGET_DISP * ENCODER_TOTAL_RESOLUTION;    while(1)    {    /* Data reception processing */    receiving_process();    /* Scan KEY1 to start the motor */    if( Key_Scan(KEY1_GPIO_PORT,KEY1_PIN) == KEY_ON  )    {        Set_Stepper_Start();    }    /* Scan KEY2 to stop the motor */    if( Key_Scan(KEY2_GPIO_PORT,KEY2_PIN) == KEY_ON  )    {        Set_Stepper_Stop();         }    /* Scan KEY3 to increase target position */    if( Key_Scan(KEY3_GPIO_PORT,KEY3_PIN) == KEY_ON  )    {        /* Increase position by 2 revolutions */        pid.target_val += 8000;    }    /* Scan KEY4 to decrease target position */    if( Key_Scan(KEY4_GPIO_PORT,KEY4_PIN) == KEY_ON  )    {        /* Decrease position by 2 revolutions */        pid.target_val -= 8000;    }} /**  * @brief  Timer update event callback function  * @param  None  * @retval None  */void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim){  /* Determine which timer triggered the interrupt */  if(htim->Instance == BASIC_TIM)  {    Stepper_Speed_Ctrl();  }  else if(htim->Instance == ENCODER_TIM)  {      /* Determine the current counting direction */    if(__HAL_TIM_IS_TIM_COUNTING_DOWN(htim))      /* Down overflow */      encoder_overflow_count--;    else      /* Up overflow */      encoder_overflow_count++;  }} /**  * @brief  System Clock Configuration  *         The system Clock is configured as follow :   *            System Clock source            = PLL (HSE)  *            SYSCLK(Hz)                     = 72000000  *            HCLK(Hz)                       = 72000000  *            AHB Prescaler                  = 1  *            APB1 Prescaler                 = 2  *            APB2 Prescaler                 = 1  *            HSE Frequency(Hz)              = 8000000  *            HSE PREDIV1                    = 2  *            PLLMUL                         = 9  *            Flash Latency(WS)              = 0  * @param  None  * @retval None  */void SystemClock_Config(void){  RCC_ClkInitTypeDef clkinitstruct = {0};  RCC_OscInitTypeDef oscinitstruct = {0};  /* Enable HSE Oscillator and activate PLL with HSE as source */  oscinitstruct.OscillatorType  = RCC_OSCILLATORTYPE_HSE;  oscinitstruct.HSEState        = RCC_HSE_ON;  oscinitstruct.HSEPredivValue  = RCC_HSE_PREDIV_DIV1;  oscinitstruct.PLL.PLLState    = RCC_PLL_ON;  oscinitstruct.PLL.PLLSource   = RCC_PLLSOURCE_HSE;  oscinitstruct.PLL.PLLMUL      = RCC_PLL_MUL9;  if (HAL_RCC_OscConfig(&oscinitstruct)!= HAL_OK)  {    /* Initialization Error */    while(1);   }  /* Select PLL as system clock source and configure the HCLK, PCLK1 and PCLK2 clocks dividers */  clkinitstruct.ClockType = (RCC_CLOCKTYPE_SYSCLK | RCC_CLOCKTYPE_HCLK | RCC_CLOCKTYPE_PCLK1 | RCC_CLOCKTYPE_PCLK2);  clkinitstruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;  clkinitstruct.AHBCLKDivider = RCC_SYSCLK_DIV1;  clkinitstruct.APB2CLKDivider = RCC_HCLK_DIV1;  clkinitstruct.APB1CLKDivider = RCC_HCLK_DIV2;    if (HAL_RCC_ClockConfig(&clkinitstruct, FLASH_LATENCY_2)!= HAL_OK)  {    while(1);   }}

Learning PID - Implementation of Speed Control for Stepper Motors

Learning PID - Implementation of Speed Control for Stepper Motors

↑ Hot Course, Limited Time Coupon! 🎉 Grab it Now ↑

Learning PID - Implementation of Speed Control for Stepper Motors

Learning PID - Implementation of Speed Control for Stepper Motors

Leave a Comment