-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathADC_Interrupt_Nano.ino
590 lines (469 loc) · 13.6 KB
/
ADC_Interrupt_Nano.ino
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
/**@file*/
/**
* @mainpage Overview
* (C) 2019 Dipl. Phys. Helmut Weber<br>
*
* Reading AD-Valures, which are generated by interrupts<br>
* ATMega328p: UNO, NANO, ...<br>
*
*
* Sketch uses 3872 bytes (12%) of program storage space. Maximum is 30720 bytes.
* Global variables use 388 bytes (18%) of dynamic memory, leaving 1660 bytes for local variables. Maximum is 2048 bytes.
*
* <h3>
* Introduction
* </h3>
*
* Realtime Operating systems are the prefered tool for most measurements.<br>
* ChibiOS and derivatives are running on the Arduino UNO.<br>
* But sometimes a TickTime of 1 ms is too long.<br>
*
* <h3>
* Interrupts
* </h3>
* Here I show another approach using Interrupts.
* Three curves are red from the AD-converter using AD-Interrupt-Conversion-Ready<br>
* About 6000 conversions per second are done.<br>
* The red values are filtered an may be displayed using "Serial Plotter"<br>
*
* <h3>
* Multitasking
* </h3>
* Besides "loop" 2 tasks are running in the backgrund"<br>
* One of them precisly ever 1 ms !
*
*
* <h3>
* Filter
* </h3>
* AD-Values for CHNUM channel are read using AD-Ready-Interrupts.
* One Channel after the other is read starting with channel 0 again.
*
* This is done in the background without any intervention of the LOOP
*
* The values are averaged for IRQ_SAMPLES samples
*
* This is the code for averaging:
* > avv=(float)analogVal; <br>
* > av[Channel] = (Alpha[Channel]*oldVal[Channel]) + ((1-Alpha[Channel])*avv); <br>
* > oldVal[Channel]=av[Channel]; <br>
*
* The sense is to get most samples possible, build an average over IRQ_SAMPLES value
* and set the IrqReadyFlag.
* Then the Measurement is stopped and the "Busy" Flag is set.
*
* LOOP or any other function has to read (and print/ plot) the values.
*
* After that the next points will be generated in the background again with:
*
* > IrqReadyFlag = 0; <br>
* > Busy = false; <br>
* > ADCSRA |= B01000000; // Start next conversion <br>
*
* <h3>
* Example-HEART-BEAT
* </h3>
* Here is an example of a HEART-BEAT-Sensor.
*
* \image html Demo.jpg
* \image latex Demo.jpg
*
* Blue - original
*
* Green - soft filtered
*
* Red - hard filtered<br
*
* The channels got an offset for better display.
*
* Filtereing implies a phase shift !
*
* <h3>
* Timing
* </h3>
* \image html Timing.jpg
* \image latex Timing.jpg
*
* We get about 6000 (filtered) samples per second - 2000 per channel
*
* - with 10 IRQ_SAMPLES there are 200 Time Points per second (180 with Serial.print)
*
* Enough to show the details of HEART-BEAT
*
* The output in LOOP needs 1,6 ms every 5,6 ms for a Time Point.
*
* There is plenty of room to do other things in LOOP !
*
* * Build pdf: in latex: pdflatex refman
*
* @author Helmut Weber
*/
//----------------------------------------------------------------------------------
/**
* @page AD-Interrupts AD-Interrupts
* The AD-Converter is initialized in "setup" and the first conversion is started<br>
* When AD is ready an interrupt to "ISR(ADC_vect)" is executed.<br>
* The Interrupt<br>
* * Reads the value from the actual channel (starting with channel 0)<br>
* * Does Filtering:<br>
* * > avv=(float)analogVal;
* * > av[Channel] = (Alpha[Channel]*oldVal[Channel]) + ((1-Alpha[Channel])*avv);<br>
* * > oldVal[Channel]=av[Channel];<br>
*
* Alpha[] for each channel must be set in "setup" befor<br>
*
* Then the AD is prepared for the next channel and another conversion is started.<br>
* If all channels got "IRQ_SAMPLES" values ReadyFlag is set to last channels+1<br>
* This is done to test "ReadyFlag>=" in "loop"<br>
* The "Busy" flag is set, which disallow further sampling.<br>
*
*/
//----------------------------------------------------------------------------------
/**
* @page Loop Loop
*
* "loop" test if there is a "ReadyFlag", which is the signal that a channel got<br>
* "IRQ_SAMPLES" (filtered) values. Because all channels get the same number of values the<br>
* After reading the filtered value of the channel the "Busy" Flag is set to 0 to enable<br>
* further AD-conversions and the ADC ist started again.
*
* "ReadyFlag" 's for all channels should send the "ReadyFlag" one after the other - <br>
* starting with channel 0.<br>
* The results are converted to an Ascii-String and a flag for output is set.<br>
*
* if NO "ReadyFlag" is set "loop" will:
* * Test, if there is an outputstring to send
* * Calls "Task_Loop"
* "Task_Loop" is called very frequently (up to 50 kHz), but there are gaps of<br>
* up to 1.6 ms.
*
*/
//----------------------------------------------------------------------------------
/**
* @page Task_Loop Task_Loop
*
* This task is called very often from "loop" but with great jitter.<br>
* The execution time must be (in this example) less than 100 µs
*
*/
//----------------------------------------------------------------------------------
/**
* @page Task_1ms Task_1ms
*
* This task is called precisely ever 1ms with very low jitter.<br>
* It is called from the Interrupt-Routine.
* The execution time must be (in this example) less than 100 µs
*
*/
// Note, many macro values are defined in <avr/io.h> and
// <avr/interrupts.h>, which are included automatically by
// the Arduino interface
/**
* \brief
* Number of AD-channels (1..5) to use
*/
#define CHNUM 3
/**
* \brief
* Number of IRQ-Samples to average befor setting "IrqReadyFlag"
*/
#define IRQ_SAMPLES 10
/**
* \brief
* "IrqReadyFlag" is set after IRQ_SAMPLES for each channel
*/
volatile int IrqReadyFlag;
// Globals
// Value to store analog result
volatile int analogVal;
int Channel=0;
/**
* \brief
* Alpha[channel] is the Filter-Parameter for each channel.<br>
* Is set in "setup".
* "Should be greater than 0.75 to see an effect.<br>
* MUST be less than 1.0 !!!
*/
float Alpha[CHNUM];
/**
* \brief
* av[channel] contains the last filtered value of a channel<br>
* It is build in ISR and used by loop to generate ouput-value for the plotter<br>
* "oldVal[]" is the last filtered value of a channel
* Read Only
*/
float av[CHNUM], oldVal[CHNUM];
/**
* \brief
* "Busy" is set together with ReadyFlag[channel] and disable reading new AD values in ISR<br>
* until it is reset in "loop"
*/
volatile bool Busy=false;
/**
* \brief
* "out[]" is the buffer of the line to send to the plotter.
* Only internal usage !
*/
char out[100];
/**
* \brief
* internal usage
*/
char *pt=out;
/**
* \brief
* internal usage
*/
char *strpt;
/**
* \brief
* internal usage
*/float val;
/**
* \brief
* "lastTaskTime" is used by ISR to call Task_1ms br>
*/
volatile unsigned long lastTaskTime=micros();
//int Trigger=400;
// End of Globals
/**
* \brief
* "ftoa"<br>
* This is the selfmade conversion from float to ascii-string<br>
* digits are the number of digits behind the"."
*/
char *ftoa(double f, int digits) {
static char b[31];
static char const digit[] = "0123456789";
char* p = b;
uint32_t i;
int d,j;
d=digits;
while (d) {
f*=10.0;
d--;
}
i=(uint32_t)f;
p=b+28;
j=0;
*p = 0;
*(p+1)=0;
do { //Move back, inserting digits as u go
if (j == digits) { p--; *p='.'; }
p--;
*p = digit[i % 10ll];
i = i/10ll;
j++;
} while(i);
return p; // return result as a pointer to string
}
/**
* \brief
* "Setup" sets the Alpha's for the channels (filtering, s.o) and<br>
* initialises the ADC to run with 125 kHz - no autorun, no freerun<br>
*
* pinModes are for output to oscilloscope only
*
*/
void setup(){
pinMode(3,OUTPUT);
pinMode(4,OUTPUT);
pinMode(5,OUTPUT);
pinMode(6,OUTPUT);
Alpha[0]=0.995;
Alpha[1]=0.98;
Alpha[2]=0.01;
Serial.begin(500000);
// Disable global interrupts
cli();
// clear ADLAR in ADMUX to right-adjust the result
// ADCL will contain lower 8 bits, ADCH upper 2 (in last two bits)
ADMUX &= B11011111;
// Set REFS1..0 in ADMUX (0x7C) to change reference voltage to the
// proper source (01)
ADMUX |= B01000000;
// Clear MUX3..0 in ADMUX (0x7C) in preparation for setting the analog
// input
ADMUX &= B11110000;
// Set MUX3 = channel=0 in ADMUX
Channel=0;
ADMUX |= Channel;
// ADMUX |= B00001000; // Binary equivalent
// Set ADEN in ADCSRA to enable the ADC.
ADCSRA |= B10000000;
// Set ADATE in ADCSRA (0x7A) to enable auto-triggering.
//// No Auto-Trigger
//// ADCSRA |= B00100000;
// free running.
// This means that as soon as an ADC has finished, the next will be
// immediately started.
//// No free running
//// ADCSRB &= B11111000;
// Set the Prescaler to 128 (16000KHz/128 = 125KHz)
ADCSRA |= B00000111;
// Set ADIE in ADCSRA to enable the ADC interrupt.
// Without this, the internal interrupt will not trigger.
ADCSRA |= B00001000;
// Enable global interrupts
sei();
// Kick off the first ADC
IrqReadyFlag = 0;
// Set ADSC in ADCSRA (0x7A) to start the ADC conversion
ADCSRA |=B01000000;
}
/**
* \brief
* "Task_Loop" <br>
* is called from "loop"r<br>
* It should be very short because loop should be able to react fast
* Often called, but with jitter !
*
* Usage: (examples) as human interface<br>
* * Driving a NeoPixel showing results as Color
* * Set signals at Digital Pins depending on measurements (HEART-BEAT)
*/
bool output = false;
void Task_Loop() {
char c;
//digitalWrite(3,HIGH);
PORTD |= (1<<3);
//digitalWrite(3,LOW);
PORTD &= ~(1<<3);
}
/**
* \brief
* "Task_1ms" <br>
* is called every ms with VERY LOW jitter!!!<br>
* It is called from ISR of the ADC - so it should be very short. <br>
*
* Usage: (examples) <br>
* * Driving a Stepper Motor
* * Set signals at Digital Pins depending on measurements (for external devices) with precision timing
*/
void Task_1ms() {
static float _old, _val;
static int count;
static int test;
//digitalWrite(4,HIGH);
PORTD |= (1<<4);
//digitalWrite(4,LOW);
PORTD &= ~(1<<4);
}
/**
* \brief
* "loop" <br>
* checks: <br>
* if "ReadyFlag" ( a channel got SAMPLE_NUM values) <br>
* * prepare string "out" for output <br>
* * reset "Busy" and "IrqReady" <br>
* * starts next conversion
*
* else <br>
* * Send "out" string
* * call "Task_Loop"
*
*
*/
void loop(){
static int cnt;
cnt++;
//digitalWrite(5, HIGH);
PORTD |= (1<<5);
// Check to see if the value has been updated
if (IrqReadyFlag){ // == Channel+1
// Busy is TRUE, we have to start conversion again
// Enable Interrupts as fast as possible:
val=av[IrqReadyFlag-1];
ADCSRA |= B01000000; // Start next conversion
Busy = false;
IrqReadyFlag = 0;
// Interrupts are ready again, DAC is started again
// Output:
// -- the simple way
// Serial.print(ftoa(val,2)); // Channel 0....
// //Serial.print(val); // Channel 0....
// Serial.print(", ");
// if (IrqReadyFlag==CHNUM) {
// Serial.println();
// }
// -- the fast way
// we use our own FTOA conversion and build a string from all channel values
strpt=ftoa(val,2); // calling fast own ftoa()
memcpy(pt,strpt,strlen(strpt)); // add string vslue from this channel
pt+=strlen(strpt); // store (add) string to out[]
*pt++=' '; // Separator for next value
if (IrqReadyFlag==CHNUM) { // this is the last value of all channels
*pt++='\n'; // End Of Line
*pt=0; // End of String
pt=out; // Reset out-ptr
}
}
else { // Print the line of values as text and do other tasks
cnt++;
if (cnt >10) {
cnt=11;
if (*out) {
Serial.print(out); // Interrrupts are running
out [0]=0;
}
}
// other Tasks
//Task_Loop();
} // else
Task_Loop();
//digitalWrite(5, LOW);
PORTD &= ~(1<<5);
}
/**
* \brief
* "ISR)ADC_Vector)"
*
* get fired (once), when an AD conversion is ready. <br>
* It <br>
* * calls Timer_1ms <br>
* * reads and filters actual channel <br>
* * adds channel*10 to read value for better display
* * if SAMPLE_NUM samples for each channel are reached: <br>
* * * sets Busy and IrqReday for loop <br>
* * increments channel <br>
* * starts next conversion
*
*/
// Interrupt service routine for the ADC completion
ISR(ADC_vect){
static unsigned int Counter[CHNUM];
float avv;
//digitalWrite(6, HIGH);
PORTD |= (1<<6);
// we use the ADC-Interrupt to do another task every 1ms
// with low jitter:
if ((micros() - lastTaskTime ) >=1000) {
lastTaskTime=micros();
Task_1ms();
}
if (Busy) return; // Block measurement if BUSY: Loop is working
// Output is running:
// Must read low first
analogVal = ADCL | (ADCH << 8);
analogVal+=Channel * 10; // Channels are better seen with offset
avv=(float)analogVal;
av[Channel] = (Alpha[Channel]*oldVal[Channel]) + ((1-Alpha[Channel])*avv);
oldVal[Channel]=av[Channel];
Counter[Channel]++;
if (Counter[Channel]==IRQ_SAMPLES) { // one channel has enough samples
// Done reading
Busy=true; // stop measurement
IrqReadyFlag = Channel + 1; // mark this channrl READY
Counter[Channel]=0;
}
Channel++; // next channel
if (Channel==CHNUM) Channel=0;
ADMUX &= B11111000; // Channel=0;
ADMUX |= Channel; // Set Channel
// Needed because free-running is disabled
// Set ADSC in ADCSRA (0x7A) to start another ADC conversion
// if Free Running is disabled:
ADCSRA |= B01000000; // Start next conversion
//digitalWrite(6, LOW);
PORTD &= ~(1<<6);
}