//
// Main.c
//
// DSD KEYPAD
// Exclusively Optical Keyboard
//
// Revision: SW=2 21-Aug-2021 for SchemRev=3
// Ben Koning (benakoning@gmail.com)
// Redwines Garage
//
// This is the firmware for a fully-optical keypad that uses reflectance
// of IR LEDs through a plexiglas legend, so that it can be mounted on the
// inside of a transparent waterproof pressure housing. It does not need
// any kind of pressure or capacitance feed-through per key site.
//



//
// The header files and the initial boilerplate version of this source file
// was created using the Microchip, Inc. Code Configurator ("MCC"), which
// creates all the .h and .c files for hardware access for the specific chip
// in use. Now imported into MPLAB X IDE (local IDE).
//
// Also the standard xc.h. And stdint/stdbool for uint8_t, etc.
//

#include <xc.h>
#include "mcc_generated_files/mcc.h"
#include "mcc_generated_files/interrupt_manager.h"
#include <stdint.h>
#include <stdbool.h>



//
// KNOBS -- TUNING CONSTANTS
//
// These constants are for empirically adjusting the various thresholds
// and score additions. Even with these, the hope is to use cardinal values
// as much as possible.
//
//      Symbol                   Val   NomiVal   Range   Description
//      ----------------------   ---   -------   -----   -----------
#define kTC_SoftwareVersion        2   //    0     255   Blinked at start.
#define kTC_SunMode_ReRate    0x1FFF   // 1FFF    16b&   Int bf choose sun mode.
#define kTC_SunModeThresh        600   //  600    1023   Above ambient sun mode.
#define kTC_SunMode_BlinkChg       0   //    0       1   Blink 5/20 if moon/sun.
#define kTC_Meas_BaseLnReRate     50   //   50   65535   Int bf re-baseline.
#define kTC_Score_Mn_BrNss_Thr   120   //   80    1023   Syndrome moonlit brigh-
#define kTC_Score_Mn_BrNss_Inc    25   //   15    6535   terness and inc amount.
#define kTC_Score_Mn_BrDk_ThrB   100   //  100    1023   Syndrome dark/medium -
#define kTC_Score_Mn_BrDk_ThrD    70   //   10    1023   alternate syndrome -
#define kTC_Score_Mn_BrDk_Inc     10   //    5    6535   some additional inc.
#define kTC_Score_Sn_Flood_Cfm   700   //  600    1023   Extra full sun enable.
#define kTC_Score_Sn_DkNss_Thr    15   //   15    6535   Syndrom for full sun
#define kTC_Score_Sn_DkNss_Inc   400   //  100    6535   have dk meas darker.
#define kTC_Score_Sn_DkSsn_Thr     4   //    5    6535   Syndrome for full sun
#define kTC_Score_Sn_DkSsn_Inc   400   //  100    6535   have dk meas be bright.
#define kTC_Score_ShowBaseLine     0   //    0       1   Show u16 base of V key.
#define kTC_Score_ShowProbeDk      0   //    0       1   Show u16 LED-OFF V key.
#define kTC_Score_ShowProbeBr      0   //    0       1   Show u16 LED-ON V key.
#define kTC_Peaks_ScoreThresh     40   //   20   65535   Consider for peak find.
#define kTC_Peaks_SharpDelta     250   //   15   65535   Min slope req on peak.
#define kTC_Histo_GenRate     0x000F   //   7F    16b&   Int bf scores->histo.
#define kTC_Histo_ActRate     0x000F   //   7F    16b&   Int bf histo->SeeIfKey.
#define kTC_Histo_ValToAct        20   //    3     255   Peaks hist to finger.
#define kTC_Debow_Disable          0   //    0       1   Disable debouncer.
#define kTC_Debow_ResetRate   0x1FFF   //  7FF    16b&   Time bf reset db cmp.
#define kTC_Tap_Blink1             0   //    0       1   Blink debug LED if hit.
#define kTC_Tap_BlinkKey           0   //    0       1   Blink debug LED key#Ns.
#define kTC_Tap_SerialTest         0   //    0       1   Serial port tester.
#define kTC_Legend_DipDimTm      850   //  600   65535   LGD "shift key" 140uS.
#define kTC_Legend_BlinkDimTm    400   //  600   65535   LGD "emerg key" 140uS.
#define kTC_Legend_ShortDimTm    300   //  600   65535   LGD "all keys" 140uS.



//
// ---------------------------------------------------------------------------
//
// HARDWARE DESCRIPTION
//
// Microchip PIC 16F188555 pin usage:
//
// MCU - PIN  - SIG - DIR - ELECTRICAL AT MCU - FUNCTION [ - FUNCTION ]
// ---   ----   ---   ---   -----------------   -----------------------
// RA0 - 0002 - RT0 - OUT - DRIVEHIGH         - IRLEDANDIRPHOTOGNDRETURN_0
// RA1 - 0003 - RT1 - OUT - DRIVEHIGH         - IRLEDANDIRPHOTOGNDRETURN_1
// RA2 - 0004 - RT2 - OUT - DRIVEHIGH         - IRLEDANDIRPHOTOGNDRETURN_2
// RA3 - 0005 - RT3 - OUT - DRIVEHIGH         - IRLEDANDIRPHOTOGNDRETURN_3
// RA4 - 0006 - RT4 - OUT - DRIVEHIGH         - IRLEDANDIRPHOTOGNDRETURN_4
// RA5 - 0007 - RT5 - OUT - DRIVEHIGH         - IRLEDANDIRPHOTOGNDRETURN_5
// RA6 - 0010 - RT6 - OUT - DRIVEHIGH         - IRLEDANDIRPHOTOGNDRETURN_6
// RA7 - 0009 - RT7 - OUT - DRIVEHIGH         - IRLEDANDIRPHOTOGNDRETURN_7
// RB0 - 0021 - LB0 - OUT - DRIVEHIGH         - IRLED280PULSEBANK_0
// RB1 - 0022 - LB1 - OUT - DRIVEHIGH         - IRLED280PULSEBANK_1
// RB2 - 0023 - LB2 - OUT - DRIVEHIGH         - IRLED280PULSEBANK_2
// RB3 - 0024 - LB3 - OUT - DRIVEHIGH         - IRLED280PULSEBANK_3
// RB4 - 0025 - LB4 - OUT - DRIVEHIGH         - IRLED280PULSEBANK_4
// RB5 - 0026 - LB5 - OUT - DRIVEHIGH         - IRLED280PULSEBANK_5
// RB6 - 0027 - CPD - INO - MICROCHIP         - PROGRAMMER
// RB7 - 0028 - CPC - INO - MICROCHIP         - PROGRAMMER
// RC0 - 0011 - ADC - INP - ANALOGVDDFS       - SELECTEDPHOTOBANKPOSTOPAMP
// RC1 - 0012 - LGD - OUT - DRIVEHIGH         - LEGENDWHITELEDTOFULLBRIGHT
// RC2 - 0013 - TST - OUT - DRIVEHIGH         - TESTDEBUGLED
// RC3 - 0014 - XXX - XXX - XXX               - UNUSED
// RC4 - 0015 - TXD - OUT - EUSART            - SERIALOUTPUTDATA
// RC5 - 0016 - PA0 - OUT - DRIVEHIGH         - PHOTOSENSEBANKSELECTADDR_0
// RC6 - 0017 - PA1 - OUT - DRIVEHIGH         - PHOTOSENSEBANKSELECTADDR_1
// RC7 - 0018 - PA2 - OUT - DRIVEHIGH         - PHOTOSENSEBANKSELECTADDR_2
//
// KEYBOARD SCANNING ARRANGEMENT
//
// 48 Keys; Spatial arrangement 12 Horizontal Columns, 4 Vertical Rows.
// The 12 columns are due to 6 horizonal banks with a pair of columns each.
// 6 Horizontal Banks (MOSFET P-ch high-side low-resistance drivers).
// Each Bank has a Bank LED select which enables 2 physical columns of 4 LEDs
// each. That is, 8 LEDs per Bank LED select enable. A Bank LED select supplies
// Vplus to a group of 8 LEDs.
// Upon CPU Reset, all 6 banks are turned OFF without software intervention.
// 8 Ground Returns (MOSFET N-ch low-side low-resistance drivers).
// A Ground Return supplies Vgnd to an individual LED in a Bank.
// To turn a LED on, enable both its Bank and enable its Ground Return.
// If the high-current jumper is installed, software must turn the LED off
// within 100us (microseconds) to avoid catastrophic LED damage.
// A Ground Return also supplies Vgnd to co-located phototransistors (PT)s
// for the corresponding LED position. This is among all 6 banks.
// All PTs have a constant Vplus supply.
// 1-of-6 Sense Select Switch (very low loss electronic switch).
// The output of the switch is buffered and supplied to a 10-bit ADC.
// In order to select a specific PT for sensing, there is a 1-of-6 switch.
// To sense a PT, enable its Ground Return and select its Switch, and then
// start the ADC conversion process, and then turn the LED back off, making
// sure the time taken is always less than 100uS. If the conversion takes
// longer you must abort it, turn the LED off, and be content with no answer.
// More light input to the PTs yields lower ADC values and vice versa, so
// software should invert the values for easier interpretation.
//
// KEYBOARD LAYOUT
//
// N=KeyNumber, LB=LEDBank, RT=LEDPTReturn, CH=SenseChannel.
//
// N00   N04   N08   N12   N16   N20   N24   N28   N32   N36   N40   N44
// LB0   LB0   LB1   LB1   LB2   LB2   LB3   LB3   LB4   LB4   LB5   LB5
// RT0   RT4   RT0   RT4   RT0   RT4   RT0   RT4   RT0   RT4   RT0   RT4
// PB0   PB0   PB1   PB1   PB2   PB2   PB3   PB3   PB4   PB4   PB5   PB5
// 1B    3B    3A    25    26    7C    2D    2B    2F    2A    3D    7F
// 1B    31    32    33    34    35    36    37    38    39    30    7F
// ESC   ;     :     %     &     |     -     +     /     *     =     DEL
// ESC   1     2     3     4     5     6     7     8     9     0     DEL
//
// N01   N05   N09   N13   N17   N21   N25   N29   N33   N37   N41   N45
// LB0   LB0   LB1   LB1   LB2   LB2   LB3   LB3   LB4   LB4   LB5   LB5
// RT1   RT5   RT1   RT5   RT1   RT5   RT1   RT5   RT1   RT5   RT1   RT5
// PB0   PB0   PB1   PB1   PB2   PB2   PB3   PB3   PB4   PB4   PB5   PB5
// F3    51    57    45    52    54    59    55    49    4F    50    0D
// F3    71    77    65    72    74    79    75    69    6F    70    0D
// EMG   Q     W     E     R     T     Y     U     I     O     P     RTN
// EMG   q     w     e     r     t     y     u     i     o     p     RTN
//
// N02   N06   N10   N14   N18   N22   N26   N30   N34   N38   N42   N46
// LB0   LB0   LB1   LB1   LB2   LB2   LB3   LB3   LB4   LB4   LB5   LB5
// RT2   RT6   RT2   RT6   RT2   RT6   RT2   RT6   RT2   RT6   RT2   RT6
// PB0   PB0   PB1   PB1   PB2   PB2   PB3   PB3   PB4   PB4   PB5   PB5
// F2    41    53    44    46    47    48    4A    4B    4C    EF    22
// F2    61    73    64    66    67    68    6A    6B    6C    E9    27
// EMO   A     S     D     F     G     H     J     K     L     FRN   "
// EMO   a     s     d     f     g     h     j     k     l     SML   '
//
// N03   N07   N11   N15   N19   N23   N27   N31   N35   N39   N43   N47
// LB0   LB0   LB1   LB1   LB2   LB2   LB3   LB3   LB4   LB4   LB5   LB5
// RT3   RT7   RT3   RT7   RT3   RT7   RT3   RT7   RT3   RT7   RT3   RT7
// PB0   PB0   PB1   PB1   PB2   PB2   PB3   PB3   PB4   PB4   PB5   PB5
// F1    5A    58    43    56    20    42    4E    4D    3C    3E    21
// F1    7A    78    63    76    20    62    6E    6D    2C    2E    3F
// SFT   Z     X     C     V     SPC   B     N     M     <     >     !
// SFT   z     x     c     v     SPC   b     n     m     ,     .     ?
//
// 48 Keys; Spatial arrangement 12 Columns, 4 Rows.
// Enumeration ID zero-based; up-to-down inner, then left-to-right outer.
//
// PHYSICAL CONSTRUCTION
//
// The following is a cross section. The top (outer) layer is the external
// (e.g. waterproof "pressure") hull; the bottom is the PC board.
//
//       HumanFinger
// |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| EXT HULL
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ILLUM.LED~~~~~~~~ KEY LEGEND
// ______________________________________________________________ IR FILTER
// .............................................................. DIFFUSER
// --------|  | |-----|  | |-----|  | |-------------------------- MASK
// --------LED-PT-----LED-PT-----LED-PT-------------------------- IR LED/PT
// ============================================================== PCB
//           ^          ^          ^          ^          ^        MOUNTING
//
// The infrared light emitting diodes (LED) and infrared phototransistors (PT)
// are of the fast (20ns) response type. There is one pair per photosite
// (keysite). A 4mm thick black ABS plastic mask layer ensures little or no
// crosstalk betweeen adjacent keysites and funnels the light upwards and
// downwards. Above the mask layer is a diffusion layer which causes a rapid
// dropoff in finger reflectance response with increasing distance from the
// key legend and external hull surface. Above the diffuser layer is a visually
// black but infrared-transparent filter sheet, which shields the PTs from
// non-infrared, and gives a black visual background for the key legend layer.
// Above the IR filter layer is the key legend layer, a 3mm thick clear acrylic
// layer etched with the key cap legend glyphs. This layer is illuminated by
// 10 white perimeter LEDs and functions as a lightpipe for the etched "keys".
// This entire assembly should then be mounted against the transparent pressure
// hull of the application.
//
// LIMITATIONS
//
// This keypad is not designed for use in direct or indirect sunlight.
// This keypad is not designed for use by gloved hands.
//
// ---------------------------------------------------------------------------
//



