Pedro Martin
Published © GPL3+

Bluetooth RPM Letterboard v3.0

Capacitive touch, field programable whiteboard w/onboard LEDs & Piezo for keystroke feedback & Bluetooth keyboard for Text-To-Speech via iOS

AdvancedFull instructions provided396

Things used in this project

Hardware components

Adafruit ESP32 Feather V2
×1
Adafruit 12-Key Capacitive Touch Sensor Breakout
×3
onsemi 74HC595 Shift Register
×2
0805 0.01uF SMD Capacitor
×2
Reverse Mount SMD LED 1204 PCB Type
×60
1206 250mW 110-ohm SMD resistor
×6
2016 SMD piezo buzzer
×1
LiPo Battery - 3.7V 420mAh
×1
THT Slide SPDT EG1218 Switches
×6

Software apps and online services

KiCad
KiCad
Arduino IDE 2
I used to edit on VS Code and compile/run on Arduino IDE 1.8 (by declaring Outside Editor the on Arduino IDE Preferences). No more. The IDE 2 rocks!
Espressif Arduino ESP32
Fusion 360
Autodesk Fusion 360
ESP32 BLE Keyboard

Story

Read more

Code

RC3.ino

C/C++
#include "Arduino.h"
#define DEBUG //uncomment for debug printouts
#ifdef DEBUG
  #define dprint(x)      Serial.print(x) 
  #define dprintln(x)    Serial.println(x)
  #define dprintBIN(x)   Serial.print(x,BIN)
  #define dprintlnBIN(x) Serial.println(x,BIN)
  #define dprintHEX(x)   Serial.print(x,HEX)
  #define dprintlnHEX(x) Serial.println(x,HEX)
  #define dprintBegin(x) Serial.begin(x)
#else
#ifdef DEBUGBLE
  #define dprint(x)      bleKeyb.print(x)
  #define dprintln(x)    bleKeyb.println(x)
  #define dprintBIN(x)   bleKeyb.print(x)
  #define dprintlnBIN(x) bleKeyb.println(x)
  #define dprintHEX(x)   bleKeyb.print(x)
  #define dprintlnHEX(x) bleKeyb.println(x)
  #define dprintBegin(x)
#else
  #define dprint(x)
  #define dprintln(x)
  #define dprintBIN(x) 
  #define dprintlnBIN(x)
  #define dprintHEX(x)  
  #define dprintlnHEX(x) 
  #define dprintBegin(x)
#endif
#endif

#include "Declarations.h"
#include "tunes.h"
#include "fSupport.h"
#include "fCore.h"

void setup() {
  dprintBegin(115200);  dprintln("------start");
  //pinMode(13, OUTPUT);     //--Built in RED LED

  //---Buzzer---
  pinMode(buzzPin, OUTPUT); 
  //ledcSetup(buzzChan, 10000, 12); //--Parms=(channel, freq, resolution)
  ledcAttachPin(buzzPin, buzzChan);

  //---LEDs---
  pinMode(latchPin, OUTPUT);
  pinMode(dataPin, OUTPUT);
  pinMode(clockPin, OUTPUT);
  send2Shift(B11000000,B11111111);  //all LEDs off

  //---Preferences---
  storedVars.begin("baseVars", false);
  //bleMode = storedVars.getBool("bleModeTemp",true);
  symbolTable = storedVars.getUChar("symbolTableTemp",1);
  //dprint("FLASH bleMode = "); dprintln(bleMode);
  dprint("FLASH symTable = "); dprintln(symbolTable);
  storedVars.end();

  //---NeoPixel---
  pinMode(NEOPIXEL_I2C_POWER, OUTPUT); // GPIO 2
  digitalWrite(NEOPIXEL_I2C_POWER, HIGH);
  pinMode(0, OUTPUT); 
  pix.begin();
  pix.setBrightness(5);
  yellow = pix.Color(255,255,0);  orange = pix.Color(255,50,0);
  blue = pix.Color(0,150,255);    red = pix.Color(255,0,0);
  green = pix.Color(0,255,0);     violet = pix.Color(255,0,255);

  //---Cap Sensors---
  resetTouchVars();
  //loadSymbols(symbolTable); 
  Wire.begin (22,20);
  Wire.setClock(400000); //MAX=400khz per MPR121 specs
  //Wire.setTimeOut(50); //default=50 ms
  dprint("I2C Clock: "); dprintln(Wire.getClock());
  dprint("Time Out : "); dprintln(Wire.getTimeOut());
  if (!S0.begin(0x5A)) {dprintln("0x5A Not Ok"); buzz(30); while (1); } else buzz(10);
  if (!S1.begin(0x5C)) {dprintln("0x5C Not Ok"); buzz(30); while (1); } else buzz(11);
  if (!S2.begin(0x5D)) {dprintln("0x5D Not Ok"); buzz(30); while (1); } else buzz(12);

  //---Functions Switches & Interrupts
  for (i=0; i<5; i++) {
    pinMode(FS[i].PIN,INPUT); 
    FS[i].value = digitalRead(FS[i].PIN); //---initial state of each switch
    if (FS[i].value) progMode = false;
    attachInterruptArg(FS[i].PIN,readInterrupts,&FS[i],CHANGE);
  }
  letterKeyb = FS[0].value; dprintln("\n0:letter: " + String(letterKeyb));
  wordKeyb = FS[1].value; dprintln("1:word: " + String(wordKeyb));
  phraseKeyb = FS[2].value; dprintln("2:phrase: " + String(phraseKeyb));
  ledKeyb = FS[3].value; dprintln("3:LED: " + String(ledKeyb));
  buzzKeyb = FS[4].value; dprintln("4:Buzz: " + String(buzzKeyb));

  //---BLE Keyboard---
  if (letterKeyb + wordKeyb + phraseKeyb == 0) {
    bleMode = false; dprintln("bleMode OFF in Setup");}
  else {  
    bleKeyb.setName("RPM Board GEN3");  
    bleKeyb.begin();
  }
  loadSymbols(symbolTable); //moved from CapSensors group to playTune at end of Setup
}

void loop() {
  if (!progMode && bleMode) {  // i.e. in runMode
    while (!bleKeyb.isConnected() && !progMode && bleMode) { //the last two conditions could change via interrupts
      waitingOnBLE = true; 
      keybON = false; 
      buzz(20); 
      if (sFlag) chkSwitches();  //in case user enters progMode or turns BLE OFF while waiting for BLE pair    
    }
    waitingOnBLE = false;
    if (!keybON && bleKeyb.isConnected()) {  //run once after connected or reconnected
      keybON = true;
      bleKeyb.setDelay(3); //to compensate for fast input in iOS. Default=8
      buzz(21);
    }
  }    
  if (sFlag) chkSwitches();
  chkTouched();
  if (LEDisON) chkOnLED(); 
  chkBattery();
}

