OLED Logger  Arduino Sketch

Variables and Declarations

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

  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;

byte newModeFlag;

 Setup and Loop

// --------------------------- 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;

  char fBuf[19];

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

    prefixIndex = 2;

  }

  else {

    prefixIndex =  dir - 'n';

  }

  digitalWrite(4, HIGH);

  DateTime now = rtc.readTime();

// The previous code stopped open() after 10/2024

  sprintf(fBuf, "%02u/%02u/%s%02u.CSV", (now.year() % 1000), now.month(), msgName[prefixIndex],now.day());

  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  =     "

                        };

byte adjVals[4], oldVals[4];


void showAdjStrings(void) {

  oled.clear();

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

    oled.setCursor(0, row);

    oled.print(adjHdgs[row]);

  }

  adjVals[0] = rtc.rtcReadReg(PCF8523_HOURS);

  adjVals[1] =  rtc.rtcReadReg(PCF8523_MINUTES);

  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) {

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

    rtc.rtcWriteReg(PCF8523_HOURS, adjVals[0]);

  }

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

    rtc.rtcWriteReg(PCF8523_MINUTES, adjVals[1]);

  }

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

    BGLimit = adjVals[2] ;

    EEPROM.write(0, BGLimit);

  }

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

    OWLimit = adjVals[3] ;

    EEPROM.write(1, OWLimit);

  }

}