2. Development Tutorial (STM32)
After the program is downloaded, the robot chassis will perform the actions in the following order:
(1) Move forward, then move backward.
(2) Move left, then move right.
(3) Rotate in place (left and right).
(4) Move diagonally to the left-front and right-back.
(5) Perform left and right drifting in place.
(6) Each action lasts for 2 seconds, with a 1-second interval between different actions.
2.1 STM32 Installation & Wiring
(1) Motor, Couplings & Mecanum-wheel Installation
(2) Expansion Board Installation
(3) STM32 Controller & Wiring
(4) LiPo Battery Installation
2.2 Hardware Introduction
2.2.1 STM32 Main Control Module Introduction
The main control module uses the STM32F407VET6 microcontroller, and the motor driver chip is YX4055 (with overcurrent protection). The voltage range is 3V–12V, depending on the motor voltage.
The interface layout is shown below:
| Interface Type | Interface Type Label |
Description |
| Encoder Motor Interface | GND | Hall sensor power negative |
| A | Pulse signal output for phase A | |
| B | Pulse signal output for phase B | |
| VCC | Hall sensor power positive | |
| M+ | Motor power positive | |
| M- | Motor power negative | |
Notes: The voltage between VCC and GND depends on the microcontroller power supply voltage (usually 3.3V or 5V). When the motor rotates clockwise, the phase A signal precedes the phase B signal; when rotating counterclockwise, the phase B signal precedes the phase A signal. The voltage between M+ and M- is determined by the motor voltage. |
||
| Power interface | + | Positive power electrode |
| - | Negative power electrode | |
2.2.2 Encoder Reduction Motor
The chassis is equipped with a motor model JGB37-528R131-08. The designation details are as follows:
(1) J: DC motor
(2) GB: Offset output shaft
(3) 37: Gearbox diameter (mm)
(4) 528: Motor model number
(5) R131: Reduction ratio of 131:1
(6) 08: Rated voltage of 8V
The interface details are shown in the diagram below:
The Hall encoder disk is aligned with the motor’s axis. As the motor rotates, the Hall sensor detects and generates a series of pulse signals. For direction detection, two sets of square wave signals with a specific phase difference are usually output.
2.3 Wiring Instructions
The wiring for the Mecanum-wheeled robot is as follows:
The two front motors (left and right) are connected to the M2 and M3 ports of the STM32 main control board.
The two rear motors (left and right) are connected to the M1 and M4 ports of the STM32 main control board.
The specific wiring diagram is shown below:
2.4 Program Download
After the project is compiled completely, download the generated hex file to the STM32 control board. Please prepare the following hardware materials:
2.4.1 Software & Hardware Preparation for USB Download
Software
FlyMcu (it is located in “Appendix-> STM32 Installation Pack/ USB Serial Port Download/ FlyMcu”)
Hardware
Type-C cable, STN32 main control board
2.4.2 USB Download Operation Steps
The specific operation steps are as follow:
Hardware connection
Use Type-C cable to connect the Type-C port (UART1) of the STM32 control board and the USB port of your computer:
[UART1]
Basic setting
(1) Open FlyMcu. Click “Enum Port” at top menu bar, then set the baud rate (bps) as 115200:
(2) Click “STMISP” option and configure as shown in the following figure.
(3) Select “DTR Low(<-3V),ISP @RTS High” at the bottom.
Software burning
(1) In the FlyMcu software interface, click
to select the hex file to be burned. The program below is just for your reference, please rely on the actual program. Select the hex file in “03 Programs/RosRobotControllerM4_mecanum_8V/MDK-ARM/RosRobotControllerM4/RosRobotControllerM4.hex”.
(2) Go back to the previous interface and click the “Start ISP” to burn the generated hex file onto the STM32 main control board:
(3) The burning is in progress:
(4) When the burning is complete, you will receive the following prompt.
In addition to use USB method for program download, you can also use either st-link or j-link method for direct compilation and burning.
Note
To avoid the presence of any exception during the burning process, please strictly follow the above operation steps to operate!
2.5 Program Outcome
Once the program is downloaded, the robot chassis will execute actions in the following order:
(1) Move forward, then move backward
(2) Move left, then move right
(3) Rotate left and right in place
(4) Move diagonally forward-left and backward-right
(5) Perform left and right drifting in place
Each action lasts for 2 seconds, with a 1-second interval between actions.
2.6 Source Code Analysis
2.6.1 Robot Motion Program Analysis
Robot Motion Settings
(1) When the chassis_init() function is called to initialize the Mecanum-wheeled chassis, the functions within the respective chassis structure variables are linked to the corresponding control functions, as shown in the diagram below.
82 83 84 85 | // Initialize chassis motor motion parameters chassis_init(); // Select chassis type set_chassis_type(Chassis_run_type); |
28 | uint32_t Chassis_run_type = CHASSIS_TYPE_JETAUTO; |
(2) In the program located at ..\Hiwonder\System\app.c, select the appropriate function to control the robot’s movement based on the chassis type. The mecanum_control() function is the control function for the Mecanum-wheeled chassis. First, set the movement along the X-axis (forward and backward). A speed of 200.0 means 200 millimeters per second; positive values represent forward movement, while negative values indicate backward movement.
105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | void mecanum_control(void) { // Define motor movement speed // Recommended range [50, 450] static float speed = 300.0f; // Move along the x-axis (i.e., forward movement) chassis->set_velocity(chassis, -200.0, 0, 0); osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Stop osDelay(1000); // Delay for 1 second // Move along the x-axis (i.e., backward movement) chassis->set_velocity(chassis, 200.0, 0, 0); osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Stop osDelay(1000); // Delay for 1 second } |
(3) Set the movement along the Y-axis (left and right translation) for the Mecanum-wheeled chassis. A speed of 200.0 means 200 millimeters per second; positive values represent movement to the left, while negative values represent movement to the right.
127 128 129 130 131 132 133 134 135 136 137 138 | // Moving at y-axis velocity (i.e., moving left) chassis->set_velocity(chassis, 0, -200.0, 0); osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Stop osDelay(1000); // Delay for 1 second chassis->set_velocity(chassis, 0, 200.0, 0); osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Stop osDelay(1000); // Delay for 1 second |
(4) Set the linear motion of the Mecanum-wheeled chassis in the left-front and right-back directions. A speed of 200.0 means 200 millimeters per second. When the robot moves forward:
Positive angular velocity values indicate movement towards the left.
Negative angular velocity values indicate movement towards the right.
When the robot moves forward:
Positive angular velocity values indicate linear motion towards the left-front.
Negative angular velocity values indicate linear motion towards the right-front.
When the robot moves backward:
Negative angular velocity values indicate linear motion towards the left-back.
Positive angular velocity values indicate linear motion towards the right-back.
186 187 188 189 190 191 192 193 194 195 196 197 198 | // Linear movement to front-left chassis->set_velocity(chassis, 200.0, 0, -0.3); // (vx, vy, omega) in mm/s and rad/s osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Full stop osDelay(1000); // Delay for 1 second // Linear movement to back-right chassis->set_velocity(chassis, -200.0, 0, 0.3); // Negative x for reverse osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Full stop osDelay(1000); // Delay for 1 second |
(5) Set the in-place rotation for the Mecanum-wheeled chassis. Positive angular velocity values indicate in-place rotation to the right, and negative angular velocity values indicate in-place rotation to the left.
141 142 143 144 145 146 147 148 149 150 151 152 153 | // Angular velocity movement (i.e., rotating in place to the left) chassis->set_velocity(chassis, 0, 0, 0.5); osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Stop osDelay(1000); // Delay for 1 second // Angular velocity movement (i.e., rotating in place to the right) chassis->set_velocity(chassis, 0, 0, -0.5); osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Stop osDelay(1000); // Delay for 1 second |
(6) Set the diagonal translation for the Mecanum-wheeled chassis. When vx and vy are set to a linear velocity of 100 mm/s, the robot will move diagonally at a 45°angle to the left. When vx and vy are set to a linear velocity of -100 mm/s, the robot will move diagonally at a 45° angle to the right. The combined motor speed will be 200 mm/s.
156 157 158 159 160 161 162 163 164 165 166 167 168 | // Diagonal movement - moving diagonally forward-left chassis->set_velocity(chassis, -100.0, -100.0, 0); osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Stop osDelay(1000); // Delay for 1 second // Diagonal movement - moving diagonally backward-right chassis->set_velocity(chassis, 100.0, 100.0, 0); osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Stop osDelay(1000); // Delay for 1 second |
(7) Set the left-right drifting motion for the Mecanum-wheeled chassis. A Boolean value of true indicates drifting to the right, and a Boolean value of false indicates drifting to the left.
171 172 173 174 175 176 177 178 179 180 181 182 183 | // Drift movement - drifting to the left chassis->drift(chassis, true); osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Stop osDelay(1000); // Delay for 1 second // Drift movement - drifting to the right chassis->drift(chassis, false); osDelay(2000); // Delay for 2 seconds chassis->stop(chassis); // Stop osDelay(1000); // Delay for 1 second |
Motion Calculation
(1) In the file located at ..\Hiwonder\Chassis\mecanum_chassis.c, the motion of the Mecanum-wheeled chassis is controlled by kinematic algorithms. The set_velocity() function is used to control the chassis’s linear motion.
{lineno-start=}
// Moving at x-axis velocity (i.e., moving backward)
chassis->set_velocity(chassis, 200.0, 0, 0);
100 101 102 103 104 105 106 | void mecanum_chassis_object_init(MecanumChassisTypeDef *self) { self->base.stop = stop; self->base.drift = drift; self->base.set_velocity = set_velocity; self->base.set_velocity_radius = set_velocity_radius; } |
83 84 85 86 87 | static void set_velocity(void *self, float vx, float vy, float angular_rate) { mecanum_chassis_set_xy(self, vx, vy, angular_rate); } |
(2) The diagram below shows the kinematic calculation for the Mecanum-wheeled chassis. The speeds of each motor are calculated to achieve normal robot movement. The process starts with vp, the angular velocity around the z-axis, which is then broken down into the real-time speeds of the four motors: v1, v2, v3, and v4. These speeds are converted into rotational speeds using the linear_speed_to_rps function. Finally, the set_motors() function sets the speeds of the left and right wheels.
42 43 | void mecanum_chassis_set_xy(MecanumChassisTypeDef *self, float vx, float vy, float angular_rate) { |
57 58 59 60 61 62 63 64 65 66 67 | float vp = angular_rate * (self->wheelbase/2 + self->shaft_length/2); float v1 = vx - vy - vp; float v2 = vx + vy + vp; float v3 = vx - vy + vp; float v4 = vx + vy - vp; v1 = linear_speed_to_rps(self, v1); v4 = linear_speed_to_rps(self, v4); v2 = linear_speed_to_rps(self, v2); v3 = linear_speed_to_rps(self, v3); self->set_motors(self, v1, v4, v2, v3); } |
(3) The linear_speed_to_rps() function is used to convert linear velocity into rotational speed. The wheel_diameter refers to the diameter of the wheel, and the correction_factor is a conversion factor, usually set to 1.0.
8 9 10 11 | static inline float linear_speed_to_rps(MecanumChassisTypeDef *self, float speed) { return speed / (PI * self->wheel_diameter) * self->correction_factor; } |
(4) The kinematic formulas used in the program for the Mecanum-wheeled chassis define the front-to-back wheelbase as wheelbase = H and the left-to-right track width as shaft_length = L, as illustrated in the diagram below:
(5) Calculate the rotational speed of the four wheels based on the robot’s motion state:
Stopping Motion Setting
(1) The following function, stop(), sets the rotational speed of all four motors to 0, stopping the robot’s motion.
137 | chassis->stop(chassis); //stop |
68 69 70 71 | static void stop(void *self) { ((MecanumChassisTypeDef*)self)->set_motors(self, 0, 0, 0, 0); } |
Drifting Setting
The following function, drift(), sets the speeds of the two rear wheels (v1 and v2) to 0.7. The positive or negative value indicates the forward or reverse direction of the motors. A Boolean value of true makes the robot drift to the right.
73 74 75 76 77 78 79 80 | static void drift(void *self, bool insitu) { if(insitu) { ((MecanumChassisTypeDef*)self)->set_motors(self, 0.7, 0, -0.7, 0); }else{ ((MecanumChassisTypeDef*)self)->set_motors(self, -0.7, 0, 0.7, 0); } } |
2.6.2 AB Quadrature Encoder Motor Analysis
The above analysis implements the basic movement of the robot. Next, we will analyze the specific motor control.
Timer Initialization
(1) In the main function of the main.c file, several timers are initialized. I will explain this process using Motor 1 as an example, as the setup is identical for other motors. Motor 1 involves Timer 1 (PWM control for motor speed), Timer 5 (encoder for motor speed measurement), Timer 7 (timer interrupt for updating motor speed measurement and PID control frequency), along with interrupt initialization. As shown in the diagram below, the parameters are configured in STM32CubeMX. You can also check these settings in the tim.c file. The three timers have been configured through the STM32CubeMX software interface, which provides a more intuitive experience (it is recommended to use STM32CubeMX for better visualization).
128 | MX_TIM1_Init(); |
130 | MX_TIM5_Init(); |
139 | MX_TIM7_Init(); |
(2) Timer 1 (PWM Control Motor Speed)
(3) Timer 5 (Encoder for Motor Speed Measurement)
(4) Timer 7 (Updates Motor Speed Measurement and PID Control Frequency)
Motor Initialization
(1) Before controlling the motor, relevant motor parameters need to be initialized. The initialization function is located in the app.c function, found in the folder ..\Hiwonder\System\app.c.
63 64 | /* Hardware Initialization */ motors_init(); // Motor initialization |
(2) The following diagram shows the portion of the program for motor initialization, located in ..\Hiwonder\motor_porting.c, where the motor structure is initialized. The “Chassis Motor Motion Parameter Initialization” section in the next chapter will overwrite these motor parameters. Simply choose the corresponding chassis type.
43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | void motors_init(void) { for(int i = 0; i < 4; ++i) { motors[i] = LWMEM_CCM_MALLOC(sizeof( EncoderMotorObjectTypeDef)); encoder_motor_object_init(motors[i]); motors[i]->ticks_overflow = 60000; motors[i]->ticks_per_circle = MOTOR_DEFAULT_TICKS_PER_CIRCLE; motors[i]->rps_limit = MOTOR_DEFAULT_RPS_LIMIT; motors[i]->pid_controller.set_point = 0.0f; motors[i]->pid_controller.kp = MOTOR_DEFAULT_PID_KP; motors[i]->pid_controller.ki = MOTOR_DEFAULT_PID_KI; motors[i]->pid_controller.kd = MOTOR_DEFAULT_PID_KD; } /* Motor 1 */ motors[0]->set_pulse = motor1_set_pulse; __HAL_TIM_SET_COUNTER(&htim1, 0); __HAL_TIM_ENABLE(&htim1); __HAL_TIM_MOE_ENABLE(&htim1); |
(3) LWMEM_CCM_MALLOC() dynamically creates a set of objects, allocates memory for objects of type EncoderMotorObjectTypeDef, and stores the memory address at the corresponding position in the motors array. The following diagram shows the structure parameters for motor1, located in ..\Hiwonder\Peripherals\encoder_motor.h. Refer to the comments in the diagram for the specific parameter content.
20 21 22 23 24 25 26 27 28 29 30 | /** * @brief Encoder motor object structure */ struct EncoderMotorObject { int64_t counter; /**< @brief Total count value, 64-bit to prevent overflow */ int64_t overflow_num; /**< @brief Overflow count */ int32_t ticks_overflow; /**< @brief Count overflow threshold */ float tps; /**< @brief Ticks per second (counter frequency) */ float rps; /**< @brief Revolutions per second (output shaft speed) */ int current_pulse; /**< @brief Current PWM output value (signed for direction control) */ PID_ControllerTypeDef pid_controller; /**< @brief PID controller */ |
(4) Initialize the defined structure (encoder_motor_object_init), located at ..\Hiwonder\Portings\motor_porting.c.
47 | encoder_motor_object_init(motors[i]); |
(5) The program in ..\Hiwonder\Peripherals\encoder_motor.c is shown in the diagram below.
72 73 74 75 76 77 78 79 80 81 82 | void encoder_motor_object_init(EncoderMotorObjectTypeDef *self) { self->counter = 0; self->overflow_num = 0; self->tps = 0; self->rps = 0; self->current_pulse = 0; self->ticks_overflow = 0; self->ticks_per_circle = 9999; /* Number of encoder ticks per full rotation of the motor output shaft (set according to actual motor specifications) */ pid_controller_init(&self->pid_controller, 0, 0, 0); } |
(6) Use TIM1 to generate the PWM signal, as shown in the diagram below.
57 58 59 60 61 | /* Motor 1 */ motors[0]->set_pulse = motor1_set_pulse; __HAL_TIM_SET_COUNTER(&htim1, 0); __HAL_TIM_ENABLE(&htim1); __HAL_TIM_MOE_ENABLE(&htim1); |
(7) Use TIM5 to control and initialize the encoder for the motor, as shown in the diagram below.
64 65 66 67 68 | __HAL_TIM_SET_COUNTER(&htim5, 0); __HAL_TIM_CLEAR_IT(&htim5, TIM_IT_UPDATE); __HAL_TIM_ENABLE_IT(&htim5, TIM_IT_UPDATE); __HAL_TIM_ENABLE(&htim5); HAL_TIM_Encoder_Start(&htim5, TIM_CHANNEL_ALL); |
(8) Use TIM7 to calculate the encoder values to measure speed, as shown in the diagram below.
115 116 117 118 | __HAL_TIM_SET_COUNTER(&htim7, 0); __HAL_TIM_CLEAR_IT(&htim7, TIM_IT_UPDATE); __HAL_TIM_ENABLE_IT(&htim7, TIM_IT_UPDATE); __HAL_TIM_ENABLE(&htim7); |
Chassis Motor Motion Parameter Initialization
In the ..\Hiwonder\System\app.c file, the chassis motor motion parameters are initialized. For the Mecanum wheel chassis, the type used is CHASSIS_TYPE_MECANUM.
27 28 | //Chassis Type uint32_t Chassis_run_type = CHASSIS_TYPE_JETAUTO; |
82 83 84 85 | //Initialize chassis motor motion parameters chassis_init(); //Select Chassis Type set_chassis_type(Chassis_run_type); |
(2) In the ..\Hiwonder\Portings\chassis_porting.c file, the function chassis_init() initializes the chassis motor motion parameters.
73 | void chassis_init(void); |
96 97 98 99 100 101 102 | mecanum_chassis_object_init(&jetauto); jetauto.base.chassis_type = CHASSIS_TYPE_JETAUTO; jetauto.correction_factor = JETAUTO_CORRECITION_FACTOR; jetauto.wheel_diameter = JETAUTO_WHEEL_DIAMETER; jetauto.shaft_length = JETAUTO_SHAFT_LENGTH; jetauto.wheelbase = JETAUTO_WHEELBASE; jetauto.set_motors = jetauto_set_motors; |
(3) The macro definitions for the parameters can be found in ..\Hiwonder\Chassis\chassis.h. The parameters include:
WHEEL_DIAMETER: The diameter of the wheels.SHAFT_LENGTH: The axle length, i.e., the distance between the left and right wheels.WHEELBASE: The wheelbase, i.e., the distance between the front and rear wheels.CORRECTION_FACTOR: The conversion factor (currently unused).
52 53 54 55 | #define MINACKER_WHEEL_DIAMETER 67.0 /* mm */ #define MINACKER_CORRECITION_FACTOR 1.0 /* mm */ #define MINACKER_SHAFT_LENGTH 174.0 /* mm */ #define MINACKER_WHEELBASE 170.0 /* mm */ |
(4) Below is the initialization of the Mecanum wheel chassis motor.
123 | void set_chassis_type(uint8_t chassis_type) |
126 127 128 129 130 131 132 | case CHASSIS_TYPE_JETAUTO: chassis = (ChassisTypeDef*)&jetauto; set_motor_type(motors[0], MOTOR_TYPE_JGB528); set_motor_type(motors[1], MOTOR_TYPE_JGB528); set_motor_type(motors[2], MOTOR_TYPE_JGB528); set_motor_type(motors[3], MOTOR_TYPE_JGB528); break; |
(5) Through function calls, the relevant motor parameters can be obtained from ..\Hiwonder\Portings\motors_param.h.
15 16 17 18 19 | #define MOTOR_JGB520_TICKS_PER_CIRCLE 1320.0f #define MOTOR_JGB520_PID_KP 63.0f #define MOTOR_JGB520_PID_KI 2.6f #define MOTOR_JGB520_PID_KD 2.4f #define MOTOR_JGB520_RPS_LIMIT 1.5f |
PWM Motor Speed Control
(1) As explained in the previous section, in the function mecanum_chassis_set_velocity, the speeds of the four motors are set by self->set_motors(self, v1, v4, v2, v3). For the Mecanum wheel chassis, jetauto_set_motors is used to set the speeds of the four motors.
96 97 98 99 100 101 102 | mecanum_chassis_object_init(&jetauto); jetauto.base.chassis_type = CHASSIS_TYPE_JETAUTO; jetauto.correction_factor = JETAUTO_CORRECITION_FACTOR; jetauto.wheel_diameter = JETAUTO_WHEEL_DIAMETER; jetauto.shaft_length = JETAUTO_SHAFT_LENGTH; jetauto.wheelbase = JETAUTO_WHEELBASE; jetauto.set_motors = jetauto_set_motors; |
(2) The jump to the jetauto_set_motors function is handled in minacker_set_motors(), as shown in the diagram, where the speeds of motors 1, 2, 3, and 4 are set.
39 40 41 42 43 44 45 | static void jetauto_set_motors(void* self, float rps_lh, float rps_lt, float rps_rh, float rps_rt) { encoder_motor_set_speed(motors[3], -rps_lh); encoder_motor_set_speed(motors[2], -rps_lt); encoder_motor_set_speed(motors[0], rps_rh); encoder_motor_set_speed(motors[1], rps_rt); } |
(3) In ..\Hiwonder\Peripherals\encoder_motor.c, speed limiting is implemented, and the PID controller’s target speed is set.
60 61 62 63 64 | void encoder_motor_set_speed(EncoderMotorObjectTypeDef *self, float rps) { rps = rps > self->rps_limit ? self->rps_limit : (rps < -self->rps_limit ? -self->rps_limit : rps); /* Speed limiting/clamping */ self->pid_controller.set_point = rps; /* Set PID controller target */ } |
(4) encoder_motor_control() is the function that updates the encoder motor’s speed control. It adjusts the PWM values to stabilize the motor at the target speed previously set. It first updates the PID parameters, then adjusts the output PWM. The self->set_pulse() function sets the new PWM signal, as shown in ..\Hiwonder\Peripherals\encoder_motor.c.
32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | /** * @brief Encoder motor speed control task * @details PID control task for encoder motor speed, requires regular timing updates * @param self Pointer to the encoder motor object * @param period Time interval since last update (update cycle) in seconds * @retval None. */ void encoder_motor_control(EncoderMotorObjectTypeDef *self, float period) { float pulse = 0; pid_controller_update(&self->pid_controller, self->rps, period); /* Update PID controller */ pulse = self->current_pulse + self->pid_controller.output; /* Calculate new PWM value */ /* Clamp the output PWM value (0-100% duty cycle corresponds to 0-1000 in this example) */ pulse = pulse > 1000 ? 1000 : pulse; pulse = pulse < -1000 ? -1000 : pulse; self->set_pulse(self, pulse > -250 && pulse < 250 ? 0 : pulse); /* Set new PWM value with dead zone (motors may buzz without movement at very low PWM) */ self->current_pulse = pulse; /* Record new PWM value */ } |
(5) self->set_pulse passes the PWM values to the motor1_set_pulse() function. When the speed is positive, TIM_CHANNEL_4 (forward) is activated. When the speed is negative, TIM_CHANNEL_3 (reverse) is activated. When the speed is zero, both channels stop, and the motor halts. This process is shown in ..\Hiwonder\Portings\motor_porting.c.
57 58 | /* Motor 1 */ motors[0]->set_pulse = motor1_set_pulse; |
123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 | static void motor1_set_pulse(EncoderMotorObjectTypeDef *self, int speed) { if(speed > 0) { __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, 0); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4, speed); } else if(speed < 0) { __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4, 0); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, -speed); } else { __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_3, 0); __HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_4, 0); } HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_3); HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_4); } |
(6) According to the STM32CubeMX configuration, TIM1 has a clock frequency of 84MHz, a prescaler value of 839, and a count period of 999. The timer counts from 0 to 999 for each PWM cycle. The pulse is the PWM duty cycle, and when the counter reaches the duty cycle value, the high and low frequencies flip. Once the count reaches 999, the timer resets. The pulse is used to set the timer’s compare match register, controlling the PWM signal’s duty cycle.
(7) The timer overflow time is calculated as follows:
Timer overflow time = (prescaler + 1) * (counter period + 1) / timer clock frequency
Thus, for TIM1, the interrupt cycle becomes 0.01 seconds, or 10ms per update interrupt.
Calculation: Timer overflow time = (999+1) * (839+1) / 84MHz = 10ms
(8) The PWM duty cycle adjustment allows the motor to adjust between its maximum and minimum speeds. The motor speed limit is set to 1.5 revolutions per second in ..\Hiwonder\Portings\motors_param.h.
19 | #define MOTOR_JGB520_RPS_LIMIT 1.5f
|
(9) Based on the interrupt function of TIM7, the PWM value is updated every 10 milliseconds to control the motor speed.The interrupt function is located in ..\Core\Src\stm32f4xx_it.c.
504 505 506 507 508 509 510 511 512 513 514 515 516 517 | void TIM7_IRQHandler(void) { /* USER CODE BEGIN TIM7_IRQn 0 */ extern EncoderMotorObjectTypeDef *motors[4]; if(__HAL_TIM_GET_FLAG(&htim7, TIM_FLAG_UPDATE) != RESET) { __HAL_TIM_CLEAR_FLAG(&htim7, TIM_FLAG_UPDATE); encoder_update(motors[0], 0.01, __HAL_TIM_GET_COUNTER(&htim5)); encoder_update(motors[1], 0.01, __HAL_TIM_GET_COUNTER(&htim2)); encoder_update(motors[2], 0.01, __HAL_TIM_GET_COUNTER(&htim4)); encoder_update(motors[3], 0.01, __HAL_TIM_GET_COUNTER(&htim3)); for(int i = 0; i < 4; ++i) { encoder_motor_control(motors[i], 0.01); } } |
(10) According to the STM32CubeMX configuration, TIM7 is connected to APB1 with a clock frequency of 84MHz, and the prescaler value is set to 84, resulting in a frequency of 1MHz.
(11) Using the same formula for the timer overflow time:
Timer overflow time = (prescaler + 1) * (counter period + 1) / timer clock frequency
Calculation: Timer overflow time = (83+1) * (9999+1) / 84MHz = 10ms
Updating Encoder Motor Speed
(1) Timer 7 generates an update interrupt every 10ms to calculate the motor speed and update the PID parameters. The interrupt function is located in ..\Core\Src\stm32f4xx_it.c. Within this function, Timer 5 retrieves and updates the motor 1 encoder speed measurement.
504 505 506 507 508 509 510 511 512 513 514 515 516 517 | void TIM7_IRQHandler(void) { /* USER CODE BEGIN TIM7_IRQn 0 */ extern EncoderMotorObjectTypeDef *motors[4]; if(__HAL_TIM_GET_FLAG(&htim7, TIM_FLAG_UPDATE) != RESET) { __HAL_TIM_CLEAR_FLAG(&htim7, TIM_FLAG_UPDATE); encoder_update(motors[0], 0.01, __HAL_TIM_GET_COUNTER(&htim5)); encoder_update(motors[1], 0.01, __HAL_TIM_GET_COUNTER(&htim2)); encoder_update(motors[2], 0.01, __HAL_TIM_GET_COUNTER(&htim4)); encoder_update(motors[3], 0.01, __HAL_TIM_GET_COUNTER(&htim3)); for(int i = 0; i < 4; ++i) { encoder_motor_control(motors[i], 0.01); } } |
(2) encoder_update() is the function that calculates and updates the motor speed using the encoder count values. The encoder count is used to calculate the motor speed. The overflow_num represents the number of overflows for Timer 5, and the delta_count represents the change in the encoder count during the timer period.
The tps (pulses per second) is calculated by dividing the count change by the time interval. A filter with coefficients of 0.9 and 0.1 is applied to smooth the results. Finally, rps (revolutions per second) is the motor speed, calculated as:
The number of pulses per revolution of the motor is detailed in the next point. The encoder_update function can be found in ..\Hiwonder\Peripherals\encoder_motor.c.
Formula for motor speed (rps):
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | /** * @brief Encoder motor speed measurement update * @details * @param self Pointer to the encoder motor object * @param period Time interval since last update (update cycle) in seconds * @param counter Current encoder count value * @retval None. */ void encoder_update(EncoderMotorObjectTypeDef *self, float period, int64_t counter) { counter = counter + self->overflow_num * self->ticks_overflow; /* Total count value (60000 based on actual timer overflow setting) */ int delta_count = counter - self->counter; self->counter = counter; /* Store new count value */ self->tps = (float)delta_count / period * 0.9f + self->tps * 0.1f; /* Calculate pulse frequency (with first-order filtering) */ self->rps = self->tps / self->ticks_per_circle; /* Calculate rotation speed in RPS (revolutions per second) */ } |
(3) According to the motor manual, the motor shaft generates 11 pulses per revolution, and the gear ratio is 45:1. The timer uses a 4x frequency counting method, meaning that each revolution of the motor’s output shaft results in a count change of:
11.0 * 4.0 * 30.0 = 1320
Every time the motor output shaft completes one revolution, the encoder interface records an increase of 3960 counts. This information can be found in ..\Hiwonder\Portings\motors_param.h.
15 16 17 18 19 | #define MOTOR_JGB520_TICKS_PER_CIRCLE 1320.0f #define MOTOR_JGB520_PID_KP 63.0f #define MOTOR_JGB520_PID_KI 2.6f #define MOTOR_JGB520_PID_KD 2.4f #define MOTOR_JGB520_RPS_LIMIT 1.5f |
As shown in the diagram, the 4x frequency counting method generates a pulse, and each pulse is counted four times. For more details on the implementation, refer to Appendix 3: Using Encoders.
(4) When the encoder count overflows, the interrupt for Timer 5 is triggered. The overflow count is adjusted, with the overflow count decreasing downward and increasing upward. This functionality is shown in the program located in ..\Core\Src\stm32f4xx_it.c.
469 470 471 472 473 474 475 476 477 478 479 | void TIM5_IRQHandler(void) { /* USER CODE BEGIN TIM5_IRQn 0 */ extern EncoderMotorObjectTypeDef *motors[4]; if(__HAL_TIM_GET_FLAG(&htim5, TIM_FLAG_UPDATE) != RESET) { __HAL_TIM_CLEAR_FLAG(&htim5, TIM_FLAG_UPDATE); if(__HAL_TIM_IS_TIM_COUNTING_DOWN(&htim5)) { --motors[0]->overflow_num; } else { ++motors[0]->overflow_num; } |
2.6.3 PID Controller for Motor Speed Stability Analysis
Position-based PID Algorithm
The PID controller update function (pid_controller_update()) is used for position-based PID control. The implementation is as shown in the code located at ..\Hiwonder\Misc\pid.c.
15 16 17 18 19 20 21 22 23 24 25 26 | void pid_controller_update(PID_ControllerTypeDef *self, float actual, float time_delta) { float err = self->set_point - actual; float proportion = err - self->previous_0_err; float integral = err * time_delta; float derivative = (err - 2 * self->previous_1_err + self->previous_0_err) / time_delta; self->output = (self->kp * err) + (self->ki * integral) + (self->kd * derivative); self->previous_1_err = self->previous_0_err; self->previous_0_err = err; } |
(1) Error Calculation (err)
The error is calculated as the difference between the target value (set point, self->set_point, desired speed) and the actual value (current speed, actual):
Error = Target value − Actual value
(2) Proportional (P) Term
The proportional term (proportion) represents the current error difference between the current and previous errors. This term is used to calculate the derivative (though this specific program does not use it).
Proportional = Error − Previous error
(3) Integral (I) Term
The integral term (integral) is the cumulative sum of errors over time. It is used in combination with the derivative term to eliminate long-term steady-state errors.
Integral = Error × Time Interval
(4) Derivative (D) Term
The derivative term (derivative) calculates the rate of change of the error, predicting future system behavior and quickly responding to error changes.
(6) PID Output to PWM Conversion
The PID controller output is stored in the output of the PID_ControllerTypeDef structure. After calling the pid_controller_update() function, the PID controller output (self->output) is used to adjust the PWM signal’s duty cycle to control motor speed. A positive or negative PID output indicates forward or reverse rotation, respectively.
(7) Update Previous Errors
After computing the PID output, the previous and previous error parameters are updated.
(8) The program uses the standard position-based PID formula, which is expressed as shown in the following diagram: