Midi foot switch with 10 oLED - RAM concerns - seeking advice

Hello,

I am working on a guitar midi controller that has 10 foot switches. Currently it works great. it takes about 1.7kb of the 2.5kb available on the Arduino Micro I am using.
I would like to improve the pedal by adding 10 oLED 0.96 above each footswitches to label their function based on the pedal configuration. I plan on using two multiplexers and work with the i2c protocole and u8g library.

I’ve read that each screen would require1kb.

Does that imply that I won’t have enough ram to do what I want ?
How do other do it ?

Any recommendations on how to proceed ?

Maybe another board ? If so which one (need a 5v board able to act as an external keyboard and if possible with the same footprint as the micro).

Thanks for your help.

Patrick.
I

Post your code. There may well be ways to reduce the RAM usage

If you are multiplexing the i2c lines, your code could have 1 instance of an oLED screen as far as the u8g library is concerned but then you adjust the multiplexers to address any given display before you actually draw it which send the data to the display.

Microprocessors are cheap.

Make custom OLED f/x labels consisting in an Arduino and an OLED.

From the master use serial communications to deliver to each label device the stuff it is to display.

RS-485 is good for this.

I2C would be way harder and mightn't work so well at a distance, whether or not the satellite devices had a microprocessor.

a7

If I understand what you are suggesting is that I should use my current Arduino to send a serial message to a auxiliary microprocessor that would then handle the 10 oLED screens. Is that correct or do you mean I would need 10 satellites microprocessor (one per screen)? What is the RS485? Can it be programmed with the Arduino language ? Also my Arduino Micro is already using the TX pin to transmit the MIDI signal. Could your suggestion still be implemented in that case ?

Here is the code without the OLED part.
I takes 1.7 kb out of the 2.5kb available on the Arduino Micro


#include <MIDI.h>
#include <TimedBlink.h>
#include <Keyboard.h>
#include <Bounce2.h>
#include <Bounce2.h>
#include <EEPROM.h>
#include "Wire.h"
#include <U8glib.h>





#define DEBUG_MODE true
#define DEBUG_MSG \
  if (DEBUG_MODE) Serial



const  byte NUM_PAGES = 4;
const  byte NUM_BUTTONS = 10;
const  byte NUMBER_LED_RING = 6;
const  int  TIME_LONGPRESS = 500;
const  byte  RELAY_PIN = 8;

const  byte NUM_PIN_CONTROLLED_BY_REGISTERS = 8;
const  uint8_t BUTTON_ARDUINO_PINS[NUM_BUTTONS] = {A4, A3, 4, 5, 6, A2, A1, 9, 8, 7};
//EXP ON A5 - blue cable
const  uint8_t LED_PINS[NUMBER_LED_RING * 2]    = {6,5,7,8,4,8,3,8,2,1,0,8};
//using 8 for LED not hooked, the structure follows button1 color 1,button 1 color 2 etc...
//


/* Line 6 HX Stomp constants
 * -------------------------
 */

const  byte STOMP_MODE = 0;
const  byte SCROLL_MODE = 1;
const  byte PRESET_MODE = 2;
const  byte SNAP_MODE = 3;

const  byte SNAPSHOT_CC = 69;
const  byte SNAPSHOT_1 = 0;
const  byte SNAPSHOT_2 = 1;
const  byte SNAPSHOT_3 = 2;

const  byte FS1_MC = 49;
const  byte FS2_MC = 50;
const  byte FS3_MC = 51;
const  byte FS4_MC = 52;
const  byte FS5_MC = 53;

const byte HX_MIDI_CHANNEL = 1;
const byte RC5_MIDI_CHANNEL = 2;


const  int DEFAULT_PROGRAM = 21;
const  int BOOKMARK_PROGRAM_1 = 1;
const  int BOOKMARK_PROGRAM_2 = 5;
const  int BOOKMARK_PROGRAM_3 = 9;
const  int BOOKMARK_PROGRAM_4 = 21;


const  byte  CC_RC5_FCT1  = 80; //Set in the RC5 to PLAY/STOP TRACK*
const  byte  CC_RC5_FCT2  = 81; //Set in the RC5 to RECORD/OVERDUMP - Not yet set
const  byte  CC_RC5_FCT3  = 82; //Set in the RC5 to PLAY/STOP RHYTHM*
const  byte  CC_RC5_FCT4  = 83; //Set in the RC5 to RHYTHM VARIATION
const  byte  CC_RC5_FCT5  = 84; //Set in the RC5 to RHYTHM PATTERN
const  byte  CC_RC5_FCT6  = 85; //Set in the RC5 to CLEAR TRACK
const  byte  CC_RC5_FCT7  = 86; //Set in the RC5 to
const  byte  CC_RC5_FCT8  = 87; //Set in the RC5 to


/* Shift register constants
 * ------------------------
 *
 * A TPIC6B595 shift register is used to control the ring LED
 * Those LED are 9V control with a common ground
 * I am using a TPIC6B595 and PNP diodes to deal with the common ground and 9V.
 * 9V is provided directly from ON/OFF switch
 */

const  uint8_t SER_DATA_Pin = 10;   //DATA  pin pin 14 on the TPIC6B595  pin 10 on the Arduino with blue cable
const  uint8_t RCLK_LATCH_Pin = 11;  //LATCH pin 12 on the TPIC6B595  pin 11 on the Arduino with white cable
const  uint8_t SRCLK_SHIFT_Pin = 12; //SHIFT pin 11 on the TPIC6B595  pin 12 on the Arduino with yellow cable

const  uint8_t UNASSIGNED_LED_PIN = 99;



/* Structures Definitions
 * ----------------------
 */

struct MIDIStatus {

  byte CC;
  int Value;
  byte Channel;
  bool FlagOnOff;
};


struct myButton {
  byte buttonNumber {1};              //Physical position on the board
  bool buttonReleased {false};         // Was the button Pressed during the loop
  uint8_t ShiftRegisterLEDPin[2] {UNASSIGNED_LED_PIN, UNASSIGNED_LED_PIN};
  bool colorStatus[2] {LOW, LOW};     //2 color per button
  long buttonPressTime;
  long buttonReleaseTime;
  bool BtnLongPress {false};


  bool isLongPress() {
    if (buttonReleaseTime - buttonPressTime > TIME_LONGPRESS) {
        return true;
    }
    else{
  return false;
    }
  }

};




struct myPage {

  byte pageNumber {100};
  byte HXmode {100};

  struct myButton ListObjButton[NUM_BUTTONS];


};


/* GLOBAL VARIABLES
 * ---------------
 */

byte currentPage = 1;


bool LEDHaveChanged = true;
unsigned long loopTime;

myPage ListObjPage[NUM_PAGES];
Bounce buttons[NUM_BUTTONS];
MIDI_CREATE_INSTANCE(HardwareSerial, Serial1, midiOut);

TimedBlink monitor(LED_BUILTIN);

byte RythmVariationIndex = 0;

//BPM assessment variables.
byte LastBtnPressed = 0;
int nunberConsecutivePress = 0;
float BPM = 60;






/* USEFUL FUNCTIONS
 * ---------------- */



struct myPage & getPage(byte pageNb = currentPage) {
  //This function is use so I can work with page number without dealing with the -1 for the index starting at 0
  return ListObjPage[pageNb-1];
}

struct myButton  & getBtn(byte BtNb, byte pageNb = currentPage) {
  //This function is use so I can work with page number without dealing with the -1 for the index starting at 0
  return getPage(pageNb).ListObjButton[BtNb-1];
}


void Play_Stop(byte CC){

}

void Record(){
  /*The RC5 pedal does not take MIDI command to start recording
   * Instead, I am using the TS jack and trigger it from the Octopus via a relay
   */

  digitalWrite(RELAY_PIN,HIGH);
  delay(100);
  digitalWrite(RELAY_PIN,HIGH);




}



void RhythmVariation() {
  int val = 0;

  switch (RythmVariationIndex){
    case 0:
      val = 127;
      RythmVariationIndex++;
      break;
    case 1:
      val = 0;
      //RythmVariationIndex++;
      RythmVariationIndex=0;

      break;
    case 2:
      val = 60;
      RythmVariationIndex++;
      break;
    case 3:
      val = 100;
      RythmVariationIndex=0;
      break;

  };

  midiOut.sendControlChange(CC_RC5_FCT4,val, RC5_MIDI_CHANNEL);

}
void RhythmPlayStop() {


      midiOut.sendControlChange(CC_RC5_FCT3, 127, RC5_MIDI_CHANNEL);
      midiOut.sendControlChange(CC_RC5_FCT3, 0, RC5_MIDI_CHANNEL);




}

float BPMToMS(){
  //I want X beats per sec but the light will be off 1/2 of that time.
  //So 1000 ms is divided by 2X beat one beat the light is on the other it is off.
  float ms = 60.0*1000.0/BPM/2.0;
  return ms;
}

void setBPM(){
  monitor.blink(BPMToMS(),BPMToMS())
  ;
}

/* SCREENS FUNCTIONS
 * --------------
 */







/* PRINTING FUNCTIONS
 * ------------------
 */

void printBtn(byte page, byte Btn){
  DEBUG_MSG.print("\tButton: ");
       DEBUG_MSG.println(Btn,1);
       DEBUG_MSG.print("\t\tbuttonNumber = ");
       DEBUG_MSG.println(getBtn(Btn,page).buttonNumber,1);
       DEBUG_MSG.print("\t\tbuttonReleased = ");
       DEBUG_MSG.println(getBtn(Btn,page).buttonReleased);
       DEBUG_MSG.print("\t\tLong press status = ");
       DEBUG_MSG.println(getBtn(Btn,page).isLongPress());
       DEBUG_MSG.print("\t\tArduinoPin = ");
       DEBUG_MSG.println(BUTTON_ARDUINO_PINS[Btn-1],1);

       for (byte indexColor=1; indexColor < 3; indexColor++){
    DEBUG_MSG.print("\t\tColor ");
    DEBUG_MSG.print(indexColor,1);
    DEBUG_MSG.print(" = ");
    DEBUG_MSG.println(getBtn(Btn,page).colorStatus[indexColor-1]);
    DEBUG_MSG.print("\t\tColor ");
    DEBUG_MSG.print(indexColor,1);
    DEBUG_MSG.print(" Pin = ");
    DEBUG_MSG.println(getBtn(Btn,page).ShiftRegisterLEDPin[indexColor-1],1);

       }
}
void printBtns(byte page){

  for (byte indexBtn = 1; indexBtn < NUM_BUTTONS + 1; indexBtn ++){
     printBtn(page,indexBtn);

  }

}
void printPage(byte page,bool showBtn = true){
  if (DEBUG_MODE) {
    DEBUG_MSG.print("Page :");
    DEBUG_MSG.println(page,1);
    DEBUG_MSG.print("\tpageNumber = ");
    DEBUG_MSG.println(getPage(page).pageNumber,1);
    DEBUG_MSG.print("\tHXmode = ");
    DEBUG_MSG.println(getPage(page).HXmode,1);
    if (showBtn){ printBtns(page);}
  }
}

void printPages(bool showBtn) {
  if (DEBUG_MODE) {
    //This function print the status of a page - used for debugging.
    for (int indexPg = 1; indexPg < NUM_PAGES + 1; indexPg ++){
  printPage(indexPg,showBtn);
    }
  }
}

/* Initialization Functions
 * -------------------------
 */
void initializeBtn(byte page,bool attachBtn) {

  int HXMode = getPage(page).HXmode;
    for (byte indexBtn = 1; indexBtn < NUM_BUTTONS + 1; indexBtn ++){

  ListObjPage[page-1].ListObjButton[indexBtn-1].buttonNumber = indexBtn;

  if (page ==1 and attachBtn){

      //The bounce instance is the same for all the pages. So I only instantiate it once
      //INPUT_PULLUP will use the internal 47k Ω resistor
      buttons[indexBtn-1].attach( BUTTON_ARDUINO_PINS[indexBtn-1] , INPUT_PULLUP  );       //setup the bounce instance for the current button
      buttons[indexBtn-1].interval(15);
  }


  //The first 6 buttons have a LED with 2 color,
  //I am assigning the pin on the shift register for the 2 LED
  if((indexBtn-1)*2 < NUMBER_LED_RING*2 ){

      ListObjPage[page-1].ListObjButton[indexBtn-1].ShiftRegisterLEDPin[0] =  LED_PINS[(indexBtn-1)*2];    //color 1 pin
      ListObjPage[page-1].ListObjButton[indexBtn-1].ShiftRegisterLEDPin[1] =  LED_PINS[(indexBtn-1)*2+1]; //color 2 pin
  }

  switch (HXMode) {
    case STOMP_MODE:
      if (indexBtn > NUMBER_LED_RING){break;}
      if (indexBtn != 6){ListObjPage[page-1].ListObjButton[indexBtn-1].colorStatus[0] = HIGH;}
      break;
    case SNAP_MODE:
      if (indexBtn > NUMBER_LED_RING){break;}
      if (indexBtn == 6){ListObjPage[page-1].ListObjButton[indexBtn-1].colorStatus[0] = HIGH;}
      if (indexBtn == 1){ListObjPage[page-1].ListObjButton[indexBtn-1].colorStatus[0] = HIGH;}

      break;
    case SCROLL_MODE:
        if (indexBtn > NUMBER_LED_RING){break;}
        if (indexBtn == 1){ListObjPage[page-1].ListObjButton[indexBtn-1].colorStatus[1] = HIGH;}
        if (indexBtn == 2){ListObjPage[page-1].ListObjButton[indexBtn-1].colorStatus[0] = HIGH;}
        if (indexBtn == 6){ListObjPage[page-1].ListObjButton[indexBtn-1].colorStatus[0] = LOW;}
        break;
  }
  LEDHaveChanged = true;
    }

}


void initializePages() {

    for (byte indexPg = 1; indexPg < NUM_PAGES + 1; indexPg ++){
        ListObjPage[indexPg-1].pageNumber =indexPg;
        switch (indexPg) {
          case 1:
            ListObjPage[indexPg-1].HXmode = SNAP_MODE;
            break;
          case 2:
            ListObjPage[indexPg-1].HXmode = STOMP_MODE;
            break;
          case 3:
            ListObjPage[indexPg-1].HXmode = SCROLL_MODE;
            break;
          case 4:
            ListObjPage[indexPg-1].HXmode = SNAP_MODE;
            break;
        }
        initializeBtn(indexPg,true);
    }
}



/* LED & Shift Register Functions
 * ------------------------------
 */

void setLED(byte button, byte color, bool value) {
      /*Do not check if they have changed because I might have changed
       * page and the reference to the btn status might not be how the LED is let.
       */
      getBtn(button).colorStatus[color-1] = value;
      LEDHaveChanged = true;

}
void toggleLED(byte button, byte color) {
  getBtn(button).colorStatus[color-1] = not getBtn(button).colorStatus[color-1];
  LEDHaveChanged = true;
}

void resetALLToLow(byte page=currentPage) {
  for (int indexLED = 0; indexLED < NUMBER_LED_RING; indexLED++) {
       ListObjPage[page-1].ListObjButton[indexLED].colorStatus[0] = LOW;
       ListObjPage[page-1].ListObjButton[indexLED].colorStatus[1] = LOW;
  }
}


void writeRegisters() {

  //Output Enable (OE_bar) is wired to GND so it is always LOW
  //so the output is always accessible via the latch pin

  //Need to make sure this only change if something has changed.

  if (LEDHaveChanged) {
  digitalWrite(RCLK_LATCH_Pin, LOW);   //Not outputting the data
  uint8_t value = LOW;
  for (byte indexPin=0; indexPin < NUM_PIN_CONTROLLED_BY_REGISTERS; indexPin++){
        //The pin are from 0 to
        //I can use indexPin to refer to them directly without the need for a lookup table

        digitalWrite(SRCLK_SHIFT_Pin, LOW);

        //Find the button for that Pin
        for (byte indexBtn=1;indexBtn < NUM_BUTTONS + 1 ;indexBtn++){
            if(getBtn(indexBtn).ShiftRegisterLEDPin[0] == indexPin ){
          value = getBtn(indexBtn).colorStatus[0];
            }
            else if (getBtn(indexBtn).ShiftRegisterLEDPin[1] == indexPin){
          value = getBtn(indexBtn).colorStatus[1];
            }
         }


        digitalWrite(SER_DATA_Pin,value);
        digitalWrite(SRCLK_SHIFT_Pin, HIGH); //Shift the bit
  }
  digitalWrite(RCLK_LATCH_Pin, HIGH);  //Output to the data
  LEDHaveChanged = false;
  }
}

void testLEDs(){
 // This function cycles the LED at the initialization of the Octopus
  //Saving the current status and resetting all to LOW
  bool color0InitialeState[NUMBER_LED_RING] {LOW} ;
  bool color1InitialeState[NUMBER_LED_RING] {LOW} ;

  for (int indexLED = 0; indexLED < NUMBER_LED_RING; indexLED++) {
          color0InitialeState[indexLED] = ListObjPage[currentPage-1].ListObjButton[indexLED].colorStatus[0];
          color1InitialeState[indexLED] = ListObjPage[currentPage-1].ListObjButton[indexLED].colorStatus[1];
          ListObjPage[currentPage-1].ListObjButton[indexLED].colorStatus[0] = LOW;
    ListObjPage[currentPage-1].ListObjButton[indexLED].colorStatus[1] = LOW;

  }

  LEDHaveChanged = true;
  writeRegisters();
  for (byte indexCol = 1; indexCol < 3;indexCol++) {
      for (byte indexBtn = 1; indexBtn < NUM_BUTTONS + 1; indexBtn++){
       //Skipping the btn without LED (they have a PIN set to UNASSIGNED_LED_PIN.
       if(ListObjPage[currentPage-1].ListObjButton[indexBtn-1].ShiftRegisterLEDPin[0] == UNASSIGNED_LED_PIN){
     continue;}
       else {
     setLED(indexBtn, indexCol, HIGH);
     writeRegisters();
     delay(200);
       }
       }
      //Resetting all to LOW
      for (int indexLED = 0; indexLED < NUMBER_LED_RING; indexLED++) {
        ListObjPage[currentPage-1].ListObjButton[indexLED].colorStatus[0] = LOW;
        ListObjPage[currentPage-1].ListObjButton[indexLED].colorStatus[1] = LOW;
      }
      LEDHaveChanged = true;
       writeRegisters();
  }

  //Re-assigning the original status
  for (int indexLED = 0; indexLED < NUMBER_LED_RING; indexLED++) {
      ListObjPage[currentPage-1].ListObjButton[indexLED].colorStatus[0] = color0InitialeState[indexLED];
      ListObjPage[currentPage-1].ListObjButton[indexLED].colorStatus[1] = color1InitialeState[indexLED];

  }
  LEDHaveChanged = true;
  writeRegisters();

}


