Merge pull request #164 from tannewt/better_timeouts

Use microsecond for better timeout tracking
This commit is contained in:
sekigon-gonnoc 2025-04-04 19:50:15 +09:00 committed by GitHub
commit dd18302231
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 152 additions and 100 deletions

View file

@ -14,7 +14,6 @@
#include "pio_usb_configuration.h"
#include "pio_usb_ll.h"
#include "usb_definitions.h"
#include "usb_rx.pio.h"
pio_usb_configuration_t pio_usb_config = PIO_USB_DEFAULT_CONFIG;

View file

@ -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);
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,87 +146,105 @@ 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 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 Low speed.
uint32_t start = get_time_us_32();
uint32_t timeout = pp->low_speed ? 12 : 3;
while (get_time_us_32() - start <= timeout) {
if ((pp->pio_usb_rx->irq & IRQ_RX_START_MASK) != 0) {
return true;
}
}
return false;
};
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;
if (!pio_usb_bus_wait_for_rx_start(pp)) {
return 0;
}
while (t--) {
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;
pp->usb_rx_buffer[idx++] = data;
start = get_time_us_32(); // reset timeout when a byte is received
if (idx == 2) {
break;
}
} 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 (t > 0) {
while ((pp->pio_usb_rx->irq & IRQ_RX_COMP_MASK) == 0) {
continue;
}
if (idx != 2) {
return 0;
}
// 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) {
if (!pio_usb_bus_wait_for_rx_start(pp)) {
return -1;
}
uint16_t crc = 0xffff;
uint16_t crc_prev = 0xffff;
uint16_t crc_prev2 = 0xffff;
uint16_t crc_receive = 0xffff;
uint16_t crc_receive_inverse;
uint16_t crc_receive_inverse = 0;
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]);
int16_t idx = 0;
while (t--) {
// 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;
pp->usb_rx_buffer[idx++] = data;
if (idx == 2) {
break;
if (idx < rx_buf_len) {
pp->usb_rx_buffer[idx] = data;
}
}
}
start = get_time_us_32(); // reset timeout when a byte is received
// 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 >= 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);
}
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);
break;
}
}
@ -244,8 +269,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 +454,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 {

View file

@ -200,10 +200,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;

View file

@ -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
@ -152,6 +150,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 +168,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 +193,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 +211,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 = get_time_us_32();
while (get_time_us_32() == start) {
tight_loop_contents();
}
}

View file

@ -10,6 +10,9 @@
#include "usb_definitions.h"
#include <stdint.h>
#include "usb_tx.pio.h"
#include "usb_rx.pio.h"
enum {
PIO_USB_INTS_CONNECT_POS = 0,
PIO_USB_INTS_DISCONNECT_POS,
@ -80,6 +83,7 @@ typedef struct {
pio_clk_div_t clk_div_ls_rx;
bool need_pre;
bool low_speed;
uint8_t usb_rx_buffer[128];
} pio_port_t;
@ -112,6 +116,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 +129,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 +147,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
//--------------------------------------------------------------------+

View file

@ -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;
}
}
#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)

View file

@ -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
@ -41,13 +41,13 @@ 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]
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
@ -75,13 +75,13 @@ 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
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)

View file

@ -33,13 +33,13 @@ 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 [1]
0x0040, // 11: jmp x--, 0
0xa0c3, // 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
@ -76,13 +76,13 @@ 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
0x4001, // 13: in pins, 1 side 0
0xc004, // 14: irq nowait 4 side 0
0xb1c3, // 12: mov isr, null side 1 [1]
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
@ -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);

View file

@ -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]

View file

@ -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]