@@ -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- }
0 commit comments