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.