Skip to content

Commit 3b7fa90

Browse files
committed
added initial support for current sensing for stepper motors
1 parent 492d147 commit 3b7fa90

21 files changed

+550
-582
lines changed

src/BLDCMotor.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
#include "Arduino.h"
55
#include "common/base_classes/FOCMotor.h"
66
#include "common/base_classes/Sensor.h"
7+
#include "common/base_classes/FOCDriver.h"
78
#include "common/base_classes/BLDCDriver.h"
89
#include "common/foc_utils.h"
910
#include "common/time_utils.h"

src/StepperMotor.cpp

Lines changed: 116 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -108,12 +108,28 @@ int StepperMotor::initFOC() {
108108
// alignment necessary for encoders!
109109
// sensor and motor alignment - can be skipped
110110
// by setting motor.sensor_direction and motor.zero_electric_angle
111-
_delay(500);
112111
if(sensor){
113112
exit_flag *= alignSensor();
114113
// added the shaft_angle update
115114
sensor->update();
116-
shaft_angle = sensor->getAngle();
115+
shaft_angle = shaftAngle();
116+
117+
// aligning the current sensor - can be skipped
118+
// checks if driver phases are the same as current sense phases
119+
// and checks the direction of measuremnt.
120+
if(exit_flag){
121+
if(current_sense){
122+
if (!current_sense->initialized) {
123+
motor_status = FOCMotorStatus::motor_calib_failed;
124+
SIMPLEFOC_DEBUG("MOT: Init FOC error, current sense not initialized");
125+
exit_flag = 0;
126+
}else{
127+
exit_flag *= alignCurrentSense();
128+
}
129+
}
130+
else { SIMPLEFOC_DEBUG("MOT: No current sense."); }
131+
}
132+
117133
} else {
118134
SIMPLEFOC_DEBUG("MOT: No sensor.");
119135
if ((controller == MotionControlType::angle_openloop || controller == MotionControlType::velocity_openloop)){
@@ -136,6 +152,26 @@ int StepperMotor::initFOC() {
136152
return exit_flag;
137153
}
138154

155+
// Calibrate the motor and current sense phases
156+
int StepperMotor::alignCurrentSense() {
157+
int exit_flag = 1; // success
158+
159+
SIMPLEFOC_DEBUG("MOT: Align current sense.");
160+
161+
// align current sense and the driver
162+
exit_flag = current_sense->driverAlign(voltage_sensor_align);
163+
if(!exit_flag){
164+
// error in current sense - phase either not measured or bad connection
165+
SIMPLEFOC_DEBUG("MOT: Align error!");
166+
exit_flag = 0;
167+
}else{
168+
// output the alignment status flag
169+
SIMPLEFOC_DEBUG("MOT: Success: ", exit_flag);
170+
}
171+
172+
return exit_flag > 0;
173+
}
174+
139175
// Encoder alignment to electrical 0 angle
140176
int StepperMotor::alignSensor() {
141177
int exit_flag = 1; //success
@@ -261,8 +297,6 @@ void StepperMotor::loopFOC() {
261297

262298
// if open-loop do nothing
263299
if( controller==MotionControlType::angle_openloop || controller==MotionControlType::velocity_openloop ) return;
264-
// shaft angle
265-
shaft_angle = shaftAngle();
266300

267301
// if disabled do nothing
268302
if(!enabled) return;
@@ -272,6 +306,44 @@ void StepperMotor::loopFOC() {
272306
// which is in range 0-2PI
273307
electrical_angle = electricalAngle();
274308

309+
// Needs the update() to be called first
310+
// This function will not have numerical issues because it uses Sensor::getMechanicalAngle()
311+
// which is in range 0-2PI
312+
electrical_angle = electricalAngle();
313+
switch (torque_controller) {
314+
case TorqueControlType::voltage:
315+
// no need to do anything really
316+
break;
317+
case TorqueControlType::dc_current:
318+
if(!current_sense) return;
319+
// read overall current magnitude
320+
current.q = current_sense->getDCCurrent(electrical_angle);
321+
// filter the value values
322+
current.q = LPF_current_q(current.q);
323+
// calculate the phase voltage
324+
voltage.q = PID_current_q(current_sp - current.q);
325+
// d voltage - lag compensation
326+
if(_isset(phase_inductance)) voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
327+
else voltage.d = 0;
328+
break;
329+
case TorqueControlType::foc_current:
330+
if(!current_sense) return;
331+
// read dq currents
332+
current = current_sense->getFOCCurrents(electrical_angle);
333+
// filter values
334+
current.q = LPF_current_q(current.q);
335+
current.d = LPF_current_d(current.d);
336+
// calculate the phase voltages
337+
voltage.q = PID_current_q(current_sp - current.q);
338+
voltage.d = PID_current_d(-current.d);
339+
// d voltage - lag compensation - TODO verify
340+
// if(_isset(phase_inductance)) voltage.d = _constrain( voltage.d - current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
341+
break;
342+
default:
343+
// no torque control selected
344+
SIMPLEFOC_DEBUG("MOT: no torque control selected!");
345+
break;
346+
}
275347
// set the phase voltage - FOC heart function :)
276348
setPhaseVoltage(voltage.q, voltage.d, electrical_angle);
277349
}
@@ -309,56 +381,70 @@ void StepperMotor::move(float new_target) {
309381
// estimate the motor current if phase reistance available and current_sense not available
310382
if(!current_sense && _isset(phase_resistance)) current.q = (voltage.q - voltage_bemf)/phase_resistance;
311383

312-
// choose control loop
384+
// upgrade the current based voltage limit
313385
switch (controller) {
314386
case MotionControlType::torque:
315-
if(!_isset(phase_resistance)) voltage.q = target; // if voltage torque control
316-
else voltage.q = target*phase_resistance + voltage_bemf;
317-
voltage.q = _constrain(voltage.q, -voltage_limit, voltage_limit);
318-
// set d-component (lag compensation if known inductance)
319-
if(!_isset(phase_inductance)) voltage.d = 0;
320-
else voltage.d = _constrain( -target*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
387+
if(torque_controller == TorqueControlType::voltage){ // if voltage torque control
388+
if(!_isset(phase_resistance)) voltage.q = target;
389+
else voltage.q = target*phase_resistance + voltage_bemf;
390+
voltage.q = _constrain(voltage.q, -voltage_limit, voltage_limit);
391+
// set d-component (lag compensation if known inductance)
392+
if(!_isset(phase_inductance)) voltage.d = 0;
393+
else voltage.d = _constrain( -target*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
394+
}else{
395+
current_sp = target; // if current/foc_current torque control
396+
}
321397
break;
322398
case MotionControlType::angle:
399+
// TODO sensor precision: this calculation is not numerically precise. The target value cannot express precise positions when
400+
// the angles are large. This results in not being able to command small changes at high position values.
401+
// to solve this, the delta-angle has to be calculated in a numerically precise way.
323402
// angle set point
324403
shaft_angle_sp = target;
325404
// calculate velocity set point
326405
shaft_velocity_sp = feed_forward_velocity + P_angle( shaft_angle_sp - shaft_angle );
327-
shaft_velocity_sp = _constrain(shaft_velocity_sp, -velocity_limit, velocity_limit);
328-
// calculate the torque command
406+
shaft_velocity_sp = _constrain(shaft_velocity_sp,-velocity_limit, velocity_limit);
407+
// calculate the torque command - sensor precision: this calculation is ok, but based on bad value from previous calculation
329408
current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if voltage torque control
330409
// if torque controlled through voltage
331-
// use voltage if phase-resistance not provided
332-
if(!_isset(phase_resistance)) voltage.q = current_sp;
333-
else voltage.q = _constrain( current_sp*phase_resistance + voltage_bemf , -voltage_limit, voltage_limit);
334-
// set d-component (lag compensation if known inductance)
335-
if(!_isset(phase_inductance)) voltage.d = 0;
336-
else voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
410+
if(torque_controller == TorqueControlType::voltage){
411+
// use voltage if phase-resistance not provided
412+
if(!_isset(phase_resistance)) voltage.q = current_sp;
413+
else voltage.q = _constrain( current_sp*phase_resistance + voltage_bemf , -voltage_limit, voltage_limit);
414+
// set d-component (lag compensation if known inductance)
415+
if(!_isset(phase_inductance)) voltage.d = 0;
416+
else voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
417+
}
337418
break;
338419
case MotionControlType::velocity:
339-
// velocity set point
420+
// velocity set point - sensor precision: this calculation is numerically precise.
340421
shaft_velocity_sp = target;
341422
// calculate the torque command
342423
current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if current/foc_current torque control
343424
// if torque controlled through voltage control
344-
// use voltage if phase-resistance not provided
345-
if(!_isset(phase_resistance)) voltage.q = current_sp;
346-
else voltage.q = _constrain( current_sp*phase_resistance + voltage_bemf , -voltage_limit, voltage_limit);
347-
// set d-component (lag compensation if known inductance)
348-
if(!_isset(phase_inductance)) voltage.d = 0;
349-
else voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
425+
if(torque_controller == TorqueControlType::voltage){
426+
// use voltage if phase-resistance not provided
427+
if(!_isset(phase_resistance)) voltage.q = current_sp;
428+
else voltage.q = _constrain( current_sp*phase_resistance + voltage_bemf , -voltage_limit, voltage_limit);
429+
// set d-component (lag compensation if known inductance)
430+
if(!_isset(phase_inductance)) voltage.d = 0;
431+
else voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
432+
}
350433
break;
351434
case MotionControlType::velocity_openloop:
352-
// velocity control in open loop
435+
// velocity control in open loop - sensor precision: this calculation is numerically precise.
353436
shaft_velocity_sp = target;
354437
voltage.q = velocityOpenloop(shaft_velocity_sp); // returns the voltage that is set to the motor
355-
voltage.d = 0; // TODO d-component lag-compensation
438+
voltage.d = 0;
356439
break;
357440
case MotionControlType::angle_openloop:
358-
// angle control in open loop
441+
// angle control in open loop -
442+
// TODO sensor precision: this calculation NOT numerically precise, and subject
443+
// to the same problems in small set-point changes at high angles
444+
// as the closed loop version.
359445
shaft_angle_sp = target;
360446
voltage.q = angleOpenloop(shaft_angle_sp); // returns the voltage that is set to the motor
361-
voltage.d = 0; // TODO d-component lag-compensation
447+
voltage.d = 0;
362448
break;
363449
}
364450
}

src/StepperMotor.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,8 @@ class StepperMotor: public FOCMotor
8989
int alignSensor();
9090
/** Motor and sensor alignment to the sensors absolute 0 angle */
9191
int absoluteZeroSearch();
92+
/** Current sense and motor phase alignment */
93+
int alignCurrentSense();
9294

9395
// Open loop motion control
9496
/**

src/common/base_classes/BLDCDriver.h

Lines changed: 6 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,40 +2,17 @@
22
#define BLDCDRIVER_H
33

44
#include "Arduino.h"
5+
#include "FOCDriver.h"
56

6-
7-
enum PhaseState : uint8_t {
8-
PHASE_OFF = 0, // both sides of the phase are off
9-
PHASE_ON = 1, // both sides of the phase are driven with PWM, dead time is applied in 6-PWM mode
10-
PHASE_HI = 2, // only the high side of the phase is driven with PWM (6-PWM mode only)
11-
PHASE_LO = 3, // only the low side of the phase is driven with PWM (6-PWM mode only)
12-
};
13-
14-
15-
class BLDCDriver{
7+
class BLDCDriver: public FOCDriver{
168
public:
179

18-
/** Initialise hardware */
19-
virtual int init() = 0;
20-
/** Enable hardware */
21-
virtual void enable() = 0;
22-
/** Disable hardware */
23-
virtual void disable() = 0;
24-
25-
long pwm_frequency; //!< pwm frequency value in hertz
26-
float voltage_power_supply; //!< power supply voltage
27-
float voltage_limit; //!< limiting voltage set to the motor
28-
29-
3010
float dc_a; //!< currently set duty cycle on phaseA
3111
float dc_b; //!< currently set duty cycle on phaseB
3212
float dc_c; //!< currently set duty cycle on phaseC
3313

34-
bool initialized = false; // true if driver was successfully initialized
35-
void* params = 0; // pointer to hardware specific parameters of driver
36-
3714
/**
38-
* Set phase voltages to the harware
15+
* Set phase voltages to the hardware
3916
*
4017
* @param Ua - phase A voltage
4118
* @param Ub - phase B voltage
@@ -51,6 +28,9 @@ class BLDCDriver{
5128
* @param sa - phase C state : active / disabled ( high impedance )
5229
*/
5330
virtual void setPhaseState(PhaseState sa, PhaseState sb, PhaseState sc) = 0;
31+
32+
/** driver type getter function */
33+
virtual DriverType type() override { return DriverType::BLDC; };
5434
};
5535

5636
#endif

0 commit comments

Comments
 (0)