diff --git a/CMakeLists.txt b/CMakeLists.txt index 8cc7608..8260a6e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,7 @@ option(USE_SYSTEMWIDE_PICOSDK "Use the systemwide Pico SDK instead of relying on the one from a deeply nested Git submodule (OFF by default)" OFF) option(PICO_NO_FLASH "Disable writing the compiled program to flash, and only load it to RAM. Useful for testing, but not much else (OFF by default)." OFF) option(PICO_COPY_TO_RAM "Run all code in RAM, while the program is also stored on flash. On bootup, everything will be copied to RAM (OFF by default)." OFF) +option(USE_USBCDC_FOR_STDIO "Export an extra USB-CDC interface for stdio, instead of echoing it to a UART port (and requiring UART loopback for receiving stdio output on a host computer)." OFF) set(FAMILY "rp2040" CACHE STRING "Board/MCU family, decides which drivers to use. Set to RP2040 by default.") set(BOARD "raspberry_pi_pico" CACHE STRING "Board used, determines the pinout. Defaults to the Raspberry Pi Pico.") @@ -27,10 +28,14 @@ if(FAMILY STREQUAL "rp2040") pico_sdk_init() add_executable(${PROJECT}) - include(${TOP}/hw/bsp/${FAMILY}/family.cmake) - # need uart stdio, usb is busy doing other stuff - pico_enable_stdio_uart(${PROJECT} 1) + if(USE_USBCDC_FOR_STDIO) + # we're going to manually implement this case + pico_enable_stdio_uart(${PROJECT} 0) + target_compile_definitions(${PROJECT} PUBLIC USE_USBCDC_FOR_STDIO=1 PICO_STDIO_USB=1) + else() + pico_enable_stdio_uart(${PROJECT} 1) + endif() pico_enable_stdio_usb(${PROJECT} 0) # Example source @@ -48,8 +53,7 @@ if(FAMILY STREQUAL "rp2040") ${CMAKE_CURRENT_SOURCE_DIR}/cdc_serprog.c ${CMAKE_CURRENT_SOURCE_DIR}/rtconf.c ) - - # Example include + # Example include target_include_directories(${PROJECT} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/ ${CMAKE_CURRENT_SOURCE_DIR}/libco/ @@ -59,13 +63,34 @@ if(FAMILY STREQUAL "rp2040") ${CMAKE_CURRENT_SOURCE_DIR}/bsp/default/ ) + # need to do this AFTER the regular includes, so that our bsp folder can + # override the tinyusb board.h header + include(${TOP}/hw/bsp/${FAMILY}/family.cmake) # tinyusb hardware-specific stuff + # Example defines - target_compile_definitions(${PROJECT} PUBLIC - ) + #target_compile_definitions(${PROJECT} PUBLIC + #) target_link_libraries(${PROJECT} pico_stdlib pico_unique_id hardware_spi pico_fix_rp2040_usb_device_enumeration) + if(USE_USBCDC_FOR_STDIO) + target_sources(${PROJECT} PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/bsp/${FAMILY}/cdc_stdio.c + ) + + target_include_directories(${PROJECT} PUBLIC + ${PICO_SDK_PATH}/src/rp2_common/pico_stdio_usb/include/ + ) + + # extremely ugly hack to prevent the Pico SDK to declare *its* TinyUSB config + target_compile_definitions(${PROJECT} PUBLIC + _PICO_STDIO_USB_TUSB_CONFIG_H=1 + ) + + target_link_libraries(${PROJECT} pico_stdio) + endif() + pico_add_extra_outputs(${PROJECT}) else() diff --git a/README.md b/README.md index e18936a..f8281dc 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ libco is licensed under the [ISC license](https://opensource.org/licenses/ISC) separate mode that temporarily disables all other IO protocols - [x] UART with CTS/RTS flow control - [x] Needs configurable stuff as well, as some UART interfaces won't use this. -- [ ] Debug interface to send printf stuff directly to USB, instead of having +- [x] Debug interface to send printf stuff directly to USB, instead of having - to use the UART interface as a loopback thing. - [ ] I2C support by emulating the I2C Tiny USB - [ ] Expose RP2040-internal temperature ADC on I2C-over-USB bus? diff --git a/bsp/rp2040/board.h b/bsp/rp2040/board.h new file mode 100644 index 0000000..2fa36df --- /dev/null +++ b/bsp/rp2040/board.h @@ -0,0 +1,51 @@ +/* + * The MIT License (MIT) + * + * Copyright (c) 2021, Ha Thach (tinyusb.org) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * This file is part of the TinyUSB stack. + */ + +#ifndef BOARD_H_ +#define BOARD_H_ + +#ifdef __cplusplus + extern "C" { +#endif + +#define LED_PIN PICO_DEFAULT_LED_PIN +#define LED_STATE_ON 1 + +// Button pin is BOOTSEL which is flash CS pin +#define BUTTON_BOOTSEL +#define BUTTON_STATE_ACTIVE 0 + +#ifndef USE_USBCDC_FOR_STDIO +#define UART_DEV uart0 +#define UART_TX_PIN 0 +#define UART_RX_PIN 1 +#endif + +#ifdef __cplusplus + } +#endif + +#endif /* BOARD_H_ */ diff --git a/bsp/rp2040/cdc_stdio.c b/bsp/rp2040/cdc_stdio.c new file mode 100644 index 0000000..5d4cf4a --- /dev/null +++ b/bsp/rp2040/cdc_stdio.c @@ -0,0 +1,105 @@ + +#include +#include +#include +#include +#include +#include + +#include "tusb.h" + +#include "protocfg.h" + +#ifndef PICO_STDIO_USB_STDOUT_TIMEOUT_US +#define PICO_STDIO_USB_STDOUT_TIMEOUT_US 500000 +#endif + +// *mostly* the same as the SDK code, *except* we have to explicitely pass the +// CDC interface number to the tusb functions, making the SDK code itself very +// non-reusable >__> + +static mutex_t stdio_usb_mutex; + +static void stdio_usb_out_chars(const char* buf, int length) { + static uint64_t last_avail_time; + uint32_t owner; + + if (!mutex_try_enter(&stdio_usb_mutex, &owner)) { + if (owner == get_core_num()) return; // would deadlock otherwise + mutex_enter_blocking(&stdio_usb_mutex); + } + + if (tud_cdc_n_connected(CDC_N_STDIO)) { + for (int i = 0; i < length; ) { + int n = length - i; + int avail = tud_cdc_n_write_available(CDC_N_STDIO); + + if (n > avail) n = avail; + if (n) { + int n2 = tud_cdc_n_write(CDC_N_STDIO, buf+i, n); + tud_task(); + tud_cdc_n_write_flush(CDC_N_STDIO); + i += n2; + last_avail_time = time_us_64(); + } else { + tud_task(); + tud_cdc_n_write_flush(CDC_N_STDIO); + + if (!tud_cdc_n_connected(CDC_N_STDIO) || + (!tud_cdc_n_write_available(CDC_N_STDIO) + && time_us_64() > last_avail_time + PICO_STDIO_USB_STDOUT_TIMEOUT_US)) { + break; + } + } + } + } else { + // reset our timeout + last_avail_time = 0; + } + + mutex_exit(&stdio_usb_mutex); +} + +static int stdio_usb_in_chars(char* buf, int length) { + uint32_t owner; + + if (!mutex_try_enter(&stdio_usb_mutex, &owner)) { + if (owner == get_core_num()) return PICO_ERROR_NO_DATA; // would deadlock otherwise + mutex_enter_blocking(&stdio_usb_mutex); + } + + int rc = PICO_ERROR_NO_DATA; + + if (tud_cdc_n_connected(CDC_N_STDIO) && tud_cdc_n_available(CDC_N_STDIO)) { + int count = tud_cdc_n_read(CDC_N_STDIO, buf, length); + rc = count ? count : PICO_ERROR_NO_DATA; + } + + mutex_exit(&stdio_usb_mutex); + + return rc; +} + +extern stdio_driver_t stdio_usb; +stdio_driver_t stdio_usb = { + .out_chars = stdio_usb_out_chars, + . in_chars = stdio_usb_in_chars , +#if PICO_STDIO_ENABLE_CRLF_SUPPORT + .crlf_enabled = PICO_STDIO_DEFAULT_CRLF +#endif +}; + +bool stdio_usb_init(void) { +//#if !PICO_NO_BI_STDIO_USB + bi_decl_if_func_used(bi_program_feature("USB stdin / stdout")); +//#endif + + mutex_init(&stdio_usb_mutex); + + // unlike with the SDK code, we don't need to add IRQ stuff for the USB + // task, as our main function handles this automatically + + stdio_set_driver_enabled(&stdio_usb, true); + return true; +} + diff --git a/bsp/rp2040/protocfg.h b/bsp/rp2040/protocfg.h index d53d617..7d037b4 100644 --- a/bsp/rp2040/protocfg.h +++ b/bsp/rp2040/protocfg.h @@ -11,5 +11,9 @@ #define CDC_N_UART 0 #define CDC_N_SERPROG 1 +#ifdef USE_USBCDC_FOR_STDIO +#define CDC_N_STDIO 2 +#endif + #endif diff --git a/main.c b/main.c index 3ee3bc7..910d18d 100644 --- a/main.c +++ b/main.c @@ -27,6 +27,8 @@ #include #include +#include "tusb_config.h" + #include "bsp/board.h" /* a tinyusb header */ #include "tusb.h" @@ -95,7 +97,7 @@ int main(void) mainthread = co_active(); // TODO: split this out in a bsp-specific file -#ifdef PICO_BOARD +#if defined(PICO_BOARD) && !defined(USE_USBCDC_FOR_STDIO) // use hardcoded values from TinyUSB board.h bi_decl(bi_2pins_with_func(0, 1, GPIO_FUNC_UART)); #endif @@ -117,6 +119,10 @@ int main(void) tusb_init(); +#ifdef USE_USBCDC_FOR_STDIO + stdio_usb_init(); +#endif + while (1) { //printf("hi\n"); diff --git a/protos.h b/protos.h index fac2d0d..5491fb7 100644 --- a/protos.h +++ b/protos.h @@ -2,11 +2,11 @@ #ifndef PROTOS_H_ #define PROTOS_H_ +#include + #include "protocfg.h" #ifdef DBOARD_HAS_UART -#include - void cdc_uart_init(void); void cdc_uart_task(void); @@ -18,5 +18,11 @@ void cdc_serprog_init(void); void cdc_serprog_task(void); #endif +#ifdef USE_USBCDC_FOR_STDIO +//#ifndef PICO_BOARD +bool stdio_usb_init(void); +//#endif +#endif + #endif diff --git a/serprog.h b/serprog.h index 0fd5dfa..0e1061f 100644 --- a/serprog.h +++ b/serprog.h @@ -25,6 +25,8 @@ enum serprog_cmd { S_CMD_SPIOP = 0x13, S_CMD_S_SPI_FREQ = 0x14, S_CMD_S_PINSTATE = 0x15, + + S_CMD_MAGIC_SETTINGS = 0x53 }; enum serprog_response { diff --git a/tusb_config.h b/tusb_config.h index 1ceb580..5ce775c 100644 --- a/tusb_config.h +++ b/tusb_config.h @@ -23,8 +23,8 @@ * */ -#ifndef _TUSB_CONFIG_H_ -#define _TUSB_CONFIG_H_ +#ifndef _TUSB_CONFIG_H__ +#define _TUSB_CONFIG_H__ #ifdef __cplusplus extern "C" { @@ -98,7 +98,12 @@ #endif //------------- CLASS -------------// +#ifdef USE_USBCDC_FOR_STDIO +#define CFG_TUD_CDC 3 +#else #define CFG_TUD_CDC 2 +#endif + #define CFG_TUD_MSC 0 #define CFG_TUD_HID 1 #define CFG_TUD_MIDI 0 diff --git a/usb_descriptors.c b/usb_descriptors.c index 146c45f..a237c84 100644 --- a/usb_descriptors.c +++ b/usb_descriptors.c @@ -115,6 +115,11 @@ enum ITF_NUM_CDC_SERPROG_DATA, #endif +#ifdef USE_USBCDC_FOR_STDIO + ITF_NUM_CDC_STDIO_COM, + ITF_NUM_CDC_STDIO_DATA, +#endif + ITF_NUM_TOTAL }; @@ -129,6 +134,9 @@ static const int CONFIG_TOTAL_LEN = TUD_CONFIG_DESC_LEN #endif #ifdef DBOARD_HAS_SERPROG + TUD_CDC_DESC_LEN +#endif +#ifdef USE_USBCDC_FOR_STDIO + + TUD_CDC_DESC_LEN #endif ; @@ -139,6 +147,9 @@ static const int CONFIG_TOTAL_LEN = TUD_CONFIG_DESC_LEN #define EPNUM_CDC_SERPROG_OUT 0x05 // 7 #define EPNUM_CDC_SERPROG_IN 0x85 // 8 #define EPNUM_CDC_SERPROG_NOTIF 0x86 // 6 +#define EPNUM_CDC_STDIO_OUT 0x07 +#define EPNUM_CDC_STDIO_IN 0x87 +#define EPNUM_CDC_STDIO_NOTIF 0x88 // NOTE: if you modify this table, don't forget to keep tusb_config.h up to date as well! uint8_t const desc_configuration[] = @@ -160,6 +171,11 @@ uint8_t const desc_configuration[] = // Interface number, string index, EP notification address and size, EP data address (out, in) and size. TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_SERPROG_COM, 0, EPNUM_CDC_SERPROG_NOTIF, 64, EPNUM_CDC_SERPROG_OUT, EPNUM_CDC_SERPROG_IN, 64), #endif + +#ifdef USE_USBCDC_FOR_STDIO + // Interface number, string index, EP notification address and size, EP data address (out, in) and size. + TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_STDIO_COM, 0, EPNUM_CDC_STDIO_NOTIF, 64, EPNUM_CDC_STDIO_OUT, EPNUM_CDC_STDIO_IN, 64), +#endif }; // Invoked when received GET CONFIGURATION DESCRIPTOR