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"