Skip to content

Commit

Permalink
Support switching between USB host and device
Browse files Browse the repository at this point in the history
  • Loading branch information
rechrtb committed Apr 24, 2024
1 parent 11f5e89 commit 395189a
Show file tree
Hide file tree
Showing 3 changed files with 226 additions and 48 deletions.
91 changes: 72 additions & 19 deletions src/SerialCDC_tusb.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

#if SUPPORT_USB

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

#if CORE_USES_TINYUSB
Expand All @@ -25,18 +26,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 @@ -47,36 +65,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 @@ -86,7 +125,11 @@ void SerialCDC::flush() noexcept

size_t SerialCDC::canWrite() noexcept
{
if (!running)
if (!running
#if CFG_TUH_ENABLED
|| CoreUsbIsHostMode()
#endif
)
{
return 0;
}
Expand All @@ -97,13 +140,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
168 changes: 140 additions & 28 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 @@ -255,37 +257,41 @@ 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
#if CFG_TUH_ENABLED
void CoreUsbInit(NvicPriority priority, Pin usbVbusDetect, Pin usbVbusOn, Pin usbModeSwitch, Pin usbModeDetect) noexcept
#else
void CoreUsbInit(NvicPriority priority) noexcept
#endif
{
#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);

// Enable peripheral clock for USBHS
pmc_enable_periph_clk(ID_USBHS);

// Start the UPLL clock. The default divider is 40 which is correct for 12MHz crystal.
pmc_enable_upll_clock();

// From the datasheet:
// "Before enabling the USB clock in the Power Management Controller, the USBHS must be enabled
// (by writing a one to the USBHS_CTRL.USBE bit and a zero to the USBHS_CTRL.FRZCLK bit)"
# if 0
pmc_switch_udpck_to_upllck(1 - 1);
USBHS->USBHS_DEVCTRL = USBHS_DEVCTRL_DETACH | USBHS_DEVCTRL_SPDCONF_FORCED_FS;
# elif TUD_OPT_HIGH_SPEED
pmc_switch_udpck_to_upllck(1 - 1);
USBHS->USBHS_DEVCTRL = USBHS_DEVCTRL_DETACH;
# else
pmc_switch_udpck_to_upllck(10 - 1); // when high speed is disabled, tinyusb uses low power mode, which requires a 48MHz clock
USBHS->USBHS_DEVCTRL = USBHS_DEVCTRL_SPDCONF_LOW_POWER | USBHS_DEVCTRL_DETACH;
# endif
USBHS->USBHS_CTRL = USBHS_CTRL_UIMOD_DEVICE | USBHS_CTRL_USBE;
pmc_switch_udpck_to_upllck(CONFIG_USBCLK_DIV - 1);
pmc_enable_udpck();

pmc_set_fast_startup_input(PMC_FSMR_USBAL);
// Enable peripheral clock for USBHS
pmc_enable_periph_clk(ID_USBHS);

#elif SAME5x

Expand Down Expand Up @@ -322,24 +328,125 @@ 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 (hostMode && digitalRead(UsbVbusDetect))
{
reply.printf("Unable to change to host mode, board plugged in to computer\n");
return false;
}

if (hostMode == CoreUsbIsHostMode())
{
reply.printf("Already in %s mode\n", hostMode ? "host" : "device");
}
else
{
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)
{
auto tusb_init = isHostMode ? tuh_init : tud_init;
auto tusb_int_enable = isHostMode ? hcd_int_enable : dcd_int_enable;
auto tusb_task = isHostMode ? tuh_task_ext : tud_task_ext;

digitalWrite(UsbVbusOn, isHostMode);
tusb_init(0);
if (changingMode)
{
if (isHostMode)
{
if (tuh_inited())
{
hcd_init(0);
}
}
else
{
tud_connect();
}
}
tusb_int_enable(0);

changingMode = false;
while (!changingMode)
{
tusb_task(100, false);
}

// Deinit current tinyUSB context.
if (isHostMode)
{
hcd_event_device_remove(0, false);
}
else
{
tud_disconnect();
}
tusb_task(100, false);

// Reset USB hardware.
USBHS->USBHS_CTRL &= ~USBHS_CTRL_USBE;
for (int i = 9; i >= 0; i--)
{
if (isHostMode)
{
USBHS->USBHS_HSTPIPCFG[i] &= ~(USBHS_HSTPIPCFG_ALLOC);
}
else
{
USBHS->USBHS_DEVEPTCFG[i] &= ~(USBHS_DEVEPTCFG_ALLOC);
}
}

// Complete the mode change.
isHostMode = !isHostMode;
}
}
#else
extern "C" void CoreUsbDeviceTask(void* param) noexcept
{
(void)param;

// RTOS forever loop
while (1)
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
NVIC_DisableIRQ((IRQn_Type)ID_USBHS);
USBHS->USBHS_CTRL &= ~USBHS_CTRL_USBE;
}

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

Expand Down Expand Up @@ -389,7 +496,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

0 comments on commit 395189a

Please sign in to comment.