16x2 LCD "how to display potentiometer MIDI values 0-127"

I am using an Arduino Mega and got my device up and running with a MIDI shield, 16 potentiometers and a 16x2 LCD display.
I managed to print text in the two rows, so the display is working well.
Now I want that each time I move one or two of the 16 potis to show the value changes on the display.
I did some research, but I am helpless in how to start the coding for this.

I gonna add all 3 code files. main code, controller.cpp and controller.h

Would be nice, if someone could get me started with maybe 2 potis.
I would like to show the two potis in the second LCD row (one to the left and one to the right)

It would look like this:
CONSTELLATION
p01=127 p02=127

Right now the second row shows "Cosmic Octave" and it would be great, if this would show until a poti is moved and after some time of no poti activity the Cosmic Octave will show again in that row.

Here is the code I have:

#include <MIDI.h>
#include "Controller.h"
#include <Wire.h> 
#include <LiquidCrystal_I2C.h>

// Set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x3F, 16, 2);

/*************************************************************
  MIDI CONTROLLER
  by Notes and Volts
  www.notesandvolts.com
 *************************************************************/

MIDI_CREATE_DEFAULT_INSTANCE();

//************************************************************
//***SET THE NUMBER OF CONTROLS USED**************************
//************************************************************
//---How many buttons are connected directly to pins?---------
byte NUMBER_BUTTONS = 0;
//---How many potentiometers are connected directly to pins?--
byte NUMBER_POTS = 16;
//---How many buttons are connected to a multiplexer?---------
byte NUMBER_MUX_BUTTONS = 0;
//---How many potentiometers are connected to a multiplexer?--
byte NUMBER_MUX_POTS = 0;
//************************************************************

//***ANY MULTIPLEXERS? (74HC4067)************************************
//MUX address pins must be connected to Arduino UNO pins 2,3,4,5
//A0 = PIN2, A1 = PIN3, A2 = PIN4, A3 = PIN5
//*******************************************************************
//Mux NAME (OUTPUT PIN, , How Many Mux Pins?(8 or 16) , Is It Analog?);

//Mux M1(10, 16, false); //Digital multiplexer on Arduino pin 10
//Mux M2(A0, 16, true); //Analog multiplexer on Arduino analog pin A0

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


//***DEFINE DIRECTLY CONNECTED POTENTIOMETERS************************
//Pot (Pin Number, Command, CC Control, Channel Number)
//**Command parameter is for future use**

Pot PO1(A0, 0, 102, 1);
Pot PO2(A1, 0, 103, 1);
Pot PO3(A2, 0, 104, 1);
Pot PO4(A3, 0, 105, 1);
Pot PO5(A4, 0, 106, 1);
Pot PO6(A5, 0, 107, 1);
Pot PO7(A6, 0, 108, 1);
Pot PO8(A7, 0, 109, 1);
Pot PO9(A8, 0, 110, 1);
Pot PO10(A9, 0, 111, 1);
Pot PO11(A10, 0, 112, 1);
Pot PO12(A11, 0, 113, 1);
Pot PO13(A12, 0, 114, 1);
Pot PO14(A13, 0, 115, 1);
Pot PO15(A14, 0, 116, 1);
Pot PO16(A15, 0, 117, 1);
//*******************************************************************
//Add pots used to array below like this->  Pot *POTS[] {&PO1, &PO2, &PO3, &PO4, &PO5, &PO6};
Pot *POTS[] {&PO1, &PO2, &PO3, &PO4, &PO5, &PO6, &PO7, &PO8, &PO9, &PO10, &PO11, &PO12, &PO13, &PO14, &PO15, &PO16};
//*******************************************************************


//***DEFINE DIRECTLY CONNECTED BUTTONS*******************************
//Button (Pin Number, Command, Note Number, Channel, Debounce Time)
//** Command parameter 0=NOTE  1=CC  2=Toggle CC **

//Button BU1(2, 0, 60, 1, 5 );
//Button BU2(3, 0, 61, 1, 5 );

//*******************************************************************
//Add buttons used to array below like this->  Button *BUTTONS[] {&BU1, &BU2, &BU3, &BU4, &BU5, &BU6, &BU7, &BU8};
Button *BUTTONS[] {};
//*******************************************************************


//***DEFINE BUTTONS CONNECTED TO MULTIPLEXER*************************
//Button::Button(Mux mux, byte muxpin, byte command, byte value, byte channel, byte debounce)
//** Command parameter 0=NOTE  1=CC  5=Toggle CC **

