Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support switching between USB host and device #4

Draft
wants to merge 4 commits into
base: 3.6-dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
91 changes: 72 additions & 19 deletions src/SerialCDC_tusb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

#if SUPPORT_USB

#include "TinyUsbInterface.h"
#include "SerialCDC_tusb.h"

#if CORE_USES_TINYUSB
Expand All @@ -27,18 +28,35 @@ SerialCDC::SerialCDC() noexcept

void SerialCDC::Start(Pin p) noexcept
{
#if CFG_TUH_ENABLED
if (CoreUsbIsHostMode())
{
return;
}
#endif
vBusPin = p;
while (!tud_inited()) { delay(10); }
running = true;
}

void SerialCDC::end() noexcept
{
#if CFG_TUH_ENABLED
if (CoreUsbIsHostMode())
{
return;
}
#endif
running = false;
}

bool SerialCDC::IsConnected() const noexcept
{
return tud_cdc_connected();
return
#if CFG_TUH_ENABLED
!CoreUsbIsHostMode() &&
#endif
tud_cdc_connected();
}

// Overridden virtual functions
Expand All @@ -49,36 +67,57 @@ bool SerialCDC::IsConnected() const noexcept
// available() returned nonzero bit read() never read it. Now we check neither when reading.
int SerialCDC::read() noexcept
{
if (!running)
{
return -1;
}

if (tud_cdc_available())
{
return tud_cdc_read_char();
}
return -1;
if (!running
#if CFG_TUH_ENABLED
|| CoreUsbIsHostMode()
#endif
)
{
return -1;
}

if (tud_cdc_available())
{
return tud_cdc_read_char();
}
return -1;
}

int SerialCDC::available() noexcept
{
if (!running)
{
return 0;
}
if (!running
#if CFG_TUH_ENABLED
|| CoreUsbIsHostMode()
#endif
)
{
return 0;
}

return tud_cdc_available();
return tud_cdc_available();
}

size_t SerialCDC::readBytes(char * _ecv_array buffer, size_t length) noexcept
{
if (!running
#if CFG_TUH_ENABLED
|| CoreUsbIsHostMode()
#endif
)
{
return 0;
}

return tud_cdc_read (buffer, length);
}

