Go Down

Topic: DIY 6 button MIDI foot controller that sends MIDI Program Change + LCD (Read 1 time) previous topic - next topic

j2sip

..so to avoid confusion largely on my part, I stared this new topic (related to this thread https://forum.arduino.cc/index.php?topic=632108.0) with an appropriate title for easy searching by beginners like me..

I have started this project and was able to make the buttons turn on LEDs as you push them (buttons), much to my delight as a complete arduino Noob. But it's way too far from my aim. My ultimate goal would be 4 buttons that send Program Change 0 to 3 with LED and 2 bank up and down buttons that choose from 10 banks in a guitar amplifier sim program and a 2 x 16 LCD that will show the name of the banks as you scroll up and down.

I am using an Arduino Micro and would send over USB. So far, this the simpliest starting code that worked with the buttons and LEDS. I already included libraries and lines (instance) from the kindhearted Pieter:

Code: [Select]
#include <frequencyToNote.h>
#include <MIDIUSB.h>
#include <MIDIUSB_Defs.h>
#include <pitchToFrequency.h>
#include <pitchToNote.h>

#include <MIDI.h>
#include <midi_UsbTransport.h>

int ledPin1 = A0;  //
int ledPin2 = A1; //
int buttonPin1 = 8;    //
int buttonPin2 = 9;    //
int buttonState1 = 0;  //
int buttonState2 = 0;


static const unsigned sUsbTransportBufferSize = 16;
typedef midi::UsbTransport<sUsbTransportBufferSize> UsbTransport;

UsbTransport sUsbTransport;

MIDI_CREATE_INSTANCE(UsbTransport, sUsbTransport, MIDI);

void setup() {   

  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(buttonPin1, INPUT);
  pinMode(buttonPin2, INPUT);
}
 
void loop() {

  buttonState1 = digitalRead(buttonPin1);
  if(buttonState1 == HIGH) {
    digitalWrite(ledPin1, HIGH);
    ledOffAll();
   digitalWrite(ledPin1, 255);
 
   }
   buttonState2 = digitalRead(buttonPin2);
   
  if(buttonState2 == HIGH)
  {
    digitalWrite(ledPin2, HIGH);
    ledOffAll();
    digitalWrite(ledPin2, 255);}
}

   void ledOffAll() {
    digitalWrite(ledPin1, 0);       
    digitalWrite(ledPin2, 0);
  }
   

 


As a total noob, this would be my preferred approach - the lines of the codes are presented in a beginner friendly chunks. My 1st question is, how will I implement the MIDI part in that code. or is it even possible?






Grumpy_Mike

Quote
I am using an Arduino Micro and would send over USB. So far, this the simpliest starting code that worked with the buttons and LEDS.
In the words of the old joke, if I was going there I wouldn't start from here.

I would in fact start from here.

Code: [Select]

// Simple MIDI output of program message through USB
// By Grumpy_Mike August 2019

#include "MIDIUSB.h"

// to add more then simply add more pins tothe list
byte ledPin[] = {A0,A1};  // list of led pins
byte buttonPin[] = {8,9};    // list of buttons pins
byte buttonState[] = {0,0};  // start off with off
byte lastButtonState[] = {0,0};  // start off with off
byte programChanges[] = {1,2};  // list of program change messages to send

byte channel = 0; // for musicians this is channel 1

void setup() {
  for(int i =0;i< sizeof(buttonPin); i++){ // initialise input pins 
    pinMode(buttonPin[i], INPUT_PULLUP); // wire all buttons between input and ground
    pinMode(ledPin[i], OUTPUT);
  }
}
 
void loop() {
  for(int i =0;i< sizeof(buttonPin); i++){  // look at all pins
  buttonState[i] = digitalRead(buttonPin[i]);
  if(buttonState[i] == LOW && lastButtonState[i] == HIGH) { // remember how you wired your buttons LOW = pressed
    ledOffAll();
    digitalWrite(ledPin[i],HIGH);
    programChange(channel, programChanges[i]); // send the program change message
    MidiUSB.flush();
   }
   lastButtonState[i] = buttonState[i];
  }
}

void ledOffAll() {
    for(int i = 0 ; i< sizeof(ledPin); i++){
     digitalWrite(ledPin[i], LOW);
    }
}

void programChange(byte channel, byte patch) {
  midiEventPacket_t event = {0x0C, 0xC0 | channel, patch};
  MidiUSB.sendMIDI(event);
}  


Quote
As a total noob, this would be my preferred approach - the lines of the codes are presented in a beginner friendly chunks.
I am sure you would actually want to learn something rather than have code that you repeat over and over ad nauseam.

When you find yourself repeating code over and over you need to learn about arrays. In this code there are arrays that hold the lists of the pins to use and what program change messages to send. To expand this to more pins / LEDs / Program messages simply add the extra numbers to the list. Make sure there is always an equal number of numbers in each list so you have a match between push button, LED to write and program change message to send.

There is also a state change detection so that you don't continuously keep sending program change messages while a button is pressed.

Note this code is written as if the buttons are wired up correctly, that is with no external resistors and the button wired between input and ground.

j2sip

Thank you so much! It works! The arrays really made the whole sketch a whole lot smaller. Now if I wanted to add an LCD that page scrolls thru the 10 banks (the last 2 buttons being bank up and down switches), where will I insert the code? I have a suspicion it's going to be another array..


"Make sure there is always an equal number of numbers in each list so you have a match between push button, LED to write and program change message to send."


What if the last two buttons will not be needing LEDs at all? They be bank up and down switches.


Grumpy_Mike

Quote
Now if I wanted to add an LCD that page scrolls thru the 10 banks (the last 2 buttons being bank up and down switches), where will I insert the code?
All the action is here:-
Code: [Select]
if(buttonState[i] == LOW && lastButtonState[i] == HIGH) { // remember how you wired your buttons LOW = pressed
    ledOffAll();
    digitalWrite(ledPin[i],HIGH);
    programChange(channel, programChanges[i]); // send the program change message
    MidiUSB.flush();
   }


So if you want to send anything to an LCD put it here at the end of this section before the closing brace }