Declarations.h

C/C++
//---Counters---
int r, c, h, i, j, k, t;
ulong speed; 

//---Preferences (FLASH Memory)---
#include <Preferences.h>  //https://randomnerdtutorials.com/esp32-save-data-permanently-preferences/
Preferences storedVars;   //https://docs.espressif.com/projects/arduino-esp32/en/latest/api/preferences.html

//---BLE Keyboard & TTS Feedback---
#define USE_NIMBLE
#include <BleKeyboard.h> //.h file modified as per https://github.com/T-vK/ESP32-BLE-Keyboard/pull/111#issuecomment-954894261
BleKeyboard bleKeyb("RPMLetterboard","PMC2022",99);
bool keybON = false;
bool bleMode = true; 
char charCache[51] = {}; // c-string to hold a 50-char string
byte charCount = 0;
char * pch;

//---Cap Sensors---
ulong lastTouch = 0;
uint16_t tReg5A = 0; uint16_t tReg5C = 0; uint16_t tReg5D = 0;
uint32_t NOTcurrCols, NOToldCols, currCols, oldCols = 0;
uint16_t NOTcurrRows, NOToldRows, currRows, oldRows = 0;
uint32_t touchedCols, releasedCols, touchedRows, releasedRows;
#include "Wire.h"  // C:\Users\pedro\AppData\Local\Arduino15\packages\esp32\hardware\esp32\2.0.4\libraries\Wire\src
#include "MPR121.h"
MPR121 S0, S1, S2;
bool seqOK = true;

//---Buzzer---
const byte buzzPin = 12;
const byte buzzChan = 0; //channel 15 misfires: don't use

//---NeoPixel---
uint32_t yellow, orange, blue, red, green, violet; 
#include <Adafruit_NeoPixel.h>
Adafruit_NeoPixel pix(1,0);

//---LEDs---  
// https://docs.arduino.cc/tutorials/communication/guide-to-shift-out
// https://www.arduino.cc/reference/en/language/functions/advanced-io/shiftout/
const byte clockPin = 5;
const byte latchPin = 19;
const byte dataPin = 21;
//           shift1 output =xxcdefgh
//              row number =  123456 (1 = current source activated)
const byte rowShiftOne[6] = {B100000,B010000,B001000,B000100,B000010,B000001};
//               shift1 output = abxxxxxx
//                  col number = 9A (0 = current drain activated)
const byte    colShiftOne[10] = {B11,B11,B11,B11,B11,B11,B11,B11,B01,B10}; 
const byte colShiftOneFat[10] = {B11,B11,B11,B11,B11,B11,B11,B11,B00,B00}; 
//                shift2 output = abcdefgh
//                    col number =67854321 (0 = current drain activated)
const byte    colShiftTwo[10] = {B11111110,B11111101,B11111011,B11110111,B11101111,B01111111,B10111111,B11011111,B11111111,B11111111};
const byte colShiftTwoFat[10] = {B11111100,B11111100,B11110011,B11110011,B01101111,B01101111,B10011111,B10011111,B11111111,B11111111};
ulong elapsedLED = 0;
bool LEDisON = false;
int onTimer = 200; //adapt to speed of user but beware also used in progMode as witness
byte sym4LED;
byte spaceShiftOne, spaceShiftTwo ,enterShiftOne, enterShiftTwo, backsShiftOne, backsShiftTwo;

//---Funtcion Switches & Interrupts---
// FORUM solution:  https://forum.arduino.cc/t/attachinterrupt-using-array-of-function-pointer/1019684/4
// https://docs.espressif.com/projects/arduino-esp32/en/latest/api/gpio.html?highlight=interrupt#about
typedef struct {
  const uint8_t PIN;
  bool value;
} fSwitch;
fSwitch FS[5] = {{27,0},{33,0},{15,0},{32,0},{14,0}}; //function switches in these pins
volatile bool sFlag = false;
volatile bool buzzFlag = false;
volatile bool waitingOnBLE = false;
volatile bool oldRead, newRead;
volatile uint8_t debCount = 0;
bool buzzKeyb = false;  
bool ledKeyb = false;
bool letterKeyb = false; // send letter. Will read letter (immediately) & word (when sending SPACE)
bool wordKeyb = false; // send word (no letter). Will read immediately because will also send SPACE
bool phraseKeyb = false; // send phrase. Will read immediately

//---Battery---
ulong elapsedBatt = 0;
int battTimer = 0;
float battRead; 

// String vs string  https://forum.arduino.cc/t/difference-between-char-array-and-string/603608/8
//            basic  https://www.arduinoplatform.com/arduino-programming/understanding-character-strings-in-arduino/
//          c++ Lib  https://cplusplus.com/reference/cstring/
//                   https://forum.arduino.cc/t/solved-printing-a-portion-of-a-char-string/159984
        //int tempInt = pch - charCache + 1;                        
        //dprintln(charCache + tempInt);
        //strncpy(charTemp,&charCache[pch - charCache + 1],charCount);
        //dprintln(charTemp);
        //dprintln(&charCache[pch - charCache + 1]);

//---Board Prog Mode, Symbol Tables
byte symbolTable = 1;
bool progMode = true;
byte cSym, vSym;
byte rSym = 2;
byte offR = 1;
byte offC = 0;
byte hSym = 6;
byte sym2Pix[14][22]; //has to be full 14x22 to allow for possible offsets in Row / Col
byte LEDrSym[60]; byte LEDcSym[60];  //60 LEDs
char symbol[61] = {}; //*************************************one more for null termination?
int lastSymNum = 99;
int currSymNum;
char currSym, lastSym;
//all symbols have same measures, Other layouts w/o LEDS can use all 14 rows these three use 12 rows (1 LED every 2 rows)
// All Layouts w/LED: rSym=2, offR=1, offC=0 --> hSym = 6
// cSym = (large 4), (medium 3), (qwerty 2)  --> vSym = (5),(7),(10) (plus 1 w/o LED))
//const byte vSym = trunc((22 - offC) / cSym) - (cSym==2); // number of vertical (cols) symbols = vSym
//const byte hSym = trunc((14 - offR) / rSym); // number of horizontal (rows) symbols = hSym*/

