Skip to content

Commit af997e6

Browse files
authored
Merge pull request #514 from simplefoc/feat_custom_motion_control
Feat custom motion control
2 parents a8e5793 + 9b0c133 commit af997e6

File tree

8 files changed

+159
-22
lines changed

8 files changed

+159
-22
lines changed

.github/workflows/arduino.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ jobs:
3333
esp32_current_control_low_side, stm32_spi_alt_example, esp32_spi_alt_example,
3434
B_G431B_ESC1, odrive_example_spi, odrive_example_encoder, single_full_control_example,
3535
double_full_control_example, stm32_current_control_low_side, open_loop_velocity_6pwm,
36-
efr32_hall_sensor_velocity_6pwm, efr32_open_loop_velocity_6pwm, efr32_torque_velocity_6pwm
36+
efr32_hall_sensor_velocity_6pwm, efr32_open_loop_velocity_6pwm, efr32_torque_velocity_6pwm,
37+
custom_motion_control
3738

3839
- arduino-boards-fqbn: arduino:sam:arduino_due_x # arduino due - one full example
3940
sketch-names: single_full_control_example.ino

.github/workflows/stm32.yml

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,14 @@ jobs:
1616
strategy:
1717
matrix:
1818
arduino-boards-fqbn:
19-
- STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 # stm32 bluepill
20-
- STMicroelectronics:stm32:Nucleo_64:pnum=NUCLEO_F411RE # stm32 nucleo
21-
- STMicroelectronics:stm32:Nucleo_144:pnum=NUCLEO_F746ZG # stm32 nucleo f746zg
22-
- STMicroelectronics:stm32:GenF4:pnum=GENERIC_F405RGTX # stm32f405 - odrive
23-
- STMicroelectronics:stm32:GenL4:pnum=GENERIC_L475RGTX # stm32l475
24-
- STMicroelectronics:stm32:Disco:pnum=B_G431B_ESC1 # B-G431-ESC1
19+
- STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 # stm32 bluepill
20+
- STMicroelectronics:stm32:Nucleo_64:pnum=NUCLEO_F411RE # stm32 nucleo
21+
- STMicroelectronics:stm32:Nucleo_144:pnum=NUCLEO_F746ZG # stm32 nucleo f746zg
22+
- STMicroelectronics:stm32:GenF4:pnum=GENERIC_F405RGTX # stm32f405 - odrive
23+
- STMicroelectronics:stm32:GenL4:pnum=GENERIC_L475RGTX # stm32l475
24+
- STMicroelectronics:stm32:Disco:pnum=B_G431B_ESC1 # B-G431-ESC1
25+
- STMicroelectronics:stm32:Nucleo_144:pnum=NUCLEO_H723ZG # stm32h7
26+
- STMicroelectronics:stm32:Nucleo_64:pnum=NUCLEO_G431RB # stm32g4
2527

2628
include:
2729
- arduino-boards-fqbn: STMicroelectronics:stm32:GenF1:pnum=BLUEPILL_F103C8 # bluepill - hs examples
@@ -45,12 +47,19 @@ jobs:
4547

4648
- arduino-boards-fqbn: STMicroelectronics:stm32:Nucleo_64:pnum=NUCLEO_F411RE # nucleo one full example
4749
platform-url: https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
48-
sketch-names: single_full_control_example.ino, stm32_spi_alt_example.ino, double_full_control_example.ino, stm32_current_control_low_side.ino
50+
sketch-names: single_full_control_example.ino, stm32_spi_alt_example.ino, double_full_control_example.ino, stm32_current_control_low_side.ino, custom_motion_control.ino
4951

5052
- arduino-boards-fqbn: STMicroelectronics:stm32:Nucleo_144:pnum=NUCLEO_F746ZG # nucleo f7 one full example
5153
platform-url: https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
52-
sketch-names: single_full_control_example.ino, stm32_spi_alt_example.ino, double_full_control_example.ino, stm32_current_control_low_side.ino
53-
54+
sketch-names: single_full_control_example.ino, stm32_spi_alt_example.ino, double_full_control_example.ino, stm32_current_control_low_side.ino, custom_motion_control.ino
55+
56+
- arduino-boards-fqbn: STMicroelectronics:stm32:Nucleo_144:pnum=NUCLEO_H723ZG # nucleo h7 one full example
57+
platform-url: https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
58+
sketch-names: single_full_control_example.ino, stm32_spi_alt_example.ino, double_full_control_example.ino, stm32_current_control_low_side.ino, custom_motion_control.ino
59+
60+
- arduino-boards-fqbn: STMicroelectronics:stm32:Nucleo_64:pnum=NUCLEO_G431RB # stm32g4 one full example
61+
platform-url: https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json
62+
sketch-names: single_full_control_example.ino, stm32_spi_alt_example.ino, double_full_control_example.ino, stm32_current_control_low_side.ino, custom_motion_control.ino
5463

5564
# Do not cancel all jobs / architectures if one job fails
5665
fail-fast: false
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
2+
#include <Arduino.h>
3+
#include <SimpleFOC.h>
4+
#include "current_sense/hardware_specific/stm32/stm32_mcu.h"
5+
6+
7+
// BLDC motor & driver instance
8+
BLDCMotor motor = BLDCMotor(7);
9+
BLDCDriver3PWM driver = BLDCDriver3PWM(D6, D10, D5, D8);
10+
11+
// encoder instance
12+
MagneticSensorSPI sensor = MagneticSensorSPI(AS5048_SPI, D4);
13+
14+
// inline current sensor instance
15+
// INA240A1 (gain 20V/V) and 5mOhm shunt resistor
16+
LowsideCurrentSense current_sense = LowsideCurrentSense(0.005, 20.0f, A0, _NC, A3);
17+
18+
// commander communication instance
19+
Commander command = Commander(Serial);
20+
// void doMotion(char* cmd){ command.motion(&motor, cmd); }
21+
void doMotor(char* cmd){ command.motor(&motor, cmd); }
22+
23+
24+
// custom PID controller instance for the custom control method
25+
// P controller with gain of 1.0f, no integral or derivative gain
26+
PIDController custom_PID = PIDController(1.0f, 0, 0);
27+
// custom motion control method
28+
float positionPControl(FOCMotor* motor, float target){
29+
// simple proportional position control
30+
float error = target - motor->shaft_angle;
31+
// set the PID output limit to the motor current limit
32+
custom_PID.limit = motor->current_limit;
33+
return custom_PID(error); // return current command based on the error
34+
}
35+
36+
// optional add the PID to command to be able to tune it in runtime
37+
void doPID(char* cmd){ command.pid(&custom_PID, cmd); }
38+
39+
void setup() {
40+
// use monitoring with serial
41+
Serial.begin(115200);
42+
// enable more verbose output for debugging
43+
// comment out if not needed
44+
SimpleFOCDebug::enable(&Serial);
45+
46+
// initialize sensor hardware
47+
sensor.init();
48+
motor.linkSensor(&sensor);
49+
50+
// driver config
51+
// power supply voltage [V]
52+
driver.voltage_power_supply = 20;
53+
driver.init();
54+
// link driver
55+
motor.linkDriver(&driver);
56+
// link current sense and the driver
57+
current_sense.linkDriver(&driver);
58+
59+
// set the custom control method
60+
motor.linkCustomMotionControl(positionPControl);
61+
// set control loop type to be used
62+
motor.controller = MotionControlType::custom;
63+
// set the torque control type to voltage control (default is voltage control)
64+
motor.torque_controller = TorqueControlType::foc_current;
65+
66+
// comment out if not needed
67+
motor.useMonitoring(Serial);
68+
motor.monitor_downsample = 0; // disable intially
69+
motor.monitor_variables = _MON_TARGET | _MON_VEL | _MON_ANGLE; // monitor target velocity and angle
70+
71+
// subscribe motor to the commander
72+
//command.add('T', doMotion, "motion control"); // a bit less resouce intensive
73+
command.add('M', doMotor, "motor");
74+
command.add('C', doPID, "custom PID");
75+
76+
// current sense init and linking
77+
current_sense.init();
78+
motor.linkCurrentSense(&current_sense);
79+
80+
// initialise motor
81+
motor.init();
82+
// align encoder and start FOC
83+
motor.initFOC();
84+
85+
_delay(1000);
86+
}
87+
88+
void loop() {
89+
// iterative setting FOC phase voltage
90+
motor.loopFOC();
91+
92+
// iterative function setting the outter loop target
93+
motor.move();
94+
95+
// // motor monitoring
96+
motor.monitor();
97+
98+
// user communication
99+
command.run();
100+
}

