[sdiy] Techniques for Multiplying MIDI Clock frequency?

Brian Willoughby brianw at audiobanshee.com
Sat Dec 25 07:23:44 CET 2021


On Dec 24, 2021, at 18:33, Matthew Skala wrote:
> On Fri, 24 Dec 2021, Brian Willoughby wrote:
>> On Dec 24, 2021, at 08:40, mskala at northcoastsynthesis.com wrote:
>>> That was my own PIC24 host code, with no operating system as such.  The
>>> device on the other end of the connection was an Akai "MPK mini" USB-MIDI
>>> keyboard.
>> 
>> Ah. I see. So, when you talk about throttling the rate you're talking
>> about Host code, which is totally within the USB spec, I suppose (unless
>> they require lots of polling somehow).
> 
> Sure - I don't think the host is ever *required* to poll a bulk endpoint,
> let alone more than once per frame.

Since you're writing the Host code, it seems like all you have to do is ensure that all Devices are polled enough to allow sufficient bandwidth. If there is bandwidth to spare, it seems like there would be no reason to throttle Bulk packets. Unless the Host has something else to do with the USB, then it might as well "over" poll the Bulk Device endpoints.

Once you get to the point where you can support a Hub with a mouse, (typing) keyboard, *and* MIDI controller, then I think your Host code will have to make sure to service all Devices according to their minimum requirements. That said, you may find that you get better performance if you poll the Bulk endpoints for USB-MIDI as much as possible, so long as other duties aren't neglected.

>> Maybe not. The PIC18 that I worked with is limited to Device only. It
>> cannot be a Host. The electronics would be vastly different. I suppose
>> it's possible that the PIC24 peripheral register set could be a strict
>> superset, but I assume they're very different peripherals due to the
>> additional capabilities of the PIC24.
> 
> Interesting.  I several times found answers to questions in the PIC18
> discussion forums that seemed applicable to the PIC24 also, but I guess
> that could simply be because of similar design philosophy, and
> similarities in the driver software at the higher level.

As I dug deeper into the various USB stacks, I noticed that the vast majority of the design is taken directly from the USB specifications. In that sense, I would expect many different USB stacks could be supported with the same Q&A. As I mentioned, Apple actually helped me with my Isochronous Device, without any idea of which processor I was using or which firmware stack.

I've written USB firmware for four product families. Two for PIC18, one for TMS320VC5506, and Host firmware for the TM4C1294. Unfortunately, every one of them has been a different Device Class, so I didn't get a chance to see how much there was in common between the different USB stacks when solving the exact same problem. Of course, the two PIC18 firmwares used the same USB stack, albeit different releases, but the second product was not USB-MIDI.

>> One mystery I've never dug in to is this: What happens when a USB Host
>> schedules a packet for a USB Device and that device responds really
>> slowly (due to poor firmware optimization)? I know that if the delay is
> 
> I think this is in section 7.1.18 of the USB 2.0 standard.  The device is
> expected to reply pretty much immediately, if it's going to reply at all.
> For low and full speed, the maximum allowed is either 6.5 or 7.5 bit times
> depending on whether the device has a detachable cable.  Many more bit
> times, but still a small amount of real time, for high speed.  If the
> device takes longer than allowed, it runs the risk of colliding with the
> host's next packet, and would be better off to keep silent (which *will*
> be an error).

Thanks. This makes sense. I suppose I'm only thinking about Control. Custom Vendor extensions to Control transfers is the only place where I've coded any sort of just-in-time response. Everything else has been prepared buffers of data for future packets.

>> The answer is partly made evident because most USB Device firmware
>> stacks require the code to set up the complete data for a packet in
>> advance, so that the hardware interrupt can immediately enable DMA to
>> transfer the data faster than the processor could handle in code alone.
> 
> Bearing in mind that the signalling is synchronous, there can't be a delay
> in the middle of a packet without corruption.  It's not like a UART
> connection that resynchronizes on every byte and could potentially have
> arbitrary pauses between bytes.  Once the device starts sending a packet,
> then whatever voltages exist on the bus over the next however many
> microseconds *are* the contents of the packet whether the device was
> driving those voltages or not.  And if it doesn't start sending the packet
> at all when the host was expecting it to, I guess that depends on the host
> but it seems like a standards-compliant host should have given up almost
> immediately.  The whole bus can't wait for one device.  Devices that
> cannot send data on schedule are supposed to send NAKs on schedule, not
> send nothing or break the schedule.

I should have said something like "transfer" instead of packet. The Control transactions involve multiple bidirectional packets. Each packet is consistently timed, from beginning to end, but between the packets of a Control transfer there might be gaps.

As far as I recall, every kind of endpoint *besides* Control has involved collecting buffered data in advance, sending it to the USB stack as a whole packet, and then allowing the USB peripheral to time the transfer when the Host asks for it, based on Descriptors.

>> However, Control endpoints have Host query commands that switch between
>> OUT and IN during a transaction, and I've wondered what would happen if
>> firmware grabs a piece of data for the Control endpoint from a slow
>> peripheral, and thus delays the Control endpoint timing.
> 
> That seems like it should be a delay at a different level.  The control
> in transfer looks something like this:
> 
> host        device
> 
> SETUP
> DATA
>            ACK
> IN
>            DATA
> ACK
> OUT
> DATA (0 length)
>            ACK
> 
> If the device needs more time to prepare the data for the IN, it is
> allowed to send NAK to indicate it's not ready, but the NAKs themselves
> have to be sent promptly:
> 
> host        device
> 
> SETUP
> DATA
>            ACK
> IN
>            NAK
> IN
>            NAK
> IN
>            DATA
> ACK
> OUT
> DATA (0 length)
>            ACK
> 
> I would expect most devices nowadays to handle this with hardware
> that will automatically send the NAK if the firmware has not yet
> instructed it to give a different response.  7.5 bit times at full speed
> is not a lot of time for a microcontroller to handle an interrupt and
> decide whether to start sending data.

In my case, I coded the firmware to send ACK, NAK, or STALL, depending upon the request in SETUP. I use the Vendor Type in the Control Request plus the 8-bit Request, 16-bit Index, and 16-bit Value to allow a huge array of queries. Values larger than 16 bits can be handled with the 16-bit Length (although I've never gone beyond 32-bit or maybe 64-bit). That's for writing values from Host to Device. For reading values, the Length must always specify the size of the value to be returned.

I use STALL to confirm that a given Request and/or Index is completely invalid. Technically, this shouldn't be necessary if the Host application is based on the latest product protocol documentation, but when other developers are writing Host software it helps to be able to force the OS USB driver to return a meaningful error for bad requests, rather than mysteriously fail to provide meaningful data.

I can't recall whether NAK is used, but I do recall that some Values come from EEPROM storage that's slow, and I worried that reading the value might take too long for USB.

Control transfers, a.k.a. Device Requests, seem to be the only USB transfers where one packet selects the data that will be returned in the very next packet. So, I was worried that a slow EEPROM access might be "too slow." I can't recall whether I ended up using NAK and some sort of asynchronous technique. I seem to recall that the PIC18 USB stack has some sort of callback mechanism so that the stack can call the custom code when it needs the value.

e.g. A lot of the product settings were stored in EEPROM. Device Requests allow writing and reading those values from the Host application. Writing is easy, because the firmware can cache the value to be written and handle the actual EEPROM write after the Control transfer is completed. Reading is slightly challenging because the Device Request specifies which setting is desired by using the Index, and then the firmware has to read the value from the EEPROM using slower-than-RAM accesses (the PIC18 model is archaic, to say the least). Anyway, I guess it doesn't matter because the product is done, and the final firmware seems to work.

Brian Willoughby

p.s. When your USB-MIDI Host synth is ready to be announced, please drop me an email ... or just let this whole list know.





More information about the Synth-diy mailing list