//Button MBU4(M1, 3, 4, 32, 1, 5);    //2"                        yes
//Button MBU3(M1, 2, 3, 32, 1, 5);    //4"                        yes

//*******************************************************************
////Add multiplexed buttons used to array below like this->  Button *MUXBUTTONS[] {&MBU1, &MBU2, &MBU3, &MBU4, &MBU5, &MBU6.....};
Button *MUXBUTTONS[] {};
//*******************************************************************


//***DEFINE POTENTIOMETERS CONNECTED TO MULTIPLEXER*******************
//Pot::Pot(Mux mux, byte muxpin, byte command, byte control, byte channel)
//**Command parameter is for future use**
//MULTIPLEXER 2 (1-15)
//LFO
//Pot MPO1(M2, 3, 0, 25, 1);       //Shape 1       (0-7)          yes
//Pot MPO2(M2, 2, 0, 24, 1);       //Speed 1                      yes

//*******************************************************************
//Add multiplexed pots used to array below like this->  Pot *MUXPOTS[] {&MPO1, &MPO2, &MPO3, &MPO4, &MPO5, &MPO6.....};
Pot *MUXPOTS[] {};
//*******************************************************************


void setup() {
  Serial.begin(9600); //9600 bits per second serial data output
  MIDI.begin(MIDI_CHANNEL_OMNI);
  
  // initialize the LCD
  lcd.begin();

  // Turn on the blacklight and print a message.
  lcd.backlight();
  lcd.setCursor(0, 0);  // set the cursor to first column, second row
  lcd.print("CONSTELLATION");
  lcd.setCursor(0, 1);  // set the cursor to first column, second row
  lcd.print("Cosmic Octave");
}

void loop() {
  MIDI.read();
  if (NUMBER_BUTTONS != 0) updateButtons();
  if (NUMBER_POTS != 0) updatePots();
  if (NUMBER_MUX_BUTTONS != 0) updateMuxButtons();
  if (NUMBER_MUX_POTS != 0) updateMuxPots();
}


//*****************************************************************
void updateButtons() {

  // Cycle through Button array
  for (int i = 0; i < NUMBER_BUTTONS; i = i + 1) {
    byte message = BUTTONS[i]->getValue();

    //  Button is pressed
    if (message == 0) {
      switch (BUTTONS[i]->Bcommand) {
        case 0: //Note
          MIDI.sendNoteOn(BUTTONS[i]->Bvalue, 127, BUTTONS[i]->Bchannel);
          break;
        case 1: //CC
          MIDI.sendControlChange(BUTTONS[i]->Bvalue, 127, BUTTONS[i]->Bchannel);
          break;
        case 2: //Toggle
          if (BUTTONS[i]->Btoggle == 0) {
            MIDI.sendControlChange(BUTTONS[i]->Bvalue, 127, BUTTONS[i]->Bchannel);
            BUTTONS[i]->Btoggle = 1;
          }
          else if (BUTTONS[i]->Btoggle == 1) {
            MIDI.sendControlChange(BUTTONS[i]->Bvalue, 0, BUTTONS[i]->Bchannel);
            BUTTONS[i]->Btoggle = 0;
          }
          break;
      }
    }

    //  Button is not pressed
    if (message == 1) {
      switch (BUTTONS[i]->Bcommand) {
        case 0:
          MIDI.sendNoteOff(BUTTONS[i]->Bvalue, 0, BUTTONS[i]->Bchannel);
          break;
        case 1:
          MIDI.sendControlChange(BUTTONS[i]->Bvalue, 0, BUTTONS[i]->Bchannel);
          break;
      }
    }
  }
}
//*******************************************************************
void updateMuxButtons() {

  // Cycle through Mux Button array
  for (int i = 0; i < NUMBER_MUX_BUTTONS; i = i + 1) {

    MUXBUTTONS[i]->muxUpdate();
    byte message = MUXBUTTONS[i]->getValue();

    //  Button is pressed
    if (message == 0) {
      switch (MUXBUTTONS[i]->Bcommand) {
        case 0: //Note
          MIDI.sendNoteOn(MUXBUTTONS[i]->Bvalue, 127, MUXBUTTONS[i]->Bchannel);
          break;
        case 6: //CC 16"
          MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 36, MUXBUTTONS[i]->Bchannel);
          break;
        case 2: //CC 8"
          MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 48, MUXBUTTONS[i]->Bchannel);
          break;
        case 3: //CC 4"
          MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 60, MUXBUTTONS[i]->Bchannel);
          break;
        case 4: //CC 2"
          MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 72, MUXBUTTONS[i]->Bchannel);
          break;          
        case 5: //Toggle
          if (MUXBUTTONS[i]->Btoggle == 0) {
            MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 127, MUXBUTTONS[i]->Bchannel);
            MUXBUTTONS[i]->Btoggle = 1;
          }
          else if (MUXBUTTONS[i]->Btoggle == 1) {
            MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 0, MUXBUTTONS[i]->Bchannel);
            MUXBUTTONS[i]->Btoggle = 0;
          }
          break;
      }
    }
    //  Button is not pressed
    if (message == 1) {
      switch (MUXBUTTONS[i]->Bcommand) {
        case 0:
          MIDI.sendNoteOff(MUXBUTTONS[i]->Bvalue, 0, MUXBUTTONS[i]->Bchannel);
          break;
        case 1:
          MIDI.sendControlChange(MUXBUTTONS[i]->Bvalue, 0, MUXBUTTONS[i]->Bchannel);
          break;
      }
    }
  }
}
//***********************************************************************
void updatePots() {
  for (int i = 0; i < NUMBER_POTS; i = i + 1) {
    byte potmessage = POTS[i]->getValue();
    if (potmessage != 255) MIDI.sendControlChange(POTS[i]->Pcontrol, potmessage, POTS[i]->Pchannel);
  }
}
//***********************************************************************
void updateMuxPots() {
  for (int i = 0; i < NUMBER_MUX_POTS; i = i + 1) {
    MUXPOTS[i]->muxUpdate();
    byte potmessage = MUXPOTS[i]->getValue();
    
    if (potmessage != 255) MIDI.sendControlChange(MUXPOTS[i]->Pcontrol, potmessage, MUXPOTS[i]->Pchannel);
    
  }
}