//
// Main chip configuration (fuse) bits. This is metadata programmed into
// the chip (to select major modes) alongside the software code. The legal
// key+value symbols for these, which are very similar but slightly different
// from the datasheet, and I'm not sure if the compiler gives an error if
// you assign a bogus value, seem to be defined in "16f18855.cfgdata" in
// mplabx.app/XXX/packs/Microchip/PIC16F1xxxx_DFP/XXX/xc8/pic/dat/cfgdata:
//

// CONFIG1
#pragma config FEXTOSC = OFF        // External Oscillator mode selection bits
                                    // ->Oscillator not enabled
#pragma config RSTOSC = HFINT32     // Power-up default value for COSC bits
                                    // ->HFINT32 HFINTOSC OSCFRQ=32MHz,CDIV=1:1
#pragma config CLKOUTEN = OFF       // Clock Out Enable bit
                                    // ->CLKOUT disabled; IO/osc func on OSC2
#pragma config CSWEN = ON           // Clock Switch Enable bit
                                    // ->NOSC,NDIV can be changed by software
#pragma config FCMEN = ON           // Fail-Safe Clock Monitor Enable bit
                                    // ->FSCM timer enabled
// CONFIG2
#pragma config MCLRE = ON           // Master Clear Enable bit
                                    // ->MCLR pin is Master Clear function
#pragma config PWRTE = ON           // Power-up Timer Enable bit
                                    // ->PWRT disabled=code start imm aft reset
#pragma config LPBOREN = OFF        // Low-Power BOR enable bit
                                    // ->ULPBOR disabled
#pragma config BOREN = ON           // Brown-out reset enable bits
                                    // ->Brown-out Reset Enabled; SBOREN ignored
#pragma config BORV = HI            // Brown-out Reset Voltage Selection
                                    // ->>BOR Thresh Hi (2.7V) (HI recc >16MHz)
#pragma config ZCD = ON             // Zero-cross detect disable
                                    // ->Zero-cross detect is disabled at POR
#pragma config PPS1WAY = ON         // Peripheral Pin Select one-way control
                                    // ->PPSLOCK clr/set only once in software
#pragma config STVREN = ON          // Stack Overflow/Underflow Reset Enable bit
                                    // ->Stack Under/Over will cause a reset
// CONFIG3
#pragma config WDTCPS = WDTCPS_31   // WDT Period Select bits
                                    // ->Div ratio 1:65536; software ctl WDTPS
#pragma config WDTE = OFF           // WDT operating mode
                                    // ->WDT Disabled, SWDTEN is ignored
#pragma config WDTCWS = WDTCWS_7    // WDT Window Select bits
                                    // ->full open; sw ctl; keyed access not req
#pragma config WDTCCS = SC          // WDT input clock selector
                                    // ->Software Control
// CONFIG4
#pragma config WRT = OFF            // UserNVM self-write protection bits
                                    // ->Write protection off
#pragma config SCANE = available    // Scanner Enable bit
                                    // ->Scanner module is available for use
#pragma config LVP = ON             // Low Voltage Programming Enable bit
                                    // ->LV pgm enabled; MCLR/Vpp pin is MCLR
// CONFIG5
#pragma config CP = OFF             // UserNVM Program mem code protection bit
                                    // ->Program Memory code protection disabled
#pragma config CPD = OFF            // DataNVM code protection bit
                                    // ->Data EEPROM code protection disabled



//
// Software shadows of registers so we can cache them for writing less often.
// Note these do not need to be decorated as volatile but the registers do.
//

volatile uint8_t    gsPORTA = 0x00;
volatile uint8_t    gsPORTB = 0x00;
volatile uint8_t    gsPORTC = 0x00;



void setup_hardware_configuration ()
{   
    for (volatile uint16_t i = 0; i < 5; i++) {}
    
    //           76543210    // GPIO PORT A - used for Vgnd posn return
    TRISA    = 0b00000000;   // TristateOn:  0=Output/1=Input
    ODCONA   = 0b00000000;   // OutputDrive: 0=TTL/1=CurrSink
    SLRCONA  = 0b00000000;   // OutputSlew:  0=Norm/1=Slower
    LATA     = 0b00000000;   // InputSource: 0=Input/1=FromOut
    ANSELA   = 0b00000000;   // InputDAType: 0=Dig/1=Analog
    WPUA     = 0b00000000;   // InputPullUp: 0=No/1=WeakPullUp
    INLVLA   = 0b00000000;   // InputSample: 0=TTL/1=Schmitt
    gsPORTA  = 0b00000000;   // Init cache to all Nch-"deselected"
    PORTA    = gsPORTA;      // First write to the output pins
    
    //           76543210    // GPIO PORT B - used for V+ bank drive and etc
    TRISB    = 0b00000000;   // TristateOn:  0=Output/1=Input
    ODCONB   = 0b00000000;   // OutputDrive: 0=TTL/1=CurrSink
    SLRCONB  = 0b00000000;   // OutputSlew:  0=Norm/1=Slower
    LATB     = 0b00000000;   // InputSource: 0=Input/1=FromOut
    ANSELB   = 0b00000000;   // InputDAType: 0=Dig/1=Analog
    WPUB     = 0b00000000;   // InputPullUp: 0=No/1=WeakPullUp
    INLVLB   = 0b00000000;   // InputSample: 0=TTL/1=Schmitt
    gsPORTB  = 0b00000000;   // Init cache to all !->Pch deselected
    PORTB    = gsPORTB;      // First write to the output pins
    
    //           76543210    // GPIO PORT C - used for photo-sel and etc
    TRISC    = 0b00000001;   // TristateOn:  0=Output/1=Input
    ODCONC   = 0b00000000;   // OutputDrive: 0=Norm/1=CurrSink
    SLRCONC  = 0b00000000;   // OutputSlew:  0=Norm/1=Slower
    LATC     = 0b00000000;   // InputSource: 0=Input/1=FromOut
    ANSELC   = 0b00000001;   // InputDAType: 0=Dig/1=Analog
    WPUC     = 0b00000000;   // InputPullUp: 0=No/1=WeakPullUp
    INLVLC   = 0b00000000;   // InputSample: 0=TTL/1=Schmitt
    gsPORTC  = 0b00000000;   // Init cache to PhBk#0,LegendModeBright,DebugOff
    PORTC    = gsPORTC;      // First write to the output pins
    
    for (volatile uint16_t i = 0; i < 5; i++) {}
    
    //           76543210    // 10-BIT ADC - used to measure photo-site lumas
    ADCON1   = 0b00000000;   // 765PreChCtl,0DoubleSample; Clr 0
    ADCON2   = 0b00000000;   // 7Flt2Prv 654CalCtl 3AC 210Mod; Clr 7,3; 210:=#0
    ADCON3   = 0b00000000;   // 654Mod 3AutoTrigSOI 210ThMod; 654:=#0 210:=#0
    ADCLK    = 0b00000000;   // 543210FoscDivFactor; 543210:=#0
    ADREF    = 0b00000000;   // 4NegRef 10PosRef; 4:=#0=Vss 10:=#0=Vdd
    FVRCON   = 0b00000000;   // VRef 7En 6Rdy 54T 32vCP 10vAD; Clr 7; 10:=#0=Off
    ADPCH    = 0b00010000;   // 543210PosInChSel; 543210:=0b010000=PC0 for input
    ADPRE    = 0b00000000;   // 76543210PrechClksOr0Skip; :=#0=NoPreChStep
    ADACQ    = 0b00000000;   // 76543210AcqTimeOr0Skip; :=#0=NoAcqStep
    ADCAP    = 0b00000000;   // 76543210AddInternalCapAtInput; :=#0=0pF
    ADCON0   = 0b10010100;   // 7Ena,6Cont,4FRCclk,2RJst,0Go; Set 7,4,2; Clr 6,0
    
    for (volatile uint16_t i = 0; i < 5; i++) {}
    
    //           76543210    // EUSART SERIAL PORT - used to send chars out
    RC4PPS   = 0x10;         // PeripheralPortSelect: Map TX (TxD) to Port C4
    TX1STA   = 0b00100100;   // 7SCk,6Tx9,5TxEn,4Sync,3Bk,2BdH,1TxRdy,0D9; Set52
    BAUD1CON = 0b00001000;   // 7AutoOv,6RxIdl,4Pol,3Bd16,1WUE,0AutoEn; Set3
    SP1BRGL  = 0x40;         // SerialPort 1 BaudRate16GenLo8; 9600->832->LE->
    SP1BRGH  = 0x03;         // SerialPort 1 BaudRate16GenHi8; ->0x40,0x03
    RC1STA   = 0b10000000;   // 7SerialEn,6Rx9,5SEn,4CEn,3AEn,2Fe,1Oe,0Ry9; Set7
}



//
// Whether we are currently doing the key-legend-backlight dip-animation or
// blink-animation or not. States (gLegendLightAnimationState) are:
//
// 0 -- Quiescent.
// 1 -- Set by master software to immediately (ungracefully) stop.
// 2 -- Set by master software to begin dip-to-dim-and-back one-shot.
// 3 -- Set by master software to begin perpetual dip-to-dim blinking.
// 4 -- Set by master software to begin short dip-to-dim-and-back one-shot.
// 20, 30, 31, 40, etc -- Internal states used to do the animation.
//

#define kLegendLightCommand_Stop                 1
#define kLegendLightCommand_DipOnce              2
#define kLegendLightCommand_BlinkCont            3
#define kLegendLightCommand_BlinkShort           4

uint8_t             gLegendLightAnimationState = 0;
uint16_t            gLegendLightAnimationTimer = 0;



//
// Whether we are currently in shifted mode on the keyboard or not; also
// whether we are currently in emergency mode.
//

bool                gCurrentCaseUpper = false;
bool                gCurrentCaseEmerg = false;



//
// Master mode whether we are in bright sunlight or not.
//

uint8_t             gModeMoonlit0Sunlit1 = 0;



//
// Master clock that keeps incrementing 0..65535 and repeats.
//

uint16_t            gClock16 = 0;



//
// Error code. On entry to any routine that might "return" (set) an error,
// this value is always pre-set to 0 at the beginning of the routine,
// so that a successful routine run will aways result in this being cleared.
// If error, then a positive nonzero semi-unique code is set.
//

uint8_t             gError = 0;



//
// Inputs to the low level illuminate and sample routine, which are also
// inputs & outputs to the increment and scan routines as far as the phase
// of operations changes over time.
//
// Coordinates (bank-0-to-5 and returnCurrent-0-to-7) of where the focus is
// that we will look at, what key is at those coordinates (keyNumber-0-to-47),
// and the dark-or-bright sample phase is (LED-dark=0-or-LED-bright=1-ON).
//
// These always point to the current one. The positions are not bumped until
// you call the increment routine, and the phase must always be set by you.
// You should set them based on the gKeyPhases-based phase for that particular
// key. In this way, we can stagger what phase each key is in.
//

uint8_t             gNowVddBank = 0;
uint8_t             gNowGndReturn = 0;
uint8_t             gNowKey = 0;

uint8_t             gNowDark0Bright1 = 0;

uint8_t             gNowPhase = 0;
uint16_t            gNowPhase8To4Or0Count16 = 0;

uint8_t             gKeyPhases [48];
uint16_t            gKeyPhases3To2Or0Count16 [48];



