diff --git a/AGENTS.md b/AGENTS.md index aa0f768..9a5c7bc 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -170,8 +170,14 @@ All CI must pass before merging PRs. - Must respect driver parameters such as microstep configuration, signal timing, etc. as documented in the driver chip datasheet ### Timing Constraints -- `step_high_min` / `step_low_min`: Minimum pulse durations (μs) - driver-specific -- `wakeup_time`: Time after enabling before movement (μs) +- `step_high_min` / `step_low_min`: Minimum pulse durations (μs) - driver-specific. + These are instance members; each driver defines a protected inline `initTiming()` + method in its header with the datasheet values and calls it from every constructor. + The base default is 1μs. Users can override at runtime with + `setMinStepPulse(high_us, low_us)` (needed on very fast MCUs like the Arduino Opta + whose GPIO outpaces the driver). +- `wakeup_time`: Time after enabling before movement (μs), instance member set by the + driver's `initTiming()` (base default 0, floored to 2μs in `enable()`). - High RPM + high microstepping = limited by MCU speed ### Movement Modes @@ -222,7 +228,9 @@ Before modifying timing-critical code paths: 3. Inherit from appropriate base class (typically `A4988` or `DRV8825`) 4. Override `getMaxMicrostep()` if different from parent 5. Override `setMicrostep()` if pin configuration differs -6. Update timing constants if needed (`step_high_min`, `wakeup_time`) +6. Update timing values if needed by defining a protected inline `initTiming()` method + in the driver's header that assigns `step_high_min`, `step_low_min`, and + `wakeup_time` (instance members), and calling it from every constructor 7. Add to `keywords.txt` for Arduino IDE highlighting 8. Create an example sketch in `examples/` diff --git a/keywords.txt b/keywords.txt index c677e3e..f5be7f9 100644 --- a/keywords.txt +++ b/keywords.txt @@ -16,6 +16,9 @@ move KEYWORD2 rotate KEYWORD2 setRPM KEYWORD2 getRPM KEYWORD2 +setMinStepPulse KEYWORD2 +getMinStepPulseHigh KEYWORD2 +getMinStepPulseLow KEYWORD2 setCurrent KEYWORD2 enable KEYWORD2 disable KEYWORD2 diff --git a/src/A4988.cpp b/src/A4988.cpp index 088ff6e..2e90f48 100644 --- a/src/A4988.cpp +++ b/src/A4988.cpp @@ -21,11 +21,11 @@ const uint8_t A4988::MS_TABLE[] = {0b000, 0b001, 0b010, 0b011, 0b111}; */ A4988::A4988(short steps, short dir_pin, short step_pin) :BasicStepperDriver(steps, dir_pin, step_pin) -{} +{ initTiming(); } A4988::A4988(short steps, short dir_pin, short step_pin, short enable_pin) :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin) -{} +{ initTiming(); } /* * Fully wired. @@ -34,12 +34,12 @@ A4988::A4988(short steps, short dir_pin, short step_pin, short enable_pin) A4988::A4988(short steps, short dir_pin, short step_pin, short ms1_pin, short ms2_pin, short ms3_pin) :BasicStepperDriver(steps, dir_pin, step_pin), ms1_pin(ms1_pin), ms2_pin(ms2_pin), ms3_pin(ms3_pin) -{} +{ initTiming(); } A4988::A4988(short steps, short dir_pin, short step_pin, short enable_pin, short ms1_pin, short ms2_pin, short ms3_pin) :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin), ms1_pin(ms1_pin), ms2_pin(ms2_pin), ms3_pin(ms3_pin) -{} +{ initTiming(); } void A4988::begin(float rpm, short microsteps){ BasicStepperDriver::begin(rpm, microsteps); diff --git a/src/A4988.h b/src/A4988.h index cadf2e8..cea23e5 100644 --- a/src/A4988.h +++ b/src/A4988.h @@ -18,12 +18,15 @@ class A4988 : public BasicStepperDriver { short ms1_pin = PIN_UNCONNECTED; short ms2_pin = PIN_UNCONNECTED; short ms3_pin = PIN_UNCONNECTED; - // tA STEP minimum, HIGH pulse width (1us) - static const int step_high_min = 1; - // tB STEP minimum, LOW pulse width (1us) - static const int step_low_min = 1; - // wakeup time, nSLEEP inactive to STEP (1000us) - static const int wakeup_time = 1000; + // Set timing requirements from A4988 datasheet + void initTiming(){ + // tA STEP HIGH pulse duration, min value (1us) + step_high_min = 1; + // tB STEP LOW pulse duration, min value (1us) + step_low_min = 1; + // tWAKE wakeup time, nSLEEP inactive to STEP (1000us) + wakeup_time = 1000; + } // also 200ns between ENBL/DIR/MSx changes and STEP HIGH // Get the microstep table diff --git a/src/BasicStepperDriver.cpp b/src/BasicStepperDriver.cpp index e03a2b3..552d164 100644 --- a/src/BasicStepperDriver.cpp +++ b/src/BasicStepperDriver.cpp @@ -342,7 +342,9 @@ long BasicStepperDriver::nextAction(void){ // account for calcStepPulse() execution time; sets ceiling for max rpm on slower MCUs last_action_end = micros(); m = last_action_end - m; - next_action_interval = (pulse > m) ? pulse - m : 1; + // floor the STEP LOW interval at step_low_min (datasheet tWL) instead of 1us + unsigned low_min = (unsigned)step_low_min; + next_action_interval = (pulse > m + low_min) ? pulse - m : low_min; } else { // end of move last_action_end = 0; @@ -380,7 +382,9 @@ void BasicStepperDriver::enable(void){ if IS_CONNECTED(enable_pin){ digitalWrite(enable_pin, enable_active_state); }; - delayMicros(2); + // wait the driver's wakeup time (datasheet tWAKE) before stepping, + // but at least 2us as the base default wakeup_time is 0 + delayMicros(stepperMax((short)2, wakeup_time)); } void BasicStepperDriver::disable(void){ diff --git a/src/BasicStepperDriver.h b/src/BasicStepperDriver.h index 033ddb0..d7d5ac8 100644 --- a/src/BasicStepperDriver.h +++ b/src/BasicStepperDriver.h @@ -74,11 +74,13 @@ class BasicStepperDriver { // current microstep level (1,2,4,8,...), must be < getMaxMicrostep() short microsteps = 1; // tWH(STEP) pulse duration, STEP high, min value (us) - static const int step_high_min = 1; + // Instance members (not static const) so derived drivers can set datasheet + // values in their constructors and users can override via setMinStepPulse(). + short step_high_min = 1; // tWL(STEP) pulse duration, STEP low, min value (us) - static const int step_low_min = 1; + short step_low_min = 1; // tWAKE wakeup time, nSLEEP inactive to STEP (us) - static const int wakeup_time = 0; + short wakeup_time = 0; float rpm = 0; @@ -137,6 +139,22 @@ class BasicStepperDriver { float getCurrentRPM(void){ return (60.0*1000000L / step_pulse / microsteps / motor_steps); } + /* + * Set minimum STEP pulse HIGH and LOW durations (microseconds). + * Useful on fast MCUs (e.g. Arduino Opta) whose GPIO toggles faster than + * the driver can register the default 1us pulses. See issue #136. + */ + void setMinStepPulse(short high_us, short low_us){ + step_high_min = (high_us > 0) ? high_us : 0; + // LOW interval must stay >= 1: nextAction() returning 0 means move complete + step_low_min = (low_us > 1) ? low_us : 1; + } + short getMinStepPulseHigh(void){ + return step_high_min; + } + short getMinStepPulseLow(void){ + return step_low_min; + } /* * Set speed profile - CONSTANT_SPEED, LINEAR_SPEED (accelerated) * accel and decel are given in [full steps/s^2] diff --git a/src/DRV8825.cpp b/src/DRV8825.cpp index d0800b7..1009f89 100644 --- a/src/DRV8825.cpp +++ b/src/DRV8825.cpp @@ -17,22 +17,22 @@ const uint8_t DRV8825::MS_TABLE[] = {0b000, 0b001, 0b010, 0b011, 0b100, 0b111}; DRV8825::DRV8825(short steps, short dir_pin, short step_pin) :A4988(steps, dir_pin, step_pin) -{} +{ initTiming(); } DRV8825::DRV8825(short steps, short dir_pin, short step_pin, short enable_pin) :A4988(steps, dir_pin, step_pin, enable_pin) -{} +{ initTiming(); } /* * A4988-DRV8825 Compatibility map: MS1-MODE0, MS2-MODE1, MS3-MODE2 */ DRV8825::DRV8825(short steps, short dir_pin, short step_pin, short mode0_pin, short mode1_pin, short mode2_pin) :A4988(steps, dir_pin, step_pin, mode0_pin, mode1_pin, mode2_pin) -{} +{ initTiming(); } DRV8825::DRV8825(short steps, short dir_pin, short step_pin, short enable_pin, short mode0_pin, short mode1_pin, short mode2_pin) :A4988(steps, dir_pin, step_pin, enable_pin, mode0_pin, mode1_pin, mode2_pin) -{} +{ initTiming(); } const uint8_t* DRV8825::getMicrostepTable() { diff --git a/src/DRV8825.h b/src/DRV8825.h index 68afe8f..5f2f2fa 100644 --- a/src/DRV8825.h +++ b/src/DRV8825.h @@ -15,12 +15,15 @@ class DRV8825 : public A4988 { protected: static const uint8_t MS_TABLE[]; - // tWH(STEP) pulse duration, STEP high, min value (1.9us) - static const int step_high_min = 2; - // tWL(STEP) pulse duration, STEP low, min value (1.9us) - static const int step_low_min = 2; - // tWAKE wakeup time, nSLEEP inactive to STEP (1000us) - static const int wakeup_time = 1700; + // Set timing requirements from DRV8825 datasheet + void initTiming(){ + // tWH(STEP) pulse duration, STEP high, min value (1.9us) + step_high_min = 2; + // tWL(STEP) pulse duration, STEP low, min value (1.9us) + step_low_min = 2; + // tWAKE wakeup time, nSLEEP inactive to STEP (1700us) + wakeup_time = 1700; + } // also 650ns between ENBL/DIR/MODEx changes and STEP HIGH // Get the microstep table diff --git a/src/DRV8834.cpp b/src/DRV8834.cpp index 6a048c3..e899893 100644 --- a/src/DRV8834.cpp +++ b/src/DRV8834.cpp @@ -15,22 +15,22 @@ */ DRV8834::DRV8834(short steps, short dir_pin, short step_pin) :BasicStepperDriver(steps, dir_pin, step_pin) -{} +{ initTiming(); } DRV8834::DRV8834(short steps, short dir_pin, short step_pin, short enable_pin) :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin) -{} +{ initTiming(); } /* * Fully wired. All the necessary control pins for DRV8834 are connected. */ DRV8834::DRV8834(short steps, short dir_pin, short step_pin, short m0_pin, short m1_pin) :BasicStepperDriver(steps, dir_pin, step_pin), m0_pin(m0_pin), m1_pin(m1_pin) -{} +{ initTiming(); } DRV8834::DRV8834(short steps, short dir_pin, short step_pin, short enable_pin, short m0_pin, short m1_pin) :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin), m0_pin(m0_pin), m1_pin(m1_pin) -{} +{ initTiming(); } /* * Set microstepping mode (1:divisor) diff --git a/src/DRV8834.h b/src/DRV8834.h index 7aeeec6..4939151 100644 --- a/src/DRV8834.h +++ b/src/DRV8834.h @@ -16,12 +16,15 @@ class DRV8834 : public BasicStepperDriver { protected: short m0_pin = PIN_UNCONNECTED; short m1_pin = PIN_UNCONNECTED; - // tWH(STEP) pulse duration, STEP high, min value (1.9us) - static const int step_high_min = 2; - // tWL(STEP) pulse duration, STEP low, min value (1.9us) - static const int step_low_min = 2; - // tWAKE wakeup time, nSLEEP inactive to STEP (1000us) - static const int wakeup_time = 1000; + // Set timing requirements from DRV8834 datasheet + void initTiming(){ + // tWH(STEP) pulse duration, STEP high, min value (1.9us) + step_high_min = 2; + // tWL(STEP) pulse duration, STEP low, min value (1.9us) + step_low_min = 2; + // tWAKE wakeup time, nSLEEP inactive to STEP (1000us) + wakeup_time = 1000; + } // also 200ns between ENBL/DIR/Mx changes and STEP HIGH // Get max microsteps supported by the device diff --git a/src/DRV8880.cpp b/src/DRV8880.cpp index 5bae6a8..ba2207b 100644 --- a/src/DRV8880.cpp +++ b/src/DRV8880.cpp @@ -14,30 +14,30 @@ */ DRV8880::DRV8880(short steps, short dir_pin, short step_pin) :BasicStepperDriver(steps, dir_pin, step_pin) -{} +{ initTiming(); } DRV8880::DRV8880(short steps, short dir_pin, short step_pin, short enable_pin) :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin) -{} +{ initTiming(); } /* * Fully wired. All the necessary control pins for DRV8880 are connected. */ DRV8880::DRV8880(short steps, short dir_pin, short step_pin, short m0, short m1) :BasicStepperDriver(steps, dir_pin, step_pin), m0(m0), m1(m1) -{} +{ initTiming(); } DRV8880::DRV8880(short steps, short dir_pin, short step_pin, short enable_pin, short m0, short m1) :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin), m0(m0), m1(m1) -{} +{ initTiming(); } DRV8880::DRV8880(short steps, short dir_pin, short step_pin, short m0, short m1, short trq0, short trq1) :BasicStepperDriver(steps, dir_pin, step_pin), m0(m0), m1(m1), trq0(trq0), trq1(trq1) -{} +{ initTiming(); } DRV8880::DRV8880(short steps, short dir_pin, short step_pin, short enable_pin, short m0, short m1, short trq0, short trq1) :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin), m0(m0), m1(m1), trq0(trq0), trq1(trq1) -{} +{ initTiming(); } void DRV8880::begin(float rpm, short microsteps){ BasicStepperDriver::begin(rpm, microsteps); diff --git a/src/DRV8880.h b/src/DRV8880.h index 8bc4e17..29db845 100644 --- a/src/DRV8880.h +++ b/src/DRV8880.h @@ -17,12 +17,19 @@ class DRV8880 : public BasicStepperDriver { short m1 = PIN_UNCONNECTED; short trq0 = PIN_UNCONNECTED; short trq1 = PIN_UNCONNECTED; - // tWH(STEP) pulse duration, STEP high, min value - static const int step_high_min = 0; // 0.47us - // tWL(STEP) pulse duration, STEP low, min value - static const int step_low_min = 0; // 0.47us - // tWAKE wakeup time, nSLEEP inactive to STEP - static const int wakeup_time = 1500; + // Set timing requirements from DRV8880 datasheet + void initTiming(){ + // tWH(STEP) pulse duration, STEP high, min value (0.47us -> 1; + // rounded up because a 0 delay could produce sub-datasheet pulses + // on fast ARM boards) + step_high_min = 1; + // tWL(STEP) pulse duration, STEP low, min value (0.47us -> 1; + // rounded up because a 0 delay could produce sub-datasheet pulses + // on fast ARM boards) + step_low_min = 1; + // tWAKE wakeup time, nSLEEP inactive to STEP (1500us) + wakeup_time = 1500; + } // also 200ns between ENBL/DIR/Mx changes and STEP HIGH // Get max microsteps supported by the device diff --git a/src/TMC2100.cpp b/src/TMC2100.cpp index f8bd10d..132b971 100644 --- a/src/TMC2100.cpp +++ b/src/TMC2100.cpp @@ -12,11 +12,11 @@ */ TMC2100::TMC2100(short steps, short dir_pin, short step_pin) :BasicStepperDriver(steps, dir_pin, step_pin) -{} +{ initTiming(); } TMC2100::TMC2100(short steps, short dir_pin, short step_pin, short enable_pin) :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin) -{} +{ initTiming(); } /* * Fully wired. @@ -25,12 +25,12 @@ TMC2100::TMC2100(short steps, short dir_pin, short step_pin, short enable_pin) TMC2100::TMC2100(short steps, short dir_pin, short step_pin, short cf1_pin, short cf2_pin) :BasicStepperDriver(steps, dir_pin, step_pin), cf1_pin(cf1_pin), cf2_pin(cf2_pin) -{} +{ initTiming(); } TMC2100::TMC2100(short steps, short dir_pin, short step_pin, short enable_pin, short cf1_pin, short cf2_pin) :BasicStepperDriver(steps, dir_pin, step_pin, enable_pin), cf1_pin(cf1_pin), cf2_pin(cf2_pin) -{} +{ initTiming(); } void TMC2100::begin(float rpm, short microsteps){ BasicStepperDriver::begin(rpm, microsteps); diff --git a/src/TMC2100.h b/src/TMC2100.h index 27ed754..3d0d961 100644 --- a/src/TMC2100.h +++ b/src/TMC2100.h @@ -13,12 +13,15 @@ class TMC2100 : public BasicStepperDriver { protected: short cf1_pin = PIN_UNCONNECTED; short cf2_pin = PIN_UNCONNECTED; - // tA STEP minimum, HIGH pulse width (1us) - static const int step_high_min = 1; - // tB STEP minimum, LOW pulse width (1us) - static const int step_low_min = 1; - // wakeup time, nSLEEP inactive to STEP (1000us) - static const int wakeup_time = 1000; + // Set timing requirements from TMC2100 datasheet + void initTiming(){ + // tA STEP HIGH pulse duration, min value (1us) + step_high_min = 1; + // tB STEP LOW pulse duration, min value (1us) + step_low_min = 1; + // tWAKE wakeup time, nSLEEP inactive to STEP (1000us) + wakeup_time = 1000; + } // Get max microsteps supported by the device short getMaxMicrostep() override;