OLED Logger  Arduino Sketch

Variables and Declarations

/* OLEDLogger,1/292024 pairs with OLEDLogger.bas 12/6/2021 John Saunders

 *  Version of OLEDLoggerSS with better hour and minute adjustmentss.

  The OLED Logger performs display and logging of RF messages and analog inputs.

  It consists of an external (to avoid interference) RF receiver with a wood base and

  three sub-assemblies which are housed in a metal clamshell case with a sloping cream-colored top.

  This has a 20-column by 4 row blue OLED character display, and a 5-button rosette,

  which are connected to the brown base by a ribbon cable.

  The base has three sub-assemblies: Power, Signal Conditioning, and Logging/Display.

  A. The Power sub-assembly has a 3400 MAH Li-Ion cell which has a switch-mode charger.

    The recommended charging power supply is the Challenger 12V, 3A with 2 pigtails.

    When the battery is fully charged, the power consumption is 1.2W.

    It also has an on-off switch and an attenuator to measure the charging voltage.

    The power must be on to charge. The battery connects to the  Logging/Display when on.

    The  Logging/Display provides +5V power to the Signal Conditioner from an internal booster,

  B. The Signal Conditioner sub-assembly used a Picaxe 28X2. It converts analog inputs to ASCII in volts.

    There are three sets of inputs:

    1. Power Monitoring: Charging voltage and Battery voltage.

    2. Rear panel: 3 jacks Green, Blue and Orange have 3 switched ranges: Up=28V,Center=52V,Down=12V.

       Scaling is corrected in the conversion. A 4.096V reference is used.

       A White jack is direct 2V using a 2.048V reference.

       Analog inputs are digitized and transferred to the Logging/Display when it sends a command.

    3. RF signals via 9-pin male jack.

       These are already digitized and verified messages are immediately transferred to the Logging/Display unmodified.

  C: The Logging/Display uses an Pololu A-Star 32u4 Prime LV. compatible.

     The 32u4 was selected over the 328P because its additional 512 bytes SRAM was needed.

     The  Pololu A-Star was selected over the Arduino Leonardo because it runs directly from the battery.

     The A-Star is covered by full-size SD logger shield,

     which also has a real-time clock-calendar and an optional serial EEPROM.

     The hard-wiring on the shield and A-star peculiarities severely constrained the choice of pins.

     The I2C RTC is hard-wired to pins 2&3 on the A-Star.

     The SD and the EEPROM are SPI hard-wired on the shield to pins 11-13. 10 is hard wired for the SD select.

     Pin 8 was home-wired to 8 for the EEPROM select.

     Serial input on the 32u4 needs 8 and up, so 9 must be used. There are additional pins on the A-Star

     but the shield makes them physically inaccessible. A0 and A1 are used for the buttons,

     This results in using 5,7 and A2-A5 for the display and  6 for the serial output. 4 is unused.

     Modified 12/21/2021 to provide custom characters for ohm and degree

     12/24/21 holdCode = txBuf[0] not keyCode; clear end of TimeDate

     12/25/21 Show manual comand on timeDate line end and wait for response for another command

     10/10/22 New measurements codes. Modified incoming. 10/11/2022 Mode changes

     This is the program running prior to attaching the Feather.

*/


// include the library code:


#include <PololuHD44780.h>              // Needed to get setCursor() to work right

#include <SoftwareSerial.h>

#include <EEPROM.h>

#include <SD.h>                         // I hate micro-SD. There is one on the A-Star.

#include <PCF8523.h>                    // Not DS1307 compatible, 2020 version

#define numPages 14


PCF8523 rtc;


// initialize the library by associating any needed LCD interface pin

// with the arduino pin number it is connected to

const  byte rs = 7, en = 5, d4 = A2, d5 = A3, d6 = A4, d7 = A5;

PololuHD44780 oled(rs, en, d4, d5, d6, d7);




const byte rxPin  = 9;

const byte txPin  = 6;

SoftwareSerial picaxeLink(rxPin, txPin);


const int chipSelect = 10;                // SD

unsigned int countUp[4] = {0, 0, 0, 0};             // for the measurements and ageing

byte dispMode = 0;

char keyHold;

char cmdChar;

 Setup and Loop

byte newModeFlag;


// --------------------------- Setup ------------------------------