//
// Output from the low-level illuminate and sample routine.
//
// The gSampleOutMeasuredLuma value is what the last measured value was.
//

uint16_t            gSampleOutMeasuredLuma = 0;



//
// Outputs from the medium-low-level sample capture routines.
//

uint16_t            gKeyLumasForBaselineDarkMeas16 [48];
uint16_t            gKeyLumasForBaselineBrightMeas16 [48];
uint16_t            gKeyLumasForProbingDarkMeas16 [48];
uint16_t            gKeyLumasForProbingBrightMeas16 [48];



//
// Outputs from the medium-level likelihood score analysis routines.
//

uint16_t            gKeyDownScores16 [48];



//
// Outputs from the medium-high-level peak detection routines.
//

uint8_t             gKeyDownPeakHits [48];



//
// Output from the high-level final debounce routines.
//

uint8_t             gLastDownKeyNumber = 99;



//
// Approximate delay; errs on the short side and has about a 10% error for
// longer (multi-hundreds) delay, and 50% error on the longer side for shorter
// (dozens or less) delays. Use sleep_ms instead wherever possible.
// 
// This works for oscillator settings for 32MHz. Do not have interrupts
// or WDT running.
//
#define SLEEP_US(inN) { \
    for (volatile uint16_t i = (inN >> 2) | 1;   i > 0;   i--) \
    { i ^= 1;   i ^= 1;   i ^= 1;   i ^= 1; } \
}



void sleep_ms (uint16_t inMilliseconds)
//
// This works for oscillator settings for 32MHz. This has been calibrated
// pretty carefully with an oscilloscope. We can replace this with something 
// more timer-using if desired. But otherwise, we must not have interrupts or
// WDT running because of the hard LED-OFF deadlines we must not violate.
//
{
    for (volatile uint16_t i = 0;   i < inMilliseconds;   i++)
    {
        for (volatile uint8_t j = 0;   j < 102;   j++)
        {
            for (volatile uint8_t k = 7;   k > 0;   k--)   {}
        }
    }
}



//
// RTx - "Ground Return" - Writing a 1 connects RTx to ground. Writing a 0
// 0..7                    causes them to be quiescent (tri-state hi-Z). These
//                         "Ground Returns" supply ground drain for both the
//                         selected LED bank as well as the same PT bank.
//                         In SchemRev 2, RT0..RT7 is allocated to PA0..PA7.
//
//                                76543210
// Desired config:   TRISA   &= 0b00000000 -- TristateOn:  0=Output/1=Input
//                   ODCONA  &= 0b00000000 -- OutputDrive: 0=TTL/1=CurrSink
//                   SLRCONA &= 0b00000000 -- OutputSlew:  0=Norm/1=Slower
//                   LATA    &= 0b00000000 -- InputSource: 0=Input/1=FromOut
//                   ANSELA  &= 0b00000000 -- InputDAType: 0=Dig/1=Analog
//                   WPUA    &= 0b00000000 -- InputPullUp: 0=No/1=WeakPullUp
//                   INLVLA  &= 0b00000000 -- InputSample: 0=TTL/1=Schmitt
//                   gsPORTA &= 0b00000000 -- Init to all Nch-"deselected"
//
//                                    76543210
#define RT0_DESELECT   { gsPORTA &= 0b11111110; PORTA = gsPORTA; SLEEP_US (5); }
#define RT0_SELECT     { gsPORTA |= 0b00000001; PORTA = gsPORTA; SLEEP_US (5); }
#define RT0_SWITCH     { gsPORTA =  0b00000001; PORTA = gsPORTA; SLEEP_US (5); }
//                                    76543210
#define RT1_DESELECT   { gsPORTA &= 0b11111101; PORTA = gsPORTA; SLEEP_US (5); }
#define RT1_SELECT     { gsPORTA |= 0b00000010; PORTA = gsPORTA; SLEEP_US (5); }
#define RT1_SWITCH     { gsPORTA =  0b00000010; PORTA = gsPORTA; SLEEP_US (5); }
//                                    76543210
#define RT2_DESELECT   { gsPORTA &= 0b11111011; PORTA = gsPORTA; SLEEP_US (5); }
#define RT2_SELECT     { gsPORTA |= 0b00000100; PORTA = gsPORTA; SLEEP_US (5); }
#define RT2_SWITCH     { gsPORTA =  0b00000100; PORTA = gsPORTA; SLEEP_US (5); }
//                                    76543210
#define RT3_DESELECT   { gsPORTA &= 0b11110111; PORTA = gsPORTA; SLEEP_US (5); }
#define RT3_SELECT     { gsPORTA |= 0b00001000; PORTA = gsPORTA; SLEEP_US (5); }
#define RT3_SWITCH     { gsPORTA =  0b00001000; PORTA = gsPORTA; SLEEP_US (5); }
//                                    76543210
#define RT4_DESELECT   { gsPORTA &= 0b11101111; PORTA = gsPORTA; SLEEP_US (5); }
#define RT4_SELECT     { gsPORTA |= 0b00010000; PORTA = gsPORTA; SLEEP_US (5); }
#define RT4_SWITCH     { gsPORTA =  0b00010000; PORTA = gsPORTA; SLEEP_US (5); }
//                                    76543210
#define RT5_DESELECT   { gsPORTA &= 0b11011111; PORTA = gsPORTA; SLEEP_US (5); }
#define RT5_SELECT     { gsPORTA |= 0b00100000; PORTA = gsPORTA; SLEEP_US (5); }
#define RT5_SWITCH     { gsPORTA =  0b00100000; PORTA = gsPORTA; SLEEP_US (5); }
//                                    76543210
#define RT6_DESELECT   { gsPORTA &= 0b10111111; PORTA = gsPORTA; SLEEP_US (5); }
#define RT6_SELECT     { gsPORTA |= 0b01000000; PORTA = gsPORTA; SLEEP_US (5); }
#define RT6_SWITCH     { gsPORTA =  0b01000000; PORTA = gsPORTA; SLEEP_US (5); }
//                                    76543210
#define RT7_DESELECT   { gsPORTA &= 0b01111111; PORTA = gsPORTA; SLEEP_US (5); }
#define RT7_SELECT     { gsPORTA |= 0b10000000; PORTA = gsPORTA; SLEEP_US (5); }
#define RT7_SWITCH     { gsPORTA =  0b10000000; PORTA = gsPORTA; SLEEP_US (5); }
//                                    76543210
#define RTX_DESELECT   { gsPORTA &= 0b00000000; PORTA = gsPORTA; SLEEP_US (5); }



//
// LBx - "LED Bank/Burst" - Writing a 1 means quiescent. Writing and keeping a
// 0..5                     1 causes a steady 280mA amount (70mA if high-current
//                          jumper not installed) to drive LED bank x, which
//                          should drive the one LED in that bank that you
//                          should have selected with RTx. You can then do your
//                          photo measurement. You should then turn the LED off
//                          by writing a 0 here. If doing high current, you
//                          absolutely MUST turn the LED off BEFORE 100uS has
//                          elapsed, else you will damage the LED(s). The LED
//                          can only handle 100mA continuous; 500mA 100uS peak.
//                          The LED used here is the Vishay VSMB294008G. These
//                          LBx bits are allocated to PB0..PB5.
//
// ----------------------------------------------------------------------------
// WARNING !!               YOU MUST NOT HAVE AN LED TURNED ON FOR MORE THAN
// POSSIBLE HW DAMAGE       100uS (one hundred microseconds), ELSE YOU WILL
//                          DESTROY THE SELECTED LED OR LEDS. (If jumper set.)
// ----------------------------------------------------------------------------
//
// CAUTION                  A stuck-ON LED condition must be avoided at all
//                          costs if you have the 280mA jumper option selected.
//                          Be careful that your code is deterministic. Do not
//                          use interrupts or careless possible-forever-running
//                          polling loops for peripherals such as the ADC.
//
//                                76543210
// Desired config:   TRISB   &= 0b11000000 -- TristateOn:  0=Output/1=Input
//                   ODCONB  &= 0b11000000 -- OutputDrive: 0=TTL/1=CurrSink
//                   SLRCONB &= 0b11000000 -- OutputSlew:  0=Norm/1=Slower
//                   LATB    &= 0b11000000 -- InputSource: 0=Input/1=FromOut
//                   ANSELB  &= 0b11000000 -- InputDAType: 0=Dig/1=Analog
//                   WPUB    &= 0b11000000 -- InputPullUp: 0=No/1=WeakPullUp
//                   INLVLB  &= 0b11000000 -- InputSample: 0=TTL/1=Schmitt
//                   gsPORTB &= 0b11000000 -- Init to all !->Pch deselected
//
//                                    76543210
#define LB0_DESELECT   { gsPORTB &= 0b11111110; PORTB = gsPORTB; SLEEP_US (2); }
#define LB0_SELECT     { gsPORTB |= 0b00000001; PORTB = gsPORTB; SLEEP_US (2); }
#define LB0_SWITCH     { gsPORTB &= 0b11000000; \
                         gsPORTB |= 0b00000001; \
                           PORTB  = gsPORTB; SLEEP_US (2); }
//                                    76543210
#define LB1_DESELECT   { gsPORTB &= 0b11111101; PORTB = gsPORTB; SLEEP_US (2); }
#define LB1_SELECT     { gsPORTB |= 0b00000010; PORTB = gsPORTB; SLEEP_US (2); }
#define LB1_SWITCH     { gsPORTB &= 0b11000000; \
                         gsPORTB |= 0b00000010; \
                           PORTB  = gsPORTB; SLEEP_US (2); }
//                                    76543210
#define LB2_DESELECT   { gsPORTB &= 0b11111011; PORTB = gsPORTB; SLEEP_US (2); }
#define LB2_SELECT     { gsPORTB |= 0b00000100; PORTB = gsPORTB; SLEEP_US (2); }
#define LB2_SWITCH     { gsPORTB &= 0b11000000; \
                         gsPORTB |= 0b00000100; \
                           PORTB  = gsPORTB; SLEEP_US (2); }
//                                    76543210
#define LB3_DESELECT   { gsPORTB &= 0b11110111; PORTB = gsPORTB; SLEEP_US (2); }
#define LB3_SELECT     { gsPORTB |= 0b00001000; PORTB = gsPORTB; SLEEP_US (2); }
#define LB3_SWITCH     { gsPORTB &= 0b11000000; \
                         gsPORTB |= 0b00001000; \
                           PORTB  = gsPORTB; SLEEP_US (2); }
//                                    76543210
#define LB4_DESELECT   { gsPORTB &= 0b11101111; PORTB = gsPORTB; SLEEP_US (2); }
#define LB4_SELECT     { gsPORTB |= 0b00010000; PORTB = gsPORTB; SLEEP_US (2); }
#define LB4_SWITCH     { gsPORTB &= 0b11000000; \
                         gsPORTB |= 0b00010000; \
                           PORTB  = gsPORTB; SLEEP_US (2); }
//                                    76543210
#define LB5_DESELECT   { gsPORTB &= 0b11011111; PORTB = gsPORTB; SLEEP_US (2); }
#define LB5_SELECT     { gsPORTB |= 0b00100000; PORTB = gsPORTB; SLEEP_US (2); }
#define LB5_SWITCH     { gsPORTB &= 0b11000000; \
                         gsPORTB |= 0b00100000; \
                           PORTB  = gsPORTB; SLEEP_US (2); }
//                                    76543210
#define LBX_DESELECT   { gsPORTB &= 0b11000000; PORTB = gsPORTB; SLEEP_US (2); }



//
// PBx - "Photo Bank Select" - A 3-bit address selects which phototransistor
// #0..#5                      bank is connected to the buffer and into the ADC.
//                             In SchemRev 2, PB(PhotoBank)0..5 is selected by
//                             a value (#0..#5, #6ignored, #7ignored), selected
//                             by a 3-bit address allocated to the PC5..PC7
//                             outputs. Note in the code below that the
//                             PhotoBank0 switch code fragment logically doesn't
//                             need to do the write ("|="), but we do it anyway,
//                             to hopefully keep the timing same/similar.
//
//                                76543210
// Desired config:   TRISC   &= 0b00011111 -- TristateOn:  0=Output/1=Input
//                   ODCONC  &= 0b00011111 -- OutputDrive: 0=Norm/1=CurrSink
//                   SLRCONC &= 0b00011111 -- OutputSlew:  0=Norm/1=Slower
//                   LATC    &= 0b00011111 -- InputSource: 0=Input/1=FromOut
//                   ANSELC  &= 0b00011111 -- InputDAType: 0=Dig/1=Analog
//                   WPUC    &= 0b00011111 -- InputPullUp: 0=No/1=WeakPullUp
//                   INLVLC  &= 0b00011111 -- InputSample: 0=TTL/1=Schmitt
//                   gsPORTC &= 0b00011111 -- Init select PhotoBank#0 to ADC
//
//                                    76543210
#define PB0_SWITCH     { gsPORTC &= 0b00011111; \
                         gsPORTC |= 0b00000000; \
                           PORTC  = gsPORTC;   SLEEP_US (2); }