MPR121.h

C/C++
#include <Adafruit_BusIO_Register.h>
#include <Adafruit_I2CDevice.h>  
  
#define I2CADDR_DEFAULT 0x5A 
  
enum {  // Device register map
  MPR121_TOUCHSTATUS_L = 0x00,
  MPR121_TOUCHSTATUS_H = 0x01,
  MPR121_FILTDATA_0L = 0x04,
  MPR121_FILTDATA_0H = 0x05,
  MPR121_BASELINE_0 = 0x1E,
  MPR121_MHDR = 0x2B,
  MPR121_NHDR = 0x2C,
  MPR121_NCLR = 0x2D,
  MPR121_FDLR = 0x2E,
  MPR121_MHDF = 0x2F,
  MPR121_NHDF = 0x30,
  MPR121_NCLF = 0x31,
  MPR121_FDLF = 0x32,
  MPR121_NHDT = 0x33,
  MPR121_NCLT = 0x34,
  MPR121_FDLT = 0x35,
  MPR121_TOUCHTH_0 = 0x41,
  MPR121_RELEASETH_0 = 0x42,
  MPR121_DEBOUNCE = 0x5B,
  MPR121_CONFIG1 = 0x5C,
  MPR121_CONFIG2 = 0x5D,
  MPR121_ECR = 0x5E,
  MPR121_AUTOCONFIG0 = 0x7B,
  MPR121_AUTOCONFIG1 = 0x7C,
  MPR121_UPLIMIT = 0x7D,     
  MPR121_LOWLIMIT = 0x7E,   
  MPR121_TARGETLIMIT = 0x7F, 
  MPR121_SOFTRESET = 0x80,
};

enum { //Input vaiables for Autoconfig
  p_USL = 245,
  p_LSL = 170,
  p_TL = 220,
  p_TT = 16,
  p_RT = 4,
  p_MHDR = 1,
  p_NHDR = 1,
  p_NCLR = 1, 
  p_FDLR = 0,
  p_MHDF = 2,
  p_NHDF = 2,
  p_NCLF = 1,
  p_FDLF = 0,
  p_NHDT = 1,
  p_NCLT = 0,
  p_FDLT = 0,
  p_DT = 4,
  p_DR = 2,
  p_FFI = 10, //initial stable value = 18
  p_SFI = 4,
  p_ESI = 4,
};

class MPR121 {
  public:
    MPR121();  //Hardware I2C
    bool begin(uint8_t i2caddr = I2CADDR_DEFAULT, TwoWire *theWire = &Wire);
    uint8_t readRegister8(uint8_t reg);
    void writeRegister(uint8_t reg, uint8_t value);
    void setThresholds(uint8_t touch, uint8_t release);
    //uint16_t readRegister16(uint8_t reg);      
    //uint16_t touched(void);
    //uint16_t filteredData(uint8_t t);
    //uint16_t baselineData(uint8_t t);
   private:
     Adafruit_I2CDevice *i2c_dev = NULL;
};

MPR121::MPR121() {}  // Default constructor

bool MPR121::begin(uint8_t i2caddr, TwoWire *theWire) {
  if (i2c_dev) delete i2c_dev;
  i2c_dev = new Adafruit_I2CDevice(i2caddr, theWire);
  delay(10);
  if (!i2c_dev->begin()) {
    dprintHEX(i2caddr); dprintln(": I2C Fail"); 
    return false;
  }
  writeRegister(MPR121_SOFTRESET, 0x63); //reset all regsiters to 0x00 except 0x5C=0x10 & 0x5D=0x24
  writeRegister(MPR121_ECR, 0x0);  //go to STOP Mode
  delay(10);
  uint8_t c = readRegister8(MPR121_CONFIG2);
  if (c != 0x24) {
    dprintHEX(i2caddr); dprintln(": Reset Fail");
    return false;
  }

  setThresholds(p_TT,p_RT);
  writeRegister(MPR121_MHDR, p_MHDR);
  writeRegister(MPR121_NHDR, p_NHDR);
  writeRegister(MPR121_NCLR, p_NCLR);
  writeRegister(MPR121_FDLR, p_FDLR);
  writeRegister(MPR121_MHDF, p_MHDF);
  writeRegister(MPR121_NHDF, p_NHDF);
  writeRegister(MPR121_NCLF, p_NCLF);
  writeRegister(MPR121_FDLF, p_FDLF);
  writeRegister(MPR121_NHDT, p_NHDT);
  writeRegister(MPR121_NCLT, p_NCLT);
  writeRegister(MPR121_FDLT, p_FDLT);
  writeRegister(MPR121_DEBOUNCE, (p_DR << 4) + p_DT);

  uint8_t AFE = 1;  // CDC=1 as default. CDCx will be used instead
  switch (p_FFI) {
    case 10: AFE += 64; break;
    case 18: AFE += 128; break;
    case 34: AFE += 192; break;
  }
  uint8_t FCR = 32; // CDT=0.5uS (32 BIN) as default. CDTx will be used instead
  switch (p_SFI) {
    case 6: FCR += 8; break;
    case 10: FCR += 16; break;
    case 18: FCR += 24; break;
  }
  uint8_t temp = p_ESI;
  FCR += log10(temp) / log10(2);
  writeRegister(MPR121_CONFIG1, AFE); 
  writeRegister(MPR121_CONFIG2, FCR); 

  writeRegister(MPR121_UPLIMIT, p_USL);     
  writeRegister(MPR121_TARGETLIMIT, p_TL); 
  writeRegister(MPR121_LOWLIMIT, p_LSL);   

  writeRegister(MPR121_AUTOCONFIG0, AFE - 1 + B111011); // Bxx011011 factory default
                //AFE-1 = FFI when CDC = 1 (default). Must match FFI in 0x5C - CONFIG1
                //RETRY=01,11 (2,8 times) for autoconfig and autoreconfig
                //BVA=10 same as the CL bits in ECR (0x5E)
                //ARE=1 (auto reconfig enabled), ACE=1 (autoconfig enabled)

  byte ECR_SETTING = B10000000 + 12; //5 bits baseline tracking, proximity disabled + 12 (11xx) electrodes running
  writeRegister(MPR121_ECR, ECR_SETTING); 
  delay(40); //make time for autoconfig and possible auto reconfig of all electrodes
  
  dprint("--Sensor at 0x"); dprintlnHEX(i2caddr);
  for (uint8_t i = 0x5F; i < 0x6B; i++) {  //Display results of autoconfig
    uint8_t r = readRegister8(i);
    dprint("CDC"); dprint(i - 0x5F);    //0 to 11
    dprint("(0x"); dprintHEX(i);      //5F to 6A
    dprint("):"); dprint(r);  
    temp = trunc((i - 0x5F)/2);
    r = readRegister8(temp + 0x6C); 
    dprint("\tCDT"); dprint(i - 0x5F);  //0 to 11
    dprint("(0x"); dprintHEX(temp + 0x6C);  //6C to 71
    dprint("):" ); 
    if ((i - 0x5F) % 2 == 0) dprintln((r & B00000111)); //lower 3 bits
    else dprintln((r >> 4));  //upper 4 bits 
  }
  return true;
}

