[SOLVED] Arduino sysex problem

tysonlt

Power User
Greetings!

I have been working on a DIY MIDI controller with an Arduino Nano. I have a problem where I can send sysex commands to get preset name, scene name, firmware version... but not sequentially. It seems that whatever sysex I send first is the only one that arrives.

For example, I can request the preset name 3 times, and I will get three responses... but if I request preset name, then scene name (eg responding to program change) then I only get the preset name.

Get ready for some code:

Code:
#include <TFT.h>
#include <MIDI.h>
#define XDEBUG
#define CHAR_W                        6
#define BANK_CHANGE_CC                0
#define BANK_SIZE                     128
#define MAX_PRESET_NAME               32
#define MAX_SCENE_NAME                MAX_PRESET_NAME
#define AXE_MANUFACTURER_B1           0x00
#define AXE_MANUFACTURER_B2           0x01
#define AXE_MANUFACTURER_B3           0x74
#define SYSEX_HEADER                  0x00, 0x01, 0x74, 0x10
#define SYSEX_REQUEST_FIRMWARE        0x08
#define SYSEX_REQUEST_PRESET_INFO     0x0D
#define SYSEX_PRESET_CHANGED          0x14
#define SYSEX_REQUEST_SCENE_INFO      0x0E
#define SYSEX_REQUEST_SCENE_NUMBER    0x0C
#define SYSEX_SCENE_STATUS            0x29
#define SYSEX_TAP_TEMPO               0x10
#define SYSEX_FRONT_PANEL_CHANGE      0x21
TFT screen = TFT(10, 9, -1);   
struct {
  byte bank;
  byte patch;
  byte scene;
  int preset;
  bool firmwareRequested;
  bool dirty;
  char sceneName[35], presetName[35];
  byte firmwareMajor;
  byte firmwareMinor;
  byte usbMajor;
  byte usbMinor;
} state;
char buf[100];
MIDI_CREATE_DEFAULT_INSTANCE();
/**
 * 
 */
void setup() {
 
  screen.initR(INITR_BLACKTAB); 
  screen.setRotation(1);
  screen.setTextColor(0xFFE0, 0x0000); 
  screen.setTextSize(1);
  screen.fillScreen(0x0000);
  MIDI.begin();
  MIDI.turnThruOff();
  //requestFirmwareVersion(); <-- enabling 'blocks' further sysex!!!
 
}
/**
 * 
 */
void loop() {
  if (MIDI.read(MIDI_CHANNEL_OMNI)) {
    if (MIDI.getType() == midi::ProgramChange) {
      //this works fine...
      requestPresetName();
      /* this works too...
      requestPresetName();
      requestPresetName();
      requestPresetName();
      */
      /* as above, this works on its own...
      requestSceneName();
      */
      /* but sending different types, I only get the first type back
      requestPresetName();
      requestSceneName();
      */
     
    } else if (MIDI.getType() == midi::SystemExclusive) {
     
      byte * sysex = (byte *) MIDI.getSysExArray();
      int length = MIDI.getData1();
      if (isAxeSysEx(sysex, length)) {
        switch (sysex[5]) {
          case SYSEX_REQUEST_FIRMWARE: {
             snprintf(buf, 5, "%d.%02d", sysex[6], sysex[7]);
             text(buf, 0, 0); 
             break;
          }
          case SYSEX_REQUEST_PRESET_INFO: {
            state.preset = (sysex[7] * 128) + sysex[6];
            for (byte i=0; i<32; i++) {
              state.presetName[i] = sysex[i+8];
            }
            snprintf(buf, sizeof(buf), "%d: %s", state.preset, state.presetName);
            text(buf, 0, 10); 
            break;
          }
          case SYSEX_REQUEST_SCENE_INFO: {
            state.scene = sysex[6];
            for (byte i=0; i<32; i++) {
              state.sceneName[i] = sysex[i+7];
            }
            snprintf(buf, sizeof(buf), "(%d) %s", state.scene, state.sceneName);
            text(buf, 0, 20); 
            break;       
          }
          case SYSEX_REQUEST_SCENE_NUMBER: {
            text("SYSEX_REQUEST_SCENE_NUMBER", 0, 30);
            break;
          }
          default: {
            toHex(sysex, length, buf);
            text(buf, 0, 100);
          }
         
        }
         
      }
   
    }
   
  }
 
}
/**
 * 
 */
boolean isAxeSysEx(const byte *sysex, const int len) {
  return 
    len > 4 && 
    sysex[1] == AXE_MANUFACTURER_B1 && 
    sysex[2] == AXE_MANUFACTURER_B2 && 
    sysex[3] == AXE_MANUFACTURER_B3;
}
/**
 * 
 */
void requestFirmwareVersion() {
  static const byte msg[] = {
    SYSEX_HEADER,
    SYSEX_REQUEST_FIRMWARE,
  };
  sendSysEx(6, (byte*) msg); //<-- 6 bytes = 4 header, 1 command, 1 spare for checksum
}
/**
 * 
 */
void requestPresetName() {
  static const byte msg[] = {
    SYSEX_HEADER,
    SYSEX_REQUEST_PRESET_INFO,
    0x7F, 0x7F,
  };
  sendSysEx(8, (byte*) msg);
}
/**
 * 
 */
void requestSceneNumber() {
  static const byte msg[] = {
    SYSEX_HEADER,
    SYSEX_REQUEST_SCENE_NUMBER,
    0x7F, 
  };
  sendSysEx(7, (byte*) msg);
}
/**
 * 
 */
void requestSceneName() {
  static const byte msg[] = {
    SYSEX_HEADER,
    SYSEX_REQUEST_SCENE_INFO,
    0x7F, 
  };
  sendSysEx(7, (byte*) msg);
}
/**
 * 
 */
void sendSysEx(byte length, byte *sysex) {
  byte sum = 0xF0;
  for (int i=0; i<length-1; ++i) {
    sum ^= sysex[i];
  }
  sysex[length-1] = (sum & 0x7F);
  MIDI.sendSysEx(length, sysex);
  Serial.flush();
 
}
/**
 * 
 */
void text(const char *text, byte x, byte y) {
  screen.setCursor(x, y);
  screen.print(text);
}
/**
 * Turn a byte buffer into a hex string.
 */
void toHex(const byte array[], int len, char buffer[]) {   
    int c = 0;
    for (int i = 0; i < len; i++) {
        byte nib1 = (array[i] >> 4) & 0x0F;
        byte nib2 = (array[i] >> 0) & 0x0F;
        buffer[c*3+0] = nib1  < 0xA ? '0' + nib1  : 'A' + nib1  - 0xA;
        buffer[c*3+1] = nib2  < 0xA ? '0' + nib2  : 'A' + nib2  - 0xA;
        buffer[c*3+2] = ' ';
        c++;
    }
    buffer[len*3] = '\0';
}

PLEEEASE tell me there's something stupid and obvious that I have missed! Thanks!
 
PS I tried putting delays between sending sysex commands, but the fact that three preset name requests in a row works just fine tells me it is not a timing issue.

I also hacked arduino core to increase the serial tx/rx buffers to 256, but no change.
 
Hi
What’s going on in isAxeSysEx?
Is that return after the first line just residue from your testing?

Thanks
Pauly
 
Again, I’m probably whistling Dixie as I have no idea about midi, but could you use getSysExArrayLength to get the size of the sysexdata?
Thanks
Pauly
 
The midi library handles sending and receiving data. The sending doesn’t seem to be the problem, I’m just not getting the responses, or the responses are being lost. I thought maybe the serial buffer was full, but I’ve increased them from 64 to 256!
 
