From 7b5629db321d00c3b075098f75041adddbb29646 Mon Sep 17 00:00:00 2001 From: heck <heck@heck.live> Date: Sat, 26 Oct 2024 05:11:26 +0200 Subject: [PATCH] starting point: usb midi device class drivers only copied and renamed so far, but still only serve device class midi. working. --- examples/main_launchpad.cc | 141 +++++++++++ src/usb_midi_launchpad_transport.cc | 362 ++++++++++++++++++++++++++++ src/usb_midi_launchpad_transport.hh | 60 +++++ src/usbh_midi_launchpad.c | 236 ++++++++++++++++++ src/usbh_midi_launchpad.h | 64 +++++ 5 files changed, 863 insertions(+) create mode 100644 examples/main_launchpad.cc create mode 100644 src/usb_midi_launchpad_transport.cc create mode 100644 src/usb_midi_launchpad_transport.hh create mode 100644 src/usbh_midi_launchpad.c create mode 100644 src/usbh_midi_launchpad.h diff --git a/examples/main_launchpad.cc b/examples/main_launchpad.cc new file mode 100644 index 0000000..f68b3c8 --- /dev/null +++ b/examples/main_launchpad.cc @@ -0,0 +1,141 @@ +/** Example of setting reading MIDI Input via USB Host +* +* +* This requires a USB-A connector +* +* This example will also log incoming messages to the serial port for general MIDI troubleshooting +*/ +#include "usb_midi_launchpad_transport.hh" +#include "daisy_seed.h" +#include "usbh_midi_launchpad.h" + +/** This prevents us from having to type "daisy::" in front of a lot of things. */ +using namespace daisy; +using MidiUsbLaunchpadHandler = MidiHandler<UsbMidiLaunchpadTransport>; + + +/** Global Hardware access */ +DaisySeed hw{}; +//MidiUsbHandler midi; +MidiUsbLaunchpadHandler midi; +USBHostHandle usbHost; + +/** FIFO to hold messages as we're ready to print them */ +FIFO<MidiEvent, 128> event_log; + +void USBH_Connect(void* data) +{ + hw.PrintLine("device connected"); +} + +void USBH_Disconnect(void* data) +{ + hw.PrintLine("device disconnected"); +} + +void USBH_ClassActive(void* data) +{ + if (usbHost.IsActiveClass(USBH_MIDI_LAUNCHPAD_CLASS)) { + hw.PrintLine("MIDI device class active"); + MidiUsbLaunchpadHandler::Config midi_config{}; + midi_config.transport_config.periph = UsbMidiLaunchpadTransport::Config::Periph::HOST; + midi.Init(midi_config); + midi.StartReceive(); + } +} + +void USBH_Error(void* data) +{ + hw.PrintLine("USB device error"); +} + +int main(void) +{ + /** Initialize our hardware */ + hw.Init(); + + hw.StartLog(true); + + hw.PrintLine("MIDI USB Host start"); + + /** Configure USB host */ + USBHostHandle::Config usbhConfig; + usbhConfig.connect_callback = USBH_Connect, usbhConfig.disconnect_callback = USBH_Disconnect, + usbhConfig.class_active_callback = USBH_ClassActive, usbhConfig.error_callback = USBH_Error, + usbHost.Init(usbhConfig); + + usbHost.RegisterClass(USBH_MIDI_LAUNCHPAD_CLASS); + + uint32_t now = System::GetNow(); + uint32_t log_time = System::GetNow(); + uint32_t blink_time = 0; + bool ledState = false; + + hw.PrintLine("MIDI USB Host initialized"); + + /** Infinite Loop */ + while (1) { + now = System::GetNow(); + + if (now > blink_time) { + hw.SetLed(ledState); + ledState = !ledState; + if (usbHost.GetPresent()) + blink_time = now + 400; + else + blink_time = now + 80; + } + /** Run USB host process */ + usbHost.Process(); + + if (usbHost.IsActiveClass(USBH_MIDI_LAUNCHPAD_CLASS) && midi.RxActive()) { + /** Process MIDI in the background */ + midi.Listen(); + + /** Loop through any MIDI Events */ + while (midi.HasEvents()) { + MidiEvent msg = midi.PopEvent(); + + /** Handle messages as they come in + * See DaisyExamples for some examples of this + */ + switch (msg.type) { + 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; + } + + /** Regardless of message, let's add the message data to our queue to output */ + event_log.PushBack(msg); + } + + /** Now separately, every 5ms we'll print the top message in our queue if there is one */ + if (now - log_time > 5) { + log_time = now; + if (!event_log.IsEmpty()) { + auto msg = event_log.PopFront(); + char outstr[128]; + const char* type_str = MidiEvent::GetTypeAsString(msg); + sprintf( + outstr, + "time:\t%ld\ttype: %s\tChannel: %d\tData MSB: " + "%d\tData LSB: %d\n", + now, + type_str, + msg.channel, + msg.data[0], + msg.data[1]); + hw.PrintLine(outstr); + } + } + } + } +} diff --git a/src/usb_midi_launchpad_transport.cc b/src/usb_midi_launchpad_transport.cc new file mode 100644 index 0000000..2fc1902 --- /dev/null +++ b/src/usb_midi_launchpad_transport.cc @@ -0,0 +1,362 @@ +#include "system.h" +#include "usbd_cdc.h" +#include "usbh_midi_launchpad.h" +#include "usb_midi_launchpad_transport.hh" +#include <cassert> + +extern "C" +{ + extern USBH_HandleTypeDef hUsbHostHS; +} + +#define pUSB_Host &hUsbHostHS + +using namespace daisy; + +class UsbMidiLaunchpadTransport::Impl +{ +public: + void Init(Config config); + + void StartRx(MidiRxParseCallback callback, void* context) + { + FlushRx(); + 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); + + /** USB Handle for CDC transfers + */ + UsbHandle usb_handle_; + Config config_; + + static constexpr size_t kBufferSize = 1024; + bool rx_active_; + // This corresponds to 256 midi messages + RingBuffer<uint8_t, kBufferSize> rx_buffer_; + MidiRxParseCallback parse_callback_; + void* parse_context_; + + // simple, self-managed buffer + uint8_t tx_buffer_[kBufferSize]; + size_t tx_ptr_; + + // MIDI message size determined by the + // code index number. You can find this + // table in the MIDI USB spec 1.0 + const uint8_t code_index_size_[16] + = {3, 3, 2, 3, 3, 1, 2, 3, 3, 3, 3, 3, 2, 2, 3, 1}; + + const uint8_t midi_message_size_[16] + = {0, 0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 2, 1, 1, 2, 0}; + + // 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; +}; + +// Global Impl +static UsbMidiLaunchpadTransport::Impl midi_usb_handle; + +void ReceiveCallback(uint8_t* buffer, uint32_t* length) +{ + if(midi_usb_handle.RxActive()) + { + for(uint16_t i = 0; i < *length; i += 4) + { + 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(); + } + } +} + +static void HostReceiveCallback(uint8_t* buffer, size_t sz, void* pUser) +{ + uint32_t len = sz; + ReceiveCallback(buffer, &len); +} + +void UsbMidiLaunchpadTransport::Impl::Init(Config config) +{ + // Borrowed from logger + /** this implementation relies on the fact that UsbHandle class has no member variables and can be shared + * assert this statement: + */ + // static_assert(1u == sizeof(UsbMidiLaunchpadTransport::Impl::usb_handle_), "UsbHandle is not static"); + + config_ = config; + rx_active_ = false; + + if(config_.periph == Config::HOST) + { + System::Delay(10); + USBH_MIDI_Launchpad_SetReceiveCallback(pUSB_Host, HostReceiveCallback, nullptr); + } + else + { + // This tells the USB middleware to send out MIDI descriptors instead of CDC + usbd_mode = USBD_MODE_MIDI; + + UsbHandle::UsbPeriph periph = UsbHandle::FS_INTERNAL; + if(config_.periph == Config::EXTERNAL) + periph = UsbHandle::FS_EXTERNAL; + + usb_handle_.Init(periph); + + System::Delay(10); + usb_handle_.SetReceiveCallback(ReceiveCallback, periph); + } +} + +void UsbMidiLaunchpadTransport::Impl::Tx(uint8_t* buffer, size_t size) +{ + int attempt_count = config_.tx_retry_count; + bool should_retry; + + MidiToUsb(buffer, size); + do + { + if(config_.periph == Config::HOST) + { + MIDI_Launchpad_ErrorTypeDef result; + result = USBH_MIDI_Launchpad_Transmit(pUSB_Host, tx_buffer_, tx_ptr_); + should_retry = (result == MIDI_BUSY) && attempt_count--; + } + else + { + UsbHandle::Result result; + if(config_.periph == Config::EXTERNAL) + result = usb_handle_.TransmitExternal(tx_buffer_, tx_ptr_); + else + result = usb_handle_.TransmitInternal(tx_buffer_, tx_ptr_); + should_retry + = (result == UsbHandle::Result::ERR) && attempt_count--; + } + + + if(should_retry) + System::DelayUs(100); + } while(should_retry); + + tx_ptr_ = 0; +} + +void UsbMidiLaunchpadTransport::Impl::UsbToMidi(uint8_t* buffer, uint8_t length) +{ + // A length of less than four in the buffer indicates + // a garbled message, since USB MIDI packets usually* + // require 4 bytes per message + if(length < 4) + return; + + // Right now, Daisy only supports a single cable, so we don't + // need to extract that value from the upper nibble + uint8_t code_index = buffer[0] & 0xF; + if(code_index == 0x0 || code_index == 0x1) + { + // 0x0 and 0x1 are reserved codes, and if they come up, + // there's probably been an error. *0xF indicates data is + // sent one byte at a time, rather than in packets of four. + // This functionality could be supported later. + // The single-byte mode does still come through as 32-bit messages + return; + } + + // Only writing as many bytes as necessary + for(uint8_t i = 0; i < code_index_size_[code_index]; i++) + { + if(rx_buffer_.writable() > 0) + rx_buffer_.Write(buffer[1 + i]); + else + { + rx_active_ = false; // disable on overflow + break; + } + } +} + +void UsbMidiLaunchpadTransport::Impl::MidiToUsbSingle(uint8_t* buffer, size_t size) +{ + if(size == 0) + return; + + // Channel voice messages + if((buffer[0] & 0xF0) != 0xF0) + { + // Check message validity + if((buffer[0] & 0xF0) == 0xC0 || (buffer[0] & 0xF0) == 0xD0) + { + if(size != 2) + return; // error + } + else + { + if(size != 3) + return; //error + } + + // CIN is the same as status byte for channel voice messages + tx_buffer_[tx_ptr_ + 0] = (buffer[0] & 0xF0) >> 4; + tx_buffer_[tx_ptr_ + 1] = buffer[0]; + tx_buffer_[tx_ptr_ + 2] = buffer[1]; + tx_buffer_[tx_ptr_ + 3] = size == 3 ? buffer[2] : 0; + + tx_ptr_ += 4; + } + else // buffer[0] & 0xF0 == 0xF0 aka System common or realtime + { + if(0xF2 == buffer[0]) + // three byte message + { + if(size != 3) + return; // error + + tx_buffer_[tx_ptr_ + 0] = 0x03; + tx_buffer_[tx_ptr_ + 1] = buffer[0]; + tx_buffer_[tx_ptr_ + 2] = buffer[1]; + tx_buffer_[tx_ptr_ + 3] = buffer[2]; + + tx_ptr_ += 4; + } + if(0xF1 == buffer[0] || 0xF3 == buffer[0]) + // two byte messages + { + if(size != 2) + return; // error + + tx_buffer_[tx_ptr_ + 0] = 0x02; + tx_buffer_[tx_ptr_ + 1] = buffer[0]; + tx_buffer_[tx_ptr_ + 2] = buffer[1]; + tx_buffer_[tx_ptr_ + 3] = 0; + + tx_ptr_ += 4; + } + else if(0xF4 <= buffer[0]) + // one byte message + { + if(size != 1) + return; // error + + tx_buffer_[tx_ptr_ + 0] = 0x05; + tx_buffer_[tx_ptr_ + 1] = buffer[0]; + tx_buffer_[tx_ptr_ + 2] = 0; + tx_buffer_[tx_ptr_ + 3] = 0; + + tx_ptr_ += 4; + } + else // sysex + { + size_t i = 0; + // Sysex messages are split up into several 4 bytes packets + // first ones use CIN 0x04 + // but packet containing the SysEx stop byte use a different CIN + for(i = 0; i + 3 < size; i += 3, tx_ptr_ += 4) + { + tx_buffer_[tx_ptr_] = 0x04; + tx_buffer_[tx_ptr_ + 1] = buffer[i]; + tx_buffer_[tx_ptr_ + 2] = buffer[i + 1]; + tx_buffer_[tx_ptr_ + 3] = buffer[i + 2]; + } + + // Fill CIN for terminating bytes + // 0x05 for 1 remaining byte + // 0x06 for 2 + // 0x07 for 3 + tx_buffer_[tx_ptr_] = 0x05 + (size - i - 1); + tx_ptr_++; + for(; i < size; ++i, ++tx_ptr_) + tx_buffer_[tx_ptr_] = buffer[i]; + for(; (tx_ptr_ % 4) != 0; ++tx_ptr_) + tx_buffer_[tx_ptr_] = 0; + } + } +} + +void UsbMidiLaunchpadTransport::Impl::MidiToUsb(uint8_t* buffer, size_t size) +{ + // We'll assume your message starts with a status byte! + size_t status_index = 0; + while(status_index < size) + { + // Search for next status byte or end + size_t next_status = status_index; + for(size_t j = status_index + 1; j < size; j++) + { + if(buffer[j] & 0x80) + { + next_status = j; + break; + } + } + if(next_status == status_index) + { + // Either we're at the end or it's malformed + next_status = size; + } + MidiToUsbSingle(buffer + status_index, next_status - status_index); + status_index = next_status; + } +} + +void UsbMidiLaunchpadTransport::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_); + } +} + +//////////////////////////////////////////////// +// UsbMidiLaunchpadTransport -> UsbMidiLaunchpadTransport::Impl +//////////////////////////////////////////////// + +void UsbMidiLaunchpadTransport::Init(UsbMidiLaunchpadTransport::Config config) +{ + pimpl_ = &midi_usb_handle; + pimpl_->Init(config); +} + +void UsbMidiLaunchpadTransport::StartRx(MidiRxParseCallback callback, void* context) +{ + pimpl_->StartRx(callback, context); +} + +bool UsbMidiLaunchpadTransport::RxActive() +{ + return pimpl_->RxActive(); +} + +void UsbMidiLaunchpadTransport::FlushRx() +{ + pimpl_->FlushRx(); +} + +void UsbMidiLaunchpadTransport::Tx(uint8_t* buffer, size_t size) +{ + pimpl_->Tx(buffer, size); +} diff --git a/src/usb_midi_launchpad_transport.hh b/src/usb_midi_launchpad_transport.hh new file mode 100644 index 0000000..da9b7ab --- /dev/null +++ b/src/usb_midi_launchpad_transport.hh @@ -0,0 +1,60 @@ +#ifndef LIBDIZZY_USB_MIDI_LAUNCHPAD_TRANSPORT_HH +#define LIBDIZZY_USB_MIDI_LAUNCHPAD_TRANSPORT_HH + +#include "hid/usb.h" +#include "sys/system.h" +#include "util/ringbuffer.h" + +namespace daisy { + /** @brief USB Transport for MIDI + * @ingroup midi + */ + class UsbMidiLaunchpadTransport { + public: + typedef void (*MidiRxParseCallback)(uint8_t* data, size_t size, void* context); + + struct Config { + enum Periph { + INTERNAL = 0, + EXTERNAL, + HOST + }; + + Periph periph; + + /** + * When sending MIDI messages immediately back-to-back in user code, + * sometimes the USB CDC driver is still "busy". + * + * This option configures the number of times to retry a Tx after + * delaying for 100 microseconds (default = 3 retries). + * + * If you set this to zero, Tx will not retry so the attempt will block + * for slightly less time, but transmit can fail if the Tx state is busy. + */ + uint8_t tx_retry_count; + + Config() : periph(INTERNAL), tx_retry_count(3) {} + }; + + void Init(Config config); + + void StartRx(MidiRxParseCallback callback, void* context); + bool RxActive(); + void FlushRx(); + void Tx(uint8_t* buffer, size_t size); + + class Impl; + + UsbMidiLaunchpadTransport() : pimpl_(nullptr) {} + ~UsbMidiLaunchpadTransport() {} + UsbMidiLaunchpadTransport(const UsbMidiLaunchpadTransport& other) = default; + UsbMidiLaunchpadTransport& operator=(const UsbMidiLaunchpadTransport& other) = default; + + private: + Impl* pimpl_; + }; + +} // namespace daisy + +#endif diff --git a/src/usbh_midi_launchpad.c b/src/usbh_midi_launchpad.c new file mode 100644 index 0000000..cb4c8d5 --- /dev/null +++ b/src/usbh_midi_launchpad.c @@ -0,0 +1,236 @@ +#include "usbh_midi_launchpad.h" +#include "daisy_core.h" + +static USB_MIDI_Launchpad_HandleTypeDef DMA_BUFFER_MEM_SECTION static_midi; + +static USBH_StatusTypeDef USBH_MIDI_InterfaceInit(USBH_HandleTypeDef *phost); +static USBH_StatusTypeDef USBH_MIDI_InterfaceDeInit(USBH_HandleTypeDef *phost); +static USBH_StatusTypeDef USBH_MIDI_Process(USBH_HandleTypeDef *phost); +static USBH_StatusTypeDef USBH_MIDI_ClassRequest(USBH_HandleTypeDef *phost); +static USBH_StatusTypeDef USBH_MIDI_SOFProcess(USBH_HandleTypeDef *phost); + +USBH_ClassTypeDef USBH_Launchpad_midi = { + "MIDI", + USB_LAUNCHPAD_CLASS, + USBH_MIDI_InterfaceInit, + USBH_MIDI_InterfaceDeInit, + USBH_MIDI_ClassRequest, + USBH_MIDI_Process, + USBH_MIDI_SOFProcess, + NULL, +}; + +#define EP_IN 0x80U + +/** +* @brief USBH_MIDI_InterfaceInit +* The function init the MIDI class. +* @param phost: Host handle +* @retval USBH Status +*/ +static USBH_StatusTypeDef USBH_MIDI_InterfaceInit(USBH_HandleTypeDef *phost) +{ + USBH_UsrLog(__FUNCTION__); + USBH_StatusTypeDef status; + USB_MIDI_Launchpad_HandleTypeDef *MIDI_Handle; + + // Single static instance of midi handle + phost->pActiveClass->pData = &static_midi; + MIDI_Handle = (USB_MIDI_Launchpad_HandleTypeDef *)phost->pActiveClass->pData; + USBH_memset(MIDI_Handle, 0, sizeof(USB_MIDI_Launchpad_HandleTypeDef)); + + uint8_t interface = USBH_FindInterface(phost, phost->pActiveClass->ClassCode, + USB_LAUNCHPAD_STREAMING_SUBCLASS, 0xFFU); + + if ((interface == 0xFFU) || (interface >= USBH_MAX_NUM_INTERFACES)) { + USBH_DbgLog("Cannot find interface for %s class.", phost->pActiveClass->Name); + return USBH_FAIL; + } + status = USBH_SelectInterface(phost, interface); + if (status != USBH_OK) { + return USBH_FAIL; + } + + /* Find the endpoints */ + for (int ep = 0; ep < phost->device.CfgDesc.Itf_Desc[interface].bNumEndpoints; ++ep) { + if (phost->device.CfgDesc.Itf_Desc[interface].Ep_Desc[ep].bEndpointAddress & EP_IN) { + MIDI_Handle->InEp = phost->device.CfgDesc.Itf_Desc[interface].Ep_Desc[ep].bEndpointAddress; + MIDI_Handle->InEpSize = phost->device.CfgDesc.Itf_Desc[interface].Ep_Desc[ep].wMaxPacketSize & 0x03FFU; + if (MIDI_Handle->InEpSize > USBH_MIDI_LAUNCHPAD_RX_BUF_SIZE) { + MIDI_Handle->InEpSize = USBH_MIDI_LAUNCHPAD_RX_BUF_SIZE; + } + /* Allocate and open input pipe */ + MIDI_Handle->InPipe = USBH_AllocPipe(phost, MIDI_Handle->InEp); + USBH_OpenPipe(phost, MIDI_Handle->InPipe, MIDI_Handle->InEp, + phost->device.address, phost->device.speed, USB_EP_TYPE_BULK, + MIDI_Handle->InEpSize); + (void)USBH_LL_SetToggle(phost, MIDI_Handle->InPipe, 0U); + USBH_UsrLog("InEP[%d] %02x size=%u", ep, MIDI_Handle->InEp, MIDI_Handle->InEpSize); + } else { + MIDI_Handle->OutEp = phost->device.CfgDesc.Itf_Desc[interface].Ep_Desc[ep].bEndpointAddress; + MIDI_Handle->OutEpSize = phost->device.CfgDesc.Itf_Desc[interface].Ep_Desc[ep].wMaxPacketSize & 0x03FFU; + /* Allocate and open output pipe */ + MIDI_Handle->OutPipe = USBH_AllocPipe(phost, MIDI_Handle->OutEp); + USBH_OpenPipe(phost, MIDI_Handle->OutPipe, MIDI_Handle->OutEp, + phost->device.address, phost->device.speed, USB_EP_TYPE_BULK, + MIDI_Handle->OutEpSize); + (void)USBH_LL_SetToggle(phost, MIDI_Handle->OutPipe, 0U); + USBH_UsrLog("OutEP[%d] %02x size=%u", ep, MIDI_Handle->OutEp, MIDI_Handle->OutEpSize); + } + } + + MIDI_Handle->state = MIDI_INIT; + MIDI_Handle->error = MIDI_OK; + + return USBH_OK; +} + +/** +* @brief USBH_MIDI_InterfaceDeInit +* The function DeInit the Pipes used for the MIDI class. +* @param phost: Host handle +* @retval USBH Status +*/ +static USBH_StatusTypeDef USBH_MIDI_InterfaceDeInit(USBH_HandleTypeDef *phost) +{ + USBH_UsrLog(__FUNCTION__); + USB_MIDI_Launchpad_HandleTypeDef *MIDI_Handle = (USB_MIDI_Launchpad_HandleTypeDef *) phost->pActiveClass->pData; + if (MIDI_Handle) { + if (MIDI_Handle->InPipe) { + USBH_ClosePipe(phost, MIDI_Handle->InPipe); + USBH_FreePipe(phost, MIDI_Handle->InPipe); + MIDI_Handle->InPipe = 0U; /* Reset the Channel as Free */ + } + if (MIDI_Handle->OutPipe) { + USBH_ClosePipe(phost, MIDI_Handle->OutPipe); + USBH_FreePipe(phost, MIDI_Handle->OutPipe); + MIDI_Handle->InPipe = 0U; /* Reset the Channel as Free */ + } + phost->pActiveClass->pData = 0U; + MIDI_Handle->state = MIDI_INIT; + MIDI_Handle->error = MIDI_OK; + } + return USBH_OK; +} + +/** +* @brief USBH_MIDI_ClassRequest +* The function is responsible for handling Standard requests +* for MIDI class. +* @param phost: Host handle +* @retval USBH Status +*/ +static USBH_StatusTypeDef USBH_MIDI_ClassRequest(USBH_HandleTypeDef *phost) +{ + phost->pUser(phost, HOST_USER_CLASS_ACTIVE); + return USBH_OK; +} + +/** +* @brief USBH_MIDI_Process +* The function is for managing state machine for MIDI data transfers +* @param phost: Host handle +* @retval USBH Status +*/ +static USBH_StatusTypeDef USBH_MIDI_Process(USBH_HandleTypeDef *phost) +{ + if (!phost->pActiveClass || !phost->pActiveClass->pData) + return USBH_FAIL; + + USB_MIDI_Launchpad_HandleTypeDef *hMidi = (USB_MIDI_Launchpad_HandleTypeDef *)phost->pActiveClass->pData; + USBH_StatusTypeDef error = USBH_OK; + USBH_URBStateTypeDef rxStatus; + + switch (hMidi->state) { + case MIDI_INIT: + hMidi->state = MIDI_IDLE; + break; + case MIDI_IDLE: + hMidi->state = MIDI_RX; + break; + case MIDI_RX: + // Always returns USBH_OK, call USBH_LL_GetURBState() for status + USBH_BulkReceiveData(phost, hMidi->rxBuffer, hMidi->InEpSize, hMidi->InPipe); + hMidi->state = MIDI_RX_POLL; + break; + case MIDI_RX_POLL: + rxStatus = USBH_LL_GetURBState(phost, hMidi->InPipe); + if (rxStatus == USBH_URB_NOTREADY || rxStatus == USBH_URB_IDLE) { + hMidi->state = MIDI_RX_POLL; + } else if (rxStatus == USBH_URB_DONE) { + size_t sz = USBH_LL_GetLastXferSize(phost, hMidi->InPipe); + hMidi->state = MIDI_RX; + if (hMidi->callback) { + hMidi->callback(hMidi->rxBuffer, sz, hMidi->pUser); + } + } else { + hMidi->state = MIDI_RX_ERROR; + error = USBH_FAIL; + } + break; + case MIDI_RX_ERROR: + error = USBH_ClrFeature(phost, hMidi->InEp); + if (error == USBH_FAIL) { + USBH_MIDI_InterfaceDeInit(phost); + hMidi->state = MIDI_FATAL_ERROR; + } else { + hMidi->state = MIDI_IDLE; + } + break; + case MIDI_FATAL_ERROR: + return USBH_FAIL; + } + return error; +} + +/** +* @brief USBH_MIDI_SOFProcess +* The function is for SOF state +* @param phost: Host handle +* @retval USBH Status +*/ +static USBH_StatusTypeDef USBH_MIDI_SOFProcess(USBH_HandleTypeDef *phost) +{ + /* Prevent unused argument(s) compilation warning */ + UNUSED(phost); + return USBH_OK; +} + +void USBH_MIDI_Launchpad_SetReceiveCallback(USBH_HandleTypeDef *phost, USBH_MIDI_Launchpad_RxCallback cb, void* pUser) +{ + USBH_UsrLog(__FUNCTION__); + USB_MIDI_Launchpad_HandleTypeDef *hMidi = (USB_MIDI_Launchpad_HandleTypeDef *)phost->pActiveClass->pData; + hMidi->callback = cb; + hMidi->pUser = pUser; +} + +MIDI_Launchpad_ErrorTypeDef USBH_MIDI_Launchpad_Transmit(USBH_HandleTypeDef *phost, uint8_t* data, size_t len) +{ + USB_MIDI_Launchpad_HandleTypeDef *hMidi = (USB_MIDI_Launchpad_HandleTypeDef *)phost->pActiveClass->pData; + int numUrbs = 0; + // This only blocks if data won't fit into one URB + while(len) + { + USBH_URBStateTypeDef txStatus = USBH_LL_GetURBState(phost, hMidi->OutPipe); + while(txStatus != USBH_URB_IDLE && txStatus != USBH_URB_DONE) + { + if(txStatus == USBH_URB_ERROR || txStatus == USBH_URB_STALL) + { + USBH_ClrFeature(phost, hMidi->OutEp); + return MIDI_ERROR; + } + if(numUrbs == 0) + return MIDI_BUSY; + + // Give previous URB time to complete + USBH_Delay(2); + } + size_t sz = (len <= hMidi->OutEpSize) ? len : hMidi->OutEpSize; + USBH_BulkSendData(phost, data, sz, hMidi->OutPipe, 1); + len -= sz; + ++numUrbs; + } + return MIDI_OK; +} + +/************************ (C) COPYRIGHT STMicroelectronics *****END OF FILE****/ diff --git a/src/usbh_midi_launchpad.h b/src/usbh_midi_launchpad.h new file mode 100644 index 0000000..02e27f0 --- /dev/null +++ b/src/usbh_midi_launchpad.h @@ -0,0 +1,64 @@ +#ifndef __USBH_MIDI_H +#define __USBH_MIDI_H + +#include "usbh_core.h" + +#ifdef __cplusplus +extern "C" { +#endif + + typedef enum { + MIDI_INIT = 0, + MIDI_IDLE, + MIDI_RX, + MIDI_RX_POLL, + MIDI_RX_ERROR, + MIDI_FATAL_ERROR + } USB_MIDI_Launchpad_StateTypeDef; + + typedef enum { + MIDI_OK, + MIDI_BUSY, + MIDI_ERROR + } MIDI_Launchpad_ErrorTypeDef; + + typedef void (*USBH_MIDI_Launchpad_RxCallback)(uint8_t* buff, size_t len, void* pUser); + +#define USBH_MIDI_LAUNCHPAD_RX_BUF_SIZE 64 + + /* Structure for MIDI process */ + typedef struct _MIDI_Process { + uint8_t InPipe; + uint8_t InEp; + uint16_t InEpSize; + uint8_t OutPipe; + uint8_t OutEp; + uint16_t OutEpSize; + USB_MIDI_Launchpad_StateTypeDef state; + MIDI_Launchpad_ErrorTypeDef error; + USBH_MIDI_Launchpad_RxCallback callback; + void* pUser; + uint8_t rxBuffer[USBH_MIDI_LAUNCHPAD_RX_BUF_SIZE]; + } USB_MIDI_Launchpad_HandleTypeDef; + +/* MIDI Class Codes */ +#define USB_LAUNCHPAD_CLASS 0x01U +#define USB_LAUNCHPAD_STREAMING_SUBCLASS 0x03U + + extern USBH_ClassTypeDef USBH_Launchpad_midi; +#define USBH_MIDI_LAUNCHPAD_CLASS &USBH_Launchpad_midi + + uint8_t USBH_MIDI_IsReady(USBH_HandleTypeDef* phost); + + void USBH_MIDI_Launchpad_SetReceiveCallback( + USBH_HandleTypeDef* phost, + USBH_MIDI_Launchpad_RxCallback cb, + void* pUser); + + MIDI_Launchpad_ErrorTypeDef USBH_MIDI_Launchpad_Transmit(USBH_HandleTypeDef* phost, uint8_t* data, size_t len); + +#ifdef __cplusplus +} +#endif + +#endif /* __USBH_MIDI_H */