void MPR121::setThresholds(uint8_t touch, uint8_t release) {  
  for (uint8_t i = 0; i < 12; i++) {  //set all thresholds to same value
    writeRegister(MPR121_TOUCHTH_0 + 2 * i, touch);
    writeRegister(MPR121_RELEASETH_0 + 2 * i, release);
  }
}

uint8_t MPR121::readRegister8(uint8_t reg) {
  Adafruit_BusIO_Register thereg = Adafruit_BusIO_Register(i2c_dev, reg, 1);
  return (thereg.read());
}

void MPR121::writeRegister(uint8_t reg, uint8_t value) {
  bool stop_required = true;  //MPR121 must be put in Stop Mode to write to most registers
  Adafruit_BusIO_Register ecr_reg = Adafruit_BusIO_Register(i2c_dev, MPR121_ECR, 1);
  uint8_t ecr_backup = ecr_reg.read();  //get current value of MPR121_ECR register
  if ((reg == MPR121_ECR) || ((0x73 <= reg) && (reg <= 0x7A))) stop_required = false;
  if (stop_required) ecr_reg.write(0x00);  //set to zero to set board in stop mode
  Adafruit_BusIO_Register the_reg = Adafruit_BusIO_Register(i2c_dev, reg, 1);
  the_reg.write(value);  //write value in passed register
  if (stop_required) ecr_reg.write(ecr_backup);  //write back the previous set ECR settings
}

/*
uint16_t MPR121::filteredData(uint8_t t) {
  if (t > 12) return 0;
  return readRegister16(MPR121_FILTDATA_0L + t * 2);
}

uint16_t MPR121::baselineData(uint8_t t) {
  if (t > 12) return 0;
  uint16_t bl = readRegister8(MPR121_BASELINE_0 + t);
  return (bl << 2);
} 

uint16_t MPR121::touched(void) {
  uint16_t t = readRegister16(MPR121_TOUCHSTATUS_L);
  return t & 0x0FFF;
}

uint16_t MPR121::readRegister16(uint8_t reg) {
  Adafruit_BusIO_Register thereg = Adafruit_BusIO_Register(i2c_dev, reg, 2, LSBFIRST);
  return (thereg.read());
}*/

fCore.h

C/C++
void setProgMode() {
  storedVars.begin("baseVars", false);
  for (k=0;k<22;k++) 
    if (bitRead(touchedCols,k)) { // this is select, not toggle
      if (k>14) {
        dprintln("layout tres"); 
        if (!storedVars.putUChar("symbolTableTemp",3)) dprintln("error preferences symbolTable 3");
        turnOnLED(3,-1,-1); 
        loadSymbols(3); k=22; } 
      else {
        if (k>7) {
          dprintln("layout dos");
          if (!storedVars.putUChar("symbolTableTemp",2)) dprintln("error preferences symbolTable 2");
          turnOnLED(2,-1,-1); 
          loadSymbols(2); k=22; }
        else {
          dprintln("layout uno");
          if (!storedVars.putUChar("symbolTableTemp",1)) dprintln("error preferences symbolTable 1"); 
          turnOnLED(1,-1,-1); 
          loadSymbols(1); k=22; }
      }
    }
  storedVars.end();
}

void printTouchData() {
  lastSymNum = 99;
  lastSym = 99;
  seqOK = true;
  for (r=0; r<14; r++)
    if (bitRead(touchedRows,r))
      for (c=0; c<22; c++) 
        if (bitRead(touchedCols,c)) {
          currSymNum = sym2Pix[r][c];
          currSym = symbol[currSymNum];
          if (lastSymNum != 99 && currSym != lastSym) { //not the first cycle and diff symbols touched
            seqOK = false; c=22; r=14;
            dprintln("\nSeq Not Ok: diff symbols"); 
          } 
          else {
            if (currSym == '&') { //touched a blank space in QWERTY layout
              seqOK = false; c=22; r=14;
              dprintln("\nSeq Not Ok: blank space"); 
            }
            else {
              if (currSymNum > (vSym*hSym)) { //touched area doesn't have a symbol -> sym2Pix is 14x22 w/default value=98
                seqOK = false; c=22; r=14;
                dprintln("\nSeq Not Ok: outside area");
              } 
            }
          }
          lastSymNum = currSymNum;
          lastSym = currSym;
        }

  if (seqOK) { 
    switch (int(currSym)) {
    case 32:  sym4LED = 12;
              if (bleMode) {
                if (letterKeyb) {dprint(currSym); bleKeyb.print(currSym);}  //-------if LETTER
                else {
                  if (wordKeyb) {  //------------------------------------------------if WORD
                    pch = strrchr(charCache, ' ');
                    if (pch != NULL) {dprintln(&charCache[pch - charCache + 1]); bleKeyb.print(&charCache[pch - charCache + 1]);}
                    else {dprintln(charCache); bleKeyb.print(charCache); }
                    bleKeyb.print(currSym);
                  } 
                }
                if (phraseKeyb) {charCache[charCount] = currSym; charCount++; } //---if PHRASE
                else charCount = 0;
                charCache[charCount] = '\0';
              }
              break;
    case 8:   sym4LED = 13;
              if (bleMode) {
                if (charCount > 0) {charCount--; charCache[charCount] = '\0'; bleKeyb.print(currSym);}
              }
              break;
    case 10:  sym4LED = 14;
              if (bleMode) {
                if (phraseKeyb) { //-------------------------------------------------if PHRASE
                  bleKeyb.print(currSym);
                  delay(350); //time for read last word and CR/LF
                  pch = strtok(charCache," ");
                  while (pch != NULL) {
                    dprint(pch); bleKeyb.print(pch);
                    dprint(" "); bleKeyb.print(" ");
                    delay(135 * strlen(pch)); //give iOS time to read whole word
                    pch = strtok(NULL," ");  
                  }
                  bleKeyb.print(currSym);
                }else {
                  if (wordKeyb && !letterKeyb) { //----------------------------------if WORD
                    pch = strrchr(charCache, ' ');
                    if (pch != NULL) {dprintln(&charCache[pch - charCache + 1]); bleKeyb.print(&charCache[pch - charCache + 1]);}
                    else {dprintln(charCache); bleKeyb.print(charCache); }
                  }
                  dprintln(currSym); bleKeyb.print(currSym); //----------------------if LETTER
                }
                charCount = 0; charCache[charCount] = '\0';
              }
              break;
    default:  sym4LED = 11;
              if (bleMode) {
                charCache[charCount] = currSym;
                charCount++; 
                charCache[charCount] = '\0'; 
                if (letterKeyb) {
                  dprint(currSym); bleKeyb.print(currSym); } //----------------------if LETTER
                  /*bleKeyb.println(micros() - speed);*/ 
              }
              break;
    }
    turnOnLED(sym4LED,LEDrSym[currSymNum],LEDcSym[currSymNum]);
    buzz(50);
    //dprint("time="); dprintln(micros() - speed);
  }else { // invalid keypress
    buzz(51);
    turnOnLED(10,-1,-1);
  }
}

void resetTouchVars() {
  touchedRows = 0; touchedCols = 0;
  releasedRows = 0; releasedCols = 0;
  oldRows = 0;  oldCols = 0;
  currRows = 0; currCols = 0;
  lastTouch = 0; currSymNum = 0; currSym = 99;
}

void chkTouched() {
  //Read Registers 00 & 01 (bytes 1 & 2) and build 14 bit & 22 bit row & col bytes
  Wire.requestFrom(0x5A,2); tReg5A = (Wire.read() + (Wire.read() << 8)) & 0x0FFF;
  Wire.requestFrom(0x5C,2); tReg5C = (Wire.read() + (Wire.read() << 8)) & 0x0FFF;
  Wire.requestFrom(0x5D,2); tReg5D = (Wire.read() + (Wire.read() << 8)) & 0x0FFF;

  currRows = bitRead(tReg5A,11) << 13; //R14 in Sensor A
  for (k=0; k<=11; k++) 
    currRows += (bitRead(tReg5D,k) << (k + 12 - (k * 2))); //R13 to R02 in Sensor D
  currRows += bitRead(tReg5C,0); //R01 in Sensor C   
  currCols = 0;
  for (k=10; k>=0; k--) {
    currCols += (bitRead(tReg5A,k) << ((k * 2) + 1)); //Evens C02 to C22 in Sensor A
    currCols += (bitRead(tReg5C,k + 11 - (k * 2)) << (k * 2)); //Odds C21 to C01 in Sensor C
  } 

  //below follows the acum results of several T/R's until (Ts = Rs) or (500mS have elasped)
  NOToldRows = (~oldRows) & 0x3FFF; NOTcurrRows = (~currRows) & 0x3FFF; //keep only rightmost 14 bits
  NOToldCols = (~oldCols) & 0x3FFFFF; NOTcurrCols = (~currCols) & 0x3FFFFF; //keep only rightmost 22 bits
  touchedRows |= (currRows & NOToldRows); touchedCols |= (currCols & NOToldCols);  //(touched now) AND (NOT touched before)
  releasedRows |= (NOTcurrRows & oldRows); releasedCols |= (NOTcurrCols & oldCols); //(NOT touched now) AND (touched before)

  if ((currRows & NOToldRows) + (currCols & NOToldCols) +  //something touched
     (NOTcurrRows & oldRows) + (NOTcurrCols & oldCols) > 0) //something released
     lastTouch = millis(); //something changed in this loop cycle only, therefore register the millis
  oldRows = currRows; oldCols = currCols;

  if (touchedRows > 0 && touchedCols > 0) {  //ACUM of (touched now) AND (NOT touched before). At least 1 row AND 1 col
    if ((touchedRows == releasedRows) && (touchedCols == releasedCols)) {  speed = micros();
      if (!progMode) printTouchData(); else setProgMode(); 
      resetTouchVars();  
    }else {
      if (millis() - lastTouch > 500) { //no matching pairs but 500ms have elapsed since last T/R change
        if (!progMode) {dprintln("unpaired timeout"); printTouchData(); } else setProgMode(); 
        resetTouchVars();
      }
    }
  }else { /*nothing was touched OR only 1 row/col was touched up to this loop cycle: maybe in process of reading full touch event or maybe touch event is done with partial read
      if (nothing was touched) then exit (i.e. continue). Can be known if T+R == 0
      if (in process of reading full touch) then exit (i.e. continue). Unknowable
      if (partial touch finished, i.e. less than 1r AND 1c) then invalid keypress. Can be known only after time lapse
    */
    if ((touchedRows > 0 && touchedCols == 0) || (touchedRows == 0 && touchedCols > 0)) //only 1r OR only 1c touched, else, nothing has been touched
      if (millis() - lastTouch > 200) { //if timeout has not occured, wait for more loops to see if another r/c is coming
        dprintln("\npartial touch timeout");   //need to press harder
        dprint("rows:"); dprintBIN(touchedRows); dprint(", cols:"); dprintlnBIN(touchedCols); //also the point of board noise = mystery
        buzz(51);
        turnOnLED(10,-1,-1);
        resetTouchVars();
      }
  }
}

void chkSwitches()  {  //called when sFlag set TRUE by Interrupt (physical switch changed), which is also called with each Buzz from ground bounce)
  //will hit this with every tick caused by buzz from waitingOnBLE. Fix w/board 2.0 cap debouncer
  sFlag = false;
  letterKeyb = FS[0].value; dprintln("\n0:letter: " + String(letterKeyb));
  wordKeyb = FS[1].value; dprintln("1:word: " + String(wordKeyb));
  phraseKeyb = FS[2].value; dprintln("2:phrase: " + String(phraseKeyb));
  ledKeyb = FS[3].value; dprintln("3:LED: " + String(ledKeyb));
  buzzKeyb = FS[4].value; dprintln("4:Buzz: " + String(buzzKeyb));
  if (letterKeyb + wordKeyb + phraseKeyb + ledKeyb + buzzKeyb == 0) // since something changed (i.e. we're here) and all=0, user selected progMode
    {progMode = true; buzz(70);}
  else {
    if (progMode) {progMode = false; buzz(71);} //could've changed any one switch so need to chk if we were in progMode
    else buzz(60); //audio witness for non-progMode switch change
  }
  if (letterKeyb + wordKeyb + phraseKeyb == 0) {  
      dprintln("bleMode OFF"); 
      bleMode = false; 
      bleKeyb.end();  
  }
  else 
    if (!bleMode) {
      dprintln("bleMode ON");
      bleMode = true; 
      bleKeyb.setName("RPM Board GEN3");  
      bleKeyb.begin(); 
    }
}

