4
4
5
5
Copyright (c) 2018 Earle F. Philhower, III. All rights reserved.
6
6
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
8
8
high and low period (defined in microseconds). TIMER1 is set to 1-shot
9
9
mode and is always loaded with the time until the next edge of any live
10
10
waveforms or Stepper motors.
@@ -106,51 +106,8 @@ static Waveform waveform[] = {
106
106
{0 , 0 , 0 , 1 , 0 , 0 , 0 , 0 } // GPIO16
107
107
};
108
108
109
+ static uint32_t (* timer1CB )() = NULL ;;
109
110
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
154
111
155
112
// Helper functions
156
113
static inline ICACHE_RAM_ATTR uint32_t MicrosecondsToCycles (uint32_t microseconds ) {
@@ -207,130 +164,21 @@ static void deinitTimer() {
207
164
timerRunning = false;
208
165
}
209
166
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 ;
270
176
}
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 ();
278
179
}
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;
315
180
}
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
334
182
}
335
183
336
184
// 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) {
368
216
for (size_t i = 0 ; i < countof (waveform ); i ++ ) {
369
217
if (((pin == 16 ) && waveform [i ].gpio16Mask ) || ((pin != 16 ) && (waveform [i ].gpioMask == 1 <<pin ))) {
370
218
waveform [i ].enabled = 0 ;
371
- int cnt = stepQCnt ;
219
+ int cnt = timer1CB ? 1 : 0 ;
372
220
for (size_t i = 0 ; i < countof (waveform ); i ++ ) {
373
221
cnt += waveform [i ].enabled ? 1 : 0 ;
374
222
}
@@ -382,105 +230,6 @@ int stopWaveform(uint8_t pin) {
382
230
return false;
383
231
}
384
232
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
-
484
233
static ICACHE_RAM_ATTR void timer1Interrupt () {
485
234
uint32_t nextEventCycles ;
486
235
#if F_CPU == 160000000
@@ -547,8 +296,8 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
547
296
}
548
297
}
549
298
550
- if (stepQCnt ) {
551
- nextEventCycles = min_u32 (nextEventCycles , ProcessSteppers ( deltaCycles ));
299
+ if (timer1CB ) {
300
+ nextEventCycles = min_u32 (nextEventCycles , timer1CB ( ));
552
301
}
553
302
554
303
#if F_CPU == 160000000
@@ -568,4 +317,3 @@ static ICACHE_RAM_ATTR void timer1Interrupt() {
568
317
569
318
ReloadTimer (nextEventCycles );
570
319
}
571
-
0 commit comments