felis / USB_Host_Shield_2.0

Revision 2.0 of USB Host Library for Arduino.
https://chome.nerpa.tech
1.79k stars 779 forks source link

USB Host Shield Mini, not reconnecting. #593

Open jhsa opened 3 years ago

jhsa commented 3 years ago

Hi, If I disconnect the USB device from the shield and reconnect, or connect another USB device, most of the times I don't get a connection unless I reboot the Arduino and Shield assembly. Here is a picture of how I connected an Arduino Pro-Mini 8Mhz at 3.3V to the shield. the green numbers are the Arduino Pins. Any ideas why this would happen? Thank you very much for your help...

UT89srLXWRXXXagOFbX8

YuuichiAkagawa commented 3 years ago

Not sure I understand what you mean.

Check the return code.

    uint8_t rc;
    rc = Midi1.SendData(msg, 0);
    if( rc != 0 ){
        // Error code in rc. Check the value of rc in some way.
    }
    rc = Midi2.SendData(msg, 0);
    if( rc != 0 ){
        // Error code in rc. Check the value of rc in some way.
    }
jhsa commented 3 years ago

I can't. the only device I have that works with this library is the pro-mini which only have one serial port. To use the serial port for something else, I cannot send MIDI. And without midi I cannot check the problem.. Is there any other way?

I will try to find out more. I think the problem is related to sending midi messages that include DataByte2 as well. And somehow the problem seems to occur only on the first device. If I comment the code to send the second device, everything seems to work fine.

xxxajk commented 3 years ago

Software serial is always an option...

jhsa commented 3 years ago

Ok, I used software serial on pins 4 and 5 of the arduino pro-mini. I take it they aren't being used by the library? Could you please check the following code is ok?

void loop()
{
  // uint8_t msg[4];
  // uint8_t msg1[4];

  Usb.Task();
  uint8_t msg[4];
  uint8_t rc1;
  uint8_t rc2;
  uint32_t t1 = (uint32_t)micros();

  if (MIDI.read()) {
    msg[0] = MIDI.getType();
    switch (msg[0]) {
      case midi::ActiveSensing :
        break;
      case midi::SystemExclusive :
        //SysEx is handled by event.
        break;
      default :

        // If this is a channel messages, set the channel number.
        if ( msg[0] < 0xf0 ) {
          // The getchannel() returns 1-16, but the MIDI status byte starts at 0.
          msg[0] |= MIDI.getChannel() - 1;
        }
        msg[1] = MIDI.getData1();
        msg[2] = MIDI.getData2();

        break;
    }
    if ( Midi1 ) Midi1.SendData(msg, 0);

    rc1 = Midi1.SendData(msg, 0);
    if( rc1 != 0 ){
      mySerial.print("MIDI 1 - ");
       mySerial.println(rc1);
    }

    if ( Midi2 ) Midi2.SendData(msg, 0);

    rc2 = Midi2.SendData(msg, 0);
    if( rc2 != 0 ){
      mySerial.print("MIDI 2 - ");
        mySerial.println(rc2);

  }
}

  if ( Midi1 ) {

    MIDI_poll(Midi1);

  }

  if ( Midi2 ) {

    MIDI_poll(Midi2);

  }

  //delay(1ms)
  //doDelay(t1, (uint32_t)micros(), 1000);
}
jhsa commented 3 years ago

ok, let me try to explain what I am seeing .

If no hub is connected to the host and I send midi I get an RC value of 14 for each device.

If a Hub is connected, but no devices connected to it, i get an RC value of 13 for each device.

If both devices are connected I get nothing meaning RC = 0.

If I disconnect a device from the hub, I get an RC value of 219 every time I send a midi message.

This seems to be the expected behavior, right?

jhsa commented 3 years ago

Now the weird stuff, which I suspect it is related toi my guitar effects pedalboard.

I am sending Presets from another controller, through the Host to the guitar effects pedalboard, and also have a USB/MIDI converter connected to the Hub as device number 2.

the Bypass preset I am sending has several MIDI type messages. PC, CC and Note. The other presets send mainly PC, and some send PC and CC.

Most of the times when i press some preset, and then press bypass I get an RC value of 4. if I connect the pedalboard as device 2, the RC value of 4 also changes to RC2. So it seems to be related to the pedalboard. If I connect another device instead, I get RC = 0 for both devices every time I send a midi message. So, what does an RC value of 4 mean? It seems my pedalboard is sending something back for some reason?

jhsa commented 3 years ago

Something else, since i am using the software serial for displaying the values of RC in the serial monitor, the MIDI seems to be behaving correctly. I haven't seen it fail yet. Sometimes it doesn't change or it takes a while to respond, but I haven't seen it go out of sync with what I am sending like it did before. So, why this? needs some delay somewhere?

I hope all this info makes sense, and it helps finding the problem Thank you so much for your help...

jhsa commented 3 years ago

Ok, replaced the USB MIDI converter with another device and now the problem is back.. Will check serial now.

jhsa commented 3 years ago

Ok, With this device connected to MIDI2, every time I send a MIDI message, RC2 = 4. If I connect it to MIDI1, I get RC1 = 4 every time I send a midi message through the USB Hub.

So, what does this mean?

Without knowing what RC = 4 means, we can't find the problem. :)

YuuichiAkagawa commented 3 years ago

https://github.com/felis/USB_Host_Shield_2.0/blob/master/max3421e.h#L213-L229

The NAK means receiving device cannot accept data or transmitting device cannot send data.

Your code is wrong. The error code has not been checked correctly. 

    if ( Midi1 ) {
      rc1 = Midi1.SendData(msg, 0);
      if( rc1 != 0 ){
        mySerial.print("MIDI 1 - ");
        mySerial.println(rc1);
      }
    }

    if ( Midi2 ) {
      rc2 = Midi2.SendData(msg, 0);
      if( rc2 != 0 ){
        mySerial.print("MIDI 2 - ");
        mySerial.println(rc2);
      }
    }
jhsa commented 3 years ago

Thanks, but which code is wrong? the serial code to check RC? I am a beginner and i do apologize, but do you have some right code then? :)

jhsa commented 3 years ago

4 : NAK from peripheral The NAK means receiving device cannot accept data or transmitting device cannot send data.

Well, the device receives and send data, but I will check that device as it is a DIY MIDI controller and uses another USB composite library for the STM32F1. The funny thing is that the same library used on the same processor, but with the USB / MIDI converter code works fine and doesn't throw any error. So, perhaps it is the controller's code. Perhaps not. Also, as you said above that you don't have more midi devices, I guess that the library is also not fully tested, at least with a hub and several midi devices??

YuuichiAkagawa commented 3 years ago

which code is wrong?

Your code

    if ( Midi1 ) Midi1.SendData(msg, 0);      // <- What is the return code for this?

    rc1 = Midi1.SendData(msg, 0);
jhsa commented 3 years ago

This:

if ( Midi1 ) Midi1.SendData(msg, 0);

is the same as this as far as I know.

if ( Midi1 ) { Midi1.SendData(msg, 0); }

I still don't see how it is wrong?? Or am I missing something here?

xxxajk commented 3 years ago

you are missing the return code :-)

do this:

if ( Midi1 ) {
        uint8_t ret = Midi1.SendData(msg, 0);
        Serial.println(ret);
}

if ret is NAK, that's a temporary failure, just retry with the same message. Also, shouldn't the second arg be the length of msg?

jhsa commented 3 years ago

Sorry guys, I am an idiot obviously. Now I see what I have done wrong. Is it ok now?

if ( Midi1 ) Midi1.SendData(msg, 0);

    if ( Midi1 ) {
      rc1 = Midi1.SendData(msg, 0);
      if( rc1 != 0 ){
        mySerial.print("MIDI 1 = ");
        mySerial.println(rc1);
      }
    }

    if ( Midi2 ) Midi2.SendData(msg, 0);

     if ( Midi2 ) {
      rc2 = Midi2.SendData(msg, 0);
      if( rc2 != 0 ){
        mySerial.print("MIDI 2 = ");
        mySerial.println(rc2);
      }
    }
jhsa commented 3 years ago

And I get the same

MIDI 2 = 4 MIDI 2 = 4 MIDI 2 = 4 MIDI 2 = 4 MIDI 2 = 4 every time I send a midi message. I think it might be related to my DIY controller because if I swap devices I get:

MIDI 1 = 4 MIDI 1 = 4 MIDI 1 = 4 MIDI 1 = 4 MIDI 1 = 4

every time I send a message. So, this seems to be related to my controller itself.

But what is weird is that I mad a USB to MIDI converter (Not Host) using the same library and processor of my controller and it doesn't have the same problem. No errors. I think the USB composite library I am using might have problems when used with other libraries. Perhaps some i2c stuff. Need to investigate better.. Meanwhile if some one know another USB composite library library that works with the STM32F103 chip, please let me know.. I can't find another. This is the one I am using:

