From bc4a068c9c144652a746aea0e92afe0420e759da Mon Sep 17 00:00:00 2001 From: Andreika Date: Wed, 6 Oct 2021 20:57:04 +0300 Subject: [PATCH] [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 --- firmware/controllers/actuators/dc_motors.cpp | 6 +- firmware/controllers/algo/rusefi_enums.h | 7 +++ firmware/controllers/system/dc_motor.cpp | 13 +++- firmware/controllers/system/dc_motor.h | 3 +- firmware/hw_layer/stepper.cpp | 11 +++- firmware/hw_layer/stepper.h | 10 ++- firmware/hw_layer/stepper_dual_hbridge.cpp | 64 ++++++++++++++++++-- firmware/integration/rusefi_config.txt | 10 ++- firmware/tunerstudio/rusefi.input | 42 ++++++++++--- unit_tests/tests/test_dc_motor.cpp | 8 +-- 10 files changed, 145 insertions(+), 29 deletions(-) diff --git a/firmware/controllers/actuators/dc_motors.cpp b/firmware/controllers/actuators/dc_motors.cpp index 5ad3f2dd76..3619cab9d4 100644 --- a/firmware/controllers/actuators/dc_motors.cpp +++ b/firmware/controllers/actuators/dc_motors.cpp @@ -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) ); diff --git a/firmware/controllers/algo/rusefi_enums.h b/firmware/controllers/algo/rusefi_enums.h index 6cb60a3d34..ac7b0b9849 100644 --- a/firmware/controllers/algo/rusefi_enums.h +++ b/firmware/controllers/algo/rusefi_enums.h @@ -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, diff --git a/firmware/controllers/system/dc_motor.cpp b/firmware/controllers/system/dc_motor.cpp index 02469537b5..dfcf454144 100644 --- a/firmware/controllers/system/dc_motor.cpp +++ b/firmware/controllers/system/dc_motor.cpp @@ -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; diff --git a/firmware/controllers/system/dc_motor.h b/firmware/controllers/system/dc_motor.h index e04d4a9467..2e0d3edc88 100644 --- a/firmware/controllers/system/dc_motor.h +++ b/firmware/controllers/system/dc_motor.h @@ -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; diff --git a/firmware/hw_layer/stepper.cpp b/firmware/hw_layer/stepper.cpp index 77267b2876..ae470ac414 100644 --- a/firmware/hw_layer/stepper.cpp +++ b/firmware/hw_layer/stepper.cpp @@ -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) { diff --git a/firmware/hw_layer/stepper.h b/firmware/hw_layer/stepper.h index 44f3ae347a..419e0ef1ec 100644 --- a/firmware/hw_layer/stepper.h +++ b/firmware/hw_layer/stepper.h @@ -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; diff --git a/firmware/hw_layer/stepper_dual_hbridge.cpp b/firmware/hw_layer/stepper_dual_hbridge.cpp index c56213f181..70aafe935d 100644 --- a/firmware/hw_layer/stepper_dual_hbridge.cpp +++ b/firmware/hw_layer/stepper_dual_hbridge.cpp @@ -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 diff --git a/firmware/integration/rusefi_config.txt b/firmware/integration/rusefi_config.txt index 03e3092bc8..de0e86d74b 100644 --- a/firmware/integration/rusefi_config.txt +++ b/firmware/integration/rusefi_config.txt @@ -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 diff --git a/firmware/tunerstudio/rusefi.input b/firmware/tunerstudio/rusefi.input index 8fc73de58a..fdbc20e1ff 100644 --- a/firmware/tunerstudio/rusefi.input +++ b/firmware/tunerstudio/rusefi.input @@ -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'" diff --git a/unit_tests/tests/test_dc_motor.cpp b/unit_tests/tests/test_dc_motor.cpp index 343469ccbb..3274b2d17a 100644 --- a/unit_tests/tests/test_dc_motor.cpp +++ b/unit_tests/tests/test_dc_motor.cpp @@ -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); }