Controller.cpp


#include "Controller.h"

//****************************************************************************************
Mux::Mux(byte outpin_, byte numPins_, bool analog_)
{
  outpin = outpin_;
  //enablepin = enablepin_;
  numPins = numPins_;
  analog = analog_;
  if (analog == false) pinMode(outpin, INPUT_PULLUP);
  //pinMode(enablepin, OUTPUT);
  pinMode(2, OUTPUT);
  pinMode(3, OUTPUT);
  pinMode(4, OUTPUT);
  if (numPins > 8) pinMode(5, OUTPUT);
}
//****************************************************************************************
//Button (Pin Number, Command, Note Number, Channel, Debounce Time)
Button::Button(byte pin, byte command, byte value, byte channel, byte debounce)
{
  _pin = pin;
  pinMode(_pin, INPUT_PULLUP);
  _value = value;
  _command = command;
  _debounce = debounce;
  _time = 0;
  _busy = false;
  _status = 0b00000010;
  _last = 1;
  Bcommand = command;
  Bvalue = value;
  Bchannel = channel;
  Btoggle = 0;
}

Button::Button(Mux mux, byte muxpin, byte command, byte value, byte channel, byte debounce)
{
  _pin = mux.outpin;
  _numMuxPins = mux.numPins;
  _muxpin = muxpin;
  _value = value;
  _command = command;
  _debounce = debounce;
  _time = 0;
  _busy = false;
  _status = 0b00000010;
  _last = 1;
  Bcommand = command;
  Bvalue = value;
  Bchannel = channel;
  Btoggle = 0;
}

void Button::muxUpdate()
{
  byte temp = _muxpin;
  temp = temp << 2;
  if (_numMuxPins > 8) PORTD = PORTD & B11000011;
  else PORTD = PORTD & B11100011;
  PORTD = PORTD | temp;
}

byte Button::getValue()
{
  // If BUSY bit not set - read button
  if (bitRead(_status, 0) == false) { // If busy false
    if (digitalRead(_pin) == _last) return 2; // If same as last state - exit
  }

  // If NEW Bit set - Key just pressed, record time
  if (bitRead(_status, 1) == true) { // If new is true
    bitSet(_status, 0); // Set busy TRUE
    bitClear(_status, 1); // Set New FALSE
    _time = millis();
    return 255;
  }

  // Check if debounce time has passed - If no, exit
  if (millis() - _time < _debounce) return 255;

  // Debounce time has passed. Read pin to see if still set the same
  // If it has changed back - assume false alarm
  if (digitalRead(_pin) == _last) {
    bitClear(_status, 0); // Set busy false
    bitSet(_status, 1); // Set new true
    return 255;
  }

  // If this point is reached, event is valid. return event type
  else {
    bitClear(_status, 0); // Set busy false
    bitSet(_status, 1); // Set new true
    _last = ((~_last) & 0b00000001); // invert _last
    return _last;
  }
}