void setup() {

  char SDId[2];

  pinMode(rs, OUTPUT);

  pinMode(en, OUTPUT);

  // oled.begin(20, 4);

  oled.initPins();

  oled.clear();

  delay(10);

  oled.display();

  delay(10);

  pinMode(rxPin, INPUT);

  pinMode(txPin, OUTPUT);

  picaxeLink.begin(57600);

  oled.setCursor(0, 3); dispMode = 0;

  delay(10);

  oled.clear();

  delay(10);

  if (picaxeLink.isListening() == true) {

    oled.print("RxPin ");

    oled.print(rxPin, DEC);

    oled.print("=OK   ");

  }

  SD.begin(chipSelect);

  File dataFile = SD.open("ID.TXT", FILE_READ);

  dataFile.read(SDId, 1);

  if (SDId > 0) {

    delay(10);

    oled.print("SD is ");

    oled.print(SDId);

    delay(1000);

  }

  else {

    oled.print("No SD present");

    delay(200);

  }

  rtc.begin();

  rtc.setBatterySwitchover();

  oled.createChar(0, degSym);

  oled.createChar(1, ohmSym);

  BGLimit = EEPROM.read(0);

  OWLimit = EEPROM.read(1);

  dispMode = 0;

  keyHold = 0;

  newModeFlag = 0;

  cmdChar = ' ';

}


// --------------------------- Loop ------------------------------


void loop() {

  char picChar, keyCode = ' ';

  byte colCount = 0;

  //byte lastCol = 0;

  byte newMsgFlag = 0;

  byte buttVal = getButtons();

  autoCommand();


  // --------------------------- Input from Picaxe ------------------------------


  colCount = 0;

  // lastCol = 0;

  while ((picaxeLink.available() > 0) && (keyCode == ' ')) {        // 130 ms, including 76 for record

    picChar = picaxeLink.read();

    if (picChar == '<')  {

      picChar = picaxeLink.read();            // Should be Key Code

      if ((picChar >= 'n') && (picChar <= 'z')) {

        keyCode = picChar;

        if (picChar == 't') {

          cmdChar = ' ';                      // Enable another command

        }

        SDBuf[0] = picChar;

        colCount = 1;

      }

    }

    delay(3);

  }

  while ((picaxeLink.available() > 0) && (keyCode != ' ')) {

    picChar = picaxeLink.read();  // skip over a comma

    if ((picChar <= 'z') && (colCount < 21) && (picChar != '>')) {

      SDBuf[colCount++] = picChar;

    }

    else {     //10/10/2022

      SDBuf[colCount] = 0;

      //lastCol = colCount - 1;

      // SDBuf[lastCol] = 0;

      newMsgFlag = 1;

      cmdChar = ' ';

    }

    delay(3);

  }


 // if (lastCol > 0) {

 //   SDBuf[lastCol] = 0;

 // }


  // --------------------------- Mode State Machine ------------------------------


  switch (dispMode) {

    case 0:

      newModeFlag = 0;

      manualCommand(buttVal);

      showIncoming(newMsgFlag);

      switch (buttVal) {

        case 4:         // left button

          dispMode = 2;

          newModeFlag = 1;

          oled.clear();

          break;

        case 16:        //center button

          dispMode = 1;

          newModeFlag = 1;

          keyHold = 0;

          break;

      }

      break;

    case 1:

      switch (buttVal) {

        case 1:                    // Up button

          keyHold = 0;

          break;

        case 3:                     // Down button

          keyHold = SDBuf[0];

          break;

        case 4:                     // left button

          dispMode = 0;

          newModeFlag = 1;

          oled.clear();

          break;

      }

      if ((newModeFlag == 1) || ((newMsgFlag == 1) && (keyHold == 0)) || ((newMsgFlag == 1) && (keyCode == keyHold))) {

        for (byte col = 0; col < SDBufSize; col++) {

          picChar = SDBuf[col];

          currMsg[col] = picChar;

          if (picChar == 0) {

            break;

          }

        }

        countUp[3] = 0;

        newModeFlag = 0;        //moved 10/11/2022

      }

      showPage(newMsgFlag);

      break;

    case 2:

      if (newModeFlag == 1) {

        showAdjStrings();

      }

      newModeFlag = 0;

      adjustments(buttVal);

      switch (buttVal) {

        case 16:                    //center button

          adjChange();

          dispMode = 0;

          newModeFlag = 1;

          oled.clear();

          break;

      }

      break;

  }

  if ((dispMode < 2) && (newMsgFlag == 1)) {

    record();

    newMsgFlag = 0;

  //  oled.setCursor(0, 3);

 //   oled.print("End of loop");

  }

  while (getButtons() > 0); {

    delay(10);

  }


  delay(10);

}

Functions

// --------------------------- Displaying ------------------------------


void showTimeDate(void) {

  byte tenSec;

  static byte oldTenSec = 0;

  DateTime now = rtc.readTime();

  tenSec = (rtc.rtcReadReg(PCF8523_SECONDS)) & 0x70;           // Tens of seconds

  if (oldTenSec != tenSec) {

    oldTenSec = tenSec;

    for (int i = 0; i < 4; i++) {

      ++countUp[i];

    }

  }

  if (dispMode < 2)   {

    oled.setCursor(17, 0);

    oled.print(" " );

    oled.print(cmdChar);

    oled.setCursor(0, 0);

    oled.print(now.hour());

    oled.print(':');

    oled.print(now.minute());

    oled.print(':');

    oled.print(now.second());

    oled.print(' ');

    oled.print(now.month());

    oled.print('/');

    oled.print(now.day());

    oled.print('/');

    oled.print(now.year());

  }

}

