Reading MIDI cc on an Arduino Nano works well, but why not with SparkFun Pro Micro?

Hi,
I am using the following sketch to read MIDI cc from a DIN5 jack, which is connected via an optocoupler to the RX-Pin of an Arduino Nano:

/*
Detach signaling wire from RX-Pin before uploading the sketch and
re-connect only after sketch has been uploaded
*/

byte statusByte;      // captures category of MIDI message and channel
byte ccByte;             // captures the cc number
byte velocityByte;   // captures the velocity parameter

void setup(){
  pinMode(6, OUTPUT);    // External LED on Pin 6
  digitalWrite(6, LOW);      // set to LOW at program start
  Serial.begin(31250);       // MIDI baud rate
}

void loop(){
  receiveMIDI();
  delay(10);  // required to get the LED lighting up
  digitalWrite(6, LOW);
}

void receiveMIDI() {
  do {
    if (Serial.available()){
 
      statusByte = Serial.read();
      ccByte = Serial.read();
      velocityByte = Serial.read();

      if (statusByte >= 176 && statusByte <= 191) {   // MIDI cc on channel 1 to 16
        digitalWrite(6,HIGH);
      }
    }
  }
  while (Serial.available() > 2); //execute as soon as there are at least 3 bytes available
}

Each time a MIDI cc is received, the LED blinks for a moment, confirming receipt. Since the RX pin is shared with USB, I have to detach the signaling wire from the RX Pin each time I upload a modified version of the sketch. I can reconnect the signaling wire only after the sketch has been uploaded successfully and then everything works fine.

So, I had the idea to switch to a simple board with a second UART, in order to avoid detaching and reconnecting of the signaling wire and in a first step, I tried the exact same sketch with a SparkFun Pro Micro.

To my surprise, however, this doesn't work as expected. The sketch is uploaded without error, but nothing visible happens, when a MIDI cc is sent to the board. It neither works via Serial with the signaling wire at the RX Pin nor via Serial1 with the signaling wire connected to Pin D2. Could someone please give me a hint on how to fix this?

Thanks a lot in advance for your support

Hi @fullcane ,

I use a Pro Micro to convert Midi signals from a keyboard to USB Midi and it works brilliant ...

The Midi input is based on SoftwareSerial, here just the necessary lines of code:

#include <SoftwareSerial.h>

constexpr byte rxPin{ 9 };
constexpr byte txPin{ 8 };

SoftwareSerial mySerial(rxPin, txPin, false);  // false => Use standard logic,
                                               // true => inverse logic

You may give it a try ...
Good luck!

ec2021

If you are interested here the full sketch I wrote:

MidiInToUSBMidi

Sketch:

#include <SoftwareSerial.h>
#include "verysimplemidi.h"
#include "MIDIUSB.h"
constexpr byte rxPin{ 9 };
constexpr byte txPin{ 8 };

VerySimpleMidi myMidi;
SoftwareSerial mySerial(rxPin, txPin, false);

void setup() {
  mySerial.begin(31250);
}

boolean zeroOk = false;

void loop() {
  if (mySerial.available()) {
    byte inByte = mySerial.read();
    if (myMidi.dataReceived(inByte)) {
      DataStruct midiData = myMidi.getData();
      switch (midiData.cmd) {
        case NOTEON:
          USB_noteOn(1, midiData.key, midiData.velocity);
          //printNoteData("NoteON", midiData);
          break;
        case NOTEOFF:
          USB_noteOff(1, midiData.key, midiData.velocity);
          //printNoteData("NoteOFF", midiData);
          break;
        case AFTERTOUCH:
          //printNoteData("AfterTouch", midiData);
          break;
        case CTRLCHANGE:
          //printCCData("ControlChange", midiData);
          break;
        case PROGCHANGE:
          //printPCData("ProgramChange", midiData);
          break;
        default:
          //Serial.println(midiData.cmd, HEX);
          break;
      }
    }
  }
}

void printNoteData(char* msg, DataStruct& mD) {
  Serial.print(msg);
  Serial.print("\t key 0x");
  Serial.print(mD.key, HEX);
  Serial.print("\t velocity 0x");
  Serial.println(mD.velocity, HEX);
}

void printCCData(char* msg, DataStruct& mD) {
  Serial.print(msg);
  Serial.print("\t controller 0x");
  Serial.print(mD.controller, HEX);
  Serial.print("\t value 0x");
  Serial.println(mD.velocity, HEX);
}

void printPCData(char* msg, DataStruct& mD) {
  Serial.print(msg);
  Serial.print("\t programm number 0x");
  Serial.println(mD.program, HEX);
}


void USB_noteOn(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOn = {0x09, 0x90 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOn);
  MidiUSB.flush();
}

void USB_noteOff(byte channel, byte pitch, byte velocity) {
  midiEventPacket_t noteOff = {0x08, 0x80 | channel, pitch, velocity};
  MidiUSB.sendMIDI(noteOff);
  MidiUSB.flush();
}

"verysimplemidi.h"

#pragma once

// V0.3
// ec2021

struct DataStruct {
  boolean isValid = false;
  byte cmd;
  byte channel;
  byte key;
  byte velocity;
  byte &controller = key;
  byte &program = key;
};


constexpr byte NOTEON{ 0x90 };
constexpr byte NOTEOFF{ 0x80 };
constexpr byte AFTERTOUCH{ 0xA0 };
constexpr byte CTRLCHANGE{ 0xB0 };
constexpr byte PROGCHANGE{ 0xC0 };


enum CmdStates { DONTCARE,
                 CMDNOTEON,
                 CMDNOTEOFF,
                 CMDAFTERTOUCH,
                 CMDCTRLCHANGE,
                 CMDPROGCHANGE };

class VerySimpleMidi {
private:
  CmdStates cmdState = DONTCARE;
  DataStruct midiData;
  byte cmdData[2];
  byte dataRead = 0;
  byte channel = 0;
  byte inByte = 0;
  void changeState();
  void handleData();
  void handleRTMsg();
  void evaluateInByte();
  void prepareData();
  void copyDataToStruct();
  byte getCmdFromState();
public:
  boolean dataReceived(byte byteRead);
  DataStruct getData();
};

DataStruct VerySimpleMidi::getData() {
  return midiData;
}

boolean VerySimpleMidi::dataReceived(byte byteRead) {
  inByte = byteRead;
  evaluateInByte();
  return midiData.isValid;
}

void VerySimpleMidi::evaluateInByte() {
  byte mode = 0;
  midiData.isValid = false;
  if (inByte < 0x80) {
    mode = 1;
  };
  if (inByte >= 0xF8) {
    mode = 2;
  };
  switch (mode) {
    case 0:
      changeState();
      break;
    case 1:
      handleData();
      break;
    case 2:
      handleRTMsg();
      break;
  }
}

void VerySimpleMidi::changeState() {
  byte cmd = inByte & 0xF0;
  switch (cmd) {
    case NOTEON:
      cmdState = CMDNOTEON;
      prepareData();
      break;
    case NOTEOFF:
      cmdState = CMDNOTEOFF;
      prepareData();
      break;
    case AFTERTOUCH:
      cmdState = CMDAFTERTOUCH;
      prepareData();
      break;
    case CTRLCHANGE:
      cmdState = CMDCTRLCHANGE;
      break;
    case PROGCHANGE:
      cmdState = CMDPROGCHANGE;
      break;
    default:
      cmdState = DONTCARE;
      break;
  }
}

void VerySimpleMidi::prepareData() {
  dataRead = 0;
  midiData.isValid = false;
  channel = (inByte & 0x0F);
}


void VerySimpleMidi::handleData() {
  switch (cmdState) {
    case CMDNOTEON:
    case CMDNOTEOFF:
    case CMDAFTERTOUCH:
    case CMDCTRLCHANGE:
      cmdData[dataRead] = inByte;
      dataRead++;
      if (dataRead == 2) {
        copyDataToStruct();
      }
      break;
    case CMDPROGCHANGE:
      cmdData[0] = inByte;
      copyDataToStruct();
      break;
    default:
      // Ignore all other data
      break;
  }
}

byte VerySimpleMidi::getCmdFromState() {
  byte returnValue = DONTCARE;
  switch (cmdState) {
    case CMDNOTEON:
      returnValue = NOTEON;
      break;
    case CMDNOTEOFF:
      returnValue = NOTEOFF;
      break;
    case CMDAFTERTOUCH:
      returnValue = AFTERTOUCH;
      break;
    case CMDCTRLCHANGE:
      returnValue = CTRLCHANGE;
      break;
    case CMDPROGCHANGE:
      returnValue = PROGCHANGE;
      break;
    default:
      returnValue = DONTCARE;
  }
  return returnValue;
}

void VerySimpleMidi::copyDataToStruct() {
  midiData.cmd = getCmdFromState();
  midiData.channel = channel;
  midiData.key = cmdData[0];
  midiData.velocity = cmdData[1];
  midiData.isValid = true;
  dataRead = 0;
}

void VerySimpleMidi::handleRTMsg() {
  // TBD in future
}

Just put both files in the same directory.

I wrote the library to handle the situation that Midi allows to send a command like "NoteOn" and the data of several keys without repeating the command to save time ...

So it could be a sequence like

  • "NoteOn Pitch1 Velocity1 Pitch2 Velocity2 Pitch3 Velocity3"
    instead of
  • "NoteOn Pitch1 Velocity1 NoteOn Pitch2 Velocity2 NoteOn Pitch3 Velocity3"

see Midi Running Status

I did not have the need to handle AFTERTOUCH, CTRLCHANGE or PROGCHANGE so they are untested.

And there is only a placeholder to handle "Real Time Messages".
See here https://www.recordingblogs.com/wiki/midi-system-realtime-messages

Hi ec2021,

Thanks a lot for your quick response! Sounds terrific, but there are still some mysteries left. I understand that you use Pin D9 for receiving the MIDI signal. What remains unclear to me, is how I will then get the MIDI bytes into statusByte, ccByte and velocityByte? Can I still use the Serial function for reading the MIDI bytes?

Sorry, I overlooked that you also provided the full sketch you are using. All the mysteries seem to be resolved there. It will take me some time to get behind everything, but it appears like bearing the solution to me.

I'm not sure but it might be worth to test it...

You' ll need the SoftwareSerial library. Just check if it's already installed.

A software serial port is used like you know from hardware Serial. The main difference is that it's completely based on software and cannot receive data while the controller is occupied by other tasks. Hardware serial is usually based on a separate hardware called UART that can handle the reception and transmission of (a few) data without controller support while the controller performs other tasks.

see here https://docs.arduino.cc/learn/built-in-libraries/software-serial/

And for even more indepth information

https://wolles-elektronikkiste.de/en/serial-and-softwareserial

You mean the exact same sketch you used on the Nano? You didn't change it at all for the Pro Micro? If not, this is the reason nothing seems to happen.

Pins 0 & 1 on the a Nano are the first and only UART and that is used by the USB chip also, as you know.

Pins 0 & 1 on the Pro Micro are the second UART, which is not shared with the USB (there is no separate USB chip on Pro Micro like there is in Nano, it's built in to the main chip).

You bought a Pro Micro for that second UART but I think you are not using it!

The second UART is (or should be) referred to as Serial1 in your code.

No. @fullcane purchased the Pro Micro because it has a second hardware UART. If a hardware UART is available, it should always be used in preference to software serial. Software Serial is inferior to hardware UART.

Hi Paul,

Thanks for pointing me to the difference between the Nano and the Pro Micro regarding Pins 0 and 1. So, I obviously used the right Pin on my breadboard, but the wrong Serial statement in my sketch. Instead of Serial.* I should have used Serial1.*. I checked it out and it works just great. Life can be so simple ;-)
Best wishes
fullcane

When I wrote "You'll need the SoftwareSerial library" I refered to the sketch I posted, not as a general guidance...

However in a dedicated application that doesn't occupy the controller a lot the use of SoftwareSerial does no harm. Especially if you are only dealing with 31250 Bd and the load is just a few midi data...