https://github.com/arpruss/USBComposite_stm32f1

jhsa commented 3 years ago

But the thing is. If i connect this device directly to my computer it works well.. It is just through the Host and hub that it doesn't. I am at loss with this. :(

xxxajk commented 3 years ago

A lot of BSP that have USB support have quirks because they follow quirks in PC USB stacks. If you can, try a different USB stack on the STM device.

jhsa commented 3 years ago

A lot of BSP that have USB support have quirks because they follow quirks in PC USB stacks. If you can, try a different USB stack on the STM device.

I wouldn't know how to do that.. Don't even know what a BSP is. As I said, I am a beginner on Arduino. I try to learn though, and it brought me this far. But still not enough to be an advanced Arduino coder. I am still very new to this.

But I think there might also be some issue when sending several different MIDI messages together. And i think I can get proof of that.. I am setting up some serial to monitor what is being sent. So far I can see that sometimes the DATA2 byte is sent with the same value of the Type byte. Will try to post the results in a while

xxxajk commented 3 years ago

BSP == Board Support Package, much like Arduino has different BSP foir the various boards, ST has different support for their MCUs. I have an ST based MIDI device here, which constantly drops data, reverse of your issue. Trouble with it is that it wants high speed USB, and low/full speed, while it works, isn't fast enough, and the MCU should be queuing the key presses for up to 100ms or so, and doesn't.

jhsa commented 3 years ago

I think the poor F103 is not the problem here. I am just uploading a video showing the problem. I had to make a video as writing a post would take too much space here. While is uploading I explain what I did.

My setup is a Midi controller on the breadboard, sending midi to MidiOX on the computer via an USB / Midi adapter, and also to the Arduino Pro-Mini connected to the USB Host shield.

I am pressing only the bypass button on my controller, and comparing the data between what is sent to MidiOX, which is the real data being sent by the controller, and the serial monitor, which is the data sent by the USB Host. As you will see, the data that is sent is wrong by the Host, and it seems to be out off sync. Sometimes, it gets in sync and it sends the correct data. will post the video as soon as it finishes uploading.

jhsa commented 3 years ago