void SerialCDC::flush() noexcept
{
if (!running)
if (!running
#if CFG_TUH_ENABLED
|| CoreUsbIsHostMode()
#endif
)
{
return;
}
Expand All @@ -88,7 +127,11 @@ void SerialCDC::flush() noexcept

size_t SerialCDC::canWrite() noexcept
{
if (!running)
if (!running
#if CFG_TUH_ENABLED
|| CoreUsbIsHostMode()
#endif
)
{
return 0;
}
Expand All @@ -99,13 +142,23 @@ size_t SerialCDC::canWrite() noexcept
// Write single character, blocking
size_t SerialCDC::write(uint8_t c) noexcept
{
#if CFG_TUH_ENABLED
if (CoreUsbIsHostMode())
{
return 0;
}
#endif
return write(&c, 1);
}

// Blocking write block
size_t SerialCDC::write(const uint8_t *buf, size_t length) noexcept
{
if (!running)
if (!running
#if CFG_TUH_ENABLED
|| CoreUsbIsHostMode()
#endif
)
{
return 0;
}
Expand Down
179 changes: 170 additions & 9 deletions src/TinyUsbInterface.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include "class/hid/hid_device.h"
#include "class/audio/audio.h"
#include "class/midi/midi.h"
#include "host/hcd.h"
#include "device/dcd.h"

#if SAME70

Expand Down Expand Up @@ -266,11 +268,27 @@ extern "C" const uint16_t *tud_descriptor_string_cb(uint8_t index, uint16_t lang
return desc_str;
}

#if CFG_TUH_ENABLED
static volatile bool isHostMode = false;
static volatile bool changingMode = false;

static Pin UsbVbusDetect;
static Pin UsbVbusOn;
static Pin UsbModeSwitch;
static Pin UsbModeDetect;
#endif

// Call this to initialise the hardware
void CoreUsbInit(NvicPriority priority) noexcept
void CoreUsbInit(NvicPriority priority, Pin usbVbusDetect, Pin usbVbusOn, Pin usbModeSwitch, Pin usbModeDetect) noexcept
{
#if SAME70
#if CFG_TUH_ENABLED
UsbVbusDetect = usbVbusDetect;
UsbVbusOn = usbVbusOn;
UsbModeSwitch = usbModeSwitch;
UsbModeDetect = usbModeDetect;
#endif

#if SAME70
// Set the USB interrupt priority to a level that is allowed to make FreeRTOS calls
NVIC_SetPriority(USBHS_IRQn, priority);

Expand Down Expand Up @@ -323,24 +341,162 @@ void CoreUsbInit(NvicPriority priority) noexcept
#endif
}

#if CFG_TUH_ENABLED
bool CoreUsbSetHostMode(bool hostMode, const StringRef& reply)
{
if (changingMode)
{
reply.printf("Previous USB mode change still in progress");
return false;
}

if (!CoreUsbIsHostMode() && hostMode && digitalRead(UsbVbusDetect))
{
reply.printf("Unable to change to host mode, board plugged in to computer\n");
return false;
}

if (hostMode != CoreUsbIsHostMode())
{
CoreUsbStop();
changingMode = true;
}

return true;
}

bool CoreUsbIsHostMode()
{
return isHostMode;
}

// USB Device Driver task
// This top level thread process all usb events and invoke callbacks
extern "C" void CoreUsbDeviceTask(void* param) noexcept
{
(void)param;

// This should be called after scheduler/kernel is started.
// Otherwise it could cause kernel issue since USB IRQ handler does use RTOS queue API.
tusb_init();
while (true)
{
digitalWrite(UsbVbusOn, isHostMode);

auto tusb_init = isHostMode ? tuh_init : tud_init;
if (changingMode)
{
auto tusb_inited = isHostMode ? tuh_inited : tud_inited;
if (tusb_inited())
{
// TinyUSB class drivers already inited previously, just call
// the functions to initialize the USB peripheral.
if (isHostMode)
{
hcd_init(0);
hcd_int_enable(0);
}
else
{
dcd_init(0);
dcd_int_enable(0);
}
}
else
{
tusb_init(0);
}
}
else
{
tusb_init(0);
}

auto tusb_task = isHostMode ? tuh_task_ext : tud_task_ext;

// RTOS forever loop
while (1)
changingMode = false;
while (!changingMode)
{
tusb_task(100, false);
}

// Disable pipes, deallocate DPRAM
for (int i = 9; i >= 0; i--)
{
if (isHostMode)
{
USBHS->USBHS_HSTPIP &= ~(USBHS_HSTPIP_PEN0 << i);
USBHS->USBHS_HSTPIPCFG[i] &= ~(USBHS_HSTPIPCFG_ALLOC);
}
else
{
USBHS->USBHS_DEVEPT &= ~(USBHS_DEVEPT_EPEN0 << i);
USBHS->USBHS_DEVEPTCFG[i] &= ~(USBHS_DEVEPTCFG_ALLOC);
}
}

// Reset DMA registers
for (int i = 0; i < 7; i++)
{
if (isHostMode)
{
USBHS->USBHS_HSTDMA[i].USBHS_HSTDMAADDRESS = 0;
USBHS->USBHS_HSTDMA[i].USBHS_HSTDMACONTROL = 0;
USBHS->USBHS_HSTDMA[i].USBHS_HSTDMASTATUS = 0;
}
else
{
USBHS->USBHS_DEVDMA[i].USBHS_DEVDMAADDRESS = 0;
USBHS->USBHS_DEVDMA[i].USBHS_DEVDMACONTROL = 0;
USBHS->USBHS_DEVDMA[i].USBHS_DEVDMASTATUS = 0;
}
}

// Deinit current tinyUSB host context. Not needed for device
// since changing mode requires the board to not be connected
// to a host, so if we are changing mode then the DCD_EVENT_UNPLUGGED
// must have been handled in the past already.
if (isHostMode)
{
hcd_event_device_remove(0, false);
tusb_task(100, false);
}

// Reset USB hardware
USBHS->USBHS_CTRL &= ~(USBHS_CTRL_USBE | USBHS_CTRL_UIMOD);
USBHS->USBHS_CTRL |= USBHS_CTRL_FRZCLK;

// Toggle mode for next loop
isHostMode = !isHostMode;
}
}
#else
extern "C" void CoreUsbDeviceTask(void* param) noexcept
{
(void)param;

tud_init(0);
while (true)
{
// tinyusb device task
tud_task();
// tud_cdc_write_flush();
}
}
#endif

void CoreUsbStop()
{
#if CFG_TUH_ENABLED
digitalWrite(UsbVbusOn, false);
#endif

#if SAME5x
NVIC_DisableIRQ(USB_0_IRQn);
NVIC_DisableIRQ(USB_1_IRQn);
NVIC_DisableIRQ(USB_2_IRQn);
NVIC_DisableIRQ(USB_3_IRQn);
USB->DEVICE.CTRLA.reg &= ~USB_CTRLA_ENABLE;
#elif SAME70
NVIC_DisableIRQ((IRQn_Type)ID_USBHS);
USBHS->USBHS_CTRL &= ~USBHS_CTRL_USBE;
#endif
}

#if RP2040 // RP2040 USB configuration has HID enabled by default

Expand Down Expand Up @@ -390,7 +546,12 @@ uint32_t numUsbInterrupts = 0;
extern "C" void USBHS_Handler() noexcept
{
++numUsbInterrupts;
#if CFG_TUH_ENABLED
auto tusb_handler = isHostMode ? tuh_int_handler : tud_int_handler;
tusb_handler(0);
#else
tud_int_handler(0);
#endif
}

#elif SAME5x
Expand Down
Loading