void IRAM_ATTR readInterrupts(void *arg) {
  if (!buzzFlag || waitingOnBLE) { // to filter out interruts triggered by ground bounce from ledcWriteTone command
                                   // and allow to go into progMode if waitingOnBLE
    fSwitch *fs = static_cast<fSwitch *>(arg);
    oldRead = digitalRead(fs->PIN);
    debCount = 0;
    while (debCount < 3) {
      newRead = digitalRead(fs->PIN);
      if (oldRead == newRead) debCount++;
      oldRead = newRead; 
    }
    fs->value = newRead;
    sFlag = true;
  }
}

fSupport.h

C/C++
void buzz(int bType) {
  buzzFlag = true; // to intercept interrupts triggered by ledcWriteTone
  switch (bType) {
    case 10: ledcWriteTone(buzzChan,1000); delay(40); ledcWrite(buzzChan,0); break;
             //--TS1 is up
    case 11: ledcWriteTone(buzzChan,1500); delay(40); ledcWrite(buzzChan,0); break;
             //--TS2 is up
    case 12: ledcWriteTone(buzzChan,2000); delay(40); ledcWrite(buzzChan,0); break; 
             //--TS3 is up 
    case 20: ledcWriteTone(buzzChan,1100); delay(10); ledcWrite(buzzChan,0); pix.setPixelColor(0,blue); pix.show(); delay(1000-10-20); 
             ledcWriteTone(buzzChan,400); delay(20); ledcWrite(buzzChan,0); pix.clear(); pix.show(); delay(1000-20-20); break; 
             //--waiting on BLE pair
    case 21: ledcWriteTone(buzzChan,900); delay(200); ledcWriteTone(buzzChan,400);
             delay(600); ledcWrite(buzzChan,0); break; 
             //--BLE pairing achieved
    case 30: ledcWriteTone(buzzChan,100); pix.setPixelColor(0,red); pix.show(); delay(2000); ledcWrite(buzzChan,0); break;
             //--fatal error, must reboot manually
    case 40: pix.setPixelColor(0,red); pix.show(); ledcWriteTone(buzzChan,1500); delay(40); 
             ledcWrite(buzzChan,0); pix.clear(); pix.show(); break;
             //--low battery   
    case 50: if (buzzKeyb) {ledcWriteTone(buzzChan,200); delay(10); ledcWriteTone(buzzChan,0);} break;
             //--valid keyb click
    case 51: if (buzzKeyb) {ledcWriteTone(buzzChan,60); delay(80); ledcWriteTone(buzzChan,0);} break;
             //--invalid keyb click
    case 60: ledcWriteTone(buzzChan,300); delay(60); ledcWriteTone(buzzChan,0); break;
             //--function switch change
    case 70: ledcWriteTone(buzzChan,500); delay(50); ledcWriteTone(buzzChan,1500); delay(50);
             ledcWriteTone(buzzChan,2500); delay(80); ledcWrite(buzzChan,0); break;
             //--progMode ON
    case 71: ledcWriteTone(buzzChan,2500); delay(50); ledcWriteTone(buzzChan,1500); delay(50); 
             ledcWriteTone(buzzChan,500); delay(80); ledcWrite(buzzChan,0); break; 
             //--progMode OFF
  }  
  buzzFlag = false;
}

void send2Shift(byte shiftOne, byte shiftTwo) {
  digitalWrite(latchPin, 0);
  shiftOut(dataPin, clockPin, LSBFIRST, shiftTwo);
  shiftOut(dataPin, clockPin, LSBFIRST, shiftOne);
  digitalWrite(latchPin, 1);
}

void turnOnLED(byte type, int rowLED, int colLED) {
  elapsedLED = millis();
  LEDisON = true;
  switch (type) {
    case 10: pix.setPixelColor(0,orange); break; // invalid touch
    case 11: pix.setPixelColor(0,yellow); // valid touch = letter or number
             if (ledKeyb){
               if (cSym == 4) send2Shift(rowShiftOne[rowLED] + (colShiftOneFat[colLED] << 6),colShiftTwoFat[colLED]);
               else send2Shift(rowShiftOne[rowLED] + (colShiftOne[colLED] << 6),colShiftTwo[colLED]);
             }
             break;
    case 12: pix.setPixelColor(0,yellow); // valid touch = spacebar
             if (ledKeyb) send2Shift(spaceShiftOne,spaceShiftTwo);  //all layouts have spacebar
             break;
    case 13: pix.setPixelColor(0,yellow); // valid touch = backspace
             if (ledKeyb) 
              if (cSym == 2) send2Shift(backsShiftOne,backsShiftTwo); //only layout=2 has backspace
             break;
    case 14: pix.setPixelColor(0,yellow); // valid touch = enter
             if (ledKeyb) 
              if (cSym == 3 || cSym == 2) send2Shift(enterShiftOne,enterShiftTwo); //only layouts=3,2 have enter
             break;
    case  1: pix.setPixelColor(0,violet); // layout 1 selected in progMode
             send2Shift(B11111111,B11111000); break; //cols 1,2,3
    case  2: pix.setPixelColor(0,violet); // layout 2 selected in progMode
             send2Shift(B11111111,B00101111); break; //cols 5,6,7
    case  3: pix.setPixelColor(0,violet); // layout 3 selected in progMode
             send2Shift(B00111111,B11011111); break; //cols 8,9,A 
  }
  pix.show();
}

void chkOnLED() {
  if (millis() - elapsedLED > onTimer) {
    LEDisON = false;
    send2Shift(B11000000,B11111111);  //all LEDs off
    pix.clear();
    pix.show(); 
  }
}

void loadSymbols(byte boardMode) {  
  symbolTable = boardMode;
  // '&' will be filtered out in printTouchData
  switch (boardMode) {
    case 1: cSym = 4; vSym = 5; 
            symbol[0]='a'; symbol[1]='b'; symbol[2]='c'; symbol[3]='d'; symbol[4]='e';
            symbol[5]='f'; symbol[6]='g'; symbol[7]='h'; symbol[8]='i'; symbol[9]='j';
            symbol[10]='k';symbol[11]='l';symbol[12]='m';symbol[13]='n';symbol[14]=';';
            symbol[15]='o';symbol[16]='p';symbol[17]='q';symbol[18]='r';symbol[19]='s';
            symbol[20]='t';symbol[21]='u';symbol[22]='v';symbol[23]='w';symbol[24]='x';
            symbol[25]='y';symbol[26]='z';symbol[27]= 32;symbol[28]= 32;symbol[29]= 32;
            spaceShiftOne = B00000001;
            spaceShiftTwo = B00001111;
            break;
    case 2: cSym = 3; vSym = 7;
            symbol[0]='a'; symbol[1]='b'; symbol[2]='c'; symbol[3]='d'; symbol[4]='e'; symbol[5]='f'; symbol[6]='g';
            symbol[7]='h'; symbol[8]='i'; symbol[9]='j';symbol[10]='k';symbol[11]='l';symbol[12]='m';symbol[13]='n';
            symbol[14]=';';symbol[15]='o';symbol[16]='p';symbol[17]='q';symbol[18]='r';symbol[19]='s';symbol[20]='t';
            symbol[21]='u';symbol[22]='v';symbol[23]='w';symbol[24]='x';symbol[25]='y';symbol[26]='z';symbol[27]='1';
            symbol[28]='2';symbol[29]='3';symbol[30]='4';symbol[31]='5';symbol[32]='6';symbol[33]='7';symbol[34]='8';
            symbol[35]='9';symbol[36]='0';symbol[37]=32; symbol[38]=32; symbol[39]=32; symbol[40]=10; symbol[41]=10;
            spaceShiftOne = B11000001;
            spaceShiftTwo = B00100111;
            enterShiftOne = B00000001;
            enterShiftTwo = B11111111;
            break;
    case 3: cSym = 2; vSym = 10;
            symbol[0]='1'; symbol[1]='2'; symbol[2]='3'; symbol[3]='4'; symbol[4]='5'; symbol[5]='6'; symbol[6]='7'; symbol[7]='8'; symbol[8]='9'; symbol[9]='0';
            symbol[10]='&';symbol[11]='&';symbol[12]='&';symbol[13]='&';symbol[14]='&';symbol[15]='&';symbol[16]='&';symbol[17]='&';symbol[18]='&';symbol[19]='&'; 
            symbol[20]='q';symbol[21]='w';symbol[22]='e';symbol[23]='r';symbol[24]='t';symbol[25]='y';symbol[26]='u';symbol[27]='i';symbol[28]='o';symbol[29]='p';
            symbol[30]='a';symbol[31]='s';symbol[32]='d';symbol[33]='f';symbol[34]='g';symbol[35]='h';symbol[36]='j';symbol[37]='k';symbol[38]='l';symbol[39]=';'; 
            symbol[40]='&';symbol[41]='z';symbol[42]='x';symbol[43]='c';symbol[44]='v';symbol[45]='b';symbol[46]='n';symbol[47]='m';symbol[48]=10; symbol[49]=10;
            symbol[50]='&';symbol[51]=8;  symbol[52]=8;  symbol[53]=8;  symbol[54]=32; symbol[55]=32; symbol[56]=32; symbol[57]=32; symbol[58]=10; symbol[59]=10;
            spaceShiftOne = B11000001;
            spaceShiftTwo = B00001111;
            enterShiftOne = B00000011;
            enterShiftTwo = B11111111;
            backsShiftOne = B11000001;
            backsShiftTwo = B11110001;
            break;            
  }
  for (i=0; i<14; i++)
    for (j=0; j<22; j++) 
      sym2Pix[i][j] = 98;
  for (h=0; h<hSym; h++) { //hSym symbols down = 6
    t = 0;
    for (i=0; i < vSym; i++) { //vSym symbols across = 10,7,5
      for (j=0; j < rSym; j++) { //rSym rows per symbol = 2
        for (k=0; k < cSym; k++) { //cSym columns per symbol = 2,3,4
          sym2Pix [j + (rSym*h) + offR] [k + (cSym*i) + offC] = vSym*h + i;    
          //dprintln("R=" + String(j+(rSym*h)+offR) + "\tC=" + String(k+(cSym*i)+offC) + "\tS=" + String(vSym*h + i) + "\tSym=" + symbol[vSym*h + i]);
        }
      }
      LEDrSym[(h*vSym)+i] = (((rSym*h) + offR) - 1) / 2;  //0 to 5
      if (cSym == 3) {
        LEDcSym[(h*vSym)+i] =  i + t;  // 0,2,3,5,6,8,9
        //dprintln(String((h*vSym)+i)+"\t"+String((((rSym*h)+offR)-1)/2)+"\t"+String(i+t));
        if ((i % 2) == 0) t++; }
      if (cSym == 4 || cSym == 2) {
        LEDcSym[(h*vSym)+i] = (((cSym*i) + offC))/2;  //0 to 9
        //dprintln(String((h*vSym)+i)+"\t"+String((((rSym*h)+offR)-1)/2)+"\t"+String((((cSym*i) + offC)    ) / 2)); 
      }
    }
  }
  playTune(symbolTable);
}

void chkBattery() {
  if (millis() - elapsedBatt > (battTimer)) {  
    elapsedBatt = millis();
    battRead = analogRead(35); 
    battRead = ((battRead*2) / 4095 * 3.3 * 1.095);
    if (battRead < 3.5) {
      buzz(40);
      battTimer = 5 * 1000;
    }else  
      battTimer = 5 * 60 * 1000; 
    //dprintln(battRead); 
    //bleKeyb.println(battRead);
  }
}

tunes.h

C/C++
// https://github.com/robsoncouto/arduino-songs

#define NOTE_B0  31
#define NOTE_C1  33
#define NOTE_CS1 35
#define NOTE_D1  37
#define NOTE_DS1 39
#define NOTE_E1  41
#define NOTE_F1  44
#define NOTE_FS1 46
#define NOTE_G1  49
#define NOTE_GS1 52
#define NOTE_A1  55
#define NOTE_AS1 58
#define NOTE_B1  62
#define NOTE_C2  65
#define NOTE_CS2 69
#define NOTE_D2  73
#define NOTE_DS2 78
#define NOTE_E2  82
#define NOTE_F2  87
#define NOTE_FS2 93
#define NOTE_G2  98
#define NOTE_GS2 104
#define NOTE_A2  110
#define NOTE_AS2 117
#define NOTE_B2  123
#define NOTE_C3  131
#define NOTE_CS3 139
#define NOTE_D3  147
#define NOTE_DS3 156
#define NOTE_E3  165
#define NOTE_F3  175
#define NOTE_FS3 185
#define NOTE_G3  196
#define NOTE_GS3 208
#define NOTE_A3  220
#define NOTE_AS3 233
#define NOTE_B3  247
#define NOTE_C4  262
#define NOTE_CS4 277
#define NOTE_D4  294
#define NOTE_DS4 311
#define NOTE_E4  330
#define NOTE_F4  349
#define NOTE_FS4 370
#define NOTE_G4  392
#define NOTE_GS4 415
#define NOTE_A4  440
#define NOTE_AS4 466
#define NOTE_B4  494
#define NOTE_C5  523
#define NOTE_CS5 554
#define NOTE_D5  587
#define NOTE_DS5 622
#define NOTE_E5  659
#define NOTE_F5  698
#define NOTE_FS5 740
#define NOTE_G5  784
#define NOTE_GS5 831
#define NOTE_A5  880
#define NOTE_AS5 932
#define NOTE_B5  988
#define NOTE_C6  1047
#define NOTE_CS6 1109
#define NOTE_D6  1175
#define NOTE_DS6 1245
#define NOTE_E6  1319
#define NOTE_F6  1397
#define NOTE_FS6 1480
#define NOTE_G6  1568
#define NOTE_GS6 1661
#define NOTE_A6  1760
#define NOTE_AS6 1865
#define NOTE_B6  1976
#define NOTE_C7  2093
#define NOTE_CS7 2217
#define NOTE_D7  2349
#define NOTE_DS7 2489
#define NOTE_E7  2637
#define NOTE_F7  2794
#define NOTE_FS7 2960
#define NOTE_G7  3136
#define NOTE_GS7 3322
#define NOTE_A7  3520
#define NOTE_AS7 3729
#define NOTE_B7  3951
#define NOTE_C8  4186
#define NOTE_CS8 4435
#define NOTE_D8  4699
#define NOTE_DS8 4978
#define REST      0

// notes of the moledy followed by the duration. A 4 means a quarter note, 8 an eighteenth , 16 sixteenth, so on 
// negative numbers are used to represent dotted notes, so -4 means a dotted quarter note, that is, a quarter plus an eighteenth!!

int superMario[] = {
  NOTE_E5,8, NOTE_E5,8, REST,8, NOTE_E5,8, REST,8, NOTE_C5,8, NOTE_E5,8, //1
  NOTE_G5,4, REST,4, NOTE_G4,8, //REST,4, 
  //NOTE_C5,-4, NOTE_G4,8, REST,4, NOTE_E4,-4, // 3
  //NOTE_A4,4, NOTE_B4,4, NOTE_AS4,8, NOTE_A4,4,
  //NOTE_G4,-8, NOTE_E5,-8, NOTE_G5,-8, NOTE_A5,4, NOTE_F5,8, NOTE_G5,8,
  //REST,8, NOTE_E5,4,NOTE_C5,8, NOTE_D5,8, NOTE_B4,-4,
};

int darthVader[] = {
  NOTE_AS4,8, NOTE_AS4,8, NOTE_AS4,8,//1
  NOTE_F5,2, NOTE_C6,2,
  NOTE_AS5,8, NOTE_A5,8, NOTE_G5,8, NOTE_F6,2, NOTE_C6,4,  
  //NOTE_AS5,8, NOTE_A5,8, NOTE_G5,8, NOTE_F6,2, NOTE_C6,4,  
  //NOTE_AS5,8, NOTE_A5,8, NOTE_AS5,8, NOTE_G5,2,
};

int pinkPanther[] = {
  /*REST,2, REST,4, REST,8, */ NOTE_DS4,8, 
  NOTE_E4,-4, REST,8, NOTE_FS4,8, NOTE_G4,-4, REST,8, NOTE_DS4,8,
  NOTE_E4,-8, NOTE_FS4,8,  NOTE_G4,-8, NOTE_C5,8, NOTE_B4,-8, //NOTE_E4,8, NOTE_G4,-8, NOTE_B4,8,   
  //NOTE_AS4,2, NOTE_A4,-16, NOTE_G4,-16, NOTE_E4,-16, NOTE_D4,-16, 
  //NOTE_E4,2, REST,4, REST,8, 
};

// sizeof = two bytes (16 bits), two values per note (pitch and duration), so for each note there are four bytes
//int notes = sizeof(melody) / sizeof(melody[0]) / 2; 
//int wholenote = (60000 * 4) / tempo; // calculate the duration of a whole note in ms
int noteDuration = 0;
int tempo, notes, wholenote, divider;

void playTune(byte tune) { 
  if (tune == 1) {
    tempo = 190;
    notes = sizeof(superMario) / sizeof(superMario[0]) / 2;
  }
  else 
    if (tune == 2) {
      tempo = 170;
      notes = sizeof(darthVader) / sizeof(darthVader[0]) / 2;
    }
    else {
      tempo = 170;
      notes = sizeof(pinkPanther) / sizeof(pinkPanther[0]) / 2;
    }
  wholenote = (60000 * 4) / tempo; 
  for (int thisNote = 0; thisNote < notes * 2; thisNote = thisNote + 2) { //array is twice the number of notes (notes + durations)
    //divider = melody[thisNote + 1]; // calculates the duration of each note
    if (tune == 1) divider = superMario[thisNote + 1];
    else 
      if (tune == 2) divider = darthVader[thisNote + 1];
      else divider = pinkPanther[thisNote + 1];
    if (divider > 0) noteDuration = (wholenote) / divider; // regular note, just proceed
    else
      if (divider < 0) { // dotted notes are represented with negative durations
        noteDuration = (wholenote) / abs(divider);
        noteDuration *= 1.5; // increases the duration in half for dotted notes
      }
    if (tune == 1) ledcWriteTone(buzzChan,superMario[thisNote]);
    else
      if (tune == 2) ledcWriteTone(buzzChan,darthVader[thisNote]);
      else ledcWriteTone(buzzChan,pinkPanther[thisNote]);
    //ledcWriteTone(buzzChan,melody[thisNote]); // play the note for 90% of the duration, leaving 10% as a pause
    delay(noteDuration * 0.9); // Wait for the specief duration before playing the next note.
    ledcWriteTone(buzzChan,0); // stop the waveform generation before the next note.
    delay(noteDuration * 0.1);
  }
}

Credits

Pedro Martin

Pedro Martin

8 projects • 16 followers

Comments