Closed doctea closed 11 months ago
hi @doctea, you're probrably getting a race condition accessing MIDI interface over concurrent functions ClockOut96PPQN and loop.
uClock has a atomic macro for those scenarios, please use it: ATOMIC(X)
dont use ATOMIC over big code blocks(more than one call)! you can mess hardly with your clock signal, instead try a ATOMIC(midix.*) per call, in that way you dont lock processor on the loop() to the point of lossing the next tick call of interruption to clock your setup.
Try to avoid process things inside ATOMIC(X), for example: so instead of do ATOMIC over this call that will runs ramdom twice, process random outside and use a variable:
//midi1.sendControlChange(random(127), random(127), 1);
uint8_t random_a = random(127);
uint8_t random_b = random(127);
ATOMIC(midi1.sendControlChange(random_a, random_b, 1))
ATOMIC always should be use carefully otherwise you can mess hardly with your clock signal.
Thanks @midilab for the fast response! I'll try this out to see if a more granular ATOMIC(X) approach helps.
Hm, so doing like below instead still freezes up in the same way:-
#define ATOMIC(X) noInterrupts(); X; interrupts();
// ...
uint8_t random_a = random(127);
uint8_t random_b = random(127);
ATOMIC(midi1.sendControlChange(random_a, random_b, 1) );
ATOMIC(midi2.sendControlChange(random_a, random_b, 1) );
ATOMIC(midi3.sendControlChange(random_a, random_b, 1) );
ATOMIC(midi4.sendControlChange(random_a, random_b, 1) );
ATOMIC(midi5.sendControlChange(random_a, random_b, 1) );
ATOMIC(midi6.sendControlChange(random_a, random_b, 1) );
ATOMIC(midi7.sendControlChange(random_a, random_b, 1) );
ATOMIC(midi8.sendControlChange(random_a, random_b, 1) );
FWIW in my full project everything works brilliantly and without much clock jitter, even with quite large blocks of ATOMIC_BLOCKs during loop(), the clock is very accurate. Even sending to serial MIDI DIN devices with the same logic seems to work fine. As far as I can tell it is only when the target is a USB MIDI device that these freezes happen.
I thought it could be related to how USBHost_t36 MIDIDeviceBase::sendControlChange() function calls MIDIDeviceBase::write_packed() (https://github.com/PaulStoffregen/USBHost_t36/blob/b6f94f7605b3f6cb57a10e41cab51b25b47f7736/midi.cpp#L292), which will mean that interrupts get turned off and back on again even within the supposedly ATOMIC() operation. I've tinkered with how the interrupts are disabled/restored in that function, without any luck.
Do you have any further insight as to what could be happening and how a solution could be found? Might this be something that the USBHost_t36 maintainer might be able to help with?
Thanks
I've restructured my full app so that it now sends all CCs during the ClockOut96PPQN callback, instead of during loop(). Its been running fine with no freeze, and no impact that I can notice on timing or performance. This is an OK workaround for me for now, but it would be great long term if we could figure out what is causing this freeze so that it is possible to also send USB MIDI from outside of the callback.
Thanks for all your help so far and for this very handy library!
Unrelated to this issue, but I wondered if you had seen my post on https://github.com/midilab/uClock/issues/10 about whether it could be possible to make the library work on RP2040/Pico?
Thanks again
Hmm - some further exploration and I /might/ have figured out a patch to the USBHost_t36 library that solves this -- I won't have chance to more thoroughly test it until the coming Tuesday though -- will report back with more info when I've had a chance to verify what's going on!
OK, so my patched USBHost_t36 library at https://github.com/doctea/USBHost_t36/tree/merge-interrupt-fix seems to solve this.
The important change here is that interrupts are only re-enabled when sending USB MIDI data if they were already enabled in the first place. This prevents interrupts being unexpectedly turned on when they shouldn't be and (I presume) causing some kind of re-entrant problem?
I believe this solves the freeze in the test sketch I posted above.
The branch also contains some other changes that work around freezes and crashes that I see in my full app.
great! thanks for testing and provide those information, i will try to incorporate to troubleshooting doc.
Hi! I have uClock working well in my Teensy USB MIDI project, except if I try to sendControlChange from inside loop(). If I do that then it doesn't last long before the program freezes.
I think its hitting some kind of race condition with USB, as Teensy CrashReport doesn't see to get triggered, and in certain cases its possible to avoid the freeze - or to recover from the frozen state - by connecting the USB Serial monitor. (I've only been able to replicate this in my 'full' project though.)
I'm using utils/atomic.h ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {}, which as far as I understand should handle enabling and disabling interrupts, and prevent concurrency related crashes -- wrapping sections of my loop() code in this macro has solved every crash so far except this one. I've also tried using __disable_irqs and __enable_irqs instead, but I get the same freezes regardless.
My use case here is that there are CCs that I need to send separately from the main clock ticks.
I've made a simple test sketch replicating the problem, attached here. Hopefully this will make it easy to replicate the problem. Defining SEND_CCS_IN_LOOP to 'true' will cause the Teensy to freeze within not much time, a minute at most. Defining it to 'false' seems to run indefinitely.
My test setup is with the Teensy 4.1 USB Host port connected to two USB hubs, and with multiple USB MIDI devices attached to the hubs. It doesn't seem to matter much which devices are connected. Curious whether the same behaviour is replicated on other hardware.
Any ideas what could be causing this, and hopefully how to avoid it, please? :)