Quote
I have a suspicion it's going to be another array
Yes this time a character array. Avoid the use of String and use the C type of string ( the case of the S makes all the difference )
See:- https://forum.arduino.cc/index.php?topic=516472.0
and
https://www.arduino.cc/reference/en/language/variables/data-types/string/
You need the bit on "Arrays of strings". It does no harm to consolidate your knowledge of arrays as well:- https://www.arduino.cc/reference/en/language/variables/data-types/array/

To make your code more readable then write a function that handles the LCD print statements and insert a call to that in the action area.

Quote
What if the last two buttons will not be needing LEDs at all? They be bank up and down switches.
Add two dummy numbers to the list and then catch the value with an if statement so you don't set the LED for that. This needs doing in the action section, the setup function and the ledOffAll function.

In that function just subtract two from the loop limit section of the for loop:-
Code: [Select]
for(int i = 0 ; i< sizeof(ledPin)-2; i++){

in the other two you need:-
Code: [Select]
ledOffAll();
   if( i <= numberOfRealLEDs) { // only light up real LEDs
       digitalWrite(ledPin[i],HIGH);
   }
    programChange(channel, programChanges[i]); // send the program change message


In the setup function this will be round the pin mode instruction rather than the digital write.

j2sip

Wow,  super detailed as ever! Though it maybe too much for my head to digest, I guess I WILL learn from it. Thanks a lot! More questions on the way :D

Grumpy_Mike

Quote
super detailed as ever
I think at this stage you need it, but as you get more experienced, you will need less and less detail. 

j2sip

As of now, I was able to graft a number UP and DOWN counter sketch to your code. It worked, but not after maybe an hour debugging "not declared" and other errors. So happy I did it! i'm beginning to get my head around some of the things in the sketch now. Reading on arrays.....

Thanks again!

j2sip

This is a partial of the "buttons and LCD" part of the main sketch i'm writing now. (More on copy-pasting, I should say)

Am I right to say that "buttonPushCounter" shouldn't be there and should be replaced by the "char *myString" array line? 

And how do I call the array in the loop?

Code: [Select]
#include <LiquidCrystal_I2C.h>

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

const int  Up_buttonPin   = 8;    // the pin that the pushbutton is attached to
const int  Down_buttonPin = 9;

// Array
char *myStrings[] = {"Acoustic Clean", "Fender Clean", "Marshall Crunch","Marshall Leads",
                     "Boogie Crunch", "Randall Sound", "Dimebag!", "Eric Tones", "80's Glam"};
                     
// Variables will change:
int buttonPushCounter = 0;   // counter for the number of button presses
int up_buttonState = 0;         // current state of the up button
int up_lastButtonState = 0;     // previous state of the up button

int down_buttonState = 0;         // current state of the down button
int down_lastButtonState = 0;     // previous state of the down button
bool bPress = false;

                   
void setup()
{
  Serial.begin(9600);
  pinMode( Up_buttonPin , INPUT_PULLUP);
  pinMode( Down_buttonPin , INPUT_PULLUP);
  }


void loop() {
  checkUp();
  checkDown();

   if( bPress){
       bPress = false;
      lcd.setCursor(2,1);
      lcd.print("               ");
      lcd.setCursor(2,1);
      lcd.print(buttonPushCounter);
 }  }

 


This is the original sketch of the pushbutton up and down counter that only outputs numbers.

Code: [Select]
#include <LiquidCrystal_I2C.h>

//YWROBOT
//Compatible with the Arduino IDE 1.0
//Library version:1.1
#include <Wire.h>


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


// this constant won't change:
const int  Up_buttonPin   = 8;    // the pin that the pushbutton is attached to
const int  Down_buttonPin = 9;

// Variables will change:
int buttonPushCounter = 0;   // counter for the number of button presses
int up_buttonState = 0;         // current state of the up button
int up_lastButtonState = 0;     // previous state of the up button

int down_buttonState = 0;         // current state of the up button
int down_lastButtonState = 0;     // previous state of the up button
bool bPress = false;

void setup()
{
  Serial.begin(9600);
  pinMode( Up_buttonPin , INPUT_PULLUP);
  pinMode( Down_buttonPin , INPUT_PULLUP);
 
  lcd.init();                      // initialize the lcd
 
  // Print a message to the LCD.
  lcd.backlight();
  lcd.setCursor(0,0);
  lcd.print("Please Select:");
  lcd.setCursor(2,1);
  lcd.print(buttonPushCounter);
 
}


void loop()
{
   checkUp();
   checkDown();

   if( bPress){
       bPress = false;
      lcd.setCursor(2,1);
      lcd.print("               ");
      lcd.setCursor(2,1);
      lcd.print(buttonPushCounter);
   }
 }

void checkUp()
{
  up_buttonState = digitalRead(Up_buttonPin);

  // compare the buttonState to its previous state
  if (up_buttonState != up_lastButtonState) {
    // if the state has changed, increment the counter
    if (up_buttonState == LOW) {
        bPress = true;
      // if the current state is HIGH then the button went from off to on:
      buttonPushCounter++;
      Serial.println("on");
      Serial.print("number of button pushes: ");
      Serial.println(buttonPushCounter);
    } else {
      // if the current state is LOW then the button went from on to off:
      Serial.println("off");
    }
    // Delay a little bit to avoid bouncing
    delay(50);
  }
  // save the current state as the last state, for next time through the loop
  up_lastButtonState = up_buttonState;
}
void checkDown()
{
  down_buttonState = digitalRead(Down_buttonPin);

  // compare the buttonState to its previous state
  if (down_buttonState != down_lastButtonState) {
    // if the state has changed, increment the counter
    if (down_buttonState == LOW) {
        bPress = true;
      // if the current state is HIGH then the button went from off to on:
      buttonPushCounter--;
     
      Serial.println("on");
      Serial.print("number of button pushes: ");
      Serial.println(buttonPushCounter);
    } else {
      // if the current state is LOW then the button went from on to off:
      Serial.println("off");
    }
    // Delay a little bit to avoid bouncing
    delay(50);
  }
  // save the current state as the last state, for next time through the loop
  down_lastButtonState = down_buttonState;
}

Grumpy_Mike

Quote
And how do I call the array in the loop?
You can't call an array, you can only call functions. If you want to select an element of an array you give it an index in square brackets just like you have done before with those LED numbers.

Quote
Am I right to say that "buttonPushCounter" shouldn't be there and should be replaced by the "char *myString" array line?
Yes but don't use the * at the start, this is the symbol for a pointer. You don't need it when you use the array.

Try just printing the appropriate string to the serial monitor, to see if that bit is right. Then when it is change to the LCD.

Use the example at the bottom of this page
https://www.arduino.cc/reference/en/language/variables/data-types/string/

Don't print a whole series of spaces first just pad out each string in the array with trailing spaces so they are all the same length.

j2sip

Do I need to use another void variable to declare myString? 

Grumpy_Mike

Quote
Do I need to use another void variable to declare myString? 
No, even given that there is no such thing as a void variable.

void means "the following function returns no value"

variable deceleration means "this is a variable I want to use later in the code" and optionally "when I use it, then the value it will have is this ..., until I issue an instruction to specifically change it." 

In addition if a variable deceleration is inside a function, that variable can only be used inside that function, its scope is local ( to that function ).

if a variable deceleration is outside any function, that variable can be used in any function, its scope is global ( can be used over the whole code ).

j2sip

#11
Aug 31, 2019, 10:48 am Last Edit: Aug 31, 2019, 11:12 am by j2sip Reason: additional
ohh.. i get it. ugghh i have a feeling it is very very simple and within my grasp.

I may have found something similar:

Code: [Select]
const uint8_t incrementPin = 10;
const uint8_t decrementPin = 11;

const uint8_t channel = 1; // MIDI channel 1

const uint8_t minProgram = 0;
const uint8_t maxProgram = 127;

ProgramSelector ps = {incrementPin, decrementPin, minProgram, maxProgram};

PushButton presetButtons[] = {
  {2}, {3}, {4}, {5}, {6}, {7},  // ...
};
const size_t numberOfPresets = sizeof(presetButtons) / sizeof(presetButtons[0]);

const uint8_t presets[numberOfPresets] = {
  43,  49,  57,  58,  60,  61,  // ...
};
const uint8_t ledPins[numberOfPresets] {
  A0,  A1,  A2,  A3,  A4,  A5,  // ...
};
const char* presetNames[] = {
  "Delay 500m",
  "EQ Blues",
  "Reverb 8v Down",
  "Reverb Noise G.",
  "Reverb Tremolo N",
  "Reverb + 8v 5^",
  // ...
};


Can I use "const char" also? The same function as char myStrings?
 

Grumpy_Mike

Quote
I may have found something similar:
No, stop looking for code to do what you want to do.

You are having a hard enough time just making simple changes to simple code without having to burden yourself with trying to understand what a whole lot of other code does.

Just take the code you were working on with with what I told you to do in reply #8 and post it and we can work on where you went wrong. Remember I said:-

"Try just printing the appropriate string to the serial monitor, to see if that bit is right. Then when it is change to the LCD."

So I would expect you to post your code with an output to the serial monitor which in not a million miles from the example at the end of the page https://www.arduino.cc/reference/en/language/variables/data-types/string/

Quote
Can I use "const char" also
Why? In your case it makes absolutely no difference. The const word just means that any attempt to change this variable will flag up an error, but you are not changing it anyway.

What sort of idiot writes this sort of thing:-
Code: [Select]
PushButton presetButtons[] = {
  {2}, {3}, {4}, {5}, {6}, {7},  // ...
};

PieterP

Why? In your case it makes absolutely no difference. The const word just means that any attempt to change this variable will flag up an error, but you are not changing it anyway.
String literals should always be const. The C++ standard doesn't actually allow assigning them to non-const pointers. It only works on Arduino because someone made the terrible decision that all Arduino sketches should be compiled using -fpermissive ...
What sort of idiot writes this sort of thing:-
Code: [Select]
PushButton presetButtons[] = {
  {2}, {3}, {4}, {5}, {6}, {7},  // ...
};

This idiot.

They are objects, not integers, so I use uniform initialization. The reason is consistency, since objects may have more than one (optional) arguments to their constructors.

Pieter

Grumpy_Mike

Quote
They are objects, not integers, so I use uniform initialization
Being a bit picky aren't you. It just looks like bad code, you could have put a comment as to why you did this. It makes no odds , doing it the conventional way would work just as well.
In my book this is one of those "software" things that software types think is so important but ultimately is not.

Go Up