//                                    76543210
#define PB1_SWITCH     { gsPORTC &= 0b00011111; \
                         gsPORTC |= 0b00100000; \
                           PORTC  = gsPORTC;   SLEEP_US (2); }
//                                    76543210
#define PB2_SWITCH     { gsPORTC &= 0b00011111; \
                         gsPORTC |= 0b01000000; \
                           PORTC  = gsPORTC;   SLEEP_US (2); }
//                                    76543210
#define PB3_SWITCH     { gsPORTC &= 0b00011111; \
                         gsPORTC |= 0b01100000; \
                           PORTC  = gsPORTC;   SLEEP_US (2); }
//                                    76543210
#define PB4_SWITCH     { gsPORTC &= 0b00011111; \
                         gsPORTC |= 0b10000000; \
                           PORTC  = gsPORTC;   SLEEP_US (2); }
//                                    76543210
#define PB5_SWITCH     { gsPORTC &= 0b00011111; \
                         gsPORTC |= 0b10100000; \
                           PORTC  = gsPORTC;   SLEEP_US (2); }



//
// ADC - "Photo Sense Analog To Digital Conversion" - Reads the light level of
//       the phototransistor that is currently selected by the combination of
//       the PBx select switch and RTx ground return enable. In SchemRev 2,
//       this is allocated to PC0 to be used as the 10-bit A/D converter input,
//       with a level assumption of 2.048V full-scale.
//
//                                76543210
// Desired config:   TRISC   |= 0b00000001 -- TristateOn:  0=Output/1=Input
//                   ODCONC  &= 0b11111110 -- OutputDrive: 0=Norm/1=CurrSink
//                   SLRCONC &= 0b11111110 -- OutputSlew:  0=Norm/1=Slower
//                   LATC    &= 0b11111110 -- InputSource: 0=Input/1=FromOut
//                   ANSELC  |= 0b00000001 -- InputDAType: 0=Dig/1=Analog
//                   WPUC    &= 0b11111110 -- InputPullUp: 0=No/1=WeakPullUp
//                   INLVLC  &= 0b11111110 -- InputSample: 0=TTL/1=Schmitt
//                                76543210
//                   ADCON1  |= 0b00000000 -- 765PreChCtl,0DoubleSample
//                   ADCON1  &= 0b11111110 -- Clr 0
//                                76543210
//                   ADCON2  |= 0b00000000 -- 7Flt2Prv 654CalCtl 3AC 210Mod
//                   ADCON2  &= 0b01110000 -- Clr 7,3; 210:=#0
//                                76543210
//                   ADCON3  |= 0b00000000 -- 654Mod 3AutoTrigSOI 210ThMod
//                   ADCON3  &= 0b10001000 -- 654:=#0 210:=#0
//                                76543210
//                   ADCLK   |= 0b00000000 -- 543210FoscDivFactor
//                   ADCLK   &= 0b11000000 -- 543210:=#0
//                                76543210
//                   ADREF   |= 0b00000000 -- 4NegRef 10PosRef
//                   ADREF   &= 0b11101100 -- 4:=#0=Vss 10:=#0=Vdd
//                                76543210
//                   FVRCON  |= 0b00000000 -- VRef 7En 6Rdy 54T 32vCMP 10vAD
//                   FVRCON  &= 0b01111100 -- Clr 7; 10:=#0=(OFF)v
//                                76543210
//                   ADPCH   |= 0b00010000 -- 543210PosInChSel
//                   ADPCH   &= 0b11010000 -- 543210:=0b010000=PC0
//                                76543210
//                   ADPRE   |= 0b00000000 -- 76543210PrechClksOr0Skip
//                   ADPRE   &= 0b00000000 -- 76543210:=#0=NoPreChStep
//                                76543210
//                   ADACQ   |= 0b00000000 -- 76543210AcqTimeOr0Skip
//                   ADACQ   &= 0b00000000 -- 76543210:=#0=NoAcqStep
//                                76543210
//                   ADCAP   |= 0b00000000 -- 76543210AddInternalCapAtInput
//                   ADCAP   &= 0b00000000 -- 76543210:=#0=0pF
//                                76543210
//                   ADCON0  |= 0b10010100 -- 7Ena,6Cont,4FRCclk,2RJust,0Go
//                   ADCON0  &= 0b10111110 -- Set 7,4,2; Clr 6,0
//
#define ADC_CURRVAL_READ                                                \
                   ((uint16_t)((uint16_t)(((uint16_t)ADRESH) << 8) +    \
                   (uint16_t)ADRESL))

#define ADC_LASTVAL_READ                                                \
                   ((uint16_t)((uint16_t)(((uint16_t)ADPREVH) << 8) +   \
                   (uint16_t)ADPREVL))

#define ADC_ISBUSY_READ                                                 \
                   (0 != (ADCON0 & 0b00000001))

#define ADC_GO_WRITE                                                    \
                   { ADCON0 |= 0b00000001; }



//
// LGD - "Legend Dimmer" - A 0 in this bit causes the "legend" (key caps) light
//                         to be brightest. With a 1, it will be at about 40%.
//                         The HW makes it so it is never able to be dimmed to
//                         0%. You can PWM this bit if you want to animate. In
//                         SchemRev 2, it is connected to a P-ch MOSFET where
//                         its active-low gives it full Vdd but on top of that,
//                         a shunt resistor supplies a constant ~40%. 
//
//                                76543210
// Desired config:   TRISC   &= 0b11111101 -- TristateOn:  0=Output/1=Input
//                   ODCONC  &= 0b11111101 -- OutputDrive: 0=Norm/1=CurrSink
//                   SLRCONC &= 0b11111101 -- OutputSlew:  0=Norm/1=Slower
//                   LATC    &= 0b11111101 -- InputSource: 0=Input/1=FromOut
//                   ANSELC  &= 0b11111101 -- InputDAType: 0=Dig/1=Analog
//                   WPUC    &= 0b11111101 -- InputPullUp: 0=No/1=WeakPullUp
//                   INLVLC  &= 0b11111101 -- InputSample: 0=TTL/1=Schmitt
//                   gsPORTC &= 0b11111101 -- Init drive=ON by Pch-to-low
//
//                                  76543210
#define LGD_DIMMED   { gsPORTC |= 0b00000010; PORTC = gsPORTC; SLEEP_US (5); }
#define LGD_BRIGHT   { gsPORTC &= 0b11111101; PORTC = gsPORTC; SLEEP_US (5); }



//
// TST - "Test & Debug" - Turns the test LED ON (write a 1) or OFF (write a 0).
//                        In SchemRev 2, TST is allocated to the PC2 output.
//
//                                76543210
// Desired config:   TRISC   &= 0b11111011 -- TristateOn:  0=Output/1=Input
//                   ODCONC  &= 0b11111011 -- OutputDrive: 0=Norm/1=CurrSink
//                   SLRCONC &= 0b11111011 -- OutputSlew:  0=Norm/1=Slower
//                   LATC    &= 0b11111011 -- InputSource: 0=Input/1=FromOut
//                   ANSELC  &= 0b11111011 -- InputDAType: 0=Dig/1=Analog
//                   WPUC    &= 0b11111011 -- InputPullUp: 0=No/1=WeakPullUp
//                   INLVLC  &= 0b11111011 -- InputSample: 0=TTL/1=Schmitt
//                   gsPORTC &= 0b11111011 -- Init to OFF
//
//                               76543210
#define TST_OFF   { gsPORTC &= 0b11111011; PORTC = gsPORTC; SLEEP_US (5); }
#define TST_ON    { gsPORTC |= 0b00000100; PORTC = gsPORTC; SLEEP_US (5); }



void show_debug_burst
(
    uint16_t    inNumCycles
)
//
// Stops the world (blocks). Blinks N number of times and returns (unblocks).
//
// No delays before or after. Blink iterations are 50ms cycles, so
// e.g. an input of 10 means 500ms.
//
// Supports up to a 16bit unsigned amount. If you pass a 0, no (easily)
// visible blink is done, but there will be a very quick on/off pulse
// that you can use to measure a single-event on a scope or freq counter.
//
{
    uint16_t i = inNumCycles;
    
    if (0 == i)
    {
        TST_ON;
        TST_OFF;
        return;
    }
    
    while (1)
    {
        TST_ON;
        sleep_ms (25);
        TST_OFF;
        i--;
        if (0 == i) { break; }
        sleep_ms (25);        
    }
}



void show_debug_decimal_uint16
(
    bool        inPreWait,
    uint16_t    inValUInt16,
    bool        inPostWait
)
//
// Stops the world (blocks). Waits a little bit; blinks to show the number
// value in decimal; waits a little bit; returns (unblocks). Supports up to
// a 16bit unsigned number. Displays it as a multi-digit decimal number. Max
// 5 digits; max input value 65535. Leading zeros are suppressed. Zeros in
// the middle or trailing zeros are shown. A non-"0" digit is shown by that
// number of blinks. A "0" digit is shown long dimmer (~50%) dash.
//
// For example, if you pass 205 (and true for both pre and post waits),
// we will wait, blink twice, wait a short amount, show a dim "dash",
// wait a short amount, blink five times, wait, and return. 
//
{
    uint16_t    acc = inValUInt16;
    uint16_t    subVal = 0;
    uint8_t     digitVal = 0;
    uint8_t     digitPos = 5;
    bool        shownNonzeros = false;
    
    // Prologue wait:
    if (inPreWait) { sleep_ms (2000); }
    
    while (1)
    {       
        if (5 == digitPos) { subVal = 10000; }
        if (4 == digitPos) { subVal = 1000; }
        if (3 == digitPos) { subVal = 100; }
        if (2 == digitPos) { subVal = 10; }
        if (1 == digitPos) { subVal = 1; }
        
        digitVal = 0;
        
        while (1)
        {
            if (acc >= subVal)
            {
                digitVal++;
                acc -= subVal;
            }
            else
            {
                break;
            }
        }
        
        if (digitVal > 0)
        {
            // The digit we need to show is "1".."9":
            while (1)
            {
                TST_ON;
                sleep_ms (380);
                TST_OFF;
                
                digitVal--;
                if (0 == digitVal) { break; }
                
                sleep_ms (240);
            }
            
            shownNonzeros = true;
        }
        else
        {
            // The digit we need to show is "0", but do so only if we
            // have shown at least one nonzero digit already (in this
            // way we suppress leading zeros). The other exception is
            // for if we were given an input value of zero (where we
            // want to show a "0" as the only digit):
            if ((true == shownNonzeros) || (0 == inValUInt16))
            {                
                // Show a zero as a dimmer steady "long dash":
                for (uint8_t showZloop = 0; showZloop < 200; showZloop++)
                {
                    TST_ON;
                    sleep_ms (1);
                    TST_OFF;
                    sleep_ms (3);
                }
                
                if (0 == inValUInt16)
                {
                    /* We would clear okToShowZeros, but we can just stop */
                    break;
                }
            }
        }
        
        digitPos--;
        if (0 == digitPos) { break; }
        
        // We have more digits to do, so do an inter-digit wait (providing
        // we have already shown digits -- we don't want to wait while
        // engaged in skipping leading zeros):
        if (true == shownNonzeros)
        {
            sleep_ms (700);
        }
    }
    
    // Epilogue wait:
    if (inPostWait) { sleep_ms (2000); }
}



