-
Notifications
You must be signed in to change notification settings - Fork 2
/
sensorCAM.ino
2480 lines (2337 loc) · 145 KB
/
sensorCAM.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
//sensorCAM Alpha release limit >|
#define BCDver 201
//v201 Experimenting with row-wise linear sensos. Clear S06 bit in EXIORDD packets for bad pkt identification
//v200 revised EXIORRDD response by adding header. Is incompatible with CS pre v2.0 ES_IOEXSensorCAM driver
//v170 'f' cmd output S0%% changed to S%%; #define CAM 700 & cam2 600 (test?)
//v169 made sensor size configurable using SEN_SIZE (0-7) to insert extra unused pixels.
//v168 add minSensors to 'm', pvtThresholds to 't##,%%' and pvtThreshold to 'i%%' printout.
//v167 added r%%* to reset all early part of a sensor bank, added brightSF to scroll,
//V166 use micros() to maintain sync even with interruptions @ 71min long rollover & fixed wait() rollover.
//v165 adjusted timerLoop accuracy,r comments,a&k row<10 now ok
//v164 adjusted 'y' command to better accommodate various PC speeds.
//v163 tweaked 'h' cmd & catered for blank EPROM startup (no active sensors 1-79)
//v162 refined command error messages and prompts
//v161 some code housekeeping and modified use of SUPPLY (50/60Hz) & CYCLETIMECTR & BRIGHTSF
//v160 added scroll toggle control to 't1/t 1'. Limit threshold >30 (t31 makes all occupied) <multiple rows>
//v159 added 'F' to cmds[] from EX-CS and changed 'F' to Force Reset without a confirmation \n
//v158 amended to use configCAM.h
#if __has_include ( "configCAM.h")
#include "configCAM.h"
#else
#warning configCAM.h not found. Using defaults from configCAM.example.h
#include "configCAM.example.h"
#endif
#define NUMdigPins 80 //CS can create fewer to save memory.
#define NUManalogPins 0 //must be 4 for UNO emulation
//#define SUPPLY 10 //see configCAM.h - set period to 10 half-cycles of mains (50Hz) (use 25 for 60Hz)
#define CYCLETIME 100000 //100mSec cycle time syncs with 5cycles of 50Hz and 6 cycles at 60 Hz
#ifndef BRIGHTSF
#define BRIGHTSF 3 //increases sensitivity. If 1, 20% change adds 3*SF to diff. 5% is ignored
#endif
#ifndef SEN_SIZE
#define SEN_SIZE 0 //sensor expansion - add SEN_SIZE rows and columns + through 4x4 sensor.
#endif
#define EXIOINIT 0xE0
#define EXIORDY 0xE1 //OK & ready for next ioexpander cmd
#define EXIODPUP 0xE2
#define EXIOVER 0xE3
#define EXIORDAN 0xE4
#define EXIOWRD 0xE5
#define EXIORDD 0xE6
#define EXIOENAN 0xE7
#define EXIOINITA 0xE8
#define EXIOPINS 0xE9
#define EXIOWRAN 0xEA
#define EXIOERR 0xEF //last command erroneous
#define CAMERR 0xFE //CAM cmd error header
#include "esp_camera.h" //Define i2c interface parameters--- Any difference between “Wire.h” and <Wire.h> ??
#include <Arduino.h>
#include <WiFi.h> //compiler still finding: Multiple libraries were found for "WiFi.h"
//Used: C:\Users\Barry\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.3-RC1\libraries\WiFi
//Not used: C:\Program Files (x86)\Arduino\libraries\WiFi
#include <Wire.h> //esp32 i2c Wire library
TwoWire MyWire = TwoWire(0); //Create second i2c interface (don't use Wire1 = )
// define appropriate board (only AI_THINKER tested so far
#define CAMERA_MODEL_AI_THINKER // Select camera model (deault: has OV2640 Camera module)
#include "camera_pins.h" //for thinker, includes #define XCLK_GPIO_NUM 0
#define I2C_FREQ 100000 //Master sets i2c clock frequency MEGA uses default clock (100kBits/sec)
//Define GPIO ports for I/O use
#define I2C_SDA2 15 //for MyWire (twowire) i2c data GPIO pin 15
#define I2C_SCL2 13 //for MyWire (twowire) i2c clock pin
#define WEBPIN 14 // if grounded on Reset, this pin invokes web server mode - uses default WiFi(1) ssid .
#define GPIO0 0 // Used as CSI MCLK output, so best to not ground while running UNLESS in reset mode.
#define FLASHLED 4 //GPIO4, when 4 HIGH turns on Flash LED. Use ~25uSec pulse to indicate new fb image.
#define BLK0LED 2 //tried GPIO12 - DANGEERUS 12 MUST BE LOW on reset to correctly reset intenals for RAM
#define BLK1LED 33 // N.B. tried: GPIO12 and stuffed a CAM -GPIO12 used to set flash programming voltage!
#define BLK2LED 33 //to use 33 (on-board LED) need to rework CAM to repurpose P_OUT pin(P1-4)(see Youtube)
#define BLK3LED 33 //consider using 4 if can disable flash (remove resistor from CAM?)(but flash useful!)
#define pLED 14 //assign a LED to be programmable. i.e. 'n' cmd assigns a bank status to the pLED
//GPIO14 multipurposed - LED pulls GPI14 high. Need to Gnd GPIO14 to select web server.
//#define GPIO16 16 //GPIO16 is used for PSRAM and CAN NOT be repurposed!
//GPIO0 unavailable. Need to Gnd GPIO0 to program then needed for CAM CSI_MCLK
//GPIO1 & GPIO3 reserved for TX/RX USB comms.
//GPIO12 best left untouched. Used to set internal voltages for Embedded Flash?
// (tried to use, but rendered FLASH RAM unprogrammable!(stuffed one CAM)
#include <EEPROM.h>
#define EEPROM_SIZE 320+8+80+80 //long pointers into image +reboot flag integer(+threshold,nLED,min2flip,maxSensors?)
#define EPvFlag EEPROM_SIZE-80-80-8 //VFlag stored as (4byte)integer, others as byte
//#define EPminSensors EEPROM??
#define EPnLED EEPROM_SIZE-80-80-4 //NOTE: these cells have garbage(FF?) in unprogrammed CAM - needing init.
#define EPthreshold EEPROM_SIZE-80-80-3
#define EPmin2flip EEPROM_SIZE-80-80-2
#define EPmaxSensors EEPROM_SIZE-80-80-1 //use to limit printout of sensor states (if > 40, loop time may suffer)
#define EPSensorTwin EEPROM_SIZE-80-80 //save SensorTwin array
#define EPpvtThreshold EEPROM_SIZE-80 //Individual thresholds (use default threshold if =255)
//maxSensors may also be used to avoid other wasted processing time.
// WARNING!!! PSRAM IC required for UXGA resolution and high JPEG quality
#define RGB565p 2 // bytes per pixel in RGB565 image
#define FBPITCH 320*RGB565p // bytes per row of FB (QVGA=320 pixels)
bool DEBUG[11]={false,false,false,false,false,false,false,false,false,false}; //show detail debug to USB output
#define IFS if(scroll==true) //output progressive scroll data
#define Spr Serial.print //shortens long print statements.
#define IF0 if(DEBUG[0]) //Higher priority debug
#define IFT if(DEBUG[1]) //Debug control of timing output
#define IFD if(DEBUG[2]) //Lower priority Debug control
#define IFI2C if(DEBUG[3]) //output generated i2c packets
#define IFN if(DEBUG[4]) //output relating to pixel noise measurements
#define IF5 if(DEBUG[5]} //not used
#define IFd if(dbug== //can set dbug to 0 to 9 using 'h#' cmd. IFd #) only execute after 'h#'
//IFd 6) grab_ref message printed on full execution of S666init_SensorRefs(prefill)
//IFd 7) stop/wait on a trip of bank # or sensor 16. only if LAST 'h' was h7 or h7#
//IFd 8) write EX-CS cmd to USB e.g. EXIODPUP gives "E2", undefined commands give "#EF" error
#define S666_pitch 48 //length of one sensor rgb666 data of 16 pixels 4x4x3bytes saved in Sensor666[]
#define S666_row 12 //length of row for one sensor (_pitch= s666_row x 4(rows) (for QVGA resolution) 4x3
#define AVCOUNT 32 //Nominate the number of frames to average for 'r' reference image
#define NUM2AVERAGE 32 //variables for auto references averaging & updating for unoccupied enabled sensors
/* sensorCAM commands:
s%% * Scan for new location for sensor %% (00-97). If found, records location in Sensor[%%]. Further setup needed.
Scan looks for a bright LED on a dimmer background. The LED should be placed on the desired sensor pos'n.
If satisfied with the scan, the user should REMOVE the LED, set lighting, and do an r%% to set enabled AND
record a new reference image (also computes colour ratios & brightness) The location must be unoccupied!
w * Wait for new command line (\n) before resuming loop(). (handy to stop display data scrolling)
a%% Activate sensor[%%] & refresh Sensor_ref[%%], cRatios etc. from the [%%] image (48 bytes) in latest frame.
a%%,rrr,xxx * Set coordinates of Sensor[%%] to row/col: rrr/xxx AND activate and auto ref. Verify with p$ cmd.
b%[#] bank % sensors. Show which sensors OCCUPIED (in bits 7-0). 1=occupied. brightSF=# <i2c Request $+1 bytes>
c$$$$ * reCalibrate camera CCD occasionally and grab new references for all enabled sensors. (Beware of doing this
while any sensors are occupied! Obstructed sensors will later need an r%% Check all bank LEDS are off
AND check all sensors are unoccupied before recalibrate. Can set BRI CON SAT AWB through c$$$$ e.g. c0120
Can change default setting for AWBg,AEC,AECd,AEL,AGC,AGg with six extras: c$$$$1111119. c$$$$ resets them!
d%%# *Difference score in colour & brightness between Ref & actual image. Show # grabs. <Request 4 data bytes>
e * EPROM save of any new Sensor offset positions & 4 parameters. Warning: May only work once per reset
f%% * Frame buffer sample display. Prints latest image of Sensor[00] & Sensor[%%] positions. <Requests 4x28 bytes>
g * Get Camera Status. Displays most current settings available in webcam window. (also works in video mode)
h$ Help(debug)output - h to turn OFF, h0 turns ON detailed USB output. h1:more; h2:timing; h3:i2c; h4:Noise
i%% * Individual sensor %% Information. Optional i%%,$$ sets a Twin sensor[$$] for S%%. <i2c Request 2 data bytes>
j$# adJust camera setting $ to value # and display most settings (as for ‘g’) ‘j’ does NOT get new refs. - use r
k%%,rrr,xxx * Set coordinates of Sensor[%%] to row: rrr & xVal: xxx. Follow with r%%. Verify values with p$ cmd
l%% (Lima) force sensor %% to ON (1= occupied (LED lit) & also set SensorActive[%%] false to inactivate updating
m$ * Min. no.($) of sequential frames to trigger Occupied threshold for detection (def.=2) (m$,## min/maxSensors)
n$ * bank Number $ assignment to the programmable LED to show its occupancy status.
o%% (Oscar)force sensor %% off (0=UN-occupied (LED off) & also set SensorActive[%%] false to inactivate updating
p$ * Position Pointer table for banks 0 to $ giving DEFINED sensor r/x positions. p%% shorter. <32 data bytes>
q$ * Query bank $, to show which sensors enabled (in bits 7-0). 1=enabled. q9 gives ALL <Req. $+2 data bytes>
r%%[*] Refresh average Sensor_Ref[%%] (Iff defined), enable & calc. cRatios etc. r%%* refreshes bank up to S%%
r00 Renew Average Refs etc. for ALL defined sensors. Ignores active[]. Sensor 00 reserved for brightness ref.
t##[,%%] * show/set Threshold level being used for Sensors (## sets 32-98)[for S%%] t1 toggle scrolling <1+ bytes>
u%% * Un-define/remove sensor %% by setting INACTIVE & Sensor[%%]=0. u99 erases ALL. Needs ‘e’ to erase from EPROM
v[2]* Video mode. Reboots CAM in webserver mode. “v2” will connect to 2nd (alt.) router ssid.("ve" gives version)
x### Sets column for start of Processing image transfer (0-318)
y### Suspend imaging. Proceeds to write header and Zlength row### pixels to USB port for Processing. 'yy' resumes
yy Used exclusively to end a series of 'y' commands (that suspended imaging) and returns CAM to run mode.
z### Sets Zlength for line length (even number of pixels) in image transfer (2-320) (see 'y')
& * Diagnostic output of statistics. histogram of No. of frames of "noise-tripped" occupations.
@## Changes Occupied indicator from 35 (#) to any nominated ASCII character from 1 to 127. @ alone gives BOLD 12
F|R * Reset commands – will Reset CAM and initiate the Sensor mode. Both will Finish the WebServer (v) mode.
* These commands typically for diagnostic/setup use only. They wait for a line feed or command to resume.
*/
unsigned long int starttime=0; //counter giving each start time in uSec.
int E6counter=200; //E6 EXIOexpander cmd occurs at 100/sec. stop E4 debug output after #
int CSstartup=2;
byte analoguePinMap[] = {12,13,14,15,16,17}; //default pin map for uno & nano vpin=firstVpin+Map[i]?
byte EXCScmd0=0; //last EXCS cmd
bool EXCScmd=false; //true if last i2c cmd from EX
int firstVpin=0; //to be set by EX-CS EXIOINIT function
int Bri=0, Con=1, Sat=2, AWB=1, AWBg=1, AEC=1, AECd=1, AEL=1, AGC=1, AGg=9; //initial settings of 'c' variables
int AWB9=1, AGC9=1; //after prefillRef delay (9sec) change AWB and AGC if desired
// arguments for 'c' command; Bri Con Sat AWB AWBg AEC AECd AEL AGC AGg (NO SPACES!)
int dbug=10;
char OCc = '#'; //OCc character to indicate OCCUPIED in scrolling status line. @12 good for Arduino IDE
int avRefCtr=0;
int avRefAccumulation[48];
int emptyStateCtr[80]; //used to count No. of loops a sensor NOISE caused "OCCUPIED" status
int rbsn = 1; //bsn for current averaging If unoccupied, new 3.2sec ref regularly(~EVERY MIN.?)
byte newRef[48];
bool autoRefSuspended = true; //use to stop auto referencing during conflicting commands e.g. 'r' and 'c' ("SUS")
bool lastsuspend=true;
int force_refs=0; //used to force block refs
byte RingBuff[2*80*16*3]; //ring buffer to save grabs to average before trip
bool avOddFrame=true; //for av2Frames()
bool boxSensors=false;
int SensorHiCount[80]; //for '&' noise histogram command
int HistoLoops=0;
int SensorHisto[80*5];
uint32_t i2cCtr = 0; //prepare for i2c
bool CmdI2C=false;
int nLED=2; //Blk No assigned to programmable LED - initially bank 2
int maxSensors=050; //use to limit USB PRINTOUT time & line length
unsigned int minSensors=000; //a lower limit for sensor data output
//used if using more sensors than can otherwise be processed in 100mSec.(42-> max bsNo=51 (5*8+1) ). It limits
//monitor output (if more than 40, sensors loop time may suffer because of printout time to monitor)
unsigned long Sensor[80]; //from EEPROM array of xy coordinates for 10 banks of 8 sensors.
//Sensor[] Offsets into FULL image buffer! xy stored in 4 bytes EPROM
byte Sensor666[80*16*3]; //buffer to hold decoded latest 4x4 sensor images from frame fb (pitch=S666_row=12)
byte Sensor_ref[80*4*4*3]; //array of 80 reference grabs (QVGA: 4x4x3RGB =48 bytes/sensor (total 3840 bytes
unsigned int SensorRefRatio[80*12]; //array of 3 x 4 quadrant colour ratios (r/g g/b b/r)x4
int Sen_Brightness_Ref[80]; //sum of pixels in all quadrant colours combined. max=3024 (4*4*3*63) for 666/pixel
byte SensorBlockStat[11]={0,0,0,0,0,0,0,0,0,0,0};//array of bits (8/bank) each high if corresponding sensor tripped
// If SensorBlockStat[b] ==0 then NO sensors in that block set are occupied (sensor[b0] is LSB in byte)
unsigned int quad[5]={0,0,0,0,0}; //a temporary place to hold current sensor image quadrant brightness values
unsigned int bright; // (4xquad[0-3] & total in quad[4] )
int brightSF=BRIGHTSF; //a scale factor to weight brightness variation when computing diff + bright*brightSF
bool SensorStat[80]; //state occupied/unoccupied true/false (can set using l%% or o%%. They set inactive)
bool SensorActive[80]; //if false (inactive) then don't update SensorStat[] (can set active with a%%)
byte SensorActiveBlk[10]; //each holds 8 sensor bits(8 bits/block) e.g. SensorActive[07-00] in SensorActiveBlk[0]
int SensorFilter[80]; //use to slow dropout by a cycle or two - needs to be unoccupied for more than 100mSec
byte SensorTwin[80]; //if sensors in pairs use to identify secondary bsn. (should save into eprom!)
byte pvtThreshold[80];
int min2flip = 2; //number of exceptional frames required before transitioning output tripped/untripped.
byte mask[8]= {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
int bsNo; //parameter from text commands
int bsn; //general use
int bsnUpdated=-1;
int dbsNo=0; //bsNo chosen with d%% cmd.
int i; //local index counter
int j; //local index counter
int b=0; //local (block) variable
int h7block=1; //h7 stop on block # trip
int aRefCtr=-1; //for 'a' cmd
char absNo1='0', absNo2='0';
char i2cCmd[64]={'0'}; //string from Master (onReceive)
byte dataPkt[128]={CAMERR}; // prepared packet(s) for i2c (max packet=32 bytes)
boolean newi2cCmd=false; // flags the presence of a new i2c serial command string for processing
int wifi=1; //identify default wifi router (1 or 2)
bool MyWebServer=true;
char Shedssid[]=WIFI_SSID;
char ShedWIFIpwd[]=WIFI_PWD;
char Altssid[]=ALTWIFI_SSID;
char AltWIFIpwd[]=ALTWIFI_PWD;
char* ssid = Shedssid; //must match local WiFi !
char* password=ShedWIFIpwd; //must match local WiFi !
char* ssid2 = Altssid; //alternate ssid use with "v2" cmd
char* password2=AltWIFIpwd;
int fbheight = 240; //number of rows in fb default QVGA 320x240
int pitch=640*3; //image pixels/row (RGB888 VGA 640x480)
int count = 0; // number of characters in serial cmdString[]
char cmdString[64]; // holds a whole command line from input source
//byte testFB[2*320*42]; // [2*320*42]; //26880 byte (50rows) of test image buffer
char cmdChar;
bool Yhold=false; //halt refreshing frames so multiple 'y's can see same fb
int Xcolumn=0; //default value for 'x' command
int Yrow=0; //default for 'y' cmd
int Zlength=320; //default full line for 'z' command
char Yheader[10]="y_x_z_ck:";
bool SendYpacket=false;
int dispX=0; //default starting column byte for sample dump
long bright_spot=0; //pointer to the brightest spot in BMP/RGB666 image
char zStr[13]; //used to print long integers using ltoa()
char i2cData[64]; //array of sensor status data to send on 't' request
bool scroll=true; //if false suppress scrolling output (use 't0')
int i2cDatai=0; //array index
int threshold=42; //difference (max Xratio+brightnessRatio) threshold for occupancy
int maxDiff=0; //exact Xdiff match produces minimum maxDiff of 32
int dFlag=0; //flag set by d%% to print out (while>0) diff and sample image for bsNo.
int dMaxDiff=0; //saved maxDiff for 'd' cmd.
int dBright=0; //saved bright for 'd' cmd.
int error=0;
unsigned int releaseTime=0; //counter for loop cycle timing control (target 10Hz?)
unsigned int timer; //*code subsection time measurement ESP32() returns uint32_t NOT long(64)
unsigned int timerLoop=0; //*loop time measurement
unsigned int cFlag=0; //*flag to set off full Sensor_ref[] refresh of all active sensors (triggers c##
unsigned int cDelay=10000; //*cDelay mSec after c## before updating active ref's
int sScan=-1; //flag set by s%% to scan for, and set, new Sensor[sScan] position
int sScanCount=3; //get several frames before doing a scan to clear pipeline
int refBrightness=0; //reference brightness sum of 16 pixles of Sensor_ref[00]
int refActual=0; //actual Sensor[00] brightness on current frame
int prefillRef=90; //after first 9sec of fb_gets() (plus seconds from cFlag?) take 80 Sensor reference grabs
byte *imagePtr; //full image buffer pointer
byte *imageFB; //ditto used by 'y' processing
byte rgb666[3]={0,0,0}; //destination for decodeRGB565() (one pixel)
String cStr=" "; //string to hold latest auto adjust settings (AWB AEC AGC CBar)
int frameNo=0; //variables for NOISE measurement code
int Sensor666Av[48];
int rollAvFrameR[64], rollAvFrameG[64], rollAvFrameB[64]; //rolling Average values of Sensor[00]
int rollNsFrameR[64], rollNsFrameG[64], rollNsFrameB[64]; //rolling Noise values
int rollAvNoise[64]; //calculate rolling average noise
int rollAvR=0, rollAvG=0, rollAvB=0; //RGB components
int rollAv=0;
int r00Av=0;
int rollNsR=0, rollNsG=0, rollNsB=0; //RGB components
int r00AvR, r00AvG, r00AvB;
int r00NsR; int r00NsG; int r00NsB;
int Noise=0; //difference between frame pixel and ref pixel
int averageRbsn=-1; //a flag for main loop to do averaging
int averageRcounter = -1; //down counter for frame averaging (see 'r');
int averageSensor[48*80]; //accumulation space for up to 64 frames of a sensor
unsigned int stdCtimer=3600*1000*24; //*24hours minimum - use with 'c' comand (int=32 bit)
void startCameraServer(); //cameraInit() seems to initiate TwoWire(1) for CAM internal i2c
// *********************************************************************
void setup() {
Serial.begin(BAUD); //downloads with 115200+. Note: Mega still using 9600 for more reliable visual basic!
Serial.setDebugOutput(true);
Serial.println();
printf("\nHello\nBCD version No %d\n",BCDver); //my software version
printf("Total heap: %d\n", ESP.getHeapSize()); //356488 //BCD: added from esp32-how-to-use-psram
printf("Free heap: %d\n", ESP.getFreeHeap()); //331728 //set Arduino IDE Core Debug Level: "Verbose"
printf("Total PSRAM: %d\n", ESP.getPsramSize()); //4192139 //should output to SerialMonitor
printf("Free PSRAM: %d\n", ESP.getFreePsram()); //4.192139 for 4MB PSRAM
for (i=0;i<80*16*3;i++) Sensor666[i]=10; //ensure none remain at 0 or risk divide by 0 reboot.
camera_config_t config;
config.ledc_channel = LEDC_CHANNEL_0;
config.ledc_timer = LEDC_TIMER_0; //AIthinker No
config.pin_d0 = Y2_GPIO_NUM; //5
config.pin_d1 = Y3_GPIO_NUM; //18
config.pin_d2 = Y4_GPIO_NUM; //19
config.pin_d3 = Y5_GPIO_NUM; //21
config.pin_d4 = Y6_GPIO_NUM; //36
config.pin_d5 = Y7_GPIO_NUM; //39
config.pin_d6 = Y8_GPIO_NUM; //34
config.pin_d7 = Y9_GPIO_NUM; //35
config.pin_xclk = XCLK_GPIO_NUM; // 0 *****!
config.pin_pclk = PCLK_GPIO_NUM; //22
config.pin_vsync = VSYNC_GPIO_NUM; //25
config.pin_href = HREF_GPIO_NUM; //23
config.pin_sccb_sda = SIOD_GPIO_NUM;//26
config.pin_sccb_scl = SIOC_GPIO_NUM;//27
config.pin_pwdn = PWDN_GPIO_NUM; //32
config.pin_reset = RESET_GPIO_NUM; //-1
config.xclk_freq_hz = 20000000; //20MHz
pinMode(WEBPIN, INPUT_PULLUP); //startup() uses GPIO(14?) (LOW) to trigger WebServer (jpg) mode
EEPROM.begin(EEPROM_SIZE); //will need to load 80 (long)Sensor[] pointers from EPROM
delay(500); //just copying an EPROM example (why?)
i=EEPROM.readInt(EPvFlag); //if 1-2 initiate web server via wifi
if(i==2){ssid=ssid2; password=password2;}
printf("Video flag read as %d; Webpin %d\n",i,(1-digitalRead(WEBPIN)));
if(i==0){ //EPROM contains data so use stored parameters - restore threshold,nLED,min2flip,maxSensors
nLED=int(EEPROM.read(EPnLED));
if (nLED==255) nLED=1; //GPIO2 LED assigned to block 0, second nLED on GPIO14 pLED
threshold=int(EEPROM.read(EPthreshold)); //use 32-255 default: 42?
if(threshold==255) threshold=45;
min2flip=int(EEPROM.read(EPmin2flip)); //default: 2
if(min2flip==255) min2flip=2; //check for unprogrammed EPROM
maxSensors=int(EEPROM.read(EPmaxSensors)); //debug limit
printf("EEPROM set threshold = %d; nLED = %d; min2flip = %d; maxSensors =%d\n",\
threshold,nLED,min2flip,maxSensors);
}
for (j=0;j<80;j++) SensorTwin[j]=0; //initialise Twins, if none in eprom.
if((i==0) && (digitalRead(WEBPIN)==1)){ //if HIGH startup in RGB565 mode (else MyWebServer mode)
MyWebServer=false; //do not start MyWebServer
config.pixel_format = PIXFORMAT_RGB565; // YUV422|GRAYSCALE|RGB565|JPEG (see esp_camera.h)
// if PSRAM IC present, init with UXGA resolution and higher JPEG quality
// for larger pre-allocated frame buffer.
if(psramFound()){
config.frame_size = FRAMESIZE_QVGA; //QVGA|CIF|VGA|SVGA|XGA|SXGA|UXGA
config.jpeg_quality = 10; //0-63 0 for highest quality?
config.fb_count = 2;
IFD Spr("fb_count set to 2 as PSRAM found\n");
} else { //setup webserver mode
config.frame_size = FRAMESIZE_QVGA; //select optimum size 640 x 480 for Sensor (speed v resolution)
config.jpeg_quality = 12; //4 better quality BUT slows any conversion to BMP by ~10%
config.fb_count = 1; //investigate speed gain using 327kB fast dynamic RAM. for a 154k fb
}
pinMode(4,OUTPUT);digitalWrite(4,LOW); //turn off flash LED
pinMode(FLASHLED,OUTPUT); //to stop cycle flash set FLASHLED to another pin (33?)
pinMode(BLK0LED, OUTPUT);digitalWrite(BLK0LED,HIGH); //normal LED HIGH=off BUT (GPIO12 inverted. AVOID 12!)
pinMode(BLK1LED, OUTPUT);digitalWrite(BLK1LED,HIGH); //set up bank occupied indicators (GPIO 2,14,4,33)
pinMode(BLK2LED, OUTPUT);digitalWrite(BLK2LED,HIGH);
pinMode(BLK3LED, OUTPUT);digitalWrite(BLK3LED,HIGH);
pinMode(pLED, OUTPUT);digitalWrite(pLED,HIGH);
}else{ MyWebServer=true; //run CAM in webserver mode (no track Sensors)
pinMode(FLASHLED,OUTPUT);
flashLed(25); //pulse flash to show webserver mode initiation
//clear EEPROM Web flag and proceed to webserver
EEPROM.writeInt(EPvFlag,0); //clear flag at end of EEPROM
EEPROM.commit();
EEPROM.end(); //burn eeprom
config.pixel_format = PIXFORMAT_JPEG;
// if PSRAM IC present, init with UXGA resolution and higher JPEG quality
// for larger pre-allocated frame buffer.
if(psramFound()){
config.frame_size = FRAMESIZE_UXGA;
// if(ov7670) config.frame_size = FRAMESIZE_QVGA; //BCD mod for ov7670
config.jpeg_quality = 10;
config.fb_count = 2;
} else { //could print error as must have psram to operate fully
config.frame_size = FRAMESIZE_SVGA;
config.jpeg_quality = 12;
config.fb_count = 1;
}
} //end of else{ MyWebServer=true;
printf("Camera init attempt...\n");
esp_err_t err = esp_camera_init(&config); // camera init
if (err != ESP_OK) {
printf("Camera init failed with error 0x%x", err);
delay(10000);
ESP.restart();
}
MyWire.onReceive(i2cReceive); //set up response to receive & request
MyWire.onRequest(i2cRequest); //links to void i2cRequest()
bool MyWireOK=MyWire.begin((uint8_t)I2C_DEV_ADDR,I2C_SDA2,I2C_SCL2,0); //Slave needs no FREQ - Master's choice
if(MyWireOK) printf("MyWire.begin()INITIAISED OK\n");
else printf("MyWire.begin()FAILED\n");
#if CONFIG_IDF_TARGET_ESP32 //ESP32 needs a call to slaveWrite for Arduino compatibility
IFT printf("Doing slaveWrite() for ESP32\n"); //ESP32-S2 and ESP32-C3 don't need slaveWrite()
char message[64];
snprintf(message, 64, "E%u Packets.", i2cCtr++);//snprintf() puts the text "1 Packets" into char message[64]
message[0]=CAMERR;
MyWire.slaveWrite((uint8_t *)message, strlen(message)); //preload onRequest buffer
IFT printf("Done slaveWrite\n");
#endif
delay(4000);
Spr("Camera initialised ");Serial.println(esp_err_to_name(err));
sensor_t * s = esp_camera_sensor_get();
// drop down frame size for higher initial frame rate
s->set_framesize(s, FRAMESIZE_QVGA); //THIS IS THE FINAL SETTING
#if defined(CAMERA_MODEL_M5STACK_WIDE) || defined(CAMERA_MODEL_M5STACK_ESP32CAM)
s->set_vflip(s, 1);
s->set_hmirror(s, 1);
#endif
/***/ //using CAMERA_FB_IN_DRAM got figures below CLEARLY ASSIGNING FB TO DRAM!
/***/ printf("AFTER good camera_init and parameter setting & before WiFi.begin\n");
/***/ printf("Total heap: %d\n", ESP.getHeapSize()); //356488->356212 BCD: added from esp32-how-to-use-psram
/***/ printf("Free heap: %d\n", ESP.getFreeHeap()); //331492->258348 set Arduino IDE Core Debug Level: "Verbose"
/***/ printf("Total PSRAM: %d\n", ESP.getPsramSize()); //4192139 unchanged for DRAM. -> 4192127
/***/ printf("Free PSRAM: %d\n", ESP.getFreePsram()); //4.192139 MB!(DRAM) & 4153727 (PSRAM)(38400 for QQVGA)
if(MyWebServer){ //set up WiFi connection
//if WEBPIN held LOW at startup, MyWebServer=true so startup in MyWebServer mode
//(if GPIO0 held low at startup then automatically goes into flash program mode)
ConnectWifi(ssid, password); //WiFibegin()
if (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.println("WiFi failed to connect. Try alt-wifi (v2)?");
ESP.restart();
}
Serial.println("");
Serial.println("WiFi connected");
startCameraServer();
Spr("Camera Ready! To connect, use 'http://");
Serial.println(WiFi.localIP());
flashLed(250); //long pulse flash to show MyWebServer mode established
}
else{ //PROCEED TO SETUP SENSORS using RGB565 & QVGA
for (i=0;i<80;i++){ //set all to UNDEFINED, Brightness Sensor(0/0) defaults to offset 000 if not from EPROM
Sensor[i]=0L; //in absence of EPROM data set all sensors to offset 0 (i.e. Sensor UNDEFINED)
SensorActive[i]=false; //can set undefined sensors active as ALL sensors scanned at present anyway
SensorActiveBlk[i>>3]=0; //clear all Blk bytes also
}
for (i=0;i<80;i++){ //will need to load 80 (long)Sensor[] pointers from EPROM and 80 byte Twin values
Sensor[i]=EEPROM.readLong(i*4); //get a long (4byte) pointer from EEPROM.
SensorTwin[i]=EEPROM.read(EPSensorTwin+i);
pvtThreshold[i]=EEPROM.read(EPpvtThreshold+i); //if =255 then use default threshold
if ((Sensor[0]>153600L)||(Sensor[0]==0)) //153600L = size of image
Sensor[0]=(320*120+160)*2; //create first S00 as k00,120,160 (~centre)
else
if (Sensor[i]>153600L) Sensor[i]=0; //eliminates wild values (all 1's) from a pristine EPROM
if (SensorTwin[i]>79) SensorTwin[i]=0;
if (Sensor[i]!=0){ IFT printf("define Sensor[0x%x]= %lu \n",i,Sensor[i]);
SensorActive[i]=true; //initially activate all sensors defined (i.e. >0) in EPROM
SensorActiveBlk[i>>3]=SensorActiveBlk[i>>3] | mask[i&0x07];
} //Never obstruct Sensor[00]. If brightness SensorStat[00] ever goes true, we have a lighting problem
} //Note: if sensor[0]=0 (top left cell undefined) cmd 'r' will ignore it!
if(Sensor[0]==0){SensorActive[0]=true; SensorActiveBlk[0]|=0x01; Sensor[0]=1*8L;} //create ref sensor[0]@0,4
delay(5000); //give the cam settings time to settle then renew
Spr("Turn AWB & AGC OFF(0) after 5 sec. Leave auto adjustments (AWBg,AEC,AECd,AEL) ON(1)\n");
//set AWB,AEC,AGC OFF, or just AWB & CB OFF?? NEEDS MORE EXPERIMENTING
new_camera_settings(Bri,Con,Sat,AWB,AWBg,AEC,AECd,AEL,AGC,AGg); //set Sat,AWB,AEC,AGC,.. to defaults
AWB=AWB9; AGC=AGC9; //Ready for Turn Off AFTER prefillRef delay, say 9000mSec.
// new_camera_settings(0,1,2,0,1,1,1,1,0,9); after prefill delay
printf("\n*** WILL WAIT >10 SECONDS THEN AUTOMATICALLY LOAD ACTIVE SENSOR IMAGE REFERENCES\n");
printf(" ASSUMING ALL SENSORS UNOCCUPIED ***\n");
cFlag=millis(); //record end of setup() time - used to determine when prefillRef happens
}
for (i=0;i<(48*80);i++) averageSensor[i]=0; //init. av. ref accumulators for function averageRcalculation()
for (i=0;i<64;i++) { //initialise rolling average values for Sensor[00]
rollAvFrameR[i]=0; rollNsFrameR[i]=0;
rollAvFrameG[i]=0; rollNsFrameG[i]=0;
rollAvFrameB[i]=0; rollNsFrameB[i]=0;
}
} //end setup()
// *************************************************************************************************************
void loop() {
/***/ if(prefillRef>1) Serial.println(prefillRef);
// ****IF MYWEBSERVER MODE, JUST MONITOR SERIAL PORTS FOR RESET (R or F)
while(MyWebServer) { //monitor for character to trigger reboot back to sensor mode
if (Serial.available() > 0) { //if MyWebServer mode, do nothing but serve web and monitor for Reset cmd.
cmdChar=Serial.read();
if((cmdChar=='R') || (cmdChar=='F')) { //'F' gives Bluetooth ability to "Finish with webserver"
Serial.println("Restarting in sensor mode\n");
WiFi.disconnect(true, true);
delay(3000);
ESP.restart(); //give up on wifi and return to sensor mode
}else Spr(cmdChar);
if((cmdChar=='g') || (i2cCmd[0]=='g')){ //get camera status
i2cCmd[0]='.';
getCAMstatus();
}
} //end if (Serial.available())
if ((i2cCmd[0]=='R') || (i2cCmd[0]=='F')) ESP.restart(); //exit back to sensor mode
} //end MyWebserver code
// ***CALCULATE AND PRINT LOOP TIME - & IF CALLED FOR, UPDATE NEW CAMERA SETTINGS
/***/ IFT Spr("\n******** ");
int loopTime=millis()-timerLoop;
if(loopTime==99) loopTime=100; //add 1 just to stop display jitter with 99
timerLoop=millis();
if(loopTime<100)Spr(' ');
/***/ IFS{ Spr(loopTime); Spr("mS; "); }
HistoLoops++; //increment loop counter
if (stdCtimer < millis()) { //time to invoke new camera settings after 'c' std delay
stdCtimer=1000*3600*24*10; //set timer too large to ever trip again. Long 2^31 > 2,147,000,000,000
new_camera_settings(Bri,Con,Sat,AWB,AWBg,AEC,AECd,AEL,AGC,AGg); //set Sat,AWB,AEC,AGC,.. select AWB OFF
cFlag=millis(); //Time stamp Flag for loop() to reload all references after cDelay sec of flushing frames
}
// ***TAKE A FULL FRAME INTO fb IN RGB565 FORMAT
/***/ IFD Serial.println(" Try a get_picture "); // Get Picture from Camera. New image starts when fb released.
/***/ IFT timer=micros(); // Images are "pipelined" so images are 2 "gets" old. (160mSec)
camera_fb_t * fb = NULL; //transfer rgb565 image #1 into fb (taken
fb = esp_camera_fb_get(); //get most recent frame into fb (and return pointer?)
if(!fb) { Serial.println("Camera capture #1 failed\n"); return;} //no pic fb ? - restart program loop
/***/ IFT{ Spr(timer);timer=micros()-timer;//does camera_fb_get return error if .jpg not yet ready, or does it wait
/***/ Spr(" fb_get time:"); Serial.println(timer); //zStr);
/***/ }
fbheight=fb->height;
/***/ IF0 printf(" got RGB565 frame. fbheight %d fb->len %d\n",fbheight,fb->len);
// ****DECODE RGB565 FRAME INTO COMPACT SENSOR FRAME OF RGB666 FORMAT
/***/ IFT timer=micros();
int pitch=RGB565p*fb->width; //set pitch for RGB565 image format
imagePtr=fb->buf;
bool converted = f565to666(imagePtr,pitch,Sensor,Sensor666,SensorActive); //decode rgb565 to rgb666 in Sensor666
/* int f565to666( byte *imageData,int pitch, long *sensor, byte *f666,bool *SensorActive) */
/***/ IFT{ timer=micros()-timer; Spr("Decode (uS):"); Serial.println(timer);}
if (!converted) {Spr("Conversion to rgb666 failed\n"); return;}
/***/ IFD printf("should now be rgb666 in Sensor666[]\n");
// ****SEE IF IMAGE DUMP (TO PROCESSING4)CALLED FOR & DUMP AS REQUESTED BEFORE NEW FRAME INITIATED
if(Yhold) count=0; //reset command string index & prepare for a new command following recent y#
while(Yhold){ //do not proceed with loop() until Yhold cleared by "yy\n" command
if(SendYpacket){
imageFB = fb->buf; //set a ptr to start of image buf
if(boxSensors){ //put a box around active sensors in image
for (int sen=0; sen<maxSensors; sen++) { //limit to first maxSensors
if (SensorActive[sen]) boxIt(Sensor[sen],sen); //box 4x4 sensor at Sensor[] location
}
boxSensors=false; //don't do again - only on first 'y' command of series
}
j = Xcolumn*2 + Yrow*320*2; //starting point in data //then send an ASCII header
/***/ Spr('\n');Spr('y');Spr(Yrow);Spr('x');Spr(Xcolumn);Spr('z');Spr(Zlength);Spr(";\n"); //faster than printf()
Yheader[1]=byte(Yrow); Yheader[3]=byte(Xcolumn/2); Yheader[5]=byte(Zlength/2);
uint32_t Ycksum=0; //Calculate checksum
for (i=0;i<6;i++) Ycksum += int(Yheader[i]); //does not include chsum or ':' in summation
for (i=j;i<(j+Zlength*2);i+=2) {
if(!imageFB[i]) imageFB[i] = 0x20; //tweak to data so no NUL bytes by adding LSB to even green
if(!imageFB[i+1]) imageFB[i+1]= 0x08; //ditto red (Note: 0x08 = BS backspace)
Ycksum += imageFB[i]; Ycksum += imageFB[i+1];
}
Yheader[6]=byte(Ycksum & 0x00FF); Yheader[7]=byte(Ycksum >>8);
i=Serial.write(Yheader,9); //write 9 header bytes
if(i != 9) printf("Y header write error %d\n",i);
i=Serial.write(&imageFB[j],Zlength*2); //write data
if(i != Zlength*2) printf("Y data write error %d\n",i);
SendYpacket=false;
}
while (Serial.available() > 0) { //each command is followed by a fresh loop to "get_image"
cmdChar=Serial.read();
cmdString[count]=cmdChar; //add it to cmd string
count+=1;
count=constrain(count,0,20); //increment & limit string length
if (cmdChar == '\n'){ //don't process cmdString until get LF (\n)
processCmd(imagePtr,pitch,Sensor); //process command line
count=0; //reset command string index
}
}
} //end of while(Yhold)
// ****BEFORE DISCARDING RGB565 fb FRAME, CHECK IF NEEDED TO PROCESS A sCAN or cALIBRATE COMMAND REQUEST
if (sScan>=0){ // scan full frame for a bright new Sensor[sScan] offset position
sScanCount -=1; // decrement frame count to flush pipeline
if(sScanCount==0){
bright_spot = scan( imagePtr, pitch,fb->width, fbheight, RGB565p); //check imageData for a bright spot
Sensor[sScan] = bright_spot - long(2 * RGB565p + 2 * pitch); //point to first byte top left corner
printf("Scan: New Sensor[0x%X] (%d/%d) bright pixel position: row=%d column(x)=%d offset %lu\n",\
sScan,sScan>>3,sScan&7,int(bright_spot/pitch),int(bright_spot%pitch)/2,bright_spot);
printf(" YOU MUST DO an r%d%d TO RECORD NEW IMAGE REFERENCE DATA *AFTER* removing brightspot and \
restoring light level.\n",sScan/8,sScan%8);
printf(" Press Enter for fresh frames (DO NOT DO r%%%% here)\n");
sScan=-1; // clear request. //NOTE: printed pixel pos'n is the bright_spot NOT the Sensor corner pos'n
wait(); // (sensor corner reduced by 2*pitch+4)
}
}
// ****DO A STEP TOWARDS AVERAGING Sensor[bsNo] IF COUNTER SET.
refRefresh(autoRefSuspended); //try to auto refresh a bsNo reference (if not tripped)
if (averageRcounter>0) averageRcalculation(averageRbsn,AVCOUNT); //update average ref[bsn] process ('r')
// ****CALCULATE AVERAGE FOR Sensor[00] AND AVERAGE NOISE (& PEAK NOISE?)
/* GLOBAL definitions (for reference)
nt frameNo=0;
int Sensor666Av[48];
int rollAvFrameR[64]; //calculate rolling average value of Sensor[00]
int rollAvFrameG[64];
int rollAvFrameB[64];
int rollNsFrameR[64]; //calculate rolling average noise
int rollNsFrameG[64];
int rollNsFrameB[64];
int rollAvNoise[64]; //calculate rolling average noise
int rollAvR=0; int rollAvG=0; int rollAvB=0;
int rollAv=0;;
int rollNsR=0; int rollNsG=0; int rollNsB=0;
int r00AvR; int r00AvG; int r00AvB;
int r00NsR; int r00NsG; int r00NsB;
int Noise=0; //difference between frame pixel and ref pixel
*/
frameNo += 1; frameNo &= 0x3F; //inc. frame counter
if(frameNo==1) { //every 64th frame update Sensor_ref[00] & print 4x4 new av & current red(noisiest?) values
/***/ IFN Spr("av&cur Red ref[0] "); //NOTE THIS DOES NOT UPDATE BRIGHT OR COLOUR RATIOS!
for (i=0;i<48;i++) { //48 = 4x4pixels*3colours
Sensor_ref[i]=Sensor666Av[i]>>6; //update Sensor_ref[0/0] every 6.4 seconds (64 frames) (av = sum/64)
/***/ IFN{ Spr('&');Spr(Sensor_ref[i]); } //print whole ref (DEC)
/***/ IFN if(i%3==0) {Spr(" [");Spr(i);Spr("]:");Spr(Sensor_ref[i]);Spr(' ');\
/***/ Spr(Sensor666[i]);} //print (DEC) one (red) colour of new sensor_ref[00] & actual[00]
Sensor666Av[i]=0; //initialise for next 64 frames
}
/***/ IFN Spr("\n......."); //try to keep next line aligned.
}
//compute new cratios etc for Sensor[00] latest av. reference UPDATE sensor_ref(00) every 6.4sec to new average
if(frameNo==2) { //grab_ref() includes calculation of new Cratios & brightness
grab_ref(0,Sensor_ref,S666_pitch,0L,&Sensor_ref[00],&Sen_Brightness_Ref[00],&SensorRefRatio[00]);
Sen_Brightness_Ref[00]=r00Av; //Sum of (grab_ref)rounded averages is < Average of sum, use r00Av instead.
/***/ IFN {Spr("adjusted _Brightness_ref[00] to Average "); Serial.println(Sen_Brightness_Ref[00]);}
}
int sumR=0; int sumG=0; int sumB=0; //calculate new bright(00) ( <= 48*63 (3024max))
for (i=0;i<48;i+=3){ //calculate colour averages (NB. not colour ratios!)
sumR=sumR+Sensor666[i]; sumG=sumG+Sensor666[i+1]; sumB=sumB+Sensor666[i+2]; //calculate rolling averages
Sensor666Av[i] += Sensor666[i]; //calculate individual pixel colour averages for _ref
Sensor666Av[i+1]+= Sensor666[i+1];
Sensor666Av[i+2]+= Sensor666[i+2];
}
//calculate whole sensor rolling averages (sumR already contains sum of R in current frame)
rollAvR=rollAvR+sumR-rollAvFrameR[frameNo]; rollAvFrameR[frameNo]=sumR; r00AvR=rollAvR>>6; //av.of Sensor[00]
rollAvG=rollAvG+sumG-rollAvFrameG[frameNo]; rollAvFrameG[frameNo]=sumG; r00AvG=rollAvG>>6;
rollAvB=rollAvB+sumB-rollAvFrameB[frameNo]; rollAvFrameB[frameNo]=sumB; r00AvB=rollAvB>>6;
r00Av = (rollAvR+rollAvG+rollAvB)>>6; //div by 64 frames to get 0-3024 (48*0x3F) av. s00 frame sum (bright)
sumR=0; sumG=0; sumB=0;
for (i=0;i<48;i+=3){ //calculate av noise
//calculate sum of noise squared for CURRENT 4x4 frameNo. aiming for RMS equivalent
Noise = Sensor666[i] - Sensor_ref[i]; sumR=sumR+Noise*Noise;
Noise = Sensor666[i+1] - Sensor_ref[i+1]; sumG=sumG+Noise*Noise;
Noise = Sensor666[i+2] - Sensor_ref[i+2]; sumB=sumB+Noise*Noise;
} //sumR is now sum from 16 pixels in one frame
rollNsR=rollNsR+sumR-rollNsFrameR[frameNo]; rollNsFrameR[frameNo]=sumR; r00NsR=rollNsR>>6; //calculate rolling average noise on Sensor[00]
rollNsG=rollNsG+sumG-rollNsFrameG[frameNo]; rollNsFrameG[frameNo]=sumG; r00NsG=rollNsG>>6; //divide by 64 (frames)
rollNsB=rollNsB+sumB-rollNsFrameB[frameNo]; rollNsFrameB[frameNo]=sumB; r00NsB=rollNsB>>6; //also divide by 16 to get av/pixel
IFN printf("s00 roll'n brAv=%d, noisAv=%d %d %d s",r00Av,r00NsR,r00NsG,r00NsB); //SHOULD THESE BE /16 as 16 pixels in every r00Ns%
// ****FOR FLUORESCENT LIGHTING TRY TO SYNCHRONISE release/start image WITH 50Hz MAINS USING INTERNAL CLOCK
// ****DELAY TO REDUCE FRAMES PER SECOND FROM one/80mS to one/100mSec FOR PSRAM 25% IDLE (DE-STRESS) TIME.
while ((micros()-starttime)>CYCLETIME) {starttime += CYCLETIME;} //bring it back into sync if loop is running late.
while ((micros()-starttime)<CYCLETIME) {;} //kill any remaining spare time before 50/60Hz sync.
starttime += CYCLETIME;
// above should sync with mains just before releasing fb to be refilled.
// this is based on the premise that the mains frequency is very accurate, stable and doesn't drift noticeably!
// also assumes ov2640 is restrained to run at 10Hz (This may not be true - suspect free runs at ~13Hz!)
/***/ IFT {Spr(starttime);Spr("uSec ");}
/***/ IFT timer=micros();
esp_camera_fb_return(fb); //release jpg camera image frame buffer - starts new frame capture?
//sensor images now in 3 byte format in array Sensor666[]
/***/ IFT{ Spr("50Hz fb return(uS) "); Serial.println(timer);}
// ****IF CALLED FOR BY c####, OR STARTUP(), DO A FULL REFERENCE UPDATE FOR ALL ACTIVE SENSORS
//if cFlag is not 0 then all defined sensors should be prefilled from good stable unoccupied image, i.e.AFTER
// CAM has run for > prefillRef frames.
if (cFlag!=0) prefillRef = S666init_SensorRefs(prefillRef); //Sensor_refs[],related brightness,cRatios etc.
/***/ //Print fb data sample //NOTE this is AFTER release of fb so use saved decoded Sensor666[] images!!!
/***/ IFD write_img_sample(Sensor666,S666_row,0x08*48,24); //write current image of sensor[00] AND Sensor[bsn(1/0)]
/* void write_img_sample(byte *imagePtr,int pitch,long offset,int nBytes ) */
// ****NOW WANT TO CLEAR CAMERA PIPELINE & START NEW IMAGE CAPTURE FOR NEXT LOOP
/***/IFT timer=micros();
// WITH FAST rgb565 decode above, it isn't worthwhile trying to clear out pipeline AS WAS NEEDED WITH jpg TO bmp
// ****DO COMPARE OF ALL SENSORS WITH THEIR REF'S, DECIDE IF OCCUPIED & IF SO SET STATUS.
/***/IFT timer=micros();
//at start of program preload sensor_ref[] automatically after (prefillRef) images
// LOOP UNTIL prefillRef frames acquired at which time S666init_SensorRefs(prefillRef) will load ref. images.
if(prefillRef>0){ //at start of program there is time to kill before ref.images are auto grabbed
/***/ if (prefillRef>1) { //during initial countdown, output some debug images to see changes (if obvious)
/***/ IF0 for (i=0;i<3;i++){ printf("Sensor_ref[]:"); //print out first 3 SensorRefs
/***/ for (int j=0;j<48;j++) printf(" %x",Sensor_ref[i*48+j]); printf("\n");
/***/ }
/***/ IF0 printf("Quad: %u %u %u %u quad[4]:%u\n",quad[0],quad[1],quad[2],quad[3],quad[4]);
/***/ }
/***/ IFD for (int i=0;i<3;i++){ //print Cratios for first 3 sensors
/***/ printf("\nbsNo %d SensorRatios: ",i); for (int j=0;j<4;j++)printf(" %3d %3d %3d :",\
SensorRefRatio[i*12+j*3],SensorRefRatio[i*12+j*3+1],SensorRefRatio[i*12+j*3+2] );
/***/ } printf("\n");
}else{ // DO NORMAL LOOP COMPARISON
i2cDatai=1; //index for i2c message data entry - leave [0] for 't'
avOddFrame=!avOddFrame; //flip flag for av2frames() execution
for(bsn=0;bsn<80;bsn++){ if(bsn==1)timer=micros(); //leave bsn==0 out of timing to see effect.
if (SensorActive[bsn]){ //skip compare if Sensor NOT active! (inactive sensors can't
// auto update/track brightness drift (& saves time))
// ****USE 2 image average for compare if bsNo < TWOIMAGE_MAXBS as a test for improved algorithm
if (bsn < TWOIMAGE_MAXBS) av2frames(bsn); //do 2xframe av. if active & bsNo < 2/0 & compare current S666 image
maxDiff= compare( Sensor666, S666_row, long(bsn*S666_pitch), &SensorRefRatio[bsn*12], bsn, RGB565p);
processDiff(bsn); //make a decision on whether to set occupied or not and set status[bsn] accordingly
//processDiff recomputes bright & sets/resets all sensor status flags
if (bsn==dbsNo) {dMaxDiff=maxDiff; dBright=bright*brightSF;} //save for delivery on dflag or i2c request
}
if(bsn==0){ //special treatment for brightness reference sensor[00]
/***/ IFD {Spr("bsNo 0/0 maxDiff: ");Serial.println(maxDiff);}
refBrightness=Sen_Brightness_Ref[0]; //a refBrightness from last time ALL references updated (startup, 'r00' or 'c' only)
refActual=quad[4]; //save refActual in case use later
/***/ IFT { Spr(refBrightness);Spr(" refBrightness > actual ");Serial.println(refActual);} //sensor[00]
else IFS{ Spr(" T");Spr(threshold);Spr(" N");Spr(nLED);Spr(" R");
Spr(refBrightness);Spr(" A");Spr(refActual);
Spr(" M");Spr(min2flip);Spr(" B");Spr(brightSF);Spr("\t"); //short & no \n
}
bsn=minSensors;
} //end if(bsn==0)
} //end for(bsn=0->80)
i2cData[1]=byte(threshold); //replace first i2c byte (bsn=0?) with threshold (don't want NULL bsn at start!
i2cData[i2cDatai]=0x50; //put block "80." to flag end of i2cdata[] (max valid bsn=79.)
i2cData[i2cDatai+1]=0x00; //put a NULL on end of data message to end transmission
timer=micros()-timer;
/***/ IFT {Spr("Compare(uS): "); Spr(timer,DEC);}
if(bsnUpdated>=0) { IFS{ Spr("Ref 0");Spr(bsnUpdated,OCT);} bsnUpdated=-1;}
IFS Serial.println(""); //was consistently getting same No. from (timer,DEC) & (zStr) (until neg nos)
// ****CHECK DFLAG AND OUTPUT REQUESTED INFO. (BUG - doesn't look like it prints requested dbsNo DIFFERENCES only LAST active sensor leftover?)
//if dFlag==0 just output diffs. if dFlag >0 output image table and repeat dFlag times
if(dFlag>=0) {
printf("Diff: bsNo %d/%d diff %d bright %d diff+bright %d\n",dbsNo>>3,dbsNo&7,dMaxDiff,dBright,dMaxDiff+dBright);
// also need to send something to i2c - do this in i2cRequest() if last i2cReceive was 'd'
dFlag-=1; //decrement towards -1
if(dFlag>=0){ //d%%n sets dFlag so can output debug sample images for bsNo '%%' on each of 'n' loops
write_img_sample(Sensor666,S666_row,long(dbsNo*48),24); //write for visual check what is being compared.
wait(); //give user chance to read
}
} //end if(dFlag) code
} //end (normal loop comparisons) (prefillRef==0)
// ****FLASH A LED ON EACH LOOP. Use FLASHLED pin
if (FLASHLED==4) flashLed(20); //flash White led on IO4 briefly (~uSec)
else { digitalWrite(FLASHLED, LOW); delay(10); digitalWrite(FLASHLED, HIGH); } //delelays loop time!
// ****CHECK FOR USB COMMAND INPUT - PROCESS ANY COMMAND
/***/ IFd 7) if (SensorBlockStat[h7block] != 0){Spr("debug-trip in Block:");Serial.println(h7block);wait();} //wait if Block # occupied
if(aRefCtr>0){aRefCtr--; //do we need to do a ref for new sensor yet?
if(aRefCtr==1){
cmdString[0]='r'; cmdString[1]=absNo1; cmdString[2]=absNo2; cmdString[3]='\n';
count = 3;
cmdChar = '\n';
processCmd(imagePtr,pitch,Sensor); //process command line
count=0; //reset command string index
}
}
if(newi2cCmd==true){
/***/ IFI2C {Serial.print("I2C:");Spr(char(i2cCmd[0]));}
for (i=0;i<64;i++)cmdString[i] = i2cCmd[i]; //copy i2cCmd to cmdString
/***/ IFT {Spr("****************i2cCmd: ");Spr(i2cCmd);}
if(cmdString[0]=='w') newi2cCmd=false; //this ensures 'w' actually waits - other i2c commands DON'T WAIT
processCmd(imagePtr,pitch,Sensor); //process command line
if(cmdString[0]!='w') newi2cCmd=false; //if() lets newi2cCmd cancel 'w' without being discarded itself.
}
else while (Serial.available() > 0) { //each command is followed by a fresh loop to "get_image"
cmdChar=Serial.read();
cmdString[count]=cmdChar; //add it to cmd string
Spr(cmdChar); //immediate echo back to monitor
count+=1;
count=constrain(count,0,20); //increment & limit string length
if (cmdChar == '\n'){ //don't process cmdString until get LF (\n)
Serial.println("**************"); // return a new line to monitor
processCmd(imagePtr,pitch,Sensor); //process command line
count=0; //reset command string index
}
}
/***/ //output the reference images sensor_Ref[00], and sensor_Ref[02] for diagnostic purposes from the captured Sensor666 array
/***/ IF0 disp_sample_pixels( Sensor666, S666_row, 4 * (01) , 1*16, 24); //dispX/16 will move multiples of bsNo
/* if using Sensor666 use: ( Sensor666, S666_row, 4*bsNo, dispX, 24) 24=2sensors*3bytes*4pixels, dispX/16 can jump multiples of bsNo
void disp_sample_pixels(byte *imagePtr,int pitch,int row,int dispX,int nBytes) //nBytes is # per line (12 per sensor)
*/
IF0 delay(1000); //delay a second for serial.print debug to catch up?
} //end of main loop
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// PROCESS A COMMAND STRING FROM USER
void processCmd(byte *imagePtr,int pitch,unsigned long *Sensor){ //process string in cmdString
//imagePtr is a pointer to the location of image Data NOTE no longer used by processCmd !
//pitch is the number of bytes in a full line of fb imagePtr
//Sensor is a pointer to the Sensor array of pointers into the image Data
int b=0; //local (block) variable
int c, i, j; //j: sensor index
int param2; // second cmd argument
int bsn;
/***/ IFI2C for(i=0;i<12;i++) Spr(char(cmdString[i]));
if(cmdString[1]==' ') for(i=1;i<62;i++) cmdString[i]=cmdString[i+1];
switch(cmdString[0]) {
case 'a':{ //a%%; enAble Sensor[bsn] and get fresh reference (only do if UNOCCUPIED!)
bsn = get_bsNo(cmdString); absNo1=cmdString[1]; absNo2=cmdString[2]; //save bsNo digits
if(bsn<0) { printf("(a%%%%,rrr,xxx) enAble Sensor(%%%%)[& sets new coordinates for it]\n"); break; }
if(cmdString[3]==','){
int rowVal=int(get_number(&cmdString[3]));
if(cmdString[3]!=',' || rowVal<0 || rowVal>239) printf("invalid rowValue\n");
else {
i=7;
if(cmdString[5]==',') i=5;
if(cmdString[6]==',') i=6;
int xVal=get_number(&cmdString[i]);
if(xVal<0 || xVal>319) printf("invalid xValue\n");
else{
Sensor[bsn]=pitch*rowVal + 2*xVal;
printf("(a%%%%,rrr,xxx)setting new coordinates for Sensor[%d/%d] to r:%d x:%d\n",bsn>>3,bsn&7,rowVal,xVal);
aRefCtr=4; //get # fresh frames then take ref for sensor[aRef]
}
}
wait();
}
if (bsn>=0) { printf("(a%%%%) enAble: sensor %d/%d with fresh reference - bsNo 0%o\n",bsn>>3,bsn&7,bsn);
SensorActive[bsn]=true; //set active and grab new ref
SensorActiveBlk[bsn>>3] |= mask[bsn&0x07];
grab_ref(bsn,Sensor666,S666_pitch,long(bsn*S666_pitch),&Sensor_ref[bsn*S666_pitch],&Sen_Brightness_Ref[bsn],&SensorRefRatio[bsn*12]);
//includes new Cratios & brightness //NOTE WELL: without new frames, an new reference won't be much use with new coordinates!
/***/ printf("updated Sensor_ref[] HEX bytes(0-5)(BGRBGR): %x %x %x %x %x %x \n",Sensor_ref[bsn*S666_pitch],Sensor_ref[bsn*S666_pitch+1],Sensor_ref[bsn*S666_pitch+2],Sensor_ref[bsn*S666_pitch+3],Sensor_ref[bsn*S666_pitch+4],Sensor_ref[bsn*S666_pitch+5]);
} break;
}
case 'b':{ //b$; Block status byte output to host on USB (& i2c) //b$# use # to change brightSF from 1 to 4
if (!isDigit(cmdString[1])) {
Spr("(b$#) Bank/BrightSF: parameters not found - brightSF(#): ");Serial.println(brightSF);
}
else {
if (isDigit(cmdString[2])){ //if b$%; use % to change brightSF from 1 to 3
brightSF=cmdString[2]-0x30;
Spr("(b$#) changing brightSF sensitivity to "); Serial.println(brightSF);
}
b = cmdString[1]-0x30; //ignore any further text after b$
if(!newi2cCmd){
Spr("(b$#)bank: ");Spr(b);Spr(" occupancy status byte: 0x");Spr(SensorBlockStat[b],HEX);Spr(" (sensors 7-0) ");
for (i=7;i>=0;i--) Spr((SensorBlockStat[b]>>i) & 0x01); Serial.println(' ');
}
else IFI2C {for (i=7;i>=0;i--) Spr((SensorBlockStat[b]>>i) & 0x01); Serial.println(' '); }
}
break;
} //c$$$$#####; set $=1 to leave AWB AEC AGC & CB respectively.
case 'c':{ //c$$$$; Calibrate: turn on/off Auto adjustments for 10seconds and reload all references!
//6x % parameters are 1/0 for AWBg AEC AECd AEL AGC AGg respectively ( default to off(0) )
//full list: arguments for 'c' command; Bri Con Sat AWB AWBg AEC AECd AEL AGC AGg (NO SPACES!)
printf("(c$$$####$##) caution recommended - USE CMD 'r00' after 15 seconds or just use 'j' ( $= _|-|0|1|2 )\n");
Bri=0; Con=1; Sat=2; AWB=1; AWBg=1; AEC=0; AECd=0; AEL=1; AGC=1; AGg=9; //reset defaults
/* if(cmdString[1]<0x20) printf("CALIBRATE CAM - syntax: c AWB AEC AGC CBar Bri Con Sat AGCgain AWBg e.g. reset defaults: c01200129\n"); */
if(cmdString[1]<0x20){
printf("Calibrate CAM - syntax: c Bri Con Sat AWB AWBg AEC AECd AEL AGC AGgain e.g. reset defaults: c0121100119\n");
wait();
break;
}
autoRefSuspended = true; //Suspend auto refRefresh() during 'c'
cStr=" ";
if (isDigit(cmdString[1])) Bri=cmdString[1]-0x30; //options: use 0,1,2,'-' for -1 and '_' for -2
else {if (cmdString[1]=='-') Bri=-1; if (cmdString[1]=='_') Bri=-2;}
Spr(" Bri=");Spr(Bri); cStr=cStr+cmdString[1];
if (isDigit(cmdString[2])) Con=cmdString[2]-0x30;
else {if (cmdString[2]=='-') Con=-1; if (cmdString[2]=='_') Con=-2;}
Spr(" Con=");Spr(Con); cStr=cStr+cmdString[2];
if (isDigit(cmdString[3])) Sat=cmdString[3]-0x30;
else {if (cmdString[3]=='-') Sat=-1; if (cmdString[3]=='_') Sat=-2;}
Spr(" Sat=");Spr(Sat); cStr=cStr+cmdString[3];
if (cmdString[4]=='1') {AWB=1; Spr(" AWB "); cStr=cStr+" AWB ";} else AWB=0;
if (cmdString[5]!='\n') {
if(cmdString[11]!='\n'){Spr(" missing arguments\n"); break;}
if (isDigit(cmdString[5])){AWBg=cmdString[5]-0x30; Spr(" AWBg=");Spr(AWBg); cStr=cStr+cmdString[5];}
if (cmdString[6]=='1') {AEC =1; Spr(" AEC "); cStr+=" AEC ";}else {AEC=0; cStr+=" ";}
if (cmdString[7]=='1') {AECd=1; Spr(" AECd ");cStr+=" AECd ";}else AECd=0;
if (isDigit(cmdString[8])) AEL=cmdString[8]-0x30; //options: use 0,1,2,'-' for -1 and '_' for -2
else {if (cmdString[8]=='-') AEL=-1; if (cmdString[8]>'_') AEL=-2;}
Spr(" AEL=");Spr(AEL); cStr=cStr+' '+cmdString[8];
if (cmdString[9]=='1') {AGC =1; Spr(" AGC "); cStr+=" AGC ";}else {AGC=0; cStr+=" ";}
if (isDigit(cmdString[10])) {AGg=cmdString[10]-0x30; Spr(" AGCgain=");Spr(AGg); cStr=cStr+cmdString[10];}
}
Spr(" \n");
new_camera_settings(Bri,Con,Sat,AWB,AWBg,AEC,AECd,AEL,AGC,AGg); //set AWB,AEC,AGC select Auto ON initially
// loop will do following call after 10 seconds and delay a further 10 before doing ref updates
// new_camera_settings(Bri,Con,Sat,AWB,AWBg,AEC,AECd,AEL,AGC,AGg); //set AWB,AEC,AGC,ColourBar select AWB OFF
Spr("Calibrate: run 10 seconds for auto calibrate before turning selected 'Auto' options off ");Serial.println(cStr);
prefillRef=1; //set to 1 should suppress normal loop data stream until end of stdCtimer
getCAMstatus(); //also print in 'g' format
Serial.println(" NOTE Calibration operation 'c' has NOT FULLY FOLLOWED THROUGH - assumes SensorStat[] & SensorBlockStat[] will automatically reset");
printf("After a new line, calibrate will take ~20 seconds to execute and refresh references\n");
wait();
stdCtimer=millis()+10000; //start a timer - wait 10sec for adjustments to settle automatically then turn off AGC say
break; //leaves flags set for main loop() to calibrate from fresh images.
}
case 'd':{ //d%%#; Differences between Sensor_ref[bsNo] and imagePtr. (optional n=No. of repetitions of image) (& i2c)
if(!isDigit(cmdString[1])) {
printf("(d%%%%#) Diff score for S%%%%. Optional repeat # times including frame samples \n ");
wait();
break;
}
dFlag=0; //set a (+ve) flag for loop() to print out data after it gets a fresh image repeating if desired.
if(isDigit(cmdString[3])) dFlag=int(get_number(&cmdString[2])); //set repeat count up to 9999
Spr("(d%%#) Diff: ");
dbsNo = get_bsNo(cmdString);
if (dbsNo>=0) {
printf(" difference score for Sensor[0%o], use %d consecutive images (remember pipelining!)\n",dbsNo,dFlag);
printf("will print data AFTER new line - follow with 'w' command to pause to read\n");
} else dFlag=-1;
wait();
break; //leaves flag for loop() to complete print output.
}
case 'e':{ //e; EPROM burn any changes to Sensor[] & some other parameters
printf("(e)EPROM: Save latest threshold (%d), min2flip (%d), nLED (%d), maxSensors (%d), Sensor[] SensorTwin[] & pvtThreshold to EPROM\n"\
,threshold,min2flip,nLED,maxSensors);
EEPROM.write(EPnLED,byte(nLED));
EEPROM.write(EPthreshold,byte(threshold));
EEPROM.write(EPmin2flip,byte(min2flip));
EEPROM.write(EPmaxSensors,byte(maxSensors));
printf("EPROM: Save latest sensor pointers to EPROM along with thresholds, nLED, min2flip & maxSensors\n");
for (i=0;i<80;i++) { //put changed values for SensorTwin[] & Sensor[] in EEPROM
EEPROM.writeLong(i*4,Sensor[i]);
EEPROM.write(EPSensorTwin+i,SensorTwin[i]);
EEPROM.write(EPpvtThreshold+i,pvtThreshold[i]);
}
EEPROM.commit(); //ESP32 method
EEPROM.end(); //burn eeprom
delay(500);
EEPROM.begin(EEPROM_SIZE); //restart EEPROM for any further EEPROM commands (e.g. e or v).
wait();
break;
}
case 'f':{ //f%%; Frame: image of bsNo sensor output to USB (if sensor[bsNo] undefined, will get no data)
Spr("(f%%) ");
bsn = get_bsNo(cmdString);
printf("Frame: Print fb ref & newest sample for S%o\n",bsn);
if (bsn>=0) write_img_sample(Sensor666,S666_row,bsn*48,24); //write current image of sensor[bsn] ref & new data
wait();
break;
}
case 'g':{ //get OV2640 camera status and print it to USB
getCAMstatus();
wait();
break;
}
case 'j':{ //j$#; Set one camera parameter($) directly to digit(#) (5=-1,6=-2)
Spr("(j$#) adJust camera setting $ to # (NO auto new Ref's)\n");
new_camera_set(cmdString[1],cmdString[2]); //if invalid $, print list of valid parameters
getCAMstatus(); // display new settings, as for (g) cmd
wait();
break;
}
case 'h':{ //h$; Help sets DEBUG flags to true (or false). ALSO "h$%" set maxSensors to %x4 (4-36)
if (isDigit(cmdString[1])){ //h$ turns on debug of type $
dbug=cmdString[1]-0x30;
DEBUG[dbug]=true;
if (dbug==8) E6counter=200;
if ((dbug==7) && isDigit(cmdString[2])){
h7block=cmdString[2]-0x30;