-
Notifications
You must be signed in to change notification settings - Fork 2
/
SimpleDSO.ino
2461 lines (2249 loc) · 101 KB
/
SimpleDSO.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
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
/*
* SimpleDSO_BlueDisplay.cpp
*
* Copyright (C) 2015-2023 Armin Joachimsmeyer
* Email: [email protected]
*
* This file is part of Arduino-Simple-DSO https://github.com/ArminJo/Arduino-Simple-DSO.
*
* Arduino-Simple-DSO is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/gpl.html>.
*
* Features:
* No dedicated hardware, just a plain arduino, a HC-05 Bluetooth module and this software.
* Full touch screen control of all parameters.
* 150/300 kSamples per second.
* Supports AC Measurement with (passive) external attenuator circuit.
* 3 different types of external attenuator detected by software.
* - no attenuator (pin 8+9 left open).
* - passive attenuator with /1, /10, /100 attenuation (pin 8 connected to ground).
* - active attenuator (pin 9 connected to ground).
* Automatic trigger, range and offset value selection.
* External trigger as well as delayed trigger possible.
* 1120 byte data buffer - 3 * display width.
* Min, max, average and peak to peak display.
* Period and frequency display.
* All settings can be changed during measurement.
* Gesture (swipe) control of timebase and chart.
* All AVR ADC input channels selectable.
* Touch trigger level select.
* 1.1 volt internal reference. 5 volt (VCC) also usable.
*
* The code can also be used as an example of C++/Assembler coding
* and complex interrupt handling routine
*
* The touch DSO software has 4 pages.
* 1. The start page which shows all the hidden buttons available at the chart page.
* 2. The chart page which shows the data and info line(s).
* 3. The settings page.
* 4. The frequency generator page.
* The display of new data on the settings and frequency generator page helps to understand the effect of the settings chosen.
*
* Control features:
* Long touch toggles between display of start and chart page.
* On chart page at the left a voltage picker (slider) is shown.
* If trigger manual is enabled a second slider area is shown to select trigger value.
* Short touch on chart page at no (hidden) button area and switches through the info line modes.
* Horizontal swipe on chart page changes timebase while running, else scrolls the data chart.
* Vertical swipe on chart page changes the range if manual range is enabled.
*
* Buttons at the settings page:
* "Trigger man timeout" means manual trigger value, but with timeout, i.e. if trigger condition not met, new data is shown after timeout.
* - This is not really a manual trigger level mode, but it helps to find the right trigger value.
* "Trigger man" means manual trigger, but without timeout, i.e. if trigger condition not met, no new data is shown.
* "Trigger free" means free running trigger, i.e. trigger condition is always met.
* "Trigger ext" uses pin 2 as external trigger source.
* "Trigger delay" can be specified from 4 us to 64.000 ms (if you really want).
*
* Info line at the chart page
* Short version - 1 line
* Average voltage, peak to peak voltage, frequency, timebase
*
* Long version - 2 lines
* 1. line: timebase, trigger slope, channel, (min, average, max, peak to peak) voltage, trigger level, reference voltage.
* - timebase: Time for div (31 pixel).
* - (min, average, max, peak to peak) voltage: In hold mode, chart is larger than display and the values are for the whole chart!
* - Reference voltage: 5=5V 1 1=1.1Volt-internal-reference.
* - Number of input channel (1-5) and Temp=AVR-temperature VRef=1.1Volt-internal-reference
*
* 2. line: frequency, period, 1st interval, 2nd interval (, trigger delay)
*
* "Offset man" is currently not implemented and works like "Offset 0V".
*
* "DC / AC" Button is only visible for these channels having a AC/DC switch/input at configurations ATTENUATOR_TYPE_FIXED_ATTENUATOR + ATTENUATOR_TYPE_ACTIVE_ATTENUATOR
*/
/*
* SIMPLE EXTERNAL ATTENUATOR CIRCUIT (configured by connecting pin 8 to ground)
*
* Attenuator circuit and AC/DC switch
*
* ADC INPUT_0 1.1 volt ADC INPUT_1 11 volt ADC INPUT_2 110 volt
* /\ /\ ______ /\ ______
* | +-----| 220k |----+ +----| 10k |-----------+
* | _______ | ______ | | ------ |
* +----| >4.7M |----+ +-----| 220k |----+ | ______ |
* | ------- | | ------ | +----| 4.7M |-+ 2*4.7M or
* _ | _ | _ ------ _ 3*3.3M |
* | | | | | | | | | | |
* | | 10k | | | 1M | | | 1 M | | 4.7 M |
* | | | | | | | | | | |
* - | - | - - |
* VREF | | | | | | |
* /\ +----+ | +----+ | +----+ +----+ |
* | | | | | | | | | | | |
* _ | = C 0.1uF | | = C 0.1uF | | = 0.1uF | = 0.1uF 400 volt
* | | | | | | | | | | | | |
* | | 100k O O | O O | O O O O |
* | | DC AC | DC AC | DC AC DC AC |
* - | | 1000 V Range |
* | | | |
* A5 ---+-------------+----------------+--------------------------+--------------------------------+
* AC bias | |
* _ | Sine/Triangle/Sawtooth D10 D2
* | | | O \/ /\
* | | 100k = 6.8uF | ______ | |
* | | | +--| 2.2k |-----+ _
* - | | ------ | | |
* | | = 100nF | | | 100k
* | | | O |_|
* GND ---+-------------+------------+ SquareWave 0.1 - 8 MHz | -
* O
* External_Trigger
*
*
*/
/*
* Attention: since 5.0 / 1024.0 = 0,004883 volt is the resolution of the ADC, depending of scale factor:
* 1. The output values for 2 adjacent display (min/max) values can be identical
* 2. The voltage picker value may not reflect the real sample value (e.g. shown for min/max)
*/
/*
* PIN
* 2 External trigger input
* 3 Not yet used
* 4 - If active attenuator, attenuator range control, else not yet used
* 5 - If active attenuator, attenuator range control, else not yet used
* 6 - If active attenuator, AC (high) / DC (low) relay, else debug output of half the timebase of Timer 0 for range 496 us and higher -> frequency <= 31,25 kHz (see changeTimeBaseValue())
* 7 Not yet used
* 8 Attenuator configuration input with internal pullup - bit 0
* 9 Attenuator configuration input with internal pullup - bit 1 11-> no attenuator attached, 10-> simple (channel 0-2) attenuator attached, 0x-> active (channel 0-1) attenuator attached
* 10 Frequency / waveform generator output of Timer1 (16 bit)
* 11 - If active attenuator, square wave for VEE (-5 volt) generation by timer2 output, else not yet used
* 12 Not yet used
* 13 Internal LED / timing debug output
*
* A5 AC bias - if mode is AC, high impedance (input), if mode is DC, set as output LOW
*
* Timer0 8 bit - Generates sample frequency
* Timer1 16 bit - Internal waveform generator
* Timer2 8 bit - Triggers ISR for Arduino millis() since timer0 is not available for this (switched from timer 0 at setup())
*/
/*
* IMPORTANT - do not use Arduino Serial.* here otherwise the usart interrupt kills the timing.
*/
//#define DEBUG
#include <Arduino.h>
/*
* Settings to configure the BlueDisplay library and to reduce its size
*/
#define DISPLAY_HEIGHT 256 // We use 8 bit resolution and have 256 different analog values
#define DISPLAY_WIDTH 320 // Use the size of the local LCD screens available
//#define BLUETOOTH_BAUD_RATE BAUD_115200 // Activate this, if you have reprogrammed the HC05 module for 115200, otherwise 9600 is used as baud rate
//#define DO_NOT_NEED_BASIC_TOUCH_EVENTS // Disables basic touch events like down, move and up. Saves 620 bytes program memory and 36 bytes RAM
#define USE_SIMPLE_SERIAL // Do not use the Serial object. Saves up to 1250 bytes program memory and 185 bytes RAM, if Serial is not used otherwise
#if !defined(USE_SIMPLE_SERIAL)
#error SimpleDSO works only with USE_SIMPLE_SERIAL activated, since the serial interrupts kill the DSO timing!
#endif
#include "BlueDisplay.hpp"
#include "SimpleDSO_BlueDisplay.h"
#include "LocalDisplay/digitalWriteFast.h"
#include "FrequencyGeneratorPage.hpp" // include sources
#include "TouchDSOGui.hpp" // include sources
#include "ADCUtils.hpp" // for getVCCVoltage()
/**********************
* Buttons
*********************/
BDButton TouchButtonBack;
// global flag for page control. Is evaluated by calling loop or page and set by buttonBack handler
bool sBackButtonPressed;
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__)
//#define INTERNAL1V1 2
#undef INTERNAL
#define INTERNAL 2
#else
#define INTERNAL 3
#endif
#if !defined(TIMSK2)
// on ATmega32U4 we have no timer2 but one timer3
#define TIMSK2 TIMSK3
#define TOIE2 TOIE3
#endif
#define ADC_TEMPERATURE_CHANNEL 0x08
#define ADC_1_1_VOLT_CHANNEL 0x0E
/*
* Timebase values overview: Polling mode
* conversion Fast Ultra
* idx range ADCpresc. clk us us/div us/320 x-scale TIMER0 CTC mode fast
* 0 10us PRESCALE4 0.25 3.25 101.75 1040 10 prescaler x
* 1 20us PRESCALE4 0.25 3.25 101.75 1040 5 micros x
* 2 50us PRESCALE4 0.25 3.25 101.75 1040 2 value x
* 3 101us PRESCALE8 0.5 6.5 201.5 2080 2 x
* 4 201us PRESCALE8 0.5 6.5 201.5 2080 1 x
* 5 496us PRESCALE16 1 16 496 5120 1 8 0.5 32
* 6 1ms PRESCALE32 2 32 992 10240 1 8 0.5 64
* 7 2ms PRESCALE64 4 64 1984 20480 1 8 0.5 128
* 8 5ms PRESCALE128 8 160 4960 51200 1 64 4 40
* 9 10ms PRESCALE128 8 320 9920 102400 1 64 4 80 Draw while
* 10 20ms PRESCALE128 8 648 20088 207360 1 64 4 162 acquire
* 11 50ms PRESCALE128 8 1616 50096 517120 1 256 16 101 x
* 12 100ms PRESCALE128 8 3224 99944 517120 1 256 16 201.5 x
* 12 100ms PRESCALE128 8 3216 99696 517120 1 256 16 201 x
* 12 100ms PRESCALE128 8 3232 100192 517120 1 256 16 202 x
* 13 200ms PRESCALE128 8 6448 199888 517120 1 1024 64 100.75 x
* 14 200ms PRESCALE128 8 6464 200384 517120 1 1024 64 101 x
* 15 500ms PRESCALE128 8 16128 499968 5160960 1 1024 64 252 x
*/
// for 31 grid
const uint16_t TimebaseDivPrintValues[TIMEBASE_NUMBER_OF_ENTRIES] PROGMEM = { 10, 20, 50, 101, 201, 496, 1, 2, 5, 10, 20, 50, 100,
200, 500 };
// exact values for 31 grid - for period and frequency
const float TimebaseExactDivValuesMicros[TIMEBASE_NUMBER_OF_ENTRIES] PROGMEM
= { 100.75/*(31*13*0,25)*/, 100.75, 100.75, 201.5, 201.5 /*(31*13*0,5)*/, 496 /*(31*16*1)*/, 992 /*(31*16*2)*/, 1984 /*(31*16*4)*/,
4960 /*(31*20*8)*/, 9920 /*(31*40*8)*/, 20088 /*(31*81*8)*/, 50096 /*(31*202*8)*/, 99696 /*(31*201*16)*/,
200384 /*(31*808*8)*/, 499968 /*(31*2016*8)*/};
const uint8_t xScaleForTimebase[TIMEBASE_NUMBER_OF_XSCALE_CORRECTION] = { 10, 5, 2, 2 }; // multiply displayed values to simulate a faster timebase.
// since prescale PRESCALE4 has bad quality use PRESCALE8 for 201 us range and display each value twice
// All timebase >= TIMEBASE_NUMBER_OF_FAST_PRESCALE use PRESCALE128
const uint8_t ADCPrescaleValueforTimebase[TIMEBASE_NUMBER_OF_FAST_PRESCALE] = { ADC_PRESCALE4, ADC_PRESCALE4, ADC_PRESCALE4,
ADC_PRESCALE8, ADC_PRESCALE8, ADC_PRESCALE16 /*496us*/, ADC_PRESCALE32, ADC_PRESCALE64 /*2ms*/};
const uint8_t CTCValueforTimebase[TIMEBASE_NUMBER_OF_ENTRIES - TIMEBASE_NUMBER_OF_FAST_MODES] = { 32/*496us*/, 64, 128/*2ms*/, 40,
80/*10ms*/, 162, 101, 201, 101, 252 };
// only for information - actual code needs 2 bytes more than code using this table, but this table takes 10 byte of RAM/Stack
const uint8_t CTCPrescaleValueforTimebase[TIMEBASE_NUMBER_OF_ENTRIES - TIMEBASE_NUMBER_OF_FAST_MODES] = { TIMER0_PRESCALE8/*496us*/,
TIMER0_PRESCALE8, TIMER0_PRESCALE8/*2ms*/, TIMER0_PRESCALE64,
TIMER0_PRESCALE64/*10ms*/, TIMER0_PRESCALE64, TIMER0_PRESCALE256, TIMER0_PRESCALE256, TIMER0_PRESCALE1024,
TIMER0_PRESCALE1024 };
/*
* storage for millis value to enable compensation for interrupt disable at signal acquisition etc.
*/
extern volatile unsigned long timer0_millis;
/****************************************
* Automatic triggering and range stuff
*/
#define TRIGGER_WAIT_NUMBER_OF_SAMPLES 3300 // Number of samples (<=112us) used for detecting the trigger condition
#define ADC_CYCLES_PER_CONVERSION 13
#define RANGE_CHANGE_DELAY_MILLIS 2000
#define ADC_MAX_CONVERSION_VALUE (1024 -1) // 10 bit
#define ATTENUATOR_FACTOR 10
#define DISPLAY_AC_ZERO_OFFSET_GRID_COUNT (-3)
/******************************
* Measurement control values
*****************************/
struct MeasurementControlStruct MeasurementControl;
/*
* Display control
* while running switch between upper info line on/off
* while stopped switch between chart / t+info line and gui
*/
DisplayControlStruct DisplayControl;
// Union to speed up the combination of low and high bytes to a word
// it is not optimal since the compiler still generates 2 unnecessary moves
// but using -- value = (high << 8) | low -- gives 5 unnecessary instructions
union Myword {
struct {
uint8_t LowByte;
uint8_t HighByte;
} byte;
uint16_t Word;
uint8_t *BytePointer;
};
// definitions from <wiring_private.h>
#if !defined(cbi)
#define cbi(sfr, bit) (_SFR_BYTE(sfr) &= ~_BV(bit))
#endif
#if !defined(sbi)
#define sbi(sfr, bit) (_SFR_BYTE(sfr) |= _BV(bit))
#endif
/***************
* Debug stuff
***************/
#if defined(DEBUG)
void printDebugData(void);
uint16_t DebugValue1;
uint16_t DebugValue2;
uint16_t DebugValue3;
uint16_t DebugValue4;
#endif
/***********************************************************************************
* Put those variables at the end of data section (.data + .bss + .noinit)
* adjacent to stack in order to have stack overflows running into DataBuffer array
* *********************************************************************************/
/*
* a string buffer for any purpose...
*/
char sStringBuffer[SIZEOF_STRINGBUFFER] __attribute__((section(".noinit")));
// safety net - array overflow will overwrite only DisplayBuffer
DataBufferStruct DataBufferControl __attribute__((section(".noinit")));
/*******************************************************************************************
* Function declaration section
*******************************************************************************************/
void acquireDataFast(void);
// Measurement auto control stuff (trigger, range + offset)
void computeAutoTrigger(void);
void setInputRange(uint8_t aShiftValue, uint8_t aActiveAttenuatorValue);
bool checkRAWValuesForClippingAndChangeRange(void);
void computeAutoRange(void);
void computeAutoOffset(void);
// Attenuator support stuff
void setAttenuator(uint8_t aNewValue);
uint16_t getAttenuatorFactor(void);
// GUI initialization and drawing stuff
void initDSOGUI(void);
void activateChartGui(void);
// BUTTON handler section
// Graphical output section
void clearDisplayedChart(uint8_t *aDisplayBufferPtr);
void drawRemainingDataBufferValues(void);
//Hardware support section
void setVCCValue(void);
inline void setPrescaleFactor(uint8_t aFactor);
void setADCReferenceShifted(uint8_t aReferenceShifted);
void setTimer2FastPWMOutput();
void initTimer2(void);
/*******************************************************************************************
* Program code starts here
* Setup section
*******************************************************************************************/
void initDisplay(void) {
BlueDisplay1.setFlagsAndSize(
BD_FLAG_FIRST_RESET_ALL | BD_FLAG_USE_MAX_SIZE | BD_FLAG_LONG_TOUCH_ENABLE | BD_FLAG_ONLY_TOUCH_MOVE_DISABLE,
DISPLAY_WIDTH, DISPLAY_HEIGHT);
BlueDisplay1.setCharacterMapping(0xD1, 0x21D1); // Ascending in UTF16 - for printInfo()
BlueDisplay1.setCharacterMapping(0xD2, 0x21D3); // Descending in UTF16 - for printInfo()
BlueDisplay1.setCharacterMapping(0xD4, 0x2227); // UP (logical AND) in UTF16
BlueDisplay1.setCharacterMapping(0xD5, 0x2228); // Down (logical OR) in UTF16
//\xB0 is degree character
//BlueDisplay1.setButtonsTouchTone(TONE_PROP_BEEP_OK, 80);
initDSOGUI();
#if !defined(__AVR_ATmega32U4__)
// no frequency page in order to save space for leonardo
initFrequencyGeneratorPage();
#endif
}
void setup() {
// Set outputs at port D
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || defined(ARDUINO_AVR_LEONARDO) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)
pinMode(ATTENUATOR_0_PIN, OUTPUT);
pinMode(ATTENUATOR_1_PIN, OUTPUT);
pinMode(AC_DC_RELAY_PIN, OUTPUT);
#else
DDRD = (DDRD & ~OUTPUT_MASK_PORTD) | OUTPUT_MASK_PORTD;
#endif
// Set outputs at port B
// pinMode(TIMER_1_OUTPUT_PIN, OUTPUT);
// pinMode(VEE_PIN, OUTPUT);
// pinMode(DEBUG_PIN, OUTPUT);
// 3 pinMode replaced by:
DDRB = (DDRB & ~OUTPUT_MASK_PORTB) | OUTPUT_MASK_PORTB;
pinMode(ATTENUATOR_DETECT_PIN_0, INPUT_PULLUP);
pinMode(ATTENUATOR_DETECT_PIN_1, INPUT_PULLUP);
// Set outputs at port C
// For DC mode, change AC_DC_BIAS_PIN pin to output and set bias to 0 volt
DDRC = OUTPUT_MASK_PORTC;
digitalWriteFast(AC_DC_BIAS_PIN, LOW);
// Shutdown SPI and TWI, enable all timers, USART and ADC
#if defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega1284__) || defined(__AVR_ATmega1284P__) || defined(__AVR_ATmega644__) || defined(__AVR_ATmega644A__) || defined(__AVR_ATmega644P__) || defined(__AVR_ATmega644PA__) || defined(ARDUINO_AVR_LEONARDO) || defined(__AVR_ATmega16U4__) || defined(__AVR_ATmega32U4__)
PRR0 = _BV(PRTWI) | _BV(PRTWI);
#else
PRR = _BV(PRTWI) | _BV(PRTWI);
#endif
// Disable digital input on all ADC channel pins to reduce power consumption for levels near half VCC
DIDR0 = ADC0D | ADC1D | ADC2D | ADC3D | ADC4D | ADC5D;
// Must be simple serial for the DSO!
initSerial();
// initialize values
MeasurementControl.isRunning = false;
MeasurementControl.TriggerSlopeRising = true;
MeasurementControl.TriggerMode = TRIGGER_MODE_AUTOMATIC;
MeasurementControl.isSingleShotMode = false;
/*
* Read input pins to determine attenuator type
*/
MeasurementControl.ChannelHasActiveAttenuator = false;
MeasurementControl.AttenuatorValue = 0; // set direct input as default
// read type of attached attenuator from 2 external connected pins
uint8_t tAttenuatorType = !digitalReadFast(ATTENUATOR_DETECT_PIN_0);
tAttenuatorType |= (!digitalReadFast(ATTENUATOR_DETECT_PIN_1)) << 1;
MeasurementControl.AttenuatorType = tAttenuatorType;
uint8_t tStartChannel = 0;
if (tAttenuatorType == ATTENUATOR_TYPE_FIXED_ATTENUATOR) {
tStartChannel = 1;
}
/*
* disable Timer0 and start Timer2 as replacement to maintain millis()
*/
TIMSK0 = _BV(OCIE0A); // enable timer0 Compare match A interrupt, since we need timer0 for timebase
initTimer2(); // start timer2 to maintain millis() (and for generating VEE - negative voltage for external hardware)
if (tAttenuatorType >= ATTENUATOR_TYPE_ACTIVE_ATTENUATOR) {
setTimer2FastPWMOutput(); // enable timer2 for output 1 kHz at Pin11 generating VEE (negative voltage for external hardware)
}
MeasurementControl.TimebaseIndex = 0;
changeTimeBaseValue(TIMEBASE_INDEX_START_VALUE);
/*
* changeTimeBaseValue() needs:
* TimebaseIndex
* TriggerMode
* AttenuatorValue
*
* changeTimeBaseValue() sets:
* DrawWhileAcquire
* TimebaseDelay
* TimebaseDelayRemaining
* TriggerTimeoutSampleCount
* XScale
* Timer0
*/
MeasurementControl.RawDSOReadingACZero = 0x200;
delay(100);
setChannel(tStartChannel); // outputs button caption!
/*
* setChannel() requires:
* AttenuatorType
* isACMode
* VCC
*
* setChannel() sets:
* ShiftValue = 2
* ADCInputMUXChannel
* ADCInputMUXChannelChar
*
* And if AttenuatorDetected == true also:
* ChannelHasAttenuator
* isACModeANDChannelHasAttenuator
* AttenuatorValue
* ADCReferenceShifted
*
* setChannel calls setInputRange(2,2) and this sets:
* OffsetValue
* HorizontalGridSizeShift8
* HorizontalGridVoltage
*/
MeasurementControl.RangeAutomatic = true;
MeasurementControl.OffsetMode = OFFSET_MODE_0_VOLT;
DataBufferControl.DataBufferDisplayStart = &DataBufferControl.DataBuffer[0];
DisplayControl.EraseColor = COLOR_BACKGROUND_DSO;
DisplayControl.showHistory = false;
DisplayControl.DisplayPage = DSO_PAGE_START;
DisplayControl.showInfoMode = INFO_MODE_SHORT_INFO;
//setACMode(!digitalReadFast(AC_DC_PIN));
clearDataBuffer();
// first synchronize. Since a complete chart data can be missing, send minimum 320 byte
for (int i = 0; i < 16; ++i) {
BlueDisplay1.sendSync();
}
// Register callback handler and check for connection
BlueDisplay1.initCommunication(&initDisplay, &redrawDisplay);
registerSwipeEndCallback(&doSwipeEndDSO);
registerTouchUpCallback(&doSwitchInfoModeOnTouchUp);
registerLongTouchDownCallback(&doLongTouchDownDSO, 900);
BDButton::playFeedbackTone();
delay(400);
setVCCValue(); // this sets ADMUX to 1.1 volt reference channel
BDButton::playFeedbackTone();
initStackFreeMeasurement();
}
/************************************************************************
* main loop - 32 microseconds
************************************************************************/
// noreturn saves 56 byte program memory! but no stack
void __attribute__((noreturn)) loop(void) {
uint32_t sMillisOfLastInfoOutput;
bool sDoInfoOutput = true; // Output info at least every second for settings page, single shot or trigger not found
for (;;) {
checkAndHandleEvents();
if (BlueDisplay1.mBlueDisplayConnectionEstablished) {
/*
* Check for cyclic info output
*/
if (millis() - sMillisOfLastInfoOutput > MILLIS_BETWEEN_INFO_OUTPUT) {
sMillisOfLastInfoOutput = millis();
sDoInfoOutput = true;
}
if (MeasurementControl.isRunning) {
if (MeasurementControl.AcquisitionFastMode) {
/*
* Fast mode here <= 201us/div
*/
acquireDataFast();
}
if (DataBufferControl.DataBufferFull) {
/*
* Data (from InterruptServiceRoutine) is ready
*/
/*
* Enable Timer0 overflow interrupt again
* and compensate for missing ticks because timer was disabled not to disturb acquisition.
* 320.0 / 31.0 = divs per screen
* 4 * 256 = micro seconds per interrupt
*/
uint32_t tCompensation = ((320.0 / 31.0) / (4 * 256))
* pgm_read_float(&TimebaseExactDivValuesMicros[MeasurementControl.TimebaseIndex]);
timer0_millis += tCompensation;
TIMSK2 = _BV(TOIE2); // Enable overflow interrupts which replaces the Arduino millis() interrupt
/*
* Handle cyclicly print info or refresh buttons
*/
if (sDoInfoOutput) {
sDoInfoOutput = false;
if (DisplayControl.DisplayPage == DSO_PAGE_CHART) {
if (!DisplayControl.showHistory) {
// This enables slow display devices to skip frames
BlueDisplay1.clearDisplayOptional(COLOR_BACKGROUND_DSO);
}
drawGridLinesWithHorizLabelsAndTriggerLine();
if (DisplayControl.showInfoMode != INFO_MODE_NO_INFO) {
printInfo();
}
} else if (DisplayControl.DisplayPage == DSO_PAGE_SETTINGS) {
// refresh buttons
drawDSOSettingsPage();
} else if (DisplayControl.DisplayPage == DSO_PAGE_FREQUENCY) {
// refresh buttons
drawFrequencyGeneratorPage();
#if !defined(__AVR__)
} else if (DisplayControl.DisplayPage == DSO_PAGE_MORE_SETTINGS) {
// refresh buttons
drawDSOMoreSettingsPage();
#endif
}
}
// Compute Average first
MeasurementControl.ValueAverage = (MeasurementControl.IntegrateValueForAverage
+ (DataBufferControl.AcquisitionSize / 2)) / DataBufferControl.AcquisitionSize;
if (MeasurementControl.StopRequested) {
/*
* Handle stop
*/
MeasurementControl.StopRequested = false;
MeasurementControl.isRunning = false;
if (MeasurementControl.ADCReferenceShifted != (DEFAULT << REFS0)) {
// get new VCC Value
setVCCValue();
}
if (MeasurementControl.isSingleShotMode) {
MeasurementControl.isSingleShotMode = false;
// Clear single shot character
clearSingleshotMarker();
}
redrawDisplay();
} else {
/*
* Normal loop -> process data, draw new chart, and start next acquisition
*/
uint8_t tLastTriggerDisplayValue = DisplayControl.TriggerLevelDisplayValue;
computeAutoTrigger();
computeAutoRange();
computeAutoOffset();
// handle trigger line
DisplayControl.TriggerLevelDisplayValue = getDisplayFromRawInputValue(MeasurementControl.RawTriggerLevel);
if (tLastTriggerDisplayValue
!= DisplayControl.TriggerLevelDisplayValue&& DisplayControl.DisplayPage == DSO_PAGE_CHART) {
clearTriggerLine(tLastTriggerDisplayValue);
drawTriggerLine();
}
if (!DisplayControl.DrawWhileAcquire) {
/*
* Clear old chart and draw new data
*/
drawDataBuffer(&DataBufferControl.DataBuffer[0], COLOR_DATA_RUN, DisplayControl.EraseColor);
}
startAcquisition();
}
} else {
/*
* Here data buffer is NOT full and acquisition is still ongoing
*/
/*
* Handle slow modes (draw while acquire)
*/
if (DisplayControl.DrawWhileAcquire) {
drawRemainingDataBufferValues();
MeasurementControl.RawValueMin = MeasurementControl.ValueMinForISR;
MeasurementControl.RawValueMax = MeasurementControl.ValueMaxForISR;
if (checkRAWValuesForClippingAndChangeRange()) {
// range changed set new values for ISR to avoid another range change by the old values
MeasurementControl.ValueMinForISR = 42;
MeasurementControl.ValueMaxForISR = 42;
}
}
}
#if defined(DEBUG)
// DebugValue1 = MeasurementControl.ShiftValue;
// DebugValue2 = MeasurementControl.RawValueMin;
// DebugValue3 = MeasurementControl.RawValueMax;
// printDebugData();
#endif
/*
* Handle single shot, or trigger not yet found -> output actual values cyclicly
*/
if (MeasurementControl.isSingleShotMode && MeasurementControl.TriggerStatus != TRIGGER_STATUS_FOUND
&& sDoInfoOutput) {
sDoInfoOutput = false;
MeasurementControl.ValueAverage = MeasurementControl.ValueBeforeTrigger;
MeasurementControl.PeriodMicros = 0;
MeasurementControl.PeriodFirst = 0;
MeasurementControl.PeriodSecond = 0;
printInfo(false);
}
/*
* Handle milliseconds delay - not timer overflow proof
*/
if (MeasurementControl.TriggerStatus == TRIGGER_STATUS_FOUND_AND_WAIT_FOR_DELAY) {
if (MeasurementControl.TriggerDelayMillisEnd == 0) {
// initialize value since this can not be efficiently done in ISR
MeasurementControl.TriggerDelayMillisEnd = millis() + MeasurementControl.TriggerDelayMillisOrMicros;
} else if (millis() > MeasurementControl.TriggerDelayMillisEnd) {
/*
* Start acquisition
*/
MeasurementControl.TriggerStatus = TRIGGER_STATUS_FOUND;
if (MeasurementControl.AcquisitionFastMode) {
// NO Interrupt in FastMode
ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | MeasurementControl.TimebaseHWValue;
} else {
// enable ADC interrupt, start with free running mode,
ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE_FOR_TRIGGER_SEARCH | _BV(ADIE);
}
}
}
} else {
/*
* Analyze mode here
*/
if (sDoInfoOutput && DisplayControl.DisplayPage == DSO_PAGE_SETTINGS) {
sDoInfoOutput = false;
/*
* show VCC and Temp and stack
*/
printVCCAndTemperature();
printFreeStack();
}
}
/*
* Handle Sub-Pages
*/
if (DisplayControl.DisplayPage == DSO_PAGE_SETTINGS) {
if (sBackButtonPressed) {
sBackButtonPressed = false;
DisplayControl.DisplayPage = DSO_PAGE_CHART;
redrawDisplay();
}
} else if (DisplayControl.DisplayPage == DSO_PAGE_FREQUENCY) {
if (sBackButtonPressed) {
sBackButtonPressed = false;
stopFrequencyGeneratorPage();
DisplayControl.DisplayPage = DSO_PAGE_SETTINGS;
redrawDisplay();
} else {
//not required here, because is contains only checkAndHandleEvents()
// loopFrequencyGeneratorPage();
}
}
} // BlueDisplay1.mBlueDisplayConnectionEstablished
} // for(;;)
}
/* Main loop end */
/************************************************************************
* Measurement section
************************************************************************/
/*
* prepares all variables for new acquisition
* switches between fast an interrupt mode depending on TIMEBASE_FAST_MODES
* sets ADC status register including prescaler
*/
void startAcquisition(void) {
ADMUX = MeasurementControl.ADMUXChannel | MeasurementControl.ADCReferenceShifted; // it may be overwritten by getVoltage()
DataBufferControl.AcquisitionSize = DISPLAY_WIDTH;
DataBufferControl.DataBufferEndPointer = &DataBufferControl.DataBuffer[DISPLAY_WIDTH - 1];
if (MeasurementControl.StopRequested) {
DataBufferControl.AcquisitionSize = DATABUFFER_SIZE;
DataBufferControl.DataBufferEndPointer = &DataBufferControl.DataBuffer[DATABUFFER_SIZE - 1];
}
/*
* setup new interrupt cycle only if not to be stopped
*/
DataBufferControl.DataBufferNextInPointer = &DataBufferControl.DataBuffer[0];
DataBufferControl.DataBufferNextDrawPointer = &DataBufferControl.DataBuffer[0];
DataBufferControl.DataBufferNextDrawIndex = 0;
MeasurementControl.IntegrateValueForAverage = 0;
DataBufferControl.DataBufferFull = false;
/*
* Timebase
*/
uint8_t tTimebaseIndex = MeasurementControl.TimebaseIndex;
if (tTimebaseIndex < TIMEBASE_NUMBER_OF_FAST_MODES) {
MeasurementControl.AcquisitionFastMode = true;
} else {
MeasurementControl.AcquisitionFastMode = false;
}
/*
* get hardware prescale value
*/
if (tTimebaseIndex < TIMEBASE_NUMBER_OF_FAST_PRESCALE) {
MeasurementControl.TimebaseHWValue = ADCPrescaleValueforTimebase[tTimebaseIndex];
} else {
MeasurementControl.TimebaseHWValue = ADC_PRESCALE_MAX_VALUE;
}
MeasurementControl.TriggerStatus = TRIGGER_STATUS_START;
// fast mode checks the INT1 pin directly and need no interrupt
if (MeasurementControl.TriggerMode == TRIGGER_MODE_EXTERN && !MeasurementControl.AcquisitionFastMode) {
/*
* wait for external trigger with INT1 pin change interrupt - NO timeout
*/
if (MeasurementControl.TriggerSlopeRising) {
EICRA = _BV(ISC01) | _BV(ISC00);
} else {
EICRA = _BV(ISC01);
}
// clear interrupt bit
EIFR = _BV(INTF0);
// enable interrupt on next change
EIMSK = _BV(INT0);
return;
} else {
// start with waiting for triggering condition
MeasurementControl.TriggerSampleCountDividedBy256 = 0;
}
/*
* Start acquisition in free running mode for trigger detection
*/
ADCSRB = 0; // free running mode
if (MeasurementControl.AcquisitionFastMode) {
// NO Interrupt in FastMode
ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | MeasurementControl.TimebaseHWValue;
} else {
// enable ADC interrupt, start with fast free running mode for trigger search.
ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE_FOR_TRIGGER_SEARCH | _BV(ADIE);
}
}
/*
* ISR for external trigger input
* not used if MeasurementControl.AcquisitionFastMode == true
*/
ISR(INT0_vect) {
/*
* Disable interrupt on trigger pin
*/
EIMSK = 0;
if (MeasurementControl.TriggerDelayMode != TRIGGER_DELAY_NONE) {
/*
* Delay
*/
if (MeasurementControl.TriggerDelayMode == TRIGGER_DELAY_MICROS) {
// It is not possible to access using internal delay handling of ISR, so duplicate code here
// delayMicroseconds(MeasurementControl.TriggerDelayMillisOrMicros - TRIGGER_DELAY_MICROS_ISR_ADJUST_COUNT); substituted by code below, to enable full 16 bit range
uint16_t tDelayMicros = MeasurementControl.TriggerDelayMillisOrMicros - TRIGGER_DELAY_MICROS_ISR_ADJUST_COUNT;
asm volatile (
"1: sbiw %0,1" "\n\t" // 2 cycles
"1: adiw %0,1" "\n\t"// 2 cycles
"1: sbiw %0,1" "\n\t"// 2 cycles
"1: adiw %0,1" "\n\t"// 2 cycles
"1: sbiw %0,1" "\n\t"// 2 cycles
"1: adiw %0,1" "\n\t"// 2 cycles
"1: sbiw %0,1" "\n\t"// 2 cycles
"brne .-16" : : "w" (tDelayMicros)// 16 cycles
);
} else {
MeasurementControl.TriggerStatus = TRIGGER_STATUS_FOUND_AND_WAIT_FOR_DELAY;
MeasurementControl.TriggerDelayMillisEnd = millis() + MeasurementControl.TriggerDelayMillisOrMicros;
return;
}
}
/*
* Start acquisition in free running mode as for trigger detection
*/
MeasurementControl.TriggerStatus = TRIGGER_STATUS_FOUND;
// enable ADC interrupt
ADCSRA = _BV(ADEN) | _BV(ADSC) | _BV(ADATE) | _BV(ADIF) | ADC_PRESCALE_FOR_TRIGGER_SEARCH | _BV(ADIE);
}
/*
* Fast ADC read routine for timebase 101-201us and ultra fast for 10-50us
* Value, which mets trigger condition, is taken as first data.
*
* Only TRIGGER_MODE_MANUAL_TIMEOUT and TRIGGER_MODE_EXTERN is supported yet.
* But TRIGGER_MODE_EXTERN has timeout here!
*/
//#define DEBUG_ADC_TIMING
void acquireDataFast(void) {
/**********************************
* wait for triggering condition
**********************************/
Myword tUValue;
uint8_t tTriggerStatus = TRIGGER_STATUS_START;
uint16_t i;
uint16_t tValueOffset = MeasurementControl.OffsetValue;
if (MeasurementControl.TriggerMode == TRIGGER_MODE_EXTERN) {
if (MeasurementControl.TriggerSlopeRising) {
EICRA = _BV(ISC01) | _BV(ISC00);
} else {
EICRA = _BV(ISC01);
}
EIFR = _BV(INTF0); // Do not enable interrupt since we do polling below
uint16_t tTimeoutCounter = 0;
/*
* Wait 65536 loops for external trigger to happen (for interrupt bit to be set)
*/
do {
tTimeoutCounter--;
} while (bit_is_clear(EIFR, INTF0) && tTimeoutCounter != 0);
// get first value after trigger
tUValue.byte.LowByte = ADCL;
tUValue.byte.HighByte = ADCH;
} else {
// start the first conversion and clear bit to recognize next conversion has finished
ADCSRA |= _BV(ADIF) | _BV(ADSC);
TIMSK2 = 0; // disable timer2 (millis()) interrupt to avoid jitter and signal dropouts
/*
* Wait for trigger for max. 10 screens e.g. < 20 ms
* if trigger condition not met it will run forever in single shot mode
*/
for (i = TRIGGER_WAIT_NUMBER_OF_SAMPLES; i != 0 || MeasurementControl.isSingleShotMode; --i) {
#if defined(DEBUG_ADC_TIMING)
digitalWriteFast(DEBUG_PIN, HIGH); // debug pulse is 1 us for (ultra fast) PRESSCALER4 and 4 us for (fast) PRESSCALER8
#endif
// wait for free running conversion to finish
loop_until_bit_is_set(ADCSRA, ADIF);
#if defined(DEBUG_ADC_TIMING)
digitalWriteFast(DEBUG_PIN, LOW);
#endif
// Get value
tUValue.byte.LowByte = ADCL;
tUValue.byte.HighByte = ADCH;
ADCSRA |= _BV(ADIF); // clear bit to recognize next conversion has finished
/*
* detect trigger slope
*/
if (MeasurementControl.TriggerSlopeRising) {
if (tTriggerStatus == TRIGGER_STATUS_START) {
// rising slope - wait for value below hysteresis level
if (tUValue.Word < MeasurementControl.RawTriggerLevelHysteresis) {
tTriggerStatus = TRIGGER_STATUS_AFTER_HYSTERESIS;
}
} else {
// rising slope - wait for value to rise above trigger level
if (tUValue.Word > MeasurementControl.RawTriggerLevel) {
break;
}
}
} else {
if (tTriggerStatus == TRIGGER_STATUS_START) {
// falling slope - wait for value above hysteresis level
if (tUValue.Word > MeasurementControl.RawTriggerLevelHysteresis) {
tTriggerStatus = TRIGGER_STATUS_AFTER_HYSTERESIS;
}
} else {
// falling slope - wait for value to go below trigger level
if (tUValue.Word < MeasurementControl.RawTriggerLevel) {
break;
}
}
}
}
}
/*
* Only microseconds delay makes sense here
*/
if (MeasurementControl.TriggerDelayMode == TRIGGER_DELAY_MICROS) {
delayMicroseconds(MeasurementControl.TriggerDelayMillisOrMicros - TRIGGER_DELAY_MICROS_POLLING_ADJUST_COUNT);
ADCSRA |= _BV(ADIF);
loop_until_bit_is_set(ADCSRA, ADIF);
// get first value after delay
tUValue.byte.LowByte = ADCL;
tUValue.byte.HighByte = ADCH;
ADCSRA |= _BV(ADIF); // clear bit to recognize next conversion has finished
}
MeasurementControl.TriggerStatus = TRIGGER_STATUS_FOUND; // for single shot mode
/********************************
* read a buffer of data here
********************************/
// setup for min, max, average
uint16_t tValueMax = tUValue.Word;
uint16_t tValueMin = tUValue.Word;
uint8_t tIndex = MeasurementControl.TimebaseIndex;
uint8_t *DataPointerFast = &DataBufferControl.DataBuffer[0];
uint16_t tLoopCount = DataBufferControl.AcquisitionSize;
if (tIndex <= TIMEBASE_INDEX_ULTRAFAST_MODES) {
/*
* 10-50us range. Data is stored directly as 16 bit value and not processed.
* => we have only half number of samples (tLoopCount)
*/
if (DATABUFFER_SIZE < DISPLAY_WIDTH * 2) {
// In this configuration (for testing etc.) we have not enough space
// for DISPLAY_WIDTH 16 bit-values which need (DISPLAY_WIDTH * 2) bytes.
// The compiler will remove the code if the configuration does not hold ;-)
tLoopCount = DATABUFFER_SIZE / 2;
} else if (tLoopCount > DISPLAY_WIDTH) {
// Last measurement before stop, fill the whole buffer with 16 bit values
// During running measurements we know here (see if above) that we can get DISPLAY_WIDTH 16 bit values
tLoopCount = tLoopCount / 2;
}
*DataPointerFast++ = tUValue.byte.LowByte;
*DataPointerFast++ = tUValue.byte.HighByte;
/*
* do very fast reading without processing
*/
for (i = tLoopCount; i > 1; --i) {
uint8_t tLow, tHigh;
#if defined(DEBUG_ADC_TIMING)
digitalWriteFast(DEBUG_PIN, HIGH); // debug pulse is 1.6 us
#endif