void illuminate_and_measure (void)
//
// This routine does the inner illuminate-and-check loop. It uses globals
// instead of parameters because of manifest state.
//
// In:      gNowVddBank              0..5 drive bank (coordinate-ish)
//          gNowGndReturn            0..7 ground return (coordinate-ish)
//
// In:      gNowDark0Bright1         0 to do LED-OFF phase; 1 to do LED-ON
//
// Out:     gSampleOutMeasuredLuma   0..1023 (or 450 if unable/error)
//
// Err:     gError                   0 if successful or else specific code
//
// This routine does the entire { LED-ON (or OFF), SELECT PT CHANNEL,
// READ PT ADC VALUE (of ON or OFF expected value), LED-OFF } sequence
// for you, with protection against leaving the LED on for too long due
// to various causes (e.g. ADC timeout), and automatic cadence increment
// to the next LED/PT site. It is safer to manage everything here with
// global state than having any higher-level software bugs cause damage.
//
// The result is 0..1023 as dark..bright (note we invert the ADC voltage
// result for you).
//
// If there is an error, the output intensity result is a middle-of-the-road
// 450, and gError will be left with some error code. If successful, then
// this routine will always leave gError with 0.
//
// A safe duty cycle maximum is enforced because this routine manages the
// cadence (therefore do not tamper with it by resetting the icoordinates
// to the same value and repeatedly calling this).
//
// To use: Once: Init gNowVddBank and gNowGndReturn (coordinates) to 0.
// To use: Repeat: Set gNowDark0Bright1 to 0/1 for dark/bright test. Call
//                 this routine. Read gError and/or gSampleOutMeasuredLuma
//                 (no need to pre-init before calling; gError and
//                 gSampleOutMeasuredLuma are always written. Note that
//                 also the gSampleOutKeyNumber value will be the "decoded"
//                 0..47 site we just looked at).
// To use: Next:   Call advance_to_next_sample to increment (with auto-wrap)
//                 to the next position.
//
// Typical values observed:
//
// With low current LED-drive: If finger on key, indoor lighting:
// dark=~3 bright=~123 allbrightsaverage=~25.
//
// With low current LED-drive: If outdoor or max incandescent (IR) lighting:
// dark=~900 bright=~980+.
//
{
    gError = 0;
    gSampleOutMeasuredLuma = 450;
    
    union 
    {
        uint8_t     asBytes[2];
        uint16_t    asWord;
    }
    arrange;
    
    uint8_t         safetyTimeout = 50;
    bool            timedOut = false;
    
    // Preflight against out of range input parameter(s). Set errors/exit.
    // The bug will be indicated by error and this routine not doing anything:
    
    if (gNowDark0Bright1 > 1) { gError = 1; goto Exit; }
    
    // Preflight against out of range coordinates. It should never happen,
    // but just in case of caller mucking, etc. It would also be a bug and
    // rather than correct it, just safely don't do anything. Also the
    // decoded key number should be in range. It will be a malfunction if
    // any of these are not set and is a bug, but to exit here is safe and
    // shows the caller something is wrong:
    
    if (gNowVddBank > 5)   { gError = 2; goto Exit; }
    if (gNowGndReturn > 7) { gError = 3; goto Exit; }
    if (gNowKey > 47)      { gError = 4; goto Exit; }
    
    //
    // Enforce a minimum intra-calling-us time spacing that has more to do
    // with ADC and circuit capacitance recovery than the LEDs or PTs. If
    // this is not done, then rapidly-repeating measurements in the LED ON
    // mode seem to meet with diminishing values:
    //
    
    if (1 == gNowDark0Bright1)
    {
        SLEEP_US (666);
    }
    
    // Set up (that is, switch to) the new ground return path. Always set
    // it, even if we (software) somehow know it hasn't changed (which
    // should never happen, due to our cadence). Only 1 bit (one return path)
    // enabled at a time:
    
    if      (0 == gNowGndReturn) { RT0_SWITCH; }
    else if (1 == gNowGndReturn) { RT1_SWITCH; }
    else if (2 == gNowGndReturn) { RT2_SWITCH; }
    else if (3 == gNowGndReturn) { RT3_SWITCH; }
    else if (4 == gNowGndReturn) { RT4_SWITCH; }
    else if (5 == gNowGndReturn) { RT5_SWITCH; }
    else if (6 == gNowGndReturn) { RT6_SWITCH; }
    else if (7 == gNowGndReturn) { RT7_SWITCH; }
    else                         { RTX_DESELECT; }
    
    // Swith the selector switch for PT channels into ADC to match the
    // current bank of interest. There CAN only be 1 active at a time.
    // Again, we always positively set it regardless of if same (which
    // should never happen, due to our cadence):
    
    if      (0 == gNowVddBank) { PB0_SWITCH; }
    else if (1 == gNowVddBank) { PB1_SWITCH; }
    else if (2 == gNowVddBank) { PB2_SWITCH; }
    else if (3 == gNowVddBank) { PB3_SWITCH; }
    else if (4 == gNowVddBank) { PB4_SWITCH; }
    else if (5 == gNowVddBank) { PB5_SWITCH; }
    else                       { PB0_SWITCH; }
    
    // Point "A": Same as above with +Voltage Drive into LEDs, if the caller
    // wants to turn the LED on. If so, then starting after this, the "100uS
    // until damage" clock is ticking. Like above, we always set the LED
    // ON or OFF regardless of it its different from last time (however, it
    // always should be different, due to our cadence):
    
    if (1 == gNowDark0Bright1)
    {
        // Bright Test Phase. Set the LED drive MOSFET to ON:
            
        if      (0 == gNowVddBank) { LB0_SWITCH; }
        else if (1 == gNowVddBank) { LB1_SWITCH; }
        else if (2 == gNowVddBank) { LB2_SWITCH; }
        else if (3 == gNowVddBank) { LB3_SWITCH; }
        else if (4 == gNowVddBank) { LB4_SWITCH; }
        else if (5 == gNowVddBank) { LB5_SWITCH; }
        else                       { LBX_DESELECT; }
        
        // Do a 30uS settle (LED rises in 20nS); we are inside the
        // critical 100uS budget now:
            
        SLEEP_US (29);
            
        // Set up how long we have left in our budget; since the
        // total LED ON time must be less than 100uS call it 65us.
        // This is empirically turned into this value:
            
        safetyTimeout = 56;
    }
    else
    {
        // Dark Test Phase. Set the LED drive MOSFET to OFF:
        
        LBX_DESELECT;
        
        // Do a 50uS dark-settling time:
        
        SLEEP_US (53);
        
        // Since LED OFF there is no critical time budget. Choose mid-ish:
        
        safetyTimeout = 110;
    }
    
    // Here, about 30uS has elapsed since Point A if energizing one of
    // the LEDs, or about 50uS if not energizing. This allows either
    // LED on or LED off state plus phototransistor state way more than
    // enough time to settle; the LED and PT spec both say they respond
    // in 20nS or less.
    
    // Start the analog to digital converter (ADC):
    
    //          76543210
    ADCON0 |= 0b00000001;
    
    // Wait for the ADC result, or time out, whichever comes first.
    // We must NOT wait forever; we must turn LED back OFF soon enough:
    
    while (1)
    {
        if (0 == safetyTimeout) { timedOut = true; break; }
        safetyTimeout--;
        
        //                   76543210
        if (0 == (ADCON0 & 0b00000001)) { break; }
    }
    
    // Now, the critically-important LED-off shutoff:
    
    LBX_DESELECT;
    
    // Abort with error if our ADC-get-loop timed out:
    
    if (true == timedOut)
    {
        gError = 5;
        goto Exit;
    }
    
    // Read a 10bit result 0..1023 from the ADC:
    
    arrange.asBytes [0] = (uint8_t) ADRESL;
    arrange.asBytes [1] = (uint8_t) ADRESH;
    gSampleOutMeasuredLuma = arrange.asWord;
    
    // Abort with error if somehow we have a bogus result from the ADC.
    // Convert it to a mid-value intensity result:
    
    if (gSampleOutMeasuredLuma > 1023)
    {
        gSampleOutMeasuredLuma = 450;
        gError = 6;
        goto Exit;
    }
    
    // Invert the result. The PTs are a resistance to ground that's pulled
    // up, so brighter leads to less voltage and a lower ADC reading. Make
    // it so our result is greater if brighter, with a range of 0..1023:
    
    gSampleOutMeasuredLuma = 1023 - gSampleOutMeasuredLuma;    
        
    // Exit sink. As an extra precaution, do an LED-off event here as well.
    // In fact, we will turn both the plus feed and ground return off:
    
    Exit:;
    
    LBX_DESELECT;
    RTX_DESELECT;
}



void advance_gNowKey_and_gCoordinates (void)
//
// Advance to looking at the next key position:
//
// gNowKey, gNowVddBank, gNowGndReturn -- all in lockstep.
//
// This increments the globals that are the coordinates of the key
// (LED Drive, GND Return, and Key Number). A complete go-around of
// these variables constitutes a complete scan of the keyboard.
//
// Cadence: Advance to the next LED/PT site, wrapping around. Go in
// the N labeled key number order, which is: 1-of-8 vertical-ish
// (LED/PT within bank, ground return) faster/inner, and then 1-of-6
// slower/outer (bank Vdd select).
//
{
    gNowGndReturn++;
    if (gNowGndReturn > 7)
    {
        gNowGndReturn = 0;
        gNowVddBank++;
        if (gNowVddBank > 5)
        {
            gNowVddBank = 0;
        }
    }
    
    gNowKey = (gNowVddBank << 3);
    gNowKey = gNowKey + gNowGndReturn;
}
        


void measure_baseline_dark_luma_of_current_key (void)
//
// On a per-current-key (gNowKey) basis.
//
// Updates, for the current gNowKey, its entry in the array of baseline
// dark field measurements. Entries are averaged-(50/50-weighted)-in
// values of luminance in 16-bits. Luma values of 0 are considered the
// same as "uninitialized" (not yet visted) and so are treated specially
// and are never "left in": we leave in at least a 1 instead.
//
{
    uint16_t oldNewAvg = 0;
    
    gNowDark0Bright1 = 0;
    illuminate_and_measure ();
    if (0 != gError) { show_debug_decimal_uint16 (true, gError, true); }
    
    oldNewAvg = gKeyLumasForBaselineDarkMeas16 [gNowKey];
    
    if (0 == oldNewAvg)
    {
        // if 0, that's like none-set-"yet", so set it in full to curr val
        oldNewAvg = gSampleOutMeasuredLuma;
    }
    else
    {
        // if non-0, then "average in" the new val and set it as curr val
        oldNewAvg = (oldNewAvg + gSampleOutMeasuredLuma) >> 1; // div 2
    }
    
    if (0 == oldNewAvg) { oldNewAvg = 1; } // initted if here, so never 0
    
    gKeyLumasForBaselineDarkMeas16 [gNowKey] = oldNewAvg;
}



void measure_baseline_bright_luma_of_current_key (void)
//
// On a per-current-key (gNowKey) basis.
//
// Updates, for the current gNowKey, its entry in the array of baseline
// bright field measurements. Entries are averaged-(50/50-weighted)-in
// values of luminance in 16-bits. Luma values of 0 are considered the
// same as "uninitialized" (not yet visted) and so are treated specially
// and are never "left in": we leave in at least a 1 instead.
//
{
    uint16_t oldNewAvg = 0;
    
    gNowDark0Bright1 = 1;
    illuminate_and_measure ();
    if (0 != gError) { show_debug_decimal_uint16 (true, gError, true); }
    
    oldNewAvg = gKeyLumasForBaselineBrightMeas16 [gNowKey];
    
    if (0 == oldNewAvg)
    {
        // if 0, that's like none-set-"yet", so set it in full to curr val
        oldNewAvg = gSampleOutMeasuredLuma;
    }
    else
    {
        // if non-0, then "average in" the new val and set it as curr val
        oldNewAvg = (oldNewAvg + gSampleOutMeasuredLuma) >> 1; // div 2
    }
    
    if (0 == oldNewAvg) { oldNewAvg = 1; } // initted if here, so never 0
    
    gKeyLumasForBaselineBrightMeas16 [gNowKey] = oldNewAvg;
}



void measure_probatory_dark_luma_of_current_key (void)
//
// On a per-current-key (gNowKey) basis.
//
// Updates, for the current gNowKey, its entry in the array of "production"
// (probing) dark field measurements. Entries are stored in values of
// luminance in 16-bits. Stored values of 0 are considered the same as
// "uninitialized" (not yet visted) and so are treated specially and are
// never "left in": we leave in at least a 1 instead.
//
{
    uint16_t newMeas = 0;
    
    gNowDark0Bright1 = 0;
    illuminate_and_measure ();
    if (0 != gError) { show_debug_decimal_uint16 (true, gError, true); }
    newMeas = gSampleOutMeasuredLuma;
    
    if (0 == newMeas) { newMeas = 1; } // initted if here, so never 0
    
    gKeyLumasForProbingDarkMeas16 [gNowKey] = newMeas;
}



void measure_probatory_bright_luma_of_current_key (void)
//
// On a per-current-key (gNowKey) basis.
//
// Updates, for the current gNowKey, its entry in the array of "production"
// (probing) bright field measurements. Entries are stored in values of
// luminance in 16-bits. Stored values of 0 are considered the same as
// "uninitialized" (not yet visted) and so are treated specially and are
// never "left in": we leave in at least a 1 instead.
//
{
    uint16_t newMeas = 0;
    
    gNowDark0Bright1 = 1;
    illuminate_and_measure ();
    if (0 != gError) { show_debug_decimal_uint16 (true, gError, true); }
    newMeas = gSampleOutMeasuredLuma;
    
    if (0 == newMeas) { newMeas = 1; } // initted if here, so never 0
    
    gKeyLumasForProbingBrightMeas16 [gNowKey] = newMeas;
}



