Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Sketch for MIDI Using Interrupts #15

Open
wants to merge 11 commits into
base: master
Choose a base branch
from

Conversation

studiohsoftware
Copy link

Here is an example sketch that uses interrupts instead of polling to read MIDI data from a single device.

The sketch requires only one small addition to usbh_midi.h to allow the sketch to fetch the device EP address.

Tested with Korg nanoKey2 and SQ-1.

The sketch illustrates how to use interrupts and might lead to further development.

Status byte is available from interrupt and is passed into method.
This version can't use hub, but is more reliable.
Need to allocate EP 2 and up to Midi
devices when present. Not sure how to
do that yet.
Interrupts mean host needs a fixed EP address.
MIDI device also needs fixed EP address.
Those two EP addresses have to align.
Hub also has EP address, and will conflict
with device EP address. Polling the hub
using the current approach causes the
pipe to switch to the hub and breaks
events on the MIDI device pipe, so that
is not an option if the EP on the device
is the same as the EP on the hub.
@dkts2000
Copy link

Ok. Thanks. I'll try that.

Fixed bug in handleBank1 that always looked at bufBk1[0] instead of bufBk1[i].
@dkts2000
Copy link

dkts2000 commented Jan 13, 2021

On second thought, maybe neither is the problem.

Stop looking for a byte greater than zero

I don't read the dataString inside the greater than zero check. At the end of the handleBank0(uint32_t epAddr) and handleBank1(uint32_t epAddr) i call a function that reads rcvd bytes from the bufBk0[i] or bufBk1[i] (depending who called it).

Then i check these bytes by chunks of 4 (and this works right until the missing bytes).
The bytes i read are 04 F0 52 00 04 64 28 02 04 11 00 00 04 10 73 10 04 01 08 2E 04 00 21 00 04 0C 00 00 04 00 00 00 which at this point is what i expect

I receive them in this order: [ rcvd = uhd_byte_count0(epAddr) or uhd_byte_count1(epAddr) respectively ]

04 F0 52 00 -> 04=3 bytes following, F0=start of sysex message (1byte), 52 00 2 more bytes (rcvd=4)
04 64 28 02 -> 04=3 bytes following (rcvd=4)
04 11 00 00 -> 04=3 bytes following (rcvd=4)
04 10 73 10 -> 04=3 bytes following (rcvd=4)
04 01 08 2E -> 04=3 bytes following (rcvd=4)
04 00 21 00 -> 04=3 bytes following (rcvd=4)
04 0C 00 00 04 00 00 00 00 00 00 00 ... 00 -> 04=3 bytes following + 04=3 bytes following + 56 bytes 00 which are not part of the expected sysex (rcvd=64)
and next is the missing part.

I don't know if this is easy to read. I'll try to provide the working part of the code.

and stop reading in chunks of four
bytes. Just read all 64 bytes every time

if i read one byte by one, i'll end up again checking 4 bytes at a time as this is the way a sysex message works.

Any other idea? I hope you have the patience to read it! Thanks.

@studiohsoftware
Copy link
Author

studiohsoftware commented Jan 13, 2021 via email

@dkts2000
Copy link

dkts2000 commented Jan 13, 2021

When i get (04 0C 00 00) (04 00 00 00) 00 00 00 00 ... 00 i know that i have 2 chunks of 4 bytes and i discard the rest zero bytes because they are not valid sysex headers. (i expect 04, 05, 06 or 07 as a valid sysex header of a chunk of 4 bytes, not zero).
After that part it seems to lose 42 bytes and then starts again receiving valid sysex bytes from the expected message.

The second time it runs it also misses 42 bytes but in another location.
And this happens exactly the same every time. It's not random misses.

@studiohsoftware
Copy link
Author

I wonder if you are trying to transfer more than 64 bytes.

@dkts2000
Copy link

dkts2000 commented Jan 13, 2021

You mean the device responds with a message larger than 64 bytes?

The device (a multieffects pedal atually) has only USB. If i send a specific sysex command (by using amidi .... --send-hex="....") it responds with the expected sysex message which is 134 bytes. It is also listed as a MIDI device in amidi -l
So i know that it can communicate right using sysex.

Also by using this code i have received a 15 bytes sysex message as a response to a sysex command that i had send to it

@studiohsoftware
Copy link
Author

studiohsoftware commented Jan 13, 2021 via email

@dkts2000
Copy link

ok. I'll check. Thanks.

@studiohsoftware
Copy link
Author

studiohsoftware commented Jan 13, 2021

Note that my modification here to the library is minimal. It just adds a way for the sketch to ask for the pipe endpoint so it can set up an interrupt on it.

This example sketch leverages the library to poll the device for initialization (enumeration). The library therefore does the work of configuring the SAMD21 pipe. Once the pipe is configured, the sketch does some minimal touch up on the pipe config, but does not specify the pipe size. Once the interrupt is configured, the library is no longer used, and the sketch just depends on receiving interrupts from the SAMD21 and receiving the data directly from the pipe endpoint.

The only part of this interrupt based approach that mentions pipe size is in the buffer declarations here, which are set to 64 in the sketch:
//SAMD21 datasheet pg 836. ADDR location needs to be aligned.
uint8_t bufBk0[64] attribute ((aligned (4))); //Bank0
uint8_t bufBk1[64] attribute ((aligned (4))); //Bank1

I am not super clear on how the library handles Sysex, since I did not try it, but you can see in the library that usbh_midi.h has the following declarations.
#define MIDI_EVENT_PACKET_SIZE 64
#define MIDI_MAX_SYSEX_SIZE 256

As near as I can tell, the SAMD21 does not impose a 64 byte limit. I am not sure if this library does, but that SYSEX define is encouraging. It looks like USB full speed has a max packet size on bulk endpoints of 64 though.

You might try bumping up the buffers in the sketch to 256 and try it.

The only other thing I can think of is that there is more than one IN endpoint set up when this device enumerates, and my function Midi.GetEpAddress() is not returning the one you want. But I don't think that's true because you are getting most of the data. We seem to have the correct endpoint.

@dkts2000
Copy link

I tried it with 128, 256 byte buffers but no good. The receiving bytes are always the same. Basically whatever i tried leads to exactly the same result. It somehow loses, or better it doesn't get some pieces of the message. The weird thing is that the first time it misses 42 bytes here and for any other run it misses 42 bytes in a later part. Always the same.

@studiohsoftware
Copy link
Author

You can try the using one of the other examples in this library and see if it works. Maybe USBH_MIDI_dump.ino. In that case, the sketch calls Midi.RecvData to get the data from the device. That call will construct a pipe, and set it up to receive data. The pipe is constructed every time you make the call. This leads to dropped data, but it may be more robust if your device is requiring a new pipe in between transfers.

Not sure if you got the print statements working. It's a really a must to troubleshoot. For example. your device might be disconnecting and reconnecting in the middle of the Sysex message. You would see that with print statements. The interrupt sketch might have trouble in that case, not sure.

@dkts2000
Copy link

I had tried the print statements and was getting the same results. But i tested something else. I stopped calling a function (that is checking the received bytes) from withing handleBank0 and handleBank1 and commented most of the debugging prints i used. I left only your prints and the result is different. It now starts getting more bytes than the previous time and it now loses 21 bytes (half of the previously missed number, if that means something). This behavior applies the first time it runs. The second time (without reset) it misses a lot of bytes. And after that it keeps cycling these 2 cases.

That means that the problem might be some kind of timing?

The print statements i get:

21:56:40.848 -> Connected
21:56:40.848 -> 0:0:0:|STATUS0:0|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:0|BYTECOUNT1:0|TRCPT0:0|TRCPT1:0|READY0:0|READY1:0|CURRBK:0|TOGGLE:0|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:0|
21:56:44.300 -> Pipe Started
21:56:44.300 -> Dump:ADDR0:200004F4:ADDR1:20000534:C:4:0
21:56:53.472 -> 8:5:55:|STATUS0:0|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:4|BYTECOUNT1:0|TRCPT0:1|TRCPT1:0|READY0:1|READY1:0|CURRBK:1|TOGGLE:1|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:10|
21:56:53.506 -> 4f0520|
21:56:53.506 -> 8:6:90:|STATUS0:0|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:4|BYTECOUNT1:4|TRCPT0:0|TRCPT1:1|READY0:0|READY1:1|CURRBK:0|TOGGLE:0|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:10|
21:56:53.506 -> 464282|
21:56:53.506 -> 8:5:55:|STATUS0:0|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:4|BYTECOUNT1:4|TRCPT0:1|TRCPT1:0|READY0:1|READY1:0|CURRBK:1|TOGGLE:1|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:10|
21:56:53.637 -> 41100|
21:56:53.637 -> 8:6:90:|STATUS0:0|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:4|BYTECOUNT1:4|TRCPT0:0|TRCPT1:1|READY0:0|READY1:1|CURRBK:0|TOGGLE:0|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:10|
21:56:53.637 -> 4107310|
21:56:53.637 -> 8:5:55:|STATUS0:0|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:4|BYTECOUNT1:4|TRCPT0:1|TRCPT1:0|READY0:1|READY1:0|CURRBK:1|TOGGLE:1|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:10|
21:56:53.637 -> 4182e|
21:56:53.637 -> 8:6:90:|STATUS0:0|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:4|BYTECOUNT1:4|TRCPT0:0|TRCPT1:1|READY0:0|READY1:1|CURRBK:0|TOGGLE:0|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:10|
21:56:53.637 -> 40210|
21:56:53.637 -> 8:5:55:|STATUS0:0|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:40|BYTECOUNT1:4|TRCPT0:1|TRCPT1:0|READY0:1|READY1:0|CURRBK:1|TOGGLE:1|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:10|
21:56:53.637 -> 4c00|4000|4001|4402|4c468|41210|42c00|4000|4000|40021|40052|47280|40640|446100|4000|4000|
21:56:53.637 -> 8:6:90:|STATUS0:0|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:40|BYTECOUNT1:40|TRCPT0:0|TRCPT1:1|READY0:0|READY1:1|CURRBK:0|TOGGLE:0|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:10|
21:56:53.637 -> 4010|4000|
21:56:53.637 -> 8:5:55:|STATUS0:0|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:1C|BYTECOUNT1:40|TRCPT0:1|TRCPT1:0|READY0:1|READY1:0|CURRBK:1|TOGGLE:1|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:10|
21:56:53.637 -> 40701|4000|404475|465720|46d6574|4652020|60f70|
22:02:13.742 -> 208:C:15:|STATUS0:28|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:1C|BYTECOUNT1:40|TRCPT0:0|TRCPT1:0|READY0:0|READY1:0|CURRBK:1|TOGGLE:1|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:0|
22:02:13.742 -> 200:C:15:|STATUS0:28|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:1C|BYTECOUNT1:40|TRCPT0:0|TRCPT1:0|READY0:0|READY1:0|CURRBK:1|TOGGLE:1|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:0|
22:02:13.742 -> Disconnected
22:02:13.742 -> 0:C:15:|STATUS0:28|STATUS1:0|STATUS_BK0:0|STATUS_BK1:0|BYTECOUNT0:1C|BYTECOUNT1:40|TRCPT0:0|TRCPT1:0|READY0:0|READY1:0|CURRBK:1|TOGGLE:1|TOGGLE_ERROR0:0|TOGGLE_ERROR1:0|NAK:0|INTSUMMARY:0|

@studiohsoftware
Copy link
Author

studiohsoftware commented Jan 14, 2021

So the last two lines there before the disconnect are where the data is dropped? What caused that disconnect?

But yes if you are doing less work now within the handler, and as a result you are getting more data, it suggests that the device is trying to send more data than the SAMD21 can receive. Perhaps while you are processing the data, the device overflows or gives up.

I just noticed as well that almost immediately you see that both banks always have data. I would consider this a bad sign. I think it means the device is filling up the banks faster than you can drain them. A healthy sign would be one of the banks empty every time you handle the interrupt. It would mean that the handling is not having trouble keeping up. Suddenly not so sure about this theory though because TRCPT flags are not both 1. It's been a while since I have looked at the debug output during my successful runs. I thought that the byte count on one of the banks was always zero.

Notice this option in the sketch.
USB->HOST.HostPipe[epAddr].BINTERVAL.reg = 0x01;//Zero here caused bus resets.

This is the interval that the SAMD21 polls the device for data. Counter intuitive perhaps, but maybe an increase would improve reliability. Perhaps telling the device to slow down a little (by polling it less frequently) you will give your sketch more time to process the data from the banks.

For bulk type, the datasheet pg 824 says this parameter means 1 ms to 255 ms.

@dkts2000
Copy link

I got it working by adding

        usb_pipe_table[epAddr].HostDescBank[0].PCKSIZE.bit.SIZE     = USB_PCKSIZE_SIZE_256_BYTES;
        usb_pipe_table[epAddr].HostDescBank[1].PCKSIZE.bit.SIZE     = USB_PCKSIZE_SIZE_256_BYTES; 

instead of USB_PCKSIZE_SIZE_64_BYTES;

in the if (doPipeConfig) { section of loop.
I saw it in void pipeConfig(uint32_t addr, uint32_t epAddr) { you had commented out. I don't fully understand what is going on during pipe configuration but this seems to solve the problem. Now i get all 134 bytes every time.
It means the pipe is "larger" so all bytes are now waiting to be received and they do not flood?

By the way thanks for your interest, it kept me trying.

@studiohsoftware
Copy link
Author

The pipeConfig function was pieced together from the calls I found within the library that set up the pipe. At first I thought I needed to have it to set up the pipe because I was going to abandon the library, but later I realized that I could use the library to enumerate the USB device and set up the pipe. So I no longer needed pipeConfig, but I left it commented out, because it contained a lot of interesting methods that I thought someone might be able to use. So your case is exactly what I had in mind. The commented out code is a kind of reserve toolbox that you can use to try things.

I am still a little confused about what is happening. I thought that the bulk endpoints had to be 64. Maybe I am confusing pipe size and endpoint size.

Anyway, congrats. Thanks for picking up this code and taking it further.

@MickGyver
Copy link

MickGyver commented Oct 24, 2021

Anyway, congrats. Thanks for picking up this code and taking it further.

Thanks for trying to figure this one out! Did you ever try this out "for real"? I'm having problems with missed events if I press more than one key at the same time, seems like this code can reliably only handle one midi event at a time. I'm experimenting with an AKAI MPK Mini.

@studiohsoftware
Copy link
Author

USB is serial, and the events will be transferred in series, not parallel. It is not possible to have more than one event at a time communicated over USB. Of course it is possible to press more than one key at a time, but the keyboard must be capable of detecting that situation and submitting individual NOTE ON events sequentially to the USB interface.

@MickGyver
Copy link

USB is serial, and the events will be transferred in series, not parallel. It is not possible to have more than one event at a time communicated over USB. Of course it is possible to press more than one key at a time, but the keyboard must be capable of detecting that situation and submitting individual NOTE ON events sequentially to the USB interface.

Thank you for the reply! Sorry, I didn't properly explain the problem. What I meant with the code only being able to handle one MIDI event at a time reliably was that as soon as I press or release two or more keys at the same time there is a big chance that some note on/off messages are missed and I can't really figure out why (same problem as with the non-interrupt based examples I have seen). The keyboard (AKAI MPK Mini) works perfectly when connected to a computer.

@studiohsoftware
Copy link
Author

I have used pipe interrupts for fast MIDI clock and polyphony without any message loss. My guess is that the problem is elsewhere. You might try the same test with a different host to check.

@MickGyver
Copy link

I have used pipe interrupts for fast MIDI clock and polyphony without any message loss. My guess is that the problem is elsewhere. You might try the same test with a different host to check.

Thanks, yes I will do some further investigation. I have this problem with the interrupt example in your fork (and this pull request) so I was wondering if you have experienced a similar issue.

@studiohsoftware
Copy link
Author

No, I have not seen any message loss problems using the interrupts approach. It's seems to be very reliable.

@MickGyver
Copy link

My guess is that the problem is elsewhere.

Thanks for your replies and yes you were right, the problem was on the receiving end. Your interrupt based code seems to work great, thanks a bunch for sharing it! :)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants