Skip to content

Commit b5557db

Browse files
committed
added the estimated curret torque control mode
1 parent 283f20b commit b5557db

File tree

9 files changed

+479
-559
lines changed

9 files changed

+479
-559
lines changed

src/BLDCMotor.cpp

Lines changed: 73 additions & 157 deletions
Large diffs are not rendered by default.

src/BLDCMotor.h

Lines changed: 0 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -100,25 +100,6 @@ class BLDCMotor: public FOCMotor
100100
int alignCurrentSense();
101101
/** Motor and sensor alignment to the sensors absolute 0 angle */
102102
int absoluteZeroSearch();
103-
104-
105-
// Open loop motion control
106-
/**
107-
* Function (iterative) generating open loop movement for target velocity
108-
* it uses voltage_limit variable
109-
*
110-
* @param target_velocity - rad/s
111-
*/
112-
float velocityOpenloop(float target_velocity);
113-
/**
114-
* Function (iterative) generating open loop movement towards the target angle
115-
* it uses voltage_limit and velocity_limit variables
116-
*
117-
* @param target_angle - rad
118-
*/
119-
float angleOpenloop(float target_angle);
120-
// open loop variables
121-
long open_loop_timestamp;
122103
};
123104

124105

src/HybridStepperMotor.cpp

Lines changed: 65 additions & 150 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ HybridStepperMotor::HybridStepperMotor(int pp, float _R, float _KV, float _induc
1515
phase_resistance = _R;
1616
// save back emf constant KV = 1/K_bemf
1717
// usually used rms
18-
KV_rating = _KV * _SQRT2;
18+
KV_rating = _KV;
1919
// save phase inductance
2020
phase_inductance = _inductance;
2121

@@ -52,17 +52,10 @@ int HybridStepperMotor::init()
5252
if (voltage_sensor_align > voltage_limit)
5353
voltage_sensor_align = voltage_limit;
5454

55-
// update the controller limits
56-
if (_isset(phase_resistance))
57-
{
58-
// velocity control loop controls current
59-
PID_velocity.limit = current_limit;
60-
}
61-
else
62-
{
63-
PID_velocity.limit = voltage_limit;
64-
}
65-
P_angle.limit = velocity_limit;
55+
// update limits in the motor controllers
56+
updateCurrentLimit(current_limit);
57+
updateVoltageLimit(voltage_limit);
58+
updateVelocityLimit(velocity_limit);
6659

6760
// if using open loop control, set a CW as the default direction if not already set
6861
// only if no sensor is used
@@ -320,42 +313,66 @@ void HybridStepperMotor::loopFOC() {
320313
// of full rotations otherwise.
321314
if (sensor) sensor->update();
322315

323-
// if open-loop do nothing
324-
if( controller==MotionControlType::angle_openloop || controller==MotionControlType::velocity_openloop ) return;
325-
326316
// if disabled do nothing
327317
if(!enabled) return;
328318

329-
// Needs the update() to be called first
330-
// This function will not have numerical issues because it uses Sensor::getMechanicalAngle()
331-
// which is in range 0-2PI
332-
electrical_angle = electricalAngle();
319+
// if open-loop do nothing
320+
if( controller==MotionControlType::angle_openloop || controller==MotionControlType::velocity_openloop )
321+
// calculate the open loop electirical angle
322+
electrical_angle = _electricalAngle((shaft_angle), pole_pairs);
323+
else
324+
// Needs the update() to be called first
325+
// This function will not have numerical issues because it uses Sensor::getMechanicalAngle()
326+
// which is in range 0-2PI
327+
electrical_angle = electricalAngle();
328+
333329
switch (torque_controller) {
334330
case TorqueControlType::voltage:
335-
// no need to do anything really
331+
voltage.q = _constrain(current_sp, -voltage_limit, voltage_limit) + feed_forward_voltage.q;
332+
voltage.d = feed_forward_voltage.d;
333+
break;
334+
case TorqueControlType::estimated_current:
335+
if(! _isset(phase_resistance)) return;
336+
// constrain current setpoint
337+
current_sp = _constrain(current_sp, -current_limit, current_limit) + feed_forward_current.q; // desired current is the setpoint
338+
// calculate the back-emf voltage if KV_rating available U_bemf = vel*(1/KV)
339+
if (_isset(KV_rating)) voltage_bemf = shaft_velocity/(KV_rating*_SQRT2)/_RPM_TO_RADS;
340+
// filter the value values
341+
current.q = LPF_current_q(current_sp);
342+
// calculate the phase voltage
343+
voltage.q = current.q * phase_resistance + voltage_bemf;
344+
// constrain voltage within limits
345+
voltage.q = _constrain(voltage.q, -voltage_limit, voltage_limit) + feed_forward_voltage.q;
346+
// d voltage - lag compensation
347+
if(_isset(phase_inductance)) voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit) + feed_forward_voltage.d;
348+
else voltage.d = feed_forward_voltage.d;
336349
break;
337350
case TorqueControlType::dc_current:
338351
if(!current_sense) return;
352+
// constrain current setpoint
353+
current_sp = _constrain(current_sp, -current_limit, current_limit) + feed_forward_current.q;
339354
// read overall current magnitude
340355
current.q = current_sense->getDCCurrent(electrical_angle);
341356
// filter the value values
342357
current.q = LPF_current_q(current.q);
343358
// calculate the phase voltage
344-
voltage.q = PID_current_q(current_sp - current.q);
359+
voltage.q = PID_current_q(current_sp - current.q) + feed_forward_voltage.q;
345360
// d voltage - lag compensation
346-
if(_isset(phase_inductance)) voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
347-
else voltage.d = 0;
361+
if(_isset(phase_inductance)) voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit) + feed_forward_voltage.d;
362+
else voltage.d = feed_forward_voltage.d;
348363
break;
349364
case TorqueControlType::foc_current:
350365
if(!current_sense) return;
366+
// constrain current setpoint
367+
current_sp = _constrain(current_sp, -current_limit, current_limit) + feed_forward_current.q;
351368
// read dq currents
352369
current = current_sense->getFOCCurrents(electrical_angle);
353370
// filter values
354371
current.q = LPF_current_q(current.q);
355-
current.d = LPF_current_d(current.d);
372+
current.d = LPF_current_d(current.d);
356373
// calculate the phase voltages
357-
voltage.q = PID_current_q(current_sp - current.q);
358-
voltage.d = PID_current_d(-current.d);
374+
voltage.q = PID_current_q(current_sp - current.q) + feed_forward_voltage.q;
375+
voltage.d = PID_current_d(feed_forward_current.d - current.d) + feed_forward_voltage.d;
359376
// d voltage - lag compensation - TODO verify
360377
// if(_isset(phase_inductance)) voltage.d = _constrain( voltage.d - current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
361378
break;
@@ -382,40 +399,31 @@ void HybridStepperMotor::move(float new_target) {
382399
if(motion_cnt++ < motion_downsample) return;
383400
motion_cnt = 0;
384401

402+
// read value even if motor is disabled to keep the monitoring updated
403+
// except for the open loop modes where the values are updated within angle/velocityOpenLoop functions
404+
385405
// shaft angle/velocity need the update() to be called first
386406
// get shaft angle
387407
// TODO sensor precision: the shaft_angle actually stores the complete position, including full rotations, as a float
388408
// For this reason it is NOT precise when the angles become large.
389409
// Additionally, the way LPF works on angle is a precision issue, and the angle-LPF is a problem
390410
// when switching to a 2-component representation.
391-
if( controller!=MotionControlType::angle_openloop && controller!=MotionControlType::velocity_openloop )
392-
shaft_angle = shaftAngle(); // read value even if motor is disabled to keep the monitoring updated but not in openloop mode
393-
// get angular velocity
394-
shaft_velocity = shaftVelocity(); // read value even if motor is disabled to keep the monitoring updated
411+
if( controller!=MotionControlType::angle_openloop && controller!=MotionControlType::velocity_openloop ){
412+
// read the values only if the motor is not in open loop
413+
// because in open loop the shaft angle/velocity is updated within angle/velocityOpenLoop functions
414+
shaft_angle = shaftAngle();
415+
shaft_velocity = shaftVelocity();
416+
}
395417

396418
// if disabled do nothing
397419
if(!enabled) return;
420+
398421

399-
400-
// calculate the back-emf voltage if KV_rating available U_bemf = vel*(1/KV)
401-
if (_isset(KV_rating)) voltage_bemf = shaft_velocity/(KV_rating*_SQRT3)/_RPM_TO_RADS;
402-
// estimate the motor current if phase reistance available and current_sense not available
403-
if(!current_sense && _isset(phase_resistance)) current.q = (voltage.q - voltage_bemf)/phase_resistance;
404-
405-
// upgrade the current based voltage limit
422+
// upgrade the current based voltage limit
406423
switch (controller) {
407424
case MotionControlType::torque:
408-
if(torque_controller == TorqueControlType::voltage){ // if voltage torque control
409-
if(!_isset(phase_resistance)) voltage.q = target;
410-
else voltage.q = target*phase_resistance + voltage_bemf;
411-
voltage.q = _constrain(voltage.q, -voltage_limit, voltage_limit);
412-
// set d-component (lag compensation if known inductance)
413-
if(!_isset(phase_inductance)) voltage.d = 0;
414-
else voltage.d = _constrain( -target*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
415-
}else{
416-
current_sp = target; // if current/foc_current torque control
417-
}
418-
break;
425+
current_sp = target;
426+
break;
419427
case MotionControlType::angle:
420428
// TODO sensor precision: this calculation is not numerically precise. The target value cannot express precise positions when
421429
// the angles are large. This results in not being able to command small changes at high position values.
@@ -424,48 +432,34 @@ void HybridStepperMotor::move(float new_target) {
424432
shaft_angle_sp = target;
425433
// calculate velocity set point
426434
shaft_velocity_sp = feed_forward_velocity + P_angle( shaft_angle_sp - shaft_angle );
427-
shaft_velocity_sp = _constrain(shaft_velocity_sp,-velocity_limit, velocity_limit);
435+
shaft_velocity_sp = _constrain(shaft_velocity_sp, -velocity_limit, velocity_limit);
428436
// calculate the torque command - sensor precision: this calculation is ok, but based on bad value from previous calculation
429437
current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if voltage torque control
430-
// if torque controlled through voltage
431-
if(torque_controller == TorqueControlType::voltage){
432-
// use voltage if phase-resistance not provided
433-
if(!_isset(phase_resistance)) voltage.q = current_sp;
434-
else voltage.q = _constrain( current_sp*phase_resistance + voltage_bemf , -voltage_limit, voltage_limit);
435-
// set d-component (lag compensation if known inductance)
436-
if(!_isset(phase_inductance)) voltage.d = 0;
437-
else voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
438-
}
439438
break;
440439
case MotionControlType::velocity:
441440
// velocity set point - sensor precision: this calculation is numerically precise.
442441
shaft_velocity_sp = target;
443442
// calculate the torque command
444443
current_sp = PID_velocity(shaft_velocity_sp - shaft_velocity); // if current/foc_current torque control
445-
// if torque controlled through voltage control
446-
if(torque_controller == TorqueControlType::voltage){
447-
// use voltage if phase-resistance not provided
448-
if(!_isset(phase_resistance)) voltage.q = current_sp;
449-
else voltage.q = _constrain( current_sp*phase_resistance + voltage_bemf , -voltage_limit, voltage_limit);
450-
// set d-component (lag compensation if known inductance)
451-
if(!_isset(phase_inductance)) voltage.d = 0;
452-
else voltage.d = _constrain( -current_sp*shaft_velocity*pole_pairs*phase_inductance, -voltage_limit, voltage_limit);
453-
}
454444
break;
455445
case MotionControlType::velocity_openloop:
456446
// velocity control in open loop - sensor precision: this calculation is numerically precise.
457447
shaft_velocity_sp = target;
458-
voltage.q = velocityOpenloop(shaft_velocity_sp); // returns the voltage that is set to the motor
459-
voltage.d = 0;
448+
// this function updates the shaft_angle and shaft_velocity
449+
// returns the voltage or current that is to be set to the motor (depending on torque control mode)
450+
// returned values correspond to the voltage_limit and current_limit
451+
current_sp = velocityOpenloop(shaft_velocity_sp);
460452
break;
461453
case MotionControlType::angle_openloop:
462454
// angle control in open loop -
463455
// TODO sensor precision: this calculation NOT numerically precise, and subject
464456
// to the same problems in small set-point changes at high angles
465457
// as the closed loop version.
466458
shaft_angle_sp = target;
467-
voltage.q = angleOpenloop(shaft_angle_sp); // returns the voltage that is set to the motor
468-
voltage.d = 0;
459+
// this function updates the shaft_angle and shaft_velocity
460+
// returns the voltage or current that is to be set to the motor (depending on torque control mode)
461+
// returned values correspond to the voltage_limit and current_limit
462+
current_sp = angleOpenloop(shaft_angle_sp);
469463
break;
470464
}
471465
}
@@ -513,82 +507,3 @@ void HybridStepperMotor::setPhaseVoltage(float Uq, float Ud, float angle_el)
513507
driver->setPwm(Ua, Ub, Uc);
514508

515509
}
516-
517-
// Function (iterative) generating open loop movement for target velocity
518-
// - target_velocity - rad/s
519-
// it uses voltage_limit variable
520-
float HybridStepperMotor::velocityOpenloop(float target_velocity)
521-
{
522-
// get current timestamp
523-
unsigned long now_us = _micros();
524-
// calculate the sample time from last call
525-
float Ts = (now_us - open_loop_timestamp) * 1e-6f;
526-
// quick fix for strange cases (micros overflow + timestamp not defined)
527-
if (Ts <= 0 || Ts > 0.5f)
528-
Ts = 1e-3f;
529-
530-
// calculate the necessary angle to achieve target velocity
531-
shaft_angle = _normalizeAngle(shaft_angle + target_velocity * Ts);
532-
// for display purposes
533-
shaft_velocity = target_velocity;
534-
535-
// use voltage limit or current limit
536-
float Uq = voltage_limit;
537-
if (_isset(phase_resistance))
538-
{
539-
Uq = _constrain(current_limit * phase_resistance + fabs(voltage_bemf), -voltage_limit, voltage_limit);
540-
// recalculate the current
541-
current.q = (Uq - fabs(voltage_bemf)) / phase_resistance;
542-
}
543-
544-
// set the maximal allowed voltage (voltage_limit) with the necessary angle
545-
setPhaseVoltage(Uq, 0, _electricalAngle(shaft_angle, pole_pairs));
546-
547-
// save timestamp for next call
548-
open_loop_timestamp = now_us;
549-
550-
return Uq;
551-
}
552-
553-
// Function (iterative) generating open loop movement towards the target angle
554-
// - target_angle - rad
555-
// it uses voltage_limit and velocity_limit variables
556-
float HybridStepperMotor::angleOpenloop(float target_angle)
557-
{
558-
// get current timestamp
559-
unsigned long now_us = _micros();
560-
// calculate the sample time from last call
561-
float Ts = (now_us - open_loop_timestamp) * 1e-6f;
562-
// quick fix for strange cases (micros overflow + timestamp not defined)
563-
if (Ts <= 0 || Ts > 0.5f)
564-
Ts = 1e-3f;
565-
566-
// calculate the necessary angle to move from current position towards target angle
567-
// with maximal velocity (velocity_limit)
568-
if (abs(target_angle - shaft_angle) > abs(velocity_limit * Ts))
569-
{
570-
shaft_angle += _sign(target_angle - shaft_angle) * abs(velocity_limit) * Ts;
571-
shaft_velocity = velocity_limit;
572-
}
573-
else
574-
{
575-
shaft_angle = target_angle;
576-
shaft_velocity = 0;
577-
}
578-
579-
// use voltage limit or current limit
580-
float Uq = voltage_limit;
581-
if (_isset(phase_resistance))
582-
{
583-
Uq = _constrain(current_limit * phase_resistance + fabs(voltage_bemf), -voltage_limit, voltage_limit);
584-
// recalculate the current
585-
current.q = (Uq - fabs(voltage_bemf)) / phase_resistance;
586-
}
587-
// set the maximal allowed voltage (voltage_limit) with the necessary angle
588-
setPhaseVoltage(Uq, 0, _electricalAngle((shaft_angle), pole_pairs));
589-
590-
// save timestamp for next call
591-
open_loop_timestamp = now_us;
592-
593-
return Uq;
594-
}

src/HybridStepperMotor.h

Lines changed: 11 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -84,30 +84,23 @@ class HybridStepperMotor : public FOCMotor
8484
*/
8585
void setPhaseVoltage(float Uq, float Ud, float angle_el) override;
8686

87+
/**
88+
* Measure resistance and inductance of a StepperMotor and print results to debug.
89+
* If a sensor is available, an estimate of zero electric angle will be reported too.
90+
* TODO: determine the correction factor
91+
* @param voltage The voltage applied to the motor
92+
* @returns 0 for success, >0 for failure
93+
*/
94+
int characteriseMotor(float voltage){
95+
return FOCMotor::characteriseMotor(voltage, 1.0f);
96+
}
97+
8798
private:
8899
int alignCurrentSense();
89100
/** Sensor alignment to electrical 0 angle of the motor */
90101
int alignSensor();
91102
/** Motor and sensor alignment to the sensors absolute 0 angle */
92103
int absoluteZeroSearch();
93-
94-
// Open loop motion control
95-
/**
96-
* Function (iterative) generating open loop movement for target velocity
97-
* it uses voltage_limit variable
98-
*
99-
* @param target_velocity - rad/s
100-
*/
101-
float velocityOpenloop(float target_velocity);
102-
/**
103-
* Function (iterative) generating open loop movement towards the target angle
104-
* it uses voltage_limit and velocity_limit variables
105-
*
106-
* @param target_angle - rad
107-
*/
108-
float angleOpenloop(float target_angle);
109-
// open loop variables
110-
long open_loop_timestamp;
111104
};
112105

113106
#endif

0 commit comments

Comments
 (0)