Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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/`

Expand Down
3 changes: 3 additions & 0 deletions keywords.txt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ move KEYWORD2
rotate KEYWORD2
setRPM KEYWORD2
getRPM KEYWORD2
setMinStepPulse KEYWORD2
getMinStepPulseHigh KEYWORD2
getMinStepPulseLow KEYWORD2
setCurrent KEYWORD2
enable KEYWORD2
disable KEYWORD2
Expand Down
8 changes: 4 additions & 4 deletions src/A4988.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
Expand Down
15 changes: 9 additions & 6 deletions src/A4988.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions src/BasicStepperDriver.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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){
Expand Down
24 changes: 21 additions & 3 deletions src/BasicStepperDriver.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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]
Expand Down
8 changes: 4 additions & 4 deletions src/DRV8825.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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()
{
Expand Down
15 changes: 9 additions & 6 deletions src/DRV8825.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/DRV8834.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
15 changes: 9 additions & 6 deletions src/DRV8834.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions src/DRV8880.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
19 changes: 13 additions & 6 deletions src/DRV8880.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 4 additions & 4 deletions src/TMC2100.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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);
Expand Down
15 changes: 9 additions & 6 deletions src/TMC2100.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading