Scientech Meter Program Includes and  Defines

/*

  ScientechMeter.ino. Uses millis() instead of thr RTC for seconds count

  This is for the greybox labelled "John's Neopixel Meter".

  The box was originally a power meter from Scientech, bought cheap in 2008 when it became obsolete.

  This is the 3rd project in it. Only the attenuator was retained from the previous version.

  It is a volt and current measuring device with digital readout and crude neopixel analog display.

  It originally had an analog mter which was destroyed and replaced bt a 15-lod 90-degree neopixel arc.

  The program is for a Spark Fun 5V MiniPro P323P at 16 MHz.

  It is connected to the following:

  A 12-position voltage and current network with an 60-mv output.

  A X50 amplifier to boost the output to 3V.

  A 3.5 mm stereo audio jack connected to a X21 differential amplifier with CM range +/- 10V.

  Primarily for an old analog volt-ammeter with 18 ranges and 200 mv output.

  A 6-pin DIN connector for an external transmitter module

  A DS1302 RTC. The OLED monovhrome HD graphics display. The neopixed arc.

  Efforts were required to minimise global memory use since the display uses half of it.

  The required using EEPROM for constants and not instantiating the USB serial.

   John Saunders 6/18/2023

  With help from Lady Ada.

*/

#include <SPI.h>

#include <Wire.h>

#include <Ds1302.h>

#include <Adafruit_GFX.h>

#include <Adafruit_SSD1306.h>

#include <EEPROM.h>

#include <Adafruit_NeoPixel.h>

#include <SoftwareSerial.h>

//for 3 rows of 9 characters:Serif`12pt7b,start y14 insetx 10, spacing y21

#include "Fonts/FreeSerif12pt7b.h"    //3 rows of 9 or 10 characters


#define rxPort 3

#define txPort 2

#define pulsePort 9

#define SCREEN_WIDTH 128 // OLED display width, in pixels

#define SCREEN_HEIGHT 64 // OLED display height, in pixels

#define Vref 2.482

#define RefPort A1

#define FrontPort A2

#define BackPort A3

#define InUpSw 7

#define InDnSw 8

#define FldUpSw 5

#define FldDnSw 6

#define LED_PIN    4

#define LED_COUNT 15

#define OLED_RESET     -1 // Reset pin # (or -1 if sharing Arduino reset pin)

#define row0y   21

#define row1y   42

#define row2y   63

#define BACK 0

#define EXT 1

#define pixelWait 1

#define frac(x)     (int(1000*(x - int(x))))


struct field_t {

  char fldName[10];

  char fldUnit[4];

  float fudge;

  uint8_t decs;

};


struct setting_t {

  char settingType[10];

  char settingName[10];

  char settingParam[6];

  uint8_t settingKey;

};

Scientech Meter Program Global Variables

/********************************************************************************

                          global Variables 90 bytes

 *********************************************************************************/


byte page;

uint8_t DST = 1;                //Saved in EEPROM at 1000

uint8_t txFlag = 1;             //Saved in EEPROM at 1001

uint8_t txInterval = 75;        //Saved in EEPROM at 1002

uint8_t settingIndex = 3;       //Saved in EEPROM at 1005

bool txTrig[(EXT + 1)] = {false, false};

field_t measFld[2];

setting_t settings;

uint8_t range[(EXT + 1)];

Scientech Meter Program Object Creation


/********************************************************************************

                          objects

 *********************************************************************************/

//This is 115 bytes, no display

SoftwareSerial frontSS(rxPort, txPort);

// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

Adafruit_NeoPixel strip(LED_COUNT, LED_PIN, NEO_GRB + NEO_KHZ800);

Ds1302 rtc(10, 13, 12);

Scientech Meter Program EEPROM Functions


/********************************************************************************

                         EEPROM

*********************************************************************************/

//EEPROM addresses  measurements=0-228, 230-52 Settings 600-708, Preferences 1000-1005,


void getSettings() {

  DST = EEPROM.read(1000);

  txFlag = EEPROM.read(1001);

  txInterval = EEPROM.read(1002);

  range[BACK] = EEPROM.read(1003);

  range[EXT] = EEPROM.read(1004);

  settingIndex = EEPROM.read(1005);

}


void putSettings() {

  EEPROM.write(1000, DST);     //DST:Standard Time

  EEPROM.write(1001, txFlag);     //Transmit off

  EEPROM.write(1002, txInterval);    //Transmit interval in seconds

  EEPROM.write(1003,  range[BACK]);    //Rear range

  EEPROM.write(1004, range[EXT] );    //Ext range

  EEPROM.write(1005,  settingIndex);    //Setting Index

}

Scientech Meter Program Display Functions

/********************************************************************************

                          Display

 *********************************************************************************/


void showMeter(int ptrVal) {            //Neopixel driver

  //Colors per 13100: 0-1 red, 1-1.9 green, 1.9-3 cerise, 3-  blue, 4-5 white

  const uint32_t lowArc = strip.gamma32(strip.ColorHSV(65533));

  const uint32_t ptrColor = strip.gamma32(strip.ColorHSV(19660));

  const uint32_t highArc = strip.gamma32(strip.ColorHSV(44564));

  for (int i = 0; i < 15; i++) { // For each pixel in strip...

    if (i < ptrVal) {

      strip.setPixelColor(i, lowArc);         //  Set pixel's color (in RAM)

    }

    if (i == ptrVal) {

      strip.setPixelColor(i, ptrColor);       //  Set pixel's color (in RAM)

    }

    if (i > ptrVal) {

      strip.setPixelColor(i, highArc);        //  Set pixel's color (in RAM)

    }

    strip.show();                             //  Update strip to match

    delay(pixelWait);                         //  Pause for a moment

  }

}


void printMeas(int port, float measData) {                    //Both mesurement pages, 1 and 2

  if (port == EXT) {

    display.print("Ex");

  }

  if (port == BACK) {

    display.print("In");

  }

  display.print("ternal");

  display.setCursor(10, row1y);

  display.print(measFld[port].fldName);

  display.setCursor(10, row2y);

  display.print(measData, measFld[port].decs);

  display.print(' ');

  display.print(measFld[port].fldUnit);

}


void showSecond() {                           //Just to give the neopixel someting to do

  static uint32_t millSave  = 0;

  static uint8_t millSecond = 0;

  static uint8_t txCountdown = txInterval;

  if ((millis() - millSave) > 1000) {

    millSave += 1000;

    millSecond++;

    if (txCountdown > 0) {

      txCountdown--;

    }

    else {

      txCountdown = txInterval;

    }

    if ((txCountdown == 1) && (txFlag == true)) {

      txTrig[BACK] = true;

    }

    if ((txCountdown == 2) && (txFlag == true)) {

      txTrig[EXT] = true;

    }

    if ((page != 1) && (page != 2)) {

      if (millSecond > 59) {

        millSecond = 0;

      }

      if (millSecond < 15) {

        showMeter(millSecond);

      }

      if ((millSecond > 14) && (millSecond < 30)) {

        showMeter(30 - millSecond);

      }

      if ((millSecond > 29) && (millSecond < 45)) {

        showMeter(millSecond - 30);

      }

      if ((millSecond > 44) && (millSecond < 60)) {

        showMeter(60 - millSecond);

      }

    }

  }

}


void printTime()                                        //Page 0

{

  Ds1302::DateTime now;

  int eepromAddr;

  rtc.getDateTime(&now);

  char timeDateBuf[10];

  sprintf(timeDateBuf, "%02u:%02u:%02u", (now.hour + DST), now.minute, now.second);

  display.print(timeDateBuf);

  display.setCursor(10, row1y);

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

    eepromAddr = (10 * now.dow) + 790 + i;

    timeDateBuf[i] = EEPROM.read(eepromAddr);

  }

  display.print(timeDateBuf); // Print day string

  display.setCursor(10, row2y);

  sprintf(timeDateBuf, "%02u/%02u/%02u", now.month, now.day, now.year);

  display.print(timeDateBuf);

}


void printManualTx() {

  display.print("Manual Tx");

  display.setCursor(10, row1y);

  display.print("UP=Store");

  display.setCursor(10, row2y);

  display.print("DOWN=Tx");

  if (digitalRead(FldUpSw) == 0) {

    putSettings();

    while (digitalRead(FldUpSw) == 0);

  }

  if (digitalRead(FldDnSw) == 0) {

    txTrig[EXT] = true;

    while (digitalRead(FldDnSw) == 0);

  }

}


void printSelSetting() {

  int eepromAddr = (settingIndex * sizeof(setting_t)) + 600;

  EEPROM.get(eepromAddr, settings);

  display.print("Selected");

  display.setCursor(10, row1y);

  display.print("setting =");

  display.setCursor(10, row2y);

  display.print(settings.settingName);

}


void printGreeting() {

  int eepromAddr = (4 * sizeof(setting_t)) + 600;

  EEPROM.get(eepromAddr, settings);

  display.setCursor(0, row0y);

  display.print(settings.settingParam);

  display.setCursor(0, row1y);

  display.print(settings.settingName);

  display.setCursor(0, row2y);

  display.print(settings.settingType);

  display.display();

}


void printSetPage() {

  int eepromAddr = (settingIndex * sizeof(setting_t)) + 600;

  EEPROM.get(eepromAddr, settings);

  display.print(settings.settingType);

  display.setCursor(10, row1y);

  display.print(settings.settingName);

  display.setCursor(10, row2y);

  display.print(settings.settingParam);

  display.setCursor(70, row2y);

  switch (settings.settingKey) {

    case 1:

      adjTxControl();

      if (txFlag == 0) {

        display.print("OFF");

      }

      else {

        display.print("ON");

      }

      break;

    case 2:

      adjTxRate();

      display.print(txInterval);

      break;

    case 3:

      adjDst();

      if (DST == 0) {

        display.print("STD");

      }

      else {

        display.print("DST");

      }

      break;

    case 4:

      adjMins();

      break;

  }

}

Scientech Meter Program  Setting Functions


/********************************************************************************

                         Adjustments

 *********************************************************************************/

void adjRange(int port) {

  int numFlds;

  int eepromAddr;

  bool newRange = false;

  if (port == BACK) {

    numFlds = 12;

    eepromAddr = 0;

  }

  if (port == EXT) {

    numFlds = 18;

    eepromAddr = 230;

  }

  uint8_t field = range[port];

  if (digitalRead(FldUpSw) == 0) {

    field++;

    newRange = true;

    while (digitalRead(FldUpSw) == 0);

  }

  if (field >= numFlds) {

    field = 0;

    newRange = true;

  }

  if (digitalRead(FldDnSw) == 0) {

    field--;

    newRange = true;

    while (digitalRead(FldDnSw) == 0);

  }

  if (field > 250) {

    field = numFlds - 1;

    newRange = true;

  }

  if (newRange == true) {

    eepromAddr += (field * sizeof(field_t));

    EEPROM.get(eepromAddr, measFld[port]);

    range[port] = field;

  }

}


void adjSettings() {

  if (digitalRead(FldUpSw) == 0) {

    settingIndex++;

    while (digitalRead(FldUpSw) == 0);

  }

  if (settingIndex > 3) {

    settingIndex = 0;

  }

  if (digitalRead(FldDnSw) == 0) {

    settingIndex--;

    while (digitalRead(FldDnSw) == 0);

  }

  if (settingIndex > 250) {

    settingIndex = 3;

  }

}


void adjMins() {

  Ds1302::DateTime now;

  rtc.getDateTime(&now);

  uint8_t mins = now.minute;

  uint8_t nowYear = now.year;

  if (nowYear < 23) {

    now.hour = 18;

    now.minute = 48;

    now.month = 6;

    now.day = 18;

    now.year = 23;

    now.dow = 7;

  }




  if (digitalRead(FldUpSw) == 0) {

    mins++;

    now.minute = mins;

    rtc.setDateTime(&now);

    while (digitalRead(FldUpSw) == 0);

  }

  if (digitalRead(FldDnSw) == 0) {

    mins--;

    now.minute = mins;

    rtc.setDateTime(&now);

    while (digitalRead(FldDnSw) == 0);

  }

  display.print(now.minute);

}


void adjDst() {

  if (digitalRead(FldUpSw) == 0) {

    DST = 1;

  }

  if (digitalRead(FldDnSw) == 0) {

    DST = 0;

  }

}


void adjTxControl() {

  if (digitalRead(FldUpSw) == 0) {

    txFlag = 1;

  }

  if (digitalRead(FldDnSw) == 0) {

    txFlag = 0;

  }

}


void adjTxRate() {

  if (digitalRead(FldUpSw) == 0) {

    txInterval++;

    while (digitalRead(FldUpSw) == 0);

  }

  if (digitalRead(FldDnSw) == 0) {

    txInterval--;

    while (digitalRead(FldDnSw) == 0);

  }

}

Scientech Meter Program Operation Functions


/********************************************************************************

                          Operations

 *********************************************************************************/

float getMeas(int port) {

  int refCount = analogRead(RefPort);

  int measCount;

  float retVal;

  if (port == BACK) {

    measCount = analogRead(BackPort);

    if (page == 1) {

      showMeter(measCount / 39.6);

    }

  }

  if (port == EXT) {

    measCount = analogRead(FrontPort);

    if (page == 2) {

      showMeter(measCount / 39.6);

    }

  }

  retVal = (measFld[port].fudge * measCount) / refCount;

  return retVal;

}


void transmit(uint8_t port, float dataVal) {

  char txBuf[13] = {"0,1,1.234,ab"};

  byte cksum = 0;

  byte chk;

  uint8_t dec;

  char decC, rangeC;

  dec = measFld[port].decs;

  decC = (char)(dec + '0');

  rangeC = (char)(range[port] + 'A');

  if (dec == 1) {

    sprintf(txBuf, "%c,%c,%03d.%01d", decC, rangeC, int(dataVal), frac(dataVal));

  }

  if (dec == 2) {

    sprintf(txBuf, "%c,%c,%02d.%02d", decC, rangeC, int(dataVal), frac(dataVal));

  }

  if (dec == 3) {

    sprintf(txBuf, "%c,%c,%01d.%03d", decC, rangeC, int(dataVal), frac(dataVal));

  }

  txBuf[9] = '.';

  for (int ix = 0; ix < 9; ix++) {      // Calculate the checksum modulo 256

    cksum += txBuf[ix];

  }

  chk = cksum / 16;                       // ... and add to the end of the message as 2 Hex

  if (chk < 10) {

    txBuf[10] = chk + '0';

  }

  else {

    txBuf[10] = chk + '7';

  }

  chk = cksum & 0x0F;

  if (chk < 10) {

    txBuf[11] = chk + '0';

  }

  else {

    txBuf[11] = chk + '7';

  }

  txBuf[12] = 0;

  digitalWrite(pulsePort, LOW);

  delay(20);

  digitalWrite(pulsePort, HIGH);

  delay(10);

  frontSS.print("14L1776v,E,");

  frontSS.println(txBuf);

  txTrig[port] = false;

}

Scientech Meter Program Setup

 Note the absence of the customary Serial.begin(). This is because it takes up so much memory that display.begin() fails. It was not practical to use the programming connector for the transmitter, so SoftwareSerial was used.

Of course, debugging was impaired, the neopixel string was of some use.


void setup() {

  frontSS.begin(2400);

  pinMode(InUpSw, INPUT_PULLUP);

  pinMode(InDnSw, INPUT_PULLUP);

  pinMode(FldUpSw, INPUT_PULLUP);

  pinMode(FldDnSw, INPUT_PULLUP);

  pinMode(pulsePort, OUTPUT);

  int eepromAddr;

  getSettings();

  eepromAddr = (range[BACK] * sizeof(field_t));

  EEPROM.get(eepromAddr, measFld[BACK]);

  eepromAddr = 230 + (range[EXT] * sizeof(field_t));

  EEPROM.get(eepromAddr, measFld[EXT]);

  strip.begin();

  strip.show(); // Initialize all pixels to 'off'

  strip.setBrightness(30); // Set BRIGHTNESS to about 1/5 (max = 255)


  // SSD1306_SWITCHCAPVCC = generate display voltage from 3.3V internally

  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {

    int nStep = 0;

    while (true) {

      nStep += 3;

      showMeter(nStep);

      if (nStep > 15) {

        nStep = 0;

      }

      delay(1000);

    }

  }

  rtc.init();

  //rtc.autoTime();

  // e.g.16:45:16 | Sunday May 14, 2023:

  // rtc.setTime(10, 30, 14, 4, 7, 6, 23);  // Uncomment to manually set time


  page = 0;

  showSecond();

  digitalWrite(pulsePort, HIGH);

  display.clearDisplay();

  display.setTextColor(SSD1306_WHITE);

  display.setFont(&FreeSerif12pt7b);

  printGreeting();                    //Greeting

  delay(2000);

}

Scientech Meter Program  Loop 


void loop() {

  float measVal[(EXT + 1)];

  if (digitalRead(InUpSw) == 0) {

    page++;

    if (page > 5) {

      page = 0;

    }

    while (digitalRead(InUpSw) == 0);

  }

  if (digitalRead(InDnSw) == 0) {

    page--;

    if (page > 250) {

      page = 5;

    }

    while (digitalRead(InDnSw) == 0);

  }

  measVal[BACK] = getMeas(BACK);

  measVal[EXT] = getMeas(EXT);

  display.clearDisplay();

  display.setCursor(10, row0y);

  showSecond();

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

    if (txTrig[i] == true) {

      strip.setPixelColor(i, 200, 200, 200);

      strip.show();

      transmit(i, measVal[i]);

    }

  }

  switch (page) {

    case 0:

      printTime();

      break;

    case 1:

      adjRange(BACK);

      printMeas(BACK, measVal[BACK]);

      break;

    case 2:

      adjRange(EXT);

      printMeas(EXT, measVal[EXT]);

      break;

    case 3:

      adjSettings();

      printSelSetting();

      break;

    case 4:

      printSetPage();

      break;

    case 5 :

      printManualTx();

      break;

  }

  display.display();      // Show  text

  delay(10);

}