void maintain_ambient_lighting_mode (void)
//
// This determines and sets the current major ambient environmental
// lighting mode. This sets gModeMoonlit0Sunlit1 according to what
// is seen at a few representative keys at the LED-off phase.
//
// This should be called periodically, but not too often, e.g. every
// 1~5 sec or so.
//
{
    uint16_t    reprKey1 = 0;
    uint16_t    reprKey2 = 0;
    
    gModeMoonlit0Sunlit1 = 0;
    
    reprKey1 = gKeyLumasForBaselineDarkMeas16 [13]; // E
    reprKey2 = gKeyLumasForBaselineDarkMeas16 [26]; // H
    
    reprKey1 += reprKey2;
    reprKey1 = reprKey1 >> 1; // div 2
            
    if (reprKey1 > kTC_SunModeThresh)
    {
        reprKey1 = gKeyLumasForBaselineDarkMeas16 [37]; // O (oh)
        if (reprKey1 > kTC_SunModeThresh)
        {
            gModeMoonlit0Sunlit1 = 1;
        }
    }
    
    // Provide for some debugging info, swithable with build time configs.
    // If enabled, we show if we are switching us into moonlit or sunlit
    // mode with a short burst for moonlit and a long burst for sunlit:
    //
    if (kTC_SunMode_BlinkChg)
    {
        if (0 == gModeMoonlit0Sunlit1) { show_debug_burst (5); }
        if (1 == gModeMoonlit0Sunlit1) { show_debug_burst (20); }
    }
}



void determine_score_of_current_key (void)
//
// On a per-current-key (gNowKey) basis.
//
// This first calculates various delta products that correspond to the current
// gNowKey position in all of the stored sense arrays. 
//
// We then examine these deltas and see if they match one or more of several
// syndromes ("use cases" of what the light is doing). Not only various use
// cases but also according to the current sun-lit mode.
//
// We then take the instantaneous score value at this moment in time and add
// it (with pinning) to a per-key score array. Or, if the calculated
// instantaneous score this time is zero, then we decrement the current key's
// value in the per-key score array.
//
// The "output" result is that we up-keep a per-key array of key-down
// likelihoods, or "syndrome" count. It is dynamic and ever-increasing and/or
// decreasing over time.
//
{
    uint16_t    b16 = 0;
    uint16_t    p16 = 0;
    uint16_t    darkerNess = 0;
    uint16_t    darkerSsen = 0;
    uint16_t    brighterNess = 0;
    uint16_t    addThisToScoreCount16 = 0;
    bool        isFloodIllum = false;
    
    // Calculate the amount, if any, by which the dark phase measurement
    // got a darker (thus possibly a finger-down) result compared to its
    // baseline (of having the LED off (and no finger)):
    //
    // Call it darkerNess, which is the amount of more-dark-ness that
    // is seen between the baseline-dark measurements and the probe-dark
    // measurements. A value of 0 means it wasn't any darker, or values
    // are not (yet) set to be able to measure a meaningful result.
    //
    // No negative numbers used and pin to 0 used.
    //
    // If we get a perverse result that the expected-darker is actually
    // brighter, then record that result in "darkerSsen" ("ness" backwards);
    // this is also of use later below.
    //
    // Also calculate a boolean that is true if we can confirm we are under
    // a flood illumination situation: the absolute values of base+probe are
    // very high, near maximum of the measurement range: Call it isFloodIllum.
    //
    b16 = gKeyLumasForBaselineDarkMeas16 [gNowKey];
    p16 = gKeyLumasForProbingDarkMeas16  [gNowKey];
    darkerNess = 0;
    darkerSsen = 0;
    isFloodIllum = false;
    if ((0 != b16) && (0 != p16))
    {
        if (p16 < b16) { darkerNess = b16 - p16; }
        
        if (p16 > b16) { darkerSsen = p16 - b16; }
        
        isFloodIllum = ((b16 > kTC_Score_Sn_Flood_Cfm) &&
                        (p16 > kTC_Score_Sn_Flood_Cfm));
    }
    
    // Calculate the amount, if any, by which the bright phase measurement
    // got a brigher (thus possibly a finger-down (reflectance)) result
    // compared to its baseline of LED ON (but no finger):
    //
    // Call it brighterNess, which is the amount of more-light-ness
    // seen between the baseline-LED-ON measurements and the probed-LED-ON
    // measurements. A value of 0 means it wasn't any brighter, or values
    // are not (yet) set to be able to measure a meaningful result.
    //
    // No negative numbers used and pin to 0 used.
    //
    b16 = gKeyLumasForBaselineBrightMeas16 [gNowKey];
    p16 = gKeyLumasForProbingBrightMeas16  [gNowKey];
    brighterNess = 0;
    if ((0 != b16) && (0 != p16))
    {
        if (p16 > b16) { brighterNess = p16 - b16; }
    }
    
    // ----------------------------------------------------------------------
    
    // If debug mode, show the value of the photosense for the "V" (N19) key
    // provided the clock has gone on for a while (causing baselines, etc, to
    // have settled). Do this for various debug flags = various phases:
    //
    if (true == kTC_Score_ShowBaseLine)
    {
        if ((gClock16 > 4000) && (gNowKey == 19/*V*/))
        {
           show_debug_decimal_uint16 (true,
                                      gKeyLumasForBaselineDarkMeas16 [19],
                                      true);
        }
    }
    if (true == kTC_Score_ShowProbeDk)
    {
        if ((gClock16 > 4000) && (gNowKey == 19/*V*/))
        {
            show_debug_decimal_uint16 (true,
                                       gKeyLumasForProbingDarkMeas16 [19],
                                       true);
        }
    }
    if (true == kTC_Score_ShowProbeBr)
    {
        if ((gClock16 > 4000) && (gNowKey == 19/*V*/))
        {
            show_debug_decimal_uint16 (true,
                                       gKeyLumasForProbingBrightMeas16 [19],
                                       true);
        }
    }
    
    // ----------------------------------------------------------------------
    
    // Two main modes for how to extract what is going on:
    //
    if (0 == gModeMoonlit0Sunlit1)
    {
        // -----
        // MOONLIT SYNDROMES
        // -----
        
        // A Key (Finger)-Down possibility syndrome:
        //
        // Finger down in medium-to-low (or black) lighting conditions.
        //
        // We'd expect a healthy amount of brighterNess, AND, we'd at the same
        // time maybe a little bit (or a lot) of darkerNess (aka finger shadow
        // due to blocking ambient).
        //
        if (brighterNess > kTC_Score_Mn_BrNss_Thr)
        {
            addThisToScoreCount16 += kTC_Score_Mn_BrNss_Inc;
        }
        
        // Additional bonus scoring possibility like the above: Not only
        // brighter (way brighter) but also darker than ambient since finger
        // is blocking ambient light:
        //
        if (darkerNess > kTC_Score_Mn_BrDk_ThrD)
        {
            addThisToScoreCount16 += kTC_Score_Mn_BrDk_Inc;
        }
    }
    else
    {
        // -----
        // SUNLIT SYNDROMES
        // -----
        
        // The problem with the bright field thing is this:
        // human fingers love to transmit IR. So in sunlight, they are
        // actually increasative of the luma. Here is something I see that 
        // might work: I see 999-ish on full luma but 900-ish on finger.
        // So look for differential a little bit less than neighbors or
        // absolute a little bit less but only if abs also >600.
        
        // Scoring possibility syndrome: A key is hugely illuminated in
        // all cases (baseline, probe, both LED-on=bright and LED-off=dark
        // phases), but we can just barely detect a little bit of darkerness
        // if covered by a (still very IR-transmissible) finger:
        //        
        if (true == isFloodIllum)
        {
            if (darkerNess > kTC_Score_Sn_DkNss_Thr)
            {
                addThisToScoreCount16 += kTC_Score_Sn_DkNss_Inc;
            }
            
            if (darkerSsen > kTC_Score_Sn_DkSsn_Thr)
            {
                addThisToScoreCount16 += kTC_Score_Sn_DkSsn_Inc;
            }
        }
    }
    
    // ----------------------------------------------------------------------
    
    // Add if "addThisToScoreCount" to syndrome-hits array for current key:
    //
    // If there is nothing to add (addThisToScoreCount is zero), then we
    // decrease that key's entry. We divide by 2. In this way, over time,
    // if a key is not getting a syndrome tickle on it (finger down on it),
    // its array entry value will quickly decay.
    //
    // If there IS something to add, then do the add, but make sure that if
    // we would saturate, that it gets pinned and not wrap around overflow.
    //
    p16 = gKeyDownScores16 [gNowKey];
    if (addThisToScoreCount16 > 0)
    {
        b16 = 65535 - p16;
        if (addThisToScoreCount16 < b16)
        {
            p16 = p16 + addThisToScoreCount16;
        }
        else
        {
            p16 = 65535;
        }
    }
    else
    {
        p16 = (p16 >> 1); // div 2
    }
    gKeyDownScores16 [gNowKey] = p16;
}



void maintain_histogram_of_peak_of_all_scores (void)
//
// Should be called periodically (but async with respect to the key phase
// scanning and score updating).
//
// The gKeyDownScores16 array is inspected for a value that is both a
// global maximum as well as sufficiently "above" the next-most-max one (that
// is, with a sharp enough peak, e.g. a large enough d/dX). If such an entry
// is found, that entry will have that key's corresponding keyDownPeakHits
// incremented. In the meantime, all other keyDownPeakHits values are
// decremented. In this way, our "output" of keyDownPeakHits will always be
// the most-likely pressed key over time.
//
// Note, however, that even a detected peak will decay over time because
// the lower level arrays will normalize to the new conditions. We accept
// that as OK because all we are interested in is rising-edge taps.
//
{
    uint16_t    firstHighestScoreVal16 = 0;
    uint8_t     firstHighestScoreKey = 99;
    uint16_t    secondHighestScoreVal16 = 0;
    uint8_t     secondHighestScoreKey = 99;
    bool        isPeakSharp = false;
    uint16_t    aScore16 = 0;
    uint8_t     i = 0;
    
    // See if any key has a score greater than a certain threshold.
    // Among those, if any, find the highest. If one is found, then set val
    // (value) and key (0..47 key number) to that one. If not found, then
    // set the key number to 99 ("none"):
    //
    firstHighestScoreVal16 = 0;
    firstHighestScoreKey = 99;
    for (i = 0;   i < 48;   i++)
    {
        aScore16 = gKeyDownScores16 [i];
        if ((aScore16  > kTC_Peaks_ScoreThresh) &&
            (aScore16 >= firstHighestScoreVal16))
        {
            firstHighestScoreVal16 = aScore16;
            firstHighestScoreKey = i;
        }
    }
    
    // If a highest-key per the above criteria was found, then look for the
    // next-highest one. If none found, then leave this output value as 99.
    // That is, if the first-highest wasn't found (was 99) then this one will
    // also be 99; or if the first-highest was found but there is no second-
    // highest that was more than a threshold, this one will also be 99:
    //
    secondHighestScoreVal16 = 0;
    secondHighestScoreKey = 99;
    if (99 != firstHighestScoreKey)
    {
        for (i = 0;   i < 48;   i++)
        {
            aScore16 = gKeyDownScores16 [i];
            if ((aScore16  > kTC_Peaks_ScoreThresh)   &&
                (aScore16 >= secondHighestScoreVal16) &&
                (i        != firstHighestScoreKey))
            {
                secondHighestScoreVal16 = aScore16;
                secondHighestScoreKey = i;
            }
        }
    }
    
    // See if the peak is sharp:
    //
    // We start by assuming it is. Then, if we have both first plus second
    // highest values, require that the delta between those is more than
    // a threshold. It is essentially the first-derivative maximum among
    // {left,right}-side at the peak. If the delta (the derivative) is too
    // weak, e.g. meaning that the peak is not unique and sharp, then we
    // set isPeakSharp to false.
    //
    isPeakSharp = true;
    if ((99 != firstHighestScoreKey) && (99 != secondHighestScoreKey))
    {
        if (secondHighestScoreVal16 > firstHighestScoreVal16)
        {
            // Should never happen
        }
        else
        {
            uint16_t delta16 = firstHighestScoreVal16 - secondHighestScoreVal16;
            if (delta16 < kTC_Peaks_SharpDelta) { isPeakSharp = false; }
        }
    }
    
    // If the peak is not sharp, we consider that as not having a peak at all:
    //
    if (false == isPeakSharp) { firstHighestScoreKey = 99; }
    
    // Maintain the peak-hits histogram:
    //
    // First: If we found a good peak, then increment its histogram value.
    // Pin it at its max value of 8-bits.
    //
    if (99 != firstHighestScoreKey)
    {
        uint8_t hits = gKeyDownPeakHits [firstHighestScoreKey];
        if (hits < 255) { hits = hits + 1; }
        gKeyDownPeakHits [firstHighestScoreKey] = hits;
    }
    
    // Maintain the peak-hits histogram:
    //
    // Second: For all keys -- not including a good peak IF we found one --
    // decrement the histogram values, pinning at zero. Note that if we did
    // not find a peak at all (firstHighestScoreKey is 99) then this will
    // decrease all of them.
    //
    for (i = 0;   i < 48;   i++)
    {
        if (i == firstHighestScoreKey)
        {
            // Spare
        }
        else
        {
            uint8_t hits = gKeyDownPeakHits [i];
            hits = hits >> 2;
            gKeyDownPeakHits [i] = hits;
        }
    }
}



