Skip to content

Commit dfc7a72

Browse files
Remove stepper control, add callback function
Remove the stepper code due to its size. If it's included here it will take a large amount of IRAM in *all* sketches, even those which do not use it. Add in a traditional callback option, which will allow Stepper to be put in as a library, only being linked when necessary.
1 parent 9bcb761 commit dfc7a72

File tree

2 files changed

+32
-275
lines changed

2 files changed

+32
-275
lines changed

cores/esp8266/core_esp8266_waveform.c

+17-269
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
66
7-
The code idea is to have a programmable waveform generator with a unique
7+
The core idea is to have a programmable waveform generator with a unique
88
high and low period (defined in microseconds). TIMER1 is set to 1-shot
99
mode and is always loaded with the time until the next edge of any live
1010
waveforms or Stepper motors.
@@ -106,51 +106,8 @@ static Waveform waveform[] = {
106106
{0, 0, 0, 1, 0, 0, 0, 0} // GPIO16
107107
};
108108

109+
static uint32_t (*timer1CB)() = NULL;;
109110

110-
// Maximum umber of moves per stepper queue
111-
#define STEPPERQUEUESIZE 16
112-
113-
// Stepper generator can send # of steps with given velocity and linear acceleration
114-
// Can do any piecewise linear acceleration profile
115-
typedef struct {
116-
float j_2; // Jerk value/2, pulses/sec/sec/sec
117-
float a0; // Initial acceleration, pulses/sec/sec
118-
float v0; // Initial velocity, pulses/sec
119-
unsigned pulses : 16; // Total # of pulses to emit
120-
unsigned sync : 1; // Wait for all channels to have a sync before popping next move
121-
unsigned dir : 1; // CW=0 or CCW=1
122-
} Motion;
123-
124-
// Minimum clock cycles per step.
125-
#define MINSTEPCYCLES (4000)
126-
// Maxmimum clock cycles per step.
127-
#define MAXSTEPCYCLES (1000000000)
128-
129-
// Pre-allocated circular buffer
130-
typedef struct {
131-
Motion * move;
132-
uint32_t nextEventCycles;
133-
134-
// Copied from head for fast access
135-
uint16_t pulses; // Pulses remaining
136-
uint32_t cumCycles; // The "t" in our equations
137-
float j_2; // j/2 (jerk divided by 2.0)
138-
float a0; // Initial constant acceleration
139-
float v0; // Initial constant velocity
140-
unsigned sync : 1; // Wait for all steppers to finish before advancing
141-
unsigned dir : 1; // CCW or CW
142-
143-
unsigned finished : 1; // Done with all moves, on next hit pop another motion
144-
unsigned gpioPin : 5; // Allow all GPIOs, we're going to be slow no matter what
145-
146-
uint8_t readPtr; // Read queue index
147-
uint8_t writePtr; // Push queue spot
148-
uint8_t validEntries; // How many entries present
149-
} StepperQueue;
150-
151-
static volatile StepperQueue *stepQ = NULL;
152-
static volatile uint8_t stepQCnt = 0;
153-
static uint8_t stepDirPin = 16; // The weird one
154111

155112
// Helper functions
156113
static inline ICACHE_RAM_ATTR uint32_t MicrosecondsToCycles(uint32_t microseconds) {
@@ -207,130 +164,21 @@ static void deinitTimer() {
207164
timerRunning = false;
208165
}
209166

210-
// Called by the IRQ to move the next Motion to the head
211-
static inline ICACHE_RAM_ATTR void PopStepper(int i) {
212-
StepperQueue *q = (StepperQueue *)&stepQ[i];
213-
if (q->validEntries == 0) {
214-
q->sync = false;
215-
q->finished = true;
216-
q->nextEventCycles = 0;
217-
return;
218-
}
219-
q->finished = false;
220-
221-
Motion *head = &q->move[q->readPtr];
222-
q->pulses = head->pulses;
223-
q->cumCycles = 0;
224-
q->j_2 = head->j_2;
225-
q->a0 = head->a0;
226-
q->v0 = head->v0;
227-
q->sync = head->sync;
228-
q->dir = head->dir;
229-
q->nextEventCycles = 0; // (uint32_t)((clockCyclesPerMicrosecond()*1000000.0) / q->v0);
230-
q->readPtr = (q->readPtr + 1) & (STEPPERQUEUESIZE - 1);
231-
q->validEntries--;
232-
}
233-
234-
// Called by the user to detach a stepper and free memory
235-
int removeStepper(uint8_t pin) {
236-
sei();
237-
for (int i = 0; i < stepQCnt; i++) {
238-
if (stepQ[i].gpioPin == pin) {
239-
memmove((void*)&stepQ[i], (void*)&stepQ[i + 1], (stepQCnt - i - 1) * sizeof(stepQ[0]));
240-
stepQ = (StepperQueue*)realloc((void*)stepQ, (stepQCnt - 1) * sizeof(stepQ[0]));
241-
stepQCnt--;
242-
cli();
243-
return true;
244-
}
245-
}
246-
cli();
247-
return false;
248-
}
249-
250-
// Add a stepper move, return TRUE on success, FALSE on out of space
251-
// Calling application needs to ensure IRQS are disabled for the call!
252-
static int PushStepper(uint8_t gpioPin, const Motion *nextMove) {
253-
StepperQueue *q = NULL;
254-
int i;
255-
// gpioPin already validated in calling function
256-
sei();
257-
// Determine which queue it should be on, or maybe add one if needed
258-
for (i = 0; i < stepQCnt; i++) {
259-
if (stepQ[i].gpioPin == gpioPin) {
260-
q = (StepperQueue *)&stepQ[i];
261-
break;
262-
}
263-
}
264-
if (q == NULL) {
265-
// Make the stepper move array
266-
Motion *move = (Motion*)malloc(sizeof(stepQ[0].move) * STEPPERQUEUESIZE);
267-
if (!move) {
268-
cli();
269-
return false;
167+
// Set a callback. Pass in NULL to stop it
168+
void setTimer1Callback(uint32_t (*fn)()) {
169+
timer1CB = fn;
170+
if (!timerRunning && fn) {
171+
initTimer();
172+
} else if (timerRunning && !fn) {
173+
int cnt = 0;
174+
for (size_t i = 0; i < countof(waveform); i++) {
175+
cnt += waveform[i].enabled ? 1 : 0;
270176
}
271-
272-
// Add a queue
273-
StepperQueue *newStepQ = (StepperQueue*)realloc((void*)stepQ, (stepQCnt + 1) * sizeof(stepQ[0]));
274-
if (!newStepQ) {
275-
cli();
276-
free(move);
277-
return false;
177+
if (!cnt) {
178+
deinitTimer();
278179
}
279-
stepQ = newStepQ;
280-
q = (StepperQueue*) & (stepQ[stepQCnt]); // The one just added
281-
memset(q, 0, sizeof(*q));
282-
q->move = move;
283-
q->readPtr = 0;
284-
q->writePtr = 0;
285-
q->validEntries = 0;
286-
q->gpioPin = gpioPin;
287-
q->finished = true;
288-
i = stepQCnt;
289-
stepQCnt++;
290-
}
291-
// Have queue ready, can we fit this new one in?
292-
if (q->validEntries == STEPPERQUEUESIZE) {
293-
return false;
294-
}
295-
296-
// Store and record
297-
q->move[q->writePtr] = *nextMove; // Copy actual values
298-
q->validEntries++;
299-
q->writePtr = (q->writePtr + 1) & (STEPPERQUEUESIZE - 1);
300-
if (!timerRunning) {
301-
initTimer();
302-
ReloadTimer(10); // Cause an interrupt post-haste
303-
}
304-
if (!q->sync) {
305-
PopStepper(i); // If there's only this in the queue and we're not waiting for sync, start it up
306-
}
307-
cli();
308-
return true;
309-
}
310-
311-
// Called by user to add a PWL move to the queue, returns false if there is no space left
312-
int pushStepperMove(uint8_t pin, int dir, int sync, uint16_t pulses, float j, float a0, float v0) {
313-
if (pin > 16) {
314-
return false;
315180
}
316-
317-
Motion m;
318-
m.pulses = pulses;
319-
m.j_2 = j / 2.0;
320-
m.a0 = a0;
321-
m.v0 = v0;
322-
m.sync = sync ? 1 : 0;
323-
m.dir = dir ? 1 : 0;
324-
return PushStepper(pin, &m);
325-
}
326-
327-
// Assign a pin to stepper DIR
328-
int setStepperDirPin(uint8_t pin) {
329-
if (pin > 16) {
330-
return false;
331-
}
332-
stepDirPin = pin;
333-
return true;
181+
ReloadTimer(MicrosecondsToCycles(1)); // Cause an interrupt post-haste
334182
}
335183

336184
// Start up a waveform on a pin, or change the current one. Will change to the new
@@ -368,7 +216,7 @@ int stopWaveform(uint8_t pin) {
368216
for (size_t i = 0; i < countof(waveform); i++) {
369217
if (((pin == 16) && waveform[i].gpio16Mask) || ((pin != 16) && (waveform[i].gpioMask == 1<<pin))) {
370218
waveform[i].enabled = 0;
371-
int cnt = stepQCnt;
219+
int cnt = timer1CB?1:0;
372220
for (size_t i = 0; i < countof(waveform); i++) {
373221
cnt += waveform[i].enabled ? 1 : 0;
374222
}
@@ -382,105 +230,6 @@ int stopWaveform(uint8_t pin) {
382230
return false;
383231
}
384232

385-
386-
387-
// Send pulses for specific direction.
388-
// Stepper direction pin needs to be set before calling (helps ensure setup time)
389-
static ICACHE_RAM_ATTR void AdvanceSteppers(uint32_t deltaCycles, int dir) {
390-
static uint32_t toClear = 0; // Store last call's pins to allow us to meet hold time by clearing on the processing of the other dir
391-
uint32_t pulseGPIO = 0;
392-
for (size_t i = 0; i < stepQCnt; i++) {
393-
StepperQueue *q = (StepperQueue*)&stepQ[i];
394-
if (q->dir != dir || q->finished) {
395-
continue;
396-
}
397-
q->cumCycles += deltaCycles;
398-
uint32_t newNextEventCycles = q->nextEventCycles - deltaCycles;
399-
if ((deltaCycles >= q->nextEventCycles) || (newNextEventCycles <= CYCLES_FLUFF)) {
400-
// If there are no more pulses in the current motion, try to pop next one here
401-
if (!q->pulses) {
402-
if (!q->sync) {
403-
PopStepper(i);
404-
if (!q->pulses) {
405-
// We tried to pop, but there's nothing left, done!
406-
continue;
407-
}
408-
// We will generate the first pulse of the next motion later on in this loop
409-
} else {
410-
// Sync won't allow us to advance here. The main loop will have to
411-
// call this loop *again* after all are processed the first time
412-
// if we are sync'd.
413-
q->nextEventCycles = 0; // Don't look at this for timing
414-
continue;
415-
}
416-
}
417-
pulseGPIO |= 1 << q->gpioPin;
418-
q->pulses--;
419-
420-
// Forgive me for going w/FP. The dynamic range for fixed math would require many 64 bit multiplies
421-
static const float cycPerSec = 1000000.0 * clockCyclesPerMicrosecond();
422-
static const float secPerCyc = 1.0 / (1000000.0 * clockCyclesPerMicrosecond());
423-
float t = q->cumCycles * secPerCyc;
424-
float newVel = ((q->j_2 * t) + q->a0) * t + q->v0;
425-
uint32_t newPeriodCycles = (uint32_t)(cycPerSec / newVel);
426-
if (newPeriodCycles < MINSTEPCYCLES) {
427-
newPeriodCycles = MINSTEPCYCLES;
428-
} else if (newPeriodCycles > MAXSTEPCYCLES) {
429-
newPeriodCycles = MAXSTEPCYCLES;
430-
}
431-
q->nextEventCycles = newPeriodCycles;
432-
} else {
433-
q->nextEventCycles = newNextEventCycles;
434-
}
435-
}
436-
ClearGPIO(toClear & 0xffff);
437-
if (toClear & 0x80000) {
438-
GP16O &= ~1; // RMW is slow, only do if needed
439-
}
440-
SetGPIO(pulseGPIO & 0xffff);
441-
if (pulseGPIO & 0x80000) {
442-
GP16O |= 1; // RMW is slow, only do if needed
443-
}
444-
toClear = pulseGPIO;
445-
}
446-
447-
448-
static ICACHE_RAM_ATTR uint32_t ProcessSteppers(uint32_t deltaCycles) {
449-
ClearGPIOPin(stepDirPin);
450-
AdvanceSteppers(deltaCycles, 0);
451-
SetGPIOPin(stepDirPin);
452-
AdvanceSteppers(deltaCycles, 1);
453-
454-
// Check for sync, and if all set and 0 steps clear it
455-
bool haveSync = true;
456-
bool wantSync = false;
457-
for (int i = 0; i < stepQCnt; i++) {
458-
haveSync &= stepQ[i].sync && stepQ[i].finished;
459-
wantSync |= stepQ[i].sync;
460-
}
461-
462-
if (wantSync && haveSync) { // Sync requested, and hit
463-
for (int i = 0; i < stepQCnt; i++) {
464-
PopStepper(i);
465-
stepQ[i].nextEventCycles = 1; // Cause the pulse to fire immediately
466-
}
467-
// Hokey, but here only now could we know it was time to fire everyone again
468-
ClearGPIOPin(stepDirPin);
469-
AdvanceSteppers(deltaCycles, 0);
470-
SetGPIOPin(stepDirPin);
471-
AdvanceSteppers(deltaCycles, 1);
472-
}
473-
// When's the next event?
474-
uint32_t nextEventCycles = MicrosecondsToCycles(MAXIRQUS);
475-
for (size_t i = 0; i < stepQCnt; i++) {
476-
if (stepQ[i].nextEventCycles) {
477-
nextEventCycles = min_u32(nextEventCycles, stepQ[i].nextEventCycles);
478-
}
479-
}
480-
481-
return nextEventCycles;
482-
}
483-
484233
static ICACHE_RAM_ATTR void timer1Interrupt() {
485234
uint32_t nextEventCycles;
486235
#if F_CPU == 160000000
@@ -547,8 +296,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
547296
}
548297
}
549298

550-
if (stepQCnt) {
551-
nextEventCycles = min_u32(nextEventCycles, ProcessSteppers(deltaCycles));
299+
if (timer1CB) {
300+
nextEventCycles = min_u32(nextEventCycles, timer1CB());
552301
}
553302

554303
#if F_CPU == 160000000
@@ -568,4 +317,3 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
568317

569318
ReloadTimer(nextEventCycles);
570319
}
571-

cores/esp8266/core_esp8266_waveform.h

+15-6
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
55
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
66
7-
The code idea is to have a programmable waveform generator with a unique
7+
The core idea is to have a programmable waveform generator with a unique
88
high and low period (defined in microseconds). TIMER1 is set to 1-shot
99
mode and is always loaded with the time until the next edge of any live
1010
waveforms or Stepper motors.
@@ -61,17 +61,26 @@
6161
extern "C" {
6262
#endif
6363

64+
// Start or change a waveform of the specified high and low times on specific pin.
65+
// If runtimeUS > 0 then automatically stop it after that many usecs.
66+
// Returns true or false on success or failure.
6467
int startWaveform(uint8_t pin, uint32_t timeHighUS, uint32_t timeLowUS, uint32_t runTimeUS);
68+
// Stop a waveform, if any, on the specified pin.
69+
// Returns true or false on success or failure.
6570
int stopWaveform(uint8_t pin);
6671

67-
int setStepperDirPin(uint8_t pin);
68-
int pushStepperMove(uint8_t pin, int dir, int sync, uint16_t pulses, float j, float a0, float v0);
69-
int removeStepper(uint8_t pin);
72+
// Add a callback function to be called on *EVERY* timer1 trigger. The
73+
// callback returns the number of microseconds until the next desired call.
74+
// However, since it is called every timer1 interrupt, it may be called
75+
// again before this period. It should therefore use the ESP Cycle Counter
76+
// to determine whether or not to perform an operation.
77+
// Pass in NULL to disable the callback and, if no other waveforms being
78+
// generated, stop the timer as well.
79+
// Make sure the CBN function has the ICACHE_RAM_ATTR decorator.
80+
void setTimer1Callback(uint32_t (*fn)());
7081

7182
#ifdef __cplusplus
7283
}
7384
#endif
7485

75-
7686
#endif
77-

0 commit comments

Comments
 (0)