src/common/base_classes/FOCMotor.cpp

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,8 @@ void FOCMotor::updateMotionControlType(MotionControlType new_motion_controller)
521521
target = 0;
522522
break;
523523
default:
524+
// if torque control set target to zero
525+
target = 0;
524526
break;
525527
}
526528

@@ -584,7 +586,8 @@ void FOCMotor::loopFOC() {
584586
if (sensor) sensor->update();
585587

586588
// if disabled do nothing
587-
if(!enabled) return;
589+
// or if the motor is not ready (e.g. failed to calibrate or not calibrated yet) do nothing
590+
if(!enabled || motor_status != FOCMotorStatus::motor_ready) return;
588591

589592
// if open-loop do nothing
590593
if( controller==MotionControlType::angle_openloop || controller==MotionControlType::velocity_openloop )
@@ -691,7 +694,8 @@ void FOCMotor::move(float new_target) {
691694
}
692695

693696
// if disabled do nothing
694-
if(!enabled) return;
697+
// and if
698+
if(!enabled || motor_status != FOCMotorStatus::motor_ready) return;
695699

696700

697701
// upgrade the current based voltage limit
@@ -745,6 +749,13 @@ void FOCMotor::move(float new_target) {
745749
// returned values correspond to the voltage_limit and current_limit
746750
current_sp = angleOpenloop(shaft_angle_sp);
747751
break;
752+
case MotionControlType::custom:
753+
// custom control - user provides the function that calculates the current_sp
754+
// based on the target value and the motor state
755+
// user makes sure to use it with appropriate torque control mode
756+
if(customMotionControlCallback)
757+
current_sp = customMotionControlCallback(this, target);
758+
break;
748759
}
749760
}
750761

@@ -768,8 +779,8 @@ int FOCMotor::initFOC() {
768779
SIMPLEFOC_MOTOR_DEBUG("No sensor.");
769780
if ((controller == MotionControlType::angle_openloop || controller == MotionControlType::velocity_openloop)){
770781
exit_flag = 1;
771-
SIMPLEFOC_MOTOR_ERROR("Openloop only!");
772782
}else{
783+
SIMPLEFOC_MOTOR_ERROR("Only openloop allowed!");
773784
exit_flag = 0; // no FOC without sensor
774785
}
775786
}
@@ -781,13 +792,13 @@ int FOCMotor::initFOC() {
781792
if(current_sense){
782793
if (!current_sense->initialized) {
783794
motor_status = FOCMotorStatus::motor_calib_failed;
784-
SIMPLEFOC_MOTOR_ERROR("Init FOC error, current sense not init");
795+
SIMPLEFOC_MOTOR_ERROR("Current sense not init!");
785796
exit_flag = 0;
786797
}else{
787798
exit_flag *= alignCurrentSense();
788799
}
789800
}
790-
else { SIMPLEFOC_MOTOR_ERROR("No current sense"); }
801+
else { SIMPLEFOC_MOTOR_ERROR("No current sense."); }
791802
}
792803

