From 25b61a317a399e0dc2665b838bc4bcb90d7ec168 Mon Sep 17 00:00:00 2001 From: Stephen Hensley Date: Fri, 16 Jun 2023 10:59:54 -0700 Subject: [PATCH 1/2] Add ConversionSpeed configuration to ADC class (#579) * added ConversionSpeed to ADC class (based on v3.0.0 release) * style fix. * removed commented-out code clutter. --------- Co-authored-by: stephenhensley --- src/per/adc.cpp | 46 +++++++++++++++++++++++++++++++++++++++------- src/per/adc.h | 39 ++++++++++++++++++++++++++++----------- 2 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/per/adc.cpp b/src/per/adc.cpp index c0fa4cbb9..1ac21acf0 100644 --- a/src/per/adc.cpp +++ b/src/per/adc.cpp @@ -199,18 +199,21 @@ static dsy_adc adc; // Begin AdcChannelConfig Implementations -void AdcChannelConfig::InitSingle(dsy_gpio_pin pin) +void AdcChannelConfig::InitSingle(dsy_gpio_pin pin, + AdcChannelConfig::ConversionSpeed speed) { pin_.pin = pin; mux_channels_ = 0; pin_.mode = DSY_GPIO_MODE_ANALOG; pin_.pull = DSY_GPIO_NOPULL; + speed_ = speed; } -void AdcChannelConfig::InitMux(dsy_gpio_pin adc_pin, - size_t mux_channels, - dsy_gpio_pin mux_0, - dsy_gpio_pin mux_1, - dsy_gpio_pin mux_2) +void AdcChannelConfig::InitMux(dsy_gpio_pin adc_pin, + size_t mux_channels, + dsy_gpio_pin mux_0, + dsy_gpio_pin mux_1, + dsy_gpio_pin mux_2, + AdcChannelConfig::ConversionSpeed speed) { size_t pins_to_init; // Init ADC Pin @@ -228,6 +231,7 @@ void AdcChannelConfig::InitMux(dsy_gpio_pin adc_pin, mux_pin_[i].mode = DSY_GPIO_MODE_OUTPUT_PP; mux_pin_[i].pull = DSY_GPIO_NOPULL; } + speed_ = speed; } // Begin AdcHandle Implementations @@ -359,7 +363,6 @@ void AdcHandle::Init(AdcChannelConfig* cfg, } // Configure Regular Channel // Configure Shared settings for all channels. - sConfig.SamplingTime = ADC_SAMPLETIME_8CYCLES_5; sConfig.SingleDiff = ADC_SINGLE_ENDED; sConfig.OffsetNumber = ADC_OFFSET_NONE; sConfig.Offset = 0; @@ -367,6 +370,35 @@ void AdcHandle::Init(AdcChannelConfig* cfg, { const auto& cfg = adc.pin_cfg[i]; + /** Handle per-channel conversions */ + switch(cfg.speed_) + { + case AdcChannelConfig::SPEED_1CYCLES_5: + sConfig.SamplingTime = ADC_SAMPLETIME_1CYCLE_5; + break; + case AdcChannelConfig::SPEED_2CYCLES_5: + sConfig.SamplingTime = ADC_SAMPLETIME_2CYCLES_5; + break; + case AdcChannelConfig::SPEED_8CYCLES_5: + sConfig.SamplingTime = ADC_SAMPLETIME_8CYCLES_5; + break; + case AdcChannelConfig::SPEED_16CYCLES_5: + sConfig.SamplingTime = ADC_SAMPLETIME_16CYCLES_5; + break; + case AdcChannelConfig::SPEED_32CYCLES_5: + sConfig.SamplingTime = ADC_SAMPLETIME_32CYCLES_5; + break; + case AdcChannelConfig::SPEED_64CYCLES_5: + sConfig.SamplingTime = ADC_SAMPLETIME_64CYCLES_5; + break; + case AdcChannelConfig::SPEED_387CYCLES_5: + sConfig.SamplingTime = ADC_SAMPLETIME_387CYCLES_5; + break; + case AdcChannelConfig::SPEED_810CYCLES_5: + sConfig.SamplingTime = ADC_SAMPLETIME_810CYCLES_5; + break; + } + // init ADC pin dsy_gpio_init(&cfg.pin_); diff --git a/src/per/adc.h b/src/per/adc.h index bb3161a04..14db03927 100644 --- a/src/per/adc.h +++ b/src/per/adc.h @@ -39,10 +39,24 @@ struct AdcChannelConfig MUX_SEL_LAST, /**< & */ }; + /** \brief per channel conversion speed added to fixed time based on bitdepth, etc. */ + enum ConversionSpeed + { + SPEED_1CYCLES_5, + SPEED_2CYCLES_5, + SPEED_8CYCLES_5, + SPEED_16CYCLES_5, + SPEED_32CYCLES_5, + SPEED_64CYCLES_5, + SPEED_387CYCLES_5, + SPEED_810CYCLES_5, + }; + /** Initializes a single ADC pin as an ADC. \param pin Pin to init. + \param speed conversion speed for this pin defaults to 8.5 cycles */ - void InitSingle(dsy_gpio_pin pin); + void InitSingle(dsy_gpio_pin pin, ConversionSpeed speed = SPEED_8CYCLES_5); /** Initializes a single ADC pin as a Multiplexed ADC. @@ -50,21 +64,24 @@ struct AdcChannelConfig You only need to supply the mux pins that are required, e.g. a 4052 mux would only require mux_0 and mux_1. Internal Callbacks handle the pin addressing. + \param adc_pin & \param mux_channels must be 1-8 \param mux_0 First mux pin \param mux_1 Second mux pin \param mux_2 Third mux pin - \param adc_pin & + \param speed conversion speed for this pin defaults to 8.5 cycles */ - void InitMux(dsy_gpio_pin adc_pin, - size_t mux_channels, - dsy_gpio_pin mux_0, - dsy_gpio_pin mux_1 = {DSY_GPIOX, 0}, - dsy_gpio_pin mux_2 = {DSY_GPIOX, 0}); - - dsy_gpio pin_; /**< & */ - dsy_gpio mux_pin_[MUX_SEL_LAST]; /**< & */ - uint8_t mux_channels_; /**< & */ + void InitMux(dsy_gpio_pin adc_pin, + size_t mux_channels, + dsy_gpio_pin mux_0, + dsy_gpio_pin mux_1 = {DSY_GPIOX, 0}, + dsy_gpio_pin mux_2 = {DSY_GPIOX, 0}, + ConversionSpeed speed = SPEED_8CYCLES_5); + + dsy_gpio pin_; /**< & */ + dsy_gpio mux_pin_[MUX_SEL_LAST]; /**< & */ + uint8_t mux_channels_; /**< & */ + ConversionSpeed speed_; }; /** From ee3e337d7363a3510514839ba3474885803b6916 Mon Sep 17 00:00:00 2001 From: Nick Donaldson Date: Fri, 16 Jun 2023 16:11:49 -0600 Subject: [PATCH 2/2] UART DMA Rework and UART MIDI improvements/refactoring (#583) * kind of a wild push, but new 'listener mode' for DMA UART Rx -- needs a lot of cleanup * updated MIDI to conform to new UART DMA Listener mode, and replaced old RingBuffer w/ FIFO. * big cleanup on UART de-FIFO'ing * cleanup removing old UART stuff. * refactor function name for dma listen mode. * example update for new usage. * style fix for midi/uart.' * added check for UART Result::OK in MIDI callback * decreased buffer size, added comments to transport, and temp. overflow handling to catch potential issue. * MIDI transport calls parse callback automatically on new bytes Addresses timing and dropped note issues for UART MIDI as a further improvement on DMA Rx * Add DMA cache clear when starting UART MIDI rx * Refactor to extract MidiParser class * Fix MIDI parser doc comments * Fix outdated comment for Parse method * More MIDIHandler comment cleanup * Provide UART MIDI Rx buffer externally Fix for transport owning buffer as member variable, ends up in non-DMA-compatible memory section for daisy bootloader apps * Revert changes to DMA Receive example * Commit clang-format autoformat changes * added new unit test for realtime messages between running status. * Fix issue with running status and SystemRealTime messages SystemRealTime messages should not alter the running status. Thats fixed now We were also supporting running status on some messages that we should have not The spec calls for running status to work on all Channel Voice and Channel Mode messages. That is what we now support, and the tests have been altered to reflect it * Lint midi code * fixed issue with uninitialized memory being passed into the rx buffer causing random parsable MIDI data post-init. * update docs for MIDI rx size. --------- Co-authored-by: stephenhensley Co-authored-by: beserge --- .vscode/launch.json | 34 +- CMakeLists.txt | 2 + Makefile | 16 +- examples/MIDI_UART_Input/MIDI_UART_Input.cpp | 8 +- .../Blocking_Transmit_Fifo_Receive.cpp | 40 +- .../Dma_Fifo_Receive/Dma_Fifo_Receive.cpp | 88 +++-- examples/uart/Dma_Receive/Makefile | 2 + src/hid/MidiEvent.h | 9 +- src/hid/midi.cpp | 18 + src/hid/midi.h | 353 +++++++----------- src/hid/midi_parser.cpp | 194 ++++++++++ src/hid/midi_parser.h | 64 ++++ src/hid/usb_midi.cpp | 48 ++- src/hid/usb_midi.h | 16 +- src/per/uart.cpp | 348 ++++++++++------- src/per/uart.h | 87 ++--- tests/Midi_gtest.cpp | 84 ++--- tests/libDaisyCombined.cpp | 1 + 18 files changed, 878 insertions(+), 534 deletions(-) create mode 100644 src/hid/midi.cpp create mode 100644 src/hid/midi_parser.cpp create mode 100644 src/hid/midi_parser.h diff --git a/.vscode/launch.json b/.vscode/launch.json index 5b38355cb..b913734c5 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -21,6 +21,38 @@ "windows": { "MIMode": "gdb", } + }, + { + "name": "Debug", + "configFiles": [ + "interface/stlink.cfg", + "target/stm32h7x.cfg" + ], + "cwd": "${workspaceFolder}", + "debuggerArgs": [ + "-d", + "${workspaceRoot}" + ], + // Here's where you can put the path to the program you want to debug: + //"executable": "${workspaceRoot}/examples/SDMMC_HelloWorld/build/SDMMC_HelloWorld.elf", + "executable": "${workspaceRoot}/examples/uart/Dma_Receive/build/Dma_Receive.elf", + "interface": "swd", + "openOCDLaunchCommands": [ + "init", + "reset init", + "gdb_breakpoint_override hard" + ], + "preRestartCommands": [ + "load", + "enable breakpoint", + "monitor reset" + ], + "request": "launch", + "runToMain": true, + "servertype": "openocd", + "showDevDebugOutput": true, + "svdFile": "${workspaceRoot}/.vscode/STM32H750x.svd", + "type": "cortex-debug" } ] -} \ No newline at end of file +} diff --git a/CMakeLists.txt b/CMakeLists.txt index 11b0201cc..22b854cc1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -44,6 +44,8 @@ add_library(${TARGET} STATIC ${MODULE_DIR}/hid/encoder.cpp ${MODULE_DIR}/hid/gatein.cpp ${MODULE_DIR}/hid/led.cpp + ${MODULE_DIR}/hid/midi.cpp + ${MODULE_DIR}/hid/midi_parser.cpp ${MODULE_DIR}/hid/parameter.cpp ${MODULE_DIR}/hid/rgb_led.cpp ${MODULE_DIR}/hid/switch.cpp diff --git a/Makefile b/Makefile index 812d0bcc6..239c867f8 100644 --- a/Makefile +++ b/Makefile @@ -41,6 +41,8 @@ hid/ctrl \ hid/encoder \ hid/gatein \ hid/led \ +hid/midi \ +hid/midi_parser \ hid/parameter \ hid/rgb_led \ hid/switch \ @@ -85,7 +87,7 @@ BUILD_DIR = build # manually adding necessary HAL files # generated by dump_filepath.py -C_SOURCES = +C_SOURCES = C_SOURCES += \ Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal.c \ Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_hal_adc.c \ @@ -197,7 +199,7 @@ Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_swpmi.c \ Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_tim.c \ Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_usart.c \ Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_usb.c \ -Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_utils.c +Drivers/STM32H7xx_HAL_Driver/Src/stm32h7xx_ll_utils.c # Middleware sources C_SOURCES += \ @@ -271,7 +273,7 @@ MCU = -mthumb $(FLOAT-ABI) $(FPU) $(CPU) # macros for gcc # AS defines -AS_DEFS = +AS_DEFS = # C defines C_DEFS = \ @@ -283,7 +285,7 @@ C_DEFS = \ -DHSE_VALUE=16000000 \ -DUSE_HAL_DRIVER \ -DUSE_FULL_LL_DRIVER \ --DDATA_IN_D2_SRAM +-DDATA_IN_D2_SRAM # ^ added for easy startup access @@ -302,7 +304,7 @@ C_INCLUDES = \ -IMiddlewares/ST/STM32_USB_Host_Library/Class/MSC/Inc \ -IMiddlewares/Third_Party/FatFs/src \ -I$(MODULE_DIR) \ --I. +-I. # suppressions for warnings introduced by HAL/FatFS WARNINGS += -Wall -Wno-attributes -Wno-strict-aliasing -Wno-maybe-uninitialized -Wno-missing-attributes -Wno-stringop-overflow #-Werror @@ -328,7 +330,7 @@ CFLAGS += \ CPPFLAGS = $(CFLAGS) $(CPP_WARNINGS) CPPFLAGS += \ -fno-exceptions \ --fno-rtti +-fno-rtti C_STANDARD = -std=gnu11 CPP_STANDARD += -std=gnu++14 @@ -369,7 +371,7 @@ $(BUILD_DIR)/$(TARGET).a: $(SORTED_OBJECTS) Makefile $(AR) -r $@ $(SORTED_OBJECTS) $(BUILD_DIR): - mkdir $@ + mkdir $@ ####################################### # clean up diff --git a/examples/MIDI_UART_Input/MIDI_UART_Input.cpp b/examples/MIDI_UART_Input/MIDI_UART_Input.cpp index 0f727857f..8df73077d 100644 --- a/examples/MIDI_UART_Input/MIDI_UART_Input.cpp +++ b/examples/MIDI_UART_Input/MIDI_UART_Input.cpp @@ -76,6 +76,12 @@ int main(void) { case NoteOn: // Do something on Note On events + { + uint8_t bytes[3] = {0x90, 0x00, 0x00}; + bytes[1] = msg.data[0]; + bytes[2] = msg.data[1]; + midi.SendMessage(bytes, 3); + } break; default: break; } @@ -91,7 +97,7 @@ int main(void) if(!event_log.IsEmpty()) { auto msg = event_log.PopFront(); - char outstr[64]; + char outstr[128]; char type_str[16]; GetMidiTypeAsString(msg, type_str); sprintf(outstr, diff --git a/examples/uart/Blocking_Transmit_Fifo_Receive/Blocking_Transmit_Fifo_Receive.cpp b/examples/uart/Blocking_Transmit_Fifo_Receive/Blocking_Transmit_Fifo_Receive.cpp index c2b13e229..fadf82958 100644 --- a/examples/uart/Blocking_Transmit_Fifo_Receive/Blocking_Transmit_Fifo_Receive.cpp +++ b/examples/uart/Blocking_Transmit_Fifo_Receive/Blocking_Transmit_Fifo_Receive.cpp @@ -1,9 +1,34 @@ +/** TODO fix / remove this example + * anticipated issue that it only shows 1 byte every 100ms + * which may be misleading to user + * + * also, it should probably use serial print to pipe out + * data instead of the patch's display. + */ #include "daisy_patch.h" using namespace daisy; +/** Consts */ +const size_t kUartBufferSize = 512; + +/** Globals */ DaisyPatch hw; UartHandler uart; +uint8_t uart_buffer[kUartBufferSize]; +char receive_str[kUartBufferSize]; + +/** Happens automatically whenever transaction completes */ +void uartCallback(uint8_t* data, + size_t size, + void* context, + UartHandler::Result res) +{ + /** Clear receive_str */ + std::fill(&receive_str[0], &receive_str[kUartBufferSize - 1], 0); + /** Copy new data into the receive str */ + std::copy(&data[0], &data[size - 1], &receive_str[0]); +} int main(void) { @@ -19,9 +44,9 @@ int main(void) // initialize the UART peripheral, and start reading uart.Init(uart_conf); - uart.DmaReceiveFifo(); + uart.DmaListenStart(uart_buffer, kUartBufferSize, uartCallback, nullptr); - uint8_t pop = 0; + uint8_t pop = 0; uint8_t send = 0; while(1) { @@ -29,11 +54,6 @@ int main(void) uart.BlockingTransmit(&send, 1); send++; - // if there's data, pop it from the FIFO - if(uart.ReadableFifo()){ - pop = uart.PopFifo(); - } - // clear the display hw.display.Fill(false); @@ -43,10 +63,10 @@ int main(void) hw.display.SetCursor(0, 0); hw.display.WriteString(cstr, Font_7x10, true); - // draw the receive buffer contents - sprintf(cstr, "%d", pop); + // draw the latest receive buffer contents + sprintf(receive_str, "%d", pop); hw.display.SetCursor(0, 12); - hw.display.WriteString(cstr, Font_7x10, true); + hw.display.WriteString(receive_str, Font_7x10, true); // update the display hw.display.Update(); diff --git a/examples/uart/Dma_Fifo_Receive/Dma_Fifo_Receive.cpp b/examples/uart/Dma_Fifo_Receive/Dma_Fifo_Receive.cpp index c49be8470..ddb2b43d6 100644 --- a/examples/uart/Dma_Fifo_Receive/Dma_Fifo_Receive.cpp +++ b/examples/uart/Dma_Fifo_Receive/Dma_Fifo_Receive.cpp @@ -1,57 +1,69 @@ +/** TODO fix / remove this example + * anticipated issue that it only shows 1 byte every 100ms + * which may be misleading to user + * + * also, it should probably use serial print to pipe out + * data instead of the patch's display. + */ #include "daisy_patch.h" using namespace daisy; -DaisyPatch hw; +/** Consts */ +const size_t kUartBufferSize = 512; + +DaisyPatch hw; UartHandler uart; +uint8_t uart_buffer[kUartBufferSize]; +char receive_str[kUartBufferSize]; + +/** Happens automatically whenever transaction completes */ +void uartCallback(uint8_t* data, + size_t size, + void* context, + UartHandler::Result res) +{ + /** Clear receive_str */ + std::fill(&receive_str[0], &receive_str[kUartBufferSize - 1], 0); + /** Copy new data into the receive str */ + std::copy(&data[0], &data[size - 1], &receive_str[0]); +} int main(void) { - // Initialize the Daisy Patch - hw.Init(); - - // Configure the Uart Peripheral - UartHandler::Config uart_conf; - uart_conf.periph = UartHandler::Config::Peripheral::USART_1; - uart_conf.mode = UartHandler::Config::Mode::RX; - uart_conf.pin_config.tx = Pin(PORTB, 6); - uart_conf.pin_config.rx = Pin(PORTB, 7); - - // Initialize the Uart Peripheral - uart.Init(uart_conf); - - // Start the FIFO Receive - uart.DmaReceiveFifo(); - - uint8_t pop = 0; - while(1) { - // if there's data, pop it from the FIFO - if(uart.ReadableFifo()){ - pop = uart.PopFifo(); - hw.seed.SetLed(false); - } - else{ - hw.seed.SetLed(true); - } - - // clear the display + // Initialize the Daisy Patch + hw.Init(); + + // Configure the Uart Peripheral + UartHandler::Config uart_conf; + uart_conf.periph = UartHandler::Config::Peripheral::USART_1; + uart_conf.mode = UartHandler::Config::Mode::RX; + uart_conf.pin_config.tx = Pin(PORTB, 6); + uart_conf.pin_config.rx = Pin(PORTB, 7); + + // Initialize the Uart Peripheral + uart.Init(uart_conf); + uart.DmaListenStart(uart_buffer, kUartBufferSize, uartCallback, nullptr); + + while(1) + { + // clear the display hw.display.Fill(false); - // draw the title text + // draw the title text char cstr[26]; sprintf(cstr, "Uart DMA Fifo Rx"); hw.display.SetCursor(0, 0); hw.display.WriteString(cstr, Font_7x10, true); - // draw the last popped data - sprintf(cstr, "%d", pop); + // draw the last popped data hw.display.SetCursor(0, 12); - hw.display.WriteString(cstr, Font_7x10, true); - - // update the display - hw.display.Update(); + hw.display.WriteString(receive_str, Font_7x10, true); + + // update the display + hw.display.Update(); - // wait 100 ms + // wait 100 ms System::Delay(100); - } + } } diff --git a/examples/uart/Dma_Receive/Makefile b/examples/uart/Dma_Receive/Makefile index 552be648b..e7e5f750f 100644 --- a/examples/uart/Dma_Receive/Makefile +++ b/examples/uart/Dma_Receive/Makefile @@ -7,6 +7,8 @@ CPP_SOURCES = Dma_Receive.cpp # Library Locations LIBDAISY_DIR = ../../.. +OPT=-O0 + # Core location, and generic Makefile. SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core include $(SYSTEM_FILES_DIR)/Makefile diff --git a/src/hid/MidiEvent.h b/src/hid/MidiEvent.h index abe121008..263fea781 100644 --- a/src/hid/MidiEvent.h +++ b/src/hid/MidiEvent.h @@ -1,3 +1,6 @@ +// TODO: make this adjustable +#define SYSEX_BUFFER_LEN 128 + namespace daisy { /** @addtogroup midi MIDI @@ -7,7 +10,7 @@ namespace daisy * @{ */ -/** @defgroup midi_events MIDI_EVENTS +/** @defgroup midi_events MIDI_EVENTS * @{ */ @@ -200,7 +203,7 @@ struct AllNotesOffEvent { int channel; /**< & */ }; -/** Struct containing OmniModeOffEvent data. +/** Struct containing OmniModeOffEvent data. * Can be made from MidiEvent */ struct OmniModeOffEvent @@ -413,4 +416,4 @@ struct MidiEvent /** @} */ // End midi_events /** @} */ // End midi -} //namespace daisy \ No newline at end of file +} //namespace daisy diff --git a/src/hid/midi.cpp b/src/hid/midi.cpp new file mode 100644 index 000000000..71f493d5e --- /dev/null +++ b/src/hid/midi.cpp @@ -0,0 +1,18 @@ +#include "midi.h" + +namespace daisy +{ +static constexpr size_t kDefaultMidiRxBufferSize = 256; + +static uint8_t DMA_BUFFER_MEM_SECTION + default_midi_rx_buffer[kDefaultMidiRxBufferSize]; + +MidiUartTransport::Config::Config() +{ + periph = UartHandler::Config::Peripheral::USART_1; + rx = {DSY_GPIOB, 7}; + tx = {DSY_GPIOB, 6}; + rx_buffer = default_midi_rx_buffer; + rx_buffer_size = kDefaultMidiRxBufferSize; +} +} // namespace daisy diff --git a/src/hid/midi.h b/src/hid/midi.h index 16dcd1dc6..0ff823736 100644 --- a/src/hid/midi.h +++ b/src/hid/midi.h @@ -2,43 +2,63 @@ #ifndef DSY_MIDI_H #define DSY_MIDI_H -// TODO: make this adjustable -#define SYSEX_BUFFER_LEN 128 - #include #include +#include #include "per/uart.h" #include "util/ringbuffer.h" -#include "hid/MidiEvent.h" +#include "util/FIFO.h" +#include "hid/midi_parser.h" #include "hid/usb_midi.h" +#include "sys/dma.h" #include "sys/system.h" namespace daisy { -/** @brief Transport layer for sending and receiving MIDI data over UART +/** @brief Transport layer for sending and receiving MIDI data over UART * @details This is the mode of communication used for TRS and DIN MIDI + * There is an additional 2kB of RAM data used within this class + * for processing bulk data from the UART peripheral * @ingroup midi */ class MidiUartTransport { public: + typedef void (*MidiRxParseCallback)(uint8_t* data, + size_t size, + void* context); + MidiUartTransport() {} ~MidiUartTransport() {} + /** @brief Configuration structure for UART MIDI */ struct Config { UartHandler::Config::Peripheral periph; dsy_gpio_pin rx; dsy_gpio_pin tx; - Config() - { - periph = UartHandler::Config::Peripheral::USART_1; - rx = {DSY_GPIOB, 7}; - tx = {DSY_GPIOB, 6}; - } + /** Pointer to buffer for DMA UART rx byte transfer in background. + * + * @details By default this uses a shared buffer in DMA_BUFFER_MEM_SECTION, + * which can only be utilized for a single UART peripheral. To + * use MIDI with multiple UART peripherals, you must provide your own + * buffer, allocated to a DMA-capable memory section. + */ + uint8_t* rx_buffer; + + /** Size in bytes of rx_buffer. + * + * @details This size determines the maximum Rx bytes readable by the UART in the background. + * By default it's set to the size of the default shared rx_buffer (256 bytes). + * While much smaller sizes can be used, data can get missed if the buffer is too small. + */ + size_t rx_buffer_size; + + Config(); }; + /** @brief Initialization of UART using config struct */ inline void Init(Config config) { UartHandler::Config uart_config; @@ -55,23 +75,74 @@ class MidiUartTransport uart_config.pin_config.rx = config.rx; uart_config.pin_config.tx = config.tx; + rx_buffer = config.rx_buffer; + rx_buffer_size = config.rx_buffer_size; + + /** zero the buffer to ensure emptiness regardless of source memory */ + std::fill(rx_buffer, rx_buffer + rx_buffer_size, 0); + uart_.Init(uart_config); } - inline void StartRx() { uart_.StartRx(); } - inline size_t Readable() { return uart_.Readable(); } - inline uint8_t Rx() { return uart_.PopRx(); } - inline bool RxActive() { return uart_.RxActive(); } - inline void FlushRx() { uart_.FlushRx(); } - inline void Tx(uint8_t* buff, size_t size) { uart_.PollTx(buff, size); } + /** @brief Start the UART peripheral in listening mode. + * This will fill an internal data structure in the background */ + inline void StartRx(MidiRxParseCallback parse_callback, void* context) + { + parse_context_ = context; + parse_callback_ = parse_callback; + dsy_dma_clear_cache_for_buffer((uint8_t*)this, + sizeof(MidiUartTransport)); + uart_.DmaListenStart( + rx_buffer, rx_buffer_size, MidiUartTransport::rxCallback, this); + } + + /** @brief returns whether the UART peripheral is actively listening in the background or not */ + inline bool RxActive() { return uart_.IsListening(); } + + /** @brief This is a no-op for UART transport - Rx is via DMA callback with circular buffer */ + inline void FlushRx() {} + + /** @brief sends the buffer of bytes out of the UART peripheral */ + inline void Tx(uint8_t* buff, size_t size) { uart_.PollTx(buff, size); } private: - UartHandler uart_; + UartHandler uart_; + uint8_t* rx_buffer; + size_t rx_buffer_size; + void* parse_context_; + MidiRxParseCallback parse_callback_; + + /** Static callback for Uart MIDI that occurs when + * new data is available from the peripheral. + * The new data is transferred from the peripheral to the + * MIDI instance's byte FIFO that feeds the MIDI parser. + * + * TODO: Handle UartHandler errors better/at all. + * (If there is a UART error, there's not really any recovery + * option at the moment) + */ + static void rxCallback(uint8_t* data, + size_t size, + void* context, + UartHandler::Result res) + { + /** Read context as transport type */ + MidiUartTransport* transport + = reinterpret_cast(context); + if(res == UartHandler::Result::OK) + { + if(transport->parse_callback_) + { + transport->parse_callback_( + data, size, transport->parse_context_); + } + } + } }; -/** - @brief Simple MIDI Handler \n - Parses bytes from an input into valid MidiEvents. \n +/** + @brief Simple MIDI Handler \n + Parses bytes from an input into valid MidiEvents. \n The MidiEvents fill a FIFO queue that the user can pop messages from. @author shensley @date March 2020 @@ -89,55 +160,47 @@ class MidiHandler typename Transport::Config transport_config; }; - /** Initializes the MidiHandler + /** Initializes the MidiHandler * \param config Configuration structure used to define specifics to the MIDI Handler. */ void Init(Config config) { config_ = config; - transport_.Init(config_.transport_config); - - event_q_.Init(); - incoming_message_.type = MessageLast; - pstate_ = ParserEmpty; + parser_.Init(); } - /** Starts listening on the selected input mode(s). MidiEvent Queue will begin to fill, and can be checked with HasEvents() */ - void StartReceive() { transport_.StartRx(); } + /** Starts listening on the selected input mode(s). + * MidiEvent Queue will begin to fill, and can be checked with HasEvents() */ + void StartReceive() + { + transport_.StartRx(MidiHandler::ParseCallback, this); + } /** Start listening */ void Listen() { - uint32_t now; - now = System::GetNow(); - while(transport_.Readable()) - { - last_read_ = now; - Parse(transport_.Rx()); - } - // In case of UART Error, (particularly // overrun error), UART disables itself. // Flush the buff, and restart. if(!transport_.RxActive()) { - pstate_ = ParserEmpty; + parser_.Reset(); transport_.FlushRx(); StartReceive(); } } - /** Checks if there are unhandled messages in the queue + /** Checks if there are unhandled messages in the queue \return True if there are events to be handled, else false. */ - bool HasEvents() const { return event_q_.readable(); } + bool HasEvents() const { return event_q_.GetNumElements() > 0; } /** Pops the oldest unhandled MidiEvent from the internal queue \return The event to be handled */ - MidiEvent PopEvent() { return event_q_.Read(); } + MidiEvent PopEvent() { return event_q_.PopFront(); } /** SendMessage Send raw bytes as message @@ -147,205 +210,39 @@ class MidiHandler transport_.Tx(bytes, size); } - /** Feed in bytes to state machine from a queue. - Populates internal FIFO queue with MIDI Messages - For example with uart: - midi.Parse(uart.PopRx()); - \param byte & + /** Feed in bytes to parser state machine from an external source. + Populates internal FIFO queue with MIDI Messages. + + \note Normally application code won't need to use this method directly. + \param byte MIDI byte to be parsed */ void Parse(uint8_t byte) { - // reset parser when status byte is received - if((byte & kStatusByteMask) && pstate_ != ParserSysEx) + MidiEvent event; + if(parser_.Parse(byte, &event)) { - pstate_ = ParserEmpty; - } - switch(pstate_) - { - case ParserEmpty: - // check byte for valid Status Byte - if(byte & kStatusByteMask) - { - // Get MessageType, and Channel - incoming_message_.channel = byte & kChannelMask; - incoming_message_.type = static_cast( - (byte & kMessageMask) >> 4); - - // Validate, and move on. - if(incoming_message_.type < MessageLast) - { - pstate_ = ParserHasStatus; - // Mark this status byte as running_status - running_status_ = incoming_message_.type; - - if(running_status_ == SystemCommon) - { - incoming_message_.channel = 0; - //system real time = 1111 1xxx - if(byte & 0x08) - { - incoming_message_.type = SystemRealTime; - running_status_ = SystemRealTime; - incoming_message_.srt_type - = static_cast( - byte & kSystemRealTimeMask); - - //short circuit to start - pstate_ = ParserEmpty; - event_q_.Write(incoming_message_); - } - //system common - else - { - incoming_message_.sc_type - = static_cast(byte - & 0x07); - //sysex - if(incoming_message_.sc_type == SystemExclusive) - { - pstate_ = ParserSysEx; - incoming_message_.sysex_message_len = 0; - } - //short circuit - else if(incoming_message_.sc_type > SongSelect) - { - pstate_ = ParserEmpty; - event_q_.Write(incoming_message_); - } - } - } - } - // Else we'll keep waiting for a valid incoming status byte - } - else - { - // Handle as running status - incoming_message_.type = running_status_; - incoming_message_.data[0] = byte & kDataByteMask; - //check for single byte running status, really this only applies to channel pressure though - if(running_status_ == ChannelPressure - || running_status_ == ProgramChange - || incoming_message_.sc_type == MTCQuarterFrame - || incoming_message_.sc_type == SongSelect) - { - //Send the single byte update - pstate_ = ParserEmpty; - event_q_.Write(incoming_message_); - } - else - { - pstate_ - = ParserHasData0; //we need to get the 2nd byte yet. - } - } - break; - case ParserHasStatus: - if((byte & kStatusByteMask) == 0) - { - incoming_message_.data[0] = byte & kDataByteMask; - if(running_status_ == ChannelPressure - || running_status_ == ProgramChange - || incoming_message_.sc_type == MTCQuarterFrame - || incoming_message_.sc_type == SongSelect) - { - //these are just one data byte, so we short circuit back to start - pstate_ = ParserEmpty; - event_q_.Write(incoming_message_); - } - else - { - pstate_ = ParserHasData0; - } - - //ChannelModeMessages (reserved Control Changes) - if(running_status_ == ControlChange - && incoming_message_.data[0] > 119) - { - incoming_message_.type = ChannelMode; - running_status_ = ChannelMode; - incoming_message_.cm_type - = static_cast( - incoming_message_.data[0] - 120); - } - } - else - { - // invalid message go back to start ;p - pstate_ = ParserEmpty; - } - break; - case ParserHasData0: - if((byte & kStatusByteMask) == 0) - { - incoming_message_.data[1] = byte & kDataByteMask; - - //velocity 0 NoteOns are NoteOffs - if(running_status_ == NoteOn - && incoming_message_.data[1] == 0) - { - incoming_message_.type = NoteOff; - } - - // At this point the message is valid, and we can add this MidiEvent to the queue - event_q_.Write(incoming_message_); - } - else - { - // invalid message go back to start ;p - pstate_ = ParserEmpty; - } - // Regardless, of whether the data was valid or not we go back to empty - // because either the message is queued for handling or its not. - pstate_ = ParserEmpty; - break; - case ParserSysEx: - // end of sysex - if(byte == 0xf7) - { - pstate_ = ParserEmpty; - event_q_.Write(incoming_message_); - } - else if(incoming_message_.sysex_message_len < SYSEX_BUFFER_LEN) - { - incoming_message_ - .sysex_data[incoming_message_.sysex_message_len] - = byte; - incoming_message_.sysex_message_len++; - } - break; - default: break; + event_q_.PushBack(event); } } private: - enum ParserState + Config config_; + Transport transport_; + MidiParser parser_; + FIFO event_q_; + + static void ParseCallback(uint8_t* data, size_t size, void* context) { - ParserEmpty, - ParserHasStatus, - ParserHasData0, - ParserSysEx, - }; - UartHandler uart_; - ParserState pstate_; - MidiEvent incoming_message_; - RingBuffer event_q_; - uint32_t last_read_; // time of last byte - MidiMessageType running_status_; - Config config_; - Transport transport_; - - // Masks to check for message type, and byte content - const uint8_t kStatusByteMask = 0x80; - const uint8_t kMessageMask = 0x70; - const uint8_t kDataByteMask = 0x7F; - const uint8_t kSystemCommonMask = 0xF0; - const uint8_t kChannelMask = 0x0F; - const uint8_t kRealTimeMask = 0xF8; - const uint8_t kSystemRealTimeMask = 0x07; + MidiHandler* handler = reinterpret_cast(context); + for(size_t i = 0; i < size; i++) + { + handler->Parse(data[i]); + } + } }; /** - * @{ + * @{ * @ingroup midi * @brief shorthand accessors for MIDI Handlers * */ diff --git a/src/hid/midi_parser.cpp b/src/hid/midi_parser.cpp new file mode 100644 index 000000000..25decc369 --- /dev/null +++ b/src/hid/midi_parser.cpp @@ -0,0 +1,194 @@ +#include "midi_parser.h" + +using namespace daisy; + +bool MidiParser::Parse(uint8_t byte, MidiEvent* event_out) +{ + // reset parser when status byte is received + bool did_parse = false; + + if((byte & kStatusByteMask) && pstate_ != ParserSysEx) + { + pstate_ = ParserEmpty; + } + switch(pstate_) + { + case ParserEmpty: + // check byte for valid Status Byte + if(byte & kStatusByteMask) + { + // Get MessageType, and Channel + incoming_message_.channel = byte & kChannelMask; + incoming_message_.type + = static_cast((byte & kMessageMask) >> 4); + if((byte & 0xF8) == 0xF8) + incoming_message_.type = SystemRealTime; + + // Validate, and move on. + if(incoming_message_.type < MessageLast) + { + pstate_ = ParserHasStatus; + + if(incoming_message_.type == SystemCommon) + { + incoming_message_.channel = 0; + incoming_message_.sc_type + = static_cast(byte & 0x07); + //sysex + if(incoming_message_.sc_type == SystemExclusive) + { + pstate_ = ParserSysEx; + incoming_message_.sysex_message_len = 0; + } + //short circuit + else if(incoming_message_.sc_type > SongSelect) + { + pstate_ = ParserEmpty; + if(event_out != nullptr) + { + *event_out = incoming_message_; + } + did_parse = true; + } + } + else if(incoming_message_.type == SystemRealTime) + { + incoming_message_.srt_type + = static_cast( + byte & kSystemRealTimeMask); + + //short circuit to start + pstate_ = ParserEmpty; + if(event_out != nullptr) + { + *event_out = incoming_message_; + } + did_parse = true; + } + else // Channel Voice or Channel Mode + { + running_status_ = incoming_message_.type; + } + } + // Else we'll keep waiting for a valid incoming status byte + } + else + { + // Handle as running status + incoming_message_.type = running_status_; + incoming_message_.data[0] = byte & kDataByteMask; + //check for single byte running status, really this only applies to channel pressure though + if(running_status_ == ChannelPressure + || running_status_ == ProgramChange + || incoming_message_.sc_type == MTCQuarterFrame + || incoming_message_.sc_type == SongSelect) + { + //Send the single byte update + pstate_ = ParserEmpty; + if(event_out != nullptr) + { + *event_out = incoming_message_; + } + did_parse = true; + } + else + { + pstate_ = ParserHasData0; //we need to get the 2nd byte yet. + } + } + break; + case ParserHasStatus: + if((byte & kStatusByteMask) == 0) + { + incoming_message_.data[0] = byte & kDataByteMask; + if(running_status_ == ChannelPressure + || running_status_ == ProgramChange + || incoming_message_.sc_type == MTCQuarterFrame + || incoming_message_.sc_type == SongSelect) + { + //these are just one data byte, so we short circuit back to start + pstate_ = ParserEmpty; + if(event_out != nullptr) + { + *event_out = incoming_message_; + } + did_parse = true; + } + else + { + pstate_ = ParserHasData0; + } + + //ChannelModeMessages (reserved Control Changes) + if(running_status_ == ControlChange + && incoming_message_.data[0] > 119) + { + incoming_message_.type = ChannelMode; + running_status_ = ChannelMode; + incoming_message_.cm_type = static_cast( + incoming_message_.data[0] - 120); + } + } + else + { + // invalid message go back to start ;p + pstate_ = ParserEmpty; + } + break; + case ParserHasData0: + if((byte & kStatusByteMask) == 0) + { + incoming_message_.data[1] = byte & kDataByteMask; + + //velocity 0 NoteOns are NoteOffs + if(running_status_ == NoteOn && incoming_message_.data[1] == 0) + { + incoming_message_.type = NoteOff; + } + + // At this point the message is valid, and we can complete this MidiEvent + if(event_out != nullptr) + { + *event_out = incoming_message_; + } + did_parse = true; + } + else + { + // invalid message go back to start ;p + pstate_ = ParserEmpty; + } + // Regardless, of whether the data was valid or not we go back to empty + // because either the message is queued for handling or its not. + pstate_ = ParserEmpty; + break; + case ParserSysEx: + // end of sysex + if(byte == 0xf7) + { + pstate_ = ParserEmpty; + if(event_out != nullptr) + { + *event_out = incoming_message_; + } + did_parse = true; + } + else if(incoming_message_.sysex_message_len < SYSEX_BUFFER_LEN) + { + incoming_message_ + .sysex_data[incoming_message_.sysex_message_len] + = byte; + incoming_message_.sysex_message_len++; + } + break; + default: break; + } + + return did_parse; +} + +void MidiParser::Reset() +{ + pstate_ = ParserEmpty; + incoming_message_.type = MessageLast; +} diff --git a/src/hid/midi_parser.h b/src/hid/midi_parser.h new file mode 100644 index 000000000..976f72dcb --- /dev/null +++ b/src/hid/midi_parser.h @@ -0,0 +1,64 @@ +#pragma once +#ifndef DSY_MIDI_PARSER_H +#define DSY_MIDI_PARSER_H + +#include +#include +#include "hid/MidiEvent.h" + +namespace daisy +{ +/** @brief Utility class for parsing raw byte streams into MIDI messages + * @details Implemented as a state machine designed to parse one byte at a time + * @ingroup midi + */ +class MidiParser +{ + public: + MidiParser(){}; + ~MidiParser() {} + + inline void Init() { Reset(); } + + /** + * @brief Parse one MIDI byte. If the byte completes a parsed event, + * its value will be assigned to the dereferenced output pointer. + * Otherwise, status is preserved in anticipation of the next sequential + * byte. Return value indicates if a new event was parsed or not. + * + * @param byte Raw MIDI byte to parse + * @param event_out Pointer to output event object, value assigned on parse success + * @return true If a new event was parsed + * @return false If no new event was parsed + */ + bool Parse(uint8_t byte, MidiEvent *event_out); + + /** + * @brief Reset parser to default state + */ + void Reset(); + + private: + enum ParserState + { + ParserEmpty, + ParserHasStatus, + ParserHasData0, + ParserSysEx, + }; + + ParserState pstate_; + MidiEvent incoming_message_; + MidiMessageType running_status_; + + // Masks to check for message type, and byte content + const uint8_t kStatusByteMask = 0x80; + const uint8_t kMessageMask = 0x70; + const uint8_t kDataByteMask = 0x7F; + const uint8_t kChannelMask = 0x0F; + const uint8_t kSystemRealTimeMask = 0x07; +}; + +} // namespace daisy + +#endif diff --git a/src/hid/usb_midi.cpp b/src/hid/usb_midi.cpp index c77bfcd0a..811b1c404 100644 --- a/src/hid/usb_midi.cpp +++ b/src/hid/usb_midi.cpp @@ -10,15 +10,20 @@ class MidiUsbTransport::Impl public: void Init(Config config); - void StartRx() { rx_active_ = true; } - size_t Readable() { return rx_buffer_.readable(); } - uint8_t Rx() { return rx_buffer_.Read(); } - bool RxActive() { return rx_active_; } - void FlushRx() { rx_buffer_.Flush(); } - void Tx(uint8_t* buffer, size_t size); + void StartRx(MidiRxParseCallback callback, void* context) + { + rx_active_ = true; + parse_callback_ = callback; + parse_context_ = context; + } + + bool RxActive() { return rx_active_; } + void FlushRx() { rx_buffer_.Flush(); } + void Tx(uint8_t* buffer, size_t size); void UsbToMidi(uint8_t* buffer, uint8_t length); void MidiToUsb(uint8_t* buffer, size_t length); + void Parse(); private: void MidiToUsbSingle(uint8_t* buffer, size_t length); @@ -32,6 +37,8 @@ class MidiUsbTransport::Impl bool rx_active_; // This corresponds to 256 midi messages RingBuffer rx_buffer_; + MidiRxParseCallback parse_callback_; + void* parse_context_; // simple, self-managed buffer uint8_t tx_buffer_[kBufferSize]; @@ -68,6 +75,7 @@ void ReceiveCallback(uint8_t* buffer, uint32_t* length) size_t remaining_bytes = *length - i; uint8_t packet_length = remaining_bytes > 4 ? 4 : remaining_bytes; midi_usb_handle.UsbToMidi(buffer + i, packet_length); + midi_usb_handle.Parse(); } } } @@ -275,6 +283,20 @@ void MidiUsbTransport::Impl::MidiToUsb(uint8_t* buffer, size_t size) } } +void MidiUsbTransport::Impl::Parse() +{ + if(parse_callback_) + { + uint8_t bytes[kBufferSize]; + size_t i = 0; + while(!rx_buffer_.isEmpty()) + { + bytes[i++] = rx_buffer_.Read(); + } + parse_callback_(bytes, i, parse_context_); + } +} + //////////////////////////////////////////////// // MidiUsbTransport -> MidiUsbTransport::Impl //////////////////////////////////////////////// @@ -285,19 +307,9 @@ void MidiUsbTransport::Init(MidiUsbTransport::Config config) pimpl_->Init(config); } -void MidiUsbTransport::StartRx() -{ - pimpl_->StartRx(); -} - -size_t MidiUsbTransport::Readable() -{ - return pimpl_->Readable(); -} - -uint8_t MidiUsbTransport::Rx() +void MidiUsbTransport::StartRx(MidiRxParseCallback callback, void* context) { - return pimpl_->Rx(); + pimpl_->StartRx(callback, context); } bool MidiUsbTransport::RxActive() diff --git a/src/hid/usb_midi.h b/src/hid/usb_midi.h index 99936a006..78e5cd983 100644 --- a/src/hid/usb_midi.h +++ b/src/hid/usb_midi.h @@ -14,8 +14,9 @@ namespace daisy class MidiUsbTransport { public: - // MidiUsbTransport() {} - ~MidiUsbTransport() {} + typedef void (*MidiRxParseCallback)(uint8_t* data, + size_t size, + void* context); struct Config { @@ -44,16 +45,15 @@ class MidiUsbTransport void Init(Config config); - void StartRx(); - size_t Readable(); - uint8_t Rx(); - bool RxActive(); - void FlushRx(); - void Tx(uint8_t* buffer, size_t size); + void StartRx(MidiRxParseCallback callback, void* context); + bool RxActive(); + void FlushRx(); + void Tx(uint8_t* buffer, size_t size); class Impl; MidiUsbTransport() : pimpl_(nullptr) {} + ~MidiUsbTransport() {} MidiUsbTransport(const MidiUsbTransport& other) = default; MidiUsbTransport& operator=(const MidiUsbTransport& other) = default; diff --git a/src/per/uart.cpp b/src/per/uart.cpp index 07fef2662..7f9301dc4 100644 --- a/src/per/uart.cpp +++ b/src/per/uart.cpp @@ -1,5 +1,7 @@ #include +#include "stm32h7xx_ll_dma.h" #include "per/uart.h" +#include "sys/dma.h" #include "util/ringbuffer.h" #include "util/scopedirqblocker.h" @@ -13,8 +15,8 @@ using namespace daisy; #define UART_RX_BUFF_SIZE 256 // the fifo buffer to be DMA read into -typedef RingBuffer UartRingBuffer; -static UartRingBuffer DMA_BUFFER_MEM_SECTION uart_dma_fifo; +// typedef RingBuffer UartRingBuffer; +// static UartRingBuffer DMA_BUFFER_MEM_SECTION uart_dma_fifo; static void Error_Handler() { @@ -69,6 +71,34 @@ class UartHandler::Impl EndCallbackFunctionPtr end_callback, void* callback_context); + /** Starts the DMA Reception in "Listen" mode. + * In this mode the DMA is configured for circular + * behavior, and the IDLE interrupt is enabled. + * + * At TC, HT, and IDLE interrupts data must be processed. + * + * Size must be set so that at maximum bandwidth, the software + * has time to process N bytes before the next circular IRQ is fired + * + */ + Result DmaListenStart(uint8_t* buff, + size_t size, + CircularRxCallbackFunctionPtr cb, + void* callback_context); + + /** Stops the DMA Reception in "Listen" mode + * by stopping the DMA reception, disabling the IDLE + * IRQ, and setting listen_mode_ to false. + */ + Result DmaListenStop(); + + /** Returns the state of the listen_mode var. + * set when DmaListen starts, and cleared + * when DmaListen stops. Used to detect if the + * reception is active. + */ + bool IsListening() const; + Result StartDmaTx(uint8_t* buff, size_t size, @@ -89,7 +119,7 @@ class UartHandler::Impl static void QueueDmaTransfer(size_t uart_idx, const UartDmaJob& job); static bool IsDmaTransferQueuedFor(size_t uart_idx); - static void DmaReceiveFifoEndCallback(void* context, Result res); + // static void DmaReceiveFifoEndCallback(void* context, Result res); Result SetDmaPeripheral(); @@ -99,16 +129,6 @@ class UartHandler::Impl Result DeInitPins(); - void HandleFifo(); - - Result DmaReceiveFifo(); - - Result FlushFifo(); - - uint8_t PopFifo(); - - size_t ReadableFifo(); - int CheckError(); static constexpr uint8_t kNumUartWithDma = 9; @@ -117,15 +137,21 @@ class UartHandler::Impl static EndCallbackFunctionPtr next_end_callback_; static void* next_callback_context_; + /** Not static -- any UART can use this + * until we had dynamic DMA stream handling + * this will consume the sole DMA stream for UART Rx + */ + CircularRxCallbackFunctionPtr circular_rx_callback_; + void* circular_rx_context_; + uint8_t* circular_rx_buff_; + size_t circular_rx_total_size_; + size_t circular_rx_last_pos_; + bool listener_mode_; + Config config_; UART_HandleTypeDef huart_; DMA_HandleTypeDef hdma_rx_; DMA_HandleTypeDef hdma_tx_; - - bool using_fifo_; - UartRingBuffer* dma_fifo_rx_; // pointer to FIFO DMA reads into - UartRingBuffer queue_rx_; // double buffer ( user reads from ) - size_t rx_last_pos_; }; @@ -231,7 +257,7 @@ UartHandler::Result UartHandler::Impl::Init(const UartHandler::Config& config) huart_.Init.HwFlowCtl = UART_HWCONTROL_NONE; huart_.Init.OverSampling = UART_OVERSAMPLING_16; huart_.Init.OneBitSampling = UART_ONE_BIT_SAMPLE_DISABLE; - huart_.Init.ClockPrescaler = UART_PRESCALER_DIV1; + huart_.Init.ClockPrescaler = UART_PRESCALER_DIV2; huart_.AdvancedInit.AdvFeatureInit = UART_ADVFEATURE_NO_INIT; if(HAL_UART_Init(&huart_) != HAL_OK) @@ -253,11 +279,8 @@ UartHandler::Result UartHandler::Impl::Init(const UartHandler::Config& config) return Result::ERR; } - // Fifo stuff - using_fifo_ = false; - dma_fifo_rx_ = &uart_dma_fifo; - dma_fifo_rx_->Init(); - queue_rx_.Init(); + /** New listener mode to replace old "Fifo" stuff */ + listener_mode_ = false; return Result::OK; } @@ -313,7 +336,7 @@ UartHandler::Result UartHandler::Impl::InitDma(bool rx, bool tx) 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 = using_fifo_ ? DMA_CIRCULAR : DMA_NORMAL; + 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; @@ -349,41 +372,9 @@ UartHandler::Result UartHandler::Impl::InitDma(bool rx, bool tx) __HAL_LINKDMA(&huart_, hdmatx, hdma_tx_); } - if(using_fifo_) - { - // Disable HalfTransfer Interrupt - ((DMA_Stream_TypeDef*)hdma_rx_.Instance)->CR &= ~(DMA_SxCR_HTIE); - - //enable idle interrupts - __HAL_UART_ENABLE_IT(&huart_, UART_IT_IDLE); - } - return UartHandler::Result::OK; } -// formerly known as "UARTRxComplete()" -void UartHandler::Impl::HandleFifo() -{ - size_t len, cur_pos; - - //get current write pointer - cur_pos = (UART_RX_BUFF_SIZE - - ((DMA_Stream_TypeDef*)huart_.hdmarx->Instance)->NDTR) - & (UART_RX_BUFF_SIZE - 1); - - //calculate how far the DMA write pointer has moved - len = (cur_pos - rx_last_pos_ + UART_RX_BUFF_SIZE) % UART_RX_BUFF_SIZE; - - dma_fifo_rx_->Advance(len); - rx_last_pos_ = cur_pos; - - // Copy to queue fifo we don't want to use primary fifo to avoid - // changes to the buffer while its being processed - uint8_t processbuf[256]; - dma_fifo_rx_->ImmediateRead(processbuf, len); - queue_rx_.Overwrite(processbuf, len); -} - void UartHandler::Impl::DmaTransferFinished(UART_HandleTypeDef* huart, UartHandler::Result result) { @@ -499,6 +490,67 @@ UartHandler::Result UartHandler::Impl::DmaTransmit( buff, size, start_callback, end_callback, callback_context); } + +UartHandler::Result +UartHandler::Impl::DmaListenStart(uint8_t* buff, + size_t size, + UartHandler::CircularRxCallbackFunctionPtr cb, + void* callback_context) +{ + /** Set internal data*/ + circular_rx_buff_ = buff; + circular_rx_total_size_ = size; + circular_rx_callback_ = cb; + circular_rx_context_ = callback_context; + circular_rx_last_pos_ = 0; + listener_mode_ = true; + + /** Initialize DMA Rx i + * TODO: Update when dynamic DMA Stream acquisition is added + */ + 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_CIRCULAR; + hdma_rx_.Init.Priority = DMA_PRIORITY_VERY_HIGH; + hdma_rx_.Init.FIFOMode = DMA_FIFOMODE_DISABLE; + hdma_rx_.Init.Direction = DMA_PERIPH_TO_MEMORY; + SetDmaPeripheral(); + + if(HAL_DMA_Init(&hdma_rx_) != HAL_OK) + return UartHandler::Result::ERR; + __HAL_LINKDMA(&huart_, hdmarx, hdma_rx_); + + // enable idle interrupts so that TC, HT, and IDLE are triggers + __HAL_UART_ENABLE_IT(&huart_, UART_IT_IDLE); + + /** cache maintanence to allow memory from cache-able regions */ + dsy_dma_invalidate_cache_for_buffer(buff, size); + if(HAL_UART_Receive_DMA(&huart_, buff, size) != HAL_OK) + return UartHandler::Result::ERR; + dma_active_peripheral_ = int(config_.periph); + return UartHandler::Result::OK; +} + +UartHandler::Result UartHandler::Impl::DmaListenStop() +{ + /** Set Listener Mode */ + listener_mode_ = false; + /** Disable IDLE IRQ*/ + __HAL_UART_DISABLE_IT(&huart_, UART_IT_IDLE); + /** Stop DMA */ + if(HAL_UART_DMAStop(&huart_) != HAL_OK) + return UartHandler::Result::ERR; + return UartHandler::Result::OK; +} + +bool UartHandler::Impl::IsListening() const +{ + return listener_mode_; +} + UartHandler::Result UartHandler::Impl::StartDmaTx( uint8_t* buff, size_t size, @@ -543,6 +595,8 @@ UartHandler::Result UartHandler::Impl::DmaReceive( UartHandler::EndCallbackFunctionPtr end_callback, void* callback_context) { + /** Normal transfer is not listener mode */ + listener_mode_ = false; // if dma is currently running - queue a job if(IsDmaBusy()) { @@ -605,6 +659,7 @@ UartHandler::Result UartHandler::Impl::StartDmaRx( return UartHandler::Result::OK; } + UartHandler::Result UartHandler::Impl::BlockingReceive(uint8_t* buff, size_t size, uint32_t timeout) { @@ -626,48 +681,6 @@ UartHandler::Result UartHandler::Impl::BlockingTransmit(uint8_t* buff, return Result::OK; } -//gets called if the buffer is overrun, transfer buffers and restart DMA -void UartHandler::Impl::DmaReceiveFifoEndCallback(void* context, - UartHandler::Result res) -{ - // when the DMA hits the end in circular mode, it's considered an ERROR - // for some reason it sometimes comes up as OK as well though - // for now we'll catch the errors and just reset - if(res == UartHandler::Result::OK || res == UartHandler::Result::ERR) - { - UartHandler::Impl* handle = (UartHandler::Impl*)context; - handle->HandleFifo(); - HAL_UART_Init(&handle->huart_); - handle->DmaReceiveFifo(); - } -} - -UartHandler::Result UartHandler::Impl::DmaReceiveFifo() -{ - using_fifo_ = true; - return DmaReceive((uint8_t*)dma_fifo_rx_->GetMutableBuffer(), - UART_RX_BUFF_SIZE, - NULL, - UartHandler::Impl::DmaReceiveFifoEndCallback, - (void*)this); -} - -UartHandler::Result UartHandler::Impl::FlushFifo() -{ - queue_rx_.Flush(); - return Result::OK; -} - -uint8_t UartHandler::Impl::PopFifo() -{ - return queue_rx_.Read(); -} - -size_t UartHandler::Impl::ReadableFifo() -{ - return queue_rx_.readable(); -} - int UartHandler::Impl::CheckError() { return HAL_UART_GetError(&huart_); @@ -950,15 +963,80 @@ extern "C" void dsy_uart_global_init() UartHandler::Impl::GlobalInit(); } +/** static handler for Listener Mode of Rx to handle + * non-aligned transfers during DMA Reception. + * + * this is the equivalent of what the old FifoHandler stuff + * did, but removes all of the fifo'ing, and replaces it with a user + * callback. The MIDI UART Transport is an example of how this might be used. + */ +static void UART_CheckRxListener(UartHandler::Impl* handle) +{ + size_t pos; + size_t old_pos = handle->circular_rx_last_pos_; + + /** calculate pos. in buffer + * TODO: make flexible for other DMA STreams + * + */ + uint8_t* buffer = handle->circular_rx_buff_; + pos = handle->circular_rx_total_size_ + - LL_DMA_GetDataLength(DMA1, LL_DMA_STREAM_5); + if(handle->circular_rx_callback_ && pos != old_pos) + { + if(pos > old_pos) + { + /** Typical lineary handling */ + { + /** Cache Invalidate */ + dsy_dma_invalidate_cache_for_buffer(&buffer[old_pos], + pos - old_pos); + handle->circular_rx_callback_(&buffer[old_pos], + pos - old_pos, + handle->circular_rx_context_, + UartHandler::Result::OK); + } + } + else + { + /** Data handled wrapped back around to the beginning in the period of time */ + if(handle->circular_rx_callback_) + { + /** First from old pos to the new end of mem */ + size_t rx_size = handle->circular_rx_total_size_ - old_pos; + dsy_dma_invalidate_cache_for_buffer(&buffer[old_pos], rx_size); + handle->circular_rx_callback_(&buffer[old_pos], + rx_size, + handle->circular_rx_context_, + UartHandler::Result::OK); + + /** then again from beginning to new pos */ + dsy_dma_invalidate_cache_for_buffer(&buffer[0], pos); + handle->circular_rx_callback_(&buffer[0], + pos, + handle->circular_rx_context_, + UartHandler::Result::OK); + } + } + handle->circular_rx_last_pos_ = pos; /**< update position */ + if(handle->circular_rx_last_pos_ == handle->circular_rx_total_size_) + { + handle->circular_rx_last_pos_ = 0; + } + } +} + // HAL Interrupts. void UART_IRQHandler(UartHandler::Impl* handle) { HAL_UART_IRQHandler(&handle->huart_); - if(__HAL_UART_GET_FLAG(&handle->huart_, UART_FLAG_IDLE) - && handle->using_fifo_) + if(handle->listener_mode_ + && __HAL_UART_GET_FLAG(&handle->huart_, UART_FLAG_IDLE)) { - handle->HandleFifo(); + /** find position, and call callback */ + UART_CheckRxListener(handle); + /** Clear IDLE Interrupt flag */ handle->huart_.Instance->ICR = UART_FLAG_IDLE; } } @@ -1007,11 +1085,34 @@ extern "C" void HAL_UART_TxCpltCallback(UART_HandleTypeDef* huart) extern "C" void HAL_UART_RxCpltCallback(UART_HandleTypeDef* huart) { - UartHandler::Impl::DmaTransferFinished(huart, UartHandler::Result::OK); + auto* handle = MapInstanceToHandle(huart->Instance); + if(handle->listener_mode_) + { + // Find data range, and callback + UART_CheckRxListener(handle); + } + else + { + UartHandler::Impl::DmaTransferFinished(huart, UartHandler::Result::OK); + } +} + +extern "C" void HAL_UART_RxHalfCpltCallback(UART_HandleTypeDef* huart) +{ + /** Only need the HalfCplt for circular DMA mode */ + auto* handle = MapInstanceToHandle(huart->Instance); + if(handle->listener_mode_) + { + UART_CheckRxListener(handle); + } } extern "C" void HAL_UART_ErrorCallback(UART_HandleTypeDef* huart) { + /** TODO: This hooks into the "Normal" DMA completion, + * might want to change this to have a different fallthrough + * for "listener_mode_" + */ UartHandler::Impl::DmaTransferFinished(huart, UartHandler::Result::ERR); } @@ -1080,24 +1181,23 @@ UartHandler::DmaReceive(uint8_t* buff, buff, size, start_callback, end_callback, callback_context); } -UartHandler::Result UartHandler::DmaReceiveFifo() -{ - return pimpl_->DmaReceiveFifo(); -} - -UartHandler::Result UartHandler::FlushFifo() +UartHandler::Result +UartHandler::DmaListenStart(uint8_t* buff, + size_t size, + UartHandler::CircularRxCallbackFunctionPtr cb, + void* callback_context) { - return pimpl_->FlushFifo(); + return pimpl_->DmaListenStart(buff, size, cb, callback_context); } -uint8_t UartHandler::PopFifo() +UartHandler::Result UartHandler::DmaListenStop() { - return pimpl_->PopFifo(); + return pimpl_->DmaListenStop(); } -size_t UartHandler::ReadableFifo() +bool UartHandler::IsListening() const { - return pimpl_->ReadableFifo(); + return pimpl_->IsListening(); } int UartHandler::CheckError() @@ -1116,23 +1216,3 @@ UartHandler::Result UartHandler::PollTx(uint8_t* buff, size_t size) { return pimpl_->BlockingTransmit(buff, size, 10); } - -uint8_t UartHandler::PopRx() -{ - return pimpl_->PopFifo(); -} - -UartHandler::Result UartHandler::StartRx() -{ - return pimpl_->DmaReceiveFifo(); -} - -UartHandler::Result UartHandler::FlushRx() -{ - return pimpl_->FlushFifo(); -} - -size_t UartHandler::Readable() -{ - return pimpl_->ReadableFifo(); -} diff --git a/src/per/uart.h b/src/per/uart.h index 197b45d9a..8a415fdc8 100644 --- a/src/per/uart.h +++ b/src/per/uart.h @@ -1,7 +1,5 @@ /* TODO -- UART1 defaults to DMA, add flexible config for DMA on all periphs -- Transmit function improvements. - Overflow handling, etc. for Rx Queue. */ @@ -81,8 +79,7 @@ class UartHandler stopbits = StopBits::BITS_1; parity = Parity::NONE; wordlength = WordLength::BITS_8; - baudrate = 4800; - // baudrate = 31250; + baudrate = 31250; } Peripheral periph; @@ -117,11 +114,27 @@ class UartHandler /** Returns the current config. */ const Config& GetConfig() const; - /** A callback to be executed right before a dma transfer is started. */ + /** A callback to be executed right before a standard dma transfer is started. */ typedef void (*StartCallbackFunctionPtr)(void* context); - /** A callback to be executed after a dma transfer is completed. */ + /** A callback to be executed after a standard dma transfer is completed. */ typedef void (*EndCallbackFunctionPtr)(void* context, Result result); + /** A callback to be executed when using circular/listening mode + * includes a callback context, as well as the data to be handled + * This fires either after half of the size of the user-defined buffer + * has been transferred from peripheral to memory, or after an IDLE frame + * is detected. + * + * @param data byte-buffer to fill with data + * @param size size of the "data" byte buffer + * @param context user-defined context variable to pass state to the callback + * @param result state of the UART Handler result, should be OK if things are OK. + */ + typedef void (*CircularRxCallbackFunctionPtr)(uint8_t* data, + size_t size, + void* context, + Result result); + /** Blocking transmit \param buff input buffer \param size buffer size @@ -171,52 +184,40 @@ class UartHandler UartHandler::EndCallbackFunctionPtr end_callback, void* callback_context); + /** Starts the DMA Reception in "Listen" mode. + * In this mode the DMA is configured for circular + * behavior, and the IDLE interrupt is enabled. + * + * At TC, HT, and IDLE interrupts data must be processed. + * + * Size must be set so that at maximum bandwidth, the software + * has time to process N bytes before the next circular IRQ is fired + * + * @param buff buffer of data accessible by DMA. + * @param size size of buffer + * @param cb callback that happens containing new bytes to process in software + * @param callback_context pointer to user-defined data accessible from callback + */ + Result DmaListenStart(uint8_t* buff, + size_t size, + CircularRxCallbackFunctionPtr cb, + void* callback_context); + + /** Stops the DMA Reception during listen mode */ + Result DmaListenStop(); + + /** Returns whether listen the DmaListen mode is active or not */ + bool IsListening() const; + /** \return the result of HAL_UART_GetError() to the user. */ int CheckError(); - /** Start the DMA Receive with a double buffered FIFO - \return OK or ERR - */ - Result DmaReceiveFifo(); - - /** Flush all of the data from the fifo - \return OK or ERR - */ - Result FlushFifo(); - - /** Get the top item off of the FIFO - \return Top item from the FIFO - */ - uint8_t PopFifo(); - - /** How much data is in the FIFO - \return number of elements ready to pop from FIFO - */ - size_t ReadableFifo(); - /** Will be deprecated soon! Wrapper for BlockingTransmit */ int PollReceive(uint8_t* buff, size_t size, uint32_t timeout); /** Will be deprecated soon! Wrapper for BlockingTransmit */ Result PollTx(uint8_t* buff, size_t size); - /** Will be deprecated soon! Wrapper for DmaReceiveFifo */ - Result StartRx(); - - /** Will be deprecated soon! - \return true. New DMA will always restart itself. - */ - bool RxActive() { return true; } - - /** Will be deprecated soon! Wrapper for FlushFifo */ - Result FlushRx(); - - /** Will be deprecated soon! Wrapper PopFifo */ - uint8_t PopRx(); - - /** Will be deprecated soon! Wrapper for ReadableFifo */ - size_t Readable(); - class Impl; /**< & */ private: diff --git a/tests/Midi_gtest.cpp b/tests/Midi_gtest.cpp index 28b15a037..68a3be796 100644 --- a/tests/Midi_gtest.cpp +++ b/tests/Midi_gtest.cpp @@ -328,8 +328,8 @@ TEST_F(MidiTest, allNotesOff) { for(uint8_t chn = 0; chn < 16; chn++) { - uint8_t msg[] = {(uint8_t)(0x80 + (3 << 4) + chn), 123, 0}; - MidiEvent event = ParseAndPop(msg, 3); + uint8_t msg[] = {(uint8_t)(0x80 + (3 << 4) + chn), 123, 0}; + MidiEvent event = ParseAndPop(msg, 3); AllNotesOffEvent allOnEvent = event.AsAllNotesOff(); EXPECT_EQ((uint8_t)event.type, ChannelMode); @@ -616,9 +616,44 @@ TEST_F(MidiTest, runningStatus) EXPECT_EQ(ccEvent.value, i); } + EXPECT_FALSE(midi.HasEvents()); } +/** This tests that SystemRealTime (clock, etc.) messages + * don't clear the running status +*/ +TEST_F(MidiTest, runningStatSysRealtime) +{ + /** Clock messages between running status notes */ + uint8_t realtime_mixed_bytes[] = { + 0x90, /**< Note On Status */ + 0x24, /**< Note Number 36 */ + 0x40, /**< velocity 40 */ + 0xf8, /**, Timing Clock */ + 0xf8, /**, Timing Clock */ + 0xf8, /**, Timing Clock */ + 0x24, /**< Note Number 36 */ + 0x00, /**< velocity 00 */ + }; + /** parse */ + for(int i = 0; i < 8; i++) + { + midi.Parse(realtime_mixed_bytes[i]); + } + + auto noteon1 = midi.PopEvent(); + EXPECT_EQ(noteon1.type, NoteOn); + auto clk1 = midi.PopEvent(); + EXPECT_EQ(clk1.type, SystemRealTime); + auto clk2 = midi.PopEvent(); + EXPECT_EQ(clk2.type, SystemRealTime); + auto clk3 = midi.PopEvent(); + EXPECT_EQ(clk3.type, SystemRealTime); + auto noteon2 = midi.PopEvent(); + EXPECT_EQ(noteon2.type, NoteOff); +} + // ================ Bad Data ================ TEST_F(MidiTest, badData) @@ -717,11 +752,11 @@ TEST_F(MidiTest, singleByteRunningStatusTest) for(uint8_t i = 0; i < 128; i++) { - MidiEvent ev = ParseAndPop(&i, 1); + MidiEvent ev = ParseAndPop(&i, 1); EXPECT_EQ(ev.type, ChannelPressure); - ChannelPressureEvent chPressure = ev.AsChannelPressure(); + ChannelPressureEvent chPressure = ev.AsChannelPressure(); EXPECT_EQ(chPressure.channel, 3); EXPECT_EQ(chPressure.pressure, i); @@ -735,52 +770,15 @@ TEST_F(MidiTest, singleByteRunningStatusTest) for(uint8_t i = 0; i < 128; i++) { - MidiEvent ev = ParseAndPop(&i, 1); + MidiEvent ev = ParseAndPop(&i, 1); EXPECT_EQ(ev.type, ProgramChange); - ProgramChangeEvent progChange = ev.AsProgramChange(); + ProgramChangeEvent progChange = ev.AsProgramChange(); EXPECT_EQ(progChange.channel, 3); EXPECT_EQ(progChange.program, i); } EXPECT_FALSE(midi.HasEvents()); - - // == MTC Quarter Frame == - status = 0xF1; // quarter frame - Parse(&status, 1); - - for(uint8_t i = 0; i < 128; i++) - { - MidiEvent ev = ParseAndPop(&i, 1); - - EXPECT_EQ(ev.type, SystemCommon); - EXPECT_EQ(ev.sc_type, MTCQuarterFrame); - - MTCQuarterFrameEvent qfEv = ev.AsMTCQuarterFrame(); - - EXPECT_EQ(qfEv.value, i & 0x0F); - EXPECT_EQ(qfEv.message_type, (i & 0x70) >> 4); - } - - EXPECT_FALSE(midi.HasEvents()); - - // == Song Select == - status = 0xF3; // song select - Parse(&status, 1); - - for(uint8_t i = 0; i < 128; i++) - { - MidiEvent ev = ParseAndPop(&i, 1); - - EXPECT_EQ(ev.type, SystemCommon); - EXPECT_EQ(ev.sc_type, SongSelect); - - SongSelectEvent songSel = ev.AsSongSelect(); - - EXPECT_EQ(songSel.song, i); - } - - EXPECT_FALSE(midi.HasEvents()); } \ No newline at end of file diff --git a/tests/libDaisyCombined.cpp b/tests/libDaisyCombined.cpp index eb2845f27..83d6844a0 100644 --- a/tests/libDaisyCombined.cpp +++ b/tests/libDaisyCombined.cpp @@ -4,3 +4,4 @@ #include "util/MappedValue.cpp" #include "util/oled_fonts.c" #include "per/qspi.cpp" +#include "hid/midi_parser.cpp"