void Button::newValue(byte command, byte value, byte channel)
{
  Bvalue = value;
  Bcommand = command;
  Bchannel = channel;
}

//********************************************************************
Pot::Pot(byte pin, byte command, byte control, byte channel)
{
  _pin = pin;
  _control = control;
  _value = analogRead(_pin);
  _value = _value >> 3;
  _oldValue = _value << 3;
  _value = _value << 3;
  Pcommand = command;
  Pcontrol = control;
  Pchannel = channel;
}

void Pot::muxUpdate()
{
  byte temp = _muxpin;
  temp = temp << 2;
  if (_numMuxPins > 8) PORTD = PORTD & B11000011;
  else PORTD = PORTD & B11100011;
  //PORTD = PORTD & B11000011;
  PORTD = PORTD | temp;
}

Pot::Pot(Mux mux, byte muxpin, byte command, byte control, byte channel)
{
  _pin = mux.outpin;
  _numMuxPins = mux.numPins;
  _muxpin = muxpin;
  _control = control;
  muxUpdate();
  _value = analogRead(_pin);
  _value = _value >> 3;
  _oldValue = _value << 3;
  _value = _value << 3;
  Pcommand = command;
  Pcontrol = control;
  Pchannel = channel;
}

byte Pot::getValue()
{
  _value = analogRead(_pin);
  int tmp = (_oldValue - _value);
  if (tmp >= 8 || tmp <= -8) {
    _oldValue = _value >> 3;
    _oldValue = _oldValue << 3;
    return _value >> 3;
  }
  return 255;
}

void Pot::newValue(byte command, byte value, byte channel) {
  Pcommand = command;
  Pcontrol = value;
  Pchannel = channel;
}

Controller.h

#ifndef Controller_h
#define Controller_h

#include <Arduino.h>

//***********************************************************************
class Mux
{
  public:
    Mux(byte outpin_, byte numPins_, bool analog_);
    byte outpin;
    byte numPins;
    bool analog;
};
//************************************************************************
//Button (Pin Number, Command, Note Number, Channel, Debounce Time)
class Button
{
  public:
    Button(byte pin, byte command, byte value, byte channel, byte debounce);
    Button(Mux mux, byte muxpin, byte command, byte value, byte channel, byte debounce);
    byte getValue();
    void muxUpdate();
    void newValue(byte command, byte value, byte channel);
    byte Bcommand;
    byte Bvalue;
    byte Bchannel;
    byte Btoggle;

  private:
    byte _previous;
    byte _current;
    unsigned long _time;
    int _debounce;
    byte _pin;
    byte _muxpin;
    byte _numMuxPins;
    byte _value;
    byte _command;
    bool _busy;
    byte _status;
    byte _last;
    byte _enablepin;
};
//*************************************************************************
class Pot
{
  public:
    Pot(byte pin, byte command, byte control, byte channel);
    Pot(Mux mux, byte muxpin ,byte command, byte control, byte channel);
    void muxUpdate();
    void newValue(byte command, byte value, byte channel);
    byte getValue();
    byte Pcommand;
    byte Pcontrol;
    byte Pchannel;

  private:
    byte _pin;
    byte _muxpin;
    byte _numMuxPins;
    byte _control;
    int _value;
    int _oldValue;
    bool _changed;
    byte _enablepin;
};
//*************************************************************************
#endif

Start by saving the values that you read. An array would be handy for this. Then next time you read the same pot you can compare the value now with the previous reading to determine whether it has changed and if so, display a message. For sanity you probably want to only display something when the value has changed by a significant amount. Something like

if (abs(oldValue - newValue) > margin)
{
  //do something
}

Thank you for the hint.
I gonna need a bit more help to do this.
The code I provided is from another source and I did only adjust it to my needs.
I can do that, but making up the code from zero I don't have enough knowledge.
I am a trained media designer and music producer, so the device I built is a MIDI controller for the studio. I am happy I got this far and it is working. Also have basics in electronics, so that part was easy for me.
The display programming seems not that easy to start.
If I would have the first potentiometer working, then I gonna figure out the rest.
Maybe you could help me a bit further, based on my code?