#define SDBufSize 21

char SDBuf[SDBufSize];


void showIncoming(byte newMsg) {

  static String row1 = " ", row2 = " ", row3 = " ";

  if (newMsg == 1) {

    oled.clear();

    row3 = row2;

    row2 = row1;

    row1 = SDBuf;

  }

  oled.setCursor(0, 3);

  oled.print(row3);

  oled.setCursor(0, 2);

  oled.print(row2);

  oled.setCursor(0, 1);

  oled.print(row1);

  oled.setCursor(0, 0);

  showTimeDate();

}


const char*units[] = {

  //          n                         o                       p

  " ", "$", "$", " ",       " ", " ", " ", " ",            "MA", "V", " ", " ",

  //          q                       r                         s

  " ", " ", "`F", "`F",     "inHg", "V", "MAH", "MAH",     "`F", "%", "MA", "V",

  //          t                       u                          v

  " ", "V", " ", "V",       "`F", " ", " ", " ",            " ", "V", " ", "V",

  //          w                       x                          y

  " ", "W", " ", " ",       " ", "V", " ", " ",            " ", "V", " ", "V",

  //         z

  "`F", " ", "V", " "

};


byte ohmSym[8] = {

  B01110,

  B10001,

  B10001,

  B10001,

  B01010,

  B01010,

  B11011,

};

byte degSym[8] = {

  B11100,

  B10100,

  B11100,

  B00000,

  B00000,

  B00000,

  B00000,

};


byte currMsg[SDBufSize];


const char *msgName[13] = {"gasAir", "event", "test", "brFan", "baro", "env", "meas", "gFan", "volts", "watts", "radar", "dau", "gasTl"};


void printUnits(byte index) {

  char unitsChar = units[index][0];

  oled.print(' ');

  switch (unitsChar) {

    case'`':

      oled.write(byte(0));

      oled.write('F');

      break;

    case'$':

      oled.write(byte(1));

      break;

    default:

      oled.print(units[index]);

      break;

  }

}


void showPage(byte flag) {

  byte id = currMsg[0] - 'n';

  byte commaCt = 0;

  byte unitsIndex;

  char msgChar;

  if (id > 12) {

    return;

  }

  if (flag == 1) {

    oled.clear();

  }

  oled.setCursor(0, 2);

  

  for (int col = 2; col <= SDBufSize; col++) {

    unitsIndex = (4 * id) + commaCt;

    msgChar = currMsg[col];

    if (msgChar == ',') {

      printUnits(unitsIndex);

      switch (commaCt) {

        case 0:

          oled.setCursor(10, 2);

          commaCt++;

          break;

        case 1:

          oled.setCursor(0, 3);

          commaCt++;

          break;

        case 2:

          oled.setCursor(10, 3);

          break;

      }

    }

    else {

      if (msgChar == 0) {

        printUnits(unitsIndex);

        break;

      }

      else {

        oled.write(msgChar);

      }

    }

  }

  oled.setCursor(0, 0);

  showTimeDate();

  oled.setCursor(0, 1);

  oled.print(msgName[id]);

  if (keyHold > 0) {

    oled.setCursor(14, 1);

    oled.print(countUp[3], DEC);

    oled.print(' ');

    oled.print(keyHold);

  }

}

// --------------------------- Control ------------------------------


int getButtons(void) {

  static int threshold[] = {885, 649, 410, 180, 0};

  int arrowsVal = 0;  // variable to store the value read for the Arrow pins

  int centerVal = 0;  // variable to store the value read for the Center pin

  int arrowPins = A0;

  int centerPin = A1;

  int pinDir = 0;

  centerVal = digitalRead(centerPin) ^ 1;

  arrowsVal = analogRead(arrowPins);

  while (threshold[pinDir] > arrowsVal) {

    pinDir++;

  }

  return ((16 * centerVal) + pinDir);

}


byte BGLimit = 30;

byte OWLimit = 30;

const byte powerLimit = 60;


byte manualCommand(byte buttonId) {

  char sendCode = ' ';

  switch (buttonId) {

    case 1:       //Up button

      sendCode = 'm';

      break;

    case  2:        //right button

      sendCode = 'n';

      break;

    case 3:       //down button

      sendCode = 'o';

      break;

  }

  if ((sendCode != ' ') && (cmdChar == ' ')) { // Inhibit until respone received

    picaxeLink.print(sendCode);

    cmdChar = sendCode;                       // Display command code sent

  }

  while (getButtons() != 0) {

    delay(10);;

  }

  return sendCode;

}


