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