Closed cyberdevnet closed 1 year ago
Because HandleSendMIDIClock
is called from interrupt so you can't MIDI_Serial.sendRealTime(midi::Clock);
.
One of the best ways to fix this is to use xSemaphore. With your code that should be some things like this:
#include "Arduino.h"
#include <HardwareSerial.h>
#include <MIDI.h>
#include <uClock.h>
#include <freertos/semphr.h>
#define TXD2 17
#define RXD2 18
#define LED 2
static SemaphoreHandle_t xSemaphore = NULL;
static BaseType_t xHigherPriorityTaskWoken = pdFALSE;
byte clockCounter = 0; // Count the MIDI clock pulses, 24 pulses per quarter note
bool midiClockLedOn = false;
MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, MIDI_Serial);
// The callback function which will be called by Clock each Pulse of 96PPQN clock resolution.
void ClockOut96PPQN(uint32_t tick)
{
// Send MIDI_CLOCK to external gears
HandleSendMIDIClock();
}
void setup()
{
xSemaphore = xSemaphoreCreateBinary();
Serial.begin(115200); // setup HW serial for debug communication
Serial2.begin(31250, SERIAL_8N1, RXD2, TXD2); // setup Serial2 for MIDI control
pinMode(LED, OUTPUT); // set the LED pin as an output
digitalWrite(LED, LOW); // turn off the LED
MIDI_Serial.begin(MIDI_CHANNEL_OMNI); // Initiate MIDI_Serial communications, listen to all channels
MIDI_Serial.turnThruOff();
// Inits the clock
uClock.init();
// Set the callback function for the clock output to send MIDI Sync message.
uClock.setClock96PPQNOutput(ClockOut96PPQN);
// Set the clock BPM to 126 BPM
uClock.setTempo(126);
// Starts the clock, tick-tac-tick-tac...
uClock.start();
};
void loop()
{
// Empty loop, no need to execute anything repeatedly.
if (xSemaphoreTake(xSemaphore, portMAX_DELAY))
{
MIDI_Serial.sendRealTime(midi::Clock);
xHigherPriorityTaskWoken = pdFALSE;
}
};
void HandleSendMIDIClock()
{
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
// Increment the clock counter, wrap around after 24 pulses
clockCounter = (clockCounter + 1) & 0x0F;
handle_bpm_led(clockCounter); // leds work fine
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void handle_bpm_led(uint32_t tick)
{
// BPM led indicator
if (tick == 0 || tick % 24 == 0)
{ // Each quarter note
midiClockLedOn = true;
digitalWrite(LED, HIGH);
}
else if (midiClockLedOn && tick % 24 == 6)
{ // Turn off the LED after 6 ticks (1/16th note duration)
midiClockLedOn = false;
digitalWrite(LED, LOW);
}
}
@animevietsub just tested with your changes and it works pretty well out of the box, thanks alot!
But now I'm facing other backtrace now and then:
assert failed: xQueueSemaphoreTake queue.c:1554 (!( ( xTaskGetSchedulerState() == ( ( BaseType_t ) 0 ) ) && ( xTicksToWait != 0 ) ))
Backtrace: 0x403780d6:0x3fc9a990 0x40380595:0x3fc9a9b0 0x40386b25:0x3fc9a9d0 0x4038157e:0x3fc9ab00 0x42038cb3:0x3fc9ab40 0x42038c75:0x3fc9ab60 0x420383f2:0x3fc9abf0 0x420155b3:0x3fc9ac10 0x42002c59:0x3fc9ac30 0x42002c6b:0x3fc9ac50 0x42036715:0x3fc9ac70 0x420367a7:0x3fc9ac90 0x4037e66f:0x3fc9acb0 0x403761d8:0x3fc9acd0 0x4037962d:0x3fc9acf0 0x400559dd:0x3fceb920 |<-CORRUPTED
let's dig into this, but thanks again!
Can you send me your current code? Which ESP32 boards are you compiling?
I try in my ESP32 DEV KIT and it works.
Look at your backtrace, you can't find the problem if you don't have EspExceptionDecoder tools or you can try to build on platformio, which has monitor_filters = esp32_exception_decoder
The code I posted at the beginning was just a test sketch, the main code is actually much, much bigger, board is ESP32-S3, this is the related code now:
#include "Arduino.h"
#include <HardwareSerial.h>
#include <MIDI.h>
#include <freertos/semphr.h>
#include <uClock.h>
#define TXD2 17
#define RXD2 18
#define LED 2
int TapTempoBPM = 120;
static SemaphoreHandle_t xSemaphore = NULL;
static BaseType_t xHigherPriorityTaskWoken = pdFALSE;
byte clockCounter = 0; // Count the MIDI clock pulses, 24 pulses per quarter note
bool midiClockLedOn = false;
MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, MIDI_Serial);
// The callback function which will be called by Clock each Pulse of 96PPQN clock resolution.
void ClockOut96PPQN(uint32_t tick)
{
// Send MIDI_CLOCK to external gears
HandleSendMIDIClock();
}
void setup()
{
xSemaphore = xSemaphoreCreateBinary();
Serial.begin(115200); // setup HW serial for debug communication
Serial2.begin(31250, SERIAL_8N1, RXD2, TXD2); // setup Serial2 for MIDI control
pinMode(LED_BUILTIN, OUTPUT); // set the LED pin as an output
digitalWrite(LED_BUILTIN, LOW); // turn off the LED
MIDI.setHandleControlChange(handleControlChangeIN); // BLEMIDI CC Callback
MIDI.setHandleProgramChange(handleProgramChangeIN); // BLEMIDI PC Callback
MIDI.setHandleClock(handleMidiClockIn); // BLEMIDI Clock Callback
MIDI.begin(MIDI_CHANNEL_OMNI); // Initiate BLEMIDI communications, listen to all channels
MIDI_Serial.setHandleControlChange(handleControlChangeIN); // MIDI_Serial CC Callback
MIDI_Serial.setHandleProgramChange(handleProgramChangeIN); // MIDI_Serial PC Callback
MIDI_Serial.setHandleClock(handleMidiClockIn); // MIDI_Serial Clock Callback
MIDI_Serial.begin(MIDI_CHANNEL_OMNI); // Initiate MIDI_Serial communications, listen to all channels
MIDI_Serial.turnThruOff();
// Inits the clock
uClock.init();
// Set the callback function for the clock output to send MIDI Sync message.
uClock.setClock96PPQNOutput(ClockOut96PPQN);
// Set the clock BPM to 126 BPM
uClock.setTempo(126);
// Starts the clock, tick-tac-tick-tac...
uClock.start();
};
void loop()
{
/// ....other code
//////MIDI CLOCK/////
if (sendMidiClock) {
uClock.setTempo(TapTempoBPM);
if (xSemaphoreTake(xSemaphore, portMAX_DELAY)) {
if (midiOutBleActive) // value can be set from APP
{
MIDI.sendRealTime(midi::Clock);
}
// DIN SERIAL MIDI
if (midiOutDINActive) {
MIDI_Serial.sendRealTime(midi::Clock);
}
xHigherPriorityTaskWoken = pdFALSE;
}
}
/// ....other code
};
void HandleSendMIDIClock()
{
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
// Increment the clock counter, wrap around after 24 pulses
clockCounter = (clockCounter + 1) & 0x0F;
handle_bpm_led(clockCounter); // leds work fine
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void handle_bpm_led(uint32_t tick)
{
// BPM led indicator
if (tick == 0 || tick % 24 == 0) { // Each quarter note
midiClockLedOn = true;
digitalWrite(LED_BUILTIN, HIGH);
} else if (midiClockLedOn && tick % 24 == 6) { // Turn off the LED after 6 ticks (1/16th note duration)
midiClockLedOn = false;
digitalWrite(LED_BUILTIN, LOW);
}
}
it actually works fine if no other tasks run in parallel, like sometimes it crashes when I update the tempo with:
uClock.setTempo(TapTempoBPM);
but is now pretty random
Trying now to import everything in PIO
@animevietsub I was able to decode the backtrace:
assert failed: xQueueSemaphoreTake queue.c:1554 (!( ( xTaskGetSchedulerState() == ( ( BaseType_t ) 0 ) ) && ( xTicksToWait != 0 ) ))
Backtrace: 0x40378122:0x3fc9a5b0 0x4037fc85:0x3fc9a5d0 0x403867d5:0x3fc9a5f0 0x40380c6e:0x3fc9a720 0x42037517:0x3fc9a760 0x420374d9:0x3fc9a780 0x42036c5a:0x3fc9a810 0x42015683:0x3fc9a830 0x42002d3d:0x3fc9a850 0x42002d4f:0x3fc9a870 0x4201f869:0x3fc9a890 0x4201f8fb:0x3fc9a8b0 0x4037dd73:0x3fc9a8d0 0x40376214:0x3fc9a8f0 0x403795a5:0x3fc9a910 0x400559dd:0x3fceba40 |<-CORRUPTED
#0 0x40378122:0x3fc9a5b0 in panic_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/panic.c:408
#1 0x4037fc85:0x3fc9a5d0 in esp_system_abort at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/esp_system/esp_system.c:137
#2 0x403867d5:0x3fc9a5f0 in __assert_func at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/newlib/assert.c:85
#3 0x40380c6e:0x3fc9a720 in xQueueSemaphoreTake at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/freertos/queue.c:1549 (discriminator 1)
#4 0x42037517:0x3fc9a760 in rmtWrite at /Users/cyberdevnet/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-rmt.c:384 (discriminator 1)
#5 0x420374d9:0x3fc9a780 in neopixelWrite at /Users/cyberdevnet/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-rgb-led.c:46
#6 0x42036c5a:0x3fc9a810 in __digitalWrite at /Users/cyberdevnet/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-gpio.c:141
(inlined by) __digitalWrite at /Users/cyberdevnet/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-gpio.c:135
#7 0x42015683:0x3fc9a830 in handle_bpm_led(unsigned int) at src/src/midi/MidiIN.cpp:446
(inlined by) handle_bpm_led(unsigned int) at src/src/midi/MidiIN.cpp:438
#8 0x42002d3d:0x3fc9a850 in HandleSendMIDIClock() at src/Medusa.ino:814
#9 0x42002d4f:0x3fc9a870 in ClockOut96PPQN(unsigned int) at src/Medusa.ino:127
#10 0x4201f869:0x3fc9a890 in umodular::clock::uClockClass::handleTimerInt() at /Users/cyberdevnet/Documents/Arduino/libraries/uClock/src/uClock.cpp:331
#11 0x4201f8fb:0x3fc9a8b0 in uclockISR() at /Users/cyberdevnet/Documents/Arduino/libraries/uClock/src/uClock.cpp:427
#12 0x4037dd73:0x3fc9a8d0 in timerFnWrapper at /Users/cyberdevnet/.platformio/packages/framework-arduinoespressif32/cores/esp32/esp32-hal-timer.c:218
#13 0x40376214:0x3fc9a8f0 in timer_isr_default at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/driver/timer.c:208
(inlined by) timer_isr_default at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/driver/timer.c:186
#14 0x403795a5:0x3fc9a910 in _xt_lowint1 at /Users/ficeto/Desktop/ESP32/ESP32S2/esp-idf-public/components/freertos/port/xtensa/xtensa_vectors.S:1114
#15 0x400559dd:0x3fceba40 in ?? ??:0
I decided to not implement this feature, a reliable clock should be provided by external source IMHO, but your code should be put in the Examples, as a Standalone works just fine!
If you still want to continue try to put:
handle_bpm_led(clockCounter); // leds work fine
before:
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
so the total code should be:
#include "Arduino.h"
#include <HardwareSerial.h>
#include <MIDI.h>
#include <freertos/semphr.h>
#include <uClock.h>
#define TXD2 17
#define RXD2 18
#define LED 2
int TapTempoBPM = 120;
static SemaphoreHandle_t xSemaphore = NULL;
static BaseType_t xHigherPriorityTaskWoken = pdFALSE;
byte clockCounter = 0; // Count the MIDI clock pulses, 24 pulses per quarter note
bool midiClockLedOn = false;
MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, MIDI_Serial);
// The callback function which will be called by Clock each Pulse of 96PPQN clock resolution.
void ClockOut96PPQN(uint32_t tick)
{
// Send MIDI_CLOCK to external gears
HandleSendMIDIClock();
}
void setup()
{
xSemaphore = xSemaphoreCreateBinary();
Serial.begin(115200); // setup HW serial for debug communication
Serial2.begin(31250, SERIAL_8N1, RXD2, TXD2); // setup Serial2 for MIDI control
pinMode(LED_BUILTIN, OUTPUT); // set the LED pin as an output
digitalWrite(LED_BUILTIN, LOW); // turn off the LED
MIDI.setHandleControlChange(handleControlChangeIN); // BLEMIDI CC Callback
MIDI.setHandleProgramChange(handleProgramChangeIN); // BLEMIDI PC Callback
MIDI.setHandleClock(handleMidiClockIn); // BLEMIDI Clock Callback
MIDI.begin(MIDI_CHANNEL_OMNI); // Initiate BLEMIDI communications, listen to all channels
MIDI_Serial.setHandleControlChange(handleControlChangeIN); // MIDI_Serial CC Callback
MIDI_Serial.setHandleProgramChange(handleProgramChangeIN); // MIDI_Serial PC Callback
MIDI_Serial.setHandleClock(handleMidiClockIn); // MIDI_Serial Clock Callback
MIDI_Serial.begin(MIDI_CHANNEL_OMNI); // Initiate MIDI_Serial communications, listen to all channels
MIDI_Serial.turnThruOff();
// Inits the clock
uClock.init();
// Set the callback function for the clock output to send MIDI Sync message.
uClock.setClock96PPQNOutput(ClockOut96PPQN);
// Set the clock BPM to 126 BPM
uClock.setTempo(126);
// Starts the clock, tick-tac-tick-tac...
uClock.start();
};
void loop()
{
/// ....other code
//////MIDI CLOCK/////
if (sendMidiClock) {
uClock.setTempo(TapTempoBPM);
if (xSemaphoreTake(xSemaphore, portMAX_DELAY)) {
if (midiOutBleActive) // value can be set from APP
{
MIDI.sendRealTime(midi::Clock);
}
// DIN SERIAL MIDI
if (midiOutDINActive) {
MIDI_Serial.sendRealTime(midi::Clock);
}
}
}
/// ....other code
};
void HandleSendMIDIClock()
{
// Increment the clock counter, wrap around after 24 pulses
xHigherPriorityTaskWoken = pdFALSE;
clockCounter = (clockCounter + 1) & 0x0F;
handle_bpm_led(clockCounter); // leds work fine
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
void handle_bpm_led(uint32_t tick)
{
// BPM led indicator
if (tick == 0 || tick % 24 == 0) { // Each quarter note
midiClockLedOn = true;
digitalWrite(LED_BUILTIN, HIGH);
} else if (midiClockLedOn && tick % 24 == 6) { // Turn off the LED after 6 ticks (1/16th note duration)
midiClockLedOn = false;
digitalWrite(LED_BUILTIN, LOW);
}
}
Hi @cyberdevnet
Has the library already been tested with these platforms?
Since its was the latest platform ported for uClock, it wasn't fully tested over all boards, but it is using Hal library behind the scene wich kinda makes it work on every esp32(so far HAL library developers claims that).
But dual core architecture makes it a harder platform to code realtime systems, so depending on what you're planning to do with the clock signal you will need to handle shared resources over parallel execution for 2 different CPUs and a ISR interruption(mechanism for sync that uClock uses).
Probrably the best start point is to chose run Arduino in one core only, use the other for any other task not arduino then. But there are ways to use both too, this is just a suggestion as start point.
Something that I have missed out?
You can try this example as start point. Like @animevietsub has pointed out, you cant use serial in this context to send data, so you need to create mechanism to handle shared resourse like a midi incoming flag in the example above:
https://github.com/midilab/uClock/blob/main/examples/ESP32UartMasterMidiClock/ESP32UartMasterMidiClock.ino
This is a very simple example, that sends data inside the main loop(), but keep in mind that use this example for heavy load processing inside loop() could lead to sync problems, if you are using freertos the better place to use the send sync it will be inside a realtime task to handle only the sync send data part of the code(that way you avoid missing sync while processing other stuffs). But please, try first this simple example to see if it works as expected on your board.
@animevietsub I commented out the handle_bpm_led function and until now no crashes:
void HandleSendMIDIClock()
{
// Increment the clock counter, wrap around after 24 pulses
xHigherPriorityTaskWoken = pdFALSE;
// clockCounter = (clockCounter + 1) & 0x0F;
// handle_bpm_led(clockCounter); // leds work fine
xSemaphoreGiveFromISR(xSemaphore, &xHigherPriorityTaskWoken);
portYIELD_FROM_ISR(xHigherPriorityTaskWoken);
}
@midilab I'm gonna try your suggestion about using a separate task with freertos, I have just to learn how ^^.
I think we're very close :)
This is more or less my implementation (just a crop of the interesting code, not the whole running code).
#include "Arduino.h"
#include <MIDI.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <uClock.h>
int TapTempoBPM;
MIDI_CREATE_INSTANCE(HardwareSerial, Serial2, MIDI_Serial);
BLEMIDI_CREATE_INSTANCE("MIDI_BLE", MIDI)
volatile bool _midi_clk_income = false;
TaskHandle_t midiClockTaskHandle;
// Internal clock handlers
void ClockOut96PPQN(uint32_t tick)
{
// Send MIDI_CLOCK to external gears
_midi_clk_income = true;
// handle_bpm_led(tick); => removed, crashes
}
// Task function to handle MIDI Clock
void HandleSendMIDIClockTask(void* pvParameters)
{
while (true) {
// watch for income signal from uClock to fire the clock over midi
if (_midi_clk_income) {
uClock.setTempo(TapTempoBPM);
if (midiOutBleActive) // value can be set from APP
{
MIDI.sendRealTime(midi::Clock);
}
// DIN SERIAL MIDI
if (midiOutDINActive) {
MIDI_Serial.sendRealTime(midi::Clock);
}
_midi_clk_income = false;
vTaskDelay(pdMS_TO_TICKS(1));
}
}
}
void setup() {
Serial.begin(115200); // setup HW serial for debug communication
Serial2.begin(31250, SERIAL_8N1, RXD2, TXD2); // setup Serial2 for MIDI control
MIDI.setHandleControlChange(handleControlChangeIN); // BLEMIDI CC Callback
MIDI.setHandleProgramChange(handleProgramChangeIN); // BLEMIDI PC Callback
MIDI.setHandleClock(handleMidiClockIn); // BLEMIDI Clock Callback
MIDI.begin(MIDI_CHANNEL_OMNI); // Initiate BLEMIDI communications, listen to all channels
MIDI_Serial.setHandleControlChange(handleControlChangeIN); // MIDI_Serial CC Callback
MIDI_Serial.setHandleProgramChange(handleProgramChangeIN); // MIDI_Serial PC Callback
MIDI_Serial.setHandleClock(handleMidiClockIn); // MIDI_Serial Clock Callback
MIDI_Serial.begin(MIDI_CHANNEL_OMNI); // Initiate MIDI_Serial communications, listen to all channels
MIDI_Serial.turnThruOff();
////////////////////////////////////////////////////////////////////////////////////////////
// Setup our clock system
// Inits the clock
uClock.init();
// Set the callback function for the clock output to send MIDI Sync message.
uClock.setClock96PPQNOutput(ClockOut96PPQN);
// Set the clock BPM to 126 BPM
uClock.setTempo(TapTempoBPM);
// Starts the clock, tick-tac-tick-tac...
uClock.start();
// Create the HandleSendMIDIClock task on core 1
xTaskCreatePinnedToCore(HandleSendMIDIClockTask, "midiClockTask",
2048, // Stack size (adjust if needed)
NULL,
1, // Priority of the task
&midiClockTaskHandle,
1 // Core 1
);
////////////////////////////////////////////////////////////////////////////////////////////
}
void loop() {
}
Everything is running pretty fine, except for the blinking LED that crashes occasionally (but I don't actually need it). Could you please take a look at it? Thanks!
By the way, I've noticed that when I change the tempo "live" with uClock.setTempo(TapTempoBPM), there's always a fluctuation of 2-3 seconds until the clock stabilizes. I think this is just a normal behavior, right?
Hi @cyberdevnet
Looking at your code i would make two changes, maybe three:
By the way, I've noticed that when I change the tempo "live" with uClock.setTempo(TapTempoBPM), there's always a fluctuation of 2-3 seconds until the clock stabilizes. I think this is just a normal behavior, right?
It depends on who is receiving the timming, or where you're seeing those fluctiations.
Hi @midilab
Thanks for your suggestions, this seems to work pretty fine, other tasks run smoothly and don't affect the Midi Clock
void HandleSendMIDIClockTask(void* pvParameters)
{
TickType_t lastWakeTime = xTaskGetTickCount(); // Initialize the last wake time
while (true) {
// watch for income signal from uClock to fire the clock over MIDI
if (_midi_clk_income) {
uClock.setTempo(TapTempoBPM);
if (midiOutBleActive) // value can be set from APP
{
MIDI.sendRealTime(midi::Clock);
}
// DIN SERIAL MIDI
if (midiOutDINActive) {
MIDI_Serial.sendRealTime(midi::Clock);
}
_midi_clk_income = false;
}
vTaskDelayUntil(&lastWakeTime, pdMS_TO_TICKS(1));
}
}
I wasn't able to remove the while loop there...crash! 👯♂️
@cyberdevnet @animevietsub
So after review the implementation(since i didn't know about the freertos integrated with esp32duino environment) i decide to code everything hidding those concurrency implementations from the user, so he can now use it the same way as other microcontorllers. you can pull the latest git commit to have access to it. also check the example for ESP32 to test it!
Hi,
I'm facing a Guru Meditation Error on my ESP32 Dev Module and ESP32-S3 Dev Module boards when trying to implement a basic code. The error occurs when sending the MIDI clock message, and it leads to a reboot with the following message:
Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1).
Additionally, the backtrace shows corruption in the core 1 and core 0 register dumps.
Code:
Error:
Bord config (vscode):
Additional Information:
I was able once or twice to let the code run for 10 seconds on ESP32-S3 and the clock accuracy is perfect, 126 sent, 126 get. but suddenly it crashed over and over...
Has the library already been tested with these platforms?
Something that I have missed out?