Ok, here is the video.. Please have a look. I have no idea why this is behaving like this :( It is driving me crazy at this point.. :( Thank you so much for your help.

https://www.youtube.com/watch?v=_OhkCo3w9Jw

Also, here is the code I used to get the serial data.

Usb.Task();
  uint8_t msg[4];
  uint8_t rc1;
  uint8_t rc2;
  uint32_t t1 = (uint32_t)micros();

  if (MIDI.read()) {
    msg[0] = MIDI.getType();
    mySerial.print("MIDI Type(hex) = ");
    mySerial.println(msg[0], HEX);
    switch (msg[0]) {
      case midi::ActiveSensing :
        break;
      case midi::SystemExclusive :
        //SysEx is handled by event.
        break;
      default :

        // If this is a channel messages, set the channel number.
        if ( msg[0] < 0xf0 ) {
          // The getchannel() returns 1-16, but the MIDI status byte starts at 0.
          msg[0] |= MIDI.getChannel() - 1;
          mySerial.print("Channel = ");
    mySerial.println(MIDI.getChannel());
    mySerial.println();
        }
        msg[1] = MIDI.getData1();
        msg[2] = MIDI.getData2();

        if ( Midi1 )
        {
          Midi1.SendData(msg, 0);
          mySerial.println("MIDI 1");

          mySerial.print("Status Byte = ");
          mySerial.println(msg[0]);
          mySerial.print("DataByte 1 = ");
          mySerial.println(msg[1]);
          mySerial.print("DataByte 2 = ");
          mySerial.println(msg[2]);
          mySerial.println();
        }
        if ( Midi2 ) {
          Midi2.SendData(msg, 0);

          mySerial.println("MIDI 2");

          mySerial.print("Status Byte = ");
          mySerial.println(msg[0]);
          mySerial.print("DataByte 1 = ");
          mySerial.println(msg[1]);
          mySerial.print("DataByte 2 = ");
          mySerial.println(msg[2]);
          mySerial.println("************************");
          mySerial.println();
        }

        break;
    }

    if ( Midi1 ) {
      rc1 = Midi1.SendData(msg, 0);
      if ( rc1 != 0 ) {
        mySerial.print("MIDI 1 = ");
        mySerial.println(rc1);
      }
    }

    if ( Midi2 ) {
      rc2 = Midi2.SendData(msg, 0);
      if ( rc2 != 0 ) {
        mySerial.print("MIDI 2 = ");
        mySerial.println(rc2);
      }
    }
  }
xxxajk commented 3 years ago

I think I see what is happening.

MIDI messages have a variable size. :-) You need to accommodate this fact, based on the message type.

jhsa commented 3 years ago

isn't the midi library supposed to do that?

xxxajk commented 3 years ago

Yes, but you are ignoring it when you are reading from serial. I do have a solution for how you are doing this, if you only want to pass messages from serial to both midi. 1: when you read from serial, check the midi type. 2: based on the type, read in the remaining part of the message. 3: send it to both.

jhsa commented 3 years ago

Thanks.. But what about using the librarie's callbacks like the one used for Sysex?? Possible?

jhsa commented 3 years ago

Hmmm, could it be a Running Status problem??

YuuichiAkagawa commented 3 years ago

I'm testing with the following code. Your code isn't made exactly as we pointed out. https://gist.github.com/YuuichiAkagawa/52710af4bc013d1af7248b1aaa80f7f2

It looks like it's working fine for sending program changes. https://youtu.be/s7ci5oXFu7g

Testing is ongoing as there seems to be a problem in the case of NAK.

You need to accommodate this fact, based on the message type.

The USBH_MIDI library correctly determines the length of the message from the message type and processes it. MIDI.read() returns one message at a time.

jhsa commented 3 years ago

Thank you so much for looking into this.

I'm testing with the following code. Your code isn't made exactly as we pointed out. https://gist.github.com/YuuichiAkagawa/52710af4bc013d1af7248b1aaa80f7f2

This code has just one direction. I am working on a bidirectional device. From MIDI devices to serial it seems to work, but not from serial to both midi devices. That is what i am trying to do.

It looks like it's working fine for sending program changes. https://youtu.be/s7ci5oXFu7g

Program change only seems to kinda work. the problem is when I try to send different messages together.

jhsa commented 3 years ago

Ok, I am testing this simple MIDI code, just to try to narrow the problem.. I am sending midi from the computer using an USB to MIDI adapter. There are definitely problems, either with my Pro-Mini boards or the MIDI library itself. I have written a script on the midi tool i am using to send the same midi as the Bypass preset of my controller. If I keep sending the same data (see below), program 1 72 noteon 2 61 72 control 1 16 0 control 1 7 0 it displays the data correctly once, and then wrong for a few times, and then correctly once again. I think it is cyclic.. Will now use another MIDI to USB adapter, to also rule it out ass a problem.

#include <MIDI.h>

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

//Arduino MIDI library v4.2 compatibility
//MIDI_CREATE_DEFAULT_INSTANCE
MIDI_CREATE_DEFAULT_INSTANCE();

void handle_sysex( byte* sysexmsg, unsigned sizeofsysex) {

}

void setup()
{
  // _MIDI_SERIAL_PORT.begin(31250);

  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.turnThruOff();

  MIDI.setHandleSystemExclusive(handle_sysex);

  mySerial.begin(9600);
  mySerial.println("Hello, world?");

}
void loop()
{

  uint32_t t1 = (uint32_t)micros();

  if (MIDI.read()) {
    uint8_t msg[4];
    msg[0] = MIDI.getType();
    mySerial.print("MIDI Type = ");
    mySerial.println(msg[0], HEX);
    switch (msg[0]) {
      case midi::ActiveSensing :
        break;
      case midi::SystemExclusive :
        //SysEx is handled by event.
        break;
      default :

        // If this is a channel messages, set the channel number.
        if ( msg[0] < 0xf0 ) {
          // The getchannel() returns 1-16, but the MIDI status byte starts at 0.
          msg[0] |= MIDI.getChannel() - 1;
          mySerial.print("Channel = ");
    mySerial.println(MIDI.getChannel());
    mySerial.println();
        }
        msg[1] = MIDI.getData1();
        msg[2] = MIDI.getData2();

          mySerial.print("Status Byte = ");
          mySerial.println(msg[0]);
          mySerial.print("DataByte 1 = ");
          mySerial.println(msg[1]);
          mySerial.print("DataByte 2 = ");
          mySerial.println(msg[2]);
          mySerial.println();

        break;
    }

  }

  //delay(1ms)
  //doDelay(t1, (uint32_t)micros(), 1000);

}

// Delay time (max 16383 us)
void doDelay(uint32_t t1, uint32_t t2, uint32_t delayTime)
{
  uint32_t t3;

  t3 = t2 - t1;
  if ( t3 < delayTime ) {
    delayMicroseconds(delayTime - t3);
  }
}
jhsa commented 3 years ago

Well, I think I have managed to kinda make it work. 1) Do not use software serial to debug this thing, just don't.. I have lost 2 days for nothing becasuse of it.. All those MIDI messages with wrong values seem to have been caused by the software serial. it is not reliable, just don't use it. 2) The pro-mini board, at least the one I was using, just can't cope with this kinda stuff. I ended up using a 5V 16MHz board, but running at 3.3V.. It still works. Even like this, I think ir just lacks the power needed.

3) the Pro-Mini doesn't seem to like the MIDI signal from my DIY Midi controller. This was the main problem I think. I connected another STM32 Blue Pill board between the Midi controller and the Pro-Mini, flashed it with the exact same MIDI input code as the USB Host serial MIDI, using the same library of course, but with Midi Thru enabled. So, Midi output from my controller into Blue Pill RX pin, and then out of the TX pin to the Pro-Mini input. It seems to work. at least I was playing with it for a while, with 2 USB devices connected to the Hub, and still haven't seen it fail. More testing needed of course.

This is weird because my DIY MIDI controller, uses exactly the same library as both the other blue pill connected as repeater, and the pro-Mini. the Controller is also based on a Blue Pill board. So, i don't understand why it does not like the controller's signal. The computer and other devices work well with it.

Having said this, I would really appreciate if the Blue Pill board (STM32F103C8) was supported in the future by the USB Host project, so I could build my little device to make my controller more complete. I am a musician, and this controller is an open project for guitar players that cannot afford commercial stuff and like to build their own stuff.. I will continue testing and report here. Will also try to add a 3rd device if I can repair the junk hub :) Thank you so much for your help and your patience..

jhsa commented 3 years ago

And here is the full code I am using at the moment.

#include <MIDI.h>
#include <usbh_midi.h>
#include <usbhub.h>

// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

//Arduino MIDI library v4.2 compatibility
#ifdef MIDI_CREATE_DEFAULT_INSTANCE
MIDI_CREATE_DEFAULT_INSTANCE();
#endif
#ifdef USBCON
#define _MIDI_SERIAL_PORT Serial1
#else
#define _MIDI_SERIAL_PORT Serial
#endif

// Set to 1 if you want to wait for the Serial MIDI transmission to complete.
// For more information, see https://github.com/felis/USB_Host_Shield_2.0/issues/570
#define ENABLE_MIDI_SERIAL_FLUSH 0

USB Usb;
USBHub  Hub1(&Usb);
USBH_MIDI  Midi1(&Usb);
USBH_MIDI  Midi2(&Usb);

void MIDI_poll();
void doDelay(uint32_t t1, uint32_t t2, uint32_t delayTime);

//If you want handle System Exclusive message, enable this #define otherwise comment out it.
//#define USBH_MIDI_SYSEX_ENABLE

#ifdef USBH_MIDI_SYSEX_ENABLE
//SysEx:
void handle_sysex( byte* sysexmsg, unsigned sizeofsysex) {
  Midi1.SendSysEx(sysexmsg, sizeofsysex);
  Midi2.SendSysEx(sysexmsg, sizeofsysex);
}
#endif
uint8_t msg[4];
void setup()
{

  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.turnThruOff();
#ifdef USBH_MIDI_SYSEX_ENABLE
  MIDI.setHandleSystemExclusive(handle_sysex);
#endif

  if (Usb.Init() == -1) {
    while (1); //halt
  }//if (Usb.Init() == -1...
  delay( 200 );
}

void loop()
{

  Usb.Task();

  uint32_t t1 = (uint32_t)micros();

  if (MIDI.read()) {

    msg[0] = MIDI.getType();

    switch (msg[0]) {
      case midi::ActiveSensing :
        break;
      case midi::SystemExclusive :
        //SysEx is handled by event.
        break;
      default :

        // If this is a channel messages, set the channel number.
        if ( msg[0] < 0xf0 ) {
          // The getchannel() returns 1-16, but the MIDI status byte starts at 0.
          msg[0] |= MIDI.getChannel() - 1;

        }
        msg[1] = MIDI.getData1();
        msg[2] = MIDI.getData2();

        if ( Midi1 )
        {
          Midi1.SendData(msg, 0);

       }

        if ( Midi2 ) {
          Midi2.SendData(msg, 0);

        }

        break;
    }

  }

  if ( Midi1 ) {

    MIDI_poll(Midi1);

  }

  if ( Midi2 ) {

    MIDI_poll(Midi2);

  }

  //delay(1ms)
  //doDelay(t1, (uint32_t)micros(), 1000);
}

