2x time critical functions from a 328p, is it asking too much? [Serial and Time]

So I've set up the code that is extremely thorough at capturing the midi data (where Rhythm/timing is critical):

while(Serial.available() == 0);
      commandByte = Serial.read();//read first byte
      // If this is not a command byte, give up
      if(commandByte & 0x80 == 0)return;
      // Mask the high nibble of the byte (top 4 bits)
      // (the low order nibble has the channel number)
      byte cmd = commandByte & 0xF0;
      //byte channel_check = commandByte & 0x0F;
      // If the command byte is not Note On or Note Off, ignore it.
      if((cmd != 0x80) && (cmd != 0x90))return;

      // channel checking 1 and 2
       // if (channel_check != 0x00){return;}
        //if (channel_check != 0x01){return;}
      
      // It is note on or note off, so we can read
      // note and velocity bytes
      while(Serial.available() == 0);
      noteByte = Serial.read();
      while(Serial.available() == 0);
      velocityByte = Serial.read();

This code is stone solid, not a single note gets missed, everything is accounted for.

The problem is, after debugging, all those while loops are slowing down the rest of the code to a snails pace... For example I had a debounce timer for physical buttons in place that would set to 50 when the button is pressed and would not allow the button to run a function unless that timer went back to 0. The timer runs so slow now. I've disabled all the while loops for debugging and they are the ones that seem to be the culprit. Problem is that I need those while loops because otherwise the incoming midi messages can get missed / lost. And I'm also outputting midi data too so if the 328p detects a midi in note ON message and thus outputs it to the synthesizer to play but it also misses the note OFF message then the synth will now get stuck on that note.

So this project is effectively a midi message looper, you play a rhythm and the looper repeats it over and over again. So I also need to have reliable time keeping and executing other code all the while the code above reliable reads the incoming messages.\

Am I maybe asking too much of the little 328p for this project?

I've been playing around with if and other loop to see if I can get the same reliable data reading but it all resulted in lost or missed midi serial messages which is just a big breaking point.

No, just don't do it with a while loop. Serial data comes in pretty slowly. Let the processor do other things while it is waiting for the next character. Just make sure none of the individual steps that it has to do block execution for long periods of time.

Let the loop function handle the looping and build a state machine to figure out what actions need to happen. Those while loops will all turn into similar if statements. It's not as simple as just replacing the word while with if, but if you look at the logic and flow you should be able to tell the program to pull one character at a time instead of being trapped while there is data available.

Especially this one:

while(Serial.available() == 0);

That sticks the code while there is nothing to do. Let it run the rest of the code. Don't just sit and twiddle your thumbs because this one individual process is waiting. Run the rest of your code.

Think of code not like a story that tells from beginning to end what happens, and more like a checklist that runs all the way through thousands of times a second and decides each time what one small step needs to be taken.

I'm having a hard time visualizing this method of data collection in code.

The midi data comes in packets of 3 bytes. Command, Note and Velocity. So the while loop makes sure that the data is properly sorted and broken down into separate variables to be used later on.

How would you go about reading this data with your method? All my attempts have resulted in less than satisfactory results with data being lost or missed. Only this while loop set up does it 100% reliably.

I could try to use the serialEvent function which gets called every time there's data incoming, and then check if there's at least 3 pieces of data before collecting it all but that results in slower read times and yet still unreliable readings with some notes off messages missing.

void serialEvent(){
      if (Serial.available() >2 ){
      commandByte = Serial.read();//read first byte
      noteByte = Serial.read();//read next byte
      velocityByte = Serial.read();//read final byte

      cmd = commandByte & 0xF0;
      
     // if(commandByte & 0x80 == 0)return;
      //if((cmd != 0x80) && (cmd != 0x90))return;
    }
    return;
}

void sendMidi(byte msg, byte note, byte vel){
  Serial.write(msg); // note on
  Serial.write(note); // note
  Serial.write(vel); // velocity
  //Serial.flush(); // was testing to see if those stuck midi messages weren't getting sent out in time
}



void loop() {

      if (cmd == 0x90){
        digitalWrite(9, HIGH);
        sendMidi(0x90, noteByte, velocityByte);
      }
      
      if (cmd == 0x80){
        digitalWrite(9, LOW);  

        sendMidi(0x80, noteByte, 0x00);
      }
        
}

TobiasRipper:
I could try to use the serialEvent function which gets called every time there's data incoming,

No, serialEvent gets called each time loop ends if Serial.available() returns non-zero. So it would be the same as polling serial in your loop.

The thing you need is a Finite State Machine. Avoid the wikipedia article on that, it's confusing. Stick to stuff from programmers.

You've got a 64 byte receive buffer with Serial. As long as your other code isn't blocking for long periods of time, you're not going to miss 3 bytes coming in.

I can't really give you specifics on how that will work with YOUR particular code. Let's see if you can guess why. It's a pretty common sense reason.

Have a look at the examples in Serial Input Basics - simple reliable ways to receive data without blocking.

And post the complete program so we can see what the Serial input stuff must fit into

...R

In the last example you're using rb = Serial.read();

does it store all the data stored in the buffer right away? And then you sort it out?

You seem to be using < and > as start and end markers but you are sticking to only serial monitor mesages where you can define your own characters to be interpreted as start and end markers.

In my case messages 0x90 and 0x80 would signify the start of a message packet so there should be 2 more bytes expected to follow.

So this? I moved the serial reading back to it's own function.

void checkMIDI(){
      while (Serial.available() > 0 ){
      commandByte = Serial.read();//read first byte
      cmd = commandByte & 0xF0;

      if (cmd != 0x80 && cmd != 0x90) {return;}
      
      noteByte = Serial.read();//read next byte
      velocityByte = Serial.read();//read final byte
  } //else {return;}
}

