#define VERSION "\nDomeBuddy Controller CMHASD V2.220311 by [email protected]"
#define LCD_VERS "$$Version 2.230311$Nby Keith Rickard"
#include <Arduino.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>
// For Arduino Mega 2560
#define TEST          1
#define RESET_EEPROM  1

#pragma region DEFINITIONS
// PARAMETERS
#define ENC_DIR       1       // 0 = test rig, 1 = dome rig
#define MIN_RES       10000L  // Minimum acceptable dome rotation resolution
#define MTR_ALL       0
#define MTR_DIR1      1       // Ordinarily, 1 is CW > and 0 is CCW <
#define MTR_DIR2      1
#define MTR_DIR3      1
#define PERIOD_DEB    80
#define PERIOD_HOLD   500
#define PERIOD_CXN    5000   // Timeout period for not receiving comms from the dongle
#define BTBAUD        38400

#define ENC_ChA       21  // W  (SCL pin)
#define ENC_ChB       20  // Bk (SDA pin)
#define INDEX_SW      19  // Gy Homeswitch
#define BTSER_PIN1    18  // Y
#define BTSER_PIN2    17  // Gn1m
#define DIR1          8   // Motor 1
#define PWM1          9
#define DIR2          13   // Motor 2
#define PWM2          11
#define DIR3          12  // Motor 3
#define PWM3          10
#define ENGAGED       29  // Circuit for engagement of motors - 0 = means all are engaged
#define ENGAGED_GND   31
#define BTTXD         17  // RX1 pin  (BTRXD is not used as nothing needs to be transmitted)
#define BTSTAT        18
#define BUZZER        A1
#define BUZZER_GND    A0

#define LCD_VCC       5V  // R LCD pins (this definition is not used in sketch)
#define LCD_RS        23  // O
#define LCD_EN        25  // Y
#define LCD_D4        45  // Gn
#define LCD_D5        47  // Be
#define LCD_D6        49  // Pe
#define LCD_D7        51  // Gy
#define LCD_BL_A      53  // W
#define LCD_BL_K      GND // Bk VSS(1) = VE(3) = RW(5) = GND (this definition is not used in sketch)

#define BTN_L         A8  // W
#define BTN_L_GND     A9  // Bk
#define BTN_O         A10 // Pe
#define BTN_O_GND     A11 // Gy
#define BTN_E         A12 // R
#define BTN_E_GND     A13 // Bn
#define BTN_R         A14 // Be
#define BTN_R_GND     A15 // Gn

#define PRESSED       1
#define RELEASED      0

#define btnL    B01000
#define btnO    B00100
#define btnE    B00010
#define btnR    B00001
#define btnH    B10000
#define BLINK   B0001
#define CURSOR  B0010
#define DISP    B0100
#define LIGHT   B1000

volatile long currEnc, indexEnc[2];
volatile byte indexPtr;
long  prevEnc,
      currEncTest,
      prevMillis,
      enc180,
      encOne,
      targEnc,
      distEnc,
      stepDist,
      tempEnc,
      lastEnc,
      midPoint;
int   brakEnc[256];
bool  domeSafe = 0;
byte  testMode = TEST,
      domeAZM,
      targAZM,
      lastTargAZM,
      scopeAZM,           // 0 = N, 64 = E, 128 = S, 192 = W.
      dir,
      lastDir,
      flag, 
      pwm = 0,
      slaved = 0,
      parking = 0,
      btStat = 0,
      newData = 0,
      calibrating = 0,
      dispC = 0,
      dispR = 0,
      dispStatus = DISP,  //Bit 0 = cursor on/off, Bit 1 = blink on/off, Bit 2 = LCD on/off
      dispStatusBuf,
      btnsPressed = 0,
      btnsLast = 0, 
      btnsCurr = 0, 
      btnsDeb = 0, 
      scanStep = 0,
      encMoving = 0;
byte startPWM1, startPWM2, startPWM3, stopPWM1, stopPWM2, stopPWM3, maxPWM1, maxPWM2, maxPWM3;
char  buf[10],
      btnDBC = 0,
      disp[2][17],
      dispBuf[2][17];
unsigned long timerBtns,
              lcdTimer,
              cxnTimer;
struct ee{
  bool bzBtns = 1;
  bool bzAll = 1;
  bool bzMoves = 1;
  bool bzAlert = 1;
  bool bzComms =1;
  bool encDir = 0;               // 0 = clockwise, 1 = counter clockwise (test-rig)
  byte indexSw = 0;
  byte azmAdj = 0;               // 0 = azimuth is measured from N, 128 = from S
  byte dir1cw = MTR_DIR1;
  byte dir2cw = MTR_DIR2;
  byte dir3cw = MTR_DIR3;
  byte maxPWM1cw = 175;
  byte maxPWM2cw = 175;
  byte maxPWM3cw = 175;
  byte startPWM1cw = 90;
  byte startPWM2cw = 90;
  byte startPWM3cw = 90;
  byte stopPWM1cw = 90;
  byte stopPWM2cw = 90;
  byte stopPWM3cw = 90;
  byte maxPWM1ccw = 175;
  byte maxPWM2ccw = 175;
  byte maxPWM3ccw = 175;
  byte startPWM1ccw = 90;
  byte startPWM2ccw = 90;
  byte startPWM3ccw = 90;
  byte stopPWM1ccw = 90;
  byte stopPWM2ccw = 90;
  byte stopPWM3ccw = 90;
  byte rampSpeed = 15;
  byte stopSpeed = 1;
  int stuckTime = 4000;
  char password[7] = {"\0\0\0\0\0\0"};
  long lcdTimeout = 300000;             // 5 minutes time out (300 seconds)
  long enc360 = 46000;
  byte homeAZM = 96;
  int  addr = sizeof(ee);
} ee;