// Poll USB MIDI Controler and send to serial MIDI
void MIDI_poll(USBH_MIDI &Midi)
{
  uint8_t size;
#ifdef USBH_MIDI_SYSEX_ENABLE
  uint8_t recvBuf[MIDI_EVENT_PACKET_SIZE];
  uint8_t rcode = 0;     //return code
  uint16_t  rcvd;
  uint8_t   readPtr = 0;

  rcode = Midi.RecvData( &rcvd, recvBuf);

  //data check
  if (rcode != 0) return;
  if ( recvBuf[0] == 0 && recvBuf[1] == 0 && recvBuf[2] == 0 && recvBuf[3] == 0 ) {
    return ;
  }

  uint8_t *p = recvBuf;
  while (readPtr < MIDI_EVENT_PACKET_SIZE)  {
    if (*p == 0 && *(p + 1) == 0) break; //data end

    uint8_t outbuf[3];
    uint8_t rc = Midi.extractSysExData(p, outbuf);
    if ( rc == 0 ) {
      p++;
      size = Midi.lookupMsgSize(*p);
      _MIDI_SERIAL_PORT.write(p, size);
      p += 3;
    } else {
      _MIDI_SERIAL_PORT.write(outbuf, rc);
      p += 4;
    }
#if ENABLE_MIDI_SERIAL_FLUSH
    _MIDI_SERIAL_PORT.flush();
#endif
    readPtr += 4;
  }
#else
  uint8_t outBuf[3];
  do {
    if ( (size = Midi.RecvData(outBuf)) > 0 ) {
      //MIDI Output
      _MIDI_SERIAL_PORT.write(outBuf, size);
#if ENABLE_MIDI_SERIAL_FLUSH
      _MIDI_SERIAL_PORT.flush();
#endif
    }
  } while (size > 0);
#endif
}

// Delay time (max 16383 us)
void doDelay(uint32_t t1, uint32_t t2, uint32_t delayTime)
{
  uint32_t t3;

  t3 = t2 - t1;
  if ( t3 < delayTime ) {
    delayMicroseconds(delayTime - t3);
  }
}
xxxajk commented 3 years ago

cool, I might get a chance to play with this tonight :-)

jhsa commented 3 years ago

Here is some code with 3 devices. My Hub wasn't broken after all, but one of the ports doesn't work on the PC. It works with this device, go figure :) Sometimes, one of the devices still gets out of sync, and I cannot understand why. I must reboot the Host to get it working correctly again. I think it is still the same problem. It would really be nice if the devs would support the STM32F103, as they are way cheaper than water ;) You can get a Blue Pill or a black pill STM32 board for a couple of Dollars.

#include <MIDI.h>
#include <usbh_midi.h>
#include <usbhub.h>

// Satisfy the IDE, which needs to see the include statment in the ino too.
#ifdef dobogusinclude
#include <spi4teensy3.h>
#endif
#include <SPI.h>

//Arduino MIDI library v4.2 compatibility
#ifdef MIDI_CREATE_DEFAULT_INSTANCE
MIDI_CREATE_DEFAULT_INSTANCE();
#endif
#ifdef USBCON
#define _MIDI_SERIAL_PORT Serial1
#else
#define _MIDI_SERIAL_PORT Serial
#endif

// Set to 1 if you want to wait for the Serial MIDI transmission to complete.
// For more information, see https://github.com/felis/USB_Host_Shield_2.0/issues/570
#define ENABLE_MIDI_SERIAL_FLUSH 0

USB Usb;
USBHub  Hub1(&Usb);
USBH_MIDI  Midi1(&Usb);
USBH_MIDI  Midi2(&Usb);
USBH_MIDI  Midi3(&Usb);

void MIDI_poll();
void doDelay(uint32_t t1, uint32_t t2, uint32_t delayTime);

//If you want handle System Exclusive message, enable this #define otherwise comment out it.
//#define USBH_MIDI_SYSEX_ENABLE

