USBMIDI Callback

With OS 5.9 I was using USBDevice - a | Mbed library to receive MIDI messages as USBDevice was not included in 5.9. OS 5.15 now has USBDevice included so the Kojto library is no longer needed. However, the two libraries handle callbacks differently. The code I used previously no longer works.

There are plenty of examples of how to write callbacks for the Kojto library but I was unable to find a single example of how to write a callback for the 5.15 library.

The following code is able to receive MIDI messages thru a callback function. Please let me know how this might be improved. Thanks.

<>
/* mbed Microcontroller Library

  • Copyright (c) 2019 ARM Limited

  • SPDX-License-Identifier: Apache-2.0

  • mbed-os 5.15.1

*/

#include “mbed.h”

#include “platform/mbed_thread.h”

#include “USBMIDI.h”

// Blinking rate in milliseconds

#define BLINKING_RATE_MS 500

// Will use ‘pc’ for printf

Serial pc(SERIAL_TX, SERIAL_RX);

// Initialise the digital pin LED1 as an output

DigitalOut led1(LED1);

DigitalOut led2(LED2);

DigitalOut led3(LED3);

MIDIMessage midimsg;

USBMIDI *pMidi;

void MidiCallback(void)

{

pMidi->read(&midimsg);

led3 = !led3;

//printf statement will cause a hang

//printf("%02x\n\r", midimsg.type());

switch (midimsg.type())

{

case MIDIMessage::NoteOnType:

    break;

case MIDIMessage::NoteOffType:

    break;

case MIDIMessage::ControlChangeType:

    break;

case MIDIMessage::PitchWheelType:

    break;

case MIDIMessage::SysExType:

    led1 = !led1;

    break;

default:

    break;

}

}

int main()

{

// Initialization for printf

pc.baud(115200);

fclose(stdout);

stdout = pc;

fclose(stderr);

stderr = pc;

// Init Midi

printf("init midi\n\r");

USBMIDI midi;

pMidi = &midi;

printf("midi defined\n\r");

midi.attach(MidiCallback); // call back for MIDI messages received

while (true) {

    led2 = !led2;

    thread_sleep_for(BLINKING_RATE_MS);

    // print the midimsg to see if MidiCallback found something

    printf("%02x,%02x,%02x,%02x,%02x,%02x\n\r", midimsg.type(), midimsg.data[0], 

        midimsg.data[1], midimsg.data[2], midimsg.data[3], midimsg.data[4]);

}

}
<
>

For your information, you can make code blocks by placing triple backticks before and after your code. It makes it easier to read.

```
// your code starts after triple backticks
#include "mbed.h"
#include "platform/mbed_thread.h"
#include "USBMIDI.h"
// Blinking rate in milliseconds
#define BLINKING_RATE_MS 500
// .....
// close the code block with triple backticks
```

I want to understand what the problem is. Are you saying that your program gets stuck in the callback function if you use printf in it and want to fix that?

Hi Kentaro:

It would be nice if we could use printf in the callback but not absolutely necessary. The topic is more about providing coders an example of using MIDI callbacks since I could find none for 5.15 USBMIDI.

I assume the printf hangs because the function which calls the callback has locked some resource.

Thanks, Mike

Hi Kentaro:

Not being able to put ‘printf’ in the callback only effects debugging. But what IS serious is I am not able to write to SPI ports inside the callback. This was never a problem with the old USBDevice - a | Mbed. Please help!!

Thanks, Mike

What error are you getting? Mutex error?

I have never used USBMIDI and cannot provide MIDI examples. But, if you haven’t tried EventQueue, you may want to look into it.

https://os.mbed.com/docs/mbed-os/v5.15/tutorials/the-eventqueue-api.html

If your error occurs because your program executes codes in an interrupt context, you may be able to solve that by deferring the execution to a different context using EventQueue.

Below is an example. This postpones an execution of myCallback function (which printf and writes to SPI port) from ISR context to a user context.

#include "mbed.h"

#define MOSI p5
#define MISO p6
#define SCLK p7
#define CS p8

EventQueue *queue = mbed_event_queue(); // event queue

RawSerial pc(USBTX, USBRX);
SPI spi(MOSI, MISO, SCLK);
DigitalOut cs(CS);

void onDataReceived();

void myCallback() {
    char c = pc.getc();
    printf("received: %02x\r\n", c);
    cs = 0;
    spi.write(c);
    cs = 1;
    pc.attach(&onDataReceived, Serial::RxIrq);  // reattach interrupt
}

void onDataReceived() {
    pc.attach(NULL, Serial::RxIrq); // detach interrupt
    queue->call(myCallback);        // process in a different context
}

int main() {
    pc.attach(&onDataReceived, Serial::RxIrq);
    queue->dispatch_forever();
}

See queue->call(myCallback) in onDataReceived. If I have just myCallback() there instead, it crashes because it is not allowed to do printf and spi write in ISR context.

Hi Kentaro:

Thanks, the EventQueue worked! I was thinking of having the callback start a Thread which does the printf and SPI writes. Which one do you think is best?

Regards, Mike

Here’s the USBMIDI Callback code using EventQueue as suggested by Kentaro (lonesometraveler). The EventQueue allows printf and spi.write to occur in a user context called by the Callback interrupt.



/* mbed Microcontroller Library

  • Copyright (c) 2019 ARM Limited

  • SPDX-License-Identifier: Apache-2.0

  • mbed-os 5.15.1

*/

#include “mbed.h”

#include “platform/mbed_thread.h”

#include “USBMIDI.h”

#include “main.h”

EventQueue *queue = mbed_event_queue(); // event queue

// Blinking rate in milliseconds

#define BLINKING_RATE_MS 500

// Will use ‘pc’ for printf

Serial pc(SERIAL_TX, SERIAL_RX);

// Initialise the digital pin LED1 as an output

DigitalOut led1(LED1);

DigitalOut led2(LED2);

DigitalOut led3(LED3);

MIDIMessage midimsg;

USBMIDI *pMidi;

SPI spi1(PD_7, PG_9, PG_11); //SPI(MOSI, MISO, SCLK); → DAC16-31 ok

DigitalOut spi1_nss(PG_10);

void MidiCallback(void)

{

pMidi->read(&midimsg);

led3 = !led3;

printf(“type: %02x\n\r”, midimsg.type());

switch (midimsg.type())

{

case MIDIMessage::NoteOnType:

break;

case MIDIMessage::NoteOffType:

break;

case MIDIMessage::ControlChangeType:

break;

case MIDIMessage::PitchWheelType:

break;

case MIDIMessage::SysExType:

printf(“data[2]: %02x\n\r”, midimsg.data[2]);

spi1_nss = 0;

spi1.write(0);

spi1_nss = 1;

led1 = !led1;

break;

default:

break;

}

pMidi->attach(onMidiReceived); // reattach interrupt

}

// Postpone the execution MidiCallback from an interrupt handler to a user context. In a user context

// functions like ‘printf’ and spi->write can be used safely.

void onMidiReceived(void)

{

pMidi->attach(NULL); // detach interrupt

queue->call(MidiCallback);

}

int main()

{

// Initialization for printf

pc.baud(115200);

fclose(stdout);

stdout = pc;

fclose(stderr);

stderr = pc;

// Init Midi

printf(“init midi\n\r”);

USBMIDI midi;

pMidi = &midi;

printf(“midi defined\n\r”);

pMidi->attach(onMidiReceived); // call back for MIDI messages received

while (true) {

led2 = !led2;

thread_sleep_for(BLINKING_RATE_MS);

    // print the midimsg to see if MidiCallback found something

    //printf("%02x,%02x,%02x,%02x,%02x,%02x\n\r", midimsg.type(), midimsg.data[0], 

    //    midimsg.data[1], midimsg.data[2], midimsg.data[3], midimsg.data[4]);

}

}




1 Like

Glad it worked. Please place triple backticks before and after the code to make a code block.

```
// your code 
```

The world’s first MbedOS5 USBMIDI callback example deserves a better formatting.

I normally run sensors, servers, etc on separate threads and handle events from the threads with EventQueue. Not sure if it is the best practice. But, it has been working for me.

Here’s the code with backticks to make it more readable:

/* mbed Microcontroller Library
 * Copyright (c) 2019 ARM Limited
 * SPDX-License-Identifier: Apache-2.0
 * mbed-os 5.15.1
 */
 
#include "mbed.h"
#include "platform/mbed_thread.h"
#include "USBMIDI.h"
#include "main.h"

EventQueue *queue = mbed_event_queue(); // event queue 
// Blinking rate in milliseconds
#define BLINKING_RATE_MS 500

// Will use 'pc' for printf
Serial pc(SERIAL_TX, SERIAL_RX); 

// Initialise the digital pin LED1 as an output
DigitalOut led1(LED1);
DigitalOut led2(LED2);
DigitalOut led3(LED3);

MIDIMessage midimsg;
USBMIDI *pMidi;

SPI spi1(PD_7, PG_9, PG_11); //SPI(MOSI, MISO, SCLK); -> DAC16-31 ok
DigitalOut spi1_nss(PG_10);

void MidiCallback(void)
{
    pMidi->read(&midimsg);
    led3 = !led3;
    printf("type: %02x\n\r", midimsg.type());
    switch (midimsg.type())
	{
	case MIDIMessage::NoteOnType:
		break;
	case MIDIMessage::NoteOffType:
		break;
	case MIDIMessage::ControlChangeType:
		break;
	case MIDIMessage::PitchWheelType:
		break;
	case MIDIMessage::SysExType:
        printf("data[2]: %02x\n\r", midimsg.data[2]);
        spi1_nss = 0;
        spi1.write(0);
        spi1_nss = 1;
        led1 = !led1;
        break;
	default:
		break;
	}
    pMidi->attach(onMidiReceived); // reattach interrupt
}

// Postpone the execution MidiCallback from an interrupt handler to a user context.  In a user context
// functions like 'printf' and spi->write can be used safely.
void onMidiReceived(void)
{
    pMidi->attach(NULL); // detach interrupt
    queue->call(MidiCallback);
}
 
int main()
{
    // Initialization for printf
    pc.baud(115200);
    fclose(stdout);
    stdout = pc;
    fclose(stderr);
    stderr = pc;
    // Init Midi
    printf("init midi\n\r");
    USBMIDI midi;
    pMidi = &midi;
    printf("midi defined\n\r");
    pMidi->attach(onMidiReceived); // call back for MIDI messages received
    while (true) {
        led2 = !led2;
        thread_sleep_for(BLINKING_RATE_MS);
    }
}