Two MIDI UARTs on Patch SM

New Daisy user here, but experienced in STM32 hardware and software development.

I’m working on a VA polysynth which is progressing well.
I set up UART4 as a MIDI input and can play the synth from a MIDI keyboard.
So far, so good.

To provide a user interface I plan to add an external STM32F103 or ATmega128 to handle the pots & switches. I decided the use MIDI CC messages to USART1 as it seemed a simple solution rather than concocting my own protocol over UART or SPI and having to write my own drivers.

Adding USART1 as a MIDI interface is where I have come unstuck.

I can use UART4 for MIDI or I can use USART1, but I can’t use both.
As far as I can tell from the comments in the MIDI drivers it’s supposed to work?

Here’s my (abbreviated) code, there are #defines to incrementally enable the second UART as part of my investigation.

#define USING_USART1A
#define USING_USART1B
#define USING_USART1C
#define USING_USART1D
#define USING_USART1E
//#define USING_USART1F

#define USING_UART4

DaisyPatchSM hw;
#ifdef USING_UART4
MidiUartHandler midiUart4;
#endif
#ifdef USING_USART1A
MidiUartHandler midiUsart1;
#endif

static constexpr size_t kMidiBufferSize = 256;
#ifdef USING_UART4
static uint8_t DMA_BUFFER_MEM_SECTION buffMidiUart4[kMidiBufferSize];
#endif
#ifdef USING_USART1B
static uint8_t DMA_BUFFER_MEM_SECTION buffMidiUsart1[kMidiBufferSize];
#endif

int main(void)
{
#ifdef USING_UART4
	MidiUartHandler::Config midiUart4Config;
#endif
#ifdef USING_USART1C
	MidiUartHandler::Config midiUsart1Config;
#endif

	// initialise the keyboard MIDI interface
#ifdef USING_UART4
	midiUart4Config.transport_config.periph = UartHandler::Config::Peripheral::UART_4;
	midiUart4Config.transport_config.rx = {DSY_GPIOA, 1}; // Pin A2
	midiUart4Config.transport_config.tx = {DSY_GPIOA, 0}; // Pin A3
	midiUart4Config.transport_config.rx_buffer_size = kMidiBufferSize;
	midiUart4Config.transport_config.rx_buffer = buffMidiUart4;

	midiUart4.Init(midiUart4Config);
#endif
#ifdef USING_USART1D
	// initialise the controls MIDI interface
	midiUsart1Config.transport_config.periph = UartHandler::Config::Peripheral::USART_1;
	midiUsart1Config.transport_config.rx = {DSY_GPIOB, 15}; // Pin A9
	midiUsart1Config.transport_config.tx = {DSY_GPIOB, 14}; // Pin A8
	midiUsart1Config.transport_config.rx_buffer_size = kMidiBufferSize;
	midiUsart1Config.transport_config.rx_buffer = buffMidiUsart1;

	midiUsart1.Init(midiUsart1Config);
#endif

	// start the MIDI interfaces
#ifdef USING_USART1E
	midiUsart1.StartReceive();
#endif
#ifdef USING_UART4
	midiUart4.StartReceive();
#endif

	while(1)
	{
		// handle MIDI events
#ifdef USING_UART4
		midiUart4.Listen();
		while(midiUart4.HasEvents())
		{
			HandleMidiKeyboardMessage(midiUart4.PopEvent());
		}
#endif
#ifdef USING_USART1F
		midiUsart1.Listen();
		while(midiUsart1.HasEvents())
		{
//			HandleMidiContolMessage(midiUsart1.PopEvent());
			HandleMidiKeyboardMessage(midiUsart1.PopEvent());
		}
#endif
	}
}

For USART1 steps A through D there is no effect on UART4, it continues to work correctly.
When USART1 step E is enabled (the call to StartReceive()) things start to go wrong.
The effect is slightly different depending on the sequence of calling StartReceive() on each UART.

midiUart4.StartReceive();
midiUsart1.StartReceive();
while (1)
{
    midiUart4.Listen();
    while (midiUart4.HasEvents())

This sequence doesn’t seem to work, as midiUart4.HasEvents() never returns true.

midiUsart1.StartReceive();
midiUart4.StartReceive();
while (1)
{
    midiUart4.Listen();
    while (midiUart4.HasEvents())

This sequence does work UNTIL a message is received on USART1.

Does anyone have any suggestions?

So after poking around in \libDaisy\src\per\uart.cpp I think I’ve found the reason.

UartHandler::Result UartHandler::Impl::InitDma(bool rx, bool tx)
{
    hdma_rx_.Instance                 = DMA1_Stream5;
    hdma_rx_.Init.PeriphInc           = DMA_PINC_DISABLE;
    hdma_rx_.Init.MemInc              = DMA_MINC_ENABLE;
    hdma_rx_.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_rx_.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
    hdma_rx_.Init.Mode                = DMA_NORMAL;
    hdma_rx_.Init.Priority            = DMA_PRIORITY_VERY_HIGH;
    hdma_rx_.Init.FIFOMode            = DMA_FIFOMODE_DISABLE;
    hdma_rx_.Init.Direction           = DMA_PERIPH_TO_MEMORY;

    hdma_tx_.Instance                 = DMA2_Stream4;
    hdma_tx_.Init.PeriphInc           = DMA_PINC_DISABLE;
    hdma_tx_.Init.MemInc              = DMA_MINC_ENABLE;
    hdma_tx_.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
    hdma_tx_.Init.MemDataAlignment    = DMA_MDATAALIGN_BYTE;
    hdma_tx_.Init.Mode                = DMA_NORMAL;
    hdma_tx_.Init.Priority            = DMA_PRIORITY_VERY_HIGH;
    hdma_tx_.Init.FIFOMode            = DMA_FIFOMODE_DISABLE;
    hdma_tx_.Init.Direction           = DMA_MEMORY_TO_PERIPH;
    SetDmaPeripheral();

And this tell-tale comment

    /** Not static -- any UART can use this 
     *  until we had dynamic DMA stream handling
     *  this will consume the sole DMA stream for UART Rx
     */

And

UartHandler::Result
UartHandler::Impl::DmaListenStart(uint8_t* buff,
                                  size_t   size,
                                  UartHandler::CircularRxCallbackFunctionPtr cb,
                                  void* callback_context)
{
...
    /** Initialize DMA Rx i
     *  TODO: Update when dynamic DMA Stream acquisition is added
     */
    hdma_rx_.Instance                 = DMA1_Stream5;
...

So it looks like there is a plan to support multiple UARTs but for now I’ll have to figure it out myself.

Have you figured out ? / Are multiple UARTs supported now ?

I did figure it out, but I haven’t figured out the “correct” way to do it, so I just hacked the UART driver for my own use.
This is currently an unofficial and unsupported mod! Use it at your own risk!

libDaisy Modifications

Requires modifications to libDaisy/src/per/uart.cpp to allow 2 UARTs to use DMA

In UartHandler::Impl::InitDma, set hdma_rx_.Instance and hdma_tx_.Instance to different DMA & stream
depending on value of config_.periph
Only supports USART1 & UART4

In UartHandler::Impl::DmaListenStart, set hdma_rx_.Instance to different DMA & stream
depending on value of config_.periph
Only supports USART1 & UART4

In UART_CheckRxListener, get length from LL_DMA_GetDataLength(DMA1, LL_DMA_STREAM_5)
or from LL_DMA_GetDataLEngth(DMA2, LL_DMA_STREAM_5) depending on the value of handle->config_.periph
Only supports USART1 & UART4

Add parameter to HalUartDmaRxStreamCallback(int8_t uart)
In extern “C” void DMA1_Stream5_IRQHandler(void), pass USART1 number into HalUartDmaRxStreamCallback
Add extern “C” void DMA2_Stream5_IRQHandler(void), call HalUartDmaRxStreamCallback with UART4

Add parameter to HalUartDmaTxStreamCallback(int8_t uart)
In extern “C” void DMA2_Stream4_IRQHandler(void), pass USART1 number into HalUartDmaTxStreamCallback
Add extern “C” void DMA2_Stream6_IRQHandler(void), call HalUartDmaTxStreamCallback with UART4

Attached is a modified version of uart.cpp. To use it, replace the existing libdaisy/src/per/uart.cpp with this file. You should rename the original file first, so you can go back to it if you want/need to. You also need to rename the attached file as the build tools look for a file called uart.cpp.

uart_modified.cpp.txt (42.6 KB)