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
12 changes: 12 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ add_executable(picoprobe
src/cdc_uart.c
src/get_serial.c
src/sw_dp_pio.c
src/tusb_edpt_handler.c
)

target_sources(picoprobe PRIVATE
Expand Down Expand Up @@ -54,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
10 changes: 9 additions & 1 deletion include/DAP_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -460,7 +460,11 @@ __STATIC_FORCEINLINE void PIN_nTRST_OUT (uint32_t bit) {
\return Current status of the nRESET DAP hardware I/O pin.
*/
__STATIC_FORCEINLINE uint32_t PIN_nRESET_IN (void) {
#ifdef PROBE_PIN_RESET
return probe_reset_level();
#else
return (0U);
#endif
}

/** nRESET I/O pin: Set Output.
Expand All @@ -469,7 +473,11 @@ __STATIC_FORCEINLINE uint32_t PIN_nRESET_IN (void) {
- 1: release device hardware reset.
*/
__STATIC_FORCEINLINE void PIN_nRESET_OUT (uint32_t bit) {
;
#ifdef PROBE_PIN_RESET
probe_assert_reset(!!bit);
#else
(void) bit;
#endif
}

///@}
Expand Down
5 changes: 4 additions & 1 deletion include/board_example_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
/* Include CDC interface to bridge to target UART. Omit if not used. */
#define PROBE_CDC_UART
/* Target reset GPIO (active-low). Omit if not used.*/
#define PROBE_PIN_RESET 0
#define PROBE_PIN_RESET 1

#define PROBE_SM 0
#define PROBE_PIN_OFFSET 12
Expand Down Expand Up @@ -68,6 +68,9 @@
#define PICOPROBE_UART_RX 5
#define PICOPROBE_UART_INTERFACE uart1
#define PICOPROBE_UART_BAUDRATE 115200
/* Flow control - some or all of these can be omitted if not used */
#define PICOPROBE_UART_RTS 9
#define PICOPROBE_UART_DTR 10
#endif

/* LED config - some or all of these can be omitted if not used */
Expand Down
16 changes: 13 additions & 3 deletions include/board_pico_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,26 @@
#define PROBE_PIN_SWCLK (PROBE_PIN_OFFSET + 0) // 2
#define PROBE_PIN_SWDIO (PROBE_PIN_OFFSET + 1) // 3
// Target reset config
#define PROBE_PIN_RESET 0
#if false
#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
183 changes: 148 additions & 35 deletions src/cdc_uart.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,14 @@
TaskHandle_t uart_taskhandle;
TickType_t last_wake, interval = 100;

static uint8_t tx_buf[CFG_TUD_CDC_TX_BUFSIZE];
static uint8_t rx_buf[CFG_TUD_CDC_RX_BUFSIZE];
/* Max 1 FIFO worth of data */
#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 @@ -54,75 +60,116 @@ 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);
gpio_set_dir(PICOPROBE_UART_RTS, GPIO_OUT);
gpio_put(PICOPROBE_UART_RTS, 1);
#endif
#ifdef PICOPROBE_UART_DTR
gpio_init(PICOPROBE_UART_DTR);
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;
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[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;
} 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 @@ -131,6 +178,9 @@ 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
* fill up half a FIFO. Millis is too coarse for integer divide.
*/
Expand All @@ -139,29 +189,92 @@ void tud_cdc_line_coding_cb(uint8_t itf, cdc_line_coding_t const* line_coding)
vTaskSuspend(uart_taskhandle);
interval = MAX(1, micros / ((1000 * 1000) / configTICK_RATE_HZ));
debounce_ticks = MAX(1, configTICK_RATE_HZ / (interval * DEBOUNCE_MS));
picoprobe_info("New baud rate %d micros %d interval %u\n",
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:
parity = UART_PARITY_ODD;
break;
case CDC_LINE_CODING_PARITY_EVEN:
parity = UART_PARITY_EVEN;
break;
default:
picoprobe_info("invalid parity setting %u\n", line_coding->parity);
/* fallthrough */
case CDC_LINE_CODING_PARITY_NONE:
parity = UART_PARITY_NONE;
break;
}

switch (line_coding->data_bits) {
case 5:
case 6:
case 7:
case 8:
data_bits = line_coding->data_bits;
break;
default:
picoprobe_info("invalid data bits setting: %u\n", line_coding->data_bits);
data_bits = 8;
break;
}

/* The PL011 only supports 1 or 2 stop bits. 1.5 stop bits is translated to 2,
* which is safer than the alternative. */
switch (line_coding->stop_bits) {
case CDC_LINE_CONDING_STOP_BITS_1_5:
case CDC_LINE_CONDING_STOP_BITS_2:
stop_bits = 2;
break;
default:
picoprobe_info("invalid stop bits setting: %u\n", line_coding->stop_bits);
/* fallthrough */
case CDC_LINE_CONDING_STOP_BITS_1:
stop_bits = 1;
break;
}

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);
#endif
#ifdef PICOPROBE_UART_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
Loading