Gotta Go Faster, Part 2.

My implementation of integrating the ADAU1701, Teensy and LED matrix is quite specific to my situation and likings. I will therefor breakdown my implementation into several generic blocks like this:

Because I guess it will make it easier for anyone who would like to reuse just some of my code. Arduino is usually programmed in plain C but the compiler actually accept a subset of C++. I personally prefer C over C++ as a language but object orientation and templating can be quite powerful if used right. What I really wanted was to both have and eat the cake so I had to make up some basic rules for my code. Use object orientation where needed to make code reusable and easier to understand. Don't use dynamic memory allocation the code is going to be executed by a micro controller after all, allocate memory statically at compile time and use templating if needed. Don't use constructors and destructors, use a method like init instead.

My previous code used some external libraries. The Teensy also comes with a set of great libraries for timers etc. A great way to learn a new language or a new architecture is to rewrite libraries. I therefor decided to refactor all external dependencies (except the Teensy Audio library) into a library of reusable support classes. So lets start with these generic classes.

The first support class is a simple timer.

class Timer {
    // a simple timer class to check if time set has expired
  private:
    unsigned long started  = 0;
    unsigned long timeout  = 0;
  public:
    void set(unsigned long ms) {
      timeout = ms;
      reset();
    }
    void reset() {
      started = millis();
    }
    bool expired() {
      return elapsed() >= timeout;
    }
    unsigned long elapsed() {
      return millis() - started;
    }
};

The next class is a trigger class. Set it up with an intervall and a callback. Keep calling update and the callback function will be called on each expiry.

// trigger callback function
typedef void (*trigger_cb)();

class Trigger {
    // a simple trigger class with callback on expiry
  private:
    trigger_cb callback = NULL;
    unsigned long started   = 0;
    unsigned long interval  = 0;
  public:
    void setup(unsigned long ms, trigger_cb callback) {
      this->callback = callback;
      set(ms);
    }
    void set(unsigned long ms) {
      interval = ms;
      reset();
    }
    void reset() {
      started = millis();
    }
    void update() {
      unsigned long now = millis();
      if (now - started >= interval) {
        if (callback) callback();
        started = now;
      }
    }
};

Next up is a simple class for button debounce. Keep calling update and the callback function will be called with pin number and state. It uses the internal pull-up resistor and got sane defaults.

// debounce callback function, called with pin and state (HIGH = closed, LOW = open)
typedef void (*debounce_cb)(uint8_t, uint8_t);

class Debounce {
    // debounces a normally open (NO) switch connected for internal pullup resistor
  private:
    static const uint8_t DEF_MS   = 35;
    debounce_cb callback          = NULL;
    uint8_t lastState             = LOW;
    unsigned long lastChangedTime = 0;
    uint8_t debounceTime;
    uint8_t pin;
  public:
    void setup(uint8_t pin, debounce_cb callback, uint8_t ms) {
      this->pin = pin;
      this->callback = callback;
      this->debounceTime = ms;
      pinMode(pin, INPUT_PULLUP);
      update();
    }
    void setup(uint8_t pin, debounce_cb callback) {
      setup(pin, callback, DEF_MS);
    }
    void setup(uint8_t pin) {
      setup(pin, NULL, DEF_MS);
    }
    void update() {
      unsigned long now = millis();
      if (now - lastChangedTime >= debounceTime) {
        uint8_t currentState = !digitalRead(pin);
        if (currentState == lastState) return;
        lastChangedTime = now;
        lastState = currentState;
        if (callback) callback(pin, currentState);
      }
    }
    // state is LOW if switch is open and HIGH if closed
    uint8_t state() {
      return lastState;
    }
};

The next class is a bit more complex. It is for persistent wear leveling storage of a variable in EEPROM.  Set it up with the data type, the start address and number of slots.

// simple class for wear level storage in EEPROM
template <class T> 
class Persistent {
  private:
    static const uint8_t BLOCK_MARK = 0xfe;
    static const uint8_t BLOCK_FREE = 0xff;
    uint8_t blockSize;
    uint8_t blockNum;
    int blockAddr;
    int startAddr;
    int endAddr;
    T value;
    // seek last block mark and return address if found else -1
    int seek() {
      uint8_t mark;
      int result = -1;
      for (int addr = startAddr; addr < endAddr; addr += blockSize) {
        mark = EEPROM[addr];
        if (mark == BLOCK_MARK) {
          result = addr; // block mark found
        } else if (mark != BLOCK_FREE) {
          result = -1; // unformatted
        }
      }
      return result;
    }
    // mark all status bytes as free
    void format() {
      for (int addr = startAddr; addr < endAddr; addr += blockSize) {
        EEPROM[addr].update(BLOCK_FREE);
      }
    }
  public:
    // default data, start address and ring buffer slots
    void setup(T data, int address, uint8_t slots) {
      value = data;
      blockNum = slots;
      blockSize = sizeof(T) + 1;
      startAddr = address;
      endAddr = startAddr + blockNum * blockSize;
      if (endAddr > EEPROM.length()) endAddr = EEPROM.length();
      blockAddr = seek();
      if (blockAddr < 0) {
        // unformatted, format ring buffer
        format();
        blockAddr = startAddr;
        // put default data into ring buffer
        put(data);
      } else {
        // get stored data from EEPROM as value
        EEPROM.get(blockAddr + 1, value);
      }
    }
    // default data with a ten slot ring buffer from beginning of EEPROM
    void setup(T data) {
      setup(data, 0, 10);
    }
    // put data into wear level ring buffer
    void put(T data) {
      if (blockAddr < 0) return;
      int nextAddr = blockAddr + blockSize;
      if (nextAddr >= endAddr) nextAddr = startAddr;
      // put new data into ring buffer
      EEPROM.put(nextAddr + 1, data);
      // update with new mark
      EEPROM.update(nextAddr, BLOCK_MARK);
      // clear old mark after new update
      EEPROM.update(blockAddr, BLOCK_FREE);
      blockAddr = nextAddr;
      // store data as value
      value = data;
    }
    // get data from wear level ring buffer
    T get() {
      // get stored value insted of reading from EEPROM
      return value;
    }
    // return start address
    int begin() {
      return startAddr;
    }
    // return end address
    int end() {
      return endAddr;
    }
    // return length of ring buffer
    uint16_t length() {
      return blockNum * blockSize;
    }
};

These support classes got a couple of dependencies and they need to live somewhere. One solution is to put them in your main source code file. Another solution is to put them in a separate header file in the same folder as your main source code file. You then gave to wrap your classes with a couple of defines, like this:

#ifndef SupportClasses_h
#define SupportClasses_h
#include "Arduino.h"
#include "EEPROM.h"

// insert your classes and additional code here...

#endif

And finally save the file as SupportClasses.h