Skip to content

Commit dbdc12d

Browse files
committed
feat: implement slew rate detector and limiter for motor drivers
1 parent 2537c07 commit dbdc12d

10 files changed

Lines changed: 536 additions & 29 deletions

File tree

Firmware/FFBoard/Inc/Axis.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ struct AxisFlashAddresses
6060
uint16_t config = ADR_AXIS1_CONFIG;
6161
uint16_t maxSpeed = ADR_AXIS1_MAX_SPEED;
6262
uint16_t maxAccel = ADR_AXIS1_MAX_ACCEL;
63+
uint16_t maxSlewRateDrv = ADR_AXIS1_MAX_SLEWRATE_DRV;
6364

6465
uint16_t endstop = ADR_AXIS1_ENDSTOP;
6566
uint16_t power = ADR_AXIS1_POWER;
@@ -122,6 +123,8 @@ enum class Axis_commands : uint32_t{
122123
fxratio,reductionScaler,
123124
filterSpeed, filterAccel, filterProfileId,cpr,axisfriction,axisinertia,
124125
maxspeed,slewrate,
126+
calibrate_maxSlewRateDrv,
127+
maxSlewRateDrv,
125128
expo,exposcale,
126129
equalizer,eqb1,eqb2,eqb3,eqb4,eqb5,eqb6,
127130
handsoff, handsoff_speed, handsoff_accel
@@ -474,6 +477,7 @@ class Axis : public PersistentStorage, public CommandHandler, public ErrorHandle
474477
uint16_t nextDegreesOfRotation = degreesOfRotation; //!< Target degrees of rotation.
475478

476479
// Limiters
480+
uint16_t maxSlewRate_Driver = MAX_SLEW_RATE; //!< Maximum slew rate as measured by the driver (in units/ms).
477481
uint16_t maxSpeedDegS = 0; //!< Maximum speed in degrees per second. 0 to disable.
478482
uint32_t maxTorqueRateMS = 0; //!< Maximum torque rate of change per millisecond. 0 to disable.
479483

@@ -503,14 +507,17 @@ class Axis : public PersistentStorage, public CommandHandler, public ErrorHandle
503507
// Axis configuration
504508
bool invertAxis = true; //!< Invert axis direction.
505509
uint8_t endstopStrength = 127; //!< Stiffness of the endstop effect.
506-
static constexpr float ENDSTOP_GAIN = 25.0f; //!< Overall maximum endstop intensity.
510+
const float endstopGain = 25; //!< Overall maximum endstop intensity.
507511

508512
// Idle spring effect
509513
uint8_t idleSpringStrength = 127; //!< Strength of the idle spring.
510514
int16_t idleSpringClip = 0; //!< Maximum force for the idle spring.
511515
float idleSpringScale = 0; //!< Scaler for the idle spring force.
512516
bool motorWasNotReady = true; //!< Flag to detect motor readiness transition.
513517

518+
// Slew rate calibration tracking: true when Axis requested a calibration and
519+
// is waiting for the driver to finish measuring the max slew rate.
520+
bool awaitingSlewCalibration = false;
514521

515522
// Filters
516523
// TODO tune these and check if it is really stable and beneficial to the FFB. index 4 placeholder
@@ -521,7 +528,7 @@ class Axis : public PersistentStorage, public CommandHandler, public ErrorHandle
521528
const biquad_constant_t filterInertiaCst = {20, 20}; //!< Inertia filter constants.
522529
uint8_t filterProfileId = 1; //!< Currently selected filter profile ID.
523530
float filter_f = 1000.0; // 1khz
524-
static constexpr int32_t INTERNAL_FX_CLIP = 20000; //!< Clipping value for internal effects.
531+
const int32_t internalFxClip = 20000; //!< Clipping value for internal effects.
525532

526533
// Internal effects intensity
527534
uint8_t damperIntensity = 30; //!< Intensity of the internal damper effect.

Firmware/FFBoard/Src/Axis.cpp

Lines changed: 76 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ Axis::Axis(char axis,volatile Control_t* control) :CommandHandler("axis", CLSID_
117117
{
118118
driverChooser = ClassChooser<MotorDriver>(axis1_drivers);
119119
setInstance(0);
120-
this->flashAddresses = AxisFlashAddresses({ADR_AXIS1_CONFIG, ADR_AXIS1_MAX_SPEED, ADR_AXIS1_MAX_ACCEL,
120+
this->flashAddresses = AxisFlashAddresses({ADR_AXIS1_CONFIG, ADR_AXIS1_MAX_SPEED, ADR_AXIS1_MAX_ACCEL, ADR_AXIS1_MAX_SLEWRATE_DRV,
121121
ADR_AXIS1_ENDSTOP, ADR_AXIS1_POWER, ADR_AXIS1_DEGREES,ADR_AXIS1_EFFECTS1,ADR_AXIS1_EFFECTS2,ADR_AXIS1_ENC_RATIO,
122122
ADR_AXIS1_SPEEDACCEL_FILTER,ADR_AXIS1_POSTPROCESS1,
123123
ADR_AXIS1_EQ1,ADR_AXIS1_EQ2,ADR_AXIS1_EQ3,
@@ -128,7 +128,7 @@ Axis::Axis(char axis,volatile Control_t* control) :CommandHandler("axis", CLSID_
128128
{
129129
driverChooser = ClassChooser<MotorDriver>(axis2_drivers);
130130
setInstance(1);
131-
this->flashAddresses = AxisFlashAddresses({ADR_AXIS2_CONFIG, ADR_AXIS2_MAX_SPEED, ADR_AXIS2_MAX_ACCEL,
131+
this->flashAddresses = AxisFlashAddresses({ADR_AXIS2_CONFIG, ADR_AXIS2_MAX_SPEED, ADR_AXIS2_MAX_ACCEL, ADR_AXIS2_MAX_SLEWRATE_DRV,
132132
ADR_AXIS2_ENDSTOP, ADR_AXIS2_POWER, ADR_AXIS2_DEGREES,ADR_AXIS2_EFFECTS1,ADR_AXIS2_EFFECTS2, ADR_AXIS2_ENC_RATIO,
133133
ADR_AXIS2_SPEEDACCEL_FILTER,ADR_AXIS2_POSTPROCESS1,
134134
ADR_AXIS2_EQ1,ADR_AXIS2_EQ2,ADR_AXIS2_EQ3,
@@ -138,7 +138,7 @@ Axis::Axis(char axis,volatile Control_t* control) :CommandHandler("axis", CLSID_
138138
else if (axis == 'Z')
139139
{
140140
setInstance(2);
141-
this->flashAddresses = AxisFlashAddresses({ADR_AXIS3_CONFIG, ADR_AXIS3_MAX_SPEED, ADR_AXIS3_MAX_ACCEL,
141+
this->flashAddresses = AxisFlashAddresses({ADR_AXIS3_CONFIG, ADR_AXIS3_MAX_SPEED, ADR_AXIS3_MAX_ACCEL, ADR_AXIS3_MAX_SLEWRATE_DRV,
142142
ADR_AXIS3_ENDSTOP, ADR_AXIS3_POWER, ADR_AXIS3_DEGREES,ADR_AXIS3_EFFECTS1,ADR_AXIS3_EFFECTS2,ADR_AXIS3_ENC_RATIO,
143143
ADR_AXIS3_SPEEDACCEL_FILTER,ADR_AXIS3_POSTPROCESS1,
144144
ADR_AXIS3_EQ1,ADR_AXIS3_EQ2,ADR_AXIS3_EQ3,
@@ -207,6 +207,9 @@ void Axis::registerCommands(){
207207
registerCommand("handsoff", Axis_commands::handsoff, "Hands-off enable", CMDFLAG_GET | CMDFLAG_SET);
208208
registerCommand("handsoff_speed", Axis_commands::handsoff_speed, "Hoff speed thrld (deg/s)", CMDFLAG_GET | CMDFLAG_SET);
209209
registerCommand("handsoff_accel", Axis_commands::handsoff_accel, "Hoff accel std dev thrld (float, val/1000)", CMDFLAG_GET | CMDFLAG_SET);
210+
211+
registerCommand("maxSlewRateDrv", Axis_commands::maxSlewRateDrv, "Max driver torque in counts/ms",CMDFLAG_GET);
212+
registerCommand("calibrate_maxSlewRateDrv", Axis_commands::calibrate_maxSlewRateDrv, "Start driver slewRate calib", CMDFLAG_GET);
210213
}
211214

212215
/*
@@ -238,6 +241,13 @@ void Axis::restoreFlash(){
238241
pulseErrLed();
239242
}
240243

244+
// save the max torque for the slew rate
245+
if (Flash_Read(flashAddresses.maxSlewRateDrv, &value)){
246+
this->maxSlewRate_Driver = value;
247+
}else{
248+
pulseErrLed();
249+
}
250+
241251

242252
uint16_t endstopRawValue, power;
243253
if(Flash_Read(flashAddresses.endstop, &endstopRawValue)) {
@@ -322,6 +332,7 @@ void Axis::saveFlash(){
322332
Flash_Write(flashAddresses.config, Axis::encodeConfToInt(this->conf));
323333
Flash_Write(flashAddresses.maxSpeed, this->maxSpeedDegS);
324334
Flash_Write(flashAddresses.maxAccel, (uint16_t)(this->maxTorqueRateMS));
335+
Flash_Write(flashAddresses.maxSlewRateDrv, (uint16_t)(this->maxSlewRate_Driver));
325336

326337
Flash_Write(flashAddresses.endstop, effectRatio | (endstopStrength << 8));
327338
Flash_Write(flashAddresses.power, power);
@@ -435,6 +446,29 @@ void Axis::prepareForUpdate(){
435446
startForceFadeIn(0, 1.0);
436447
}
437448

449+
// Check for pending slew rate calibration result
450+
if(this->awaitingSlewCalibration){
451+
// If driver reports calibration finished, retrieve measured value and persist
452+
if(!drv->isSlewRateCalibrationInProgress()){
453+
// Get value from drv
454+
this->maxSlewRate_Driver = drv->getDrvSlewRate();
455+
456+
// If the driver's max slew rate is lowest thant current max flew rate, cap the value and send the new value to the UI
457+
if (this->maxSlewRate_Driver < this->maxTorqueRateMS) {
458+
this->maxTorqueRateMS = this->maxSlewRate_Driver;
459+
CommandHandler::broadcastCommandReply(CommandReply(this->maxTorqueRateMS), (uint32_t)Axis_commands::slewrate, CMDtype::get);
460+
}
461+
462+
// Broadcast a friendly completion message and the numeric value
463+
CommandHandler::broadcastCommandReply(CommandReply("Slew rate calibration complete",0), (uint32_t)Axis_commands::calibrate_maxSlewRateDrv, CMDtype::get);
464+
CommandHandler::broadcastCommandReply(CommandReply(this->maxSlewRate_Driver), (uint32_t)Axis_commands::maxSlewRateDrv, CMDtype::get);
465+
466+
467+
this->awaitingSlewCalibration = false;
468+
}
469+
}
470+
471+
438472
this->updateMetrics(angle);
439473
this->updateHandsOffState();
440474

@@ -499,6 +533,7 @@ void Axis::setDrvType(uint8_t drvtype)
499533
old_drv_ptr = this->drv.release(); // Detach the old driver safely
500534
this->drv.reset(drv_new); // Attach the new driver
501535
this->conf.drvtype = drvtype;
536+
this->maxTorqueRateMS = drv_new->getDrvSlewRate();
502537
cpp_freertos::CriticalSection::Exit();
503538

504539
// Delete the old driver outside of the critical section to avoid blocking destructors or FreeRTOS issues
@@ -671,13 +706,13 @@ void Axis::calculateMechanicalEffects(bool ffb_on){
671706
// Always active damper
672707
if(damperIntensity != 0){
673708
float speedFiltered = (metric.current.speed) * (float)damperIntensity * AXIS_DAMPER_RATIO;
674-
mechanicalEffectTorque -= damperFilter.process(clip<float, int32_t>(speedFiltered, -INTERNAL_FX_CLIP, INTERNAL_FX_CLIP));
709+
mechanicalEffectTorque -= damperFilter.process(clip<float, int32_t>(speedFiltered, -internalFxClip, internalFxClip));
675710
}
676711

677712
// Always active inertia
678713
if(inertiaIntensity != 0){
679714
float accelFiltered = metric.current.accel * (float)inertiaIntensity * AXIS_INERTIA_RATIO;
680-
mechanicalEffectTorque -= inertiaFilter.process(clip<float, int32_t>(accelFiltered, -INTERNAL_FX_CLIP, INTERNAL_FX_CLIP));
715+
mechanicalEffectTorque -= inertiaFilter.process(clip<float, int32_t>(accelFiltered, -internalFxClip, internalFxClip));
681716
}
682717

683718
// Always active friction. Based on effectsCalculator implementation
@@ -696,7 +731,7 @@ void Axis::calculateMechanicalEffects(bool ffb_on){
696731
}
697732
int8_t sign = speed >= 0 ? 1 : -1;
698733
float force = (float)frictionIntensity * rampupFactor * sign * INTERNAL_AXIS_FRICTION_SCALER * 32;
699-
mechanicalEffectTorque -= frictionFilter.process(clip<float, int32_t>(force, -INTERNAL_FX_CLIP, INTERNAL_FX_CLIP));
734+
mechanicalEffectTorque -= frictionFilter.process(clip<float, int32_t>(force, -internalFxClip, internalFxClip));
700735
}
701736

702737
}
@@ -805,7 +840,7 @@ int32_t Axis::calculateEndstopTorque(){
805840
return 0;
806841
}
807842
float endstopTorque = clipDirection*metric.current.posDegrees - (float)this->degreesOfRotation/2.0; // degress of rotation counts total range so multiply by 2
808-
endstopTorque *= (float)endstopStrength * ENDSTOP_GAIN; // Apply endstop gain for stiffness.
843+
endstopTorque *= (float)endstopStrength * endstopGain; // Apply endstop gain for stiffness.
809844
endstopTorque *= -clipDirection;
810845

811846
return clip<int32_t,int32_t>(endstopTorque,-0x7fff,0x7fff);
@@ -1132,13 +1167,38 @@ CommandStatus Axis::command(const ParsedCommand& cmd,std::vector<CommandReply>&
11321167
case Axis_commands::slewrate:
11331168
{
11341169
if(cmd.type == CMDtype::get){
1170+
// If driver has a more restrictive calibrated value, update the axis limit
1171+
if(maxSlewRate_Driver < this->maxTorqueRateMS) {
1172+
this->maxTorqueRateMS = maxSlewRate_Driver;
1173+
}
11351174
replies.emplace_back(this->maxTorqueRateMS);
11361175
}else if(cmd.type == CMDtype::set){
1137-
this->maxTorqueRateMS = cmd.val;
1176+
this->maxTorqueRateMS = clip<uint32_t,uint32_t>(cmd.val, 0, maxSlewRate_Driver);
11381177
}
11391178
}
11401179
break;
11411180

1181+
case Axis_commands::calibrate_maxSlewRateDrv:
1182+
{
1183+
if(cmd.type == CMDtype::get){
1184+
// Start calibration on driver and set awaiting flag if start is OK
1185+
if (drv->startSlewRateCalibration()) {
1186+
this->awaitingSlewCalibration = true;
1187+
} else {
1188+
// Inform user that calibration can't started
1189+
CommandHandler::broadcastCommandReply(CommandReply("Slew rate calibration unsupported",1), (uint32_t)Axis_commands::calibrate_maxSlewRateDrv, CMDtype::get);
1190+
}
1191+
replies.emplace_back(1); // ack
1192+
}
1193+
break;
1194+
}
1195+
1196+
case Axis_commands::maxSlewRateDrv:
1197+
if (cmd.type == CMDtype::get) {
1198+
replies.emplace_back(maxSlewRate_Driver);
1199+
}
1200+
break;
1201+
11421202
case Axis_commands::fxratio:
11431203
if(cmd.type == CMDtype::get){
11441204
replies.emplace_back(this->effectRatio);
@@ -1200,6 +1260,14 @@ CommandStatus Axis::command(const ParsedCommand& cmd,std::vector<CommandReply>&
12001260
if(this->getEncoder() != nullptr){
12011261
cpr = this->getEncoder()->getCpr();
12021262
}
1263+
// TODO: For TMC4671 drivers, CPR reporting might be inconsistent. Investigate if a prescale is needed or if the UI should handle the readout correction.
1264+
//#ifdef TMC4671DRIVER // CPR should be consistent with position. Maybe change TMC to prescale to encoder count or correct readout in UI
1265+
// TMC4671 *tmcdrv = dynamic_cast<TMC4671 *>(this->drv.get()); // Special case for TMC. Get the actual encoder resolution
1266+
// if (tmcdrv && tmcdrv->hasIntegratedEncoder())
1267+
// {
1268+
// cpr = tmcdrv->getEncCpr();
1269+
// }
1270+
//#endif
12031271
replies.emplace_back(cpr);
12041272
}else{
12051273
return CommandStatus::ERR;

Firmware/FFBoard/UserExtensions/Inc/TMC4671.h

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,11 @@
3232
#define TMC_THREAD_PRIO 25 // Must be higher than main thread
3333
#define TMC_ADCOFFSETFAIL 5000 // How much offset from 0x7fff to allow before a calibration is failed
3434

35+
#ifdef COGGING_TABLE_FLASH_START_ADDRESS
36+
// --- Constants for anti-cogging calibration ---
37+
#define CALIB_SPEED 50 // Slow speed in RPM used for calibration.
38+
#endif
39+
3540
extern SPI_HandleTypeDef HSPIDRV;
3641

3742
#ifdef TIM_TMC
@@ -57,13 +62,25 @@ extern TIM_HandleTypeDef TIM_TMC;
5762
#define TIM_TMC_ARR 250
5863
#endif
5964

60-
enum class TMC_ControlState : uint32_t {uninitialized,waitPower,Shutdown,Running,EncoderInit,EncoderFinished,HardError,OverTemp,IndexSearch,FullCalibration,ExternalEncoderInit,Pidautotune, NONE
65+
enum class TMC_ControlState : uint32_t {uninitialized,waitPower,Shutdown,Running,EncoderInit,EncoderFinished,HardError,OverTemp,IndexSearch,FullCalibration,ExternalEncoderInit,Pidautotune
66+
#ifdef COGGING_TABLE_FLASH_START_ADDRESS
67+
,CoggingCalibration
68+
#endif
69+
,SlewRateCalibration, NONE
6170
};
6271

6372
enum class TMC_PwmMode : uint8_t {off = 0,HSlow_LShigh = 1, HShigh_LSlow = 2, res2 = 3, res3 = 4, PWM_LS = 5, PWM_HS = 6, PWM_FOC = 7};
6473

6574
enum class TMC_StartupType{NONE,coldStart,warmStart};
6675

76+
enum class CoggingState : uint8_t { Init, ForwardWait, ForwardMeasure, BackwardWait, BackwardMeasure, Compute };
77+
78+
struct CoggingCalibData {
79+
int32_t temp_fw[CALIB_MAP_SIZE] = {0};
80+
int32_t temp_bw[CALIB_MAP_SIZE] = {0};
81+
uint16_t counts_fw[CALIB_MAP_SIZE] = {0};
82+
uint16_t counts_bw[CALIB_MAP_SIZE] = {0};
83+
};
6784

6885
enum class TMC_GpioMode{DebugSpi,DSAdcClkOut,DSAdcClkIn,Aout_Bin,Ain_Bout,Aout_Bout,Ain_Bin};
6986
enum class MotorType : uint8_t {NONE=0,DC=1,STEPPER=2,BLDC=3};
@@ -265,6 +282,9 @@ struct TMC4671FlashAddrs{
265282
uint16_t encOffset = ADR_TMC1_ENC_OFFSET;
266283
uint16_t phieOffset = ADR_TMC1_PHIE_OFS;
267284
uint16_t torqueFilter = ADR_TMC1_TRQ_FILT;
285+
#ifdef COGGING_TABLE_FLASH_START_ADDRESS
286+
uint16_t coggingEnable = ADR_TMC1_COGGING_CAL;
287+
#endif
268288
};
269289

270290
struct TMC4671ABNConf{
@@ -395,7 +415,10 @@ class TMC4671 :
395415
torqueP,torqueI,fluxP,fluxI,velocityP,velocityI,posP,posI,
396416
tmctype,pidPrec,phiesrc,fluxoffset,seqpi,tmcIscale,encdir,temp,reg,
397417
svpwm,fullCalibration,calibrated,abnindexenabled,findIndex,getState,encpol,combineEncoder,invertForce,vmTmc,
398-
extphie,torqueFilter_mode,torqueFilter_f,torqueFilter_q,pidautotune,fluxbrake,pwmfreq
418+
extphie,torqueFilter_mode,torqueFilter_f,torqueFilter_q,pidautotune,fluxbrake,pwmfreq,
419+
#ifdef COGGING_TABLE_FLASH_START_ADDRESS
420+
cogging,calibrateCogging, coggingTable
421+
#endif
399422
};
400423

401424
#ifdef TMCDEBUG
@@ -457,6 +480,9 @@ friend class TMCDebugBridge;
457480
bool checkEncoder();
458481
void calibrateAenc();
459482
void calibrateEncoder();
483+
#ifdef COGGING_TABLE_FLASH_START_ADDRESS
484+
void calibrateCogging();
485+
#endif
460486

461487
void setEncoderType(EncoderType_TMC type);
462488
uint32_t getEncCpr();
@@ -488,6 +514,11 @@ friend class TMCDebugBridge;
488514

489515
void setBBM(uint8_t bbml,uint8_t bbmh);
490516

517+
518+
// Slew rate calibration control
519+
uint16_t getDrvSlewRate();
520+
bool startSlewRateCalibration();
521+
bool isSlewRateCalibrationInProgress();
491522

492523
#ifdef TIM_TMC
493524
void timerElapsed(TIM_HandleTypeDef* htim);
@@ -696,6 +727,23 @@ friend class TMCDebugBridge;
696727

697728
uint8_t spi_buf[5] = {0};
698729

730+
#ifdef COGGING_TABLE_FLASH_START_ADDRESS
731+
// Cogging Calibration
732+
int16_t data_cogging[CALIB_MAP_SIZE] = {0};
733+
bool cogging_enabled = false;
734+
void saveCoggingTable();
735+
void clearCoggingTable();
736+
737+
CoggingState coggingCalibState = CoggingState::Init;
738+
std::unique_ptr<CoggingCalibData> coggingData = nullptr;
739+
uint32_t calibStartTime = 0;
740+
MotionMode prevCalibMode = MotionMode::stop;
741+
void handleStateCoggingCalibration();
742+
#endif
743+
744+
uint16_t maxSlewRate = MAX_SLEW_RATE; // in mA/ms
745+
void measureMaxSlewRate();
746+
699747
enum class PidTuneState : uint8_t { Init, RampFluxP, TuneFluxI_Pulse, TuneFluxI_Measure, Done };
700748
PidTuneState pidTuneState = PidTuneState::Init;
701749
uint32_t pidTuneStartTime = 0;

0 commit comments

Comments
 (0)