diff --git a/lib/README b/lib/README index a5c3042..afcaf30 100644 --- a/lib/README +++ b/lib/README @@ -122,3 +122,7 @@ has been modfied to enable the US code page (437) and enable label support. The file "diskio.c" in the original archive is not included as it is only serves as an example for how to implement the media api callbacks. + +The can2040 directory contains code from: + https://github.com/KevinOConnor/can2040 +revision 177b0073fe6f19281ee7f7fdbe9599e32d1b4b8b. diff --git a/lib/can2040/can2040.c b/lib/can2040/can2040.c index 5fa9e11..fb9e3fb 100644 --- a/lib/can2040/can2040.c +++ b/lib/can2040/can2040.c @@ -116,6 +116,13 @@ static const uint16_t can2040_program_instructions[] = { 0xc027, // 31: irq wait 7 }; +// Local names for PIO state machine IRQs +#define SI_MAYTX PIO_IRQ0_INTE_SM0_BITS +#define SI_MATCHED PIO_IRQ0_INTE_SM2_BITS +#define SI_ACKDONE PIO_IRQ0_INTE_SM3_BITS +#define SI_RX_DATA PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS +#define SI_TXPENDING PIO_IRQ0_INTE_SM1_BITS // Misc bit manually forced + // Setup PIO "sync" state machine (state machine 0) static void pio_sync_setup(struct can2040 *cd) @@ -213,14 +220,6 @@ pio_rx_check_stall(struct can2040 *cd) return pio_hw->fdebug & (1 << (PIO_FDEBUG_RXSTALL_LSB + 1)); } -// Report number of bytes still pending in PIO "rx" fifo queue -static int -pio_rx_fifo_level(struct can2040 *cd) -{ - pio_hw_t *pio_hw = cd->pio_hw; - return (pio_hw->flevel & PIO_FLEVEL_RX1_BITS) >> PIO_FLEVEL_RX1_LSB; -} - // Set PIO "match" state machine to raise a "matched" signal on a bit sequence static void pio_match_check(struct can2040 *cd, uint32_t match_key) @@ -250,7 +249,7 @@ pio_tx_reset(struct can2040 *cd) pio_hw_t *pio_hw = cd->pio_hw; pio_hw->ctrl = ((0x07 << PIO_CTRL_SM_ENABLE_LSB) | (0x08 << PIO_CTRL_SM_RESTART_LSB)); - pio_hw->irq = (1 << 2) | (1<< 3); // clear "matched" and "ack done" signals + pio_hw->irq = (SI_MATCHED | SI_ACKDONE) >> 8; // clear PIO irq flags // Clear tx fifo struct pio_sm_hw *sm = &pio_hw->sm[3]; sm->shiftctrl = 0; @@ -295,54 +294,49 @@ pio_tx_inject_ack(struct can2040 *cd, uint32_t match_key) pio_match_check(cd, match_key); } -// Check if the PIO "tx" state machine stopped due to passive/dominant conflict +// Did PIO "tx" state machine unexpectedly finish a transmit attempt? static int -pio_tx_did_conflict(struct can2040 *cd) +pio_tx_did_fail(struct can2040 *cd) { pio_hw_t *pio_hw = cd->pio_hw; - return pio_hw->sm[3].addr == can2040_offset_tx_conflict; + // Check for passive/dominant bit conflict without parser noticing + if (pio_hw->sm[3].addr == can2040_offset_tx_conflict) + return !(pio_hw->intr & SI_RX_DATA); + // Check for unexpected drain of transmit queue without parser noticing + return (!(pio_hw->flevel & PIO_FLEVEL_TX3_BITS) + && (pio_hw->intr & (SI_MAYTX | SI_RX_DATA)) == SI_MAYTX); } -// Enable host irq on a "may transmit" signal (sm irq 0) +// Enable host irqs for state machine signals static void -pio_irq_set_maytx(struct can2040 *cd) +pio_irq_set(struct can2040 *cd, uint32_t sm_irqs) { pio_hw_t *pio_hw = cd->pio_hw; - pio_hw->inte0 = PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS; + pio_hw->inte0 = sm_irqs | SI_RX_DATA; } -// Enable host irq on a "may transmit" or "matched" signal (sm irq 0 or 2) -static void -pio_irq_set_maytx_matched(struct can2040 *cd) +// Return current host irq mask +static uint32_t +pio_irq_get(struct can2040 *cd) { pio_hw_t *pio_hw = cd->pio_hw; - pio_hw->inte0 = (PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM2_BITS - | PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS); + return pio_hw->inte0; } -// Enable host irq on a "may transmit" or "ack done" signal (sm irq 0 or 3) +// Raise the txpending flag static void -pio_irq_set_maytx_ackdone(struct can2040 *cd) +pio_signal_set_txpending(struct can2040 *cd) { pio_hw_t *pio_hw = cd->pio_hw; - pio_hw->inte0 = (PIO_IRQ0_INTE_SM0_BITS | PIO_IRQ0_INTE_SM3_BITS - | PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS); + pio_hw->irq_force = SI_TXPENDING >> 8; } -// Atomically enable "may transmit" signal (sm irq 0) +// Clear the txpending flag static void -pio_irq_atomic_set_maytx(struct can2040 *cd) +pio_signal_clear_txpending(struct can2040 *cd) { pio_hw_t *pio_hw = cd->pio_hw; - hw_set_bits(&pio_hw->inte0, PIO_IRQ0_INTE_SM0_BITS); -} - -// Disable PIO host irqs (except for normal data read irq) -static void -pio_irq_set_none(struct can2040 *cd) -{ - pio_hw_t *pio_hw = cd->pio_hw; - pio_hw->inte0 = PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS; + pio_hw->irq = SI_TXPENDING >> 8; } // Setup PIO state machines @@ -353,6 +347,8 @@ pio_sm_setup(struct can2040 *cd) pio_hw_t *pio_hw = cd->pio_hw; pio_hw->ctrl = PIO_CTRL_SM_RESTART_BITS | PIO_CTRL_CLKDIV_RESTART_BITS; pio_hw->fdebug = 0xffffffff; + pio_hw->irq = 0xff; + pio_signal_set_txpending(cd); // Load pio program int i; @@ -478,6 +474,14 @@ unstuf_clear_state(struct can2040_bitunstuffer *bu) bu->stuffed_bits = (bu->stuffed_bits & (lb - 1)) | (lb << 1); } +// Restore raw bitstuffing state (used to undo unstuf_clear_state() ) +static void +unstuf_restore_state(struct can2040_bitunstuffer *bu, uint32_t data) +{ + uint32_t cs = bu->count_stuff; + bu->stuffed_bits = (bu->stuffed_bits & ((1 << cs) - 1)) | (data << cs); +} + // Pull bits from unstuffer (as specified in unstuf_set_count() ) static int unstuf_pull_bits(struct can2040_bitunstuffer *bu) @@ -504,10 +508,10 @@ unstuf_pull_bits(struct can2040_bitunstuffer *bu) } bu->count_stuff = cs = cs - 1; if (rm_bits & (1 << (cs + 1))) { - // High bit of try_cnt a stuff bit + // High bit is a stuff bit if (unlikely(rm_bits & (1 << cs))) { // Six consecutive bits - a bitstuff error - if ((sb >> cs) & 1) + if (sb & (1 << cs)) return -1; return -2; } @@ -524,6 +528,13 @@ unstuf_pull_bits(struct can2040_bitunstuffer *bu) } } +// Return most recent raw (still stuffed) bits +static uint32_t +unstuf_get_raw(struct can2040_bitunstuffer *bu) +{ + return bu->stuffed_bits >> bu->count_stuff; +} + /**************************************************************** * Bit stuffing @@ -630,33 +641,30 @@ tx_qpos(struct can2040 *cd, uint32_t pos) } // Queue the next message for transmission in the PIO -static void +static uint32_t tx_schedule_transmit(struct can2040 *cd) { - if (cd->tx_state == TS_QUEUED && !pio_tx_did_conflict(cd)) + if (cd->tx_state == TS_QUEUED && !pio_tx_did_fail(cd)) // Already queued or actively transmitting - return; + return 0; if (cd->tx_push_pos == cd->tx_pull_pos) { // No new messages to transmit cd->tx_state = TS_IDLE; - return; + pio_signal_clear_txpending(cd); + return SI_TXPENDING; } cd->tx_state = TS_QUEUED; struct can2040_transmit *qt = &cd->tx_queue[tx_qpos(cd, cd->tx_pull_pos)]; pio_tx_send(cd, qt->stuffed_data, qt->stuffed_words); + return 0; } // Setup PIO state for ack injection -static int +static void tx_inject_ack(struct can2040 *cd, uint32_t match_key) { - if (cd->tx_state == TS_QUEUED && !pio_tx_did_conflict(cd) - && pio_rx_fifo_level(cd) > 1) - // Rx state is behind - acking wont succeed and may halt active tx - return -1; cd->tx_state = TS_ACKING_RX; pio_tx_inject_ack(cd, match_key); - return 0; } // Check if the current parsed message is feedback from current transmit @@ -683,7 +691,11 @@ tx_check_local_message(struct can2040 *cd) // Report state flags (stored in cd->report_state) enum { - RS_IDLE = 0, RS_IS_TX = 1, RS_IN_MSG = 2, RS_AWAIT_EOF = 4, + RS_NEED_EOF_FLAG = 1<<2, + // States + RS_IDLE = 0, RS_NEED_RX_ACK = 1, RS_NEED_TX_ACK = 2, + RS_NEED_RX_EOF = RS_NEED_RX_ACK | RS_NEED_EOF_FLAG, + RS_NEED_TX_EOF = RS_NEED_TX_ACK | RS_NEED_EOF_FLAG, }; // Report error to calling code (via callback interface) @@ -713,13 +725,10 @@ report_callback_tx_msg(struct can2040 *cd) static void report_handle_eof(struct can2040 *cd) { - if (cd->report_state == RS_IDLE) - // Message already reported or an unexpected EOF - return; - if (cd->report_state & RS_AWAIT_EOF) { + if (cd->report_state & RS_NEED_EOF_FLAG) { // RS_NEED_xX_EOF // Successfully processed a new message - report to calling code pio_sync_normal_start_signal(cd); - if (cd->report_state & RS_IS_TX) + if (cd->report_state == RS_NEED_TX_EOF) report_callback_tx_msg(cd); else report_callback_rx_msg(cd); @@ -728,76 +737,68 @@ report_handle_eof(struct can2040 *cd) pio_match_clear(cd); } -// Check if in an rx ack is pending +// Check if in an rx message is being processed static int -report_is_acking_rx(struct can2040 *cd) +report_is_rx_eof_pending(struct can2040 *cd) { - return cd->report_state == (RS_IN_MSG | RS_AWAIT_EOF); + return cd->report_state == RS_NEED_RX_EOF; } // Parser found a new message start static void report_note_message_start(struct can2040 *cd) { - pio_irq_set_maytx(cd); + pio_irq_set(cd, SI_MAYTX); } // Setup for ack injection (if receiving) or ack confirmation (if transmit) static void report_note_crc_start(struct can2040 *cd) { - uint32_t cs = cd->unstuf.count_stuff; - uint32_t crcstart_bitpos = cd->raw_bit_count - cs - 1; - uint32_t last = ((cd->unstuf.stuffed_bits >> cs) << 15) | cd->parse_crc; - uint32_t crc_bitcount = bitstuff(&last, 15 + 1) - 1; - uint32_t crcend_bitpos = crcstart_bitpos + crc_bitcount; - int ret = tx_check_local_message(cd); if (ret) { // This is a self transmit - setup tx eof "matched" signal - cd->report_state = RS_IN_MSG | RS_IS_TX; - last = (last << 10) | 0x02ff; - pio_match_check(cd, pio_match_calc_key(last, crcend_bitpos + 10)); + cd->report_state = RS_NEED_TX_ACK; + uint32_t bits = (cd->parse_crc_bits << 9) | 0x0ff; + pio_match_check(cd, pio_match_calc_key(bits, cd->parse_crc_pos + 9)); return; } - // Inject ack - cd->report_state = RS_IN_MSG; - last = (last << 1) | 0x01; - ret = tx_inject_ack(cd, pio_match_calc_key(last, crcend_bitpos + 1)); - if (ret) - // Ack couldn't be scheduled (due to lagged parsing state) - return; - pio_irq_set_maytx_ackdone(cd); - // Setup for future rx eof "matched" signal - last = (last << 8) | 0x7f; - cd->report_eof_key = pio_match_calc_key(last, crcend_bitpos + 9); + // Setup for ack inject (after rx fifos fully drained) + cd->report_state = RS_NEED_RX_ACK; + pio_signal_set_txpending(cd); + pio_irq_set(cd, SI_MAYTX | SI_TXPENDING); } // Parser successfully found matching crc static void report_note_crc_success(struct can2040 *cd) { - if (cd->report_state == (RS_IN_MSG | RS_IS_TX)) + if (cd->report_state == RS_NEED_TX_ACK) // Enable "matched" irq for fast back-to-back transmit scheduling - pio_irq_set_maytx_matched(cd); + pio_irq_set(cd, SI_MAYTX | SI_MATCHED); } // Parser found successful ack static void report_note_ack_success(struct can2040 *cd) { - if (!(cd->report_state & RS_IN_MSG)) - // Got rx "ackdone" and "matched" signals already + if (cd->report_state == RS_IDLE) + // Got "matched" signal already return; - cd->report_state |= RS_AWAIT_EOF; + // Transition RS_NEED_xX_ACK to RS_NEED_xX_EOF + cd->report_state |= RS_NEED_EOF_FLAG; } // Parser found successful EOF static void report_note_eof_success(struct can2040 *cd) { + if (cd->report_state == RS_IDLE) + // Got "matched" signal already + return; report_handle_eof(cd); + pio_irq_set(cd, SI_TXPENDING); } // Parser found unexpected data on input @@ -809,35 +810,34 @@ report_note_parse_error(struct can2040 *cd) pio_match_clear(cd); } pio_sync_slow_start_signal(cd); - pio_irq_set_maytx(cd); + pio_irq_set(cd, SI_MAYTX | SI_TXPENDING); } // Received PIO rx "ackdone" irq static void report_line_ackdone(struct can2040 *cd) { - if (!(cd->report_state & RS_IN_MSG)) { - // Parser already processed ack and eof bits - pio_irq_set_maytx(cd); - return; - } // Setup "matched" irq for fast rx callbacks - pio_match_check(cd, cd->report_eof_key); - pio_irq_set_maytx_matched(cd); + uint32_t bits = (cd->parse_crc_bits << 8) | 0x7f; + pio_match_check(cd, pio_match_calc_key(bits, cd->parse_crc_pos + 8)); // Schedule next transmit (so it is ready for next frame line arbitration) - tx_schedule_transmit(cd); + uint32_t check_txpending = tx_schedule_transmit(cd); + pio_irq_set(cd, SI_MAYTX | SI_MATCHED | check_txpending); } // Received PIO "matched" irq static void report_line_matched(struct can2040 *cd) { - // Implement fast rx callback and/or fast back-to-back tx scheduling - if (cd->report_state & RS_IN_MSG) - cd->report_state |= RS_AWAIT_EOF; - report_handle_eof(cd); - pio_irq_set_none(cd); - tx_schedule_transmit(cd); + // A match event indicates an ack and eof are present + if (cd->report_state != RS_IDLE) { + // Transition RS_NEED_xX_ACK to RS_NEED_xX_EOF (if not already there) + cd->report_state |= RS_NEED_EOF_FLAG; + report_handle_eof(cd); + } + // Implement fast back-to-back tx scheduling (if applicable) + uint32_t check_txpending = tx_schedule_transmit(cd); + pio_irq_set(cd, check_txpending); } // Received 10+ passive bits on the line (between 10 and 17 bits) @@ -845,10 +845,28 @@ static void report_line_maytx(struct can2040 *cd) { // Line is idle - may be unexpected EOF, missed ack injection, - // missed "matched" signal, or can2040_transmit() kick. - report_handle_eof(cd); - pio_irq_set_none(cd); - tx_schedule_transmit(cd); + // or missed "matched" signal. + if (cd->report_state != RS_IDLE) + report_handle_eof(cd); + uint32_t check_txpending = tx_schedule_transmit(cd); + pio_irq_set(cd, check_txpending); +} + +// Schedule a transmit +static void +report_line_txpending(struct can2040 *cd) +{ + if (cd->report_state == RS_NEED_RX_ACK) { + // Ack inject request from report_note_crc_start() + uint32_t mk = pio_match_calc_key(cd->parse_crc_bits, cd->parse_crc_pos); + tx_inject_ack(cd, mk); + pio_irq_set(cd, SI_MAYTX | SI_ACKDONE); + return; + } + // Tx request from can2040_transmit(), report_note_eof_success(), + // or report_note_parse_error(). + uint32_t check_txpending = tx_schedule_transmit(cd); + pio_irq_set(cd, (pio_irq_get(cd) & ~SI_TXPENDING) | check_txpending); } @@ -897,18 +915,19 @@ data_state_line_error(struct can2040 *cd) static void data_state_line_passive(struct can2040 *cd) { - if (cd->parse_state != MS_DISCARD) { + if (cd->parse_state != MS_DISCARD && cd->parse_state != MS_START) { // Bitstuff error data_state_go_discard(cd); return; } - uint32_t stuffed_bits = cd->unstuf.stuffed_bits >> cd->unstuf.count_stuff; + uint32_t stuffed_bits = unstuf_get_raw(&cd->unstuf); uint32_t dom_bits = ~stuffed_bits; if (!dom_bits) { // Counter overflow in "sync" state machine - reset it - pio_sync_setup(cd); cd->unstuf.stuffed_bits = 0; + cd->raw_bit_count = cd->unstuf.count_stuff = 0; + pio_sm_setup(cd); data_state_go_discard(cd); return; } @@ -927,6 +946,14 @@ static void data_state_go_crc(struct can2040 *cd) { cd->parse_crc &= 0x7fff; + + // Calculate raw stuffed bits after crc and crc delimiter + uint32_t crcstart_bitpos = cd->raw_bit_count - cd->unstuf.count_stuff - 1; + uint32_t crc_bits = (unstuf_get_raw(&cd->unstuf) << 15) | cd->parse_crc; + uint32_t crc_bitcount = bitstuff(&crc_bits, 15 + 1) - 1; + cd->parse_crc_bits = (crc_bits << 1) | 0x01; // Add crc delimiter + cd->parse_crc_pos = crcstart_bitpos + crc_bitcount + 1; + report_note_crc_start(cd); data_state_go_next(cd, MS_CRC, 16); } @@ -1032,6 +1059,10 @@ static void data_state_update_ack(struct can2040 *cd, uint32_t data) { if (data != 0x01) { + // Undo unstuf_clear_state() for correct SOF detection in + // data_state_line_passive() + unstuf_restore_state(&cd->unstuf, (cd->parse_crc_bits << 2) | data); + data_state_go_discard(cd); return; } @@ -1055,7 +1086,7 @@ data_state_update_eof0(struct can2040 *cd, uint32_t data) static void data_state_update_eof1(struct can2040 *cd, uint32_t data) { - if (data >= 0x1c || (data >= 0x18 && report_is_acking_rx(cd))) + if (data >= 0x1c || (data >= 0x18 && report_is_rx_eof_pending(cd))) // Message is considered fully transmitted report_note_eof_success(cd); @@ -1095,7 +1126,7 @@ data_state_update(struct can2040 *cd, uint32_t data) * Input processing ****************************************************************/ -// Process an incoming byte of data from PIO "rx" state machine +// Process incoming data from PIO "rx" state machine static void process_rx(struct can2040 *cd, uint32_t rx_data) { @@ -1128,7 +1159,7 @@ can2040_pio_irq_handler(struct can2040 *cd) { pio_hw_t *pio_hw = cd->pio_hw; uint32_t ints = pio_hw->ints0; - while (likely(ints & PIO_IRQ0_INTE_SM1_RXNEMPTY_BITS)) { + while (likely(ints & SI_RX_DATA)) { uint32_t rx_data = pio_hw->rxf[1]; process_rx(cd, rx_data); ints = pio_hw->ints0; @@ -1136,15 +1167,18 @@ can2040_pio_irq_handler(struct can2040 *cd) return; } - if (ints & PIO_IRQ0_INTE_SM3_BITS) + if (ints & SI_ACKDONE) // Ack of received message completed successfully report_line_ackdone(cd); - else if (ints & PIO_IRQ0_INTE_SM2_BITS) + else if (ints & SI_MATCHED) // Transmit message completed successfully report_line_matched(cd); - else if (ints & PIO_IRQ0_INTE_SM0_BITS) + else if (ints & SI_MAYTX) // Bus is idle, but not all bits may have been flushed yet report_line_maytx(cd); + else if (ints & SI_TXPENDING) + // Schedule a transmit + report_line_txpending(cd); } @@ -1222,7 +1256,7 @@ can2040_transmit(struct can2040 *cd, struct can2040_msg *msg) writel(&cd->tx_push_pos, tx_push_pos + 1); // Wakeup if in TS_IDLE state - pio_irq_atomic_set_maytx(cd); + pio_signal_set_txpending(cd); return 0; } diff --git a/lib/can2040/can2040.h b/lib/can2040/can2040.h index 26a49eb..fc0bdd6 100644 --- a/lib/can2040/can2040.h +++ b/lib/can2040/can2040.h @@ -63,12 +63,11 @@ struct can2040 { // Input data state uint32_t parse_state; - uint32_t parse_crc; + uint32_t parse_crc, parse_crc_bits, parse_crc_pos; struct can2040_msg parse_msg; // Reporting uint32_t report_state; - uint32_t report_eof_key; // Transmits uint32_t tx_state;