Start simple

Try this

int previousPotValue = analogRead(A7);

void setup()
{
  Serial.begin(115200);
}

void loop()
{
  int currentPotValue = analogRead(A7);
  if (currentPotValue != previousPotValue)
  {
    Serial.print("pot value changed to ");
    Serial.println(currentPotValue);
  }
  previousPotValue = currentPotValue;
}

This helped ) First value showing up now. Needs formatting. Still got the 1024 range.
I am researching how to convert this.
Later on I will get to different value ranges.
Thanks for the help.

If you want to convert the 0-1023 range returned by analogRead() to a different range then take a look at the map() function. For some target ranges there are easier ways to do it.
For instance

int value = analogRead(A7);
value = map(value, 0, 1023, 0, 255); //convert range to 0 to 255

is equivalent to

int value = analogRead(A7);
value = value / 4; //convert range to 0 to 255

which is equivalent to

int value = analogRead(A7);
value = value  >> 2;  //convert range to 0 to 255

Great! I was looking into the map function. Have found this at another threat.
Thanks. This will do.

I have one last question, if I may :wink:
When it shows the value on the display and I change the value it somehow mixes with the previous printed. Example: value is 1023 and i low the value to 0050 it will leave the 10.. or write lower number. Somehow the display should refresh at each value change, so the previous one is gone.
don't now if you get what I mean. It is mixing all numbers and it makes it impossible to read the actual value properly. Some sort of clear screen or refesh function for this?

You need to remove the previous value before printing the new one. There are several ways to do this

  • clear the screen - this causes flicker. Don't do it
  • print spaces in the background colour where the old value is - better
  • print the old value in the background colour - best

Whatever method you use, only write to the screen when a value changes

Got it :wink: First found the clear function and had the flickering.
Next things is to read all 16 potis when value changes and print to the screen the ones which changes. Since I will use only 2 hands only 2 potis at the time can change.
This gonna take me some time to figure out.
like: if A1 has a new value print it then if A2 has a new value print instead where A1 was.
I have already two spots for the values to be shown at the second row of the screen.
this works all fine now with the one A7 poti you gave me as an example.
I am expecting issues in case i change value A1 and A2... how to decide which write on value 1 position and which writes on value 2 position.
on the row it looks like this right now:
p01:000 p02:000
the number after "p" will show the poti number of course and then the value.
I feel silly to ask you every step, but for you it seems a kids game :wink:

I would read all 16 values into a new values array and put the difference between the current and previous values in a second array. Once you have read all 16 you can then determine the 2 highest values in the differences array and display the corresponding values from the values array

How are you with arrays ?

I am terrible with arrays. I usually end up get long if - then - else codes.
The values which change need to show either at p01 or p02 position.
Value can lower or higher. I think some sort of "when" function.
when value changes - then print :wink:
hahaha... the principe is clear, but how to write it down is the issue.
could you recommend a good way of learning?
should I buy a book or how can i learn the best way?
I usually search for youtube tutorials about whatever I need to do, get the code and then adjust it to my needs. This works mostly fine, until it gets more individual.

One longwinded way to get there is to take the code that handles one pot and copy it. Then change all the variables it uses and add a suffix, so for the currentPotValue, you'll have currentPotValue1, currentPotValue2 currentPotValue3 etc.

This quickly bloats your code and becomes annoying to wade through, especially if you have a bug that you've now replicated sixteen times.

Having done this, you will be in the right frame of mind to realize that currentPotValue can be an array and those irritating suffixes are array indexes.

A little after than, you'll notice that you can process your arrays with a for loop making your program a lot shorter.

Creep up on the problem and test each stage

Stage1

  • declare an array of 16 ints named newValues
  • read the 16 values into the array usig a for loop and print each value

Stage 2

  • declare a second array of 16 ints named previousValues
  • before you read each new value save the value in the newValues array to the previousValues array * after each reading print the difference between them

Stage 3

  • declare a third array named differences
  • calculate the difference for each pot as you read the values and save the difference in the differences array
  • once you have read all 16 values print the 16 values in the differences array

That will do for now !

Thanks for teaching.
I will find my way through this.
The concept is clear.

Good luck with your project. Post in this topic if you get stuck

1 Like