void LEDSnapshotSelection(byte btn) {

   switch (btn){
     case 1:
  setLED(1, 1, HIGH);
  setLED(1, 2, LOW);
  setLED(2, 1, LOW);
  setLED(2, 2, LOW);
  setLED(3, 1, LOW);
  setLED(3, 2, LOW);
  break;
     case 2:

  setLED(1, 1, LOW);
  setLED(1, 2, LOW);
  setLED(2, 1, HIGH);
  setLED(2, 2, LOW);
  setLED(3, 1, LOW);
  setLED(3, 2, LOW);
  break;
     case 3:
  setLED(1, 1, LOW);
  setLED(1, 2, LOW);
  setLED(2, 1, LOW);
  setLED(2, 2, LOW);
  setLED(3, 1, HIGH);
  setLED(3, 2, LOW);
  break;
     case 4:
        toggleLED(4,1);
        setLED(5,2,LOW); //In case i press play while I am recording. I need to turn off the red LED.
        break;
     case 6:
       toggleLED(5,2);
       toggleLED(4,1);
       break;

   }
}
void LEDBehaviorForMode(byte btn){
  //When press on a btn the LED respond differently depending on the HX Mode

  if (btn==6) {return;};
  switch (getPage(currentPage).HXmode){

    case SNAP_MODE :

      LEDSnapshotSelection(btn);
      writeRegisters();
      break;
    case STOMP_MODE :
      toggleLED(btn,1);
      writeRegisters();
      break;
    case SCROLL_MODE :
      //Blinking the button when pressed
      byte color = 1;
      if (btn == 1) {color=2;}
      for(int i=1;i < 5;i++){
    toggleLED(btn,color);
    writeRegisters();
    delay(200);
      }
      break;

  }


}



/* ACTIONS FOR THE BUTTONS
 * ------------------------
 */
void changePage(bool isLongPress){

  LastBtnPressed = 0; // BMP count is not estimated for btn pressed that occurred two different pages
  if(isLongPress){
      if (currentPage == 1) {
   currentPage = 3;
      }
      else {
   currentPage = 1;
      }

  }
  else {
      currentPage++;
      DEBUG_MSG.print("CurrentPage = ");
      DEBUG_MSG.println(currentPage);

  }

  if (currentPage > NUM_PAGES){
     currentPage = 1;
     DEBUG_MSG.print("CurrentPage = ");
     DEBUG_MSG.println(currentPage);
  }

  for (int indexBtn = 1; indexBtn < NUM_BUTTONS + 1; indexBtn ++) {
       setLED(indexBtn,1,getBtn(indexBtn).colorStatus[0]);
       setLED(indexBtn,2,getBtn(indexBtn).colorStatus[1]);
  }

  midiOut.sendControlChange(71, getPage().HXmode, 1);
  writeRegisters();

}

void changeHXProgram(byte CP) {
  //When changing a program on HX, i need to reset the LEDs
  midiOut.sendProgramChange(CP, HX_MIDI_CHANNEL);
  for (byte indexPg = 1; indexPg < NUM_PAGES+1;indexPg++) {
      resetALLToLow(indexPg);
      initializeBtn(indexPg,false);
  }

  writeRegisters();

}

void pageOneCmd(){
      //This is where all the commands for the page 1 are set.
      for (byte indexBtn = 1; indexBtn < NUM_BUTTONS +1; indexBtn++){
        if (not getBtn(indexBtn).buttonReleased) {
      continue;}
        switch (indexBtn) {
    case 1:
                  midiOut.sendControlChange(SNAPSHOT_CC, SNAPSHOT_1, HX_MIDI_CHANNEL);
      break;
    case 2:
            midiOut.sendControlChange(SNAPSHOT_CC, SNAPSHOT_2, HX_MIDI_CHANNEL);
      break;
    case 3:
                  midiOut.sendControlChange(SNAPSHOT_CC, SNAPSHOT_3, HX_MIDI_CHANNEL);
      break;
    case 4:
            Play_Stop(CC_RC5_FCT1);
            setLED(4, 1, HIGH);
            setLED(5, 1, LOW);
            setLED(5, 1, LOW);
            writeRegisters();
      break;
    case 5:
            setLED(5, 1, HIGH);
            setLED(5, 1, HIGH);
      Record();
            writeRegisters();

      break;
    case 6:
      changePage(getBtn(6).isLongPress());
      break;
    case 7:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_1);}else{
           RhythmPlayStop();
      }
      break;
    case 8:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_2);}else{
          RhythmVariation();
      }
      break;
    case 9:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_3);}else{
                midiOut.sendControlChange(CC_RC5_FCT5, 0, RC5_MIDI_CHANNEL);
                midiOut.sendControlChange(CC_RC5_FCT5, 127, RC5_MIDI_CHANNEL);
      }
      break;
    case 10:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_4);}else{
                midiOut.sendControlChange(CC_RC5_FCT6, 0, RC5_MIDI_CHANNEL);
                midiOut.sendControlChange(CC_RC5_FCT6, 127, RC5_MIDI_CHANNEL);
      }
      break;
        }
      LEDBehaviorForMode(indexBtn);
      getBtn(indexBtn).buttonReleased = false;
      }
 }

void pageTwoCmd(){
      if (currentPage !=2) {
     DEBUG_MSG.println("ERROR - ACCESSING PageTwoCmd when the current page is not 2 ");
     return;
  }

       for (byte indexBtn = 1; indexBtn < NUM_BUTTONS +1; indexBtn++){
        if (not getBtn(indexBtn).buttonReleased) {

      continue;}

        switch (indexBtn) {
    case 1:
      midiOut.sendControlChange(FS1_MC, 0, HX_MIDI_CHANNEL);
      break;
    case 2:
       midiOut.sendControlChange(FS2_MC, 0, HX_MIDI_CHANNEL);
       break;
    case 3:
       midiOut.sendControlChange(FS3_MC, 0, HX_MIDI_CHANNEL);
       break;
    case 4:
       midiOut.sendControlChange(FS4_MC, 0, HX_MIDI_CHANNEL);
       break;
    case 5:
       midiOut.sendControlChange(FS5_MC, 0, HX_MIDI_CHANNEL);
       break;
    case 6:
       changePage(getBtn(6).isLongPress());
       break;
    case 7:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_1);}else{
               Keyboard.write('k');
      }
       break;
    case 8:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_2);}else{
          Keyboard.write('0');
       }
       break;
    case 9:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_3);}else{
          Keyboard.write('j');
       }
       break;
    case 10:
       if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_4);}else{

       }
       break;


        }
        LEDBehaviorForMode(indexBtn);
        getBtn(indexBtn).buttonReleased = false;
       }


}

void pageThreeCmd(){
  if (currentPage !=3) {
     DEBUG_MSG.print("ERROR - ACCESSING PageThreeCmd when the current page is not 3 ");
     return;
  }
  for (byte indexBtn = 1; indexBtn < NUM_BUTTONS +1; indexBtn++){
      if (not getBtn(indexBtn).buttonReleased) {continue;}
      switch (indexBtn) {
        case 1:
           midiOut.sendControlChange(FS1_MC, 0, HX_MIDI_CHANNEL);
     break;
        case 2:
           midiOut.sendControlChange(FS2_MC, 0, HX_MIDI_CHANNEL);
     break;
        case 3:
     setBPM();
     //Send the tap to the  HX (I not sending the bpm byt instead a tap so the hX can do its own BPM calculation
     //BPM IS ONLY USE HERE TO DRIVE THE led / screen
     midiOut.sendControlChange(64, 127, HX_MIDI_CHANNEL);
     break;
        case 4:
     break;
        case 5:
     break;
        case 6:
     changePage(getBtn(6).isLongPress());
     break;
        case 7:
    if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_1);}else{
        //RC5 Play/Stop Rhythm (apparently 0 and 127 are needed ?)
        midiOut.sendControlChange(CC_RC5_FCT2, 0, RC5_MIDI_CHANNEL);
        midiOut.sendControlChange(CC_RC5_FCT2, 127, RC5_MIDI_CHANNEL);
    }
     break;
        case 8:
    if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_2);}else{
        RhythmVariation();
     }
     break;
        case 9:
    if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_3);}else{

     }
     break;
        case 10:
     if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_4);}else{

     }
     break;


            }
      LEDBehaviorForMode(indexBtn);
      getBtn(indexBtn).buttonReleased = false;
  }


}


void pageFourCmd(){
  //This is where all the commands for the page 4 are set.

       for (byte indexBtn = 1; indexBtn < NUM_BUTTONS +1; indexBtn++){
          if (not getBtn(indexBtn).buttonReleased) {
        continue;}
          switch (indexBtn) {
      case 1:
                   midiOut.sendControlChange(SNAPSHOT_CC, SNAPSHOT_1, HX_MIDI_CHANNEL);
        break;
      case 2:
              midiOut.sendControlChange(SNAPSHOT_CC, SNAPSHOT_2, HX_MIDI_CHANNEL);
        break;
      case 3:
                   midiOut.sendControlChange(SNAPSHOT_CC, SNAPSHOT_3, HX_MIDI_CHANNEL);
        break;
      case 4:
        break;
      case 5:
        break;
      case 6:
        changePage(getBtn(6).isLongPress());
        break;
      case 7:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_1);}else{
                //RC5 Play/Stop Rhythm (apparently 0 and 127 are needed ?)
                midiOut.sendControlChange(CC_RC5_FCT2, 0, RC5_MIDI_CHANNEL);
                midiOut.sendControlChange(CC_RC5_FCT2, 127, RC5_MIDI_CHANNEL);
      }
        break;
      case 8:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_2);}else{
          //YouTube restart
           Keyboard.write('0');     }
        break;
      case 9:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_3);}else{
          //YouTube play/stop
           Keyboard.write('k');
      }
        break;
      case 10:
      if (getBtn(indexBtn).isLongPress()){ changeHXProgram(BOOKMARK_PROGRAM_4);}else{
          //YouTube rewind 5sec
           Keyboard.write('j');
      }
        break;
          }
       LEDBehaviorForMode(indexBtn);
       getBtn(indexBtn).buttonReleased = false;
       }

}


/* BUTTONS RELATED FUNCTIONS
 * -------------------------
 */


void readButtons(unsigned long time) {

   //Read the button and see which ones are pressed.
   for (byte indexBtn = 1; indexBtn < NUM_BUTTONS + 1; indexBtn ++) {
       getBtn(indexBtn).buttonReleased= false;

       buttons[indexBtn-1].update();

       if (buttons[indexBtn-1].fell()) {

     /* BPM determination
      * I look if the same btn is pressed multiple time in short intervals
      * in order to count the desired BPM.
      * If there is too much time between pressed, I reset the count
      * I limit BPM  between 30 and 250 which correspond to
      * one beat every 2000 ms to one beat every 240 ms.
      * ms per beat = BPM/60000
      * 1 -> 2000 ms
      * 1/2000*60*1000
      * BPM is not sent to the HX until the executeBtn() function is instructed to do so.
      * Here it is simply counting what it could be without further actions.
     */
     if (indexBtn == LastBtnPressed and time <= getBtn(indexBtn).buttonPressTime  + 2000){
       //Quick multi-press detected. This could be a BPM tap button
             nunberConsecutivePress = nunberConsecutivePress + 1;
             long  TimeBetweenPressed = time - getBtn(indexBtn).buttonPressTime;
             long  instantBPM = 60.0*1000.0/TimeBetweenPressed;  //Non averaged BPM
       if (nunberConsecutivePress == 2) { BPM = instantBPM;}
       else {
       BPM = ((nunberConsecutivePress-1) * BPM + instantBPM)/(nunberConsecutivePress);
       }


       DEBUG_MSG.print("Instant BPM ");
       DEBUG_MSG.println(instantBPM);
       DEBUG_MSG.print("BPM ");
       DEBUG_MSG.println(BPM);
       DEBUG_MSG.print("TimeBetweenPressed ");
       DEBUG_MSG.println(TimeBetweenPressed);
       DEBUG_MSG.print("Nb Pressed ");
       DEBUG_MSG.println(nunberConsecutivePress);


     }
     else {
         LastBtnPressed = indexBtn;
         nunberConsecutivePress = 1;
         }

     getBtn(indexBtn).buttonPressTime = time;
   //  DEBUG_MSG.print("Btn ");
   //  DEBUG_MSG.print(indexBtn,1);
   //  DEBUG_MSG.println("was pressed ");
           }

       if (buttons[indexBtn-1].rose()) {
     getBtn(indexBtn).buttonReleaseTime = time;
     getBtn(indexBtn).buttonReleased= true;

    // DEBUG_MSG.print("Btn ");
          // DEBUG_MSG.print(indexBtn,1);
    // DEBUG_MSG.println("was released ");
    // printBtn(currentPage,indexBtn);

       }
   }

}

void executeBtn(){
  switch (currentPage) {
     case 1:
        pageOneCmd();
        break;
     case 2:
       pageTwoCmd();
       break;
     case 3:
       pageThreeCmd();
       break;
     case 4:
       pageFourCmd();
       break;

  }

}
/*  SETUP AND LOOP
 * ---------------
 */


void setup() {

  //Using the Serial for debug messages.
  if (DEBUG_MODE) {
      Serial.begin(9600);
      while (!SerialUSB){
    ;   //Wait for the serial to connect.
      }
  }

  //BPM is kept in memory even if the arduino is shut-off
  byte BPM_stored;
  EEPROM.get(0, BPM_stored);
  if (isnan(BPM_stored))
  {
   DEBUG_MSG.println("BPM is not initialized.");
   DEBUG_MSG.println("Initializing BPM to 60");
   EEPROM.put(0, BPM);
  }
  DEBUG_MSG.print("Debug MODE ");


  pinMode(LED_BUILTIN, OUTPUT);
  monitor.blink(BPMToMS(),BPMToMS());//LED on for 150 ms and off for 50 ms.

  pinMode(SER_DATA_Pin, OUTPUT);
  pinMode(RCLK_LATCH_Pin, OUTPUT);
  pinMode(SRCLK_SHIFT_Pin, OUTPUT);

  initializePages();
  testLEDs();
  Keyboard.begin();
  Serial1.begin(31250);

  midiOut.sendProgramChange(DEFAULT_PROGRAM, HX_MIDI_CHANNEL);
  midiOut.sendControlChange(71,SNAP_MODE, HX_MIDI_CHANNEL);
  midiOut.sendControlChange(SNAPSHOT_CC, SNAPSHOT_1, HX_MIDI_CHANNEL);



  DEBUG_MSG.println("Done with Initialization");
//  monitor.blink(500,250);

}




