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

Add EXTRA_UART cmake option, bridges UART0 over USB-CDC as tty1 #102

Closed
wants to merge 9 commits into from
11 changes: 11 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,17 @@ if (DEBUGPROBE)
)
endif ()

option (EXTRA_UART "bridge both probe UARTs")
if (EXTRA_UART)
target_compile_definitions (picoprobe PRIVATE
CDC_UARTS=2
)
else ()
target_compile_definitions (picoprobe PRIVATE
CDC_UARTS=1
)
endif ()


target_link_libraries(picoprobe PRIVATE
pico_multicore
Expand Down
12 changes: 10 additions & 2 deletions include/board_pico_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,22 @@
#define PROBE_PIN_RESET 1
#endif

// UART config
// UART config, ttyACMn
#define PICOPROBE_UART_TX 4
#define PICOPROBE_UART_RX 5
#define PICOPROBE_UART_INTERFACE uart1
#define PICOPROBE_UART_BAUDRATE 115200

#if (CDC_UARTS > 1)
// if enabled, always ttyACM(n+1)
#define PICOPROBE_EX_UART_TX 0
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not immediately obvious what EX stands for here - could be EXternal, EXtra, EXtended, EXpired ? 😉

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was originally EXTRA, but just seemed too long - let me know preferred

#define PICOPROBE_EX_UART_RX 1
#define PICOPROBE_EX_UART_INTERFACE uart0
#define PICOPROBE_EX_UART_BAUDRATE 115200
#endif

#define PICOPROBE_USB_CONNECTED_LED 25

#define PROBE_PRODUCT_STRING "Picoprobe (CMSIS-DAP)"

#endif
#endif
122 changes: 82 additions & 40 deletions src/cdc_uart.c
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,13 @@ TaskHandle_t uart_taskhandle;
TickType_t last_wake, interval = 100;

/* Max 1 FIFO worth of data */
static uint8_t tx_buf[32];
static uint8_t rx_buf[32];
#define TX_BUF_SIZ 32
#define RX_BUF_SIZ 32
static uint8_t tx_buf[CDC_UARTS][TX_BUF_SIZ];
static uint8_t rx_buf[CDC_UARTS][RX_BUF_SIZ];
static int was_connected[CDC_UARTS];
static uint cdc_tx_oe[CDC_UARTS];

// Actually s^-1 so 25ms
#define DEBOUNCE_MS 40
static uint debounce_ticks = 5;
Expand All @@ -55,6 +60,9 @@ void cdc_uart_init(void) {
gpio_set_pulls(PICOPROBE_UART_TX, 1, 0);
gpio_set_pulls(PICOPROBE_UART_RX, 1, 0);
uart_init(PICOPROBE_UART_INTERFACE, PICOPROBE_UART_BAUDRATE);
for (int n = 0; n < CDC_UARTS; n++) {
was_connected[n] = 0; cdc_tx_oe[n] = 0;
}

#ifdef PICOPROBE_UART_RTS
gpio_init(PICOPROBE_UART_RTS);
Expand All @@ -66,80 +74,102 @@ void cdc_uart_init(void) {
gpio_set_dir(PICOPROBE_UART_DTR, GPIO_OUT);
gpio_put(PICOPROBE_UART_DTR, 1);
#endif
#if (CDC_UARTS > 1)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should there be a compile-time error or warning if CDC_UARTS is set to a value larger than 2?

Copy link
Author

@milipropasm milipropasm Oct 2, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will add that error once you give me the go/no-go on other two points...
My thinking was that user provides or omits EXTRA_UART , then CDC_UARTS only really settable by configure: 1 by default, 2 if EXTRA_UART present. again thoughts were that separating the boolean from the count allowed future expansion by not excluding additional software uarts bridged to CDC.

gpio_set_function(PICOPROBE_EX_UART_TX, GPIO_FUNC_UART);
gpio_set_function(PICOPROBE_EX_UART_RX, GPIO_FUNC_UART);
gpio_set_pulls(PICOPROBE_EX_UART_TX, 1, 0);
gpio_set_pulls(PICOPROBE_EX_UART_RX, 1, 0);
uart_init(PICOPROBE_EX_UART_INTERFACE, PICOPROBE_EX_UART_BAUDRATE);
#endif
}

void cdc_task(void)
void cdc_task(uint8_t tty)
{
static int was_connected = 0;
static uint cdc_tx_oe = 0;
uint rx_len = 0;

uart_inst_t* uart_ptr = PICOPROBE_UART_INTERFACE;
#if (CDC_UARTS > 1)
if (tty != 0) { uart_ptr = PICOPROBE_EX_UART_INTERFACE; }
#endif

// Consume uart fifo regardless even if not connected
while(uart_is_readable(PICOPROBE_UART_INTERFACE) && (rx_len < sizeof(rx_buf))) {
rx_buf[rx_len++] = uart_getc(PICOPROBE_UART_INTERFACE);
while(uart_is_readable(uart_ptr) && (rx_len < RX_BUF_SIZ)) {
rx_buf[tty][rx_len++] = uart_getc(uart_ptr);
}

if (tud_cdc_connected()) {
was_connected = 1;
if (tud_cdc_n_connected(tty)) {
was_connected[tty] = 1;
int written = 0;
/* Implicit overflow if we don't write all the bytes to the host.
* Also throw away bytes if we can't write... */
if (rx_len) {
if (!tty) {
#ifdef PICOPROBE_UART_RX_LED
gpio_put(PICOPROBE_UART_RX_LED, 1);
rx_led_debounce = debounce_ticks;
gpio_put(PICOPROBE_UART_RX_LED, 1);
rx_led_debounce = debounce_ticks;
#endif
written = MIN(tud_cdc_write_available(), rx_len);
}
written = MIN(tud_cdc_n_write_available(tty), rx_len);
if (rx_len > written)
cdc_tx_oe++;
cdc_tx_oe[tty]++;

if (written > 0) {
tud_cdc_write(rx_buf, written);
tud_cdc_write_flush();
tud_cdc_n_write(tty, rx_buf[tty], written);
tud_cdc_n_write_flush(tty);
}
} else {
if (!tty) {
#ifdef PICOPROBE_UART_RX_LED
if (rx_led_debounce)
rx_led_debounce--;
else
gpio_put(PICOPROBE_UART_RX_LED, 0);
if (rx_led_debounce)
rx_led_debounce--;
else
gpio_put(PICOPROBE_UART_RX_LED, 0);
#endif
}
}

/* Reading from a firehose and writing to a FIFO. */
size_t watermark = MIN(tud_cdc_available(), sizeof(tx_buf));
size_t watermark = MIN(tud_cdc_n_available(tty), TX_BUF_SIZ);
if (watermark > 0) {
size_t tx_len;
if (!tty) {
#ifdef PICOPROBE_UART_TX_LED
gpio_put(PICOPROBE_UART_TX_LED, 1);
tx_led_debounce = debounce_ticks;
gpio_put(PICOPROBE_UART_TX_LED, 1);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if it might be useful to have e.g. PICOPROBE_EX_UART_TX_LED (etc.) which defaults to PICOPROBE_UART_TX_LED if not defined? 🤔

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't use it, but happy to add - again let me know your preference

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually, those are debugprobe-only, which conflicts with one of the default UART0 pins (and would be tricky to solder the extra 2 uart tx/rx wires to), so I think its better if I make the picoprobe-only nature of EXTRA_UART explicit in picoprobe_config.h - does this sound OK?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'll also change all instances of CDC_UARTS > 1 to CDC_UARTS == 2
which makes more sense / is less confusing once the compiler range check you suggested is in place

tx_led_debounce = debounce_ticks;
#endif
}
/* Batch up to half a FIFO of data - don't clog up on RX */
watermark = MIN(watermark, 16);
tx_len = tud_cdc_read(tx_buf, watermark);
uart_write_blocking(PICOPROBE_UART_INTERFACE, tx_buf, tx_len);
tx_len = tud_cdc_n_read(tty, tx_buf[tty], watermark);
uart_write_blocking(uart_ptr, tx_buf[tty], tx_len);
} else {
if (!tty) {
#ifdef PICOPROBE_UART_TX_LED
if (tx_led_debounce)
tx_led_debounce--;
else
gpio_put(PICOPROBE_UART_TX_LED, 0);
#endif
}
}
} else if (was_connected) {
tud_cdc_write_clear();
was_connected = 0;
cdc_tx_oe = 0;
} else if (was_connected[tty]) {
tud_cdc_n_write_clear(tty);
was_connected[tty] = 0;
cdc_tx_oe[tty] = 0;
}
}

void cdc_tasks(void) {
for (int n = 0; n < CDC_UARTS; n++) { cdc_task(n); }
}

void cdc_thread(void *ptr)
{
BaseType_t delayed;
last_wake = xTaskGetTickCount();
/* Threaded with a polling interval that scales according to linerate */
while (1) {
cdc_task();
cdc_tasks();
delayed = xTaskDelayUntil(&last_wake, interval);
if (delayed == pdFALSE)
last_wake = xTaskGetTickCount();
Expand All @@ -148,6 +178,7 @@ void cdc_thread(void *ptr)

void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_coding)
{
uint8_t tty = itf;
uart_parity_t parity;
uint data_bits, stop_bits;
/* Set the tick thread interval to the amount of time it takes to
Expand All @@ -160,10 +191,16 @@ void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_coding)
debounce_ticks = MAX(1, configTICK_RATE_HZ / (interval * DEBOUNCE_MS));
picoprobe_info("New baud rate %ld micros %ld interval %lu\n",
line_coding->bit_rate, micros, interval);
uart_deinit(PICOPROBE_UART_INTERFACE);
tud_cdc_write_clear();
tud_cdc_read_flush();
uart_init(PICOPROBE_UART_INTERFACE, line_coding->bit_rate);

uart_inst_t* uart_ptr = PICOPROBE_UART_INTERFACE;
#if defined(PICOPROBE_EXTRA_UART)
if (tty) { uart_ptr = PICOPROBE_EX_UART_INTERFACE; }
#endif

uart_deinit(uart_ptr);
tud_cdc_n_write_clear(tty);
tud_cdc_n_read_flush(tty);
uart_init(uart_ptr, line_coding->bit_rate);

switch (line_coding->parity) {
case CDC_LINE_CODING_PARITY_ODD:
Expand Down Expand Up @@ -208,31 +245,36 @@ void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_coding)
break;
}

uart_set_format(PICOPROBE_UART_INTERFACE, data_bits, stop_bits, parity);
uart_set_format(uart_ptr, data_bits, stop_bits, parity);
vTaskResume(uart_taskhandle);
}

void tud_cdc_line_state_cb(uint8_t itf, bool dtr, bool rts)
{
uint8_t tty = itf;
if (!tty) {
#ifdef PICOPROBE_UART_RTS
gpio_put(PICOPROBE_UART_RTS, !rts);
gpio_put(PICOPROBE_UART_RTS, !rts);
#endif
#ifdef PICOPROBE_UART_DTR
gpio_put(PICOPROBE_UART_DTR, !dtr);
gpio_put(PICOPROBE_UART_DTR, !dtr);
#endif
}

/* CDC drivers use linestate as a bodge to activate/deactivate the interface.
* Resume our UART polling on activate, stop on deactivate */
if (!dtr && !rts) {
vTaskSuspend(uart_taskhandle);
if (!tty) {
#ifdef PICOPROBE_UART_RX_LED
gpio_put(PICOPROBE_UART_RX_LED, 0);
rx_led_debounce = 0;
gpio_put(PICOPROBE_UART_RX_LED, 0);
rx_led_debounce = 0;
#endif
#ifdef PICOPROBE_UART_RX_LED
gpio_put(PICOPROBE_UART_TX_LED, 0);
tx_led_debounce = 0;
gpio_put(PICOPROBE_UART_TX_LED, 0);
tx_led_debounce = 0;
#endif
}
} else
vTaskResume(uart_taskhandle);
}
4 changes: 2 additions & 2 deletions src/cdc_uart.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@

void cdc_thread(void *ptr);
void cdc_uart_init(void);
void cdc_task(void);
void cdc_tasks(void);

extern TaskHandle_t uart_taskhandle;

#endif
#endif
4 changes: 2 additions & 2 deletions src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
#include "tusb_edpt_handler.h"
#include "DAP.h"

// UART0 for Picoprobe debug
// UART0 available / for Picoprobe debug
// UART1 for picoprobe to target device

static uint8_t TxDataBuffer[CFG_TUD_HID_EP_BUFSIZE];
Expand Down Expand Up @@ -104,7 +104,7 @@ int main(void) {

while (!THREADED) {
tud_task();
cdc_task();
cdc_tasks();

#if (PICOPROBE_DEBUG_PROTOCOL == PROTO_DAP_V2)
if (tud_vendor_available()) {
Expand Down
2 changes: 1 addition & 1 deletion src/tusb_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@

//------------- CLASS -------------//
#define CFG_TUD_HID 1
#define CFG_TUD_CDC 1
#define CFG_TUD_CDC CDC_UARTS
#define CFG_TUD_MSC 0
#define CFG_TUD_MIDI 0
#define CFG_TUD_VENDOR 1
Expand Down
19 changes: 16 additions & 3 deletions src/usb_descriptors.c
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,10 @@ enum
ITF_NUM_PROBE, // Old versions of Keil MDK only look at interface 0
ITF_NUM_CDC_COM,
ITF_NUM_CDC_DATA,
#if (CDC_UARTS > 1)
ITF_NUM_CDC_EX_COM,
ITF_NUM_CDC_EX_DATA,
#endif
ITF_NUM_TOTAL
};

Expand All @@ -80,10 +84,16 @@ enum
#define PROBE_OUT_EP_NUM 0x04
#define PROBE_IN_EP_NUM 0x85

#if (CDC_UARTS > 1)
#define CDC_EX_NOTIFICATION_EP_NUM 0x86
#define CDC_EX_DATA_OUT_EP_NUM 0x07
#define CDC_EX_DATA_IN_EP_NUM 0x88
#endif

#if (PICOPROBE_DEBUG_PROTOCOL == PROTO_DAP_V1)
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_HID_INOUT_DESC_LEN)
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN * CDC_UARTS + TUD_HID_INOUT_DESC_LEN)
#else
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN + TUD_VENDOR_DESC_LEN)
#define CONFIG_TOTAL_LEN (TUD_CONFIG_DESC_LEN + TUD_CDC_DESC_LEN * CDC_UARTS + TUD_VENDOR_DESC_LEN)
#endif

static uint8_t const desc_hid_report[] =
Expand Down Expand Up @@ -113,6 +123,9 @@ uint8_t const desc_configuration[] =
#endif
// Interface 1 + 2
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_COM, 6, CDC_NOTIFICATION_EP_NUM, 64, CDC_DATA_OUT_EP_NUM, CDC_DATA_IN_EP_NUM, 64),
#if (CDC_UARTS > 1)
TUD_CDC_DESCRIPTOR(ITF_NUM_CDC_EX_COM, 6, CDC_EX_NOTIFICATION_EP_NUM, 64, CDC_EX_DATA_OUT_EP_NUM, CDC_EX_DATA_IN_EP_NUM, 64),
#endif
};

// Invoked when received GET CONFIGURATION DESCRIPTOR
Expand Down Expand Up @@ -242,4 +255,4 @@ TU_VERIFY_STATIC(sizeof(desc_ms_os_20) == MS_OS_20_DESC_LEN, "Incorrect size");
uint8_t const * tud_descriptor_bos_cb(void)
{
return desc_bos;
}
}