From 9be1f3ef760b4915ab0b697515bede9a2ac43163 Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Fri, 21 Feb 2025 16:21:14 -0800 Subject: [PATCH 1/4] Use microsecond tick for better timeout tracking Also: * Sideset the USB pins when starting and resetting TX. The output may be active. * Delay EOP sampling capture until later to reduce incorrect EOP detection. --- examples/test_ll/test_ll.c | 2 +- src/pio_usb.c | 188 +++++++++++++++++++++---------------- src/pio_usb_device.c | 8 +- src/pio_usb_host.c | 12 ++- src/pio_usb_ll.h | 20 +++- src/usb_rx.pio | 8 +- src/usb_rx.pio.h | 10 +- src/usb_tx.pio | 12 +-- src/usb_tx.pio.h | 2 + 9 files changed, 150 insertions(+), 112 deletions(-) diff --git a/examples/test_ll/test_ll.c b/examples/test_ll/test_ll.c index 5ad402a..1f9fed9 100644 --- a/examples/test_ll/test_ll.c +++ b/examples/test_ll/test_ll.c @@ -14,7 +14,7 @@ #include "pio_usb_configuration.h" #include "pio_usb_ll.h" #include "usb_definitions.h" -#include "usb_rx.pio.h" +#include pio_usb_configuration_t pio_usb_config = PIO_USB_DEFAULT_CONFIG; diff --git a/src/pio_usb.c b/src/pio_usb.c index 80e343a..01bc4fb 100644 --- a/src/pio_usb.c +++ b/src/pio_usb.c @@ -23,8 +23,6 @@ #include "pio_usb_configuration.h" #include "pio_usb_ll.h" #include "usb_crc.h" -#include "usb_tx.pio.h" -#include "usb_rx.pio.h" #define UNUSED_PARAMETER(x) (void)x @@ -42,24 +40,33 @@ static uint8_t pre_encoded[5]; // Bus functions //--------------------------------------------------------------------+ -static void __no_inline_not_in_flash_func(send_pre)(const pio_port_t *pp) { +static void __no_inline_not_in_flash_func(send_pre)(pio_port_t *pp) { // send PRE token in full-speed + pp->low_speed = false; uint16_t instr = pp->fs_tx_pre_program->instructions[0]; pp->pio_usb_tx->instr_mem[pp->offset_tx] = instr; SM_SET_CLKDIV(pp->pio_usb_tx, pp->sm_tx, pp->clk_div_fs_tx); pio_sm_exec(pp->pio_usb_tx, pp->sm_tx, pp->tx_start_instr); + pp->pio_usb_tx->irq = IRQ_TX_ALL_MASK; // clear complete flag dma_channel_transfer_from_buffer_now(pp->tx_ch, pre_encoded, sizeof(pre_encoded)); - pp->pio_usb_tx->irq = IRQ_TX_ALL_MASK; // clear complete flag while ((pp->pio_usb_tx->irq & IRQ_TX_EOP_MASK) == 0) { continue; } - pio_sm_clear_fifos(pp->pio_usb_tx, pp->sm_tx); + // Wait for complete transmission of the PRE packet. We don't want to + // accidentally send trailing Ks in low speed mode due to an early start + // instruction that re-enables the outputs. + uint32_t stall_mask = 1 << (PIO_FDEBUG_TXSTALL_LSB + pp->sm_tx); + pp->pio_usb_tx->fdebug = stall_mask; // clear sticky stall mask bit + while (!(pp->pio_usb_tx->fdebug & stall_mask)) { + continue; + } // change bus speed to low-speed + pp->low_speed = true; pio_sm_set_enabled(pp->pio_usb_tx, pp->sm_tx, false); instr = pp->fs_tx_program->instructions[0]; pp->pio_usb_tx->instr_mem[pp->offset_tx] = instr; @@ -75,7 +82,7 @@ static void __no_inline_not_in_flash_func(send_pre)(const pio_port_t *pp) { pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_eop, true); } -void __not_in_flash_func(pio_usb_bus_usb_transfer)(const pio_port_t *pp, +void __not_in_flash_func(pio_usb_bus_usb_transfer)(pio_port_t *pp, uint8_t *data, uint16_t len) { if (pp->need_pre) { send_pre(pp); @@ -89,14 +96,14 @@ void __not_in_flash_func(pio_usb_bus_usb_transfer)(const pio_port_t *pp, while ((pp->pio_usb_tx->irq & IRQ_TX_ALL_MASK) == 0) { continue; } - pio_sm_clear_fifos(pp->pio_usb_tx, pp->sm_tx); - while (*pc < PIO_USB_TX_ENCODED_DATA_COMP) { + pp->pio_usb_tx->irq = IRQ_TX_ALL_MASK; // clear complete flag + while (*pc <= PIO_USB_TX_ENCODED_DATA_COMP) { continue; } } void __no_inline_not_in_flash_func(pio_usb_bus_send_handshake)( - const pio_port_t *pp, uint8_t pid) { + pio_port_t *pp, uint8_t pid) { switch (pid) { case USB_PID_ACK: pio_usb_bus_usb_transfer(pp, ack_encoded, 5); @@ -113,7 +120,7 @@ void __no_inline_not_in_flash_func(pio_usb_bus_send_handshake)( } } -void __no_inline_not_in_flash_func(pio_usb_bus_send_token)(const pio_port_t *pp, +void __no_inline_not_in_flash_func(pio_usb_bus_send_token)(pio_port_t *pp, uint8_t token, uint8_t addr, uint8_t ep_num) { @@ -139,88 +146,96 @@ void __no_inline_not_in_flash_func(pio_usb_bus_prepare_receive)(const pio_port_t pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, true); } -void __no_inline_not_in_flash_func(pio_usb_bus_start_receive)(const pio_port_t *pp) { - pp->pio_usb_rx->irq = IRQ_RX_ALL_MASK; +static uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_packet)(pio_port_t* pp) { + uint16_t crc = 0xffff; + uint16_t crc_prev = 0xffff; + uint16_t crc_prev2 = 0xffff; + uint16_t crc_receive = 0xffff; + uint16_t crc_receive_inverse = 0; + bool crc_match = false; + + const uint16_t rx_buf_len = sizeof(pp->usb_rx_buffer) / sizeof(pp->usb_rx_buffer[0]); + + // USB 2.0 specs: 7.1.19.1: handshake timeout + // Full-Speed (12 Mbps): 1 bit time = 1 / 12 MHz = 83.3 ns --> 16 bit times = 1.33 µs + // Low-Speed (1.5 Mbps): 1 bit time = 1 / 1.5 MHz = 666.7 ns --> 16 bit times = 10.67 µs + + // We're starting the timing somewhere in the current microsecond so always assume the first one + // is less than a full microsecond. For example, a wait of 2 could actually be 1.1 microseconds. + // We will use 3 us (24 bit time) for Full speed and 12us (18 bit time) for Lowspeed. + uint32_t start = time_us_32(); + uint32_t timeout = pp->low_speed ? 12 : 3; + while (time_us_32() - start <= timeout) { + if ((pp->pio_usb_rx->irq & IRQ_RX_START_MASK) != 0) { + break; + } + } + + // Timeout if we're not started. + if ((pp->pio_usb_rx->irq & IRQ_RX_START_MASK) == 0) { + return 0; + } + + int16_t idx = 0; + // Timeout in seven microseconds. That is enough time to receive one byte at + // low speed. This is to detect packets without an EOP because the device was + // unplugged. + start = time_us_32(); + while (time_us_32() - start <= 7) { + if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) { + uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24; + if (idx < rx_buf_len) { + pp->usb_rx_buffer[idx] = data; + } + start = time_us_32(); + + if (idx >= 2) { + crc_prev2 = crc_prev; + crc_prev = crc; + crc = update_usb_crc16(crc, data); + crc_receive = (crc_receive >> 8) | (data << 8); + crc_receive_inverse = crc_receive ^ 0xffff; + crc_match = (crc_receive_inverse == crc_prev2); + } + idx++; + } else if ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) != 0) { + // Exit early if we've gotten an EOP. There *might* be a race between EOP + // detection and NRZI decoding but it is unlikely. + break; + } + } + + if (idx >= 4 && !crc_match) { + // CRC failed, discard the packet. + return 0; + } + return idx; } uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_handshake)(pio_port_t* pp) { - int16_t t = 240; - int16_t idx = 0; - while (t--) { - if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) { - uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24; - pp->usb_rx_buffer[idx++] = data; - if (idx == 2) { - break; - } - } + uint8_t len = pio_usb_bus_wait_packet(pp); + if (len != 2) { + return 0; } - if (t > 0) { - while ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) { - continue; - } - } - - // pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, true); - return pp->usb_rx_buffer[1]; } int __no_inline_not_in_flash_func(pio_usb_bus_receive_packet_and_handshake)( pio_port_t *pp, uint8_t handshake) { - uint16_t crc = 0xffff; - uint16_t crc_prev = 0xffff; - uint16_t crc_prev2 = 0xffff; - uint16_t crc_receive = 0xffff; - uint16_t crc_receive_inverse; - bool crc_match = false; - int16_t t = 240; - uint16_t idx = 0; - uint16_t nak_timeout = 10000; - const uint16_t rx_buf_len = sizeof(pp->usb_rx_buffer) / sizeof(pp->usb_rx_buffer[0]); - - while (t--) { - if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) { - uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24; - pp->usb_rx_buffer[idx++] = data; - if (idx == 2) { - break; - } - } + uint8_t len = pio_usb_bus_wait_packet(pp); + if (len == 0) { + // Return and don't respond with a handshake. + return -1; + } + // Only handshake if the other end gave data. + if (len >= 4) { + pio_usb_bus_send_handshake(pp, handshake); } - // timing critical start - if (t > 0) { - if (handshake == USB_PID_ACK) { - while ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0 && idx < rx_buf_len - 1) { - if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) { - uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24; - crc_prev2 = crc_prev; - crc_prev = crc; - crc = update_usb_crc16(crc, data); - pp->usb_rx_buffer[idx++] = data; - crc_receive = (crc_receive >> 8) | (data << 8); - crc_receive_inverse = crc_receive ^ 0xffff; - crc_match = (crc_receive_inverse == crc_prev2); - } - } - - if (idx >= 4 && crc_match) { - pio_usb_bus_send_handshake(pp, USB_PID_ACK); - // timing critical end - return idx - 4; - } - } else { - // just discard received data since we NAK/STALL anyway - while ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0 && nak_timeout--) { - continue; - } - pio_sm_clear_fifos(pp->pio_usb_rx, pp->sm_rx); - - pio_usb_bus_send_handshake(pp, handshake); - } + if (handshake == USB_PID_ACK) { + return len - 4; } return -1; @@ -244,8 +259,15 @@ static void __no_inline_not_in_flash_func(initialize_host_programs)( pp->offset_tx = 0; usb_tx_fs_program_init(pp->pio_usb_tx, pp->sm_tx, pp->offset_tx, port->pin_dp, port->pin_dm); - pp->tx_start_instr = pio_encode_jmp(pp->offset_tx + 4); - pp->tx_reset_instr = pio_encode_jmp(pp->offset_tx + 2); + uint32_t sideset_fj_lk; + if (c->pinout == PIO_USB_PINOUT_DPDM) { + sideset_fj_lk = pio_encode_sideset(2, usb_tx_dpdm_FJ_LK); + } else { + sideset_fj_lk = pio_encode_sideset(2, usb_tx_dmdp_FJ_LK); + } + + pp->tx_start_instr = pio_encode_jmp(pp->offset_tx + 4) | sideset_fj_lk; + pp->tx_reset_instr = pio_encode_jmp(pp->offset_tx + 2) | sideset_fj_lk; add_pio_host_rx_program(pp->pio_usb_rx, &usb_nrzi_decoder_program, &usb_nrzi_decoder_debug_program, &pp->offset_rx, @@ -422,11 +444,11 @@ uint8_t __no_inline_not_in_flash_func(pio_usb_ll_encode_tx_data)( int current_state = 1; int bit_stuffing = 6; for (int idx = 0; idx < buffer_len; idx++) { - uint8_t byte = buffer[idx]; + uint8_t data_byte = buffer[idx]; for (int b = 0; b < 8; b++) { uint8_t byte_idx = bit_idx >> 2; encoded_data[byte_idx] <<= 2; - if (byte & (1 << b)) { + if (data_byte & (1 << b)) { if (current_state) { encoded_data[byte_idx] |= PIO_USB_TX_ENCODED_DATA_K; } else { diff --git a/src/pio_usb_device.c b/src/pio_usb_device.c index f608d7b..ec456a2 100644 --- a/src/pio_usb_device.c +++ b/src/pio_usb_device.c @@ -15,8 +15,7 @@ #include "pio_usb_ll.h" #include "usb_crc.h" -#include "usb_rx.pio.h" -#include "usb_tx.pio.h" +#include #include "hardware/dma.h" #include "hardware/irq.h" @@ -200,10 +199,10 @@ static void __no_inline_not_in_flash_func(usb_device_packet_handler)(void) { endpoint_t *ep = PIO_USB_ENDPOINT(ep_num << 1); gpio_clr_mask(1<<4); - uint8_t hanshake = ep->stalled + uint8_t handshake = ep->stalled ? USB_PID_STALL : (ep->has_transfer ? USB_PID_ACK : USB_PID_NAK); - int res = pio_usb_bus_receive_packet_and_handshake(pp, hanshake); + int res = pio_usb_bus_receive_packet_and_handshake(pp, handshake); pio_sm_clear_fifos(pp->pio_usb_rx, pp->sm_rx); restart_usb_receiver(pp); pp->pio_usb_rx->irq = IRQ_RX_ALL_MASK; @@ -257,6 +256,7 @@ usb_device_t *pio_usb_device_init(const pio_usb_configuration_t *c, pio_usb_bus_init(pp, c, rport); gpio_disable_pulls(rport->pin_dp); // needs external pull-up rport->mode = PIO_USB_MODE_DEVICE; + pp->host = false; memset(dev, 0, sizeof(*dev)); for (int i = 0; i < PIO_USB_DEV_EP_CNT; i++) { diff --git a/src/pio_usb_host.c b/src/pio_usb_host.c index 005042d..7d2fa1b 100644 --- a/src/pio_usb_host.c +++ b/src/pio_usb_host.c @@ -18,8 +18,6 @@ #include "pio_usb.h" #include "pio_usb_ll.h" #include "usb_crc.h" -#include "usb_rx.pio.h" -#include "usb_tx.pio.h" enum { TRANSACTION_MAX_RETRY = 3, // Number of times to retry a failed transaction @@ -68,6 +66,7 @@ usb_device_t *pio_usb_host_init(const pio_usb_configuration_t *c) { pio_usb_bus_init(pp, c, root); root->mode = PIO_USB_MODE_HOST; + pp->host = true; float const cpu_freq = (float)clock_get_hz(clk_sys); pio_calculate_clkdiv_from_float(cpu_freq / 48000000, @@ -152,6 +151,7 @@ __no_inline_not_in_flash_func(configure_tx_program)(pio_port_t *pp, static void __no_inline_not_in_flash_func(configure_fullspeed_host)( pio_port_t *pp, root_port_t *port) { + pp->low_speed = false; configure_tx_program(pp, port); pio_sm_clear_fifos(pp->pio_usb_tx, pp->sm_tx); override_pio_program(pp->pio_usb_tx, pp->fs_tx_program, pp->offset_tx); @@ -169,6 +169,7 @@ static void __no_inline_not_in_flash_func(configure_fullspeed_host)( static void __no_inline_not_in_flash_func(configure_lowspeed_host)( pio_port_t *pp, root_port_t *port) { + pp->low_speed = true; configure_tx_program(pp, port); pio_sm_clear_fifos(pp->pio_usb_tx, pp->sm_tx); override_pio_program(pp->pio_usb_tx, pp->ls_tx_program, pp->offset_tx); @@ -193,8 +194,9 @@ static void __no_inline_not_in_flash_func(configure_root_port)( } } -static void __no_inline_not_in_flash_func(restore_fs_bus)(const pio_port_t *pp) { +static void __no_inline_not_in_flash_func(restore_fs_bus)(pio_port_t *pp) { // change bus speed to full-speed + pp->low_speed = false; pio_sm_set_enabled(pp->pio_usb_tx, pp->sm_tx, false); SM_SET_CLKDIV(pp->pio_usb_tx, pp->sm_tx, pp->clk_div_fs_tx); pio_sm_set_enabled(pp->pio_usb_tx, pp->sm_tx, true); @@ -210,8 +212,8 @@ static void __no_inline_not_in_flash_func(restore_fs_bus)(const pio_port_t *pp) // Time about 1us ourselves so it lives in RAM. static void __not_in_flash_func(busy_wait_1_us)(void) { - uint32_t start = timer_hw->timerawl; - while (timer_hw->timerawl == start) { + uint32_t start = time_us_32(); + while (time_us_32() == start) { tight_loop_contents(); } } diff --git a/src/pio_usb_ll.h b/src/pio_usb_ll.h index 3b4ac6d..7961dd3 100644 --- a/src/pio_usb_ll.h +++ b/src/pio_usb_ll.h @@ -10,6 +10,9 @@ #include "usb_definitions.h" #include +#include +#include + enum { PIO_USB_INTS_CONNECT_POS = 0, PIO_USB_INTS_DISCONNECT_POS, @@ -80,6 +83,8 @@ typedef struct { pio_clk_div_t clk_div_ls_rx; bool need_pre; + bool low_speed; + bool host; uint8_t usb_rx_buffer[128]; } pio_port_t; @@ -112,6 +117,7 @@ extern pio_port_t pio_port[1]; #define IRQ_TX_EOP_MASK (1 << IRQ_TX_EOP) #define IRQ_TX_ALL_MASK (IRQ_TX_EOP_MASK) #define IRQ_RX_COMP_MASK (1 << IRQ_RX_EOP) +#define IRQ_RX_START_MASK (1 << IRQ_RX_START) #define IRQ_RX_ALL_MASK \ ((1 << IRQ_RX_EOP) | (1 << IRQ_RX_BS_ERR) | (1 << IRQ_RX_START) | \ (1 << DECODER_TRIGGER)) @@ -124,15 +130,14 @@ extern pio_port_t pio_port[1]; void pio_usb_bus_init(pio_port_t *pp, const pio_usb_configuration_t *c, root_port_t *root); -void pio_usb_bus_start_receive(const pio_port_t *pp); void pio_usb_bus_prepare_receive(const pio_port_t *pp); int pio_usb_bus_receive_packet_and_handshake(pio_port_t *pp, uint8_t handshake); -void pio_usb_bus_usb_transfer(const pio_port_t *pp, uint8_t *data, +void pio_usb_bus_usb_transfer(pio_port_t *pp, uint8_t *data, uint16_t len); uint8_t pio_usb_bus_wait_handshake(pio_port_t *pp); -void pio_usb_bus_send_handshake(const pio_port_t *pp, uint8_t pid); -void pio_usb_bus_send_token(const pio_port_t *pp, uint8_t token, uint8_t addr, +void pio_usb_bus_send_handshake(pio_port_t *pp, uint8_t pid); +void pio_usb_bus_send_token(pio_port_t *pp, uint8_t token, uint8_t addr, uint8_t ep_num); static __always_inline port_pin_status_t @@ -143,6 +148,13 @@ pio_usb_bus_get_line_state(root_port_t *root) { return (dm << 1) | dp; } +static __always_inline void pio_usb_bus_start_receive(const pio_port_t *pp) { + pp->pio_usb_rx->irq = IRQ_RX_ALL_MASK; + while ((pp->pio_usb_rx->irq & IRQ_RX_ALL_MASK) != 0) { + continue; + } +} + //--------------------------------------------------------------------+ // Low Level functions //--------------------------------------------------------------------+ diff --git a/src/usb_rx.pio b/src/usb_rx.pio index aa0807e..b2afabe 100644 --- a/src/usb_rx.pio +++ b/src/usb_rx.pio @@ -41,11 +41,11 @@ pin_went_low: .wrap pin_still_high: - mov x, isr [2] + mov x, isr [1] jmp x-- eop ; Jump to eop if jmp_pin and in_pin are high because both inputs are inverted ; Jump to here on rising edge pin_went_high: - mov isr, null + mov isr, null [1] in pins, 1 ; Capture the pin to check eop. irq DECODER_TRIGGER ; Trigger NRZI decoder jmp pin pin_still_high @@ -75,11 +75,11 @@ pin_went_low: .wrap pin_still_high: - mov x, isr [2] side db1 + mov x, isr [1] side db1 jmp x-- eop side db1 ; Jump to eop if jmp_pin and in_pin are high because both inputs are inverted ; Jump to here on rising edge pin_went_high: - mov isr, null side db1 + mov isr, null [1] side db1 in pins, 1 side db0 ; Capture the pin to check eop. irq DECODER_TRIGGER side db0 ; Trigger NRZI decoder jmp pin pin_still_high side db0 diff --git a/src/usb_rx.pio.h b/src/usb_rx.pio.h index 29341e1..d8de179 100644 --- a/src/usb_rx.pio.h +++ b/src/usb_rx.pio.h @@ -33,9 +33,9 @@ static const uint16_t usb_edge_detector_program_instructions[] = { 0x00cc, // 8: jmp pin, 12 0x00cc, // 9: jmp pin, 12 // .wrap - 0xa226, // 10: mov x, isr [2] + 0xa126, // 10: mov x, isr [2] 0x0040, // 11: jmp x--, 0 - 0xa0c3, // 12: mov isr, null + 0xa1c3, // 12: mov isr, null 0x4001, // 13: in pins, 1 0xc004, // 14: irq nowait 4 0x00ca, // 15: jmp pin, 10 @@ -76,9 +76,9 @@ static const uint16_t usb_edge_detector_debug_program_instructions[] = { 0x10cc, // 8: jmp pin, 12 side 1 0x10cc, // 9: jmp pin, 12 side 1 // .wrap - 0xb226, // 10: mov x, isr side 1 [2] + 0xb126, // 10: mov x, isr side 1 [1] 0x1040, // 11: jmp x--, 0 side 1 - 0xb0c3, // 12: mov isr, null side 1 + 0xb1c3, // 12: mov isr, null side 1 [1] 0x4001, // 13: in pins, 1 side 0 0xc004, // 14: irq nowait 4 side 0 0x00ca, // 15: jmp pin, 10 side 0 @@ -196,7 +196,7 @@ static inline void usb_rx_fs_program_init(PIO pio, uint sm, uint offset, uint pi } sm_config_set_in_pins(&c, pin_dp); // for WAIT, IN sm_config_set_jmp_pin(&c, pin_dp); // for JMP - // Shift to right, autopull enabled, 8bit + // Shift to right, autopush enabled, 8bit sm_config_set_in_shift(&c, true, true, 8); sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_RX); pio_sm_init(pio, sm, offset, &c); diff --git a/src/usb_tx.pio b/src/usb_tx.pio index b64fe64..607b9d8 100644 --- a/src/usb_tx.pio +++ b/src/usb_tx.pio @@ -3,15 +3,15 @@ .define public IRQ_TX_EOP 0 ; EOP start flag bit ; USB NRZI transmitter -; Run at 48 MHz for full-spped (x4) -; Run at 6 MHz for low-spped (x4) +; Run at 48 MHz for full-speed (x4) +; Run at 6 MHz for low-speed (x4) ; autopull enabled ; Should be placed at address 0 .program usb_tx_dpdm .side_set 2 ; J for fs, K for ls -.define FJ_LK 0b01 +.define public FJ_LK 0b01 ; K for fs, J for ls .define FK_LJ 0b10 .define SE0 0b00 @@ -49,14 +49,14 @@ set pindirs, 0b11 side FJ_LK .wrap ; USB NRZI transmitter -; Run at 48 MHz for full-spped -; Run at 6 MHz for low-spped +; Run at 48 MHz for full-speed +; Run at 6 MHz for low-speed ; autopull enabled .program usb_tx_dmdp .side_set 2 .define FK_LJ 0b01 -.define FJ_LK 0b10 +.define public FJ_LK 0b10 .define SE0 0b00 irq IRQ_TX_EOP side SE0 [7] diff --git a/src/usb_tx.pio.h b/src/usb_tx.pio.h index 991565b..bb972a1 100644 --- a/src/usb_tx.pio.h +++ b/src/usb_tx.pio.h @@ -16,6 +16,7 @@ #define usb_tx_dpdm_wrap_target 1 #define usb_tx_dpdm_wrap 4 +#define usb_tx_dpdm_FJ_LK 1 static const uint16_t __not_in_flash("tx_program") usb_tx_dpdm_program_instructions[] = { 0xc700, // 0: irq nowait 0 side 0 [7] @@ -80,6 +81,7 @@ static inline pio_sm_config usb_tx_pre_dpdm_program_get_default_config(uint offs #define usb_tx_dmdp_wrap_target 1 #define usb_tx_dmdp_wrap 4 +#define usb_tx_dmdp_FJ_LK 2 static const uint16_t __not_in_flash("tx_program") usb_tx_dmdp_program_instructions[] = { 0xc700, // 0: irq nowait 0 side 0 [7] From 1862cc008e026cbd07b97b28e29eafb5f38b35fb Mon Sep 17 00:00:00 2001 From: Scott Shawcroft Date: Wed, 5 Mar 2025 13:42:32 -0800 Subject: [PATCH 2/4] Capture for EOP after triggering decoder. Correct low duration too. --- src/usb_rx.pio | 8 ++++---- src/usb_rx.pio.h | 16 ++++++++-------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/usb_rx.pio b/src/usb_rx.pio index b2afabe..fc71dbf 100644 --- a/src/usb_rx.pio +++ b/src/usb_rx.pio @@ -32,8 +32,8 @@ pin_still_low: ; Resync on rising edge pin_low: jmp pin pin_went_high -pin_went_low: jmp pin pin_went_high +pin_went_low: jmp pin pin_went_high jmp pin pin_went_high jmp pin pin_went_high @@ -46,8 +46,8 @@ pin_still_high: ; Jump to here on rising edge pin_went_high: mov isr, null [1] + irq DECODER_TRIGGER ; Trigger NRZI decoder in pins, 1 ; Capture the pin to check eop. - irq DECODER_TRIGGER ; Trigger NRZI decoder jmp pin pin_still_high jmp pin_went_low ; To adjust interval of decoder trigger, jump to pin_went_low (not pin_low) @@ -66,8 +66,8 @@ pin_still_low: ; Resync on rising edge pin_low: jmp pin pin_went_high side db1 -pin_went_low: jmp pin pin_went_high side db1 +pin_went_low: jmp pin pin_went_high side db1 jmp pin pin_went_high side db1 jmp pin pin_went_high side db1 @@ -80,8 +80,8 @@ pin_still_high: ; Jump to here on rising edge pin_went_high: mov isr, null [1] side db1 + irq DECODER_TRIGGER side db0 ; Trigger NRZI decoder in pins, 1 side db0 ; Capture the pin to check eop. - irq DECODER_TRIGGER side db0 ; Trigger NRZI decoder jmp pin pin_still_high side db0 jmp pin_went_low side db1 ; To adjust interval of decoder trigger, jump to pin_went_low (not pin_low) diff --git a/src/usb_rx.pio.h b/src/usb_rx.pio.h index d8de179..5212aa1 100644 --- a/src/usb_rx.pio.h +++ b/src/usb_rx.pio.h @@ -33,13 +33,13 @@ static const uint16_t usb_edge_detector_program_instructions[] = { 0x00cc, // 8: jmp pin, 12 0x00cc, // 9: jmp pin, 12 // .wrap - 0xa126, // 10: mov x, isr [2] + 0xa126, // 10: mov x, isr [1] 0x0040, // 11: jmp x--, 0 - 0xa1c3, // 12: mov isr, null - 0x4001, // 13: in pins, 1 - 0xc004, // 14: irq nowait 4 + 0xa1c3, // 12: mov isr, null [1] + 0xc004, // 13: irq nowait 4 + 0x4001, // 14: in pins, 1 0x00ca, // 15: jmp pin, 10 - 0x0005, // 16: jmp 5 + 0x0006, // 16: jmp 6 }; #if !PICO_NO_HARDWARE @@ -79,10 +79,10 @@ static const uint16_t usb_edge_detector_debug_program_instructions[] = { 0xb126, // 10: mov x, isr side 1 [1] 0x1040, // 11: jmp x--, 0 side 1 0xb1c3, // 12: mov isr, null side 1 [1] - 0x4001, // 13: in pins, 1 side 0 - 0xc004, // 14: irq nowait 4 side 0 + 0xc004, // 13: irq nowait 4 side 0 + 0x4001, // 14: in pins, 1 side 0 0x00ca, // 15: jmp pin, 10 side 0 - 0x1005, // 16: jmp 5 side 1 + 0x1006, // 16: jmp 6 side 1 }; #if !PICO_NO_HARDWARE From c1fea8cf6a7c013df2db8d0d049011124b48f9fe Mon Sep 17 00:00:00 2001 From: hathach Date: Fri, 28 Mar 2025 14:07:24 +0700 Subject: [PATCH 3/4] fix rp2040 critical timing when sending handshake --- src/pio_usb.c | 118 ++++++++++++++++++++++++--------------------- src/pio_usb_host.c | 4 +- src/usb_crc.h | 9 +++- 3 files changed, 74 insertions(+), 57 deletions(-) diff --git a/src/pio_usb.c b/src/pio_usb.c index 01bc4fb..900853d 100644 --- a/src/pio_usb.c +++ b/src/pio_usb.c @@ -97,7 +97,7 @@ void __not_in_flash_func(pio_usb_bus_usb_transfer)(pio_port_t *pp, continue; } pp->pio_usb_tx->irq = IRQ_TX_ALL_MASK; // clear complete flag - while (*pc <= PIO_USB_TX_ENCODED_DATA_COMP) { + while (*pc < PIO_USB_TX_ENCODED_DATA_COMP) { continue; } } @@ -146,58 +146,42 @@ void __no_inline_not_in_flash_func(pio_usb_bus_prepare_receive)(const pio_port_t pio_sm_set_enabled(pp->pio_usb_rx, pp->sm_rx, true); } -static uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_packet)(pio_port_t* pp) { - uint16_t crc = 0xffff; - uint16_t crc_prev = 0xffff; - uint16_t crc_prev2 = 0xffff; - uint16_t crc_receive = 0xffff; - uint16_t crc_receive_inverse = 0; - bool crc_match = false; - - const uint16_t rx_buf_len = sizeof(pp->usb_rx_buffer) / sizeof(pp->usb_rx_buffer[0]); - +static inline __force_inline bool pio_usb_bus_wait_for_rx_start(const pio_port_t* pp) { // USB 2.0 specs: 7.1.19.1: handshake timeout // Full-Speed (12 Mbps): 1 bit time = 1 / 12 MHz = 83.3 ns --> 16 bit times = 1.33 µs // Low-Speed (1.5 Mbps): 1 bit time = 1 / 1.5 MHz = 666.7 ns --> 16 bit times = 10.67 µs // We're starting the timing somewhere in the current microsecond so always assume the first one // is less than a full microsecond. For example, a wait of 2 could actually be 1.1 microseconds. - // We will use 3 us (24 bit time) for Full speed and 12us (18 bit time) for Lowspeed. - uint32_t start = time_us_32(); + // We will use 3 us (24 bit time) for Full speed and 12us (18 bit time) for Low speed. + uint32_t start = get_time_us_32(); uint32_t timeout = pp->low_speed ? 12 : 3; - while (time_us_32() - start <= timeout) { + while (get_time_us_32() - start <= timeout) { if ((pp->pio_usb_rx->irq & IRQ_RX_START_MASK) != 0) { - break; + return true; } } + return false; +}; - // Timeout if we're not started. - if ((pp->pio_usb_rx->irq & IRQ_RX_START_MASK) == 0) { +uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_handshake)(pio_port_t* pp) { + if (!pio_usb_bus_wait_for_rx_start(pp)) { return 0; } int16_t idx = 0; - // Timeout in seven microseconds. That is enough time to receive one byte at - // low speed. This is to detect packets without an EOP because the device was - // unplugged. - start = time_us_32(); - while (time_us_32() - start <= 7) { + // Timeout in seven microseconds. That is enough time to receive one byte at low speed. + // This is to detect packets without an EOP because the device was unplugged. + uint32_t start = get_time_us_32(); + while (get_time_us_32() - start <= 7) { if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) { uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24; - if (idx < rx_buf_len) { - pp->usb_rx_buffer[idx] = data; - } - start = time_us_32(); + pp->usb_rx_buffer[idx++] = data; - if (idx >= 2) { - crc_prev2 = crc_prev; - crc_prev = crc; - crc = update_usb_crc16(crc, data); - crc_receive = (crc_receive >> 8) | (data << 8); - crc_receive_inverse = crc_receive ^ 0xffff; - crc_match = (crc_receive_inverse == crc_prev2); + start = get_time_us_32(); // reset timeout when a byte is received + if (idx == 2) { + break; } - idx++; } else if ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) != 0) { // Exit early if we've gotten an EOP. There *might* be a race between EOP // detection and NRZI decoding but it is unlikely. @@ -205,17 +189,7 @@ static uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_packet)(pio_port_t } } - if (idx >= 4 && !crc_match) { - // CRC failed, discard the packet. - return 0; - } - return idx; -} - -uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_handshake)(pio_port_t* pp) { - - uint8_t len = pio_usb_bus_wait_packet(pp); - if (len != 2) { + if (idx != 2) { return 0; } @@ -224,18 +198,54 @@ uint8_t __no_inline_not_in_flash_func(pio_usb_bus_wait_handshake)(pio_port_t* pp int __no_inline_not_in_flash_func(pio_usb_bus_receive_packet_and_handshake)( pio_port_t *pp, uint8_t handshake) { - uint8_t len = pio_usb_bus_wait_packet(pp); - if (len == 0) { - // Return and don't respond with a handshake. + if (!pio_usb_bus_wait_for_rx_start(pp)) { return -1; } - // Only handshake if the other end gave data. - if (len >= 4) { - pio_usb_bus_send_handshake(pp, handshake); - } - if (handshake == USB_PID_ACK) { - return len - 4; + uint16_t crc = 0xffff; + uint16_t crc_prev = 0xffff; + uint16_t crc_prev2 = 0xffff; + uint16_t crc_receive = 0xffff; + uint16_t crc_receive_inverse = 0; + bool crc_match = false; + const uint16_t rx_buf_len = sizeof(pp->usb_rx_buffer) / sizeof(pp->usb_rx_buffer[0]); + int16_t idx = 0; + + // Timeout in seven microseconds. That is enough time to receive one byte at low speed. + // This is to detect packets without an EOP because the device was unplugged. + uint32_t start = get_time_us_32(); + while (get_time_us_32() - start <= 7) { + if (pio_sm_get_rx_fifo_level(pp->pio_usb_rx, pp->sm_rx)) { + uint8_t data = pio_sm_get(pp->pio_usb_rx, pp->sm_rx) >> 24; + if (idx < rx_buf_len) { + pp->usb_rx_buffer[idx] = data; + } + start = get_time_us_32(); // reset timeout when a byte is received + + if (idx >= 2) { + crc_prev2 = crc_prev; + crc_prev = crc; + crc = update_usb_crc16(crc, data); + crc_receive = (crc_receive >> 8) | (data << 8); + crc_receive_inverse = crc_receive ^ 0xffff; + crc_match = (crc_receive_inverse == crc_prev2); + } + idx++; + } else if ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) != 0) { + // Exit early if we've gotten an EOP. There *might* be a race between EOP + // detection and NRZI decoding but it is unlikely. + if (handshake == USB_PID_ACK) { + // Only ACK if crc matches + if (idx >= 4 && crc_match) { + pio_usb_bus_send_handshake(pp, USB_PID_ACK); + return idx - 4; + } + } else { + // always send other handshake NAK/STALL + pio_usb_bus_send_handshake(pp, handshake); + } + break; + } } return -1; diff --git a/src/pio_usb_host.c b/src/pio_usb_host.c index 7d2fa1b..5456bb6 100644 --- a/src/pio_usb_host.c +++ b/src/pio_usb_host.c @@ -212,8 +212,8 @@ static void __no_inline_not_in_flash_func(restore_fs_bus)(pio_port_t *pp) { // Time about 1us ourselves so it lives in RAM. static void __not_in_flash_func(busy_wait_1_us)(void) { - uint32_t start = time_us_32(); - while (time_us_32() == start) { + uint32_t start = get_time_us_32(); + while (get_time_us_32() == start) { tight_loop_contents(); } } diff --git a/src/usb_crc.h b/src/usb_crc.h index 3c6831e..c7f5d03 100644 --- a/src/usb_crc.h +++ b/src/usb_crc.h @@ -12,4 +12,11 @@ extern const uint16_t crc16_tbl[256]; static inline uint16_t __time_critical_func(update_usb_crc16)(uint16_t crc, uint8_t data) { crc = (crc >> 8) ^ crc16_tbl[(crc ^ data) & 0xff]; return crc; -} \ No newline at end of file +} + +#ifndef PICO_DEFAULT_TIMER_INSTANCE // not defined in sdk v1 +#define PICO_DEFAULT_TIMER_INSTANCE() timer_hw +#endif + +// time_us_32() not force inline and may be in flash, implement timestamp ourselves +#define get_time_us_32() (PICO_DEFAULT_TIMER_INSTANCE()->timerawl) From 5c74cc161e22dc539922711c4d1b70d32734994e Mon Sep 17 00:00:00 2001 From: hathach Date: Tue, 1 Apr 2025 15:03:24 +0700 Subject: [PATCH 4/4] clean up --- examples/test_ll/test_ll.c | 1 - src/pio_usb_device.c | 4 ++-- src/pio_usb_host.c | 1 - src/pio_usb_ll.h | 5 ++--- 4 files changed, 4 insertions(+), 7 deletions(-) diff --git a/examples/test_ll/test_ll.c b/examples/test_ll/test_ll.c index 1f9fed9..c621ba7 100644 --- a/examples/test_ll/test_ll.c +++ b/examples/test_ll/test_ll.c @@ -14,7 +14,6 @@ #include "pio_usb_configuration.h" #include "pio_usb_ll.h" #include "usb_definitions.h" -#include pio_usb_configuration_t pio_usb_config = PIO_USB_DEFAULT_CONFIG; diff --git a/src/pio_usb_device.c b/src/pio_usb_device.c index ec456a2..5c24347 100644 --- a/src/pio_usb_device.c +++ b/src/pio_usb_device.c @@ -15,7 +15,8 @@ #include "pio_usb_ll.h" #include "usb_crc.h" -#include +#include "usb_rx.pio.h" +#include "usb_tx.pio.h" #include "hardware/dma.h" #include "hardware/irq.h" @@ -256,7 +257,6 @@ usb_device_t *pio_usb_device_init(const pio_usb_configuration_t *c, pio_usb_bus_init(pp, c, rport); gpio_disable_pulls(rport->pin_dp); // needs external pull-up rport->mode = PIO_USB_MODE_DEVICE; - pp->host = false; memset(dev, 0, sizeof(*dev)); for (int i = 0; i < PIO_USB_DEV_EP_CNT; i++) { diff --git a/src/pio_usb_host.c b/src/pio_usb_host.c index d7ecc0f..71d71a7 100644 --- a/src/pio_usb_host.c +++ b/src/pio_usb_host.c @@ -66,7 +66,6 @@ usb_device_t *pio_usb_host_init(const pio_usb_configuration_t *c) { pio_usb_bus_init(pp, c, root); root->mode = PIO_USB_MODE_HOST; - pp->host = true; float const cpu_freq = (float)clock_get_hz(clk_sys); pio_calculate_clkdiv_from_float(cpu_freq / 48000000, diff --git a/src/pio_usb_ll.h b/src/pio_usb_ll.h index e6fcca2..15621ff 100644 --- a/src/pio_usb_ll.h +++ b/src/pio_usb_ll.h @@ -10,8 +10,8 @@ #include "usb_definitions.h" #include -#include -#include +#include "usb_tx.pio.h" +#include "usb_rx.pio.h" enum { PIO_USB_INTS_CONNECT_POS = 0, @@ -84,7 +84,6 @@ typedef struct { bool need_pre; bool low_speed; - bool host; uint8_t usb_rx_buffer[128]; } pio_port_t;