void have_key_tap (uint8_t inKeyNumber)
//
// Here we have ruled that we have a key tap, and we will take all of the
// high-level key-is-pressed actions. There are several:
//
// (1) Implement some debug/diagnostics modes for showing the pressed event.
// (2) Implement some debug/diagnostics modes for testing the output port.
// (3) If appropriate, toggle shifted state; toggle emergency state.
// (4) Start one of the legend blink animations for human "tactile" feedback.
// (5) Translate our key index into an ASCII code (with a few extensions).
// (6) Send the given key out of the I/O port for the client.
//
{
    uint8_t     kcOut = 0x00;
    
    // Debug/diag modes: If build flags set, show we are outputting a
    // key tap, either the fact itself as 1 blink, or the key number
    // as 0..47 blinks. Also, have a debug mode for repeatedly outputting
    // text out the serial port forever:
    //
    if (kTC_Tap_Blink1)
    {
        show_debug_burst (1);
    }
    if (kTC_Tap_BlinkKey)
    {
        if (0 == inKeyNumber) { show_debug_decimal_uint16 (false, 0, false); }
        show_debug_burst (inKeyNumber); // if 0 the above will have shown
    }
    if (kTC_Tap_SerialTest)
    {
        uint16_t cnt = 0;
        uint8_t  cts = 0x00;
        while (1)
        {
            cts = 'U';
            if      (                (cnt < 200)) { cts = 'H'; }
            if      ((cnt >= 200) && (cnt < 300)) { cts = 'e'; }
            if      ((cnt >= 300) && (cnt < 400)) { cts = 'L'; }
            if      ((cnt >= 400) && (cnt < 500)) { cts = 'l'; }
            if      ((cnt >= 500) && (cnt < 600)) { cts = 'O'; }
            if      ((cnt > 1200)               ) { cnt = 0;   }
            while (1)
            {
                //                   76543210
                if (0 != (TX1STA & 0b00000010)) break; // b1=TRMT:#0=bsy,#1=rdy
            }
            TX1REG = cts;
            sleep_ms (15);
            cnt++;
        }
    }
    
    // Convert from our key number (N) to ASCII. We use 7-bit ASCII but
    // do add a (very) few extended codes for shift, emojis, etc:
    //
    if (false == gCurrentCaseUpper)
    {
        switch (inKeyNumber)
        {
            case 0:      kcOut = 0x1B;   /* ESC */     break;
            case 1:      kcOut = 0xF3;   /* PANIC */   break;
            case 2:      kcOut = 0xF2;   /* EMOJI */   break;
            case 3:      kcOut = 0xF1;   /* SHIFT */   break;
            case 4:      kcOut = 0x31;   /* 1 */       break;
            case 5:      kcOut = 0x71;   /* q */       break;
            case 6:      kcOut = 0x61;   /* a */       break;
            case 7:      kcOut = 0x7A;   /* z */       break;
            case 8:      kcOut = 0x32;   /* 2 */       break;
            case 9:      kcOut = 0x77;   /* w */       break;
            case 10:     kcOut = 0x73;   /* s */       break;
            case 11:     kcOut = 0x78;   /* x */       break;
            case 12:     kcOut = 0x33;   /* 3 */       break;
            case 13:     kcOut = 0x65;   /* e */       break;
            case 14:     kcOut = 0x64;   /* d */       break;
            case 15:     kcOut = 0x63;   /* c */       break;
            case 16:     kcOut = 0x34;   /* 4 */       break;
            case 17:     kcOut = 0x72;   /* r */       break;
            case 18:     kcOut = 0x66;   /* f */       break;
            case 19:     kcOut = 0x76;   /* v */       break;
            case 20:     kcOut = 0x35;   /* 5 */       break;
            case 21:     kcOut = 0x74;   /* t */       break;
            case 22:     kcOut = 0x67;   /* g */       break;
            case 23:     kcOut = 0x20;   /* SPACE */   break;
            case 24:     kcOut = 0x36;   /* 6 */       break;
            case 25:     kcOut = 0x79;   /* y */       break;
            case 26:     kcOut = 0x68;   /* h */       break;
            case 27:     kcOut = 0x62;   /* b */       break;
            case 28:     kcOut = 0x37;   /* 7 */       break;
            case 29:     kcOut = 0x75;   /* u */       break;
            case 30:     kcOut = 0x6A;   /* j */       break;
            case 31:     kcOut = 0x6E;   /* n */       break;
            case 32:     kcOut = 0x38;   /* 8 */       break;
            case 33:     kcOut = 0x69;   /* i */       break;
            case 34:     kcOut = 0x6B;   /* k */       break;
            case 35:     kcOut = 0x6D;   /* m */       break;
            case 36:     kcOut = 0x39;   /* 9 */       break;
            case 37:     kcOut = 0x6F;   /* o */       break;
            case 38:     kcOut = 0x6C;   /* l */       break;
            case 39:     kcOut = 0x2C;   /* , */       break;
            case 40:     kcOut = 0x30;   /* 0 */       break;
            case 41:     kcOut = 0x70;   /* p */       break;
            case 42:     kcOut = 0xE9;   /* SMILE */   break;
            case 43:     kcOut = 0x2E;   /* . */       break;
            case 44:     kcOut = 0x7F;   /* DEL */     break;
            case 45:     kcOut = 0x0D;   /* CR */      break;
            case 46:     kcOut = 0x27;   /* ' */       break;
            case 47:     kcOut = 0x3F;   /* ? */       break;
        }
    }
    else
    {
        switch (inKeyNumber)
        {
            case 0:      kcOut = 0x1B;   /* ESC */     break;
            case 1:      kcOut = 0xF3;   /* PANIC */   break;
            case 2:      kcOut = 0xF2;   /* EMOJI */   break;
            case 3:      kcOut = 0xF1;   /* SHIFT */   break;
            case 4:      kcOut = 0x3B;   /* ; */       break;
            case 5:      kcOut = 0x51;   /* Q */       break;
            case 6:      kcOut = 0x41;   /* A */       break;
            case 7:      kcOut = 0x5A;   /* Z */       break;
            case 8:      kcOut = 0x3A;   /* : */       break;
            case 9:      kcOut = 0x57;   /* W */       break;
            case 10:     kcOut = 0x53;   /* S */       break;
            case 11:     kcOut = 0x58;   /* X */       break;
            case 12:     kcOut = 0x25;   /* % */       break;
            case 13:     kcOut = 0x45;   /* E */       break;
            case 14:     kcOut = 0x44;   /* D */       break;
            case 15:     kcOut = 0x43;   /* C */       break;
            case 16:     kcOut = 0x26;   /* & */       break;
            case 17:     kcOut = 0x52;   /* R */       break;
            case 18:     kcOut = 0x46;   /* F */       break;
            case 19:     kcOut = 0x56;   /* V */       break;
            case 20:     kcOut = 0x7C;   /* | */       break;
            case 21:     kcOut = 0x54;   /* T */       break;
            case 22:     kcOut = 0x47;   /* G */       break;
            case 23:     kcOut = 0x20;   /* SPACE */   break;
            case 24:     kcOut = 0x2D;   /* - */       break;
            case 25:     kcOut = 0x59;   /* Y */       break;
            case 26:     kcOut = 0x48;   /* H */       break;
            case 27:     kcOut = 0x42;   /* B */       break;
            case 28:     kcOut = 0x2B;   /* + */       break;
            case 29:     kcOut = 0x55;   /* U */       break;
            case 30:     kcOut = 0x4A;   /* J */       break;
            case 31:     kcOut = 0x4E;   /* N */       break;
            case 32:     kcOut = 0x2F;   /* / */       break;
            case 33:     kcOut = 0x49;   /* I */       break;
            case 34:     kcOut = 0x4B;   /* K */       break;
            case 35:     kcOut = 0x4D;   /* M */       break;
            case 36:     kcOut = 0x2A;   /* * */       break;
            case 37:     kcOut = 0x4F;   /* O */       break;
            case 38:     kcOut = 0x4C;   /* L */       break;
            case 39:     kcOut = 0x3C;   /* < */       break;
            case 40:     kcOut = 0x3D;   /* = */       break;
            case 41:     kcOut = 0x50;   /* P */       break;
            case 42:     kcOut = 0xEF;   /* FROWN */   break;
            case 43:     kcOut = 0x3E;   /* > */       break;
            case 44:     kcOut = 0x7F;   /* DEL */     break;
            case 45:     kcOut = 0x0D;   /* CR */      break;
            case 46:     kcOut = 0x22;   /* " */       break;
            case 47:     kcOut = 0x21;   /* ! */       break;
        }
    }
    
    // Toggle SHIFTed state:
    //
    // If the key being tapped is the SHIFT key, then: (A) It is still a 
    // key and will be transmitted now in its own right, but also, (B) toggle
    // the upper/lower case state, and (C) start a little backlight animation
    // as confirmation to the user, and (D) the shift has a side-effect of
    // cancelling the emergency mode (just as starting the shift animation
    // also cancels any emergency-mode animation in progress):
    //
    if (0xF1 == kcOut)
    {
        gCurrentCaseUpper = ! gCurrentCaseUpper;
        gCurrentCaseEmerg = false;
        gLegendLightAnimationState = kLegendLightCommand_DipOnce;
    }
    
    // Handle (toggle) EMERGency state:
    //
    // If the key being tapped is the EMERG key, then (A) It is still a
    // key to transmit, but also (B) toggle a state, so that we can know to
    // start or stop (C) a little backlight animation as general distress
    // indication:
    //
    if (0xF3 == kcOut)
    {
        gCurrentCaseEmerg = ! gCurrentCaseEmerg;
        if (true == gCurrentCaseEmerg)
        {
            gLegendLightAnimationState = kLegendLightCommand_BlinkCont;
        }
        else
        {
            gLegendLightAnimationState = kLegendLightCommand_Stop;
        }
    }
    
    // Provide "tactile" human feedback:
    //
    // In addition to some of the special modes above, we want to give a
    // little bit of feedback for all keys, because it helps the human know
    // when we have "registered" a key tap. Do this only if nothing else is
    // currently going on with the animation state.
    //
    if (0 == gLegendLightAnimationState)
    {
        gLegendLightAnimationState = kLegendLightCommand_BlinkShort;
    }
    
    // Send kcOut out via the serial port:
    //
    // The serial port, and which pin it is "connected" to, was configured
    // in our hardware init routine.
    //
    // We have interrupts off and will simply send a code when ready. Here
    // we will wait if the port is not ready (which usually happens if the
    // port is still shifting out (sending) the previous character). It is
    // expected that this busy wait will almost never have to happen, and
    // even if so, the delay will take (1/9600 baud * 10 bits) = 1ms max.
    //
    while (1)
    {
        //                   76543210
        if (0 != (TX1STA & 0b00000010)) break; // b1=TRMT:#0=bsy,#1=rdy
    }
    TX1REG = kcOut;
}



void do_keypress_debounce (uint8_t inKeyNumber)
//
// Final debounce routine. Just because we have a finger down, it is not
// necessarily a good idea to report it as a keypress.
//
// If it is a different key than last time, then report it, else ignore.
//
// However, we also reset this compare (in favor of acceping new keys) after a
// reasonable amount of time has elapsed, otherwise we would never accept a
// the same key pressed multiple times in a row. A reasonable such time might
// be on the order of 0.5 seconds. We don't care if our compare techinque
// might cause a wrap.
//
{
    bool    keyIsDifferent = false;
    
    if (inKeyNumber != gLastDownKeyNumber)
    {
        keyIsDifferent = true;
        gLastDownKeyNumber = inKeyNumber;
    }
    
    if (keyIsDifferent || kTC_Debow_Disable)
    {
        have_key_tap (inKeyNumber);
    }
}