void loop() {
  monitor.blink(BPMToMS(),BPMToMS());

  readButtons(millis());

  executeBtn();




}

Oh I see. But I would still need 1kB for that one instance, correct?

How far apart are the displays physically located?

Depending on what you intend to display, you may not need the full 1k of buffer memory. Simple static text is a lot different than a dynamic graphic display.

Yeah, that's what I had mind, "smart OLEDs", one microprocessor per.

RS-485 is a serial communication thing meant for this kind of talking. You could use another UART or maybe software serial.

Probably not the best idea that whole plan…

Maybe, but you'd have to figure out how to initialise (begin) them all, which might be tricky.

If you solved that or it isn't a problem, a deeper hack would share the needed Arduino memory, above my pay grade.

a7

less than 5 inches from the Arduino.
Ideally I would like a mix of static test and logo (e.g. big arrow up/down for the footswitch that select the MIDI bank/program)

The screen messages will only be a matter of

  • The MIDI "pages" (i.e one int)
  • Whether or not the corresponding footswitch has been pressed.

I therefore wonder if I could use a second Arduino (a Nano) that I have collecting dust in my drawer.
The idea would be that the primary Arduino send to the second Arduino, a page number and a pressed status for each buttons at each loop. The connection would have to be done via the Digital PIN as I am already using the serial pin.
When reading those values, the second Arduino would then handle the screens. I would still have to figure out how to only insatiate one Screen for the multiplexer but at least no more worry with 1.7kb used for the first Arduino

Does that sound like a viable option ? Can Arduino communicate via a digitalPIN ??

I would strongly advise against using multiple microcontrollers. If you need more resources, get a more powerful microcontroller, e.g. a Teensy. For an application like this one, using separate microcontrollers just complicates things.

Then definitely don't use RS-485 for communication, even if you do decide to use multiple microcontrollers.
Software Serial is a hack that is unreliable and wastes resources.

Why?

No, you only need 1 KiB in total, for all instances. This just requires you to update one display at a time, which is usually the case anyway.

And as mentioned in other replies, you don't need such a large buffer if you're just going to be writing text.

I once edited the Adafruit_SSD1306 library to allow sharing a single buffer among multiple display instances.
I didn't keep it up to date, but my changes are still relevant: Comparing adafruit:master...tttapa:master · adafruit/Adafruit_SSD1306 · GitHub

Sure, that would be possible in theory, but why bother with all of that if you can just get a more suitable/powerful board for a couple of dollars?

1 Like

A couple of suggestions:

Use the F() macro for printing text literals, that will store the text in program memory instead of ram.

Use the U8g2 library for the OLED displays, and use a page buffer instead of a full buffer, that will significantly decrease the size of the display buffer.

2 Likes

I recommend these 2 libraries. If they work for you, they will use less memory, especially for the display.

Hi Pieter,

Do you have any recommendations for a board. I would prefer a 5V because the pedal is also using a couple of 5V relays to drive another pedal and I would rather not have to add step ups if possible.
I also need a board capable of simulating a USB keyboard (HID) which I use to control YouTube Play/Stop.
I don’t know anything about Teensy. Do you have a model in mind ? Can those be coded with the Arduino C++ ish language ?

Google is a wonderful thing: Teensy USB Development Board
Teensies are a much higher performance class of processors that are part of the arduino environment. On that site, they explain how to install support, etc.

By" I don't know anything " I really meant that I have no practical experience. I visited the Teensy website for the first time a week or so ago. I am considering the Teensy 2++.

Is using an Arduino code an easy task with the Teensy 2++. (i.e. are most the common libraries compatible or is it going to be a real pain ?). Is it easily usable as a HID?

Easy is a term that depends on your abilities. The teensies are widely supported in arduino libraries which is part of the power of arduino - the same source code can move to different processors which change. The changes are tucked away in the libraries.

You need some kind of driver for the relays either way (e.g. a simple NPN transistor), so it shouldn't really matter whether the IO is 3.3V or 5V.

Teensies can do this, see Teensyduino: Using USB Keyboard with Teensy on the Arduino IDE

The Teensy LC might fit the bill: Teensy LC (Low Cost)

Yes, they are programmed using C++ in the Arduino IDE.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.