mirror of
https://github.com/espressif/esp-idf.git
synced 2025-10-27 03:55:01 +00:00
TWAI: FIFO overrun handling and errata workarounds
This commit adds handling for FIFO overruns and adds workarounds for HW errats on the ESP32. Closes https://github.com/espressif/esp-idf/issues/2519 Closes https://github.com/espressif/esp-idf/issues/4276
This commit is contained in:
@@ -13,83 +13,173 @@
|
||||
// limitations under the License.
|
||||
|
||||
#include <stddef.h>
|
||||
#include <string.h>
|
||||
#include "sdkconfig.h"
|
||||
#include "hal/twai_hal.h"
|
||||
|
||||
#ifdef CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT
|
||||
//Errata condition occurs at 64 messages. Threshold set to 62 to prevent the chance of failing to detect errata condition.
|
||||
#define TWAI_RX_FIFO_CORRUPT_THRESH 62
|
||||
#endif
|
||||
|
||||
/* ----------------------------- Event Handling ----------------------------- */
|
||||
|
||||
uint32_t twai_hal_decode_interrupt_events(twai_hal_context_t *hal_ctx)
|
||||
/**
|
||||
* Helper functions that can decode what events have been triggered based on
|
||||
* the values of the interrupt, status, TEC and REC registers. The HAL context's
|
||||
* state flags are also updated based on the events that have triggered.
|
||||
*/
|
||||
static inline uint32_t twai_hal_decode_interrupt(twai_hal_context_t *hal_ctx)
|
||||
{
|
||||
uint32_t events = 0;
|
||||
//Read interrupt, status
|
||||
uint32_t interrupts = twai_ll_get_and_clear_intrs(hal_ctx->dev);
|
||||
uint32_t status = twai_ll_get_status(hal_ctx->dev);
|
||||
uint32_t tec = twai_ll_get_tec(hal_ctx->dev);
|
||||
uint32_t rec = twai_ll_get_rec(hal_ctx->dev);
|
||||
uint32_t state_flags = hal_ctx->state_flags;
|
||||
|
||||
//Error Warning Interrupt set whenever Error or Bus Status bit changes
|
||||
if (interrupts & TWAI_LL_INTR_EI) {
|
||||
if (status & TWAI_LL_STATUS_BS) {
|
||||
//Currently in BUS OFF state
|
||||
if (status & TWAI_LL_STATUS_BS) { //Currently in BUS OFF state
|
||||
if (status & TWAI_LL_STATUS_ES) { //EWL is exceeded, thus must have entered BUS OFF
|
||||
twai_ll_set_mode(hal_ctx->dev, TWAI_MODE_LISTEN_ONLY); //Set to listen only to freeze tec and rec
|
||||
events |= TWAI_HAL_EVENT_BUS_OFF;
|
||||
TWAI_HAL_SET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_BUS_OFF);
|
||||
TWAI_HAL_RESET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_RUNNING);
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_BUS_OFF);
|
||||
TWAI_HAL_SET_BITS(state_flags, TWAI_HAL_STATE_FLAG_BUS_OFF);
|
||||
//Any TX would have been halted by entering bus off. Reset its flag
|
||||
TWAI_HAL_RESET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED);
|
||||
TWAI_HAL_CLEAR_BITS(state_flags, TWAI_HAL_STATE_FLAG_RUNNING | TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED);
|
||||
} else {
|
||||
//Below EWL. Therefore TEC is counting down in bus recovery
|
||||
events |= TWAI_HAL_EVENT_BUS_RECOV_PROGRESS;
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_BUS_RECOV_PROGRESS);
|
||||
}
|
||||
} else {
|
||||
//Not in BUS OFF
|
||||
} else { //Not in BUS OFF
|
||||
if (status & TWAI_LL_STATUS_ES) { //Just Exceeded EWL
|
||||
events |= TWAI_HAL_EVENT_ABOVE_EWL;
|
||||
TWAI_HAL_SET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_ERR_WARN);
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_ABOVE_EWL);
|
||||
TWAI_HAL_SET_BITS(state_flags, TWAI_HAL_STATE_FLAG_ERR_WARN);
|
||||
} else if (hal_ctx->state_flags & TWAI_HAL_STATE_FLAG_RECOVERING) {
|
||||
//Previously undergoing bus recovery. Thus means bus recovery complete
|
||||
twai_ll_enter_reset_mode(hal_ctx->dev); //Enter reset mode to stop the peripheral
|
||||
events |= TWAI_HAL_EVENT_BUS_RECOV_CPLT;
|
||||
TWAI_HAL_RESET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_RECOVERING);
|
||||
TWAI_HAL_RESET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_BUS_OFF);
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_BUS_RECOV_CPLT);
|
||||
TWAI_HAL_CLEAR_BITS(state_flags, TWAI_HAL_STATE_FLAG_RECOVERING | TWAI_HAL_STATE_FLAG_BUS_OFF);
|
||||
} else { //Just went below EWL
|
||||
events |= TWAI_HAL_EVENT_BELOW_EWL;
|
||||
TWAI_HAL_RESET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_ERR_WARN);
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_BELOW_EWL);
|
||||
TWAI_HAL_CLEAR_BITS(state_flags, TWAI_HAL_STATE_FLAG_ERR_WARN);
|
||||
}
|
||||
}
|
||||
}
|
||||
//Receive Interrupt set whenever RX FIFO is not empty
|
||||
//Receive Interrupt set whenever RX FIFO is not empty
|
||||
if (interrupts & TWAI_LL_INTR_RI) {
|
||||
events |= TWAI_HAL_EVENT_RX_BUFF_FRAME;
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_RX_BUFF_FRAME);
|
||||
}
|
||||
//Transmit interrupt set whenever TX buffer becomes free
|
||||
#ifdef CONFIG_TWAI_ERRATA_FIX_TX_INTR_LOST
|
||||
if ((interrupts & TWAI_LL_INTR_TI || hal_ctx->state_flags & TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED) && status & TWAI_LL_STATUS_TBS) {
|
||||
#else
|
||||
if (interrupts & TWAI_LL_INTR_TI) {
|
||||
events |= TWAI_HAL_EVENT_TX_BUFF_FREE;
|
||||
TWAI_HAL_RESET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED);
|
||||
#endif
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_TX_BUFF_FREE);
|
||||
TWAI_HAL_CLEAR_BITS(state_flags, TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED);
|
||||
}
|
||||
//Error Passive Interrupt on transition from error active to passive or vice versa
|
||||
if (interrupts & TWAI_LL_INTR_EPI) {
|
||||
if (tec >= TWAI_ERR_PASS_THRESH || rec >= TWAI_ERR_PASS_THRESH) {
|
||||
events |= TWAI_HAL_EVENT_ERROR_PASSIVE;
|
||||
TWAI_HAL_SET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_ERR_PASSIVE);
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_ERROR_PASSIVE);
|
||||
TWAI_HAL_SET_BITS(state_flags, TWAI_HAL_STATE_FLAG_ERR_PASSIVE);
|
||||
} else {
|
||||
events |= TWAI_HAL_EVENT_ERROR_ACTIVE;
|
||||
TWAI_HAL_RESET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_ERR_PASSIVE);
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_ERROR_ACTIVE);
|
||||
TWAI_HAL_CLEAR_BITS(state_flags, TWAI_HAL_STATE_FLAG_ERR_PASSIVE);
|
||||
}
|
||||
}
|
||||
//Bus error interrupt triggered on a bus error (e.g. bit, ACK, stuff etc)
|
||||
if (interrupts & TWAI_LL_INTR_BEI) {
|
||||
twai_ll_clear_err_code_cap(hal_ctx->dev);
|
||||
events |= TWAI_HAL_EVENT_BUS_ERR;
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_BUS_ERR);
|
||||
}
|
||||
//Arbitration Lost Interrupt triggered on losing arbitration
|
||||
if (interrupts & TWAI_LL_INTR_ALI) {
|
||||
twai_ll_clear_arb_lost_cap(hal_ctx->dev);
|
||||
events |= TWAI_HAL_EVENT_ARB_LOST;
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_ARB_LOST);
|
||||
}
|
||||
hal_ctx->state_flags = state_flags;
|
||||
return events;
|
||||
}
|
||||
|
||||
uint32_t twai_hal_get_events(twai_hal_context_t *hal_ctx)
|
||||
{
|
||||
uint32_t events = twai_hal_decode_interrupt(hal_ctx);
|
||||
|
||||
//Handle low latency events
|
||||
if (events & TWAI_HAL_EVENT_BUS_OFF) {
|
||||
twai_ll_set_mode(hal_ctx->dev, TWAI_MODE_LISTEN_ONLY); //Freeze TEC/REC by entering LOM
|
||||
#ifdef CONFIG_TWAI_ERRATA_FIX_BUS_OFF_REC
|
||||
//Errata workaround: Force REC to 0 by re-triggering bus-off (by setting TEC to 0 then 255)
|
||||
twai_ll_set_tec(hal_ctx->dev, 0);
|
||||
twai_ll_set_tec(hal_ctx->dev, 255);
|
||||
(void) twai_ll_get_and_clear_intrs(hal_ctx->dev); //Clear the re-triggered bus-off interrupt
|
||||
#endif
|
||||
}
|
||||
if (events & TWAI_HAL_EVENT_BUS_RECOV_CPLT) {
|
||||
twai_ll_enter_reset_mode(hal_ctx->dev); //Enter reset mode to stop the controller
|
||||
}
|
||||
if (events & TWAI_HAL_EVENT_BUS_ERR) {
|
||||
#ifdef CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID
|
||||
twai_ll_err_type_t type;
|
||||
twai_ll_err_dir_t dir;
|
||||
twai_ll_err_seg_t seg;
|
||||
twai_ll_parse_err_code_cap(hal_ctx->dev, &type, &dir, &seg); //Decode error interrupt
|
||||
//Check for errata condition (RX message has bus error at particular segments)
|
||||
if (dir == TWAI_LL_ERR_DIR_RX &&
|
||||
((seg == TWAI_LL_ERR_SEG_DATA || seg == TWAI_LL_ERR_SEG_CRC_SEQ) ||
|
||||
(seg == TWAI_LL_ERR_SEG_ACK_DELIM && type == TWAI_LL_ERR_OTHER))) {
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_NEED_PERIPH_RESET);
|
||||
}
|
||||
#endif
|
||||
twai_ll_clear_err_code_cap(hal_ctx->dev);
|
||||
}
|
||||
if (events & TWAI_HAL_EVENT_ARB_LOST) {
|
||||
twai_ll_clear_arb_lost_cap(hal_ctx->dev);
|
||||
}
|
||||
#ifdef CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT
|
||||
//Check for errata condition (rx_msg_count >= corruption_threshold)
|
||||
if (events & TWAI_HAL_EVENT_RX_BUFF_FRAME && twai_ll_get_rx_msg_count(hal_ctx->dev) >= TWAI_RX_FIFO_CORRUPT_THRESH) {
|
||||
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_NEED_PERIPH_RESET);
|
||||
}
|
||||
#endif
|
||||
#if defined(CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID) || defined(CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT)
|
||||
if (events & TWAI_HAL_EVENT_NEED_PERIPH_RESET) {
|
||||
//A peripheral reset will invalidate an RX event;
|
||||
TWAI_HAL_CLEAR_BITS(events, (TWAI_HAL_EVENT_RX_BUFF_FRAME));
|
||||
}
|
||||
#endif
|
||||
return events;
|
||||
}
|
||||
|
||||
#if defined(CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID) || defined(CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT)
|
||||
void twai_hal_prepare_for_reset(twai_hal_context_t *hal_ctx)
|
||||
{
|
||||
uint32_t status = twai_ll_get_status(hal_ctx->dev);
|
||||
if (!(status & TWAI_LL_STATUS_TBS)) { //Transmit buffer is NOT free, indicating an Ongoing TX will be cancelled by the HW reset
|
||||
TWAI_HAL_SET_BITS(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_TX_NEED_RETRY);
|
||||
//Note: Even if the TX completes right after this, we still consider it will be retried.
|
||||
//Worst case the same message will get sent twice.
|
||||
}
|
||||
//Some register must saved before entering reset mode
|
||||
hal_ctx->rx_msg_cnt_save = (uint8_t) twai_ll_get_rx_msg_count(hal_ctx->dev);
|
||||
twai_ll_enter_reset_mode(hal_ctx->dev); //Enter reset mode to stop the controller
|
||||
twai_ll_save_reg(hal_ctx->dev, &hal_ctx->reg_save); //Save remaining registers after entering reset mode
|
||||
}
|
||||
|
||||
void twai_hal_recover_from_reset(twai_hal_context_t *hal_ctx)
|
||||
{
|
||||
twai_ll_enter_reset_mode(hal_ctx->dev);
|
||||
twai_ll_enable_extended_reg_layout(hal_ctx->dev);
|
||||
twai_ll_restore_reg(hal_ctx->dev, &hal_ctx->reg_save);
|
||||
twai_ll_exit_reset_mode(hal_ctx->dev);
|
||||
(void) twai_ll_get_and_clear_intrs(hal_ctx->dev);
|
||||
|
||||
if (hal_ctx->state_flags & TWAI_HAL_STATE_FLAG_TX_NEED_RETRY) {
|
||||
//HW reset has cancelled a TX. Re-transmit here
|
||||
twai_hal_set_tx_buffer_and_transmit(hal_ctx, &hal_ctx->tx_frame_save);
|
||||
TWAI_HAL_CLEAR_BITS(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_TX_NEED_RETRY);
|
||||
}
|
||||
}
|
||||
#endif //defined(CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID) || defined(CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT)
|
||||
|
||||
void twai_hal_set_tx_buffer_and_transmit(twai_hal_context_t *hal_ctx, twai_hal_frame_t *tx_frame)
|
||||
{
|
||||
//Copy frame into tx buffer
|
||||
@@ -106,5 +196,9 @@ void twai_hal_set_tx_buffer_and_transmit(twai_hal_context_t *hal_ctx, twai_hal_f
|
||||
} else {
|
||||
twai_ll_set_cmd_tx(hal_ctx->dev);
|
||||
}
|
||||
TWAI_HAL_SET_FLAG(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED);
|
||||
TWAI_HAL_SET_BITS(hal_ctx->state_flags, TWAI_HAL_STATE_FLAG_TX_BUFF_OCCUPIED);
|
||||
#if defined(CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID) || defined(CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT)
|
||||
//Save transmitted frame in case we need to retry
|
||||
memcpy(&hal_ctx->tx_frame_save, tx_frame, sizeof(twai_hal_frame_t));
|
||||
#endif //defined(CONFIG_TWAI_ERRATA_FIX_RX_FRAME_INVALID) || defined(CONFIG_TWAI_ERRATA_FIX_RX_FIFO_CORRUPT)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user