void maintain_keypress_debounce (void)
//
// Periodically reset is-the-key-different comparator. This task routine
// should be called often, so that we can use the running clock to know
// when to clear the comparator.
//
{    
    if (0 == (gClock16 & kTC_Debow_ResetRate))
    {
        gLastDownKeyNumber = 99;
    }
}



void act_on_histogram_of_peak_of_all_scores (void)
//
// If there is any entry in the peak hits histogram that is bigger than
// a constant, then consider a finger down on that key, choosing, of
// course, the largest such entry in the histogram. 
//
// If a finger is ruled down on a key, then clear that and all other
// peak hits entry and entries; that is, reset the entire histogram.
// Then, call the finger-down routine for further processing.
//
// Note that the histogram hit values here are essentially an 
// integration over time.
//
{
    uint8_t     thisKeyIdx = 0;
    uint8_t     thisVal = 0;
    uint8_t     biggestVal = 0;
    uint8_t     biggestKeyIdx = 99;
    
    for (thisKeyIdx = 0;   thisKeyIdx < 48;   thisKeyIdx++)
    {
        thisVal = gKeyDownPeakHits [thisKeyIdx];
        if ((thisVal > kTC_Histo_ValToAct) &&
            (thisVal >= biggestVal))
        {
            biggestVal = thisVal;
            biggestKeyIdx = thisKeyIdx;
        }
    }
    
    if (99 != biggestKeyIdx)
    {
        for (thisKeyIdx = 0;   thisKeyIdx < 48;   thisKeyIdx++)
        {
            gKeyDownPeakHits [thisKeyIdx] = 0;
            
            gKeyDownScores16 [thisKeyIdx] = 0;
        }
             
        do_keypress_debounce (biggestKeyIdx);
   }
}



void initialize_key_phases (void)
//
// PER-KEY PHASE STATES
//
// This here sets the initial phase states for each (all) key sites.
//
// These phases are co-incremented with the current key position (e.g. at
// the same time; it is not that the phase state is a sub-loop; it is a
// loop that happens at the same time).
//
// What's the phase for a key? 8-bit value of 0,1,2,3 where most of the
// time we return to state 2, but sometimes it is state 0. For example:
// 0,1,2,3,2,3,2,3,2,3,2,3,2,3,2,3,...,2,3,2,3,0,1,2,3,2,3,2,3,2,3,...
//
// We want everything to be staggered yet also start at 0 initially.
// Thus: the phases should all init to 0 but the phase3To2Or0Count
// should be initially staggered (but reset to a proper-length
// recharge). After a second or so, all key phases will be staggered
// or relatively scrambled yet get their full times of everything.
//
// Phase transition: 8-bit currPhase0..3, 16-bit phase3To2Or0Count0..N
//
{
    for (uint8_t i = 0;   i < 48;   i++)
    {
        gKeyPhases               [i] = 0x00;
        gKeyPhases3To2Or0Count16 [i] = i; // setting to "i" staggers them
    }
}



void get_gNowPhase_for_gNowKey (void)
{
    gNowPhase = gKeyPhases [gNowKey];
}



void advance_phase_for_gNowKey (void)
//
// Advance to next phase state.
//
// Get, advance, store the current (now next) phase state, with
// states 0..1 happening a lot less commonly than 2..3. The states of 0..1
// are the re-baselining states, and therefore, the rate at which those
// states 0..1 happen compared to the others, is the re-baseline rate.
//
 {
    uint8_t     aKeysPhase = 0;
    uint16_t    aKeysPhasePhase8HitCounter16 = 0;
    
    aKeysPhase = gKeyPhases [gNowKey];
    aKeysPhasePhase8HitCounter16 = gKeyPhases3To2Or0Count16 [gNowKey];
    
    if ((0 == aKeysPhase) || (1 == aKeysPhase) || (2 == aKeysPhase))
    {
        aKeysPhase = aKeysPhase + 1;
    }
    else
    {
        aKeysPhase = 2;
        
        aKeysPhasePhase8HitCounter16 = aKeysPhasePhase8HitCounter16 + 1;
        if (aKeysPhasePhase8HitCounter16 > kTC_Meas_BaseLnReRate)
        {
            aKeysPhase = 0;
            aKeysPhasePhase8HitCounter16 = 0;
        }
    }
    
    gKeyPhases [gNowKey] = aKeysPhase;
    gKeyPhases3To2Or0Count16 [gNowKey] = aKeysPhasePhase8HitCounter16;
}



void maintain_legend_light_animation (void)
//
// Do the tasks of animation (various) of the main legend key cap LEDs,
// if so triggered. This routine should be called as often as possible
// from the main loop; if no animation is happening we try to exit fast.
//
{
    if (0 == gLegendLightAnimationState)
    {
        goto Exit;
    }
    
    // ----------------------------------------------------------------------
    
    if (kLegendLightCommand_Stop == gLegendLightAnimationState)   // Asked
    {
        LGD_BRIGHT;
        gLegendLightAnimationTimer = 0;
        gLegendLightAnimationState = 0;
        goto Exit;
    }
    
    // ----------------------------------------------------------------------
    
    if (kLegendLightCommand_DipOnce == gLegendLightAnimationState)  // Asked
    {
        LGD_DIMMED;
        gLegendLightAnimationTimer = kTC_Legend_DipDimTm;
        gLegendLightAnimationState = 20;
        goto Exit;
    }
    
    if (20 == gLegendLightAnimationState)
    {
        if (0 == gLegendLightAnimationTimer)
        {
            LGD_BRIGHT;
            gLegendLightAnimationState = 0;
            goto Exit;
        }
        gLegendLightAnimationTimer--;
        goto Exit;
    }
    
    // ----------------------------------------------------------------------
    
    if (kLegendLightCommand_BlinkCont == gLegendLightAnimationState)   // Asked
    {
        LGD_DIMMED;
        gLegendLightAnimationTimer = kTC_Legend_BlinkDimTm;
        gLegendLightAnimationState = 30;
        goto Exit;
    }
    
    if (30 == gLegendLightAnimationState)
    {
        if (0 == gLegendLightAnimationTimer)
        {
            LGD_BRIGHT;
            gLegendLightAnimationTimer = kTC_Legend_BlinkDimTm;
            gLegendLightAnimationState = 31;
            goto Exit;
        }
        gLegendLightAnimationTimer--;
        goto Exit;
    }
    
    if (31 == gLegendLightAnimationState)
    {
        if (0 == gLegendLightAnimationTimer)
        {
            LGD_DIMMED;
            gLegendLightAnimationTimer = kTC_Legend_BlinkDimTm;
            gLegendLightAnimationState = 30;
            goto Exit;
        }
        gLegendLightAnimationTimer--;
        goto Exit;
    }
    
    // ----------------------------------------------------------------------
    
    if (kLegendLightCommand_BlinkShort == gLegendLightAnimationState)   // Asked
    {
        LGD_DIMMED;
        gLegendLightAnimationTimer = kTC_Legend_ShortDimTm;
        gLegendLightAnimationState = 40;
        goto Exit;
    }
    
    if (40 == gLegendLightAnimationState)
    {
        if (0 == gLegendLightAnimationTimer)
        {
            LGD_BRIGHT;
            gLegendLightAnimationState = 0;
            goto Exit;
        }
        gLegendLightAnimationTimer--;
        goto Exit;
    }
    
    // ----------------------------------------------------------------------
    
    Exit:;
}



void scan_and_score_and_report (void)
//
// Main loop. Performs timing and scanning. Assumes global variables and
// arrays have been set up. Does a global loop and periodically dispatches
// to subroutines based on clock phases. Never returns.
//
{
    while (1)
    {
        // Do various measurements and collection/averaging calculations for
        // this key site, depending / driven by its per-key phase:
        //
        get_gNowPhase_for_gNowKey ();
        //
        if (0 == gNowPhase) { measure_baseline_dark_luma_of_current_key ();    }
        if (1 == gNowPhase) { measure_baseline_bright_luma_of_current_key ();  }
        if (2 == gNowPhase) { measure_probatory_dark_luma_of_current_key ();   }
        if (3 == gNowPhase) { measure_probatory_bright_luma_of_current_key (); }
        
        // Always take stock-1: Add/subtract a per-key score:
        //
        // Depending on what is going on with the measurements, try to see if
        // the measurements are consistent with a finger-down situation. This
        // determine-score routine will function differently according to
        // ambient lighting conditions which is globally set.
        //
        // Do this every iteration (do not divide the rate because else we're
        // just wasting measurements).
        //
        determine_score_of_current_key ();
        
        // Periodically take stock-2: Take action on integrated scores:
        //
        // This is time-divided to save compute resources and to let some
        // scoring integration happen. Driving by dividing the master clock.
        // With 0x007F, this runs at about a 48ms = 20Hz rate, asynchronous
        // with respect to the phase states above.
        //
        // This is a very average rate that fluctuates based on what we're
        // (how long it takes) going to run here. Here we run a score peak
        // detector to see what we have. We look at all key scores, pick a
        // maximum, and do other anti-false-positive processing. It is from
        // this subroutine cascade that final reporting is done (which is
        // why it's variable time).
        //
        if (0 == (gClock16 & kTC_Histo_GenRate))
        {
            maintain_histogram_of_peak_of_all_scores ();
        }
        //
        if (0 == (gClock16 & kTC_Histo_ActRate))
        {
            act_on_histogram_of_peak_of_all_scores ();            
        }
        
        // Every few seconds, see if we've moved into "sunlit" or "moonlit"
        // ambient lighting:
        //
        if (0 == (gClock16 & kTC_SunMode_ReRate))
        {
            maintain_ambient_lighting_mode ();
        }
        
        // Do cetain task timeslices as they need:
        //
        maintain_legend_light_animation ();
        maintain_keypress_debounce ();
        
        // Advance the phase for the current key being worked on.
        // Do this BEFORE advancing the current-key!:
        //
        advance_phase_for_gNowKey ();
        
        // Advance to looking at the next key position:
        //
        // gNowKey, gNowVddBank, gNowGndReturn -- all in lockstep.
        // We always advance in this scan cadence (by N key number),
        // regardless of what "purpose" we are looking at a particular key
        // for. For example, we might be measuring with its LED ON (bright
        // phase), or LED OFF, or the 2nd (for averaging) versions of the
        // same, or for a baseline dark field, etc. That is, instead of
        // doing such measurements en masse for all keys, we stagger/
        // amortize/interleave those activities.
        //
        // The iteration rate is about 140uS = 7.143KHz; thus the entire
        // keyboard is finishedly scanned at a rate of that /48keys =
        // 6.72ms = 148.8Hz. That is, the entire keyboard is completely
        // scanned in just under 7ms. But that does not include the scoring
        // and peak detection and interpretation rate, which are slower.
        //
        advance_gNowKey_and_gCoordinates ();
        
        // Advance the 16-bit master clock (0..65535 and repeat).
        // Same iteration rate as the innermost key iteration rate above.
        // That is, gClock16 increases by 1 about every 140uS:
        //
        gClock16 = gClock16 + 1;
    }
}



void initialize_arrays_and_globals (void)
{
    // Arrays are 8-bits unless named with 16, in which case they are 16-bits.
    // The 16-bit arrays are used to carry luma values from the ADC or time
    // phase counts where we want more than 255:
    //
    for (uint8_t i = 0;   i < 48;   i++)
    {       
        gKeyLumasForBaselineDarkMeas16   [i] = 0x0000;
        gKeyLumasForBaselineBrightMeas16 [i] = 0x0000;
        gKeyLumasForProbingDarkMeas16    [i] = 0x0000;
        gKeyLumasForProbingBrightMeas16  [i] = 0x0000;
         
        gKeyDownScores16 [i] = 0x00;
        gKeyDownPeakHits [i] = 0x00;
    }
    
    // Global timing of everything. These are always in lockstep. Each
    // one of these below increments by 1 each scan-loop iteration:
    //
    gClock16 = 0;               // 0..65535 and repeat
    gNowKey = 0;                // 0..47 and repeat; is the key site number
    gNowVddBank = 0;            // 0..5 and repeat; is the bank power supply
    gNowGndReturn = 0;          // 0..7 and repeat; is the within-bank gnd
    
    // Clear certain inputs/outputs:
    //
    gNowDark0Bright1 = 0;       // an input to the measure routine
    gError = 0;                 // set to 0 or to err by supporting routines
    
    // Clear global modes:
    //
    gModeMoonlit0Sunlit1 = 0;   // major environment mode
    gCurrentCaseUpper = false;
    
    // Arrange for staggerment of phases what happens at each key:
    //
    initialize_key_phases ();
}



void main (void)
{
    setup_hardware_configuration ();
        
    show_debug_burst (kTC_SoftwareVersion);
    
    initialize_arrays_and_globals ();
    
    scan_and_score_and_report ();  
}