To confirm, the actual mechanism works just fine. I get patchname, scene name, etc as long as I only ask for one of them. If I ask for two, only the first request is answered. I don’t really think it’s a midi problem, because if I send 3 patch name requests in a row, I get three responses with no problem. The mystery is why the different requests seem to get lost. It’s probably something stupid and obvious in the onSysEx function, but I based it on FCBInfinity which seems to work (albeit for axe fox 2)
 
Thanks...

I got tricky and put all sysex commands into a FIFO queue, and setup a button so that the queue would not drain (eg sysex sent) unless the button was pressed. This produces interesting results. If I let the queue get up to 20 or so, it sends all the sysex commands before any any are received, and then only the last one seems to be applied. Then, the arduino locks up!
 
I have updated my main sketch and added an incoming and outgoing queue for all sysex messages. When I change presets on the front panel and a program change comes from the Axe, I put a patch name and a scene name request on the queue. All incoming sysex responses are put on an incoming queue.

Interestingly, both of these messages successfully go out to the Axe, but only one little duck comes back, which is a response to whatever message I sent first. So if I ask for patch then scene name, I get a patch name, but no scene name, and vice versa.

Each loop, only one message is popped of the outgoing queue, and then there is a MIDI read, so the Axe should have an opportunity to respond before the next sysex is sent. This works fine for single messages, or multiple messages of the same command (ie 3 patch name requests gets me 3 responses.)

Is it possible that this is something to do with the Axe itself???? I find that hard to accept.
 
You are using a fixed (and small) "buffer" in the function "toHex" that receives "len" as a variable.
Are you sure the length is never bigger than 33? (once buffer needs to be bigger than "3x len")

I'd use dynamic memory so you can allocate what you actually need, but if you don't want to, at least put an assert to let you know if you would get a buffer overrun.
 
In my firmware I send one request then wait for the reply before sending the next request. I realise this doesn't answer your question.
 
You are using a fixed (and small) "buffer" in the function "toHex" that receives "len" as a variable.
Are you sure the length is never bigger than 33? (once buffer needs to be bigger than "3x len")

I'd use dynamic memory so you can allocate what you actually need, but if you don't want to, at least put an assert to let you know if you would get a buffer overrun.

True, that’s just a quick and dirty debug function I pinched from somewhere. It’s not actually being called in the case that causes the problem.
 
In my firmware I send one request then wait for the reply before sending the next request. I realise this doesn't answer your question.
Thanks GM, I was hoping you’d pitch in! I have made a new sketch that only sends one Sussex before calling MIDI.read(), it is my understanding that this should handle the incoming response. However I’m not sure if that method is blocking... the weird thing is that I can fire off 3 preset name requests, and I get 3 responses, so I think the polling mechanism is working fine, but if I ask for preset AND scene name, I only get a response to the first request. I believe that the MIDI library stores responses in an internal buffer anyway.

The next step is to start reading from the serial manually... cry emoji!
 
Can you be certain your multi request of preset name isn’t just pulling the data one time, and then applying the results of your already filled variable, for the second and third “replies”? This is probably a WAG, as it’s been a minute since I’ve touched any code.

Either way, hope you run this to ground.

Lee
 
You can try to use a delay using a timer with the millis() function instead of using the delay. The delay just holds the entire program for the entered time.
 
Can you be certain your multi request of preset name isn’t just pulling the data one time, and then applying the results of your already filled variable, for the second and third “replies”? This is probably a WAG, as it’s been a minute since I’ve touched any code.

Either way, hope you run this to ground.

Lee
Yep, I’ve done another test where it printed the preset name at three different locations, so it’s definitely able to handle multiple requests without any delays. Also good idea re the vars, but I am reading into different variables.

The problem is definitely that if I mix request types, only the first one is answered. Even more infuriating is that I’ve had different versions that have been able to get the firmware version early on, and then the patch name. I have tried delays from 1-1000, same results. It’s a head-scratcher!
 
Back
Top Bottom