[DRAFT] IAC H-Bridge Microstepping (#3213)
* fix for stepperHbridgeHardware * new config fields for microstepping * add stepper dir pin mode to the dialog * refactor stepper hw dialog & add microstepping panel * microstepper modes enum * stepperDcInvertedPins needed by Hellen * add sleep() for stepper and divisor for pause() * microstepping impl. * make unit-tests more happy * small progress * fix etb-stepper dlg conflict Co-authored-by: Andrei <andreikagit@users.noreply.github.com>
This commit is contained in:
parent
e8710c0abd
commit
bc4a068c9c
|
@ -52,6 +52,7 @@ public:
|
|||
brain_pin_e pinDir1,
|
||||
brain_pin_e pinDir2,
|
||||
brain_pin_e pinDisable,
|
||||
bool isInverted,
|
||||
ExecutorInterface* executor,
|
||||
int frequency) {
|
||||
dcMotor.setType(useTwoWires ? TwoPinDcMotor::ControlType::PwmDirectionPins : TwoPinDcMotor::ControlType::PwmEnablePin);
|
||||
|
@ -85,7 +86,7 @@ public:
|
|||
);
|
||||
#endif // EFI_UNIT_TEST
|
||||
|
||||
dcMotor.configure(wrappedEnable, m_pwm1, m_pwm2);
|
||||
dcMotor.configure(wrappedEnable, m_pwm1, m_pwm2, isInverted);
|
||||
} else {
|
||||
m_pinDir1.initPin("ETB Dir 1", pinDir1);
|
||||
m_pinDir2.initPin("ETB Dir 2", pinDir2);
|
||||
|
@ -101,7 +102,7 @@ public:
|
|||
);
|
||||
#endif // EFI_UNIT_TEST
|
||||
|
||||
dcMotor.configure(m_pwm1, wrappedDir1, wrappedDir2);
|
||||
dcMotor.configure(m_pwm1, wrappedDir1, wrappedDir2, isInverted);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -117,6 +118,7 @@ DcMotor* initDcMotor(const dc_io& io, size_t index, bool useTwoWires DECLARE_ENG
|
|||
io.directionPin1,
|
||||
io.directionPin2,
|
||||
io.disablePin,
|
||||
CONFIG(stepperDcInvertedPins),
|
||||
&ENGINE(executor),
|
||||
CONFIG(etbFreq)
|
||||
);
|
||||
|
|
|
@ -1118,6 +1118,13 @@ typedef enum __attribute__ ((__packed__)) {
|
|||
// todo: rename to dc_function_e? rename to hbrg_function_e?
|
||||
} etb_function_e;
|
||||
|
||||
typedef enum __attribute__ ((__packed__)) {
|
||||
STEPPER_FULL = 0,
|
||||
STEPPER_HALF = 2,
|
||||
STEPPER_FOURTH = 4,
|
||||
STEPPER_EIGHTH = 8,
|
||||
} stepper_num_micro_steps_e;
|
||||
|
||||
typedef enum __attribute__ ((__packed__)) {
|
||||
IPT_Low = 0,
|
||||
IPT_High = 1,
|
||||
|
|
|
@ -16,10 +16,11 @@ TwoPinDcMotor::TwoPinDcMotor(OutputPin& disablePin)
|
|||
disable();
|
||||
}
|
||||
|
||||
void TwoPinDcMotor::configure(IPwm& enable, IPwm& dir1, IPwm& dir2) {
|
||||
void TwoPinDcMotor::configure(IPwm& enable, IPwm& dir1, IPwm& dir2, bool isInverted) {
|
||||
m_enable = &enable;
|
||||
m_dir1 = &dir1;
|
||||
m_dir2 = &dir2;
|
||||
m_isInverted = isInverted;
|
||||
}
|
||||
|
||||
void TwoPinDcMotor::enable() {
|
||||
|
@ -86,8 +87,14 @@ bool TwoPinDcMotor::set(float duty)
|
|||
float dirDuty = m_type == ControlType::PwmDirectionPins ? duty : 1;
|
||||
|
||||
m_enable->setSimplePwmDutyCycle(enableDuty);
|
||||
m_dir1->setSimplePwmDutyCycle(isPositive ? dirDuty : 0);
|
||||
m_dir2->setSimplePwmDutyCycle(isPositive ? 0 : dirDuty);
|
||||
float recipDuty = 0;
|
||||
if (m_isInverted) {
|
||||
dirDuty = 1.0f - dirDuty;
|
||||
recipDuty = 1.0f;
|
||||
}
|
||||
|
||||
m_dir1->setSimplePwmDutyCycle(isPositive ? dirDuty : recipDuty);
|
||||
m_dir2->setSimplePwmDutyCycle(isPositive ? recipDuty : dirDuty);
|
||||
|
||||
// This motor has no fault detection, so always return false (indicate success).
|
||||
return false;
|
||||
|
|
|
@ -76,6 +76,7 @@ private:
|
|||
IPwm* m_dir2 = nullptr;
|
||||
OutputPin* const m_disable;
|
||||
float m_value = 0;
|
||||
bool m_isInverted = false;
|
||||
|
||||
ControlType m_type = ControlType::PwmDirectionPins;
|
||||
public:
|
||||
|
@ -86,7 +87,7 @@ public:
|
|||
*/
|
||||
TwoPinDcMotor(OutputPin& disable);
|
||||
|
||||
void configure(IPwm& enable, IPwm& dir1, IPwm& dir2);
|
||||
void configure(IPwm& enable, IPwm& dir1, IPwm& dir2, bool isInverted);
|
||||
|
||||
virtual bool set(float duty) override;
|
||||
float get() const override;
|
||||
|
|
|
@ -137,7 +137,7 @@ void StepperMotorBase::doIteration() {
|
|||
}
|
||||
|
||||
if (targetPosition == currentPosition) {
|
||||
m_hw->pause();
|
||||
m_hw->sleep();
|
||||
m_isBusy = false;
|
||||
return;
|
||||
}
|
||||
|
@ -188,8 +188,13 @@ bool StepDirectionStepper::pulse() {
|
|||
return true;
|
||||
}
|
||||
|
||||
void StepperHw::pause() const {
|
||||
chThdSleepMicroseconds((int)(MS2US(m_reactionTime)));
|
||||
void StepperHw::sleep(void) {
|
||||
pause();
|
||||
}
|
||||
|
||||
void StepperHw::pause(int divisor) const {
|
||||
// currently we can't sleep less than 1ms (see #3214)
|
||||
chThdSleepMicroseconds(maxI(MS2US(1), (int)(MS2US(m_reactionTime)) / divisor));
|
||||
}
|
||||
|
||||
void StepperHw::setReactionTime(float ms) {
|
||||
|
|
|
@ -15,7 +15,10 @@
|
|||
class StepperHw {
|
||||
public:
|
||||
virtual bool step(bool positive) = 0;
|
||||
void pause() const;
|
||||
// pause between steps
|
||||
void pause(int divisor = 1) const;
|
||||
// pause and enter the idle mode (less current consumption)
|
||||
virtual void sleep(void);
|
||||
|
||||
protected:
|
||||
void setReactionTime(float ms);
|
||||
|
@ -48,6 +51,11 @@ public:
|
|||
|
||||
bool step(bool positive) override;
|
||||
|
||||
void sleep(void) override;
|
||||
|
||||
protected:
|
||||
bool update(float dutyMult);
|
||||
|
||||
private:
|
||||
DcMotor* m_motorPhaseA = nullptr;
|
||||
DcMotor* m_motorPhaseB = nullptr;
|
||||
|
|
|
@ -22,12 +22,26 @@ static const int8_t phaseB[] =
|
|||
-1
|
||||
};
|
||||
|
||||
static const int8_t microPhases[] = {
|
||||
0, 20, 38, 56, 71, 83, 92, 98,
|
||||
100, 98, 92, 83, 71, 56, 38, 20,
|
||||
0, -20, -38, -56, -71, -83, -92, -98,
|
||||
-100, -98, -92, -83, -71, -56, -38, -20
|
||||
};
|
||||
|
||||
static const int maxNumSteps = 8;
|
||||
static constexpr int tableSizeMask = efi::size(microPhases) - 1;
|
||||
static constexpr float phaseDutyCycleDivisor = 1.0f / (100.0f * 100.0f); // both the phase degrees and duty cycle settings are in %
|
||||
|
||||
|
||||
void DualHBridgeStepper::initialize(DcMotor* motorPhaseA, DcMotor* motorPhaseB, float reactionTime)
|
||||
{
|
||||
setReactionTime(reactionTime);
|
||||
|
||||
m_motorPhaseA = motorPhaseA;
|
||||
m_motorPhaseB = motorPhaseB;
|
||||
|
||||
efiAssertVoid(CUSTOM_ERR_ASSERT, CONFIG(stepperNumMicroSteps) <= maxNumSteps, "stepperNumMicroSteps");
|
||||
}
|
||||
|
||||
bool DualHBridgeStepper::step(bool positive) {
|
||||
|
@ -36,6 +50,24 @@ bool DualHBridgeStepper::step(bool positive) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (CONFIG(stepperNumMicroSteps) > 1) {
|
||||
float dutyMult = CONFIG(stepperMaxDutyCycle) * phaseDutyCycleDivisor;
|
||||
int numStepIncr = maxNumSteps / CONFIG(stepperNumMicroSteps);
|
||||
if (!positive)
|
||||
numStepIncr = -numStepIncr;
|
||||
for (int i = CONFIG(stepperNumMicroSteps); i > 0; i--) {
|
||||
m_phase = (m_phase + numStepIncr) & tableSizeMask;
|
||||
update(dutyMult);
|
||||
// sleep 1/Nth of the pause time
|
||||
pause(CONFIG(stepperNumMicroSteps));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// For the full-stepping mode, we use a traditional "two phase on" drive model
|
||||
// because "wave drive" (one phase on) method has less torque.
|
||||
// For explanation, pls see: https://github.com/rusefi/rusefi/pull/3213#discussion_r700746453
|
||||
|
||||
// step phase, wrapping
|
||||
if (positive) {
|
||||
m_phase = (m_phase + 1) & 0x03;
|
||||
|
@ -43,12 +75,36 @@ bool DualHBridgeStepper::step(bool positive) {
|
|||
m_phase = (m_phase - 1) & 0x03;
|
||||
}
|
||||
|
||||
// Set phases according to the table
|
||||
m_motorPhaseA->set(phaseA[m_phase]);
|
||||
m_motorPhaseB->set(phaseB[m_phase]);
|
||||
|
||||
update(1.0f);
|
||||
pause();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool DualHBridgeStepper::update(float dutyMult) {
|
||||
if (!m_motorPhaseA || !m_motorPhaseB) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (CONFIG(stepperNumMicroSteps) > 1) {
|
||||
// phase B is 90 degrees shifted
|
||||
uint8_t m_phaseB = (m_phase + CONFIG(stepperNumMicroSteps)) & tableSizeMask;
|
||||
|
||||
// Set phases according to the table
|
||||
m_motorPhaseA->set(dutyMult * microPhases[m_phase]);
|
||||
m_motorPhaseB->set(dutyMult * microPhases[m_phaseB]);
|
||||
return true;
|
||||
}
|
||||
// Set phases according to the table
|
||||
m_motorPhaseA->set(phaseA[m_phase]);
|
||||
m_motorPhaseB->set(phaseB[m_phase]);
|
||||
return true;
|
||||
}
|
||||
|
||||
void DualHBridgeStepper::sleep(void) {
|
||||
float sleepingCoef = minI(CONFIG(stepperMinDutyCycle), CONFIG(stepperMaxDutyCycle)) * phaseDutyCycleDivisor;
|
||||
update(sleepingCoef);
|
||||
pause();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1356,7 +1356,7 @@ int16_t tps2Max;Full throttle#2. tpsMax value as 10 bit ADC value. Not Voltage!\
|
|||
bit enableVerboseCan2Tx;+CAN broadcast using custom rusEFI protocol\nenable can_broadcast/disable can_broadcast
|
||||
bit can2ReadEnabled;enable can_read/disable can_read
|
||||
bit can2WriteEnabled;enable can_write/disable can_write
|
||||
bit unused1126
|
||||
bit stepperDcInvertedPins;+Enable if DC-motor driver (H-bridge) inverts the signals (eg. RZ7899 on Hellen boards)
|
||||
bit unused1127
|
||||
bit unused1128
|
||||
bit unused1129
|
||||
|
@ -1437,7 +1437,13 @@ tle8888_mode_e tle8888mode;
|
|||
int16_t knockSamplingDuration;;"Deg", 1, 0, 0, 720, 0
|
||||
int16_t etbFreq;;"Hz", 1, 0, 0, @@ETB_HW_MAX_FREQUENCY@@, 0
|
||||
pid_s etbWastegatePid;
|
||||
uint8_t[4] unused2536;;"units", 1, 0, -20, 100, 0
|
||||
|
||||
#define stepper_num_micro_steps_e_enum "Full-Step (Default)", "INVALID", "Half-Step", "INVALID", "1/4 Micro-Step", "INVALID", "INVALID", "INVALID", "1/8 Micro-Step"
|
||||
custom stepper_num_micro_steps_e 1 bits, U08, @OFFSET@, [0:3], @@stepper_num_micro_steps_e_enum@@
|
||||
stepper_num_micro_steps_e stepperNumMicroSteps;+For micro-stepping, make sure that PWM frequency (etbFreq) is high enough;
|
||||
uint8_t stepperMinDutyCycle;+Use to limit the current when the stepper motor is idle, not moving (100% = no limit);"%", 1, 0, 0, 100, 0
|
||||
uint8_t stepperMaxDutyCycle;+Use to limit the max.current through the stepper motor (100% = no limit);"%", 1, 0, 0, 100, 0
|
||||
uint8_t unused2536;;"units", 1, 0, -20, 100, 0
|
||||
|
||||
angle_t[MAX_CYLINDER_COUNT iterate] timing_offset_cylinder;per-cylinder timing correction;"deg", 1, 0, -720, 720, 1
|
||||
|
||||
|
|
|
@ -2635,7 +2635,7 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@\x00\x31\x00\x00"
|
|||
field = "Second Idle Solenoid Pin", secondSolenoidPin, { !useStepperIdle && isDoubleSolenoidIdle }
|
||||
field = "Idle Solenoid Frequency", idle_solenoidFrequency, !useStepperIdle
|
||||
|
||||
dialog = hbridgeHardware, "H-Bridge Hardware"
|
||||
dialog = etbHbridgeHardware, "ETB H-Bridge Hardware"
|
||||
field = "PWM Frequency", etbFreq
|
||||
field = "Two-wire mode", etb_use_two_wires
|
||||
field = "No1 Direction #1", etbIo1_directionPin1
|
||||
|
@ -2647,25 +2647,49 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@\x00\x31\x00\x00"
|
|||
field = "No2 Control", etbIo2_controlPin
|
||||
field = "No2 Disable", etbIo2_disablePin
|
||||
|
||||
dialog = stepperHbridgeHardware, "Stepper H-Bridge Hardware"
|
||||
field = "Inverted driver pins", stepperDcInvertedPins
|
||||
field = "No1 Direction #1", stepperDcIo1_directionPin1
|
||||
field = "No1 Direction #2", stepperDcIo1_directionPin2
|
||||
field = "No1 Disable", stepperDcIo1_disablePin
|
||||
field = "No2 Direction #1", stepperDcIo2_directionPin1
|
||||
field = "No2 Direction #2", stepperDcIo2_directionPin2
|
||||
field = "No2 Disable", stepperDcIo2_disablePin
|
||||
|
||||
dialog = idleStepperHw, "Stepper Hardware"
|
||||
field = "Idle Stepper Step Pin", idle_stepperStepPin
|
||||
field = "Idle Stepper Dir Pin", idle_stepperDirectionPin
|
||||
field = "Idle Stepper Dir Pin Mode", stepperDirectionPinMode
|
||||
field = "Idle Stepper Enable Pin", stepperEnablePin
|
||||
field = "Idle Stepper Enable Pin Mode", stepperEnablePinMode
|
||||
|
||||
dialog = idleStepper, "Stepper"
|
||||
dialog = idleStepperHwType, "", xAxis
|
||||
panel = idleStepperHw, East, { useStepperIdle && !useHbridgesToDriveIdleStepper }
|
||||
panel = stepperHbridgeHardware, West, { useStepperIdle && useHbridgesToDriveIdleStepper }
|
||||
|
||||
dialog = idleStepperGeneral, ""
|
||||
field = "Drive stepper with dual H bridges", useHbridgesToDriveIdleStepper, useStepperIdle
|
||||
field = "Stepper reaction time", idleStepperReactionTime, useStepperIdle
|
||||
field = "Stepper total steps", idleStepperTotalSteps, useStepperIdle
|
||||
field = "Stepper parking extra steps, %", stepperParkingExtraSteps, useStepperIdle
|
||||
field = "Force parking every restart", stepperForceParkingEveryRestart, useStepperIdle
|
||||
panel = idleStepperHw, { useStepperIdle && !useHbridgesToDriveIdleStepper }
|
||||
; todo: reminder that enable condition has to match in both usages of hbridgeHardware due to TS defect?
|
||||
panel = hbridgeHardware, { etbFunctions1 != @@ETB_FUNCTION_NONE@@ || etbFunctions2 != @@ETB_FUNCTION_NONE@@ || (useStepperIdle && useHbridgesToDriveIdleStepper) }
|
||||
|
||||
dialog = idleHwType, "Idle Valve Hardware", border
|
||||
panel = idleSolenoid, West
|
||||
panel = idleStepper, East
|
||||
dialog = idleStepperMicro, "Micro-Stepping"
|
||||
field = "Stepping Mode", stepperNumMicroSteps, useHbridges
|
||||
field = "Min. Duty Cycle", stepperMinDutyCycle, { useHbridges && stepperNumMicroSteps > 1 }
|
||||
field = "Max. Duty Cycle", stepperMaxDutyCycle, { useHbridges && stepperNumMicroSteps > 1 }
|
||||
|
||||
dialog = idleStepperSettings, "", xAxis
|
||||
panel = idleStepperGeneral, East
|
||||
panel = idleStepperMicro, West
|
||||
|
||||
dialog = idleStepper, "Stepper"
|
||||
panel = idleStepperSettings
|
||||
panel = idleStepperHwType
|
||||
|
||||
dialog = idleHwType, "Idle Valve Hardware", yAxis
|
||||
panel = idleStepper
|
||||
panel = idleSolenoid
|
||||
|
||||
dialog = idlehw, "", yAxis
|
||||
field = "!ECU reboot needed to apply these settings"
|
||||
|
@ -3413,7 +3437,7 @@ cmd_set_engine_type_default = "@@TS_IO_TEST_COMMAND_char@@\x00\x31\x00\x00"
|
|||
; criteria for the same panel when used in multiple places
|
||||
; todo: report bug to TS?
|
||||
; another todo: split panel into two panels so that we can enable/disable h-bridge #1 separately from h-bridge #2
|
||||
panel = hbridgeHardware, { etbFunctions1 != @@ETB_FUNCTION_NONE@@ || etbFunctions2 != @@ETB_FUNCTION_NONE@@ || (useStepperIdle && useHbridgesToDriveIdleStepper) }
|
||||
panel = etbHbridgeHardware, { etbFunctions1 != @@ETB_FUNCTION_NONE@@ || etbFunctions2 != @@ETB_FUNCTION_NONE@@ || (useStepperIdle && useHbridgesToDriveIdleStepper) }
|
||||
|
||||
dialog = etbAutotune, "PID Autotune"
|
||||
field = "First step: calibrate TPS and hit 'Burn'"
|
||||
|
|
|
@ -67,7 +67,7 @@ TEST(DcMotor, PwmEnablePinModePositive) {
|
|||
EXPECT_CALL(dir1, setSimplePwmDutyCycle(1));
|
||||
EXPECT_CALL(dir2, setSimplePwmDutyCycle(0));
|
||||
|
||||
dut.configure(enable, dir1, dir2);
|
||||
dut.configure(enable, dir1, dir2, false);
|
||||
dut.set(0.5f);
|
||||
}
|
||||
|
||||
|
@ -85,7 +85,7 @@ TEST(DcMotor, PwmEnablePinModeNegative) {
|
|||
EXPECT_CALL(dir1, setSimplePwmDutyCycle(0));
|
||||
EXPECT_CALL(dir2, setSimplePwmDutyCycle(1));
|
||||
|
||||
dut.configure(enable, dir1, dir2);
|
||||
dut.configure(enable, dir1, dir2, false);
|
||||
dut.set(-0.5f);
|
||||
}
|
||||
|
||||
|
@ -103,7 +103,7 @@ TEST(DcMotor, PwmDirectionPinsModePositive) {
|
|||
EXPECT_CALL(dir1, setSimplePwmDutyCycle(0.5f));
|
||||
EXPECT_CALL(dir2, setSimplePwmDutyCycle(0));
|
||||
|
||||
dut.configure(enable, dir1, dir2);
|
||||
dut.configure(enable, dir1, dir2, false);
|
||||
dut.set(0.5f);
|
||||
}
|
||||
|
||||
|
@ -121,6 +121,6 @@ TEST(DcMotor, PwmDirectionPinsModeNegative) {
|
|||
EXPECT_CALL(dir1, setSimplePwmDutyCycle(0));
|
||||
EXPECT_CALL(dir2, setSimplePwmDutyCycle(0.5f));
|
||||
|
||||
dut.configure(enable, dir1, dir2);
|
||||
dut.configure(enable, dir1, dir2, false);
|
||||
dut.set(-0.5f);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue