midilab / uClock

A tight BPM clock generator for Arduino and PlatformIO using hardware timer interruption. AVR, Teensy, STM32xx, ESP32 and RP2040 support
https://midilab.co/umodular
MIT License
153 stars 19 forks source link

ESP32/ESP32-s3 crash Guru Meditation Error: Core 1 panic'ed (Interrupt wdt timeout on CPU1). #25

Closed cyberdevnet closed 10 months ago

cyberdevnet commented 11 months ago

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:

#include "Arduino.h"
#include <HardwareSerial.h>
#include <MIDI.h>
#include <uClock.h>

#define TXD2 17
#define RXD2 18
#define LED 2

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()
{
    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.
};

void HandleSendMIDIClock()
{
    //MIDI_Serial.sendRealTime(midi::Clock); // <= crashes as soon commented out
    // Serial2.write(0xF8); <= tried also this, same crash

    // Increment the clock counter, wrap around after 24 pulses
    clockCounter = (clockCounter + 1) & 0x0F;

    handle_bpm_led(clockCounter); // leds work fine
}

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);
    }
}

Error:

Guru Meditation Error: Core  1 panic'ed (Interrupt wdt timeout on CPU1). 

Core  1 register dump:
PC      : 0x4008ab2a  PS      : 0x00060735  A0      : 0x80089aa2  A1      : 0x3ffbf16c  
A2      : 0x3ffb8a4c  A3      : 0x3ffb81a4  A4      : 0x00000004  A5      : 0x00060723  
A6      : 0x00060723  A7      : 0x00000001  A8      : 0x3ffb81a4  A9      : 0x00000018  
A10     : 0x3ffb81a4  A11     : 0x00000018  A12     : 0x00000004  A13     : 0x00060723  
A14     : 0x007bf318  A15     : 0x003fffff  SAR     : 0x0000000a  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x4008416d  LEND    : 0x40084175  LCOUNT  : 0x00000027  
Core  1 was running in ISR context:
EPC1    : 0x400db3fb  EPC2    : 0x00000000  EPC3    : 0x00000000  EPC4    : 0x00000000

Backtrace: 0x4008ab27:0x3ffbf16c |<-CORRUPTED

Core  0 register dump:
PC      : 0x4008acc7  PS      : 0x00060035  A0      : 0x800896cb  A1      : 0x3ffbea3c  
A2      : 0x3ffbf318  A3      : 0xb33fffff  A4      : 0x0000abab  A5      : 0x00060023  
A6      : 0x00060021  A7      : 0x0000cdcd  A8      : 0x0000abab  A9      : 0xffffffff  
A10     : 0x3ffc298c  A11     : 0x00000000  A12     : 0x3ffc2988  A13     : 0x00000007  
A14     : 0x007bf318  A15     : 0x003fffff  SAR     : 0x0000001d  EXCCAUSE: 0x00000006  
EXCVADDR: 0x00000000  LBEG    : 0x00000000  LEND    : 0x00000000  LCOUNT  : 0x00000000  

Backtrace: 0x4008acc4:0x3ffbea3c |<-CORRUPTED

Bord config (vscode):

{
    "configuration": "JTAGAdapter=default,PSRAM=disabled,PartitionScheme=default,CPUFreq=240,FlashMode=qio,FlashFreq=80,FlashSize=4M,UploadSpeed=921600,LoopCore=1,EventsCore=1,DebugLevel=none,EraseFlash=none",
    "board": "esp32:esp32:esp32",
    "port": "/dev/tty.SLAB_USBtoUART",
    "output": "../build",
    "sketch": "testUclock.ino",
    "programmer": "esptool"
}

Additional Information:

Board used: ESP32 Dev Module and ESP32-S3 Dev Module.
Libraries used: Arduino, HardwareSerial, MIDI, and uClock.
The backtrace shows corruption in core 1 and core 0 register dumps.

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?

animevietsub commented 11 months 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);
    }
}
cyberdevnet commented 11 months ago

@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!

animevietsub commented 11 months ago

Can you send me your current code? Which ESP32 boards are you compiling? I try in my ESP32 DEV KIT and it works. tempimg 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

cyberdevnet commented 11 months ago

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

cyberdevnet commented 11 months ago

Trying now to import everything in PIO

cyberdevnet commented 11 months ago

@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
cyberdevnet commented 11 months ago

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!

animevietsub commented 11 months ago

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);
    }
}
midilab commented 11 months ago

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.

cyberdevnet commented 11 months ago

@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 :)

cyberdevnet commented 11 months ago

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?

midilab commented 11 months ago

Hi @cyberdevnet

Looking at your code i would make two changes, maybe three:

  1. Only change _midi_clk_income using between interrupts() and noInterrupts() just as the example i sent to you. otherwise you can't garantee that the variable are being writing correctly. But in your code is kinda of safe to be use this way, but not a good programming pratice to manipulate shared resources(variables or hardware devices running in different and concurrent context) without using the proper safe access mechanisms.
  2. Delays and realtime operations are not a good way to go. So i would take that delay function off. Why are you using it?
  3. Gives the task a time of 250us and remove the while loop, use no delay at all inside the task. This willl gives your CPU some great time to handle other stuffs.

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.

cyberdevnet commented 11 months ago

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! 👯‍♂️

midilab commented 10 months ago

@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!