Robin2:
Have a look at the examples in Serial Input Basics - simple reliable ways to receive data without blocking.

And post the complete program so we can see what the Serial input stuff must fit into

...R

What you saw in my last post is pretty much all there is:

#include <TM1637Display.h>




byte commandByte;
byte noteByte;
byte velocityByte;
byte cmd; 

// input storage
bool btn_record; // in D8
bool btn_record_old = true; 

int btn_check = 0;              // debounce timer

bool btn_clear = false; //          in D7

// booleans, conditions
int recording = 0; 
// 0 not recording
// 1 record on midi;
// 2 record right away

//#include <SoftwareSerial.h>
//SoftwareSerial mySerial(5, 6); // RX, TX

TM1637Display display(5, 6); //set up the 4-Digit Display.


void setup() {
  // put your setup code here, to run once:

// buttons
pinMode(8,INPUT_PULLUP); // record on midi note press
pinMode(7,INPUT_PULLUP); // clear

// UI
pinMode(10,OUTPUT); // midi armed
pinMode(11,OUTPUT); // midi recording

// default ui states
digitalWrite(10,LOW);   // midi armed
digitalWrite(11,LOW);  // midi recording

  pinMode(9,OUTPUT);
  Serial.begin(31250);
  Serial.setTimeout(10);
  //mySerial.begin(31250);

  display.setBrightness(0x0f); //set the diplay to maximum brightness
//  DISPLAY.init();
}



void checkMIDI(){
      while (Serial.available() > 0 ){
      commandByte = Serial.read();//read first byte
      cmd = commandByte & 0xF0;

      if (cmd != 0x80 && cmd != 0x90) {return;}
      
      noteByte = Serial.read();//read next byte
      velocityByte = Serial.read();//read final byte
  } //else {return;}
}


void sendMidi(byte msg, byte note, byte vel){
  Serial.write(msg); // note on
  Serial.write(note); // note
  Serial.write(vel); // velocity
  //Serial.flush();
}



void loop(){

checkMIDI();


 
      if (cmd == 0x90){
        digitalWrite(9, HIGH);
        sendMidi(0x90, noteByte, velocityByte);
      }
      
      if (cmd == 0x80){
        digitalWrite(9, LOW);  

        sendMidi(0x80, noteByte, 0x00);
      }
        

} // end of loop()

Try this version in which I have changed the function checkMIDI() and added an IF line into loop() (not tested)

#include <TM1637Display.h>

byte commandByte;
byte noteByte;
byte velocityByte;
byte cmd; 

char messageState = 'W';   // W = waiting S = started R = received

    // input storage
bool btn_record; // in D8
bool btn_record_old = true; 

int btn_check = 0;              // debounce timer

bool btn_clear = false; //          in D7

    // booleans, conditions
int recording = 0; 
// 0 not recording
// 1 record on midi;
// 2 record right away

//#include <SoftwareSerial.h>
//SoftwareSerial mySerial(5, 6); // RX, TX

TM1637Display display(5, 6); //set up the 4-Digit Display.


void setup() {
    // put your setup code here, to run once:

        // buttons
    pinMode(8,INPUT_PULLUP); // record on midi note press
    pinMode(7,INPUT_PULLUP); // clear

        // UI
    pinMode(10,OUTPUT); // midi armed
    pinMode(11,OUTPUT); // midi recording

        // default ui states
    digitalWrite(10,LOW);   // midi armed
    digitalWrite(11,LOW);  // midi recording

    pinMode(9,OUTPUT);
    Serial.begin(31250);
    Serial.setTimeout(10);
    //mySerial.begin(31250);

    display.setBrightness(0x0f); //set the diplay to maximum brightness
    //  DISPLAY.init();
}



void checkMIDI(){
    if (Serial.available() > 0 ){
        byte newByte = Serial.read();
        static byte byteNum = 0;
        if (messageState == 'W') {  // W for waiting between messages
            cmd = newByte & 0xF0;
            if (cmd == 0x80 or cmd == 0x90) {
                messageState = 'S'; // S for started
                byteNum = 1;
            }
        }
        else {
            if (byteNum == 1) {
                noteByte = newByte;
                byteNum = 2;
            }
            else {
                velocityByte = newByte;
                messageState = 'R';  // R for received
            }
        }
    } 
}

void sendMidi(byte msg, byte note, byte vel){
    Serial.write(msg); // note on
    Serial.write(note); // note
    Serial.write(vel); // velocity
    //Serial.flush();
}



void loop(){

    checkMIDI();
    if (messageState == 'R') {  //   NEW

        if (cmd == 0x90){
          digitalWrite(9, HIGH);
          sendMidi(0x90, noteByte, velocityByte);
        }
        
        if (cmd == 0x80){
          digitalWrite(9, LOW);  

          sendMidi(0x80, noteByte, 0x00);
        }
        
        messageState = 'W'; // W = waiting for next message

} // end of loop()

Okay so I think I might have now figured out the logic behind your method of reading messages. Since the serial data actually comes in at a slower rate you have implemented a counter of a sort, which counts up whenever the correct data comes in. So if you're expecting a package of 3 bytes then you wait for the first byte to come in, check if it contains the value usually expect this data packet to contain, and then simply wait until more data comes in as you kick counter up with every piece incoming. Am I getting this right?

TobiasRipper:
then simply wait until more data comes in as you kick counter up with every piece incoming. Am I getting this right?

Yes.

It is a variant of the examples in Serial Input Basics.

Note that it is not waiting inside the checkMIDI() function - the Arduino is available to do other stuff between the incoming bytes.

...R