byte autoCommand(void) {

  char sendCode = ' ';

  if (countUp[0] > BGLimit) {

    countUp[0] = 0;

    sendCode = 'm';

  }

  if (countUp[1] > OWLimit) {

    countUp[1] = 0;

    sendCode = 'o';

  }

  if (countUp[2] > powerLimit) {

    countUp[2] = 0;

    sendCode = 'n';

  }

  if (sendCode != ' ') {

    picaxeLink.print(sendCode);

  }

  while (getButtons() != 0) {

    delay(10);;

  }

  return sendCode;

}

// --------------------------- Logging ------------------------------


void record() {

  char dir = SDBuf[0];

  uint8_t prefixIndex;

  String filename;

  char fBuf[19];

  if ((dir < 'n') || (dir > 'z')) {

    prefixIndex = 2;

  }

  else {

    prefixIndex =  dir - 'n';

  }

  digitalWrite(4, HIGH);

  DateTime now = rtc.readTime();

  filename = String((now.year() % 1000));

  filename += String('/');

  filename += String(now.month());

  filename += String('/');

  filename += String(msgName[prefixIndex]);

  filename += String(now.day());

  filename += String(".csv");

  if (sizeof(filename) > 19) {

    return;

  }

  filename.toCharArray(fBuf, 19);

  File dataFile = SD.open(fBuf, FILE_WRITE);

  if (dataFile) {

    dataFile.print(now.hour());

    dataFile.print(':');

    dataFile.print(now.minute());

    dataFile.print(':');

    dataFile.print(now.second());

    dataFile.print(',');

    for (int i = 0; i < SDBufSize; i++) {

      if ((SDBuf[i] > 42)  && (SDBuf[i] < 127)) {

        dataFile.print(SDBuf[i]);

      }

      SDBuf[i] = 0;

    }

    dataFile.write(13);

    dataFile.write(10);

    dataFile.close();

  }

  else {

    oled.clear();

    oled.setCursor(0, 0);

    oled.print("Open file failure:");

    oled.setCursor(0, 2);

    oled.print(filename);

    while (picaxeLink.available() > 0) {

      fBuf[0] = picaxeLink.read();

    }

    

  }

  delay(200);

}

// --------------------------- Setting ------------------------------


const char *adjHdgs[] = {"Hours         =     ",

                         "Minutes       =     ",

                         "Green/Blue    =     ",

                         "Orange/White  =     "

                        };

uint8_t adjVals[4], oldVals[4];


void showAdjStrings(void) {

  DateTime now = rtc.readTime();

  oled.clear();

  for (int row = 0; row < 4; row++) {

    oled.setCursor(0, row);

    oled.print(adjHdgs[row]);

  }

  adjVals[0] = now.hour();

  oldVals[0] = adjVals[0];

  adjVals[1] = now.minute();

  oldVals[1] = adjVals[1];

  adjVals[2] = BGLimit;

  adjVals[3] = OWLimit;

  for (int i = 0; i < 4; i++) {

    oldVals[i] = adjVals[i];

  }

}


void adjustments(byte buttonId) {

  unsigned int intRate;

  static byte  adjIndex = 3;


  if (buttonId == 2) {

    adjIndex++;

    if (adjIndex > 3) {

      adjIndex = 0;

    }

  }

  if (buttonId == 4) {

    if (adjIndex > 1) {

      adjIndex--;

    }

    else {

      adjIndex = 3;

    }

  }

  for (int row = 0; row < 4; row++) {

    oled.setCursor(17, row);

    if (row > 1) {

      intRate = 10 * adjVals[row];

      oled.print(intRate, DEC);

    }

    else {

      oled.print(adjVals[row], DEC);

    }

    oled.setCursor(15, row);

    if (row == adjIndex) {

      oled.print('*');

      if (buttonId == 1) {

        adjVals[row]++;


      }

      if ((buttonId == 3) && (adjVals[row] > 1)) {

        adjVals[row]--;

      }

    }

    else {

      oled.print(' ');

    }

  }

}


void adjChange(void) {

  DateTime now = rtc.readTime();

  if (adjVals[0] != oldVals[0]) {

    rtc.setTime(DateTime(now.year(),now.month(),now.day(),adjVals[0],now.minute(),now.second()));

  }

  if (adjVals[1] != oldVals[1]) {

   rtc.setTime(DateTime(now.year(),now.month(),now.day(),now.hour(),adjVals[1],now.second()));

  }

  if (adjVals[2] != oldVals[2]) {

    BGLimit = adjVals[2] ;

    EEPROM.write(0, BGLimit);

  }

  if (adjVals[3] != oldVals[3]) {

    OWLimit = adjVals[3] ;

    EEPROM.write(1, OWLimit);

  }

}