[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:
Andreika 2021-10-06 20:57:04 +03:00 committed by GitHub
parent e8710c0abd
commit bc4a068c9c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 145 additions and 29 deletions

View File

@ -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)
);

View File

@ -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,

View File

@ -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;

View File

@ -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;

View File

@ -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) {

View File

@ -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;

View File

@ -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

View File

@ -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

View File

@ -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'"

View File

@ -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);
}