Need help using Arduino and MIDI

Hi there,

I just realized that I had previously posted this thread in the wrong topic (sorry Mods!!). Hoping to get more advice here.

Original post:
Hi, I'm using an Arduino Nano to control an ALPS motorized fader.

A motorized fader consists of the following components:

  • 10v DC Motor
  • B10k Potentiometer
  • Capacitive Touch Sensor

I have an L298N DC Motor Driver, and a pair of MIDI input/output jacks (with corresponding circuitry, i.e. a 6138N Optocoupler)

Right now, the motorized fader responds to data coming in from my DAW (Ableton Live 9 Standard) by moving up and down along with the first track's volume/automation as I change it in the DAW. It also sends MIDI data into my DAW to control the tracks volume. When I touch the fader and move it to zero, my first track in my DAW is selected.

I'd like to change the code so that when I touch the fader, the track is immediately selected, rather than only when I move the fader all the way down.

Here's the code:

#include <SoftwareSerial.h>
#include <CapacitiveSensor.h>
#include <MIDI.h>
#define rxPin 4
#define txPin 1

SoftwareSerial midiSerial (rxPin, txPin);

const byte motorDown = 5;
const byte motorUp = 6;
const byte motorPWM = 3;
const byte wiper = 7; //Position of fader relative to GND (Analog 0)
const byte touchSend = 2;//Send pin for Capacitance Sensing Circuit (Digital 7)
const byte touchReceive = 7; //Receive pin for Capacitance Sensing Circuit (Digital 8)
int incomingVelocity;
byte motorSpeed = 75; // Raise if the fader is too slow (0-255)
byte minimumCp = 75; // Raise if the fader is too sensitive (0-16383)
byte tolerance = 10; // Raise if the fader is too shaky (0-1023)
double faderMax = 0; //Value read by fader's maximum position (0-1023)
double faderMin = 0; //Value read by fader's minimum position (0-1023)
bool touched = false; //Is the fader currently being touched?

CapacitiveSensor cs_7_2 = CapacitiveSensor(touchReceive, touchSend);
MIDI_CREATE_DEFAULT_INSTANCE();

void setup() {
  cs_7_2.set_CS_AutocaL_Millis(0xFFFFFFFF);
  midiSerial.begin(31250);
  MIDI.begin(MIDI_CHANNEL_OMNI);
  while (!Serial);
  pinMode(motorDown, OUTPUT);
  pinMode(motorUp, OUTPUT);
  pinMode(motorPWM, OUTPUT);
  pinMode(rxPin, INPUT );
  pinMode(txPin, OUTPUT);
  analogWrite(motorPWM, motorSpeed);
  calibrateFader(); }

void calibrateFader() {
  digitalWrite(motorUp, HIGH);
  analogWrite(motorPWM, 100);
  delay(300);
  digitalWrite(motorUp, LOW);
  faderMax = analogRead(wiper) - tolerance;
  digitalWrite(motorDown, HIGH);
  analogWrite(motorPWM, 100);
  delay(300);
  digitalWrite(motorDown, LOW);
  faderMin = analogRead(wiper) + tolerance; }

void loop() {
  int faderPos = analogRead(wiper);
  int lastfaderValue;
  int faderMIDI = faderPos / 8;
  int faderHiResMIDI = ((faderPos *16) - 8191);
  while ( midiSerial.available() > 0 ) {
    int commandByte = midiSerial.read();
    int noteByte = midiSerial.read();
    byte velocityByte = midiSerial.read();
    midiSerial.write( commandByte);
    midiSerial.write( noteByte);
    midiSerial.write( velocityByte);
    incomingVelocity = velocityByte*8; }
    
 { int totalCp =  cs_7_2.capacitiveSensor(30); 
    if (totalCp <= minimumCp) { touched = false; updateFader(incomingVelocity); }// Not Touching Fader 
    if ((faderPos == lastfaderValue) && (totalCp > minimumCp)) { touched = true; // Touching Fader
      MIDI.sendNoteOn(104, 127, 1);
      digitalWrite(motorDown, LOW);
      digitalWrite(motorUp, LOW); }
    if ((faderPos != lastfaderValue) && (totalCp > minimumCp)) { touched = true; // Moving Fader
      MIDI.sendPitchBend(faderHiResMIDI, 1);
      digitalWrite(motorDown, LOW);
      digitalWrite(motorUp, LOW); }
    lastfaderValue = faderPos; } }

void updateFader(int position) {     //Function to move fader to a specific position between 0-1023 if it's not already there
  if (position < analogRead(wiper) - tolerance && position > faderMin && !touched) {
    digitalWrite(motorDown, HIGH);
    while (position < analogRead(wiper) - tolerance && !touched) {}; //Loops until motor is done moving
    digitalWrite(motorDown, LOW); }
  else if (position > analogRead(wiper) + tolerance && position < faderMax && !touched) {
    digitalWrite(motorUp, HIGH);
    while (position > analogRead(wiper) + tolerance && !touched) {}; //Loops until motor is done moving
    digitalWrite(motorUp, LOW); } }

Still left to do:

  • Try to get the tracks in my DAW to respond immediately after touching the sensor, rather than only when my motor is all the way down (not sure why it's doing this to begin with??)
  • Try to implement PID DC Motor Control (it's easier 'said' than 'done') - I've tried for MANY hours at this point, no luck (and I'm seriously confused about the concept in general)
  • Try to get the Arduino to ONLY read MIDI data from a certain command and channel (i.e. Note G#7, MIDI channel 1, which would be read by the Arduino as '104' and '1')
  • Try to get the motor to respond to quick automation movements without lag/skipping

Edit 1: Fixed some typos
Edit 2: Added 'Still left to do' list
Edit 3: fixed some more typos

Using a separate piece of code, I was able to track Ableton's incoming MIDI data, and I figured out why all of this isn't working with my DAW.

In Ableton, when I select a different track, a lot of different data gets sent through midi, not just the command, note and velocity bytes. When the Arduino reads this extra information, it doesn't know how to interpret it, so it starts acting crazy.

If I don't click on anything in Ableton after uploading my code, then I get a command value of 224 when I move the volume from the first track, and 225 when I move the volume from the second track. This goes up to 8 tracks, and beyond that nothing happens.

When I plug a MIDI keyboard into my Arduino, everything works perfectly and I can assign each fader to any control on my keyboard.

So, if anyone knows how I should be interpreting this data, I'm all ears. If not, I'll keep hacking at this. I really want to make it work, or at least learn why it can't.

I might be able to get the track selection working if I can just figure out why it won't send a signal until the fader is moved. They should be separate.

If anybody has any thoughts, please feel free to share.

Thanks

Been at this all week, no luck.

I tried removing the bit of code that sends a control change, and found that my code isn't differentiating between when I'm touching the fader, when I'm not touching the fader, and when I'm moving it.

I'll try rewriting it someway, but I'm still hoping someone with some more experience with Arduino code can help. If you're out there, please know that you're a unicorn.

It's also worth noting that I'm using two separate libraries for MIDI input and MIDI output.

I tried using one to do both, but found that neither worked.

Again, if someone who has more experience with that code can shed some light, I'd highly appreciate it.

Why are you using SoftwareSerial on a HardwareSerial pin?

Why?

So which is it, do you want to use those pins for a serial port or as GPIO pins? Using pinMode configures a pin as GPIO, and messes up the UART configuration. For SoftwareSerial, calling the begin method should be sufficient to configure the hardware.

You check if there is one byte ready, but then you go ahead and read three bytes.

You cannot read bytes in chunks of three, you need a correct MIDI parser, not all MIDI messages are three bytes long. You included the MIDI library, why are you not using it for MIDI input?

Not sure what you were expecting, there doesn't seem to be a single specific question in your post, just a vague description and a todo list.
You can't just dump a bunch of code and specifications on here and expect someone to figure out what's wrong with it.

1 Like

Hi PieterP,
Thanks for the reply.

  • I'm using pin 1 as the txPin, because no other pin will actually send MIDI data out of my Arduino - Tried many combinations. For softwareserial, using any other pin besides 1 resulted in absolutely no output being sent.

  • while! Serial is added as a formality, as suggested in the SoftwareSerial example. "wait for serial port to connect. Needed for native USB port only" - I believe it's only necessary on certain Arduino's.

  • For softwareserial, again calling begin without specifying where my inputs and outputs are was not sufficient. I tried many combinations, and only the one pin sent midi out correctly.

  • I wasn't able to get the MIDI library to function at all with MIDI input the way that I can with softwareSerial. I tried seeking assistance, but found none, so I moved on.

Happy to keep working on this though. Right now I've changed this code quite a bit from where I was when I posted this. This version of the code is pretty outdated (which is why I was hoping to find/hire someone asap for a quicker turnaround, as I'm very actively working on this project. It's changing every day).

I did remove the pinMode lines though, but found the functionality to be the same.

Here's the latest version. I added some debugging functionality to limit the data being read by the arduino. I'll look into making a "MIDI Parser" - I've never heard of that until now.

#include <SoftwareSerial.h> // MIDI Input
#include <MIDI.h> // MIDI Output
#include <CapacitiveSensor.h>

#define rxPin 2 // Input  (Grey)
#define txPin 1 // Output (Yellow)

SoftwareSerial midiSerial (rxPin, txPin);

const byte wiper        = 0; //Position of fader relative to GND (Analog 0)

const byte motorUp      = 4;
const byte motorDown    = 5;
const byte motorPWM     = 6;
const byte touchSend    = 7;//Send pin for Capacitance Sensing Circuit (Digital 7)
const byte touchReceive = 8; //Receive pin for Capacitance Sensing Circuit (Digital 8)

unsigned int incomingCommand;
unsigned int incomingNote;
unsigned int incomingVelocity;
byte motorSpeed = 150;  // Raise if the fader is too slow (0-255)
byte minimumCp  = 200; // Raise if the fader is too sensitive (0-16383)
byte tolerance  = 10;  // Raise if the fader is too shaky (0-1023)
double faderMax = 1023; //Value read by fader's maximum position (0-1023)
double faderMin = 0; //Value read by fader's minimum position (0-1023)
long lastDebounceTime = 0;  // the last time the output pin was toggled
long debounceDelay    = 15; // the debounce time; increase if the output flickers
int lastfaderValue    = 0;
int targetPosition;

byte ch1Vol = 176;
byte fullVel = 127;
byte ch_1 = 1;
byte Db_1 = 1;

CapacitiveSensor touch = CapacitiveSensor(touchReceive, touchSend);
MIDI_CREATE_DEFAULT_INSTANCE();

void setup() {
  touch.set_CS_AutocaL_Millis(0xFFFFFFFF);
  midiSerial.begin(31250);
  MIDI.begin();
  //while (!Serial);
  pinMode(motorDown, OUTPUT);
  pinMode(motorUp, OUTPUT);
  pinMode(motorPWM, OUTPUT);
//  pinMode(rxPin, INPUT );
//  pinMode(txPin, OUTPUT);
  calibrateFader(); }

void calibrateFader() {
  digitalWrite(motorUp, HIGH);
  analogWrite(motorPWM, motorSpeed);
  delay(300);
  digitalWrite(motorUp, LOW);
  faderMax = analogRead(wiper) - tolerance;
  digitalWrite(motorDown, HIGH);
  analogWrite(motorPWM, motorSpeed);
  delay(300);
  digitalWrite(motorDown, LOW);
  faderMin = analogRead(wiper) + tolerance; }

void loop() {
  int faderPos = analogRead(wiper);
  int faderHiResMIDI = ((faderPos *16) - 8191);
  while ( midiSerial.available() > 0) {
    incomingCommand = midiSerial.read();
    incomingNote = midiSerial.read();
    incomingVelocity = midiSerial.read()*8.05511811024; 
  if (incomingVelocity <=1023) {
    midiSerial.write(incomingCommand);
    midiSerial.write(incomingNote);
    midiSerial.write(incomingVelocity); } }
    
 { int totalCp =  touch.capacitiveSensor(30); 
 if ( (millis() - lastDebounceTime) > debounceDelay) {
    if (totalCp <= minimumCp) { 
      if (incomingCommand == ch1Vol) { 
        updateFader(incomingVelocity); } }    // Not Touching fader
    if ((faderPos == lastfaderValue) && (totalCp > minimumCp)) { MIDI.sendNoteOn(Db_1, fullVel, ch_1); // Touching Fader
      digitalWrite(motorDown, LOW);
      digitalWrite(motorUp, LOW); }
    if (((faderPos > lastfaderValue+1) or (faderPos < lastfaderValue-1)) && (totalCp > minimumCp)) {   // Moving Fader
      MIDI.sendPitchBend(faderHiResMIDI, ch_1);
      digitalWrite(motorDown, LOW);
      digitalWrite(motorUp, LOW); }
    lastfaderValue = faderPos;  
    lastDebounceTime = millis(); } } }

void updateFader(int position) {     //Function to move fader to a specific position between 0-1023 if it's not already there
  if (position < analogRead(wiper) - tolerance && position > faderMin) {
    digitalWrite(motorDown, HIGH);
    while (position < analogRead(wiper) - tolerance) {}; //Loops until motor is done moving
    digitalWrite(motorDown, LOW); }
  else if (position > analogRead(wiper) + tolerance && position < faderMax) {
    digitalWrite(motorUp, HIGH);
    while (position > analogRead(wiper) + tolerance) {}; //Loops until motor is done moving
    digitalWrite(motorUp, LOW); } }

And here's my To Do list (right now I'm focusing on getting rid of any lag/choppiness in the data being read by my DAW):

  • Fix the lag in the response coming in from a DAW
  • Find a MIDI library that can do Input and Output well
  • Find a DC Motor library that can do acceleration well using PID control

Basically you are using the wrong sort of Arduino. You should be using a Leonardo or Micro.

Forget software serial just use serial. I am assuming you are using something like hairless in your PC.

I would also suggest you forget any MIDI library and do it all with Serial read and write. That way you are in control and can concentrate on each function, send and receive separately.

Don’t try and do everything at once, build it up slowly. Ten when you run into trouble you know what the trouble is and can ask a more specific question.

Hey Grumpy_Mike,

Thanks for the reply! I thought about swapping microcontrollers to see if it made a difference - I'll get a Micro and see how it runs.

I'll make a version that scraps any MIDI libraries and see if I can get it to work better

I believe those conclusions are misguided, (part of) the issues you're seeing are caused by mixing the MIDI library and SoftwareSerial on the same pins, this cannot possibly work correctly.
The MIDI library uses the hardware UART, but you also have a software UART that operates on the same pins, this is bound to clash.
Either (A) remove all use of SoftwareSerial, and use just the MIDI library, or (B) tell the MIDI library to use the SoftwareSerial port you created.
I believe option A is the right one here.

You don't have to make a MIDI parser, the MIDI library already has one.

I don't agree that removing the MIDI library is the right thing to do here, it is known to work, you just have to use it correctly. Writing all low-level MIDI code yourself without a library is not going to be easier, and just leaves even more room for error.

Yes but the big advantage is that you are in control of what your code does. And it is not too difficult to do.

I've tried for quite a while to get the MIDI library working for what I'm doing, but I've yet to be able to. Using the MIDI library, how would I assign values to the 'incomingCommand', 'incomingNote' and 'incomingVelocity' variables?

I tried this, but being that I can't use Serial Monitor with this library, I'm in the dark about what information (if any) is being received.

#include <MIDI.h> // MIDI Output

MIDI_CREATE_DEFAULT_INSTANCE();

void setup() {
  MIDI.begin(); }

void loop() {
 while (MIDI.read()) switch (MIDI.getType()) {
      byte MIDIChannel = MIDI.getChannel();
    case midi::ProgramChange:
      byte programByte1 = MIDI.getData1();
      break;
    case midi::ControlChange:
      byte controlByte1 = MIDI.getData1();
      byte controlByte2 = MIDI.getData2();
      break; 
    default: break; } }

Trying to replace this section of code:

  while ( midiSerial.available()) {
    incomingCommand = midiSerial.read();
    incomingNote = midiSerial.read();
    incomingVelocity = midiSerial.read(); 
  if (incomingVelocity <=1023) {
    midiSerial.write(incomingCommand);
    midiSerial.write(incomingNote);
    midiSerial.write(incomingVelocity); } }

I'll also try doing this without libraries later tonight

As regard that first code you posted, any variable declared inside a switch statement only has scope inside that switch statement. Those variables should be made global and their value changed inside the switch statement.

The second section of code waits until there is one byte in the serial buffer and then proceeds to read three bytes. Because the buffer will not always have three bytes in it then when reading an empty buffer you will get back a value of 0xFF.

Ahh, yea I must've done that right after posting the code

I've tried reading 3 bytes, and it works with my keyboard, but it doesn't work with my DAW.
Edit - Just checked, that doesn't work with my keyboard either, the fader gets lost when data comes in.

What like?
you should do:-

while ( midiSerial.available() > 2) {

However your DAW is probably sending one or two byte midi messages which will not work with this code because it is always looking for three byte massages.

Turn on a MIDI monitor to see what they are.

I know that Ableton does send a burst of messages when connecting a new device

Yep, tried that and it doesn't work with my keyboard or DAW. I've tried reading it as >=3 as well. No luck with either. However, my fader responds perfectly (with my keyboard only) when I read it as is. Still a bit sluggish with my DAW, but it works

So what do you get being sent from your DAW when using a MIDI monitor?