793804
if(exit_flag){

src/common/base_classes/FOCMotor.h

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ enum MotionControlType : uint8_t {
4848
angle = 0x02, //!< Position/angle motion control
4949
velocity_openloop = 0x03,
5050
angle_openloop = 0x04,
51-
angle_nocascade = 0x05 //!< Position/angle motion control without velocity cascade
51+
angle_nocascade = 0x05, //!< Position/angle motion control without velocity cascade
52+
custom = 0x06 //!< Custom control method - control method added by user
5253
};
5354

5455
/**
@@ -367,6 +368,16 @@ class FOCMotor
367368
*/
368369
float angleOpenloop(float target_angle);
369370

371+
372+
/**
373+
* Function setting a custom motion control method defined by the user
374+
* @note the custom control method has to be defined by the user and should follow the signature: float controlMethod(FOCMotor* motor, float target)
375+
* @param controlMethod - pointer to the custom control method function defined by the user
376+
*/
377+
void linkCustomMotionControl(float (*controlMethod)(FOCMotor* motor, float target)){
378+
customMotionControlCallback = controlMethod;
379+
}
380+
370381
protected:
371382

372383
/**
@@ -410,6 +421,8 @@ class FOCMotor
410421
// open loop variables
411422
uint32_t open_loop_timestamp;
412423

424+
// function pointer for custom control method
425+
float (*customMotionControlCallback)(FOCMotor* motor, float target) = nullptr;
413426

414427
};
415428

src/common/pid.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,18 @@ float PIDController::operator() (float error){
3737
// u_ik = u_ik_1 + I*Ts/2*(ek + ek_1)
3838
float integral = integral_prev + I*dt*0.5f*(error + error_prev);
3939
// antiwindup - limit the output
40-
integral = _constrain(integral, -limit, limit);
40+
if(_isset(limit)) integral = _constrain(integral, -limit, limit);
4141
// Discrete derivation
4242
// u_dk = D(ek - ek_1)/Ts
4343
float derivative = D*(error - error_prev)/dt;
4444

4545
// sum all the components
4646
float output = proportional + integral + derivative;
4747
// antiwindup - limit the output variable
48-
output = _constrain(output, -limit, limit);
48+
if(_isset(limit)) output = _constrain(output, -limit, limit);
4949

5050
// if output ramp defined
51-
if(output_ramp > 0){
51+
if(_isset(output_ramp) && output_ramp > 0){
5252
// limit the acceleration by ramping the output
5353
float output_rate = (output - output_prev)/dt;
5454
if (output_rate > output_ramp)

src/common/pid.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ class PIDController
2525
* @note Sampling time can be changed dynamically as well by modifying the
2626
* variable Ts in runtime.
2727
*/
28-
PIDController(float P, float I, float D, float ramp, float limit, float sampling_time = NOT_SET);
28+
PIDController(float P, float I, float D, float ramp = NOT_SET, float limit = NOT_SET, float sampling_time = NOT_SET);
2929
~PIDController() = default;
3030

3131
float operator() (float error);

src/communication/Commander.cpp

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -439,7 +439,7 @@ void Commander::motion(FOCMotor* motor, char* user_cmd, char* separator){
439439
break;
440440
default:
441441
// change control type
442-
if(!GET && value >= 0 && (int)value < 6) // if set command
442+
if(!GET && value >= 0 && (int)value < 7) // if set command
443443
motor->updateMotionControlType((MotionControlType)value); // update motion control type
444444
switch(motor->controller){
445445
case MotionControlType::torque:
@@ -460,14 +460,17 @@ void Commander::motion(FOCMotor* motor, char* user_cmd, char* separator){
460460
case MotionControlType::angle_nocascade:
461461
println(F("angle nocascade"));
462462
break;
463+
case MotionControlType::custom:
464+
println(F("custom"));
465+
break;
463466
}
464467
break;
465468
}
466469
break;
467470
case CMD_TORQUE_TYPE:
468471
// change control type
469472
printVerbose(F("Torque: "));
470-
if(!GET && (int8_t)value >= 0 && (int8_t)value < 4)// if set command
473+
if(!GET && (int8_t)value >= 0 && (int8_t)value < 4) // if set command
471474
motor->updateTorqueControlType((TorqueControlType)value); // update torque control type
472475
switch(motor->torque_controller){
473476
case TorqueControlType::voltage:

0 commit comments

Comments
 (0)