//SoftwareSerial btSerial(btTXD, btRXD);  // Bluetooth Module is in Master mode & bound to DomeBuddy Dongle's address
LiquidCrystal lcd(LCD_RS, LCD_EN, LCD_D4, LCD_D5, LCD_D6, LCD_D7);
#pragma endregion DEFINITIONS
//==============================================================================================================
#pragma region SETUP
void setup() {
  pinMode(ENC_ChA, INPUT_PULLUP);
  pinMode(ENC_ChB, INPUT_PULLUP);
  pinMode(INDEX_SW, INPUT_PULLUP);
  pinMode(DIR1, OUTPUT);
  pinMode(DIR2, OUTPUT);
  pinMode(DIR3, OUTPUT);
  pinMode(PWM1, OUTPUT);
  pinMode(PWM2, OUTPUT);
  pinMode(PWM3, OUTPUT);
  pinMode(BUZZER, OUTPUT);
  pinMode(BUZZER_GND, OUTPUT);
  pinMode(BTN_R, INPUT_PULLUP);
  pinMode(BTN_R_GND,OUTPUT);
  pinMode(BTN_O, INPUT_PULLUP);
  pinMode(BTN_O_GND, OUTPUT);
  pinMode(BTN_E, INPUT_PULLUP);
  pinMode(BTN_E_GND, OUTPUT);
  pinMode(BTN_L, INPUT_PULLUP);
  pinMode(BTN_L_GND, OUTPUT);
  pinMode(ENGAGED, INPUT_PULLUP);
  pinMode(ENGAGED_GND, OUTPUT);
  pinMode(BTSTAT, INPUT);
  pinMode(LCD_BL_A, OUTPUT);

  analogWrite(PWM1, 0);
  analogWrite(PWM2, 0);
  analogWrite(PWM3, 0);
  digitalWrite(DIR1, LOW);
  digitalWrite(DIR2, LOW);
  digitalWrite(DIR3, LOW);
  digitalWrite(BTN_R_GND, LOW);
  digitalWrite(BTN_O_GND, LOW);
  digitalWrite(BTN_E_GND, LOW);
  digitalWrite(BTN_L_GND, LOW);
  digitalWrite(BUZZER, LOW);
  digitalWrite(BUZZER_GND, LOW);
  digitalWrite(LCD_BL_A, HIGH);

  lcd.begin(16, 2);
  Serial.begin(230400);
  Serial.println(VERSION);
  Serial.println("Send ? for help");
  Serial2.begin(BTBAUD);                            // Open connection to Bluetooth module
  Serial2.setTimeout(500);
  ee.addr = sizeof(ee);
  #if RESET_EEPROM
    EEPROM.put(0, ee);
    EEPROM.put(ee.addr, 46000L / 2);
  #endif
  EEPROM.get(0, ee);
  EEPROM.get(ee.addr, prevEnc);                     // Get last known dome encoder position
  currEnc = prevEnc;
  setupISR();                                       // Activate encoders
  enc180 = ee.enc360 >> 1;                          // Value for 180 degrees (divide by 2)
  encOne = ee.enc360 >> 8;                          // Number of encoder counts for one azimuth reference point (divide by 256)
  scopeAZM = domeAZM = lastTargAZM = enc2azm(currEnc);
  lcdUDGs();
  do {
    LCD("$$ DICK CHAMBERS$N  OBSERVATORY"); sendDisp();
    beep(50, 1); delay(50); beep(50, 1); delay(50); beep(50, 1);
    lcdTimer = millis() + ee.lcdTimeout;
    waitBtns(2250);
    if (btnsLast & btnL && btnsLast & btnR) {
        for (byte i = 0; i <= 5; i++) ee.password[i] = 0;
        EEPROM.put(0, ee);
        LCD("$$PASSWORD RESET$NPress OK"); sendDisp();
        beep(1000, 1);
        waitOKesc(0);
    }
    if (btnsLast) break;
    LCD("$$   DOME BUDDY$N     CMHASD"); sendDisp();
    if (waitBtns(1500)) break;
    LCD(LCD_VERS); sendDisp();
    if (waitBtns(2000)) break;
    LCD("$$Credits:$NMartin Crow"); sendDisp();
    if (waitBtns(1500)) break;
    LCD("$$Martin Crow$NSteve Floodgate"); sendDisp();
    if (waitBtns(750)) break;
    LCD("$$Steve Floodgate$NAndy Barber"); sendDisp();
    if (waitBtns(750)) break;
    LCD("$$Andy Barber"); sendDisp();
    if (waitBtns(750)) break;
  } while (0);
}
#pragma endregion SETUP
//==============================================================================================================
#pragma region MAIN MENU
//--------------------------------------------------------------------------------------------------------------
void loop() {
  char *items[] = {"Track telescope",
                   "Move dome",
                   "Park dome",
                   "Home position",
                   "Dome safe: NO ",
                   "Sync to scope",
                   "Settings"};
  byte selection = 1;
  do{
    if (domeSafe) {
      *(items[4] + 11) = 'Y';
      *(items[4] + 12) = 'E';
      *(items[4] + 13) = 'S';
    } else {
      *(items[4] + 11) = 'N';
      *(items[4] + 12) = 'O';
      *(items[4] + 13) = ' ';
    }
    slaved = parking = calibrating = 0;
    selection =  menu(items, 7, selection);
    switch(selection & 15){
      case 1: aTrackTelescope();    break;
      case 2: manualMove(MTR_ALL);  break;
      case 3: aParkDome();          break;
      case 4: aSetHomePosn();       break;
      case 5: aConfirmSafety();     break;
      case 6: aSync2scope();        break;
      case 7: bSettings();          break;
    }
  } while (selection & 15);
}
//--------------------------------------------------------------------------------------------------------------
void aTrackTelescope() {
  if (!safe()) return;
  while (Serial2.available()) Serial2.read();             // Clear Bluetooth serial buffer (DongleBuddy)
  while (Serial.available()) Serial.read();               // Clear USB serial buffer (PC)
  scopeAZM = domeAZM;
  cxnTimer = millis() + PERIOD_CXN;
  slaved = 1;

  while (slaved == 1) {
    getScopeAZM();
    btCxn() ? LCD("$HAwaiting azmth ^") : LCD("$HConnecting...  _");
    if (disp[1][0] != 'A') LCD("$NAz:     Pt:    .");
    LCDintAt(1, 3, (360UL * byte(scopeAZM + ee.azmAdj) / 256.0), 3); LCD("` ");  // Azimuth in degrees
    LCDintAt(1, 11, domeAZM, 3); sendDisp();
    if (cxnTimer < millis()) {
        beep(100, ee.bzComms); if (!btCxn()) {delay(100); beep(100, ee.bzComms);}
        cxnTimer = millis() + PERIOD_CXN;
    }

    if (manualMove(MTR_ALL) & btnE) return;               // See if dome has been moved manually or if Esc has been pressed
    domeAZM = enc2azm(normEnc());                         // Normalise currEnc then map domeAZM from value 0 - 255
    if (pressedBtns() & btnO) {
      LCD("$HEncoder:       _");
      while (pressedBtns()) LCDintAt(0, 9, normEnc(), 5);
    }
    moveDome(scopeAZM, 1);
  }
}
//--------------------------------------------------------------------------------------------------------------
void aParkDome() {
  if (!safe()) return;
  if (domeAZM != ee.homeAZM) {
    LCD("$$Park Dome?$NPress OK or Esc"); sendDisp();
    if (waitOKesc (0) & btnE) return;
    slaved = parking = 1;
    while (parking && moveDome(ee.homeAZM, 0));
    if (!parking) return;
  }
  LCD ("$$DOME PARKED$");
  LCD("$NPress OK$F"); sendDisp();
  beep(1000, ee.bzAlert);
  waitOKesc(0);
}
//--------------------------------------------------------------------------------------------------------------
void aSetHomePosn() {
  LCD("$$Set as home$Nposition? OK/Esc");
  sendDisp();
  if (waitOKesc(0) & btnE) return;
  ee.homeAZM = enc2azm(normEnc());
  updateEEPROM(currEnc);
  LCD("$$Home position$Nhas been set. OK");
  sendDisp();
  while(waitOKesc(0) != btnO);
}
//--------------------------------------------------------------------------------------------------------------
void aConfirmSafety() {
  domeSafe = 0;
  LCD("$$Are all straps  $Nundone?       OK"); sendDisp();
  if (waitOKesc(0) & btnE) return;
  LCD("$$Are all wedges  $Nremoved?      OK"); sendDisp();
  if (waitOKesc(0) & btnE) return;
  LCD("$$Is the battery  $Ndisconnected? OK"); sendDisp();
  if (waitOKesc(0) & btnE) return;
  LCD("$$All obstructions$Nremoved?      OK"); sendDisp();
  if (waitOKesc(0) & btnE) return;
  LCD("$$Are all motors  $Nengaged?      OK"); sendDisp();
  if (waitOKesc(0) & btnE) return;
  beep(1000, 1);
  domeSafe = 1;
}
//--------------------------------------------------------------------------------------------------------------
bool safe(){
  if (domeSafe) return 1;
  for (byte i = 0; i <= 3; i++) {
    beep(100, 1);
    delay(100);
  }
  LCD("$$DOME NOT SAFE TO$NTURN.   Press OK"); sendDisp();
  waitOKesc(0);
  return 0;
}
//--------------------------------------------------------------------------------------------------------------
void aSync2scope() {
  char temp[2][17];
  strcpy(temp[0], disp[0]);
  strcpy(temp[1], disp[1]);
  if (!btCxn) {
    LCD("$$Can't sync dome.$NScope not found."); sendDisp();
  } 
  else {
    while(scanBtns());
    beep(250, ee.bzBtns);
    LCD("$$Sync dome to$Nscope?    OK/Esc");
    if (waitOKesc(0) & btnO) {
      currEnc = azm2enc(scopeAZM);
      domeAZM = scopeAZM;
      LCD("$$DOME SYNCHED$NTO TELESCOPE"); sendDisp();
    }
  }
  beep(3000, ee.bzAlert);
  LCD("$$"); LCD(temp[0]); LCD("$N"); LCD(temp[1]); sendDisp();
}
#pragma endregion MAIN MENU
//==============================================================================================================
#pragma region SETTINGS MENU
//--------------------------------------------------------------------------------------------------------------
void bSettings() {
  if (checkPassword()) return;  
  char *items[] = {"Calibrate dome",
                   "Motor setup",
                   "Buzzer",
                   "LCD timeout",
                   "Enc. direction",
                   "Index switch",
                   "Azimuth mode  ",
                   "Check hardware",
                   "Set password",
                   "Factory reset",
                   "Exit",
                   "About"};
  byte selection = 1;
  do{

    selection =  menu(items, 12, selection);
    switch(selection & 15){
      case 1: fCalibrateDome();     break;
      case 2: cMotorSetup();        break;
      case 3: eBuzzer();            break;
      case 4: bLCDTimeout();        break;
      case 5: bEncoderDirection();  break;
      case 6: bIndexSwitch();       break;
      case 7: bAzimuthMode();       break;
      case 8: gCheckHardware();     break;
      case 9: bSetPassword();       break;
      case 10: bFactoryReset();     break;
      case 11: return;              break;
      case 12: bShowVars();         break;
    }
  } while (selection & 15);
}
//--------------------------------------------------------------------------------------------------------------
void bLCDTimeout(){
	LCD("$$Enter seconds$Nfor timeout:");
	ee.lcdTimeout = getValue(ee.lcdTimeout / 1000, 999, 0, 1, 15) * 1000;
  EEPROM.put(0, ee);
	lcdTimer = millis() + ee.lcdTimeout;
}
//--------------------------------------------------------------------------------------------------------------
void bEncoderDirection() {
  bool mode = ee.encDir;
  while (1) {
    LCD("$$Enc. direction$NUse <> : ");
    mode ? LCD("CW  >") : LCD("CCW <");
    sendDisp();
    switch (waitBtns(0) & 15) {
      case btnL:  mode = 0; break;
      case btnR:  mode = 1; break;
      case btnE:  return;   break;
      case btnO:  ee.encDir = mode;
                  EEPROM.put(0, ee);
                  setupISR();
                  return;
                  break;
    }
  }
}
//--------------------------------------------------------------------------------------------------------------
void bIndexSwitch() {
  bool onOff = ee.indexSw;
  while (1) {
    LCD("$$Index sw active?$NUse <> : ");
    onOff ? LCD("Yes") : LCD("No");
    sendDisp();
    switch (waitBtns(0) & 15) {
      case btnL:  onOff^=1;  break;
      case btnR:  onOff^=1;  break;
      case btnE:  return;    break;
      case btnO:  ee.indexSw = onOff;
                  EEPROM.put(0, ee);
                  return;
                  break;
    }
  }
}
//--------------------------------------------------------------------------------------------------------------
void bAzimuthMode() {
  byte adj = ee.azmAdj;
  while (1) {
    LCD("$$Measure azmimuth$Nfrom ");
    adj == 128 ? LCD("South (<>)") : LCD("North (<>)");
    sendDisp();
    switch(waitBtns(0) & 15) {
      case btnL:  adj = 0; break;
      case btnR:  adj = 128; break;
      case btnE:  return;   break;
      case btnO:  ee.azmAdj = adj;
                  EEPROM.put(0, ee);
                  setupISR();
                  return;
                  break;
    }
  }
}
//--------------------------------------------------------------------------------------------------------------
void bSetPassword() {
  char pswd[] = "\0\0\0\0\0\0";
  LCD("$$Enter password:$N$B$C"); sendDisp();
  for (byte i = 0; i <= 5; i++) {
    switch (waitBtns(0)) {
      case btnL: pswd[i] = 'L'; break;
      case btnO: pswd[i] = 'O'; break;
      case btnE: pswd[i] = 'E'; break;
      case btnR: pswd[i] = 'R'; break;
      case btnE | btnH: return; break;
      default: break;
    }
    if (btnsLast == (btnO | btnH)) break;
    LCD("$N"); LCD(pswd); sendDisp();
  }
  strcpy(ee.password, pswd);
  if (!ee.password[0]) LCD("$$No password set.$NPress OK");
  else {
    LCD("$$$b$cPassword set to:$N"); LCD(ee.password); LCD(".");
  }
  sendDisp();
  EEPROM.put(0, ee);
  beep(500, ee.bzAlert);
  waitOKesc(3000);
}
//--------------------------------------------------------------------------------------------------------------
bool checkPassword () {
  if (ee.password[0] == '\0') return 0;                     // If no set password then return immediately
  LCD("$$Enter password:$N$B"); sendDisp();
  char pswd[] = "\0\0\0\0\0\0";
  byte len = strlen(ee.password);
  Serial.println(len);
  for (byte i = 0; i <= 5; i++)  {
    switch (waitBtns(0) & 15) {
      case btnL: pswd[i] = 'L'; break;
      case btnO: pswd[i] = 'O'; break;
      case btnE: pswd[i] = 'E'; break;
      case btnR: pswd[i] = 'R'; break;
    }
    if (btnsLast == (btnE | btnH)) return 1;
    LCD("$N"); LCD(pswd); sendDisp();
    if (i == len - 1) {
      if (strcmp(ee.password, pswd) == 0) return 0;       // Password is correct
    }
  };
  LCD("$H$bWrong password! "); sendDisp();
  waitOKesc(0);
  return 1;
}
//--------------------------------------------------------------------------------------------------------------
void bFactoryReset() {
  LCD("$$Factory reset$NAre you sure?");
  sendDisp();
  if (waitOKesc(0) == btnE) return;
  LCD("$$Press <, OK, Esc$Nthen > to reset.");
  sendDisp();
  if (waitBtns(0) != btnL) return;
  if (waitBtns(0) != btnO) return;
  if (waitBtns(0) != btnE) return;
  if (waitBtns(0) != btnR) return;
  LCD("$$RESETTING...");
  sendDisp();
  ee.bzBtns = 1;
  ee.bzAll = 1;
  ee.bzMoves = 1;
  ee.bzAlert = 1;
  ee.bzComms = 1;
  ee.encDir = 0;
  ee.indexSw = 0;
  ee.azmAdj = 0;
  ee.dir1cw = MTR_DIR1;
  ee.dir2cw = MTR_DIR2;
  ee.dir3cw = MTR_DIR3;
  ee.maxPWM1cw = 175;
  ee.maxPWM2cw = 175;
  ee.maxPWM3cw = 175;
  ee.startPWM1cw = 90;
  ee.startPWM2cw = 90;
  ee.startPWM3cw = 90;
  ee.stopPWM1cw = 90;
  ee.stopPWM2cw = 90;
  ee.stopPWM3cw = 90;
  ee.maxPWM1ccw = 175;
  ee.maxPWM2ccw = 175;
  ee.maxPWM3ccw = 175;
  ee.startPWM1ccw = 90;
  ee.startPWM2ccw = 90;
  ee.startPWM3ccw = 90;
  ee.stopPWM1ccw = 90;
  ee.stopPWM2ccw = 90;
  ee.stopPWM3ccw = 90;
  ee.password[0] = 0;
  ee.rampSpeed = 20;
  ee.stopSpeed = 1;
  ee.stuckTime = 4000;
  ee.lcdTimeout = 300000;
  ee.enc360 = 46000 * 20 /16;
  ee.homeAZM = 96;
  ee.addr = sizeof(ee);
  EEPROM.put(0, ee);
  updateEEPROM(17100L);
  currEnc = 0;
  beep(1000, 1);
  LCD("$$Factory reset is$Ncomplete.     OK");
  sendDisp();
  waitOKesc(0);
}
//--------------------------------------------------------------------------------------------------------------
void bShowVars() {
  char *items[] =  {"enc360         ",
                    "enc180         ",
                    "encOne         ",
                    "currEnc        ",
                    "ee.addr        ",
                    "ee.enc         ",
                    "homeAZM        ",
                    "domeAZM        ",
                    "scopeAZM       ",
                    "lcdTimeOut     ",
                    "lcdTimer       ",
                    "BTSTAT         ",
                    "btCxn()        ",
                    "indexSw        ",
                    "More...        ",
                    "Exit           "};
  byte selection = 1;
  do {
    embedValue(items[0], ee.enc360);
    embedValue(items[1], enc180);
    embedValue(items[2], encOne);
    getEnc();
    embedValue(items[3], currEnc);
    embedValue(items[4], ee.addr);
    long eeEnc;
    EEPROM.get(ee.addr, eeEnc);
    embedValue(items[5], eeEnc);
    embedValue(items[6], ee.homeAZM);
    embedValue(items[7], domeAZM);
    embedValue(items[8], scopeAZM);
    embedValue(items[9], ee.lcdTimeout / 1000);
    embedValue(items[10], (lcdTimer - millis()) / 1000);
    embedValue(items[11], digitalRead(BTSTAT));
    embedValue(items[12], btCxn());
    embedValue(items[13], ee.indexSw);
    selection = menu(items, 16, selection);
    switch(selection & 15){
      case 15: bShowVars2();  break;
      case 0: return;        break;
    }
  } while (selection & 15);
}
//--------------------------------------------------------------------------------------------------------------
void bShowVars2() {
  char *items[] =  {"startMtr1cw    ",
                    "startMtr2cw    ",
                    "startMtr3cw    ",
                    "startMtr1ccw   ",
                    "startMtr2ccw   ",
                    "startMtr3ccw   ",
                    "stopMtr1cw     ",
                    "stopMtr2cw     ",
                    "stopMtr3cw     ",
                    "stopMtr1ccw    ",
                    "stopMtr2ccw    ",
                    "stopMtr3ccw    ",
                    "testMode   LIVE",
                    "More...        ",
                    "Exit           "};
  byte selection = 1;
  do {
    embedValue(items[0], ee.startPWM1cw);
    embedValue(items[1], ee.startPWM2cw);
    embedValue(items[2], ee.startPWM3cw);
    embedValue(items[3], ee.startPWM1ccw);
    embedValue(items[4], ee.startPWM2ccw);
    embedValue(items[5], ee.startPWM3ccw);
    embedValue(items[6], ee.stopPWM1cw);
    embedValue(items[7], ee.stopPWM2cw);
    embedValue(items[8], ee.stopPWM3cw);
    embedValue(items[9], ee.stopPWM1ccw);
    embedValue(items[10], ee.stopPWM2ccw);
    embedValue(items[11], ee.stopPWM3ccw);
    if (testMode) strcpy(items[12], "testMode  TEST");
    selection = menu(items, 15, selection);
    switch(selection & 15){
      case 14: bShowVars3();  break;
      case 15: return;        break;
    }
  } while (selection & 15);
}
//--------------------------------------------------------------------------------------------------------------
void bShowVars3() {
  char *items[] =  {"maxMtr1cw     ",
                    "maxMtr2cw     ",
                    "maxMtr3cw     ",
                    "maxMtr1ccw    ",
                    "maxMtr2ccw    ",
                    "maxMtr3ccw    ",
                    "dir1cw        ",
                    "dir2cw        ",
                    "dir3cw        ",
                    "rampSpeed     ",
                    "stopSpeed     ",
                    "stuckTime     ",
                    "ee.encDir     ",
                    "ee.azmAdj     ",
                    "pswd    ROLLER",
                    "Exit          "};
  byte selection = 1;
  do {
    embedValue(items[0], ee.maxPWM1cw);
    embedValue(items[1], ee.maxPWM2cw);
    embedValue(items[2], ee.maxPWM3cw);
    embedValue(items[3], ee.maxPWM1ccw);
    embedValue(items[4], ee.maxPWM2ccw);
    embedValue(items[5], ee.maxPWM3ccw);
    embedValue(items[6], ee.dir1cw);
    embedValue(items[7], ee.dir2cw);
    embedValue(items[8], ee.dir3cw);
    embedValue(items[9], ee.rampSpeed);
    embedValue(items[10], ee.stopSpeed);
    embedValue(items[11], ee.stuckTime);
    items[12][12] = ee.encDir + '0';
    items[12][13] = ee.encDir ? '>' : '<';
    embedValue(items[13], ee.azmAdj);
    for(byte i =0; i <=5; i++) items[14][i + 8] = ee.password[i];
    selection = menu(items, 16, selection);
    switch(selection & 15){
      case 0: return; break;
    }
  } while (selection & 15);
}
#pragma endregion SETUP MENU
//==============================================================================================================
#pragma region MOTOR SETUP MENU
//--------------------------------------------------------------------------------------------------------------
void cMotorSetup() {
  char *items[] = {"Motor 1       ",
                   "Motor 2       ",
                   "Motor 3       ",
                   "Speed up rate ",
                   "Slow down rate",
                   "Stuck time    ",
                   "Start PWMs    ",
                   "Stop PWMs     ",
                   "Max PWMs      ",
                   "Exit          "};
  byte selection = 1;
  do{
    embedValue(items[5], ee.rampSpeed);
    embedValue(items[6], ee.stopSpeed);
    embedValue(items[7], ee.stuckTime / 1000);
    selection = menu(items, 10, selection);
    switch(selection & 15){
      case 1: cMotorMenu(1);    break;
      case 2: cMotorMenu(2);    break;
      case 3: cMotorMenu(3);    break;
      case 4: cSpeedUpRate();   break;
      case 5: cSlowDownRate();  break;
      case 6: cStuckTime();     break;
      case 7: dStartPWMs();     break;
      case 8: dStopPWMs();      break;
      case 9: dMaxPWMs();       break;
      case 10: return;          break;
    }
  } while (selection & 15);
}
//--------------------------------------------------------------------------------------------------------------
void cMotorMenu(byte mtr) {
  char *items[] = {"MOTOR 1 MENU  ",
                   "Turn motor    ",
                   "Align Motor   ",
                   "Direction    1",
                   "PWMS SPEEDS:  ",
                   "Start >       ",
                   "Stop  >       ",
                   "Max   >       ",
                   "Start <       ",
                   "Stop  <       ",
                   "Max   <       ",
                   "Exit          "};
  byte selection = 1;
  do {
    items[0][6] = '0' + mtr;
    switch (mtr) {
      case 1: items[3][13] =  ee.dir1cw + '0';
              embedValue(items[5], ee.startPWM1cw);
              embedValue(items[6], ee.stopPWM1cw);
              embedValue(items[7], ee.maxPWM1cw);
              embedValue(items[8], ee.startPWM1ccw);
              embedValue(items[9], ee.stopPWM1ccw);
              embedValue(items[10], ee.maxPWM1ccw);   break;
      case 2: items[3][13] =  ee.dir2cw + '0';
              embedValue(items[5], ee.startPWM2cw);
              embedValue(items[6], ee.stopPWM2cw);
              embedValue(items[7], ee.maxPWM2cw);
              embedValue(items[8], ee.startPWM2ccw);
              embedValue(items[9], ee.stopPWM2ccw);
              embedValue(items[10], ee.maxPWM2ccw);   break;
      case 3: items[3][13] =  ee.dir3cw + '0';
              embedValue(items[5], ee.startPWM3cw);
              embedValue(items[6], ee.stopPWM3cw);
              embedValue(items[7], ee.maxPWM3cw);
              embedValue(items[8], ee.startPWM3ccw);
              embedValue(items[9], ee.stopPWM3ccw);
              embedValue(items[10], ee.maxPWM3ccw);   break;
    }
    selection =  menu(items, 12, selection);
    switch (mtr) {
      case 1 :switch (selection & 15) {
                case 2: gMotor(PWM1, DIR1, 1);  break;
                case 3: cAlignMotor(1);         break;
                case 4: cDirMotor(1);           break;
                case 6: ee.startPWM1cw = setPWM(3, 1, ee.startPWM1cw, 0, ee.maxPWM1cw);     break;
                case 7: ee.stopPWM1cw = setPWM(5, 1, ee.stopPWM1cw, 0, ee.maxPWM1cw);       break;
                case 8: ee.maxPWM1cw = setPWM(1, 1, ee.maxPWM1cw, (ee.startPWM1cw > ee.stopPWM1cw) ? ee.startPWM1cw : ee.stopPWM1cw, 255); break;
                case 9: ee.startPWM1ccw = setPWM(4, 1, ee.startPWM1ccw, 0, ee.maxPWM1ccw);  break;
                case 10: ee.stopPWM1ccw = setPWM(6, 1, ee.stopPWM1ccw, 0, ee.maxPWM1ccw);    break;
                case 11: ee.maxPWM1ccw = setPWM(2, 1, ee.maxPWM1ccw, (ee.startPWM1ccw > ee.stopPWM1ccw) ? ee.startPWM1ccw : ee.stopPWM1ccw, 255); break;
                case 12: return; break;
              } break;
      case 2 :switch (selection & 15) {
                case 2: cAlignMotor(2);         break;
                case 3: gMotor(PWM2, DIR2, 2);  break;
                case 4: cDirMotor(2);           break;
                case 6: ee.startPWM2cw = setPWM(3, 2, ee.startPWM2cw, 0, ee.maxPWM2cw);     break;
                case 7: ee.stopPWM2cw = setPWM(5, 2, ee.stopPWM2cw, 0, ee.maxPWM2cw);       break;
                case 8: ee.maxPWM2cw = setPWM(1, 2, ee.maxPWM2cw, (ee.startPWM2cw > ee.stopPWM2cw) ? ee.startPWM2cw : ee.stopPWM2cw, 255); break;
                case 9: ee.startPWM2ccw = setPWM(4, 2, ee.startPWM2ccw, 0, ee.maxPWM2ccw);  break;
                case 10: ee.stopPWM2ccw = setPWM(6, 2, ee.stopPWM2ccw, 0, ee.maxPWM2ccw);    break;
                case 11: ee.maxPWM2ccw = setPWM(2, 2, ee.maxPWM2ccw, (ee.startPWM2ccw > ee.stopPWM2ccw) ? ee.startPWM2ccw : ee.stopPWM2ccw, 255); break;
                case 12: return; break;
              } break;
      case 3 :switch (selection & 15) {
                case 2: cAlignMotor(3);         break;
                case 3: gMotor(PWM3, DIR3, 3);  break;
                case 4: cDirMotor(3);           break;
                case 6: ee.startPWM3cw = setPWM(3, 3, ee.startPWM3cw, 0, ee.maxPWM3cw);     break;
                case 7: ee.stopPWM3cw = setPWM(5, 3, ee.stopPWM3cw, 0, ee.maxPWM3cw);       break;
                case 8: ee.maxPWM3cw = setPWM(1, 3, ee.maxPWM3cw, (ee.startPWM3cw > ee.stopPWM3cw) ? ee.startPWM3cw : ee.stopPWM3cw, 255); break;
                case 9: ee.startPWM3ccw = setPWM(4, 3, ee.startPWM3ccw, 0, ee.maxPWM3ccw);  break;
                case 10: ee.stopPWM3ccw = setPWM(6, 3, ee.stopPWM3ccw, 0, ee.maxPWM3ccw);    break;
                case 11: ee.maxPWM3ccw = setPWM(2, 3, ee.maxPWM3ccw, (ee.startPWM3ccw > ee.stopPWM3ccw) ? ee.startPWM3ccw : ee.stopPWM3ccw, 255); break;
                case 12: return; break;
              } break;
    }
    EEPROM.put(0, ee);
  } while (selection & 15);
}
//--------------------------------------------------------------------------------------------------------------
void cSpeedUpRate() {
  LCD("$$Speed up rate:");
  ee.rampSpeed = getValue(ee.rampSpeed, 100, 0, 1, 15);
  EEPROM.put(0, ee);
}
//--------------------------------------------------------------------------------------------------------------
void cSlowDownRate() {
  LCD("$$Slow down rate:");
  ee.stopSpeed = getValue(ee.stopSpeed, 100, 0, 1, 15);
  EEPROM.put(0, ee);
}
//--------------------------------------------------------------------------------------------------------------
void cStuckTime() {
  LCD("$$Enter stuck time");
  ee.stuckTime = getValue(ee.stuckTime / 1000, 30, 0, 1, 15) * 1000;
  EEPROM.put(0, ee);
}
//--------------------------------------------------------------------------------------------------------------
void cAlignMotor(byte mtr) {
  if (!safe()) return;
  LCD("$$IMPORTANT!$NRelease motor ");
  LCDint(mtr);
  sendDisp();
  if (waitOKesc(0) == btnE) return;
  if ((mtr - 1) >= 3) return;
  LCD("$$Press < or > to$Nto turn MOTOR ");
  LCDint(mtr);
  sendDisp();
  manualMove(mtr); // Turn motor until OK or ESC has been pressed
}
//--------------------------------------------------------------------------------------------------------------
void cDirMotor(byte mtr) {
  byte dirCW;                           // Ordinarily, 1 is CCW and 0 is CW
  switch (mtr) {
    case 1: dirCW = ee.dir1cw; break;
    case 2: dirCW = ee.dir2cw; break;
    case 3: dirCW = ee.dir3cw; break;
  }
  while (1) {
    LCD("$$CW dir input for$Nmotor ");
    LCDint(mtr);
    LCD(": "); LCDint(dirCW);
    LCD("    <>");
    sendDisp();
    switch(waitBtns(0) & 15) {
      case btnL:  dirCW = 0; break;
      case btnR:  dirCW = 1; break;
      case btnE:  return;    break;
      case btnO:  switch (mtr) {
                    case 1: ee.dir1cw = dirCW; break;
                    case 2: ee.dir2cw = dirCW; break;
                    case 3: ee.dir3cw = dirCW; break;
                  }
                  EEPROM.put(0, ee);
                  return;    break;
    }
  }
}
//--------------------------------------------------------------------------------------------------------------
void cMotorSetupOLD() {
  char *items[] = {"Start PWMs    ",
                   "Stop PWMs     ",
                   "Max PWMs      ",
                   "Speed up rate ",
                   "Slow down rate",
                   "Stuck time    ",
                   "Dir motor 1:  ",
                   "Dir motor 2:  ",
                   "Dir motor 3:  ",
                   "Align motor 1 ",
                   "Align motor 2 ",
                   "Align motor 3 ",
                   "Exit"};
  byte selection = 1;
  do{
    embedValue(items[6], ee.rampSpeed);
    embedValue(items[7], ee.stopSpeed);
    embedValue(items[8], ee.stuckTime / 1000);
    items[6][13] =  ee.dir1cw + '0';
    items[7][13] = ee.dir2cw + '0';
    items[8][13] = ee.dir3cw + '0';
    selection = menu(items, 13, selection);
    switch(selection & 15){
      case 1: dStartPWMs();     break;
      case 2: dStopPWMs();      break;
      case 3: dMaxPWMs();       break;
      case 4: cSpeedUpRate();   break;
      case 5: cSlowDownRate();  break;
      case 6: cStuckTime();     break;
      case 7: cDirMotor(1);     break;
      case 8: cDirMotor(2);     break;
      case 9: cDirMotor(3);     break;
      case 10: cAlignMotor(1);  break;
      case 11: cAlignMotor(2);  break;
      case 12: cAlignMotor(3);  break;
      case 13: return;          break;
    }
  } while (selection & 15);
}
//--------------------------------------------------------------------------------------------------------------
#pragma endregion MOTOR SETUP MENU
//==============================================================================================================
#pragma region MOTOR PWMS MENUS
//--------------------------------------------------------------------------------------------------------------
void dMaxPWMs() {
  char *items[] = {"MAX PWMS      ",
                   "Turn Motor 1  ",
                   "Motor 1 >     ",
                   "Motor 1 <     ",
                   "Turn Motor 2  ",
                   "Motor 2 >     ",
                   "Motor 2 <     ",
                   "Turn Motor 3  ",
                   "Motor 3 >     ",
                   "Motor 3 <     ",
                   "Exit"};
  byte selection = 1;
  do {
    embedValue(items[2], ee.maxPWM1cw);
    embedValue(items[3], ee.maxPWM1ccw);
    embedValue(items[5], ee.maxPWM2cw);
    embedValue(items[6], ee.maxPWM2ccw);
    embedValue(items[8], ee.maxPWM3cw);
    embedValue(items[9], ee.maxPWM3ccw);
    selection =  menu(items, 11, selection);
    switch (selection & 15) {
      case 2: gMotor(PWM1, DIR1, 1);  break;
      case 3: ee.maxPWM1cw = setPWM(1, 1, ee.maxPWM1cw, (ee.startPWM1cw > ee.stopPWM1cw) ? ee.startPWM1cw : ee.stopPWM1cw, 255); break;
      case 4: ee.maxPWM1ccw = setPWM(2, 1, ee.maxPWM1ccw, (ee.startPWM1ccw > ee.stopPWM1ccw) ? ee.startPWM1ccw : ee.stopPWM1ccw, 255); break;
      case 5: gMotor(PWM1, DIR2, 2);  break;
      case 6: ee.maxPWM2cw = setPWM(1, 2, ee.maxPWM2cw, (ee.startPWM2cw > ee.stopPWM2cw) ? ee.startPWM2cw : ee.stopPWM2cw, 255); break;
      case 7: ee.maxPWM2ccw = setPWM(2, 2, ee.maxPWM2ccw, (ee.startPWM2ccw > ee.stopPWM2ccw) ? ee.startPWM2ccw : ee.stopPWM2ccw, 255); break;
      case 8: gMotor(PWM1, DIR3, 3);  break;
      case 9: ee.maxPWM3cw = setPWM(1, 3, ee.maxPWM3cw, (ee.startPWM3cw > ee.stopPWM3cw) ? ee.startPWM3cw : ee.stopPWM3cw, 255); break;
      case 10: ee.maxPWM3ccw = setPWM(2, 3, ee.maxPWM3ccw, (ee.startPWM3ccw > ee.stopPWM3ccw) ? ee.startPWM3ccw : ee.stopPWM3ccw, 255); break;
      case 11: return; break;
    }
    EEPROM.put(0,ee);
  } while (selection & 15);
}
//--------------------------------------------------------------------------------------------------------------
void dStartPWMs() {
  char *items[] = {"START PWMS    ",
                   "Turn Motor 1  ",
                   "Motor 1 >     ",
                   "Motor 1 <     ",
                   "Turn Motor 2  ",
                   "Motor 2 >     ",
                   "Motor 2 <     ",
                   "Turn Motor 3  ",
                   "Motor 3 >     ",
                   "Motor 3 <     ",
                   "Exit"};
  byte selection = 1;
  do {
    embedValue(items[2], ee.startPWM1cw);
    embedValue(items[3], ee.startPWM1ccw);
    embedValue(items[5], ee.startPWM2cw);
    embedValue(items[6], ee.startPWM2ccw);
    embedValue(items[8], ee.startPWM3cw);
    embedValue(items[9], ee.startPWM3ccw);
    selection =  menu(items, 11, selection);
    switch (selection & 15) {
      case 2: gMotor(PWM1, DIR1, 1);  break;
      case 3: ee.startPWM1cw = setPWM(3, 1, ee.startPWM1cw, 0, ee.maxPWM1cw); break;
      case 4: ee.startPWM1ccw = setPWM(4, 1, ee.startPWM1ccw, 0, ee.maxPWM1ccw); break;
      case 5: gMotor(PWM2, DIR2, 2);  break;
      case 6: ee.startPWM2cw = setPWM(3, 2, ee.startPWM2cw, 0, ee.maxPWM2cw); break;
      case 7: ee.startPWM2ccw = setPWM(4, 2, ee.startPWM2ccw, 0, ee.maxPWM2ccw); break;
      case 8: gMotor(PWM3, DIR3, 3);  break;
      case 9: ee.startPWM3cw = setPWM(3, 3, ee.startPWM3cw, 0, ee.maxPWM3cw); break;
      case 10: ee.startPWM3ccw = setPWM(4, 3, ee.startPWM3ccw, 0, ee.maxPWM3ccw); break;
      case 11: return; break;
    }
    EEPROM.put(0,ee);
  } while (selection & 15);
}
//--------------------------------------------------------------------------------------------------------------
void dStopPWMs() {
  char *items[] = {"STOP PWMS     ",
                   "Turn Motor 1  ",
                   "Motor 1 >     ",
                   "Motor 1 <     ",
                   "Turn Motor 2  ",
                   "Motor 2 >     ",
                   "Motor 2 <     ",
                   "Turn Motor 3  ",
                   "Motor 3 >     ",
                   "Motor 3 <     ",
                   "Exit"};
  byte selection = 1;
  do {
    embedValue(items[2], ee.stopPWM1cw);
    embedValue(items[3], ee.stopPWM1ccw);
    embedValue(items[5], ee.stopPWM2cw);
    embedValue(items[6], ee.stopPWM2ccw);
    embedValue(items[8], ee.stopPWM3cw);
    embedValue(items[9], ee.stopPWM3ccw);
    selection =  menu(items, 11, selection);
    switch (selection & 15) {
      case 2: gMotor(PWM1, DIR1, 1);  break;
      case 3: ee.stopPWM1cw = setPWM(5, 1, ee.stopPWM1cw, 0, ee.maxPWM1cw); break;
      case 4: ee.stopPWM1ccw = setPWM(6, 1, ee.stopPWM1ccw, 0, ee.maxPWM1ccw); break;
      case 5: gMotor(PWM1, DIR2, 2);  break;
      case 6: ee.stopPWM2cw = setPWM(5, 2, ee.stopPWM2cw, 0, ee.maxPWM2cw); break;
      case 7: ee.stopPWM2ccw = setPWM(6, 2, ee.stopPWM2ccw, 0, ee.maxPWM2ccw); break;
      case 8: gMotor(PWM1, DIR3, 2);  break;
      case 9: ee.stopPWM3cw = setPWM(5, 3, ee.stopPWM3cw, 0, ee.maxPWM3cw); break;
      case 10: ee.stopPWM3ccw = setPWM(6, 3, ee.stopPWM3ccw, 0, ee.maxPWM3ccw); break;
      case 11: return; break;
    }
    EEPROM.put(0,ee);
  } while (selection & 15);
}
//--------------------------------------------------------------------------------------------------------------
void insertNo(byte i, char *item[], int v) {
  *(item[i] + 10) = char(int(v / 100) + 48);
  v = v - 100 * int(v / 100);
  *(item[i] + 11) = char(int(v / 10) + 48);
  v = v - 10 * int(v / 10);
  *(item[i] + 12) = char(int(v) + 48);
}
//--------------------------------------------------------------------------------------------------------------
byte setPWM(byte msg, byte motor, byte value, byte lower, byte upper) {
  switch (msg) {
    case 1: LCD("$$Enter max PWM  >$Nfor motor "); break;
    case 2: LCD("$$Enter max PWM  <$Nfor motor "); break;
    case 3: LCD("$$Enter start PWM>$Nfor motor "); break;
    case 4: LCD("$$Enter start PWM<$Nfor motor "); break;
    case 5: LCD("$$Enter stop PWM >$Nfor motor "); break;
    case 6: LCD("$$Enter stop PWM <$Nfor motor "); break;
  }
  LCDint(motor);
  LCD(":");
	return getValue(value, upper, lower, 1, 15);
}
#pragma endregion MOTOR PWMS MENU
//==============================================================================================================
#pragma region BUZZER MENU
//--------------------------------------------------------------------------------------------------------------
void eBuzzer() {
  char *items[] = {"All sounds     ",
                   "Buttons        ",
                   "Dome moves     ",
                   "Alerts         ",
                   "Comms warning  ",
                   "Exit           "};
  byte selection = 1;
  do{
 		*(items[0] + 13) = tick(ee.bzAll);
		*(items[1] + 13) = tick(ee.bzBtns);
		*(items[2] + 13) = tick(ee.bzMoves);
		*(items[3] + 13) = tick(ee.bzAlert);
		*(items[4] + 13) = tick(ee.bzComms);
    selection =  menu(items, 6, selection);
    switch(selection & 15){
		  case 1:	ee.bzAll   = 1^ee.bzAll   ;		break;
  		case 2:	ee.bzBtns  = 1^ee.bzBtns  ;		break;
      case 3: ee.bzMoves = 1^ee.bzMoves ;   break;
	 		case 4:	ee.bzAlert= 1^ee.bzAlert;		  break;
	  	case 5:	ee.bzComms = 1^ee.bzComms ;		break;
      case 6: return;                       break;
	  }
  } while (selection & 15);
  EEPROM.put(0, ee);
}
//--------------------------------------------------------------------------------------------------------------
char tick(bool t) {
	return t ? '\x01' : '\x1F';
}
#pragma endregion BUZZER MENU
//==============================================================================================================
#pragma region CALIBRATION MENU
//--------------------------------------------------------------------------------------------------------------
void fCalibrateDome() {
  char *items[] = {"Manual",
                   "Set resolution",
                   "Auto",
                   "Exit"};
  byte selection = 1;
  do{
    calibrating = 0;
    selection =  menu(items, 4, selection);
    switch(selection & 15){
      case 1: fManualCalib();     break;
      case 2: fSetResolution();   break;
      case 3: fAutoCalib();       break;
      case 4: return;             break;
    }
    if (calibrating) {
      calibrating = 0;                            // Finish off the calibration work
      enc180 = ee.enc360 >> 1;                    // Reference points for 180 degrees
      encOne = ee.enc360 >> 8;                    // Reference points for 1 degree
      ee.homeAZM = 0;                             // Home position is lost so set it to 0
      currEnc = 0;
      domeAZM = 0;
      normEnc();
      EEPROM.put(0, ee);                          // Save the new value of the dome resolution
      currEnc = enc180;                           // Set currEnc to mid point
      updateEEPROM(currEnc);
      LCD("$$Calibration done$NResolution:");
      LCDint(ee.enc360);
      sendDisp();
      while(scanBtns());
      waitOKesc(2000);
      LCD("$$Dome now needs$Nto be resynched.");
      sendDisp();
      waitOKesc(2000);
    }
  } while (selection & 15);  
}
//--------------------------------------------------------------------------------------------------------------
void fManualCalib() {
  if (!safe()) return;
  LCD("$$CALIBRATING DOME$NAre you sure?"); sendDisp();
  if (waitOKesc(0) & btnE) return;
  LCD("$$CALIBRATION MODE$NACTIVATED"); sendDisp();
  for (byte i = 1; i <= 10; i++) {                          // Give 10 quick beeps
    delay(50); beep(50, ee.bzAlert);
  }
  long enc360old = ee.enc360;
  getEnc();
  prevEnc = currEnc;
  do {
    calibrating = 1;
    currEnc = 0;
    if (manualMove(MTR_ALL) == btnE) break;
    ee.enc360 = abs(currEnc);                               // Calculate the new resolution
    if (ee.enc360 > MIN_RES && prevEnc != currEnc) return;  // A reasonable resoluton has been got
    ee.enc360 = enc360old;                                  // Restore enc360 with original value
    LCD("$$Bad resolution.$NRetry?    OK/Esc"); sendDisp();
  } while(!(waitOKesc(0) & btnE));                          // Loop if Esc button not pressed

  calibrating = 0;                                          // Calibration has been aborted
  getEnc();
  currEnc = prevEnc + currEnc;                              // Restore currEnc
  updateEEPROM(normEnc());
  ee.enc360 = enc360old;                                    // Restore original resolution
  LCD("$$CALIBRATION$NABORTED.      OK"); sendDisp();
  beep(1000, ee.bzAlert);
  waitOKesc(0);
  return;
}
//--------------------------------------------------------------------------------------------------------------
void fSetResolution() {
  LCD("$$Enter resolution");
  calibrating = 1;
  long value = getValue(ee.enc360, 99999L, MIN_RES, 1, 15); 
  if (value == ee.enc360) return;
  ee.enc360 = value;
}
//--------------------------------------------------------------------------------------------------------------
void fAutoCalib() {
  if (!ee.indexSw) {
    LCD("$$Index switch is$Nnot active.   OK");
    sendDisp();
    waitOKesc(0);
    return;
  }
  if (!safe()) return;
  LCD("$$Auto calibration$NPress OK or Esc");
  sendDisp();
  if (waitOKesc(0) & 15 == BTN_E) return;

  long prevEnc360 = ee.enc360;
  indexEnc[0] = indexEnc[1] = currEnc = 0;
  ee.enc360 = 99999;
  enc180 = ee.enc360 >> 1;                    // Reference points for 180 degrees
  encOne = ee.enc360 >> 8;                    // Reference points for 1 degree
  indexPtr = domeAZM = 0;
  calibrating = 1;
  attachInterrupt(digitalPinToInterrupt(INDEX_SW), indexISR, FALLING);
  moveDome(255, 0);
  if (indexPtr > 1) ee.enc360 = abs(indexEnc[1] - indexEnc[0]);
  else ee.enc360 = prevEnc360;                // Calibration aborted - nothing's changed
  detachInterrupt(digitalPinToInterrupt(INDEX_SW));
}
#pragma endregion CALIBRATION MENU
//==============================================================================================================
#pragma region CHECK HARDWARE
//--------------------------------------------------------------------------------------------------------------
void gCheckHardware() {
  char *items[] = {"Dongle test",
                   "Encoder test",
                   "Motor 1 test",
                   "Motor 2 test",
                   "Motor 3 test",
                   "All motors",
                   "Index switch",
                   "Buttons test",
                   "Exit"};
  byte selection = 1;
  do{
    selection =  menu(items, 9, selection);
    switch(selection & 15){
      case 1: gDongle();              break;
      case 2: gEncoder();             break;
      case 3: gMotor(PWM1, DIR1, 1);  break;
      case 4: gMotor(PWM2, DIR2, 2);  break;
      case 5: gMotor(PWM3, DIR3, 3);  break;
      case 6: gMotor(0, 0, 0);        break;
      case 7: gIndexSwitch();         break;
      case 8: gButtons();             break;
      case 9: return;                 break;
    }
  } while (selection & 15);  
}
//--------------------------------------------------------------------------------------------------------------
void gDongle() {
  byte  csr = 0,
        flag = 2;
  char c = 0;
  while (Serial2.available()) Serial2.read();           // Clear Bluetooth serial buffer (DongleBuddy)
  LCD("$$Connecting...");
  do {
    if (btCxn()) {
      LCD("$HReceiving:     ^$N");
      while (Serial2.available()) {
        for (byte i = 0; i <= 14; i++) disp[1][i] = disp[1][i + 1];
        disp[1][15] = Serial2.read();
        LCD("$N");
        LCD(disp[1]);
        delay(100);
        flag = 1;
      }
      if (flag == 1) {
        sendDisp();
        flag = 2;
      }
    }
    else if (flag == 2) {
      LCD("$HDongle not found");
      flag = 0;
      sendDisp();
    }
    scanBtns();
  } while (!(btnsLast & btnO) && !(btnsLast & btnE));
}
//--------------------------------------------------------------------------------------------------------------
void gEncoder() {
  long oldEnc = -1;
  LCD("$$");
  LCD(  "Encoder:");
  LCD("$NMax value:");
  LCDintAt(1, 11, ee.enc360 - 1, 5);
  do {
    LCDat(0, 8);
    PIND & B10 ? LCD("A") : LCD("a");
    PIND & B01 ? LCD("B") : LCD("b");
    if (oldEnc != normEnc()) {
      oldEnc = normEnc();
      LCDintAt(0, 11, oldEnc, 5);
      sendDisp();
    }
    scanBtns();
  } while (!(btnsLast & btnO) && !(btnsLast & btnE));
}
//--------------------------------------------------------------------------------------------------------------
void gMotor(int pwmPin, int dirPin, byte mtr) {
  if (!safe()) return;
  int   pwm = 0,
        oldPwm = 1;                               // 1 force the display to be sent on first pass
  bool  flag = 0;
  long  oldEnc = 0;
  LCD("$$IMPORTANT!");
  if(mtr) {
    LCD("$NRelease motor ");
    LCDint(mtr);
  }
  else LCD("$NRelease motors");
  sendDisp();
  beep(500, ee.bzAlert);
  if (waitOKesc(0) & btnE) return;
  LCD("$$Encoder:");
  LCD("$NDir: -  PWM:");
  LCDintAt(0, 11, normEnc(), 5);
  LCDintAt(1, 13, pwm, 3);
  sendDisp();
  do {
    setDirection(pwm < 0);
    pwm ? startEnc() : stopEnc();
    if (mtr) {
      analogWrite(pwmPin, abs(pwm));
    }
    else {
      analogWrite(PWM1, abs(pwm));
      analogWrite(PWM2, abs(pwm));
      analogWrite(PWM3, abs(pwm));
    }
    flag = 0;
    if (oldPwm != pwm || oldEnc != normEnc()) {
      oldPwm = pwm; oldEnc = normEnc();
      LCDintAt(0, 11, oldEnc, 5);
      LCDintAt(1, 13, abs(oldPwm), 3);
      LCDat(1, 5);
      if (!pwm) LCD(".");
      else (pwm > 0) ? LCD("<") : LCD(">");
      flag = 1;
    }
    if (flag) sendDisp();                       // Only send display to PC if finished changing
    btnsLast = pressedBtns();
    switch (btnsLast & B1111) {
      case btnL:
        if (pwm < 255) pwm++;
        delay(5);
        break;
      case btnR:
        if (pwm > -255) pwm--;
        delay(5);
        break;
      case btnO:
        pwm = abs(pwm);
        while (pwm) {
          pwm--;
          if (mtr) analogWrite(pwmPin, pwm);
          else {
            analogWrite(PWM1, pwm);
            analogWrite(PWM2, pwm);
            analogWrite(PWM3, pwm);
          }
          LCDintAt(0, 11, normEnc(), 5);
          LCDintAt(1, 13, abs(pwm), 3);
          delay(ee.stopSpeed);
        }
        break;
    }
  } while (!(btnsLast & btnE));
  pwm = abs(pwm);
  while (pwm) {
    pwm--;
    if (mtr) analogWrite(pwmPin, pwm);
    else {
      analogWrite(PWM1, pwm);
      analogWrite(PWM2, pwm);
      analogWrite(PWM3, pwm);
    }
    LCDintAt(0, 11, normEnc(), 5);
    LCDintAt(1, 13, abs(pwm), 3);
    delay(ee.stopSpeed);
  }
  beep(20, ee.bzAlert);
}
//--------------------------------------------------------------------------------------------------------------
void gIndexSwitch() {
  int lastIndexSw = 2;                    // '2' forces display to be sent to PC on first pass
  LCD("$$Index swtch test");
  LCD("$NStatus: ");
  do {
    if (lastIndexSw != digitalRead(INDEX_SW)) {
      lastIndexSw = digitalRead(INDEX_SW);
      LCDintAt(1, 8, digitalRead(INDEX_SW), 0);
      lastIndexSw ? LCD(" OPEN  ") : LCD(" CLOSED ");
      sendDisp();
    }
    scanBtns();
  } while (!(btnsLast & btnO) && !(btnsLast & btnE));
}
//--------------------------------------------------------------------------------------------------------------
void gButtons() {
  bool flag = 1;
  LCD("$$  Test buttons");
  LCD("$N   . .. ... .");
  sendDisp();
  while(1) {
    scanBtns();
    LCDat(1, 6);
    if (digitalRead(BTN_L)) {LCD("$N   < .. ... ."); flag = 1;}
    if (!digitalRead(BTN_O)) {LCD("$N   . OK ... ."); flag = 1;}
    if (!digitalRead(BTN_E)) {LCD("$N   . .. Esc ."); flag = 1;}
    if (digitalRead(BTN_R)) {LCD("$N   . .. ... >"); flag = 1;}
    if (!digitalRead(BTN_O) && !digitalRead(BTN_E)) return;
    if (flag) {sendDisp(); flag = 0;}
  }
}
#pragma endregion CHECK HARDWARE
//==============================================================================================================
#pragma region MENU HANDLING
//--------------------------------------------------------------------------------------------------------------
byte menu(char *item[], byte n, byte focus) {
//*item[] = list of menu items, n = no. of items,
//focus => bits 3-0: item no. in focus (to be arrowed) bits 7-4: item to show on top of LCD display
  byte  topLine = focus / 16,
        flag = 0;
  --focus &= 15;
  do{
    LCD("$$$b$c");                                                                    // Clear screen, no blink, no cursor
    for(byte i = 0; i < 2; i++){
      LCDat(i, 0);
      if ((i + topLine) == focus) LCD(">");
      else LCD(" ");
      LCD(*(item + i + topLine));
    };
    do {   
      LCDat(1, 15); btCxn() ? LCD("^") : LCD(" ");                                    // Wait for a button press
      sendDisp();
    } while (!scanBtns());
    beepBtns();
    switch (btnsLast){
      case btnL:      if(focus > 0) if(--focus<topLine) topLine = focus;              // Move up menu
                       break;
      case btnR:      if(focus < n-1) if(++focus > topLine + 1) topLine = focus - 1;  // Move down menu
                       break;
      case btnO:      return ++focus + topLine * 16;                                  // Return item no. selected
                      break;
      case btnO|btnH: return ++focus + topLine * 16;                                  // Return item no. selected
                      break;
    }
  } while(!(btnsLast & btnE));                                                    // Leave once OK or Esc pressed
  return 0;                                                                       // 0 means Esc has been pressed
}
//--------------------------------------------------------------------------------------------------------------
long getValue(long oldVal, long u, long l, byte r, byte c) {          // Value, upper limit, lower limit, row, column of last digit
  char buf[2][17];
	char val[6] = "00000";
	byte d = (u > 9) + (u > 99) + (u > 999) + (u > 9999) + (u > 99999);
	val[d + 1] = 0;							                                        // Reduce 'val; string length to the number of decimal digits of the upper limit
 	long v = oldVal;
	byte csr = 0;
	for(byte i = 0; i <= d; i++){			                                  // Convert Decimal > string of characters
		val[d - i] = v % 10 + '0';
		v /= 10;
	}
	
	LCD("$B");								                                          // Blinking cursor!
	while(true){
		if(btnsLast){
			LCDat(r, c-d);
			LCD(val);
			LCDat(r, c-d+csr);
			LCD("$C");
		}
    sendDisp();
    while(!scanBtns());
		switch (btnsLast){
			case btnE:		val[csr] -= (val[csr] >'0')-9*(val[csr] =='0');		// Increment digit
							sendDisp(); break;
			case btnO:		val[csr] += (val[csr] <'9')-9*(val[csr] =='9');		// Decrement digit
							sendDisp(); break;
			case btnR:		csr += (csr < d);                                 // Move cursor right
							sendDisp(); break;
			case btnL:		csr -= (csr > 0);                                 // Move cursor left
							sendDisp(); break;
			case btnE|btnH: return oldVal;										              // Escape and keep original value
			case btnO|btnH: v = 0;												                  // Try and accept value
							for(byte i = 0; i <=d; i++) v = v*10 +(val[i] - '0');
							if(v <= u && v >= l){
								LCD("$$Value has been$Nset to: ");
								LCDint(v);
                sendDisp();
								waitBtns(2000);
								return v;										                          // Return value if within limit
							}
              strcpy(buf[0], disp[0]);
              strcpy(buf[1], disp[1]);
              LCD("$$$cOut of range of$N");
              LCDint(l); LCD(" to "); LCDint(u);
              sendDisp();
							beep(200, ee.bzAlert);
							waitBtns(1000);
              LCD("$$");
              LCD(buf[0]);
              LCD("$N");
              LCD(buf[1]);
         			LCDat(r, c-d+csr);
              LCD("$C$B");
              sendDisp();
							break;
		}
	}
}
//--------------------------------------------------------------------------------------------------------------
void embedValue(char line[], long v) {
  char buf[10];
  byte ptr = 0;
  for (byte i = 13; i >= 0; i--) {
    if(isdigit(*(line + i))) {
      *(line + i) = ' ';
    }
    else break;
  }
  //Serial.println("embedValue()");
  //Serial.println(line);
  if (v < 0) {
    v = - v;
    buf[ptr++] = '-';
  }
  byte len = byte(log10(v));
  long div = 1;
  for (byte i = 1; i <= len; i++) div *= 10;
  for (byte i = 0; i <= len; i++) {
    buf[ptr++] = long(v / div) + '0';
    v -= int(v / div) * div;
    div /= 10;
  }
  for (byte i = 0; i < ptr; i++) {
    *(line + 14 - ptr + i) = buf[i];
  }
  //Serial.println(line);
}
#pragma endregion MENU HANDLING
//==============================================================================================================
#pragma region BUTTONS
byte scanBtns() {
  btnsLast = 0;
  byte btnsPressed = pressedBtns();
  switch(scanStep){
    case 0:
      if(btnsPressed){
        btnsDeb = btnsPressed;
        timerBtns = millis() + PERIOD_DEB;      //Eliminate Debounce
        scanStep = 1;
      }
      break;
    case 1:
      if(btnsPressed == btnsDeb){               //Check for no Debounce
        if(millis() > timerBtns){
          btnsCurr = btnsDeb;
          timerBtns = millis() + PERIOD_HOLD;
          scanStep = 2;                         // Now see if held
        }
      }
      else scanStep = 0;
      break;
    case 2:
      if(btnsPressed == btnsCurr){              // Check for Hold
        if(millis() > timerBtns){
          btnsCurr = btnsCurr|btnH;             // Buttons have been held long enough
          beep(100, ee.bzBtns);
          scanStep = 3;                         // Wait for release debounce
        }
      }
      else{                                     // Pressed buttons have changed
        if(btnsPressed) scanStep = 0;           // Any buttons pressed? Yes - start over
        else{                                   // Buttons are no longer pressed
          timerBtns = 0;
          scanStep = 3;                         // Wait for release debounce
        }
      }
      break;
    case 3:
      if(millis() > timerBtns){                 // Wait for release debounce
        if(btnsPressed) timerBtns = millis() + PERIOD_DEB;
        else{
          btnsLast = btnsCurr;
          scanStep = btnsCurr = 0;
          beepBtns();
        }
      }
      break;
    case 4:
      scanStep = 0;
      beepBtns();
  }
  return btnsLast;
}
//--------------------------------------------------------------------------------------------------------------
byte pressedBtns() {
  btnsPressed = 0;
  if (!scanStep) if (Serial.available()) {     // Handle PC "key" presses
    getScopeAZM();
    btnsPressed = btnsLast;
  }
  if (scanStep < 4) {
    if (testMode) {
      btnsPressed  = !digitalRead(BTN_O) * btnO; // Push-to-make button
      btnsPressed += !digitalRead(BTN_E) * btnE; // Push-to-make button
      btnsPressed += !digitalRead(BTN_R) * btnR; // Push-to-break button!
      btnsPressed += !digitalRead(BTN_L) * btnL; // Push-to-break button!
    } else {
      btnsPressed  = !digitalRead(BTN_O) * btnO; // Push-to-make button
      btnsPressed += !digitalRead(BTN_E) * btnE; // Push-to-make button
      btnsPressed +=  digitalRead(BTN_R) * btnR; // Push-to-break button!
      btnsPressed +=  digitalRead(BTN_L) * btnL; // Push-to-break button!
    }
  }
  if (btnsPressed == B1111) {
    LCD("$l");
    beep(1000, 1);
    asm volatile ("jmp 0");
    return;
  }

  if (btnsPressed) {
    LCD("$L");
    if (ee.lcdTimeout > 0) lcdTimer = millis() + ee.lcdTimeout; // A button has been pressed - reset display timeout
  }
  else if (millis() > lcdTimer && ee.lcdTimeout) LCD("$l");
  return btnsPressed; 
}
//--------------------------------------------------------------------------------------------------------------
byte waitBtns(unsigned long period) {
	if(!period) period = 0xFFFFFFFF;  // Wait for period in milliseconds, but indefinitely if period is 0 (or at most 49 days!).
  else period+= millis();
	while((millis() < period) && !scanBtns());
	return btnsLast;
}
//--------------------------------------------------------------------------------------------------------------
byte waitOKesc(unsigned long period){
	while((waitBtns(period) != btnO) && (btnsLast != btnE));
	if((btnsLast == btnO) | (btnsLast == btnE)) return btnsLast;
	return 0;
}
//--------------------------------------------------------------------------------------------------------------
void beepBtns(){
  beep(3, ee.bzBtns);
}
//--------------------------------------------------------------------------------------------------------------
void beep(int t, bool noise){
  if (ee.bzAll && noise) digitalWrite(BUZZER, HIGH);
  delay(t);
  digitalWrite(BUZZER, LOW);
}
#pragma endregion BUTTONS
//==============================================================================================================
#pragma region LCD STUFF
void LCD(char *p){
// Motor back EMF can crash the LCD display.  This function keeps a record of what has been printed to it and the
// cursor status so that the display can be restored.
  char c, e;
  for(byte i = 0; *(p + i) > 0; i++){
    c = *(p + i);
    if(c == '$'){                                                             // Handle LCD escape codes
      switch(*(p + ++i)) {
        case 'B': dispStatus |=  BLINK;  lcd.blink(); break;                  // Switch on blinking cursor
        case 'b': dispStatus &= !BLINK;  lcd.noBlink(); break;                // Switch off blinking cursor
        case 'C': dispStatus |=  CURSOR; lcd.cursor();  break;                // Switch on underscore cursor
        case 'c': dispStatus &= !CURSOR; lcd.noCursor(); break;               // Switch off underscore cursor
        case 'O': dispStatus |=  DISP;   lcd.display();  break;               // Switch on display
        case 'o': dispStatus &= !DISP;   lcd.noDisplay(); break;              // Switch off display
        case 'L': dispStatus |=  LIGHT;  digitalWrite(LCD_BL_A, HIGH); break; // Switch on backlight
        case 'l': dispStatus &= !LIGHT;  digitalWrite(LCD_BL_A, LOW); break;  // Switch off backlight
        case 'H': dispC = dispR = 0; lcd.home(); break;                       // Place cursor in top left corner
        case 'N': dispR = 1; dispC = 0; lcd.setCursor(dispC, dispR); break;   // Next line (CR & LF!)
        case 'F': for(byte j = dispC; j<16; j++){                             // Fill row with spaces to its end
                    lcd.print(disp[dispR][dispC++] = ' ');
                  };
                  break;
        case '$': strcpy(disp[0], "                ");                        // Clear screen
                  strcpy(disp[1], disp[0]);
                  dispC = dispR = 0;
                  dispStatus &= B0100;
                  lcd.clear();
                  lcd.noBlink();
                  lcd.noCursor();
                  break;
      }
    }
    else if((dispC < 16) & (dispC >=0)){
      switch (c) {
        case '}':  e = (*(p + ++i) - '0') * 16;
                   e += *(p + ++i) - '0';
                   c = char(e);
                  break;
        case '>': c = '\x7E';   break;  // Right arrow
        case '<': c = '\x7F';   break;  // Left arrow
        case '`': c = '\xDF';   break;  // Degree symbol
        case '^': c = '\x02';   break;  // Bluetooth Symbol
        case '\x1F': c = '\xEB'; break;
        default: break;
      }
      dispStatus &= B1111;
      disp[dispR][dispC++]= c;        //Place the next character into the display buffer
      lcd.write(byte(c));             //and then print it.
    }
  }
}
//--------------------------------------------------------------------------------------------------------------
void LCDat(byte r, byte c){           //Postion cursor correctly on the display buffer
  dispC = c & 15;
  dispR = r & 1;
  lcd.setCursor(dispC, dispR);
}
//--------------------------------------------------------------------------------------------------------------
void LCDint(long v){                   //Write a number to the display buffer
  char buf1[10], buf2[10];
  if (v < 0) {LCD("-"); v = -v;}
  if (v > 9999) {
    sprintf(buf1, "%04d", v % 10000);
    sprintf(buf2, "%d", long(v / 10000));
    LCD(buf2);
    LCD(buf1);
  }
  else {
    sprintf(buf1, "%d", v);
    LCD(buf1);
  }
  return;

  sprintf(buf, "%d", long(v));
  LCD(buf);
}
//--------------------------------------------------------------------------------------------------------------
void LCDintAt(byte r, byte c, long v, byte d) {
  long value = v;
  byte len = 1;
  if (v < 0) {
    len++;
    value = -v;
  }
  if (v) len += log10(value);
  LCDat(r, c);
  for (byte i = 0; i <= d - len - 1; i++) LCD(" ");
  LCDint(v);
}
//--------------------------------------------------------------------------------------------------------------
void sendDisp() {
  if (strcmp(dispBuf[0], disp[0]) != 0 || strcmp(dispBuf[1], disp[1]) != 0 || dispStatusBuf != dispStatus) {
    strcpy(dispBuf[0], disp[0]);
    strcpy(dispBuf[1], disp[1]);
    dispStatusBuf = dispStatus;
  } else return;                                // Return if LCD has not been updated

  char v;
  char buf[] = "\n+------------------+";
  Serial.print(buf);
  for (byte r = 0; r <= 1; r++) {
    Serial.print("\n| ");
    for (byte c = 0; c <= 15; c++) {
      v = disp[r][c];
      switch (v) {
        case '\x7E': v = '>';     break;
        case '\x7F': v = '<';     break;
        case '\xDF': v = '*';     break;
        case '\xEB': v = 'x';     break;
        case '\xFF': v = '\x01';  break;
        case '\x01': v = '/';     break;
        case '\x02': v = 'B';     break;
        case '\x03': v = '^';     break;
        case '\x04': v = 'v';     break;
        case '\x05': v = '-';     break;
      }
      Serial.write(v);
    }
    Serial.print(" |");
  }
  if (dispStatus & CURSOR) buf[dispC + 3] = '^';
  Serial.println(buf);
}
//--------------------------------------------------------------------------------------------------------------
void LCDrestore(){                    //Restore the LCD with the contents of the display buffer
  lcd.begin(16, 2);
  lcdUDGs();
  lcd.clear();
  lcd.print(disp[0]);
  lcd.setCursor(0, 1);
  lcd.print(disp[1]);
  lcd.setCursor(dispC, dispR);
  if(dispStatus & B0001) lcd.blink();
  if(dispStatus & B0010) lcd.cursor();
  if((!dispStatus) & B0100) lcd.noDisplay();
  digitalWrite(LCD_BL_A, dispStatus > B0111);   // Switch on (or off) LCD backlight
}
//--------------------------------------------------------------------------------------------------------------
void lcdUDGs(){
  byte data1[8] = {   // Tick
    B00000,
    B00001,
    B00010,
    B10100,
    B01000,
    B00000,
    B00000,
    B00000};
  byte data2[8] = {   // Bluetooth symbol
    B00100,
    B10110,
    B01101,
    B00110,
    B01101,
    B10110,
    B00100,
    B00000};
  byte data3[8] = {   // Up arrow
    B00100,
    B01110,
    B10101,
    B00100,
    B00100,
    B00100,
    B00100,
    B00000};
  byte data4[8] = {   // Down arrow
    B00100,
    B00100,
    B00100,
    B00100,
    B10101,
    B01110,
    B00100,
    B00000};
  byte data5[8] = {   // Top line
    B11111,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000,
    B00000};
  lcd.createChar(1,data1);  // Tick
  lcd.createChar(2,data2);  // Bluetooth symbol
  lcd.createChar(3,data3);  // Up arrow
  lcd.createChar(4,data4);  // Down arrow
  lcd.createChar(5,data5);  // Top line
}
#pragma endregion CONSOLE
//==============================================================================================================
#pragma region DOME MOTION
//--------------------------------------------------------------------------------------------------------------
byte moveDome(byte azm, byte tol) {             // Move dome to target azimuth (tol = tolerence)
  if (!safe()) return 0;
  if (domeAZM == azm || lastTargAZM == azm)  return 0; // Return if target is same as current dome azimuth
  targAZM = azm;
  // INITIALISE ------------------------------------------------------------------------------------------------
  targEnc = azm2enc(targAZM);                   // Convert target (0 - 255) into encoder reference point
  getEnc();
  distEnc = targEnc - currEnc;                  // Get distance to travel

  if (abs(distEnc) > enc180) {                  // Is the distance more than 180 degs?
    if (currEnc < enc180) currEnc += ee.enc360; // If currEnc is less than 180 degs then 360 degs needs to be added
    else targEnc += ee.enc360;                  // Otherwise 360 degs needs to be added to the targEnc
    distEnc = targEnc - currEnc;                // Recalculate distance (now <= 180 degs)
  }
  dir = (distEnc >= 0);                         // Get direction (TRUE = right (CW), FALSE = left (CCW) )
  prevEnc = currEnc;

//  if (tol) if (dir == lastDir) if (targAZM == lastTargAZM) return 0;  // Return if in tolerance of last targAZM 

  midPoint = targEnc - distEnc / 2;             // Calc mid point reference point between start and end positions
  for (int i = 0; i <= 255; i++) brakEnc[i] = 0;
  sendDisp();
  for (byte i; i <= 3; i++) {                   // Give audible warning - move about to take place
    beep(50, ee.bzMoves);
    delay(50);
  }
  unsigned long moveTimer = millis() + ee.stuckTime;
  lastEnc = prevEnc;
  pwm = 1;
  setDirection(dir);
  sendInfo("MOVE DOME - INITIALISE");

  LCD("$$T:    0 Tg:  0 _$NE:    0 Pt:  0 .");              // T = targEnc, TZ targAZM / E = normEnc(), AZ = domeAZM 
  LCDintAt(0, 2, norm(targEnc), 5);                         // Display Target Enc
  LCDintAt(0, 11, targAZM, 3); LCD(" }03");                 // Display targAZm + Ramp up arrow 
  LCDintAt(1, 2, norm(prevEnc) , 5);                        // Display currEnc
  LCDintAt(1, 11, domeAZM, 3); dir ? LCD(" >"): LCD(" <");  // Display domeAZM + Direction arrow
  sendDisp();

  // RAMP-UP ---------------------------------------------------------------------------------------------------
  do {                                          // Ramp-up the motors
    delay(ee.rampSpeed);                        // Delay creates a rate of ramping-up
    if (indexPtr > 2) {slaved = 0; break;}      // Auto calibration is complete
    if (pressedBtns()) {
      Serial.println("KEY PRESSED - MOVE ABORTED");
      slaved = 2; break;                        // Stop moving and slaving immediately if a button is pressed
    }
    startEnc();
    tempEnc = currEnc;                          // Grab currEnc
    if (dir)  if (tempEnc > midPoint) break;    // Mid point check for RIGHT (CW) - break out if reached
    if (!dir) if (tempEnc < midPoint )break;    // Mid point check for LEFT (CCW) - break out if reached
    stepDist = tempEnc - prevEnc;               // See how far the dome has moved
    prevEnc = tempEnc;                          // Remember current encoder value for next time

    if (lastEnc != prevEnc) {                   // See if the dome is stuck
      moveTimer = millis() + ee.stuckTime;      // Reset timer if not
      lastEnc = prevEnc;                        // Remember for next time
    }
    if((millis() > moveTimer)) {slaved = 0; break;}           // Dome is stuck - stop moving & slaving immediately
    targEnc -= brakEnc[pwm] = int(stepDist);                  // Update tragEnc - point for when to slow down
    analogWrite(PWM1, map(pwm, 0, 255, startPWM1, maxPWM1));  // Increase the motors' speed
    analogWrite(PWM2, map(pwm, 0, 255, startPWM2, maxPWM2));  // The 'map' function helps to account for diffs
    analogWrite(PWM3, map(pwm, 0, 255, startPWM3, maxPWM3));  // in speeds of the motors
    LCDintAt(1, 2, norm(currEnc), 5);                         // Display currEnc
    LCDintAt(1, 11, enc2azm(currEnc), 3);                     // Display domeAZM
  } while (++pwm);                                            // Loop if maximum speed not reached

  // SLEW ------------------------------------------------------------------------------------------------------
  if (!dir) targEnc += (encOne / 2);
  sendInfo("MOVE DOME - SLEWING");
  LCDat(0, 15); LCD("}05");                                               // Top line character - top speed
  sendDisp();
  //  if (abs(distEnc) > encOne * 2) targEnc += (dir ? encOne : -encOne) / 2; // (CW : CCW) - adj for moves > 2 ref points
  prevEnc = targEnc;

  // SLOW DOWN -------------------------------------------------------------------------------------------------
  getEnc();
  lastEnc = currEnc;
  moveTimer = millis() + ee.stuckTime;
  byte firstPass = 1;
  while (--pwm) {                                                     // Slow down the motors
    if (indexPtr > 2) slaved = 0;
    if (slaved != 1) {
      delay(slaved == 2 ?  1 :ee.stopSpeed);                          // If slew aborted (slaved = 0) then just slow down (2 = keypressed)
      getEnc();
      LCDintAt(1, 2, norm(currEnc), 5);
      LCDintAt(1, 11, enc2azm(currEnc), 3);
    }
    else {                                                            // Else do a controlled slow down
      while (!pressedBtns()) {                                        // Wait for encoder to reach the value for this PWM step
        getEnc();
        if (dir)  if (currEnc > targEnc) break;                       // RIGHT (CW) - slew complete when targEnc reached
        if (!dir) if (currEnc < targEnc) break;                       // LEFT (CCW) - slew complete when targEnc reached
        if (currEnc != lastEnc) moveTimer = millis() + ee.stuckTime;  // If encoder is moving then moveTimer is reset
        if (!(slaved = (millis() < moveTimer))) break;                // If no motion detected within STUCK_TIME then abort
        lastEnc = currEnc;                                            // Remember current encoder value for next time round
        LCDintAt(1, 2, norm(currEnc), 5);
        LCDintAt(1, 11, enc2azm(currEnc), 3);
      }
      if (btnsPressed) slaved = 2;
    }
    if (firstPass) {
      sendInfo("MOVE DOME - SLOW DOWN");
      LCDat(0, 15); LCD("}04");                                     // Ramping down arrow
      sendDisp();
      firstPass = 0;
    }

    targEnc += brakEnc[pwm];                                        // Upate targEnc
    analogWrite(PWM1, map(pwm, 0, 255, stopPWM1, maxPWM1));         // Slow down the motors
    analogWrite(PWM2, map(pwm, 0, 255, stopPWM2, maxPWM2));
    analogWrite(PWM3, map(pwm, 0, 255, stopPWM3, maxPWM3));
  };                                                                // When pwm is 0 then the motors are turned off
  
  // FINISH-UP -------------------------------------------------------------------------------------------------
  analogWrite(PWM1, 0);                             // Switch off the motors completely
  analogWrite(PWM2, 0);
  analogWrite(PWM3, 0);
  stopEnc();
  domeAZM = enc2azm(normEnc());                     // Normalise currEnc and update domeAZM
  LCDintAt(1, 2, currEnc, 5);
  LCDintAt(1, 11, enc2azm(currEnc), 3);

  updateEEPROM(currEnc);                            // Update EEPROM with current Dome Azimuth
  LCDat(0, 15); LCD("_");                           // Dome finished moving
  LCDat(1, 15); LCD(".");
  sendInfo("MOVE DOME - FINISH UP");

  if (!slaved) {
    if (indexPtr > 1) LCD("$CALIBRATION DONE");
    else LCD("$HDOME IS STUCK$F");
  }
  if (slaved == 2) parking ? LCD("$HPARKING ABORTED") : LCD("$HTRACKING ABORTED");
  if (slaved != 1) {
    LCD("$NPress OK or ESC ");
    sendDisp();
    beep(1000, ee.bzAlert);
    if (!slaved) {
      while (!waitOKesc(1)) {
        beep(1000, ee.bzAlert);            // Dome is stuck - beep continuously
      }
    }
    else waitOKesc(0);
    beep(1, ee.bzAlert);
    parking = slaved = 0;                           // Stop reacting to received azimuth from scope
  }
  cxnTimer = millis() + PERIOD_CXN;
  lastDir = dir;
  lastTargAZM = targAZM;
  
  if (slaved == 2) return slaved;                   // A button was pressed so stop slaving
  return !((byte(domeAZM - targAZM) <= tol) || (byte(targAZM - domeAZM) <= tol)); // 0 if within tolerance, 1 if not

  //return domeAZM != targAZM;                        // Return 0 if domeAZM is same as target azimuth, 1 if not
}
//--------------------------------------------------------------------------------------------------------------
void sendInfo(char msg[]) {
  Serial.println(msg);
  Serial.print("domeAZM:  "); Serial.println(domeAZM);
  Serial.print("targAZM:  "); Serial.println(targAZM);
  Serial.print("lastTarg: "); Serial.println(lastTargAZM);
  Serial.print("prevEnc:  "); Serial.println(prevEnc);
  Serial.print("lastEnc:  "); Serial.println(lastEnc);
  getEnc();
  Serial.print("currEnc:  "); Serial.println(currEnc);
  Serial.print("targEnc:  "); Serial.println(targEnc);
  Serial.print("distEnc:  "); Serial.println(distEnc);
  Serial.print("midPoint: "); Serial.println(midPoint);
  Serial.print("dir:      "); Serial.print(dir); Serial.print(" "); Serial.println(dir ? "> CW" : "< CCW");
  Serial.print("pwm:      "); Serial.println(pwm);
  Serial.print("slaved:   "); Serial.println(slaved);
}
//--------------------------------------------------------------------------------------------------------------
byte manualMove(byte mtr) { // return
  if (!safe()) return btnE;
  byte button, pwm;
  if (slaved) {
    button = pressedBtns();
    if (!(button & (btnL | btnR))) return button;     // Return if Left nor Right buttons pressed
    LCD("$HResynching dome "); sendDisp();
  } else {
    LCD("$$Press < or > to$Nrotate the dome"); sendDisp();

    while(scanBtns());                                // Wait for all buttons to be released.
  }
  while(1) {                                          // Main loop
    btnsCurr = btnsDeb = btnsLast = scanStep = 0;
    pwm = 1;
    do {
      button = pressedBtns();
      if (button) beep(50, ee.bzBtns);
      if (button & btnE || button & btnO) {           // Esc button pressed?
        while(scanBtns());
        delay(500);
        while(scanBtns());
        return btnE;                                  // Exit
      }
      //if ((button & btnO) && slaved) return btnO;
      if (slaved) if (!(button & (btnR | btnL))) return btnO;
    } while (!(button & (btnR | btnL)));
    getEnc();
    LCD("$N$F");
//  "PWM 123 DZ 123 _" 
//   0123456789ABCDEF
    if (disp[1][0] != 'P') LCD("$NPWM:    ");
    if (!slaved) {
      if (disp[0][0] != 'E') LCD("$HEncoder:$F");
      if (disp[1][8] != 'P') if (!calibrating) LCDat(1, 8); LCD("Pt:");
      LCDintAt(0, 9, calibrating ? currEnc :normEnc(), 5);
      if (!calibrating) LCDintAt(1, 11, enc2azm(norm(currEnc)), 3);
    }
    if (!slaved) {LCDat(0, 15); LCD("}03"); sendDisp();}      // Ramping up arrow
    LCDat(1, 14); (button & btnR) ? LCD(" >") : LCD(" <");    // Direction arrow.
    sendDisp();
    setDirection(button & btnR);                              // CW is '>' if pressed, CCW if  '<'

    // START-UP ------------------------------------------------------------------------------------------------
    do {                                                      // Ramp-up the motor(s)
      delay(ee.rampSpeed);
      if (pressedBtns() != button) break;                     // Break out if a button is no longer pressed
      LCDintAt(1, 4, pwm, 3);
      startEnc();
      if (!slaved) {
        LCDintAt(0, 9, calibrating ? currEnc :normEnc(), 5);
        if (!calibrating) LCDintAt(1, 11, enc2azm(norm(currEnc)), 3);
      }            
      if (mtr == 1 || !mtr) analogWrite(PWM1, map(pwm, 0, 255, startPWM1, mtr ? startPWM1 : maxPWM1));  // mtr > 0 = align mtr
      if (mtr == 2 || !mtr) analogWrite(PWM2, map(pwm, 0, 255, startPWM2, mtr ? startPWM2 : maxPWM2));
      if (mtr == 3 || !mtr) analogWrite(PWM3, map(pwm, 0, 255, startPWM3, mtr ? startPWM3 : maxPWM3));
    } while(++pwm);                                           // Loop if maximum speed not reached

    // CONTINUE --------------------------------------------------------------------------------------------------
    if (!slaved) {LCDat(0, 15); LCD("}05"); sendDisp();}                // Top line
    while (pressedBtns() == button) {
      if (!slaved) {
        getEnc();
        LCDintAt(0, 9, calibrating ? currEnc :normEnc(), 5);            // Keep the dome turning while button is being pressed
        if (!calibrating) LCDintAt(1, 11, enc2azm(norm(currEnc)), 3);
      }
    }     
    sendDisp();
    beepBtns();

    // SLOW DOWN -------------------------------------------------------------------------------------------------
    if (!slaved) {LCDat(0, 15); LCD("}04");}                            // Down arrow
    do {
      pwm--;
      if (mtr == 1 || !mtr) analogWrite(PWM1, map(pwm, 0, 255, stopPWM1, mtr ? stopPWM1 : maxPWM1));  // mtr > 0 = align mtr
      if (mtr == 2 || !mtr) analogWrite(PWM2, map(pwm, 0, 255, stopPWM2, mtr ? stopPWM2 : maxPWM2));
      if (mtr == 3 || !mtr) analogWrite(PWM3, map(pwm, 0, 255, stopPWM3, mtr ? stopPWM3 : maxPWM3));
      delay(ee.stopSpeed);
      LCDintAt(1, 4, pwm, 3);
      if (!slaved) {
        getEnc();
        LCDintAt(0, 9, calibrating ? currEnc :normEnc(), 5);            // Keep the dome turning while button is being pressed
        if (!calibrating) LCDintAt(1, 11, enc2azm(norm(currEnc)), 3);
      }
    } while(pwm);

    // FINISH-UP --------------------------------------------------------------------------------------------------
    pwm = 0;
    analogWrite(PWM1, 0);                                   // Switch off the motors completely
    analogWrite(PWM2, 0);
    analogWrite(PWM3, 0);
    stopEnc();
    if (!slaved) {LCDat(0, 15); LCD("_");}
    LCDat(1, 15); LCD("."); sendDisp();
    if (!calibrating) {                                     // Do stuff if not calibrating
      if (!slaved) domeAZM = enc2azm(normEnc());            // If not slaving, normalise currEnc
      else currEnc = azm2enc(domeAZM);                      // Make currEnc be encoder value for domeAZM
      updateEEPROM(currEnc);
    }
  }
}
//--------------------------------------------------------------------------------------------------------------
void setDirection(byte setDir) {
  dir = setDir;
  digitalWrite(DIR1, ee.dir1cw ? dir^1 : dir);   // Select direction on the motor control board
  digitalWrite(DIR2, ee.dir2cw ? dir^1 : dir);   // Select direction on the motor control board
  digitalWrite(DIR3, ee.dir3cw ? dir^1 : dir);   // Select direction on the motor control board
  if (dir) {                                      // Is dome to move CW (right) ?
    startPWM1 = ee.startPWM1cw;
    startPWM2 = ee.startPWM2cw;
    startPWM3 = ee.startPWM3cw;
    stopPWM1 = ee.stopPWM1cw;
    stopPWM2 = ee.stopPWM2cw;
    stopPWM3 = ee.stopPWM3cw;
    maxPWM1 = ee.maxPWM1cw;
    maxPWM2 = ee.maxPWM2cw;
    maxPWM3 = ee.maxPWM3cw;
  } else {                                        // Or is dome to move CCW (left)?
    startPWM1 = ee.startPWM1ccw;
    startPWM2 = ee.startPWM2ccw;
    startPWM3 = ee.startPWM3ccw;
    stopPWM1 = ee.stopPWM1ccw;
    stopPWM2 = ee.stopPWM2ccw;
    stopPWM3 = ee.stopPWM3ccw;
    maxPWM1 = ee.maxPWM1ccw;
    maxPWM2 = ee.maxPWM2ccw;
    maxPWM3 = ee.maxPWM3ccw;
  }
}
#pragma endregion DOME MOTION
//==============================================================================================================
#pragma region DOME BUDDY STUFF
//--------------------------------------------------------------------------------------------------------------
bool getScopeAZM() {                                            // Get and interpret scope's azimuth from DomeBuddy Dongle via Bluetooth
  newData = 0;
  for (byte i = 0; i <= 9; i++) buf[i] = 0;
  if (getFromUSB()) return newData = 0;                         // Return if a "button" has been pressed
  if (!buf[0]) if (!getFromBT()) return newData = 0;            // No valid data received
  if (parking || !slaved) return;

  LCD("$H$F$H");
  if (buf[1] ==  'T' || buf[1] == 'G') {
    byte value, chksum;                                           // Telescope is tracking or doing a GO TO
    newData = 0;
    value = buf[2] - ((buf[2] >= 'A') ? 55 : 48);                 // Get telescope azimuth into value (from hex)
    value = value * 16 + buf[3] - ((buf[3] >= 'A') ? 55 : 48);
    chksum = buf[4] - ((buf[4] >= 'A') ? 55 : 48);                // Calculate the checksum (from hex)
    chksum = chksum * 16 + buf[5] - ((buf[5] >= 'A') ? 55 : 48);
    if (value == byte(~chksum)) {                                 // Is the data valid?
      scopeAZM = value;                                           // Azimuth has been received correctly
      LCD("Go to azmth:"); LCDint((360UL * byte(scopeAZM + ee.azmAdj) / 256.0)); LCD("`");
      newData = 1;
    }
    else LCD("BAD CHECK SUM!");
  }
  else {  
    switch (buf[1]) {
      case 'S' : LCD("Scope is moving.");   newData = 1; break;   // Telescope is slewing
      case 'L' : LCD("Scope not on?");      newData = 0; break;   // Lost connection with telescope - last known position given
      default  : LCD("DATA NOT VALID!");    newData = 0; break;
    }
  }
  sendDisp();
  delay(1000);
  cxnTimer = millis() + PERIOD_CXN;                            // Reset connection timer for comms timeout
  return newData;                                              // Valid data has been received
}
//--------------------------------------------------------------------------------------------------------------
bool getFromUSB() {                               // Returns 1 for "key pressed", 0 if not
  if (!Serial.available()) return 0;
  char peek = Serial.peek();
  if (peek == '\n' || peek == '\r') {
    Serial.read();
    return 0;
  }
  if (peek == ':' || isDigit(peek)) {
    Serial.readBytesUntil('#', buf, 7);           // Read in simulated dongle data
    if (peek == ':') return 0;
    int azm = 0;                                  // Else form and return :T00FF# or similar
    for (byte i = 0; i <= 2; i++) {               // A number between 0 and 255 has been given
      if (!isdigit(buf[i])) break;
      azm = azm * 10 + (buf[i] - '0');
    }
    if (azm > 255) return buf[0] = 0;
    sprintf(buf, ":T%02X%02X#", azm, ~azm & 255);
    return 0;                                     // "Scope" azimuth received. 0 means no "key pressed"
  }

  switch (Serial.read()) {
    case '\n': return 0; break;
    case '\r': return 0; break;
    case '?': help();     return 0; break;
    case 'D': sendDisp(); return 0; break;
    case 'd': sendDisp(); return 0; break;
    case 'V': sendVars(); return 0; break;
    case 'v': sendVars(); return 0; break;
    case 'A': sendBrakEnc(); return 0; break;
    case 'C': sendEEPROM(); return 0; break;
    case '<': btnsLast = btnL; scanStep = 4; return 1; break;
    case '>': btnsLast = btnR; scanStep = 4; return 1; break;
    case ',': btnsLast = btnL; scanStep = 4; return 1; break;
    case '.': btnsLast = btnR; scanStep = 4; return 1; break;
    case 'O': btnsLast = btnO; scanStep = 4; return 1; break;
    case 'E': btnsLast = btnE; scanStep = 4; return 1; break;
    case 'o': btnsLast = btnO|btnH; scanStep = 4; return 1; break;
    case 'e': btnsLast = btnE|btnH; scanStep = 4; return 1; break;
    case 'X': btStat = 0; Serial.println("Live BT mode"); return 0; break;
    case 'x': btStat = 0; Serial.println("Live BT mode"); return 0; break;
    case 'B': btStat = 2; Serial.println("Simulate BT connected"); return 0; break;
    case 'b': btStat = 1; Serial.println("Simulate BT not connected");return 0; break;
    case 'T': testMode = 1; Serial.println("TEST MODE ACTIVE"); return 0; break;
    case 't': testMode = 0; Serial.println("LIVE MODE ACTIVE"); return 0; break;
  }
}
//--------------------------------------------------------------------------------------------------------------
bool getFromBT() {
  if (!btCxn()) return 0;                                       // Return immediately if no Bluetooth connection
  if (btStat) return 0;                                         // Return immediately if in simulated mode
  for (byte i = 0; i <= 9; i++) buf[i] = 0;
  do {
    while (Serial2.peek() >= 0){                                // Find the sentence start marker ':'
      if (Serial2.peek() == ':') break;                         // Found the marker so break out
      if (Serial2.peek() < 0) return;                           // Return if timed out
      Serial2.read();                                           // Dump the invalid character in the receive buffer
    }                                                           // Loop back and try again
    Serial2.readBytesUntil('#', buf, 7);                        // Get azmiuth info (7 bytes)
  } while (Serial2.peek() >= 0);
  return buf[0] > 0;
}
//--------------------------------------------------------------------------------------------------------------
long normEnc() {
  getEnc();
  if (calibrating) return currEnc;
  long value = currEnc;                                       // Normalises currEnc between 0 and enc360 - 1
  return currEnc = value % ee.enc360 + ((value < 0) ? ee.enc360 : 0);
}
//--------------------------------------------------------------------------------------------------------------
long norm(long value) {
  return value % ee.enc360 + ((value < 0) ? ee.enc360 : 0); // Normalises currEnc between 0 and enc360 - 1
}
//--------------------------------------------------------------------------------------------------------------
byte enc2azm (long enc){
  return ((enc + encOne / 2) * 256) / ee.enc360;
}
//--------------------------------------------------------------------------------------------------------------
long azm2enc (long azm){
  return (azm * ee.enc360) / 256;
}
//--------------------------------------------------------------------------------------------------------------
bool btCxn() {
  if (!btStat) return digitalRead(BTSTAT);                      // If not in simulation mode, return real BTSTAT
  return btStat - 1;                                            // Return simulated value (1: 0, 2: 1)
}
//--------------------------------------------------------------------------------------------------------------
void help() {
  Serial.println("\nCOMMANDS");
  Serial.println("--------");
  Serial.println("<\t Left button pressed (or ,)");
  Serial.println(">\t Right button pressed (or .)");
  Serial.println("O\t OK button pressed");
  Serial.println("E\t Esc button pressed");
  Serial.println("o\t OK button held then released");
  Serial.println("e\t ESC button held then released");
  Serial.println("D\t Get the LCD display");
  Serial.println("B\t Simulate Bluetooth connected");
  Serial.println("b\t Simulate Bluetooth not connected");
  Serial.println("X\t Stop Bluetooth simulation");
  Serial.println("T\t Enter test mode");
  Serial.println("t\t Enter live mode");
  Serial.println("S\t Pretend telescope is slewing");
  Serial.println("V\t See key variables");
  Serial.println("A\t See brakEnc[] array");
  Serial.println("C\t See contents of EPROM");
  Serial.println("L\t Pretend connection lost with telescope");
  Serial.println("N\t Go to azimuth point where N is a number 0 to 255");
  Serial.println(":T00FF#  Go to azimuth point Hex 00 (FF is inverse of 00)");
}
//--------------------------------------------------------------------------------------------------------------
void sendVars() {
  Serial.print("\nmaxPWM1cw  \t"); Serial.println(ee.maxPWM1cw);
  Serial.print("maxPWM2cw    \t"); Serial.println(ee.maxPWM2cw);
  Serial.print("maxPWM3cw    \t"); Serial.println(ee.maxPWM3cw);
  Serial.print("maxPWM1ccw   \t"); Serial.println(ee.maxPWM1ccw);
  Serial.print("maxPWM2ccw   \t"); Serial.println(ee.maxPWM2ccw);
  Serial.print("maxPWM3ccw   \t"); Serial.println(ee.maxPWM3ccw);
  Serial.print("startPWM1cw  \t"); Serial.println(ee.startPWM1cw);
  Serial.print("startPWM2cw  \t"); Serial.println(ee.startPWM2cw);
  Serial.print("startPWM3cw  \t"); Serial.println(ee.startPWM3cw);
  Serial.print("startPWM1ccw \t"); Serial.println(ee.startPWM1ccw);
  Serial.print("startPWM2ccw \t"); Serial.println(ee.startPWM2ccw);
  Serial.print("startPWM3ccw \t"); Serial.println(ee.startPWM3ccw);
  Serial.print("stopPWM1cw   \t"); Serial.println(ee.stopPWM1cw);
  Serial.print("stopPWM2cw   \t"); Serial.println(ee.stopPWM2cw);
  Serial.print("stopPWM3cw   \t"); Serial.println(ee.stopPWM3cw);
  Serial.print("stopPWM1ccw  \t"); Serial.println(ee.stopPWM1ccw);
  Serial.print("stopPWM2ccw  \t"); Serial.println(ee.stopPWM2ccw);
  Serial.print("stopPWM3ccw  \t"); Serial.println(ee.stopPWM3ccw);
  Serial.print("ee.dir1cw    \t"); Serial.println(ee.dir1cw);
  Serial.print("ee.dir2cw    \t"); Serial.println(ee.dir2cw);
  Serial.print("ee.dir3cw    \t"); Serial.println(ee.dir3cw);
  Serial.print("ee.rampSpeed \t"); Serial.println(ee.rampSpeed);
  Serial.print("ee.stopSpeed \t"); Serial.println(ee.stopSpeed);
  Serial.print("ee.stuckTime \t"); Serial.println(ee.stuckTime);
  Serial.print("enc360       \t"); Serial.println(ee.enc360);
  Serial.print("enc180       \t"); Serial.println(enc180);
  Serial.print("encOne       \t"); Serial.println(encOne);
  Serial.print("azmAdj       \t"); Serial.println(ee.azmAdj);
  Serial.print("ee.indexSw   \t"); Serial.println(ee.indexSw);
  getEnc();
  Serial.print("currEnc      \t"); Serial.println(currEnc);
  Serial.print("homeAZM      \t"); Serial.println(ee.homeAZM);
  Serial.print("domeAZM      \t"); Serial.println(domeAZM);
  Serial.print("scopeAZM     \t"); Serial.println(scopeAZM);
  Serial.print("ee.addr      \t"); Serial.println(ee.addr);
  Serial.print("ee.enc       \t"); Serial.println(long(EEPROM.read(ee.addr) + EEPROM.read(ee.addr + 1) *256 + EEPROM.read(ee.addr + 2) * 65536));
  Serial.print("domeSafe     \t"); Serial.println(domeSafe);
  Serial.print("ee.password  \t"); Serial.print(ee.password); Serial.println(".");
  Serial.print("ee.encDir    \t"); Serial.print(ee.encDir); Serial.println(ee.encDir ? " >CW" :  " <CCW");
  Serial.print("BTSTAT       \t"); Serial.println(digitalRead(BTSTAT));
  Serial.print("btStat       \t"); Serial.println(btStat);
  Serial.print("btCxn()      \t"); Serial.println(btCxn());
  Serial.print("newData      \t"); Serial.println(newData);
  Serial.print("slaved       \t"); Serial.println(slaved);
  Serial.print("prevEnc      \t"); Serial.println(prevEnc);
  Serial.print("targEnc      \t"); Serial.println(targEnc);
  Serial.print("distEnc      \t"); Serial.println(distEnc);
  Serial.print("stepDist     \t"); Serial.println(stepDist);
  Serial.print("tempEnc      \t"); Serial.println(tempEnc);
  Serial.print("lastEnc      \t"); Serial.println(lastEnc);
  Serial.print("midPoint     \t"); Serial.println(midPoint);
  Serial.print("dir          \t"); Serial.println(dir);
  Serial.print("pwm          \t"); Serial.println(pwm);
  Serial.print("testMode     \t"); Serial.println(testMode ? "TEST" :  "LIVE");
}
//--------------------------------------------------------------------------------------------------------------
void sendBrakEnc() {
  Serial.println("brakEnc[256]:");
  for (int i = 0; i <= 240; i+= 16) {
    printHex3(i);
    Serial.print(':');
    for (byte j = 0; j <= 15; j++) {
      Serial.print(' ');
      printHex3(brakEnc[i + j]);
    }
    Serial.println();
    if (i == 240) break;
  }
}
//--------------------------------------------------------------------------------------------------------------
void sendEEPROM() {
  Serial.println("EEPROM:");
  for (int i = 0; i <= 4064; i+= 32) {
    printHex3(i);
    Serial.print(':');
    for (byte j = 0; j <= 31; j++) {
      Serial.print(' ');
      if (EEPROM.read(i + j) < 16) Serial.print('0');
      Serial.print(EEPROM.read(i + j), HEX);
    }
    Serial.println();
  }
}
//--------------------------------------------------------------------------------------------------------------
void printHex3(long v) {
  if (v < 256) Serial.print('0');
  if (v < 16) Serial.print('0');
  Serial.print(v, HEX);
}
//-------------------------- ------------------------------------------------------------------------------------
void updateEEPROM(long enc) {
  long eeEnc;

// Store at EEPROM address ee.addr.  If it cannot store at this address, the next address is tried.
  ee.addr--;
  do {
    ee.addr++;
    if (ee.addr > (4096 - 4)) {
      LCD("$$EEPROM write $Nfailed: ");
      LCDint(ee.addr);
      beep(3000, ee.bzAlert);
      return;
    }
    EEPROM.put(ee.addr, enc);
    EEPROM.get(ee.addr, eeEnc);
  } while (eeEnc != enc);
  EEPROM.put(0, ee);                // Update addr pointer as necessary
}
//--------------------------------------------------------------------------------------------------------------
#pragma endregion DOME BUDDY STUFF
//==============================================================================================================
#pragma region ISR STUFF
//--------------------------------------------------------------------------------------------------------------
void setupISR() {   // Mode = 0 live, Mode = 1 test rig
  detachInterrupt(digitalPinToInterrupt(ENC_ChA));
  if (testMode) return;
  if (ee.encDir) attachInterrupt(digitalPinToInterrupt(ENC_ChA), encISRccw, CHANGE);
  else attachInterrupt(digitalPinToInterrupt(ENC_ChA), encISRcw, CHANGE);
}
//--------------------------------------------------------------------------------------------------------------
void encISRcw() {                   // Clockwise
  switch (PIND & B11) {             // PIND Bit 1 is ChA, PIND Bit 0 is ChB
    case B00: currEnc++; return;
    case B01: currEnc--; return;
    case B10: currEnc--; return;
    case B11: currEnc++; return;
  }
}
//--------------------------------------------------------------------------------------------------------------
void encISRccw() {                  // Counter clockwise
  switch (PIND & B11) {             // PIND Bit 1 is ChA, PIND Bit 0 is ChB
    case B00: currEnc--; return;
    case B01: currEnc++; return;
    case B10: currEnc++; return;
    case B11: currEnc--; return;
  }
}
//--------------------------------------------------------------------------------------------------------------
void getEnc() {
  if (testMode) {
    if (!encMoving) return;
    long offset  = millis() - prevMillis;
    prevMillis = millis();
    if (!dir) offset = -offset;
    currEnc += offset;
  }
}
//--------------------------------------------------------------------------------------------------------------
void startEnc() {
  if (testMode) {
    if (!encMoving) {
      prevMillis = millis();
      encMoving = 1;
    }
    getEnc();
  }
}
//--------------------------------------------------------------------------------------------------------------
void stopEnc() {
  if (testMode) {
    getEnc();
    encMoving = 0;
  }
}
//--------------------------------------------------------------------------------------------------------------
void indexISR() {
  if (currEnc - indexEnc[indexPtr] < 1000) return;    // Index switch triggered - get rid off debounce
  indexEnc[indexPtr++] = currEnc;
  if (indexPtr < 2) return;
  detachInterrupt(digitalPinToInterrupt(INDEX_SW));   // Triggered twice (one rotation) - stop interrupt
}
#pragma endregion ISR STUFF
//==============================================================================================================