From c94592a11ad2c989e65313d23a8876cf38787d70 Mon Sep 17 00:00:00 2001 From: Michael Balzer Date: Thu, 7 Jan 2021 16:43:11 +0100 Subject: [PATCH] CAN drivers: fix & harmonize frame transmission failure handling Design goals: - any TX can either fail or succeed, the result state is terminal - the respective TX callback is called exactly once - transmissions fail on reaching the error-passive bus state and on message/bus errors while in error-passive state - a failed TX will be aborted (no retries after bus recovery), i.e. will be retried at most 128 times (in error-active phase) - reduce excessive CAN error logging - reduce excessive interrupt load with switched-off buses This results in the application being able to reliably detect a switched-off vehicle bus by the TX callback's success indicator. It also results in frames no longer being held in the TX buffer or added to the TX queue when the bus is switched off. The application can now rely on getting a clean bus state on every reconnect, without any queued old frames to be sent automatically. Secondary benefit from aborting the transmission is, the module doesn't need to handle the load from the continuously triggered CAN error interrupts by retransmission attempts in error-passive state. --- .../components/esp32can/src/esp32can.cpp | 111 +++++++++++---- .../components/esp32can/src/esp32can.h | 1 + .../components/esp32can/src/esp32can_regdef.h | 36 +++-- .../components/mcp2515/src/mcp2515.cpp | 128 ++++++++++++------ .../components/mcp2515/src/mcp2515_regdef.h | 19 +++ 5 files changed, 222 insertions(+), 73 deletions(-) diff --git a/vehicle/OVMS.V3/components/esp32can/src/esp32can.cpp b/vehicle/OVMS.V3/components/esp32can/src/esp32can.cpp index c63eecaea..0889ac588 100644 --- a/vehicle/OVMS.V3/components/esp32can/src/esp32can.cpp +++ b/vehicle/OVMS.V3/components/esp32can/src/esp32can.cpp @@ -158,6 +158,7 @@ static IRAM_ATTR void ESP32CAN_isr(void *pvParameters) esp32can *me = (esp32can*)pvParameters; BaseType_t task_woken = pdFALSE; uint32_t interrupt; + uint32_t error_irqs = 0; ESP32CAN_ENTER_CRITICAL_ISR(); @@ -172,43 +173,100 @@ static IRAM_ATTR void ESP32CAN_isr(void *pvParameters) interrupt |= ESP32CAN_rxframe(me, &task_woken); } - // Handle TX complete interrupt: + // Handle TX interrupt: if ((interrupt & __CAN_IRQ_TX) != 0) { - // Request TxCallback: CAN_queue_msg_t msg; - msg.type = CAN_txcallback; + // The TX interrupt occurs when the TX buffer becomes available, which may be due + // to transmission success or abortion. A real SJA1000 would tell the actual result + // by the SR.3 TCS bit, but the ESP32CAN also sets TCS on aborts. So there is no + // way to tell if the frame was really aborted, we can only rely on our own abort + // request status: + if (me->m_tx_abort) + { + // Clear abort command: + MODULE_ESP32CAN->CMR.B.AT = 0; + me->m_tx_abort = false; + msg.type = CAN_txfailedcallback; + } + else + { + msg.type = CAN_txcallback; + } msg.body.frame = me->m_tx_frame; msg.body.bus = me; xQueueSendFromISR(MyCan.m_rxqueue, &msg, &task_woken); } - // Handle error interrupts: - uint8_t error_irqs = interrupt & - (__CAN_IRQ_ERR //0x4 - |__CAN_IRQ_DATA_OVERRUN //0x8 - |__CAN_IRQ_ERR_PASSIVE //0x20 - |__CAN_IRQ_ARB_LOST //0x40 - |__CAN_IRQ_BUS_ERR //0x80 + // Collect error interrupts: + error_irqs |= interrupt & + (__CAN_IRQ_ERR_WARNING // IR.2 Error Interrupt (warning state change) + |__CAN_IRQ_DATA_OVERRUN // IR.3 Data Overrun Interrupt + |__CAN_IRQ_ERR_PASSIVE // IR.5 Error Passive Interrupt (passive state change) + |__CAN_IRQ_BUS_ERR // IR.7 Bus Error Interrupt ); - if (error_irqs) - { - // Record error data: - me->m_status.error_flags = error_irqs << 16 | (MODULE_ESP32CAN->SR.U & 0b11001111) << 8 | MODULE_ESP32CAN->ECC.B.ECC; - me->m_status.errors_rx = MODULE_ESP32CAN->RXERR.U; - me->m_status.errors_tx = MODULE_ESP32CAN->TXERR.U; - // Request error log: - CAN_queue_msg_t msg; - msg.type = CAN_logerror; - msg.body.bus = me; - xQueueSendFromISR(MyCan.m_rxqueue, &msg, &task_woken); - } // Handle wakeup interrupt: if ((interrupt & __CAN_IRQ_WAKEUP) != 0) { // Todo } + } // while interrupt + + // Get error counters: + uint32_t rxerr = MODULE_ESP32CAN->RXERR.U; + uint32_t txerr = MODULE_ESP32CAN->TXERR.U; + + // Handle error interrupts: + if (error_irqs) + { + uint32_t status = MODULE_ESP32CAN->SR.U; + uint32_t ecc = MODULE_ESP32CAN->ECC.U; + uint32_t error_flags = error_irqs << 16 | (status & 0b11001110) << 8 | (ecc & 0xff); + + // Check for TX failure: + // We consider the TX to have failed if a bus error is detected during the + // transmission attempt and the controller gave up on retrying (= entered + // passive mode, txerr >= 128). + if ((status & (__CAN_STS_TXDONE|__CAN_STS_TXFREE)) == 0 && + (error_irqs & __CAN_IRQ_BUS_ERR) != 0 && + (ecc & __CAN_ECC_DIR) == 0 && + txerr >= 128) + { + // Set abort command: + me->m_tx_abort = true; + MODULE_ESP32CAN->CMR.B.AT = 1; + // Note: another TX IRQ will occur from the abort, see above + } + + // Request error log? + if (me->m_status.error_flags != error_flags || + me->m_status.errors_rx != rxerr || + me->m_status.errors_tx != txerr) + { + me->m_status.error_flags = error_flags; + me->m_status.errors_rx = rxerr; + me->m_status.errors_tx = txerr; + // …only necessary if no abort has been issued: + if (!me->m_tx_abort) + { + CAN_queue_msg_t msg; + if (ecc != 0 || (status & (__CAN_STS_DATA_OVERRUN|__CAN_STS_BUS_OFF)) != 0) + msg.type = CAN_logerror; + else + msg.type = CAN_logstatus; + msg.body.bus = me; + xQueueSendFromISR(MyCan.m_rxqueue, &msg, &task_woken); + } + } + } + else + { + // Update status: + if (rxerr == 0 && txerr == 0) + me->m_status.error_flags = 0; + me->m_status.errors_rx = rxerr; + me->m_status.errors_tx = txerr; } ESP32CAN_EXIT_CRITICAL_ISR(); @@ -240,6 +298,7 @@ esp32can::esp32can(const char* name, int txpin, int rxpin) // on-chip controller, and let housekeeping power us down // after startup. m_powermode = Off; + m_tx_abort = false; MODULE_ESP32CAN->MOD.B.RM = 1; // Launch ISR allocator task on core 0: @@ -284,8 +343,8 @@ void esp32can::InitController() * 0 -> single; the bus is sampled once; recommended for high speed buses (SAE class C)*/ MODULE_ESP32CAN->BTR1.B.SAM=0x1; - // Enable all interrupts - MODULE_ESP32CAN->IER.U = 0xff; + // Enable all interrupts except arbitration loss (can be ignored): + MODULE_ESP32CAN->IER.U = 0xff - __CAN_IRQ_ARB_LOST; // No acceptance filtering, as we want to fetch all messages MODULE_ESP32CAN->MBX_CTRL.ACC.CODE[0] = 0; @@ -313,6 +372,7 @@ void esp32can::InitController() // Clear interrupt flags (void)MODULE_ESP32CAN->IR.U; + m_tx_abort = false; } esp_err_t esp32can::Start(CAN_mode_t mode, CAN_speed_t speed) @@ -402,6 +462,9 @@ esp_err_t esp32can::WriteFrame(const CAN_frame_t* p_frame) ESP32CAN_ENTER_CRITICAL(); uint8_t __byte_i; // Byte iterator + // Clear abort command: + MODULE_ESP32CAN->CMR.B.AT = 0; + // check if TX buffer is available: if (MODULE_ESP32CAN->SR.B.TBS == 0) { diff --git a/vehicle/OVMS.V3/components/esp32can/src/esp32can.h b/vehicle/OVMS.V3/components/esp32can/src/esp32can.h index b3baa6c92..a326aabcb 100644 --- a/vehicle/OVMS.V3/components/esp32can/src/esp32can.h +++ b/vehicle/OVMS.V3/components/esp32can/src/esp32can.h @@ -72,6 +72,7 @@ class esp32can : public canbus gpio_num_t m_txpin; // TX pin gpio_num_t m_rxpin; // RX pin OvmsMutex m_write_mutex; + bool m_tx_abort; }; #endif //#ifndef __ESP32CAN_H__ diff --git a/vehicle/OVMS.V3/components/esp32can/src/esp32can_regdef.h b/vehicle/OVMS.V3/components/esp32can/src/esp32can_regdef.h index 8b21951e4..8edfa4667 100644 --- a/vehicle/OVMS.V3/components/esp32can/src/esp32can_regdef.h +++ b/vehicle/OVMS.V3/components/esp32can/src/esp32can_regdef.h @@ -59,17 +59,35 @@ MODULE_ESP32CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[2] = ((x) >> 5); \ MODULE_ESP32CAN->MBX_CTRL.FCTRL.TX_RX.EXT.ID[3] = ((x) << 3); \ -// Interrupt status register +// Status register (SR) typedef enum { - __CAN_IRQ_RX= BIT(0), // RX Interrupt - __CAN_IRQ_TX= BIT(1), // TX Interrupt - __CAN_IRQ_ERR= BIT(2), // Error Interrupt - __CAN_IRQ_DATA_OVERRUN= BIT(3), // Date Overrun Interrupt - __CAN_IRQ_WAKEUP= BIT(4), // Wakeup Interrupt - __CAN_IRQ_ERR_PASSIVE= BIT(5), // Passive Error Interrupt - __CAN_IRQ_ARB_LOST= BIT(6), // Arbitration lost interrupt - __CAN_IRQ_BUS_ERR= BIT(7), // Bus error Interrupt + __CAN_STS_RXBUF= BIT(0), // SR.0 Receive Buffer Status (1=message(s) in buffer) + __CAN_STS_DATA_OVERRUN= BIT(1), // SR.1 Data Overrun Status (1=message(s) lost) + __CAN_STS_TXFREE= BIT(2), // SR.2 Transmit Buffer Status (1=released) + __CAN_STS_TXDONE= BIT(3), // SR.3 Transmission Complete Status (1=successful) + __CAN_STS_RXPEND= BIT(4), // SR.4 Receive Status (1=receiving a message) + __CAN_STS_TXPEND= BIT(5), // SR.5 Transmit Status (1=transmitting a message) + __CAN_STS_ERR_WARNING= BIT(6), // SR.6 Error Status (1=warning; error count >= 96) + __CAN_STS_BUS_OFF= BIT(7), // SR.7 Bus Status (1=bus-off) + } ESP32CAN_STS_t; + +// Error code capture (ECC) +#define __CAN_ECC_ERRC 0b11000000 +#define __CAN_ECC_DIR 0b00100000 +#define __CAN_ECC_SEGMENT 0b00011111 + +// Interrupt status register (IR) +typedef enum + { + __CAN_IRQ_RX= BIT(0), // IR.0 Receive Interrupt + __CAN_IRQ_TX= BIT(1), // IR.1 Transmit Interrupt + __CAN_IRQ_ERR_WARNING= BIT(2), // IR.2 Error Interrupt (warning state change) + __CAN_IRQ_DATA_OVERRUN= BIT(3), // IR.3 Data Overrun Interrupt + __CAN_IRQ_WAKEUP= BIT(4), // IR.4 Wake-Up Interrupt + __CAN_IRQ_ERR_PASSIVE= BIT(5), // IR.5 Error Passive Interrupt (passive state change) + __CAN_IRQ_ARB_LOST= BIT(6), // IR.6 Arbitration Lost Interrupt + __CAN_IRQ_BUS_ERR= BIT(7), // IR.7 Bus Error Interrupt } ESP32CAN_IRQ_t; /** \brief OCMODE options. */ diff --git a/vehicle/OVMS.V3/components/mcp2515/src/mcp2515.cpp b/vehicle/OVMS.V3/components/mcp2515/src/mcp2515.cpp index eda02c3a1..b15b76af7 100644 --- a/vehicle/OVMS.V3/components/mcp2515/src/mcp2515.cpp +++ b/vehicle/OVMS.V3/components/mcp2515/src/mcp2515.cpp @@ -286,17 +286,19 @@ esp_err_t mcp2515::Stop() esp_err_t mcp2515::ViewRegisters() { - uint8_t buf[16]; + uint8_t buf[20]; uint8_t cnf[3]; // fetch configuration registers - uint8_t * rcvbuf = m_spibus->spi_cmd(m_spi, buf, 8, 2, CMD_READ, REG_CNF3); + uint8_t * rcvbuf = m_spibus->spi_cmd(m_spi, buf, 9, 2, CMD_READ, REG_CNF3); cnf[0] = rcvbuf[2]; cnf[1] = rcvbuf[1]; cnf[2] = rcvbuf[0]; - ESP_LOGI(TAG, "%s: configuration registers: CNF 0x%02x 0x%02x 0x%02x", this->GetName(), - cnf[0], cnf[1], cnf[2]); - ESP_LOGI(TAG, "%s: CANINTE 0x%02x CANINTF 0x%02x EFLG 0x%02x CANSTAT 0x%02x CANCTRL 0x%02x", this->GetName(), - rcvbuf[3], rcvbuf[4], rcvbuf[5], rcvbuf[6], rcvbuf[7]); + ESP_LOGI(TAG, + "%s: configuration registers: CNF 0x%02x 0x%02x 0x%02x", + this->GetName(), cnf[0], cnf[1], cnf[2]); + ESP_LOGI(TAG, + "%s: CANINTE 0x%02x CANINTF 0x%02x EFLG 0x%02x CANSTAT 0x%02x CANCTRL 0x%02x TXB0CTRL 0x%02x", + this->GetName(), rcvbuf[3], rcvbuf[4], rcvbuf[5], rcvbuf[6], rcvbuf[7], rcvbuf[8]); // read error counters rcvbuf = m_spibus->spi_cmd(m_spi, buf, 2, 2, CMD_READ, REG_TEC); uint8_t errors_tx = rcvbuf[0]; @@ -403,11 +405,13 @@ bool mcp2515::AsynchronousInterruptHandler(CAN_frame_t* frame, bool * frameRecei uint8_t buf[16]; *frameReceived = false; + CAN_log_type_t log_status = CAN_LogNone; - // read interrupts (CANINTF 0x2c) and errors (EFLG 0x2d): - uint8_t *p = m_spibus->spi_cmd(m_spi, buf, 2, 2, CMD_READ, REG_CANINTF); + // read interrupts (CANINTF 0x2c), errors (EFLG 0x2d) and transmission status (TXB0CTRL 0x30): + uint8_t *p = m_spibus->spi_cmd(m_spi, buf, 5, 2, CMD_READ, REG_CANINTF); uint8_t intstat = p[0]; uint8_t errflag = p[1]; + uint8_t txb0ctrl = p[4]; if (intstat == 0) { @@ -469,16 +473,17 @@ bool mcp2515::AsynchronousInterruptHandler(CAN_frame_t* frame, bool * frameRecei if (intstat & CANINTF_TX012IF) { - // some TX buffers have become available; clear IRQs and fill up: + // TX buffer(s) have become available; clear IRQs and fill up: m_spibus->spi_cmd(m_spi, buf, 0, 4, CMD_BITMODIFY, REG_CANINTF, intstat & CANINTF_TX012IF, 0); m_status.error_flags |= 0x0100; - // send "tx success" callback request to main CAN processor task + // Note: the TXnIF bits only get set on successful transmission (see TX flowchart) + // Queue "tx success" callback: CAN_queue_msg_t msg; msg.type = CAN_txcallback; msg.body.frame = m_tx_frame; msg.body.bus = this; - xQueueSend(MyCan.m_rxqueue,&msg,0); + xQueueSend(MyCan.m_rxqueue, &msg, 0); } if (intstat & (CANINTF_MERRF | CANINTF_WAKIF | CANINTF_ERRIF)) @@ -488,11 +493,13 @@ bool mcp2515::AsynchronousInterruptHandler(CAN_frame_t* frame, bool * frameRecei // WAKIF = wakeup // ERRIF = overflow / error state change (details in errflag) + // Check for RX overflow: if (errflag & EFLG_RX1OVR) // RXB1 overflow { m_status.rxbuf_overflow++; m_status.error_flags |= 0x0200; - ESP_LOGW(TAG, "CAN Bus 2/3 receive overflow; Frame lost."); + //ESP_LOGW(TAG, "CAN Bus 2/3 receive overflow; Frame lost."); + log_status = CAN_LogStatus_Error; } if (errflag & EFLG_RX0OVR) // RXB0 overflow. No data lost in this case (it went into RXB1) { @@ -500,27 +507,61 @@ bool mcp2515::AsynchronousInterruptHandler(CAN_frame_t* frame, bool * frameRecei } // Read error counters: - uint8_t *p = m_spibus->spi_cmd(m_spi, buf, 2, 2, CMD_READ, REG_TEC); - uint8_t txerr = p[0]; - uint8_t rxerr = p[1]; + p = m_spibus->spi_cmd(m_spi, buf, 2, 2, CMD_READ, REG_TEC); + m_status.errors_tx = p[0]; + m_status.errors_rx = p[1]; // Check for TX failure: - if ((intstat & CANINTF_MERRF) && (txerr > m_status.errors_tx)) + // We consider the TX to have failed if a bus error is detected during the + // transmission attempt and the controller gave up on retrying (= entered + // passive mode, txerr >= 128). + if ((intstat & CANINTF_MERRF) != 0 && + (txb0ctrl & TXBCTRL_TXREQ) != 0 && + m_status.errors_tx >= 128) { - ESP_LOGE(TAG, "AsynchronousInterruptHandler: error while sending frame. msgId 0x%x", m_tx_frame.MsgID); - // send "tx failed" callback request to main CAN processor task + //ESP_LOGE(TAG, "AsynchronousInterruptHandler: error while sending frame. msgId 0x%x", m_tx_frame.MsgID); + + // Abort TX to cancel further retransmission attempts: + // … set ABAT, poll for TXREQ to become clear, clear ABAT: + m_spibus->spi_cmd(m_spi, buf, 0, 4, CMD_BITMODIFY, REG_CANCTRL, CANCTRL_ABAT, CANCTRL_ABAT); + do + p = m_spibus->spi_cmd(m_spi, buf, 1, 1, CMD_READ_STATUS); + while (p[0] & STATUS_TX012REQ); + m_spibus->spi_cmd(m_spi, buf, 0, 4, CMD_BITMODIFY, REG_CANCTRL, CANCTRL_ABAT, 0); + + // … get TXERR & ABTF flags: + p = m_spibus->spi_cmd(m_spi, buf, 1, 2, CMD_READ, REG_TXB0CTRL); + bool tx_aborted = ((p[0] & (TXBCTRL_ABTF | TXBCTRL_TXERR)) != 0); + + // … and clear TX IRQs in case the abort request came too late: + m_spibus->spi_cmd(m_spi, buf, 0, 4, CMD_BITMODIFY, REG_CANINTF, CANINTF_TX012IF, 0); + + // Queue TX callback: CAN_queue_msg_t msg; - msg.type = CAN_txfailedcallback; + msg.type = tx_aborted ? CAN_txfailedcallback : CAN_txcallback; msg.body.frame = m_tx_frame; msg.body.bus = this; - xQueueSend(MyCan.m_rxqueue,&msg,0); + xQueueSend(MyCan.m_rxqueue, &msg, 0); + // …which will log the error as well + } + else + { + // Request log entry for other errors: + log_status = CAN_LogStatus_Error; + } + } + else + { + // No error interrupt signaled, but we want to follow the error recovery: + if (m_status.errors_tx || m_status.errors_rx) + { + // Read error counters: + p = m_spibus->spi_cmd(m_spi, buf, 2, 2, CMD_READ, REG_TEC); + m_status.errors_tx = p[0]; + m_status.errors_rx = p[1]; + if (!m_status.errors_tx && !m_status.errors_rx) + m_status.error_flags = 0; } - - m_status.errors_tx = txerr; - m_status.errors_rx = rxerr; - - // log: - LogStatus(CAN_LogStatus_Error); } // clear RX buffer overflow flags: @@ -530,22 +571,24 @@ bool mcp2515::AsynchronousInterruptHandler(CAN_frame_t* frame, bool * frameRecei m_spibus->spi_cmd(m_spi, buf, 0, 4, CMD_BITMODIFY, REG_EFLG, errflag & EFLG_RX01OVR, 0); } - // log bus error state change: - if (intstat & CANINTF_ERRIF) + // Log bus error state change: + uint8_t log_errflag = errflag & ~EFLG_RX01OVR; + if (log_errflag && log_errflag != m_last_errflag) { - uint8_t log_errflag = errflag & ~EFLG_RX01OVR; - if (log_errflag && log_errflag != m_last_errflag) - { - ESP_LOGW(TAG, "%s EFLG: %s%s%s%s%s%s", this->GetName(), - (errflag & EFLG_TXBO) ? "Bus-off " : "", - (errflag & EFLG_TXEP) ? "TX_Err_Passv " : "", - (errflag & EFLG_RXEP) ? "RX_Err_Passv " : "", - (errflag & EFLG_TXWAR) ? "TX_Err_Warn " : "", - (errflag & EFLG_RXWAR) ? "RX_Err_Warn " : "", - (errflag & EFLG_EWARN) ? "EWARN " : "" ); - } - m_last_errflag = log_errflag; + ESP_LOGW(TAG, "%s EFLG: %s%s%s%s%s%s", this->GetName(), + (errflag & EFLG_TXBO) ? "Bus-off " : "", + (errflag & EFLG_TXEP) ? "TX_Err_Passv " : "", + (errflag & EFLG_RXEP) ? "RX_Err_Passv " : "", + (errflag & EFLG_TXWAR) ? "TX_Err_Warn " : "", + (errflag & EFLG_RXWAR) ? "RX_Err_Warn " : "", + (errflag & EFLG_EWARN) ? "EWARN " : "" ); } + if (log_errflag == 0 && (m_last_errflag & ~EFLG_RX01OVR) != 0) + { + // Recovered: + log_status = CAN_LogStatus_Statistics; + } + m_last_errflag = log_errflag; // clear error & wakeup interrupts: if (intstat & (CANINTF_MERRF | CANINTF_WAKIF | CANINTF_ERRIF)) @@ -555,6 +598,11 @@ bool mcp2515::AsynchronousInterruptHandler(CAN_frame_t* frame, bool * frameRecei intstat & (CANINTF_MERRF | CANINTF_WAKIF | CANINTF_ERRIF), 0); } + if (log_status != CAN_LogNone) + { + LogStatus(log_status); + } + // Read the interrupt pin status and if it's still active (low), require another interrupt handling iteration return !gpio_get_level((gpio_num_t)m_intpin); } diff --git a/vehicle/OVMS.V3/components/mcp2515/src/mcp2515_regdef.h b/vehicle/OVMS.V3/components/mcp2515/src/mcp2515_regdef.h index 6e4990b76..72ec980b1 100644 --- a/vehicle/OVMS.V3/components/mcp2515/src/mcp2515_regdef.h +++ b/vehicle/OVMS.V3/components/mcp2515/src/mcp2515_regdef.h @@ -76,6 +76,25 @@ #define EFLG_RXWAR 0b00000010 // Receive Error Warning (REC >= 96) #define EFLG_EWARN 0b00000001 // Error Warning (TXWAR or RXWAR set) +// TXBnCTRL (Transmission Buffer Control) register flags +#define TXBCTRL_ABTF 0b01000000 // Message Aborted +#define TXBCTRL_MLOA 0b00100000 // Message Lost Arbitration +#define TXBCTRL_TXERR 0b00010000 // Transmission Error (bus error) +#define TXBCTRL_TXREQ 0b00001000 // Message Transmit Request (TX pending) + +// CMD_READ_STATUS flags +#define STATUS_TX2IF 0b10000000 // CANINTF.TX2IF +#define STATUS_TX2REQ 0b01000000 // TXB2CNTRL.TXREQ +#define STATUS_TX1IF 0b00100000 // CANINTF.TX1IF +#define STATUS_TX1REQ 0b00010000 // TXB1CNTRL.TXREQ +#define STATUS_TX0IF 0b00001000 // CANINTF.TX0IF +#define STATUS_TX0REQ 0b00000100 // TXB0CNTRL.TXREQ +#define STATUS_RX1IF 0b00000010 // CANINTF.RX1IF +#define STATUS_RX0IF 0b00000001 // CANINTF.RX0IF +#define STATUS_TX012IF 0b10101000 // Mask: any/all TXnIF +#define STATUS_TX012REQ 0b01010100 // Mask: any/all TXnREQ +#define STATUS_RX01IF 0b00000011 // Mask: any/all RXnIF + // Register addresses #define REG_CANSTAT 0x0E #define REG_CANCTRL 0x0F