Browse Source
only copied and renamed so far, but still only serve device class midi. working.usb_midi_launchpad
5 changed files with 863 additions and 0 deletions
@ -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); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
@ -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 |
@ -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****/ |
@ -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 */ |
Loading…
Reference in new issue