#ifdef USBH_MIDI_SYSEX_ENABLE
//SysEx:
void handle_sysex( byte* sysexmsg, unsigned sizeofsysex) {
  Midi1.SendSysEx(sysexmsg, sizeofsysex);
  Midi2.SendSysEx(sysexmsg, sizeofsysex);
  Midi3.SendSysEx(sysexmsg, sizeofsysex);
}
#endif
uint8_t msg[4];
void setup()
{
  // _MIDI_SERIAL_PORT.begin(31250);

  MIDI.begin(MIDI_CHANNEL_OMNI);
  MIDI.turnThruOff();
#ifdef USBH_MIDI_SYSEX_ENABLE
  MIDI.setHandleSystemExclusive(handle_sysex);
#endif

  if (Usb.Init() == -1) {
    while (1); //halt
  }//if (Usb.Init() == -1...
  delay( 200 );
}

void loop()
{

  Usb.Task();

  uint32_t t1 = (uint32_t)micros();

  if (MIDI.read()) {

    msg[0] = MIDI.getType();

    switch (msg[0]) {
      case midi::ActiveSensing :
        break;
      case midi::SystemExclusive :
        //SysEx is handled by event.
        break;
      default :

        // If this is a channel messages, set the channel number.
        if ( msg[0] < 0xf0 ) {
          // The getchannel() returns 1-16, but the MIDI status byte starts at 0.
          msg[0] |= MIDI.getChannel() - 1;

        }
        msg[1] = MIDI.getData1();
        msg[2] = MIDI.getData2();

        if ( Midi1 )
        {
          Midi1.SendData(msg, 0);        
       }

        if ( Midi2 ) {
          Midi2.SendData(msg, 0);
         }

         if ( Midi3 ) {
          Midi3.SendData(msg, 0);
         }

        break;
    }

  }

  if ( Midi1 ) {

    MIDI_poll(Midi1);

  }

  if ( Midi2 ) {

    MIDI_poll(Midi2);

  }

  if ( Midi3 ) {

    MIDI_poll(Midi3);

  }

  //delay(1ms)
  //doDelay(t1, (uint32_t)micros(), 1000);
}

// Poll USB MIDI Controler and send to serial MIDI
void MIDI_poll(USBH_MIDI &Midi)
{
  uint8_t size;
#ifdef USBH_MIDI_SYSEX_ENABLE
  uint8_t recvBuf[MIDI_EVENT_PACKET_SIZE];
  uint8_t rcode = 0;     //return code
  uint16_t  rcvd;
  uint8_t   readPtr = 0;

  rcode = Midi.RecvData( &rcvd, recvBuf);

  //data check
  if (rcode != 0) return;
  if ( recvBuf[0] == 0 && recvBuf[1] == 0 && recvBuf[2] == 0 && recvBuf[3] == 0 ) {
    return ;
  }

  uint8_t *p = recvBuf;
  while (readPtr < MIDI_EVENT_PACKET_SIZE)  {
    if (*p == 0 && *(p + 1) == 0) break; //data end

    uint8_t outbuf[3];
    uint8_t rc = Midi.extractSysExData(p, outbuf);
    if ( rc == 0 ) {
      p++;
      size = Midi.lookupMsgSize(*p);
      _MIDI_SERIAL_PORT.write(p, size);
      p += 3;
    } else {
      _MIDI_SERIAL_PORT.write(outbuf, rc);
      p += 4;
    }
#if ENABLE_MIDI_SERIAL_FLUSH
    _MIDI_SERIAL_PORT.flush();
#endif
    readPtr += 4;
  }
#else
  uint8_t outBuf[3];
  do {
    if ( (size = Midi.RecvData(outBuf)) > 0 ) {
      //MIDI Output
      _MIDI_SERIAL_PORT.write(outBuf, size);
#if ENABLE_MIDI_SERIAL_FLUSH
      _MIDI_SERIAL_PORT.flush();
#endif
    }
  } while (size > 0);
#endif
}

// Delay time (max 16383 us)
void doDelay(uint32_t t1, uint32_t t2, uint32_t delayTime)
{
  uint32_t t3;

  t3 = t2 - t1;
  if ( t3 < delayTime ) {
    delayMicroseconds(delayTime - t3);
  }
}
jhsa commented 3 years ago

And I have made a little video showing where I'm at. I would really like to be able to remove the other board that is used only to pass the midi signal thru :( It doesn't do anything else :( Anyway, thank you so much for your help so far.

https://www.youtube.com/watch?v=xy4WEP2-t50

jhsa commented 3 years ago

Nahh, still not good. After a while Midi 1 gets out of sync and stays always one midi message behind. It seems to affect Midi 1 only. It gets out of sync, and it only recovers after reboot. What could cause this?

I am getting tired of this thing and not very far from giving up.

xxxajk commented 3 years ago

I think you might have overthought the project. What I would do is skip all of the midi-specific protocol parts, and just raw copy everything to each direction.

jhsa commented 3 years ago

I think that wouldn't work.. Serial MIDI and USB MIDI use different baudrates?? Or am I misunderstanding your comment?

xxxajk commented 3 years ago

Don't worry about baud rates. Here's what I would do. Open serial with the standard MIDI baudrate. Check if there is something that came in, and if so, read it in, and send it raw to BOTH UHS MIDI (this is a method in UHS midi, should do what you want!) then check each midi and send that data to serial.

Should work.

jhsa commented 3 years ago

thanks for hanging in there. I could try that, but there is a problem. As i said I am a beginner. Do you have an example to put me on the right track? I did try raw MIDI (No Library), but it didn't work. perhaps I did something wrong. Thanks again. Oh, and I found an EP32 board that I am using on another project.. Do I still need the Host shield if i try with it? or can it handle USB Host directly?

xxxajk commented 3 years ago

Don't bother with the ESP32, especially since you are a beginner. You just need to do Serial.read() and Serial.write(), there are examples for this built-in on the Arduino IDE. It's very simple to do, See under Communication SerialPassthrough example, and just change it to do midi with the proper baud rate... 31250 It's honestly as simple as that :-)

jhsa commented 3 years ago

So, if I understand, that instead of:

if (MIDI.read()) {

    msg[0] = MIDI.getType();

    switch (msg[0]) {
      case midi::ActiveSensing :
        break;
      case midi::SystemExclusive :
        //SysEx is handled by event.
        break;
      default :

        // If this is a channel messages, set the channel number.
        if ( msg[0] < 0xf0 ) {
          // The getchannel() returns 1-16, but the MIDI status byte starts at 0.
          msg[0] |= MIDI.getChannel() - 1;

        }
        msg[1] = MIDI.getData1();
        msg[2] = MIDI.getData2();

        if ( Midi1 )
        {
          Midi1.SendData(msg, 0);        
       }

        if ( Midi2 ) {
          Midi2.SendData(msg, 0);
         }

         if ( Midi3 ) {
          Midi3.SendData(msg, 0);
         }

        break;
    }

  }

Is this what you mean? by the way, I did a project with an ESP32. And i am also quite familiar with the Blue Pill STM32 board. Shame that the blue pill is not supported by the Host library :(

xxxajk commented 3 years ago

Close. when a midi message comes in from the midi instance, just send the entire message as-is. I'll put together some stuff here, I got a MIDI keyboard, and I can always program an arm chip I have here as MIDI output. I'm working on a bunch of stuff here currently (work, producing hardware) but will have a chance in a few hours to do this.

jhsa commented 3 years ago

Thank you so much.. I am just about to check the example you mentioned. Although I suspect that there might also be some problem with the USB Host library or the MIDI library. The reason I want to try the STM32F103 chip I have, or the EP32 is that they are way faster than the Arduino Pro-Mini..

xxxajk commented 3 years ago

I think it would be better to use a Teensy, you can even get them with host built in, and use UHS3, however right now UHS3 is a touch broken, and I have to fix that yet. :-/

jhsa commented 3 years ago

Yeah a teensy would definitely be better. But I am a bit broke at the moment (musician in a Pandemic :( ). I already have some STM32 boards and Pro Minis :) I have a black pill STM32F103, Maple board, and Blue Pills. I had an F401 that I used on another project already. You mean something like this?

void loop() {

  if (Serial.available()) {      // If anything comes in Serial (USB),

if ( Midi1 )   {
          Midi1.SendData(Serial.read(), 0);

  }
}

}
xxxajk commented 3 years ago

Again, I'll have an example after I'm done with what I have to do here. This might be a good "example" for the midi driver.

jhsa commented 3 years ago

Ok thanks.. i will wait then. Anyway, the code on my last post didn't work :(

xxxajk commented 3 years ago

Right, it needs just a touch of post-processing, probably some routing? I'm not all that familiar with midi, or what you are actually attempting to do.