Multiple Megas reading the same serial data stream; data getting corrupted

Hi all.

I’m building a MIDI-operated reed organ. It has 88 keys and I’m using solenoids + custom 16-channel MOSFET drivers to drive the solenoids. I’m using 3 Mega’s to control the whole thing: pins 22-53 on each Mega simply going on and off as the MIDI tells them to.

The whole setup looks like this:

(Note: the small PCB on the right is that Mega’s USB port. I desoldered it and put it on breadboard and connected it back up with some short wires so that I can mount it well once the action is back in the case).

I’m using Hairless Midi, which is a MIDI-over-serial program that simply sends out MIDI data at whatever baud you need over a serial port. Using a single Mega, this works great. The following is an example of my code that is running perfectly:

//variables setup

byte incomingByte;
byte note;
byte velocity;

int statusLed = 13;   // select the pin for the LED

int action = 2; //0 =note off ; 1=note on ; 2= nada


//setup: declaring iputs and outputs and begin serial
void setup() {
  pinMode(statusLed, OUTPUT);  // declare the LED's pin as output

  int inMin = 22; // Lowest input pin
  int inMax = 53; // Highest input pin
  for (int i = inMin; i <= inMax; i++) {
    pinMode(i, OUTPUT);
  }

  Serial.begin(38400);
  digitalWrite(statusLed, HIGH);
}

//loop: wait for serial data, and interpret the message
void loop () {
  if (Serial.available() > 0) {
    // read the incoming byte:
    incomingByte = Serial.read();

    // wait for as status-byte, channel 1, note on or off
    if (incomingByte == 144) { // note on message starting starting
      action = 1;
    } else if (incomingByte == 128) { // note off message starting
      action = 0;
    } else if (incomingByte == 208) { // aftertouch message starting
      //not implemented yet
    } else if (incomingByte == 160) { // polypressure message starting
      //not implemented yet
    } else if ( (action == 0) && (note == 0) ) { // if we received a "note off", we wait for which note (databyte)
      note = incomingByte;
      playNote(note, 0);
      note = 0;
      velocity = 0;
      action = 2;
    } else if ( (action == 1) && (note == 0) ) { // if we received a "note on", we wait for the note (databyte)
      note = incomingByte;
    } else if ( (action == 1) && (note != 0) ) { // ...and then the velocity
      velocity = incomingByte;
      playNote(note, velocity);
      note = 0;
      velocity = 0;
      action = 0;
    } else {
      //nada
    }
  }
}

void blink() {
  digitalWrite(statusLed, HIGH);
  delay(100);
  digitalWrite(statusLed, LOW);
  delay(100);
}


void playNote(byte note, byte velocity) {
  int value = LOW;
  if (velocity > 10) {
    value = HIGH;
  } else {
    value = LOW;
  }
  //Basic, always runs whether doubling is enabled or not. Numbers for 3 SHOULD be 84, 109, -63
  if (note > 84 && note < 109) {
    digitalWrite((note - 63), value);
  }
}

What also works perfectly is connecting another Mega running the exact same code (albeit code that responds to a different range of MIDI because its controlling different keys). I connect them by connecting their grounds together and RX0. Both respond to the serial data I’m sending them from my computer, which is going through one of their USB>serial adapters before going out to both of them.

In fact, it works perfectly using either the middle Mega and the right-side Mega, or the middle Mega and the left-side Mega, but as soon as I connect all three up to my serial “bus” (a cable that’s running between all three) it simply does not work. Nothing happens.

So I figured either I was having noise issues (there are 88 solenoids close to the cable and power coming out of slightly-sketchy power supplies that may not be very noise-free) or maybe the built-in USB>serial adapter couldn’t drive that many inputs. So I modified my code and had the middle Arduino copy every byte it recieved on Serial0 to Serial1, and had the left-side Arduino listen to Serial1 instead.

Middle Arduino code for this:

//variables setup

byte incomingByte;
byte note;
byte velocity;

int statusLed = 13;   // select the pin for the LED

int action = 2; //0 =note off ; 1=note on ; 2= nada


//setup: declaring iputs and outputs and begin serial
void setup() {
  pinMode(statusLed, OUTPUT);  // declare the LED's pin as output

  int inMin = 22; // Lowest input pin
  int inMax = 53; // Highest input pin
  for (int i = inMin; i <= inMax; i++) {
    pinMode(i, OUTPUT);
  }


  //start serial with midi baudrate 31250 or 38400 for debugging
  Serial.begin(38400);
  Serial1.begin(38400);
  digitalWrite(statusLed, HIGH);
}

//loop: wait for serial data, and interpret the message
void loop () {
  if (Serial.available() > 0) {
    // read the incoming byte:
    incomingByte = Serial.read();
    Serial1.write(incomingByte);

    // wait for as status-byte, channel 1, note on or off
    if (incomingByte == 144) { // note on message starting starting
      action = 1;
    } else if (incomingByte == 128) { // note off message starting
      action = 0;
    } else if (incomingByte == 208) { // aftertouch message starting
      //not implemented yet
    } else if (incomingByte == 160) { // polypressure message starting
      //not implemented yet
    } else if ( (action == 0) && (note == 0) ) { // if we received a "note off", we wait for which note (databyte)
      note = incomingByte;
      playNote(note, 0);
      note = 0;
      velocity = 0;
      action = 2;
    } else if ( (action == 1) && (note == 0) ) { // if we received a "note on", we wait for the note (databyte)
      note = incomingByte;
    } else if ( (action == 1) && (note != 0) ) { // ...and then the velocity
      velocity = incomingByte;
      playNote(note, velocity);
      note = 0;
      velocity = 0;
      action = 0;
    } else {
      //nada
    }
  }
}

void blink() {
  digitalWrite(statusLed, HIGH);
  delay(100);
  digitalWrite(statusLed, LOW);
  delay(100);
}


void playNote(byte note, byte velocity) {
  int value = LOW;
  if (velocity > 10) {
    value = HIGH;
  } else {
    value = LOW;
  }
  //Basic, always runs whether doubling is enabled or not
  if (note > 52 && note < 85) {
    digitalWrite((note - 31), value);
  }
}

And the left-most Arduino was running identical code except Serial was swapped out for Serial1.

This setup mostly worked, except data was clearly getting corrupted or some bytes were being skipped or something: random notes were not going down, or weren’t coming back up.

Any general help on what to do now or why the serial “copying” setup was corrupting data is highly appreciated.

Thank you.

why the serial "copying" setup was corrupting data

"Corruption" is possible any time you try to write at the same speed you are reading. The reading process can be a tad slow because polling (testing available()) does not catch the newly-received character immediately. This means the write is a little slower, too. And the write can also block if the previous output characters are not quite finished. After hundreds or thousands of characters, these tiny delays on input and output will cause an input buffer overflow (i.e., lost characters).

This is one place I would use a driver for the Arduino TX line to multiple receivers. A hex "buffer" chip would work, maybe with Schmitt triggers to make it more immune to noise.

You might take a look at RS-485 modules. They hook directly to the TX pin, and RS-485 is intended for multiple listeners (i.e., it has sufficient drive for 3 listeners). It will be more immune to noise, too. Four modules won't cost very much, and you could use the original sketch (not the forwarding sketches).

Cheers,
/dev

P.S. Can't see the pic.