-
Notifications
You must be signed in to change notification settings - Fork 1
/
spinning_globe.ino
2643 lines (2067 loc) · 167 KB
/
spinning_globe.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
/*
Name: spinning_globe.ino
Created: 10/08/2019 - 17/10/2022
Author: Herwig Taveirne
Version: 1.0.2
Program written and tested for Arduino Nano
Timer 1 reading in class MyTime, in procedure idleLoop and in ISR assumes clock speed is 16Mhz
This program 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/>.
*/
#include <LiquidCrystal.h> // lcd
#include <util/atomic.h> // atomic operations
#include <avr/wdt.h> // watchdog timer
#include <limits.h> // specific constants
#include <Arduino.h>
#include <stdlib.h>
#define boardVersion 101 // board version: 100 = v1, 101 = v1 rev A and B
#define highAnalogGain 1 // 0: analog gain is 10, 1: analog gain is 15 (defined by resistors R9 to R12)
#define onboardLedDimming 0 // 1: enable onboard led dimming
#define test_showEventStats 0 // only for testing (event message mechanism)
// *** enumerations ***
enum userCmds :int {
uNoCmd = -1,
uPrevious = 0, uNext, uEdit, uCancel, uShowAll, uLive, uTimeStamp, uHelp, // cmds without parameters: 0 - 99
uStepResponseTest = 100, // cmds with 1 parameter: 100-199
uLedstripSettings = 200, // cmds with 2 parameters: 200-299
uUnknownCmd = 999 // unknown command receives code 999
};
enum rotStatus :uint8_t { rotNoPosSync, rotFreeRunning, rotMeasuring, rotUnlocked, rotLocked }; // rotNoPosSync: also if rotation OFF or not floating
enum errStatus :uint8_t { errNoError = 0, errDroppedGlobe, errStickyGlobe, errMagnetLoad, errTemp };
// eBlink, eSpareNoDataEvent1: cue only (no data) events. additional time cues can be added
enum events :uint8_t { eNoEvent = 0, eGreenwich, eStatusChange, eFastRateData, eLedstripData, eStepResponseData, eSecond, eBlink, eSpareNoDataEvent1 };
enum colorCycles :uint8_t { cLedstripOff = 0, cCstBrightWhite, cCstBrightMagenta, cCstBrightBlue, cWhiteBlue, cRedGreenBlue }; // led strip color cycle
enum colorTiming :uint8_t { cLedstripVeryFast = 0, cLedstripFast, cLedstripSlow, cLedStripVerySlow }; // led strip color cycle
// *** I/O ***
// port A
constexpr uint8_t A0_liftHallPin{ A0 }; // port A analog input pin A0: vertical position sensor
constexpr uint8_t A1_temperaturePin{ A1 }; // port A analog input pin A1: temperature sensor
// port B
constexpr uint8_t B1_OC1Apin{ 9 }; // port B bit 1 (Nano pin D9): output pin for 16-bit timer 1 channel A (drives magnet)
#if (boardVersion == 100)
constexpr uint8_t B2_LCDenablePin{ 13 }; // port B bit 5 (Nano pin D13): LCD enable
constexpr uint8_t portB_IOchannelSelectBitMask{ B00011100 }; // port B bits 432: I/O channel select (74HCT138 decoder)
constexpr uint8_t portB_coilFlipFlopSelect{ 0 << 2 }; // decoder select lines: bits 432 = 000, 001, 010, 011
constexpr uint8_t portB_auxFlipFlopSelect{ 1 << 2 };
constexpr uint8_t portB_switchesBufferSelect{ 2 << 2 };
constexpr uint8_t portB_ledstripSelect{ 3 << 2 };
#else
constexpr uint8_t B2_LCDenablePin{ 10 }; // port B bit 2 (Nano pin D10): LCD enable
constexpr uint8_t portB_IOchannelSelectBitMask{ B00110001 }; // port B bits 540: I/O channel select (74HCT138 decoder)
constexpr uint8_t portB_coilFlipFlopSelect{ B00000000 }; // decoder select lines: bits 540 = 000, 001, 010, 011
constexpr uint8_t portB_auxFlipFlopSelect{ B00000001 };
constexpr uint8_t portB_switchesBufferSelect{ B00010000 };
constexpr uint8_t portB_ledstripSelect{ B00010001 };
#endif
// port C
constexpr uint8_t A2_IONotEnablePin{ A2 }; // port C bit 2 (Nano pin A2): I/O channel not enable (74HCT138 decoder)
constexpr uint8_t portC_IOdisableBit{ B00000100 }; // port C bit 2
// port D
constexpr uint8_t D3_LCDregSelPin{ 3 }; // port D bit 3 (Nano pin D3): LCD register select
constexpr uint8_t portD_redStatusLedbit{ B00001000 }; // port D bit 3: red status led bit mask
constexpr uint8_t portD_greenStatusLedBit{ B00010000 }; // port D bit 4: green status led bit mask
#if (boardVersion == 100)
constexpr uint8_t portD_interruptInProgressBit{ B00100000 }; // port D bit 5: interrupt in progress bit mask
constexpr uint8_t portD_blueStatusLedBit{ B01000000 }; // port D bit 6: blue status led bit mask
#else
constexpr uint8_t portD_blueStatusLedBit{ B00100000 }; // port D bit 5: blue status led bit mask
constexpr uint8_t portD_interruptInProgressBit{ B01000000 }; // port D bit 6: interrupt in progress bit mask
#endif
constexpr uint8_t portD_enableMotorBit{ B10000000 }; // port D bit 7: enable motor bit mask
constexpr uint8_t pinD_firstKeyBit{ B00000100 }; // port D bit 2: first key bit
constexpr uint8_t pinD_switchStateBits{ B01111100 }; // port D bits 65432: all switches AND keys (producing switch states)
constexpr uint8_t pinD_keyBits{ B00111100 }; // port D bits 5432: keys ONLY (producing keycodes when pressed)
constexpr uint8_t pinD_greenwichBit{ B10000000 }; // port D bit 7: Greenwich sync bit mask
uint8_t portDbuffer{ 0 }, dataInBuffer{ 0 };
// port depending on board version
#if (boardVersion == 100)
constexpr uint8_t A3_ledstripDataPin{ A3 }; // port C bit 3 (Nano pin A3): ledstrip data
constexpr uint8_t portC_ledstripDataBit{ B00001000 }; // port C bit 3
#else
constexpr uint8_t ledstripDataBits{ B11000000 }; // port D bits 7 and 6
#endif
// *** flash memory constants ***
const char str_build[] PROGMEM = "***spinning globe v1.0 build June 27, 2021 ***\n";
const char str_empty16[] PROGMEM = " ";
const char str_rotationOff[] PROGMEM = "rotation off";
const char str_freeRunning[] PROGMEM = "free running";
const char str_noPosSync[] PROGMEM = "wait for pos sync";
const char str_measuring[] PROGMEM = "measuring";
const char str_notLocked[] PROGMEM = "not locked";
const char str_locked[] PROGMEM = "locked";
const char str_notFloating[] PROGMEM = "not floating";
const char str_ErrDroppedGlobe[] PROGMEM = "E! dropped globe";
const char str_ErrStickyGlobe[] PROGMEM = "E! sticky globe";
const char str_ErrOverload[] PROGMEM = "E! overload";
const char str_ErrTemp[] PROGMEM = "E! temp too high";
const char str_rotTimeSet[] PROGMEM = "rot time>";
const char str_rotTimeAct[] PROGMEM = "rot t act";
const char str_syncError[] PROGMEM = "sync err ";
const char str_tLocked[] PROGMEM = "t locked ";
const char str_tFloat[] PROGMEM = "t float ";
const char str_tempAct[] PROGMEM = "temp ";
const char str_avgDutyC[] PROGMEM = "avg duty c";
const char str_vertPos[] PROGMEM = "vert pos>";
const char str_errSigVar[] PROGMEM = "vp avg err";
const char str_intTerm[] PROGMEM = "integ term";
const char str_avgPhase[] PROGMEM = "avg phase";
const char str_isrTime[] PROGMEM = "adc isr t";
const char str_procLoad[] PROGMEM = "proc load";
const char str_editValue[] PROGMEM = " << +, - to change value, E to end edit, C to cancel";
const char str_help1[] PROGMEM = "Type + or - to change parameter shown, E to edit value, S to show or stop live values, ";
const char str_help2[] PROGMEM = "A to show all values, T for time stamp, LC0..5 to change ledstrip cycle (0 = off), ";
const char str_help3[] PROGMEM = "LT1..4 to change ledstrip cycle time (1 = fastest), R0..1 for (step) response, ? for help";
const char str_cmdError[] PROGMEM = "== Not a valid command or parameter";
const char str_showLive[] PROGMEM = "== Show Live";
const char str_stopLive[] PROGMEM = "== Stop Live";
const char str_colorCycle[] PROGMEM = "== ledstrip color cycle ";
const char str_timeStamp[] PROGMEM = "== Time stamp ";
const char str_stepResponse[] PROGMEM = "== (Step) response (ms;hall;ctr)";
const char str_stepResponseEnd[] PROGMEM = "== (Step) response end";
const char str_eventsMissed[] PROGMEM = "event(s) missed !";
const char str_programMode[] PROGMEM = "PROGRAM MODE";
const char str_fmtTime[] PROGMEM = "%ldd %02ld:%02ld:%02ld %03ld";
const char str_fmt3unsignedInteger[] PROGMEM = "%u;%u;%u";
const char str_fmtDayHour[] PROGMEM = "%3ldd%2ldh";
const char str_fmtHourMinute[] PROGMEM = "%3ldh%2ldm";
#if test_showEventStats
const char str_eventMaxStats[] PROGMEM = "event max stats: events pending %d, mem size %d";
#endif
const char* const paramLabels[] PROGMEM = { str_rotTimeSet, str_rotTimeAct, str_syncError, str_tLocked, str_tFloat, str_tempAct, str_avgDutyC,
str_vertPos, str_errSigVar, str_intTerm, str_avgPhase, str_isrTime, str_procLoad };
// *** user selectable parameter values ***
constexpr int paramNo_rotTimes{ 0 }, paramNo_hallmVoltRefs{ 7 }; // order in sequence of parameters
/*v1.0.1 high speed rotation times adapted or created new*/
long rotationTimes[] = { 0, 12000, 9000, 7500, 6000, 4500, 3000, 1500, 900 }; // must be divisible by 12 (steps), 0 means OFF
#if highAnalogGain // TWO limits: voltage before opamp >= 100 mV, voltage after opamp <= 2700 mV (prevent output saturation)
long hallMilliVolts[] = { 1500, 1800, 2100, 2400, 2700 }; // ADC setpoint expressed in mV (hall output after 15 x amplification by opamp, converted to mVolt)
#else
long hallMilliVolts[] = { 1000, 1200, 1400, 1600, 1800 }; // ADC setpoint expressed in mV (hall output after 10 x amplification by opamp, converted to mVolt)
#endif
constexpr int paramValueCounts[] = { sizeof(rotationTimes) / sizeof(rotationTimes[0]), 0, 0, 0, 0, 0, 0, sizeof(hallMilliVolts) / sizeof(hallMilliVolts[0]), 0, 0, 0, 0, 0 }; // 0 if no value list for parameter
constexpr long* paramValueSets[] = { rotationTimes, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, hallMilliVolts, nullptr, nullptr, nullptr, nullptr, nullptr }; // nullptr if no value list for parameter
int ParamsSelectedValueNos[] = { 2, -1, -1, -1, -1, -1, -1, 0, -1, -1, -1, -1, -1 }; // -1 if display only (no changeable parameter); otherwise default if eeprom not used to store values
constexpr int paramCount = sizeof(paramLabels) / sizeof(paramLabels[0]);
bool paramChangeMode{ false };
int paramNo{ 0 }, paramValueNo{ 0 };
// *** strings ***
constexpr char degreesSymbol[] = { ' ', 'C', 0 }; // character '°' not in lcd character set
constexpr char microSecSymbol[] = { 'u', 's', 0 }; // charachter 'µ' not in lcd character set: use 'u'
char s150[150], s30[30]; // general purpose long and short character strings
#if (F_CPU != 16000000L)
#error code expects CPU frequency 16 MHz
#endif
// *** time and other measurements ***
constexpr uint8_t keyBufferLength{ 3 };
constexpr long timer1PWMfreq{ 1000L }; // 1 KHz
constexpr long timer1PreScaler{ 8 }; // as set in setup();
constexpr long timer1ClockFreq{ F_CPU / timer1PreScaler }; // 2 MHz
constexpr long timer1Top{ timer1ClockFreq / timer1PWMfreq / 2 }; // timer counts up and down : 2000 steps, TOP =1000
constexpr long fasteDataRateSamplingPeriods{ 1 << 7 }; // in sampling periods (milli seconds, power of 2)
constexpr float samplingPeriod{ 1. / (float)timer1PWMfreq }; // 1 milli second sampling period, in seconds
constexpr long oneSecondCount{ 1000L }, blinkTimeCount{ 800 }; // milli seconds
constexpr long spareTimeCount{ 500 }; // milli seconds
bool showLiveValues{ true };
bool forceWriteLedstripSpecs{ false };
uint8_t currentSwitchStates{};
uint8_t ISRevent{ eNoEvent }; // current ISR event retrieved for processing in main loop
int userCommand{ uNoCmd }, commandParam1, commandParam2;
float idleLoopMicrosSmooth{ 0 }, magnetOnCyclesSmooth{ 0 }, ISRdurationSmooth{ 0 }; // values smoothed by first order filter
float errSignalMagnitudeSmooth{ 0 };
// interface between ISR and main
volatile bool resetHWwatchDog{ false }; // periodic reset of hardware watchdog (disabling magnets)
volatile bool forceStatusEvent{ false };
volatile bool ISRoccurred{ false }; // for idle time counting
volatile bool highLoad{ false };
volatile bool ADCisTemp{ false }; // comm. between timer 1 overflow ISR and ADC conversion complete ISR
volatile bool useButtons{ false }; // signals SW3 to SW0: interpret as buttons ?
volatile bool switchesSetRotationTime{ false }, switchesSetLedstrip{ false }; // signals SW3 to SW0: interpret as switches for specific settings ?
volatile bool switchesSetHallmVoltRef{ false };
volatile int8_t keyBuffer[keyBufferLength]{ 0 }; // can hold negative key codes
volatile uint8_t rotationStatus{ rotNoPosSync }, errorCondition{ errNoError };
volatile uint8_t switchStates{ 0 }, prevSwitchStates{ 0 }; // current and previous state of the board switches / buttons
volatile uint8_t keysAvailable{ 0 }; // No of keys available in board key buffer
volatile unsigned int idleLoopNanos500{ 0 }; // at least reset every mS = 1000 micro seconds: will not overflow
volatile unsigned int millis16bits{ 0 };
volatile long milliSecond{ 0 }, second{ 0 };
volatile long tempSmooth{ 0 }; // temperature smoothed by first order filter; long for speed, used by ISR
// *** PID controller ***
constexpr long ADCsteps{ 1024L }; // globe vertical position sensor: resolution (10 bit ADC)
constexpr uint16_t printPIDperiod{ 20000 }, PIDstepTime{ 1000 }; // for step response measurement
// interface between ISR and main
volatile long targetHallRef_ADCsteps{}; // globe vertical position ref (controller reference input) to reach after changing ref, in ADC steps
volatile long hallRef_ADCsteps{}; // current globe vertical position ref (controller reference input) in ADC steps
volatile uint32_t firstFullAccIntTerm{}; // for printing step response (allows PC simulations)
volatile uint16_t printPIDtimeCounter{ printPIDperiod + 1 }; // for step response measurement; printPIDperiod + 1 : 'printing stopped'
volatile bool applyStep{ false }; // apply step when measuring (step) response
// *** globe rotation controller ***
constexpr float slowDownRatio{ 0.8 }; // during slow down, set magnetic field rotation time to current globe rotation times this factor
constexpr float speedUpRatio{ 1.2 }; // during speed up, set magnetic field rotation time to current globe rotation times this factor
constexpr float autoLock_lowRelGlobeRotTime{ 0.9 }; // relative globe rotation time limits to flag a rotation as 'in autolock range')
constexpr float autoLock_highRelGlobeRotTime{ 1.1 };
constexpr float slowDownPhaseAdjust_slowSpeeds{ 0.25 };/*v1.0.2 value changed*/ // during slow down, phase adjustment as a fraction of one rotation
constexpr float speedUpPhaseAdjust_slowSpeeds{ 0.25 };/*v1.0.2 value changed*/ // during speed up, phase adjustment as a fraction of one rotation
constexpr float slowDownPhaseAdjust_highSpeeds{ 0.30 };/*v1.0.2 value added*/ // during slow down, phase adjustment as a fraction of one rotation
constexpr float speedUpPhaseAdjust_highSpeeds{ 0.40 };/*v1.0.2 value added*/ // during speed up, phase adjustment as a fraction of one rotation
constexpr long defaultStepTime{ 750L }; // one turn = stepTime * # steps, milli seconds;
constexpr long steps{ 12L };
// interface between ISR and main
volatile int rotationTimerSamplePeriod{};
volatile long stepTime{ defaultStepTime }, targetGlobeRotationTime{ stepTime * steps };
volatile long slowDown_timeLimit{}; // if rotation time lower than limit, then slow down
volatile long speedUp_timeLimit{}; // if rotation time higher than limit, then speed up
volatile long autoLock_lowGlobeRotTime{}; // if rotation time lower than limit, then set phase
volatile long autoLock_highGlobeRotTime{}; // if rotation time lower than limit, then set phase
volatile long stepTimeNewRotation{ stepTime };
#if onboardLedDimming
// *** on board led dimming - check out documentation (excel) for values ***
constexpr uint8_t brightnessItemCount{ 2 };
constexpr uint8_t ledMinBrightnessLevel[3] = { 1, 1, 1 }; // can be set for each led up down cycle type; 0 (led off) or >= 1
constexpr uint8_t ledMaxBrightnessLevel[3] = { 23, 45, 64 }; // can be set for each led up down cycle type; <= (low + high) brightness levels
constexpr int ledMaxAllowedStepTime{ 100 }; // ms
// interface between ISR and main
volatile uint8_t ledUpDownCycleType{ 0 }; // led up down cycle type determines brightness change approach, and depends on rotation time
volatile int ledBrightnessStepsUpDown{ 0 }; // min level -> max level -> min level + 1 = 2 * (max level - min level)
volatile long scaledGreenLedDelay{ 0 }; // optional delay between blue and green led, scaled by factor 'ledBrightnessStepsUpDown' (because time counter increments by this factor)
#endif // onboardLedDimming
// *** led strip dimming ***
constexpr uint8_t LSbrightnessItemCount{ 3 }; // max no of brightness values available for color led dimming
constexpr uint8_t LSminBrightnessLevel{ 0x00 }; // min: 0 (LS off)
constexpr uint8_t LSmaxBrightnessLevel{ 0x80 }; // 0x80^2 / 256 = 64 (gamma correction); max: 0xFF
// interface between ISR and main
volatile bool LSlongTimeUnit{ false }; // 128 ms instead of 1 ms
volatile uint8_t LScolorCycle, LScolorTiming; // color cycle
volatile uint8_t LSbrightness[LSbrightnessItemCount]; // brightness levels (not yet assigned to specific colors or leds)
volatile uint8_t LStransitionStops; // MUST be > 0 => set LSbrightnessFreezeTime = 0 if no transition stops desired
volatile long LSbrightnessTransitionTime; // total brightness transition time (in a complete color cycle), in milliseconds - excludes 'frozen brightness' times
volatile long LSbrightnessFreezeTime; // total 'frozen brightness' time (summed up constant brightness time between all transitions), in milli seconds (minimum = 0)
volatile long LSbrightnessCycleTime; // COMPLETE color cycle in milli seconds
volatile long LSminBrightnessTime; // if 3 colors: 1/3 of cycle time (only two primary colors at the same time - no white)
volatile long LSmaxBrightnessTime; // min 0: primary stronger than CMY), max 1/(no of colors) of cycle time (CMY stronger than primary)
volatile long LSbrightnessDelay; // delay between brightness cycles (here: between to or three brightness values)
// number of brightness steps, including steps of min / max brightness (but excluding 'frozen brightness' time)
volatile long LSbrightnessUpDownSteps;
volatile long LSbrightnessMinLvlSteps;
volatile long LSbrightnessMaxLvlSteps;
volatile long LSbrightnessTransitionSteps;
volatile long LSscaledDelay; // delay between brightness cycles, scaled by total no of brightness steps
// initial values per brightness change counter: step size and delay between brightness cycle, scaled by total no of brightness steps (step size x no of brightness steps = total transition time)
volatile long LSbrightnessStepTimer[LSbrightnessItemCount];
volatile long LSbrightnessFreezeTimer; // optional: skip first 'frozen brightness' step because a brightness is still missing (is starting from 'all brightness values OFF')
volatile long LSminBrightnessStepNo[LSbrightnessItemCount], LSmaxBrightnessStepNo[LSbrightnessItemCount]; // for counting min / max level steps
volatile uint8_t LSupdate; // update flag for leds per ledgroup
volatile uint8_t LSup; // initial dimming direction up for all brightness items (true if initially counting up OR in a max. brightness period)
volatile uint8_t LSminReached, LSmaxReached;
// *** for testing purposes ***
constexpr bool enableSafety{ true }; // enable safety checks lifting magnet ? (Note: temperature check lifting magnet is always ON)
constexpr bool controlRotation{ true }; // only measure rotation speed but do not control
// *** structures: communication between ISR and main ***
struct GreenwichData { // initialize members, awaiting first event
long eventMilliSecond{ 0 }, eventSecond{ 0 };
long globeRotationTime{ 0 };
long rotationOutOfSyncTime{ 0 };
long summedMagneticFieldPhase{ 0 };
long summedMagneticFieldRotations{ 0 };
long lockedRotations{ 0 };
};
struct StatusData { // initialize members, awaiting first event
long eventMilliSecond{ 0 }, eventSecond{ 0 };
uint8_t rotationStatus{ rotNoPosSync }, errorCondition{ errNoError };
bool isGreenwich{ false };
bool isFloating{ false };
};
struct SecondData { // initialize members, awaiting first event
long eventSecond{ 0 };
long liftingSecond{ 0 }, lockedSecond{ 0 };
long realTTTintTerm{ 0 };
};
struct FastRateData {
long sumIdleLoopMicros;
long sumISRdurations;
long sumMagnetOnCycles;
long sumADCtemp;
long sumErrSignalMagnitude;
};
struct LedstripData {
uint8_t LSupdate, LSmaxReached, LSminReached;
uint8_t LSbrightness[LSbrightnessItemCount];
};
struct StepResponseData {
uint16_t count, hallReading_ADCsteps, TTTcontrOut;
};
struct EventStats {
uint8_t* activeMsgPtr{ nullptr };
uint8_t activeEventType{ eNoEvent };
uint8_t eventsPending{ 0 }, largestEventsPending{ 0 }; // No of ISR events currently logged for processing in main loop
unsigned int eventBufferBytesUsed{ 0 }, largestEventBufferBytesUsed{ 0 }; // No of ISR events logged for processing in main loop: all-time max
long eventsMissed{ 0 }; // keeps track of events missed (not used at this stage)
};
// *** class: millis and micros function based on timer1 and own time counting logic (millis and micros < one second, count seconds) ***
class MyTime {
private:
long m_milliSecond, microSecond;
unsigned int volatile m_nanoSecond500, m_nextNanoSecond500;
public:
long millis(long* secondsPtr = nullptr) { // return millis in current second & seconds as well (run time in micros = seconds * 1E6 + micros)
uint8_t oldSREG = SREG;
cli(); // retrieve seconds and mS while interrupts disabled
if (secondsPtr != nullptr) { *secondsPtr = second; } // total
SREG = oldSREG;
return milliSecond; // 0 to 999
}
// execution time is very close to 20 microSeconds (16MHz clock)
long micros(long* secondsPtr = nullptr) { // return micros in current second & seconds as well (run time in micros = seconds * 1E6 + micros)
uint8_t oldSREG = SREG;
cli(); // retrieve seconds and mS, read timer while interrupts disabled
if (secondsPtr != nullptr) { *secondsPtr = second; } // total running
m_milliSecond = milliSecond; // 0 to 999
bool oldTOV1 = (TIFR1 & _BV(TOV1)); // timer 1 interrupt pending ?
m_nanoSecond500 = TCNT1; // one read takes 8 clock cycles (2 LDS and 2 STS instructions -> 4 * 2 = 8 clock cycles = 500 nS with 16 MHz clock)
m_nextNanoSecond500 = TCNT1; // take 2 counter readings (500 nS steps) to determine slope (difference between 2 readings is exactly -1 or +1)
if (m_nanoSecond500 > m_nextNanoSecond500) { m_nanoSecond500 = 2 * timer1Top - m_nextNanoSecond500; } // counting down (500 nS per cycle)
if (TIFR1 & _BV(TOV1)) { // timer 1 interrupt pending: milliSecond is not yet updated
m_milliSecond++; // milliSecond will be updated when ISR runs
if (!oldTOV1) { m_nanoSecond500 = m_nextNanoSecond500; } // two microsecond readings around overflow (zero) point: prevent "2 * timer1Top - m_nanoSecond500" calculation)
}
SREG = oldSREG;
// calculate micro seconds in current second: 0 to 999999 (1 period = 500 nS)
microSecond = ((long)(m_nanoSecond500 >> 1)) + (m_milliSecond << 10) - (m_milliSecond << 4) - (m_milliSecond << 3);
if (microSecond >= 1000000L) {
microSecond = microSecond - 1000000L;
if (secondsPtr != nullptr) { (*secondsPtr)++; }
}
return microSecond;
}
};
// *** class: MyEvents ***
class MyEvents {
private:
const uint8_t eventBufferSize{ 255 }; // max 255
uint8_t* eventBuffer{ nullptr }; // dynamic memory to store event messages until they are processed
uint8_t* oldestMessageStartPtr{ nullptr }, * newestMessageStartPtr{ nullptr };
EventStats eventStats;
public:
// interface between ISR and main
MyEvents(); // constructor
bool addChunk(uint8_t eventType, uint8_t newChunkSize, uint8_t** messagePtrPtr);
bool removeOldestChunk(bool remove);
bool isEventsWaiting();
void snapshot(EventStats* eventSnapshotPtr);
};
MyEvents::MyEvents() { // constructor
eventBuffer = (uint8_t*)malloc(eventBufferSize);
}
// *** reserve space in the event message buffer for a new event message ***
bool MyEvents::addChunk(uint8_t eventType, uint8_t newChunkSize, uint8_t** messagePtrPtr) { // prevent interference with another call to addChunk() - which may be called from ISR;
bool OK{ false };
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
uint8_t* newestEndPtr{ nullptr };
uint8_t** holdLastStartPtrPtr;
newChunkSize += 4; // add 4 bytes for event type, length and pointer to next event message (ok if event is cue only and carries no data)
bool isEmpty = (oldestMessageStartPtr == nullptr); // currently any event message logged ?
if (!isEmpty) { newestEndPtr = newestMessageStartPtr + *(newestMessageStartPtr + 1) - 1; } // pointer to last byte of current event message
bool wrappedAround = (!isEmpty) && (oldestMessageStartPtr > newestEndPtr);
uint8_t freeChunkSize = isEmpty ? eventBufferSize : (wrappedAround ? (oldestMessageStartPtr - newestEndPtr) - 1 : eventBuffer + eventBufferSize - newestEndPtr - 1);
OK = (freeChunkSize >= newChunkSize);
bool wrap{ false };
if ((!OK) && (!wrappedAround) && (!isEmpty)) { // append at end not possible: check if wraparound possible
uint8_t freeChunkSize = oldestMessageStartPtr - eventBuffer;
OK = (freeChunkSize >= newChunkSize);
wrap = OK;
}
if (OK) { // room available to store next message
if (isEmpty) { oldestMessageStartPtr = eventBuffer; newestMessageStartPtr = eventBuffer; }
else {
holdLastStartPtrPtr = (uint8_t**)(newestMessageStartPtr + 2);
newestMessageStartPtr = wrap ? eventBuffer : newestMessageStartPtr + *(newestMessageStartPtr + 1);
*holdLastStartPtrPtr = newestMessageStartPtr; // pointer from (now) previously created event to newly created event
}
*newestMessageStartPtr = eventType; // newly created event: set event type
*(newestMessageStartPtr + 1) = newChunkSize; // newly created event: set message length (including 4-byte header)
holdLastStartPtrPtr = (uint8_t**)(newestMessageStartPtr + 2);
*holdLastStartPtrPtr = nullptr; // pointer to next event: set to nullptr (there is no next event)
*messagePtrPtr = newestMessageStartPtr + 4; // oldest event (next event to process): pointer to optional event message (NOT to the 4-byte header)
eventStats.activeMsgPtr = oldestMessageStartPtr + 4;
eventStats.activeEventType = *oldestMessageStartPtr; // oldest event: event type
eventStats.eventsPending++;
// update statistics
eventStats.largestEventsPending = max(eventStats.largestEventsPending, eventStats.eventsPending);
eventStats.eventBufferBytesUsed += newChunkSize;
eventStats.largestEventBufferBytesUsed = max(eventStats.largestEventBufferBytesUsed, eventStats.eventBufferBytesUsed);
#if test_showEventStats
Serial.println(); Serial.print("+ ;"); Serial.println((int)newestMessageStartPtr);
#endif
}
else {
eventStats.eventsMissed++;
#if test_showEventStats
Serial.println(); Serial.println("missed");
#endif
}
}
return OK;
}
// *** release space occupied by the oldest message in the event message buffer ***
bool MyEvents::removeOldestChunk(bool remove) {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { // prevent interference with addChunk() - which may be called from ISR;
if ((oldestMessageStartPtr == nullptr) || (!remove)) { return false; } // nothing to remove: is empty
eventStats.eventsPending--;
eventStats.eventBufferBytesUsed -= *(oldestMessageStartPtr + 1); // before pointer update
#if test_showEventStats
Serial.println(); Serial.print("- ;"); Serial.println((int)oldestMessageStartPtr);
#endif
if (oldestMessageStartPtr == newestMessageStartPtr) { oldestMessageStartPtr = nullptr; newestMessageStartPtr = nullptr; } // remove last remaining
else { oldestMessageStartPtr = *(uint8_t**)(oldestMessageStartPtr + 2); } // remove oldest (which is not the last remaining)
eventStats.activeMsgPtr = (oldestMessageStartPtr == nullptr) ? nullptr : oldestMessageStartPtr + 4; // pointer to optional structure; after pointer update
eventStats.activeEventType = (oldestMessageStartPtr == nullptr) ? eNoEvent : *oldestMessageStartPtr;
}
return true;
}
bool MyEvents::isEventsWaiting() {
bool eventsArePending{ false };
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { // prevent interference with addChunk() - which may be called from ISR;
eventsArePending = (eventStats.eventsPending > 0);
}
return eventsArePending;
}
void MyEvents::snapshot(EventStats* eventSnapshotPtr) {
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { // prevent interference with addChunk() - which may be called from ISR;
*eventSnapshotPtr = eventStats;
}
}
// *** objects ***
LiquidCrystal lcd(D3_LCDregSelPin, B2_LCDenablePin, 4, 5, 6, 7); // define I/O pins (LCD RS, LCD enable, data = PORT D bits 4 to 7)
MyTime myTime;
MyEvents myEvents;
EventStats eventSnapshot;
// retain values from event message buffer
StatusData statusData;
GreenwichData greenwichData;
SecondData secondData;
// pointers into event message buffer
FastRateData* fastRateDataPtr;
LedstripData* ledstripDataPtr;
StepResponseData* stepResponseDataPtr;
// *** forward declarations ***
void getEventOrUserCommand(); // retrieve an event or a user command - exit if nothing available
void getISRevent(); // copy one ISR event (greenwich, status change, second cue, blink, fast rate data events, ...) for processing, if available
void getCommand(); // parse one user command - exit if no more characters available or command is complete
void processEvent(); // process one event, if available
void processCommand(); // process one user command, if available
void checkSwitches(bool forceSwitchCheck = false); // if SW3 to SW0 to be interpreted as switches only (instead of buttons)
void writeStatus(); // print on event or on command
void writeParamLabelAndValue(); // print on event or on command
void writeLedStrip(); // apply gamma correction and write led strip
void LSout(uint8_t* led, uint8_t* ledstripMasks); // write led strip
void LSoneLedOut(uint8_t holdPortC, uint8_t* LedData, uint8_t ledMask = B111); // write one ledstrip led
void idleLoop();
void formatTime(char* s, long totalSeconds, long totalMillis, long* days = nullptr, long* hours = nullptr, long* minutes = nullptr, long* seconds = nullptr);
void readKey(char* keyAscii); // from Serial interface and on board keys
void saveAndUseParam();
void fetchParameterValue(char* s, int paramNo, int paramValueNo);
void setRotationTime(int paramValueNo, bool init = false);
void setColorCycle(uint8_t newColorCycle, uint8_t newColorTiming, bool initColorCycle = false);
void setup()
{
// *** disable watchdog, open serial port and lcd ***
wdt_disable();
Serial.begin(1000000); // baudrate as entered
lcd.begin(16, 2); // 16 characters, 2 rows
// *** hardware: initialize ports ***
// PORT A & C (same Nano pins)
pinMode(A0_liftHallPin, INPUT); // A0: hall sensor analog input pin
pinMode(A1_temperaturePin, INPUT); // A1: temperature sensor analog input pin
pinMode(A2_IONotEnablePin, OUTPUT); // port C bit 2 (Nano pin A2): I/O channel not enable (74HCT138 decoder)
#if (boardVersion == 100)
pinMode(A3_ledstripDataPin, OUTPUT);
#else
pinMode(A3, INPUT_PULLUP); // not used
#endif
pinMode(A4, INPUT_PULLUP); // not used
pinMode(A5, INPUT_PULLUP); // not used
PORTC = (PORTC | portC_IOdisableBit); // disable I/O hardware
// PORT B
pinMode(B1_OC1Apin, OUTPUT); // timer 1 channel A to output pin
DDRB = DDRB | portB_IOchannelSelectBitMask; // set other port B output pins
PORTB = PORTB | portB_IOchannelSelectBitMask;
// *** read initial switch status and adapt settings accordingly ***
PORTD = PORTD | B11111100; // PORT D pins 2 to 7: prepare to enable pull ups
DDRD = DDRD & B00000011; // PORT D pins 2 to 7: inputs (pins 0 and 1: serial I/O)
PORTB = ((PORTB & ~portB_IOchannelSelectBitMask) | portB_switchesBufferSelect); // PORT B: select switch buffers
PORTC = (PORTC & ~portC_IOdisableBit); // enable switch buffers
switchStates = PIND; // read switches twice (stall) - allow for setup time (see Atmel data sheet)
switchStates = PIND & pinD_switchStateBits; // (read twice)
PORTC = (PORTC | portC_IOdisableBit); // disable switch buffers
PORTB = PORTB | portB_IOchannelSelectBitMask;
DDRD = DDRD | B11111100; // PORT D pins 2 to 7: outputs (pins 0 and 1: serial I/O)
// *** retrieve settings from eeprom and switches ***
uint8_t cnt{ 0 };
uint8_t eepromValue{ 0 };
uint8_t ledstripCycle{}, ledstripTiming{};
eepromValue = eeprom_read_byte((uint8_t*)0); // restore globe rotation time from eeprom
cnt = paramValueCounts[paramNo_rotTimes]; // No of defined rotation times
eepromValue = ((eepromValue < 0) || (eepromValue >= cnt)) ? 0 : eepromValue;
ParamsSelectedValueNos[paramNo_rotTimes] = eepromValue;
setRotationTime(eepromValue, true); // set rotation time and store in eeprom
eepromValue = eeprom_read_byte((uint8_t*)1); // restore globe vertical position setpoint from eeprom
cnt = paramValueCounts[paramNo_hallmVoltRefs]; // No of defined hall setpoints in millivolt
eepromValue = ((eepromValue < 0) || (eepromValue >= cnt)) ? 0 : eepromValue;
ParamsSelectedValueNos[paramNo_hallmVoltRefs] = eepromValue;
long hallmVoltRef = hallMilliVolts[eepromValue]; // globe vertical position ref in mVolt (after analog amplification)
targetHallRef_ADCsteps = (ADCsteps * hallmVoltRef) / 5000L; // globe vertical position ref in ADC steps
hallRef_ADCsteps = targetHallRef_ADCsteps;
// restore ledstrip cycle & timing from eeprom
eepromValue = eeprom_read_byte((uint8_t*)2); // b7654 = ledstrip cycle time, b3210 = ledstrip cycle
eepromValue = eepromValue + (eeprom_read_byte((uint8_t*)3) & (uint8_t)0x01); // if running time after previous reset was small: switch to next ledstrip color cycle
ledstripCycle = eepromValue & (uint8_t)0x0F;
ledstripTiming = eepromValue >> 4;
ledstripCycle = ((ledstripCycle < cLedstripOff) || (ledstripCycle > cRedGreenBlue)) ? cLedstripOff : ledstripCycle;
ledstripTiming = ((ledstripTiming < cLedstripVeryFast) || (ledstripTiming > cLedStripVerySlow)) ? cLedstripVeryFast : ledstripTiming;
setColorCycle(ledstripCycle, ledstripTiming, true); // set ledstrip cycle and timing and store in eeprom
// DIP switches 2 to 5 (signals SW3 to SW0): interpret as buttons if all 4 switches OFF (= 'high') after reset. If NOT all OFF, then interpret as switches and enter program mode (do not connect buttons then)
// if in program mode, a selected setting restored from eeprom will be overridden
switchesSetLedstrip = (switchStates & pinD_keyBits) == (uint8_t)0x00; // signals SW3 to SW0: interpret as switches and use to program ledstrip
switchesSetRotationTime = ((switchStates & pinD_keyBits) >> 2) == (uint8_t)0x01; // signals SW3 to SW0: interpret as switches and use to program rotation time
switchesSetHallmVoltRef = ((switchStates & pinD_keyBits) >> 2) == (uint8_t)0x02; // signals SW3 to SW0: interpret as switches and use to program globe vertical position reference
useButtons = (switchStates & pinD_keyBits) == pinD_keyBits; // signals SW3 to SW0: interpret as buttons if all corresponding 4 switches OFF (= 'high') after reset (if not all OFF, then do not connect buttons)
checkSwitches(true); // adapt settings according to switch states - note that switch 1 (signal SW4) is currently not used
cli();
eeprom_update_byte((uint8_t*)3, (uint8_t)1); // flag that reset took place
sei();
paramNo = switchesSetHallmVoltRef ? paramNo_hallmVoltRefs : paramNo_rotTimes; // initial setting to display: rotation time, except if currently in program mode
paramValueNo = ParamsSelectedValueNos[paramNo];
// *** do a first temp reading here and assign it to temperature filter output, to avoid slow temperature ramp up ***
tempSmooth = (((long)analogRead(A1_temperaturePin) * 50000L - (5000L << 10)) >> 10);// convert to degrees Celcius x 100 (multiply or divide by 1024 = ADC resolution: shift 10 bits instead)
// *** init serial and lcd ***
while (!Serial);
Serial.println();
if (!useButtons) {
Serial.println(strcpy_P(s150, str_programMode));
Serial.println();
}
Serial.println(strcpy_P(s150, str_build));
Serial.print(strcpy_P(s150, str_help1));
Serial.print(strcpy_P(s150, str_help2));
Serial.println(strcpy_P(s150, str_help3));
lcd.clear();
lcd.noAutoscroll();
// *** enable watchdog timer (2 seconds) ***
wdt_enable(WDTO_2S);
// *** setup timer 1 (16 bit) for phase correct PWM 1 Khz and enable overflow interrupt ***
// timer 1 is used as timebase AND to generate PWM for lifting magnet
// Prescaler 8 (16MHz / 8 = 2 MHz clock => T = 500 nanoS), 1 kHz = 2 Mhz / (2 * 1000 = 2 * TOP value)
TCCR1A = _BV(COM1A1) | _BV(WGM11); // COM1A1 set: clear OC1A pin on compare match when upcounting, set when downcounting
TCCR1B = _BV(WGM13) | _BV(CS11); // WGM13 & WGM11 set: PWM, phase correct, TOP = ICR1 register; CS11: prescaler factor 8
ICR1 = timer1Top; // counter TOP value
do {} while (TCNT1 < 100); // prevent first of two timer interrupts in succession after reset, with ADC re-trigger before ADC interrupt
TIMSK1 = _BV(TOIE1); // enable overflow interrupt for this timer
}
void loop()
{
getEventOrUserCommand(); // get ONE event or assembled user command, exit anyway if none available
processEvent(); // process event, if available
processCommand(); // process command, if available
checkSwitches(); // if SW3 to SW0 to be interpreted as switches only (instead of buttons)
writeStatus(); // print status to Serial and LCD (if connected)
writeParamLabelAndValue(); // print parameter label and value to Serial and LCD (if connected)
writeLedStrip(); // write ledstrip on event
myEvents.removeOldestChunk(ISRevent != eNoEvent); // has an event been processed now ? remove from message queue
wdt_reset(); // reset watchdog timer
resetHWwatchDog = true; // allow ISR to set ISR-IN-EXEC signal high then low (set low again in ISR). Tmax around 400mS
idleLoop(); // only for idle time measuring. Results in slight jitter on start of ISR (+/- 10 micros)
}
// *** get either an event or a user command, but not both at the same time
void getEventOrUserCommand() {
ISRevent = eNoEvent;
userCommand = uNoCmd;
getISRevent(); // retrieve an event for processing, if available
if (ISRevent == eNoEvent) { getCommand(); } // if no event waiting to be processed and character(s) available, parse until command is complete or out of characters
}
// *** process a single ISR event ***
void getISRevent() {
// select an ISR event (greenwich, status change, second cue, blink, fast rate data events, ...) for processing
// note that pressing and releasing HW buttons results in updating a character buffer, NOT in setting an event
myEvents.snapshot(&eventSnapshot); // current event status (active event data & statistics)
ISRevent = eventSnapshot.activeEventType; // event type to process now (oldest), if any
if (ISRevent == eNoEvent) { // do nothing
}
else if (ISRevent == eStatusChange) { // status change event ? copy event message (needed later)
statusData = *(StatusData*)(eventSnapshot.activeMsgPtr);
}
else if (ISRevent == eGreenwich) { // greenwich position event ? copy event message (needed later)
greenwichData = *(GreenwichData*)(eventSnapshot.activeMsgPtr);
}
else if (ISRevent == eSecond) { // second tick event ? copy event message (needed later)
secondData = *(SecondData*)(eventSnapshot.activeMsgPtr);
}
else if (ISRevent == eFastRateData) { // 'fast rate data logging' event ? set pointer to message
fastRateDataPtr = (FastRateData*)(eventSnapshot.activeMsgPtr);
}
else if (ISRevent == eLedstripData) { // led strip event ? set pointer to message
ledstripDataPtr = (LedstripData*)(eventSnapshot.activeMsgPtr);
}
else if (ISRevent == eStepResponseData) { // step response event ? set pointer to message
stepResponseDataPtr = (StepResponseData*)(eventSnapshot.activeMsgPtr);
}
else if ((ISRevent == eBlink) || (ISRevent == eSpareNoDataEvent1)) { // time cue event ?
// do nothing here (no data associated with event)
}
}
// *** parse multiple characters until either a command is complete OR no characters available (even if command is not yet complete) ***
void getCommand() {
// all user commands are 1 character, without the need for a CR or LF (both are ignored) because globe buttons do not have these keys
// commands can take single-character parameters (currently, only one parameter, but this can be extended)
// commands come either from key presses read from within the ISR or from the Serial interface
// exit even if command is not complete, and continue assembly in next main loop
// command -1: no command to execute
// commands 0 - 99: without parameters
// command 100 - 199: with one parameter
// command 200 - 299: with two parameters
// command 999: command error
// commandState: 0 = wait for new command, 1 = wait for command parameter 1, 9 = command complete, 10 = command error
constexpr bool enterCmdUsingSeparators{ false }; // enter with separators: not useful because Globe keyboard has no keys to type in separators and CR
char keyAscii{ 0 };
static int commandBuffer{ 0 }, commandParam1Buffer{ 0 }, commandParam2Buffer{ 0 }, commandState{ 0 };
userCommand = uNoCmd; // init: no assembled command yet
do {
readKey(&keyAscii); // from HW buttons AND from Serial
if (keyAscii == 0) {
return; // no character read
}
bool ignoreChar = ((keyAscii == (char)0x20) || (keyAscii == (char)0x0D) || (keyAscii == (char)0x0A)) && !enterCmdUsingSeparators; // space, CR, LF characters
if ((keyAscii == (char)0x1B) || (keyAscii == '/')) { // use ESC or '/' as abort characters
commandState = 0; // abort: wait for new command
}
else if (!ignoreChar) {
switch (commandState) {
case 0: // currently no command: read command
commandBuffer = uNoCmd;
if (keyAscii == '-') { commandBuffer = uPrevious; } // previous
else if (keyAscii == '+') { commandBuffer = uNext; } // next
else if ((keyAscii == 'e') || (keyAscii == 'E')) { commandBuffer = uEdit; } // enter edit / end edit
else if ((keyAscii == 'c') || (keyAscii == 'C')) { commandBuffer = uCancel; }
else if ((keyAscii == 'a') || (keyAscii == 'A')) { commandBuffer = uShowAll; } // show all parameters (Serial output only)
else if ((keyAscii == 's') || (keyAscii == 'S')) { commandBuffer = uLive; } // show / stop live values
else if ((keyAscii == 't') || (keyAscii == 'T')) { commandBuffer = uTimeStamp; } // time stamp (Serial output only)
else if (keyAscii == '?') { commandBuffer = uHelp; }
else if ((keyAscii == 'r') || (keyAscii == 'R')) { commandBuffer = uStepResponseTest; } // measure step response
else if ((keyAscii == 'l') || (keyAscii == 'L')) { commandBuffer = uLedstripSettings; } // select ledstrip color cycle type or cycle timing
if ((commandBuffer == uPrevious) || (commandBuffer == uNext) || (commandBuffer == uEdit) || (commandBuffer == uCancel)) { commandBuffer = commandBuffer + (paramChangeMode ? 10 : 0); }
if ((commandBuffer >= 0) && (commandBuffer <= 99)) { commandState = 9; } // these command take no parameter: signal 'command complete' if command detected
else if (commandBuffer >= 100) { commandState = 1; } // command takes at least 1 parameter: signal 'look for parameter 1'
else { commandState = 10; } // unknown command: error
break;
case 1: // command needing 1 parameter has been read: read parameter
// limit allowable characters for parameters (additional parameter checks depending on command: enter in execution block)
commandParam1Buffer = -1; // no command parameter 1 yet: read
if ((keyAscii >= '0') && (keyAscii <= '9')) { commandParam1Buffer = ((uint8_t)keyAscii & B00001111); }
else if (keyAscii == '-') { commandParam1Buffer = 10; }
else if (keyAscii == '+') { commandParam1Buffer = 11; }
else if (keyAscii == '=') { commandParam1Buffer = 12; }
else if (((keyAscii >= 'A') && (keyAscii <= 'Z')) || ((keyAscii >= 'a') && (keyAscii <= 'z'))) { commandParam1Buffer = keyAscii & ~0x20; }
if (commandParam1Buffer >= 0) {
if (commandBuffer <= 199) { commandState = 9; } // command takes 1 parameter only: signal 'command complete' if command detected
else { commandState = 2; } // command takes at least one additional parameter: signal 'look for parameter'
}
else { commandState = 10; } // illegal parameter value: error
break;
case 2:
commandParam2Buffer = -1; // no command parameter 1 yet: read
if ((keyAscii >= '0') && (keyAscii <= '9')) { commandParam2Buffer = ((uint8_t)keyAscii & B00001111); }
if (commandParam2Buffer >= 0) { commandState = 9; }
else { commandState = 10; } // illegal parameter value: error
break;
}
}
if (commandState == 9) { // command assembled
userCommand = commandBuffer;
commandParam1 = commandParam1Buffer;
commandParam2 = commandParam2Buffer;
commandState = 0;
}
else if (commandState == 10) { // command error
userCommand = uUnknownCmd;
commandState = 0;
}
} while (commandState != 0);
}
// *** process an event (except printing) ***
void processEvent() {
constexpr int averagingPeriodsMagnetLoad{ 10 };
constexpr long maxOnCycles = (long)((averagingPeriodsMagnetLoad * 256 * fasteDataRateSamplingPeriods * timer1Top) * 0.8); // can normally not occur, but as this concerns safety ...
constexpr int tempTimeCst_BinaryFractionDigits{ 16 };
constexpr long tempTimeCst1024 = (long)(samplingPeriod * fasteDataRateSamplingPeriods * (1L << tempTimeCst_BinaryFractionDigits)); // temp time cst * 2^n for added accuracy (long integer)
static uint8_t fastRateDataEventCounter{ 0 }; // overflows at 255
static long partialSumMagnetOnCycles{ 0 };
static long movingSumMagnetOnCycles{ 0 };
static long sumMagnetOnCycles[averagingPeriodsMagnetLoad] = { 0 };
if (ISRevent == eNoEvent) { return; }
// first order filters below are implemented as integrator with negative feedback:
// y(k) = sampling period / time constant * sigma(x(k) - y(k-1)) = y(k-1) + sampling period / time constant * (x(k) - y(k-1))
// samping period expressed in seconds / time constant 1 second (5 seconds for vertical position error signal and temp. reading)
switch (ISRevent) {
case eStatusChange:
if (!statusData.isFloating) { errSignalMagnitudeSmooth = 0; } // globe currently not floating ? reset smoothed error value immediately
break;
case eFastRateData: { // data provided at a high rate (every 128 milli seconds)
// feed idle time, ISR duration, magnet ON cycles and error signal totaled in fasteDataRateSamplingPeriods to smoothing filters
// -> NOTE that at this stage, the smoothed values are NOT AVERAGES BUT SUMS
idleLoopMicrosSmooth += ((((float)fastRateDataPtr->sumIdleLoopMicros) - idleLoopMicrosSmooth) * (samplingPeriod * fasteDataRateSamplingPeriods / 1.0F));
ISRdurationSmooth += ((((float)fastRateDataPtr->sumISRdurations) - ISRdurationSmooth) * (samplingPeriod * fasteDataRateSamplingPeriods / 1.0F));
magnetOnCyclesSmooth += ((((float)fastRateDataPtr->sumMagnetOnCycles) - magnetOnCyclesSmooth) * (samplingPeriod * fasteDataRateSamplingPeriods / 1.0F));
errSignalMagnitudeSmooth += ((((float)fastRateDataPtr->sumErrSignalMagnitude) - errSignalMagnitudeSmooth) * (samplingPeriod * fasteDataRateSamplingPeriods / 5.0F));
// feed temp. sensor reading to smoothing filter
// TMP36 sensor: 10 mV per °C, 750 mV at 25 °C : 1 ADC step * 5000 mV / 1024 steps * 1 °C / 10 mV = 0.488 °C which gives sufficient accuracy for safety purposes
long temp = ((fastRateDataPtr->sumADCtemp * 50000L - (5000L << 10)) >> 10); // convert to degrees Celcius x 100 (multiply or divide by 1024 = ADC resolution: shift 10 bits)
cli(); // tempSmooth is passed back to ISR for safety check high temperature
tempSmooth = tempSmooth + ((((temp - tempSmooth) * tempTimeCst1024) / 5L) >> tempTimeCst_BinaryFractionDigits);
sei();
partialSumMagnetOnCycles += fastRateDataPtr->sumMagnetOnCycles;
fastRateDataEventCounter++; // overflows at 255 idle events = 255 * 128 mS, period = 32768 milli seconds
if (fastRateDataEventCounter == 0) {
movingSumMagnetOnCycles = movingSumMagnetOnCycles + partialSumMagnetOnCycles - sumMagnetOnCycles[averagingPeriodsMagnetLoad - 1]; // spans more than 5 minutes
for (int i = averagingPeriodsMagnetLoad - 2; i >= 0; i--) { sumMagnetOnCycles[i + 1] = sumMagnetOnCycles[i]; }
sumMagnetOnCycles[0] = partialSumMagnetOnCycles;
partialSumMagnetOnCycles = 0;
cli(); // highLoad is passed back to ISR for safety check high magnet load
highLoad = (movingSumMagnetOnCycles >= maxOnCycles);
sei();
}
break;
}
case eSecond:
if (secondData.eventSecond == 3) {
cli(); // interrupts off: interface with ISR and eeprom write
eeprom_update_byte((uint8_t*)3, (uint8_t)0); // time after reset is longer than 3 seconds
sei();
}
break;
}
}
// *** execute a user command (except printing) ***
void processCommand() {
if (userCommand == uNoCmd) { return; }
bool commandParamError{ false };
bool doSaveParams{ false };