feat(driver_twai): new driver add programming guide

This commit is contained in:
wanckl
2025-05-09 20:22:02 +08:00
parent a952037d82
commit 77277b59fc
25 changed files with 1009 additions and 1339 deletions

View File

@ -247,10 +247,13 @@ static void twai_intr_handler_main(void *arg)
//Handle events that only require alerting (i.e. no handler)
if (events & TWAI_HAL_EVENT_BUS_OFF) {
twai_ll_set_mode(p_twai_obj->hal->dev, true, false, false); //Freeze TEC/REC by entering LOM
p_twai_obj->state = TWAI_STATE_BUS_OFF;
twai_alert_handler(p_twai_obj, TWAI_ALERT_BUS_OFF, &alert_req);
}
if (events & TWAI_HAL_EVENT_BUS_RECOV_CPLT) {
//Back to STOPPED state after recovered, for cautious engineering strategy
twai_ll_enter_reset_mode(p_twai_obj->hal->dev);
p_twai_obj->state = TWAI_STATE_STOPPED;
twai_alert_handler(p_twai_obj, TWAI_ALERT_BUS_RECOVERED, &alert_req);
}

View File

@ -234,6 +234,15 @@ static void _node_isr_main(void *arg)
if (twai_ctx->cbs.on_state_change) {
do_yield |= twai_ctx->cbs.on_state_change(&twai_ctx->api_base, &e_data, twai_ctx->user_data);
}
// node recover from busoff, restart remain tx transaction
if ((e_data.old_sta == TWAI_ERROR_BUS_OFF) && (e_data.new_sta == TWAI_ERROR_ACTIVE)) {
if (xQueueReceiveFromISR(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, &do_yield)) {
atomic_store(&twai_ctx->hw_busy, true);
_node_start_trans(twai_ctx);
} else {
atomic_store(&twai_ctx->hw_busy, false);
}
}
}
// deal RX event, then TX later, TODO: DIG-620
@ -263,9 +272,9 @@ static void _node_isr_main(void *arg)
// deal TX event
if (events & TWAI_HAL_EVENT_TX_BUFF_FREE) {
// only call tx_done_cb when tx without error, otherwise on_error_cb should triggered if it is registered
if (twai_ctx->cbs.on_tx_done && (events & TWAI_HAL_EVENT_TX_SUCCESS)) {
if (twai_ctx->cbs.on_tx_done) {
twai_tx_done_event_data_t tx_ev = {
.is_tx_success = (events & TWAI_HAL_EVENT_TX_SUCCESS), // find 'on_error_cb' if not success
.done_tx_frame = twai_ctx->p_curr_tx,
};
do_yield |= twai_ctx->cbs.on_tx_done(&twai_ctx->api_base, &tx_ev, twai_ctx->user_data);
@ -482,6 +491,9 @@ static esp_err_t _node_config_mask_filter(twai_node_handle_t node, uint8_t filte
twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base);
ESP_RETURN_ON_FALSE(filter_id < SOC_TWAI_MASK_FILTER_NUM, ESP_ERR_INVALID_ARG, TAG, "Invalid mask filter id %d", filter_id);
ESP_RETURN_ON_FALSE(mask_cfg->num_of_ids <= 1, ESP_ERR_INVALID_ARG, TAG, "Invalid num_of_ids");
uint32_t id = mask_cfg->num_of_ids ? mask_cfg->id_list[0] : mask_cfg->id;
bool full_close = (mask_cfg->mask == UINT32_MAX) && (id == UINT32_MAX);
ESP_RETURN_ON_FALSE(full_close || mask_cfg->dual_filter || mask_cfg->is_ext || !((mask_cfg->mask | id) & ~TWAI_STD_ID_MASK), ESP_ERR_INVALID_ARG, TAG, "std_id only (is_ext=0) but valid id/mask larger than 11 bits");
#if SOC_TWAI_SUPPORT_FD
// FD targets don't support Dual filter
ESP_RETURN_ON_FALSE(!mask_cfg->dual_filter, ESP_ERR_NOT_SUPPORTED, TAG, "The target don't support Dual Filter");
@ -497,6 +509,8 @@ static esp_err_t _node_config_range_filter(twai_node_handle_t node, uint8_t filt
{
twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base);
ESP_RETURN_ON_FALSE(filter_id < SOC_TWAI_RANGE_FILTER_NUM, ESP_ERR_INVALID_ARG, TAG, "Invalid range filter id %d", filter_id);
ESP_RETURN_ON_FALSE((range_cfg->range_low > range_cfg->range_high) || range_cfg->is_ext || !(range_cfg->range_high & ~TWAI_STD_ID_MASK), \
ESP_ERR_INVALID_ARG, TAG, "std_id only (is_ext=0) but valid low/high id larger than 11 bits");
ESP_RETURN_ON_FALSE(atomic_load(&twai_ctx->state) == TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "config filter must when node stopped");
twai_hal_configure_range_filter(twai_ctx->hal, filter_id, range_cfg);

View File

@ -13,6 +13,46 @@
extern "C" {
#endif
/**
* @brief TWAI on-chip node initialization configuration structure
*/
typedef struct {
struct {
gpio_num_t tx; /**< GPIO pin for twai TX */
gpio_num_t rx; /**< GPIO pin for twai RX */
gpio_num_t quanta_clk_out; /**< GPIO pin for quanta clock output, Set -1 to not use */
gpio_num_t bus_off_indicator; /**< GPIO pin for bus-off indicator, Set -1 to not use */
} io_cfg; /**< I/O configuration */
twai_clock_source_t clk_src; /**< Optional, clock source, remain 0 to using TWAI_CLK_SRC_DEFAULT by default */
twai_timing_basic_config_t bit_timing; /**< Timing configuration for classic twai and FD arbitration stage */
twai_timing_basic_config_t data_timing; /**< Optional, timing configuration for FD data stage */
int8_t fail_retry_cnt; /**< Hardware retry limit if failed, range [-1:15], -1 for re-trans forever */
uint32_t tx_queue_depth; /**< Depth of the transmit queue */
int intr_priority; /**< Interrupt priority, [0:3] */
struct {
uint32_t enable_self_test: 1; /**< Transmission does not require acknowledgment. Use this mode for self testing */
uint32_t enable_loopback: 1; /**< The TWAI controller receive back frames what it send out */
uint32_t enable_listen_only: 1; /**< The TWAI controller will not influence the bus (No transmissions or acknowledgments) but can receive messages */
uint32_t no_receive_rtr: 1; /**< Don't receive remote frames */
} flags; /**< Misc configuration flags */
} twai_onchip_node_config_t;
/**
* @brief Allocate a TWAI hardware node by specific init config structure
* To delete/free the TWAI, call `twai_node_delete()`
*
* @param[in] node_config Init config structure
* @param[out] node_ret Return driver handle
*
* @return ESP_OK Allocate success
* ESP_ERR_NO_MEM No enough free memory
* ESP_ERR_NOT_FOUND No free hardware controller
* ESP_ERR_INVALID_ARG Config argument not available
* ESP_ERR_INVALID_STATE State error, including hardware state error and driver state error
* ESP_FAIL Other reasons
*/
esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twai_node_handle_t *node_ret);
/**
* @brief Helper function to configure a dual 16-bit acceptance filter.
* @note For 29bits Extended IDs, ONLY high 16bits id/mask is used for eache filter.
@ -51,46 +91,6 @@ static inline twai_mask_filter_config_t twai_make_dual_filter(uint16_t id1, uint
return dual_cfg;
}
/**
* @brief TWAI on-chip node initialization configuration structure
*/
typedef struct {
struct {
gpio_num_t tx; /**< GPIO pin for twai TX */
gpio_num_t rx; /**< GPIO pin for twai RX */
gpio_num_t quanta_clk_out; /**< GPIO pin for quanta clock output, Set -1 to not use */
gpio_num_t bus_off_indicator; /**< GPIO pin for bus-off indicator, Set -1 to not use */
} io_cfg; /**< I/O configuration */
twai_clock_source_t clk_src; /**< Optional, clock source, remain 0 to using TWAI_CLK_SRC_DEFAULT by default */
twai_timing_basic_config_t bit_timing; /**< Timing configuration for classic twai and FD arbitration stage */
twai_timing_basic_config_t data_timing; /**< Optional, timing configuration for FD data stage */
int8_t fail_retry_cnt; /**< Hardware retry limit if failed, range [-1:15], -1 for re-trans forever */
uint32_t tx_queue_depth; /**< Depth of the transmit queue */
int intr_priority; /**< Interrupt priority, [0:3] */
struct {
uint32_t enable_self_test: 1; /**< Transmission does not require acknowledgment. Use this mode for self testing */
uint32_t enable_loopback: 1; /**< The TWAI controller receive back frames what it send out */
uint32_t enable_listen_only: 1; /**< The TWAI controller will not influence the bus (No transmissions or acknowledgments) but can receive messages */
uint32_t no_receive_rtr: 1; /**< Don't receive remote frames */
} flags;
} twai_onchip_node_config_t;
/**
* @brief Allocate a TWAI hardware node by specific init config structure
* To delete/free the TWAI, call `twai_node_delete()`
*
* @param[in] node_config Init config structure
* @param[out] node_ret Return driver handle
*
* @return ESP_OK Allocate success
* ESP_ERR_NO_MEM No enough free memory
* ESP_ERR_NOT_FOUND No free hardware controller
* ESP_ERR_INVALID_ARG Config argument not available
* ESP_ERR_INVALID_STATE State error, including hardware state error and driver state error
* ESP_FAIL Other reasons
*/
esp_err_t twai_new_node_onchip(const twai_onchip_node_config_t *node_config, twai_node_handle_t *node_ret);
#ifdef __cplusplus
}
#endif

View File

@ -29,7 +29,7 @@ typedef struct {
/**
* @brief TWAI transaction frame param type
*/
typedef struct twai_frame_t {
typedef struct {
twai_frame_header_t header; /**< message attribute/metadata, exclude data buffer*/
uint8_t *buffer; /**< buffer address for tx and rx message data*/
size_t buffer_len; /**< buffer length of provided data buffer pointer, in bytes.*/
@ -57,6 +57,7 @@ typedef struct {
* @brief TWAI "TX done" event data
*/
typedef struct {
bool is_tx_success; /**< Indicate if frame send successful, refer `on_error` callback for fail reason if send failed */
const twai_frame_t *done_tx_frame; /**< Pointer to the frame that has been transmitted */
} twai_tx_done_event_data_t;

View File

@ -17,8 +17,8 @@ extern "C" {
#endif
/* valid bits in TWAI ID for frame formats */
#define TWAI_STD_ID_MASK 0x000007FFU /* Mask of the ID fields in a standard frame */
#define TWAI_EXT_ID_MASK 0x1FFFFFFFU /* Mask of the ID fields in an extended frame */
#define TWAI_STD_ID_MASK 0x000007FFU /**< Mask of the ID fields in a standard frame */
#define TWAI_EXT_ID_MASK 0x1FFFFFFFU /**< Mask of the ID fields in an extended frame */
/* TWAI payload length and DLC definitions */
#define TWAI_FRAME_MAX_DLC 8
@ -32,10 +32,10 @@ extern "C" {
* @brief TWAI node error fsm states
*/
typedef enum {
TWAI_ERROR_ACTIVE, /**< Error active state: TEC/REC < 96 */
TWAI_ERROR_WARNING, /**< Error warning state: TEC/REC >= 96 and < 128 */
TWAI_ERROR_PASSIVE, /**< Error passive state: TEC/REC >= 128 and < 256 */
TWAI_ERROR_BUS_OFF, /**< Bus-off state: TEC >= 256 (node offline) */
TWAI_ERROR_ACTIVE, /**< Error active state: TEC/REC < 96 */
TWAI_ERROR_WARNING, /**< Error warning state: TEC/REC >= 96 and < 128 */
TWAI_ERROR_PASSIVE, /**< Error passive state: TEC/REC >= 128 and < 256 */
TWAI_ERROR_BUS_OFF, /**< Bus-off state: TEC >= 256 (node offline) */
} twai_error_state_t;
/**

View File

@ -216,8 +216,8 @@ static inline uint32_t twai_hal_decode_interrupt(twai_hal_context_t *hal_ctx)
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_ERROR_WARNING);
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_HAL_SET_BITS(events, TWAI_HAL_EVENT_BUS_RECOV_CPLT);
//Previously undergoing bus recovery. Thus means bus recovery complete, hardware back to error_active
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_BUS_RECOV_CPLT | TWAI_HAL_EVENT_ERROR_ACTIVE);
TWAI_HAL_CLEAR_BITS(state_flags, TWAI_HAL_STATE_FLAG_RECOVERING | TWAI_HAL_STATE_FLAG_BUS_OFF);
} else { //Just went below EWL
TWAI_HAL_SET_BITS(events, TWAI_HAL_EVENT_BELOW_EWL);
@ -269,7 +269,6 @@ uint32_t twai_hal_get_events(twai_hal_context_t *hal_ctx)
//Handle low latency events
if (events & TWAI_HAL_EVENT_BUS_OFF) {
twai_ll_set_mode(hal_ctx->dev, true, false, false); //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);
@ -277,9 +276,6 @@ uint32_t twai_hal_get_events(twai_hal_context_t *hal_ctx)
(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) {
twai_ll_err_type_t type;
twai_ll_err_dir_t dir;

View File

@ -1,19 +0,0 @@
#Diagram of Acceptance Filter (Dual) Configuration
packetdiag acceptance_filter_dual {
node_width = 30
node_height = 35
default_fontsize = 15
colwidth = 32
0-3: "F1 DB1[0:3]" [color = lightyellow];
4: "F2\nRTR" [color = lightyellow];
5-15: "F2 11-bit ID" [color = lightyellow];
16-19: "F1 DB1[4:7]" [color = lightyellow];
20: "F1\nRTR" [color = lightyellow];
21-31: "F1 11-bit ID" [color = lightyellow];
32-47: "F2 29-bit ID [13:28]" [color = lightblue];
48-63: "F1 29-bit ID [13:28]" [color = lightblue];
}

View File

@ -1,21 +0,0 @@
#Diagram of Acceptance Filter (Single) Configuration
packetdiag acceptance_filter_single {
node_width = 30
node_height = 35
default_fontsize = 15
colwidth = 32
#Single Filter Standard Frame Format
0-7: Data Byte 2 [color = lightyellow];
8-15: Data Byte 1 [color = lightyellow];
16-19: Unsed [color = lightgrey];
20: RTR [color = lightyellow];
21-31: 11 bit ID [color = lightyellow];
#Single Filter Extended Frame Format
32-33: Unused [color = lightgrey];
34: RTR [color = lightblue];
35-63: 29 bit ID [color = lightblue];
}

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 21 KiB

View File

@ -1,12 +0,0 @@
#Example of bit timing configuration for 500KBPS
packetdiag bit_timing_diag{
node_width = 40
node_height = 35
default_fontsize = 15
colwidth = 20
0: "Sync" [color = lightgrey];
1-14: "Tseg1 = 15" [color = lightblue];
15-19: "Tseg2 = 4" [color = lightyellow];
}

View File

@ -0,0 +1,65 @@
<svg width="600" height="240" xmlns="http://www.w3.org/2000/svg" font-family="monospace" font-size="12" text-anchor="middle">
<style>
.wave { stroke: black; stroke-width: 2; fill: none; }
</style>
<!-- 主波形 -->
<text x="100" y="55">TX/RX</text>
<polyline class="wave" points="90,30 190,30"/>
<polyline class="wave" points="190,30 190,60 220,60"/>
<polyline class="wave" points="220,60 220,30 250,30"/>
<polyline class="wave" points="250,30 250,60 280,60"/>
<polyline class="wave" points="280,60 280,30 310,30"/>
<polyline class="wave" points="310,30 310,60 340,60"/>
<polyline class="wave" points="340,60 370,60"/>
<polyline class="wave" points="370,60 370,30 400,30"/>
<polyline class="wave" points="400,30 430,30"/>
<polyline class="wave" points="430,30 430,60 460,60"/>
<polyline class="wave" points="460,60 460,30 580,30"/>
<line x1="280" y1="60" x2="80" y2="110" stroke="black" stroke-dasharray="5,5"/>
<line x1="310" y1="60" x2="590" y2="110" stroke="black" stroke-dasharray="5,5"/>
<!-- 放大视图 -->
<rect x="80" y="110" width="500" height="30" fill="#f0f0f0" stroke="black"/>
<text x="330" y="130">one bit in time quanta</text>
<rect x="80" y="140" width="60" height="30" fill="#cccccc" stroke="black"/>
<text x="110" y="160">sync_seg</text>
<rect x="140" y="140" width="146.67" height="30" fill="#8ecae6" stroke="black"/>
<text x="213" y="160">prop_seg</text>
<rect x="286.67" y="140" width="146.67" height="30" fill="#219ebc" stroke="black"/>
<text x="360" y="160" fill="white">tseg1</text>
<rect x="433.34" y="140" width="146.67" height="30" fill="#023047" stroke="black"/>
<text x="506" y="160" fill="white">tseg2</text>
<!-- 虚线框 -->
<rect x="220" y="180" width="60" height="30" fill="none" stroke="black" stroke-dasharray="5,5" />
<text x="250" y="200">sync_seg</text>
<!-- 虚线向右延伸 -->
<line x1="280" y1="180" x2="530" y2="180" stroke="black" stroke-dasharray="5,5"/>
<line x1="280" y1="210" x2="530" y2="210" stroke="black" stroke-dasharray="5,5"/>
<!-- sjw 横向箭头 -->
<line x1="80" y1="195" x2="220" y2="195" stroke="black" marker-start="url(#arrow-left)" marker-end="url(#arrow-right)" stroke-width="2"/>
<text x="150" y="188" fill="black">sjw</text>
<line x1="433" y1="225" x2="433" y2="180" stroke="black" marker-end="url(#arrow-up)" stroke-width="2"/>
<text x="433" y="235">sample_point</text>
<defs>
<marker id="arrow-up" markerWidth="6" markerHeight="6" refX="5" refY="5" orient="0" markerUnits="strokeWidth">
<path d="M5,0 L10,10 L0,10 Z" fill="black"/>
</marker>
<marker id="arrow-right" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L6,3 L0,6 Z" fill="black"/>
</marker>
<marker id="arrow-left" markerWidth="6" markerHeight="6" refX="1" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M6,0 L0,3 L6,6 Z" fill="black"/>
</marker>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

View File

@ -1,57 +0,0 @@
#Diagram of TWAI controller signal lines
blockdiag controller_signals_diagram {
orientation = portrait;
span_width = 80;
#Column 1 nodes
twai[label = "TWAI Controller", fontsize = 15, shape = roundedbox];
#Column 2 nodes
tx[label = "TX", shape = endpoint];
rx[label = "RX", shape = endpoint];
bus_off[label = "BUS-OFF", shape = endpoint];
clkout[label = "CLKOUT", shape = endpoint];
#Column 3 nodes
hide1 [shape = none];
hide2 [shape = none];
hide3 [shape = none];
hide4 [shape = none];
group {
orientation = portrait;
color = none;
#Group column 1 nodes vertically
twai;
}
group {
orientation = portrait;
color = none;
#Group column 2 nodes vertically
tx; rx; bus_off; clkout;
}
group {
orientation = portrait;
color = none;
label = "GPIO Matrix";
fontsize = 20;
shape = line;
#Group column 3 nodes vertically
hide1; hide2; hide3; hide4;
}
twai -> tx [folded];
twai -> rx [folded, dir = none];
twai -> bus_off [folded];
twai -> clkout [folded];
tx -> hide1 [folded];
rx <- hide2 [folded];
bus_off -> hide3 [folded, label = "Optional"];
clkout -> hide4 [folded, label = "Optional"];
}

View File

@ -0,0 +1,82 @@
<svg width="980" height="160" xmlns="http://www.w3.org/2000/svg">
<style>
.segment { stroke: #333; stroke-width: 1; }
.label { font-family: sans-serif; font-size: 12px; text-anchor: middle; dominant-baseline: central; }
.title { font-family: sans-serif; font-size: 14px; font-weight: bold; }
.arrow { stroke: #333; marker-start: url(#startArrow); marker-end: url(#endArrow); }
</style>
<!-- Arrowhead definitions -->
<defs>
<marker id="startArrow" markerWidth="10" markerHeight="7" refX="3" refY="3.5" orient="auto">
<polygon points="6 0, 0 3.5, 6 7" fill="#333" />
</marker>
<marker id="endArrow" markerWidth="10" markerHeight="7" refX="0" refY="3.5" orient="auto">
<polygon points="0 0, 6 3.5, 0 7" fill="#333" />
</marker>
</defs>
<!-- Header arrow (ID to DLC) -->
<line x1="60" y1="30" x2="450" y2="30" class="arrow" stroke-width="1.5"/>
<text x="260" y="22" class="label">Header</text>
<!-- Data arrow (Data field only) -->
<line x1="462" y1="30" x2="690" y2="30" class="arrow" stroke-width="1.5"/>
<text x="580" y="22" class="label">Data</text>
<!-- TX line -->
<text x="8" y="60" class="label">TX</text>
<!-- SOF -->
<rect x="20" y="40" width="40" height="40" fill="#ffcccc" class="segment"/>
<text x="40" y="60" class="label">SOF</text>
<!-- ID -->
<rect x="60" y="40" width="160" height="40" fill="#ffe599" class="segment"/>
<text x="140" y="60" class="label">ID (11/29)</text>
<!-- RTR -->
<rect x="220" y="40" width="40" height="40" fill="#c9daf8" class="segment"/>
<text x="240" y="60" class="label">RTR</text>
<!-- IDE -->
<rect x="260" y="40" width="40" height="40" fill="#d9ead3" class="segment"/>
<text x="280" y="60" class="label">IDE</text>
<!-- FDF -->
<rect x="300" y="40" width="40" height="40" fill="#d0e0e3" class="segment"/>
<text x="320" y="60" class="label">FDF</text>
<!-- BRS -->
<rect x="340" y="40" width="40" height="40" fill="#ead1dc" class="segment"/>
<text x="360" y="60" class="label">BRS</text>
<!-- ESI -->
<rect x="380" y="40" width="40" height="40" fill="#f4cccc" class="segment"/>
<text x="400" y="60" class="label">ESI</text>
<!-- DLC -->
<rect x="420" y="40" width="40" height="40" fill="#d9d2e9" class="segment"/>
<text x="440" y="60" class="label">DLC</text>
<!-- Data -->
<rect x="460" y="40" width="240" height="40" fill="#cfe2f3" class="segment"/>
<text x="580" y="60" class="label">Data (0~64 bytes)</text>
<!-- CRC -->
<rect x="700" y="40" width="120" height="40" fill="#d9d2e9" class="segment"/>
<text x="760" y="60" class="label">CRC</text>
<!-- EOF -->
<rect x="900" y="40" width="60" height="40" fill="#fff2cc" class="segment"/>
<text x="930" y="60" class="label">EOF</text>
<!-- TX base line -->
<line x1="20" y1="80" x2="960" y2="80" stroke="#999" stroke-width="2"/>
<!-- RX line -->
<text x="8" y="120" class="label">RX</text>
<rect x="820" y="100" width="80" height="40" fill="#b6d7a8" class="segment"/>
<text x="860" y="120" class="label">ACK</text>
<line x1="20" y1="140" x2="960" y2="140" stroke="#999" stroke-width="2" stroke-dasharray="5,5"/>
</svg>

After

Width:  |  Height:  |  Size: 3.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 59 KiB

View File

@ -0,0 +1,45 @@
<svg width="480" height="180" viewBox="40 40 480 140" xmlns="http://www.w3.org/2000/svg">
<!-- ESP32 芯片框 -->
<rect x="40" y="40" width="120" height="140" fill="#e0f7fa" stroke="#000"/>
<text x="100" y="110" text-anchor="middle" dominant-baseline="middle" font-weight="bold" font-size="14">ESP32 Series</text>
<!-- CAN收发器 芯片框 -->
<rect x="340" y="40" width="120" height="140" fill="#ffe0b2" stroke="#000"/>
<text x="400" y="110" text-anchor="middle" dominant-baseline="middle" font-weight="bold" font-size="14">Transceiver</text>
<!-- TX -->
<line x1="160" y1="70" x2="340" y2="70" stroke="black" marker-end="url(#arrow)"/>
<text x="250" y="65" text-anchor="middle" font-size="12" font-weight="bold">TX</text>
<!-- RX -->
<line x1="340" y1="100" x2="160" y2="100" stroke="black" marker-end="url(#arrow)"/>
<text x="250" y="95" text-anchor="middle" font-size="12" font-weight="bold">RX</text>
<!-- BUSOFF (可选) -->
<line x1="160" y1="130" x2="340" y2="130" stroke="gray" marker-end="url(#arrow)" stroke-dasharray="5,3"/>
<text x="250" y="125" text-anchor="middle" font-size="12">BUS_OFF (Optional)</text>
<!-- CLOCK_OUT (可选) -->
<line x1="160" y1="160" x2="340" y2="160" stroke="gray" marker-end="url(#arrow)" stroke-dasharray="5,3"/>
<text x="250" y="155" text-anchor="middle" font-size="12">CLK_OUT (Optional)</text>
<!-- CANH -->
<line x1="460" y1="80" x2="500" y2="80" stroke="#00695c" stroke-width="2"/>
<text x="505" y="80" font-size="12" fill="#00695c" dominant-baseline="middle">H</text>
<!-- CANL -->
<line x1="460" y1="120" x2="500" y2="120" stroke="#00695c" stroke-width="2"/>
<text x="505" y="120" font-size="12" fill="#00695c" dominant-baseline="middle">L</text>
<!-- “to bus” 注释 -->
<text x="475" y="150" font-size="12" fill="#555">To BUS</text>
<!-- 箭头定义 -->
<defs>
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="3" orient="auto" markerUnits="strokeWidth">
<path d="M0,0 L0,6 L9,3 z" fill="black"/>
</marker>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.0 KiB

View File

@ -1,30 +0,0 @@
#State transition diagram of the TWAI Driver
blockdiag state_transition_diagram {
orientation = landscape;
default_fontsize = 18;
node_width = 180;
node_height = 40;
span_width = 100;
span_height = 40;
#First Row
bus_off [label = "Bus-Off"];
recovering [label = "Recovering"];
#Second Row
uninstalled [label = "Uninstalled"];
stopped [label = "Stopped"];
running [label = "Running"];
app_start[label = "Entry", shape = beginpoint];
bus_off -> uninstalled [folded, thick, fontsize = 14, label = "F"];
bus_off -> recovering [thick, fontsize = 14, label = "G"];
recovering -> stopped [folded, thick, color = blue, fontsize = 14, label = "H"];
uninstalled <-> stopped [thick, fontsize = 14, label = "A/B"];
stopped <-> running [thick, fontsize = 14, label = "C/D"];
running -> bus_off [folded, thick, color = red, fontsize = 14, label = "E"];
app_start -> uninstalled [folded, style = dashed]
}

View File

@ -20,7 +20,6 @@ api-reference/storage/mass_mfg.rst
api-reference/storage/fatfsgen.rst
api-reference/storage/index.rst
api-reference/storage/nvs_partition_parse.rst
api-reference/peripherals/twai.rst
api-reference/peripherals/touch_pad.rst
api-reference/peripherals/index.rst
api-reference/peripherals/ecdsa.rst

View File

@ -74,7 +74,6 @@ INPUT = \
$(PROJECT_PATH)/components/bt/host/bluedroid/api/include/api/esp_spp_api.h \
$(PROJECT_PATH)/components/bt/host/nimble/esp-hci/include/esp_nimble_hci.h \
$(PROJECT_PATH)/components/console/esp_console.h \
$(PROJECT_PATH)/components/driver/twai/include/driver/twai.h \
$(PROJECT_PATH)/components/driver/test_apps/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_sdio.h \
$(PROJECT_PATH)/components/driver/test_apps/components/esp_serial_slave_link/include/esp_serial_slave_link/essl_spi.h \
$(PROJECT_PATH)/components/driver/test_apps/components/esp_serial_slave_link/include/esp_serial_slave_link/essl.h \
@ -89,6 +88,9 @@ INPUT = \
$(PROJECT_PATH)/components/esp_common/include/esp_check.h \
$(PROJECT_PATH)/components/esp_common/include/esp_err.h \
$(PROJECT_PATH)/components/esp_common/include/esp_idf_version.h \
$(PROJECT_PATH)/components/esp_driver_twai/include/esp_twai.h \
$(PROJECT_PATH)/components/esp_driver_twai/include/esp_twai_types.h \
$(PROJECT_PATH)/components/esp_driver_twai/include/esp_twai_onchip.h \
$(PROJECT_PATH)/components/esp_driver_ana_cmpr/include/driver/ana_cmpr.h \
$(PROJECT_PATH)/components/esp_driver_ana_cmpr/include/driver/ana_cmpr_etm.h \
$(PROJECT_PATH)/components/esp_driver_ana_cmpr/include/driver/ana_cmpr_types.h \
@ -267,7 +269,7 @@ INPUT = \
$(PROJECT_PATH)/components/hal/include/hal/spi_types.h \
$(PROJECT_PATH)/components/hal/include/hal/temperature_sensor_types.h \
$(PROJECT_PATH)/components/hal/include/hal/timer_types.h \
$(PROJECT_PATH)/components/hal/include/hal/twai_types_deprecated.h \
$(PROJECT_PATH)/components/hal/include/hal/twai_types.h \
$(PROJECT_PATH)/components/hal/include/hal/uart_types.h \
$(PROJECT_PATH)/components/hal/include/hal/efuse_hal.h \
$(PROJECT_PATH)/components/hal/include/hal/eth_types.h \

View File

@ -82,6 +82,7 @@ Here are the other configuration parameters of the :cpp:type:`gptimer_config_t`
- :cpp:member:`gptimer_config_t::resolution_hz` sets the resolution of the internal counter. Each tick is equivalent to **1 / resolution_hz** seconds.
- :cpp:member:`gptimer_config_t::intr_priority` sets the interrupt priority. If set to ``0``, a default priority interrupt will be allocated; otherwise, the specified priority will be used.
- :cpp:member:`gptimer_config_t::flags` is used to fine-tune some behaviors of the driver, including the following options:
- :cpp:member:`gptimer_config_t::flags::allow_pd` configures whether the driver allows the system to power down the peripheral in sleep mode. Before entering sleep, the system will back up the GPTimer register context, which will be restored when the system wakes up. Note that powering down the peripheral can save power but will consume more memory to save the register context. You need to balance power consumption and memory usage. This configuration option depends on specific hardware features. If enabled on an unsupported chip, you will see an error message like ``not able to power down in light sleep``.
.. note::

View File

@ -3,617 +3,374 @@ Two-Wire Automotive Interface (TWAI)
:link_to_translation:`zh_CN:[中文]`
This programming guide is split into the following sections:
This document introduces the features of the Two-Wire Automotive Interface (TWAI) controller driver in ESP-IDF. The chapter structure is as follows:
.. contents::
:local:
:depth: 1
.. -------------------------------- Overview -----------------------------------
:local:
:depth: 2
Overview
--------
The Two-Wire Automotive Interface (TWAI) is a real-time serial communication protocol suited for automotive and industrial applications. It is compatible with ISO11898-1 Classical frames, thus can support Standard Frame Format (11-bit ID) and Extended Frame Format (29-bit ID). The {IDF_TARGET_NAME} contains {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} TWAI controller(s) that can be configured to communicate on a TWAI bus via an external transceiver.
TWAI is a highly reliable, multi-master, real-time, serial asynchronous communication protocol designed for automotive and industrial applications. It is compatible with the frame structure defined in the ISO 11898-1 standard and supports both standard frames with 11-bit identifiers and extended frames with 29-bit identifiers. The protocol supports message prioritization with lossless arbitration, automatic retransmission, and fault confinement mechanisms. The {IDF_TARGET_NAME} includes {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} TWAI controllers, allowing for the creation of {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} driver instances.
.. warning::
.. only:: SOC_TWAI_SUPPORT_FD
The TWAI controller is not compatible with ISO11898-1 FD Format frames, and will interpret such frames as errors.
The TWAI controllers on the {IDF_TARGET_NAME} also compatible with FD format frames defined in ISO 11898-1, and can transmit and receive both classic and FD format frames.
.. --------------------------- Basic TWAI Concepts -----------------------------
.. only:: not SOC_TWAI_SUPPORT_FD
TWAI Protocol Summary
---------------------
The TWAI controllers on the {IDF_TARGET_NAME} are **not compatible with FD format frames and will interpret such frames as errors.**
The TWAI is a multi-master, multi-cast, asynchronous, serial communication protocol. TWAI also supports error detection and signalling, and inbuilt message prioritization.
Thanks to its hardware-based fault tolerance and multi-master architecture, the TWAI driver is ideal for scenarios such as:
**Multi-master:** Any node on the bus can initiate the transfer of a message.
- Serving as a robust communication bus in environments with significant electrical noise
- Enabling long-distance communication across multiple sensors/actuators with resilience to single-node failures
- Building decentralized distributed local networks that avoid the unpredictability of single-master designs
- Acting as a bridging node alongside other communication protocols
**Multi-cast:** When a node transmits a message, all nodes on the bus will receive the message (i.e., broadcast) thus ensuring data consistency across all nodes. However, some nodes can selectively choose which messages to accept via the use of acceptance filtering (multi-cast).
Getting Started
---------------
**Asynchronous:** The bus does not contain a clock signal. All nodes on the bus operate at the same bit rate and synchronize using the edges of the bits transmitted on the bus.
This section provides a quick overview of how to use the TWAI driver. Through simple examples, it demonstrates how to create a TWAI node instance, transmit and receive messages on the bus, and safely stop and uninstall the driver. The general usage flow is as follows:
**Error Detection and Signaling:** Every node constantly monitors the bus. When any node detects an error, it signals the detection by transmitting an error frame. Other nodes will receive the error frame and transmit their own error frames in response. This results in an error detection being propagated to all nodes on the bus.
.. image:: ../../../_static/diagrams/twai/base_flow.drawio.svg
:align: center
**Message Priorities:** Messages contain an ID field. If two or more nodes attempt to transmit simultaneously, the node transmitting the message with the lower ID value will win arbitration of the bus. All other nodes will become receivers ensuring that there is at most one transmitter at any time.
Hardware Connection
^^^^^^^^^^^^^^^^^^^
TWAI Messages
The {IDF_TARGET_NAME} does not integrate an internal TWAI transceiver. Therefore, an external transceiver is required to connect to a TWAI bus. The model of the external transceiver depends on the physical layer standard used in your specific application. For example, a TJA105x transceiver can be used to comply with the ISO 11898-2 standard.
.. image:: ../../../_static/diagrams/twai/hw_connection.svg
:alt: ESP32 to Transceiver Wiring
:align: center
Specifically:
- For single-node testing, you can directly short the TX and RX pins to omit the transceiver.
- BUS_OFF (optional): Outputs a low logic level (0V) when the TWAI controller enters the bus-off state. Otherwise, it remains at a high logic level (3.3V).
- CLK_OUT (optional): Outputs the time quantum clock of the controller, which is a divided version of the source clock.
Creating and Starting a TWAI Node
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
First, we need to create a TWAI instance. The following code demonstrates how to create a TWAI node with a baud rate of 200kHz:
.. code:: c
#include "esp_twai.h"
#include "esp_twai_onchip.h"
twai_node_handle_t node_hdl = NULL;
twai_onchip_node_config_t node_config = {
.io_cfg.tx = 4, // TWAI TX GPIO pin
.io_cfg.rx = 5, // TWAI RX GPIO pin
.bit_timing.bitrate = 200000, // 200 kbps bitrate
.tx_queue_depth = 5, // Transmit queue depth set to 5
};
// Create a new TWAI controller driver instance
ESP_ERROR_CHECK(twai_new_node_onchip(&node_config, &node_hdl));
// Start the TWAI controller
ESP_ERROR_CHECK(twai_node_enable(node_hdl));
When creating a TWAI instance, you must configure parameters such as GPIO pins and baud rate using the :cpp:type:`twai_onchip_node_config_t` structure. These parameters determine how the TWAI node operates. Then, you can call the :cpp:func:`twai_new_node_onchip` function to create a new TWAI instance. This function returns a handle to the newly created instance. A TWAI handle is essentially a pointer to an internal TWAI memory object of type :cpp:type:`twai_node_handle_t`.
Below are additional configuration fields of the :cpp:type:`twai_onchip_node_config_t` structure along with their descriptions:
- :cpp:member:`twai_onchip_node_config_t::clk_src`: Specifies the clock source used by the controller. Supported sources are listed in :cpp:type:`twai_clock_source_t`.
- :cpp:member:`twai_onchip_node_config_t::bit_timing::sp_permill`: Specifies the location of the sample point. ssp_permill sets the location of the secondary sample point and can be used to fine-tune timing in low SNR conditions.
- :cpp:member:`twai_onchip_node_config_t::data_timing`: Specifies the baud rate and sample point for the data phase in FD frames. This field is ignored if the controller does not support FD format.
- :cpp:member:`twai_onchip_node_config_t::fail_retry_cnt`: Sets the number of retry attempts on transmission failure. -1 indicates infinite retries until success or bus-off; 0 disables retries (single-shot mode); 1 retries once, and so on.
- :cpp:member:`twai_onchip_node_config_t::intr_priority`: Interrupt priority in the range [0:3], where higher values indicate higher priority.
- :cpp:member:`twai_onchip_node_config_t::flags`: A set of flags for fine-tuning driver behavior. Options include:
- :cpp:member:`twai_onchip_node_config_t::flags::enable_self_test`: Enables self-test mode. In this mode, ACK is not checked during transmission, which is useful for single-node testing.
- :cpp:member:`twai_onchip_node_config_t::flags::enable_loopback`: Enables loopback mode. The node will receive its own transmitted messages (subject to filter configuration), while also transmitting them to the bus.
- :cpp:member:`twai_onchip_node_config_t::flags::enable_listen_only`: Configures the node in listen-only mode. In this mode, the node only receives and does not transmit any dominant bits, including ACK and error frames.
- :cpp:member:`twai_onchip_node_config_t::flags::no_receive_rtr`: When using filters, determines whether remote frames matching the ID pattern should be filtered out.
The :cpp:func:`twai_node_enable` function starts the TWAI controller. Once enabled, the controller is connected to the bus and can transmit messages. It also generates events upon receiving messages from other nodes on the bus or when bus errors are detected.
The corresponding function, :cpp:func:`twai_node_disable`, immediately stops the node and disconnects it from the bus. Any ongoing transmissions will be aborted. When the node is re-enabled later, if there are pending transmissions in the queue, the driver will immediately initiate a new transmission attempt.
Transmitting Messages
^^^^^^^^^^^^^^^^^^^^^
TWAI messages come in various types, which are specified by their headers. A typical data frame consists primarily of a header and data payload, with a structure similar to the following:
.. image:: ../../../_static/diagrams/twai/frame_struct.svg
:align: center
To reduce performance overhead caused by memory copying, the TWAI driver uses pointers to pass messages. The following code demonstrates how to transmit a typical data frame:
.. code:: c
uint8_t send_buff[8] = {0};
twai_frame_t tx_msg = {
.header.id = 0x1, // Message ID
.header.ide = true, // Use 29-bit extended ID format
.buffer = send_buff, // Pointer to data to transmit
.buffer_len = sizeof(send_buff), // Length of data to transmit
};
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_msg, 0)); // Timeout = 0: returns immediately if queue is full
In this example, :cpp:member:`twai_frame_t::header::id` specifies the ID of the message as 0x01. Message IDs are typically used to indicate the type of message in an application and also play a role in bus arbitration during transmission—lower values indicate higher priority on the bus. :cpp:member:`twai_frame_t::buffer` points to the memory address where the data to be transmitted is stored, and :cpp:member:`twai_frame_t::buffer_len` specifies the length of that data.
Note that :cpp:member:`twai_frame_t::header::dlc` can also specify the length of the data in the frame. The DLC (Data Length Code) is mapped to the actual data length as defined in ISO 11898-1. You can use either :cpp:func:`twaifd_dlc2len` or :cpp:func:`twaifd_len2dlc` for conversion. If both dlc and buffer_len are non-zero, they must represent the same length.
The :cpp:type:`twai_frame_t` message structure also includes other configuration fields:
- :cpp:member:`twai_frame_t::dlc`: Data Length Code. For classic frames, values [0:8] represent lengths [0:8]; for FD format, values [0:15] represent lengths up to 64 bytes.
- :cpp:member:`twai_frame_t::header::ide`: Indicates use of a 29-bit extended ID format.
- :cpp:member:`twai_frame_t::header::rtr`: Indicates the frame is a remote frame, which contains no data payload.
- :cpp:member:`twai_frame_t::header::fdf`: Marks the frame as an FD format frame, supporting up to 64 bytes of data.
- :cpp:member:`twai_frame_t::header::brs`: Enables use of a separate data-phase baud rate when transmitting.
- :cpp:member:`twai_frame_t::header::esi`: For received frames, indicates the error state of the transmitting node.
Receiving Messages
^^^^^^^^^^^^^^^^^^
Receiving messages must be done within a receive event callback. Therefore, to receive messages, you need to register a receive event callback via :cpp:member:`twai_event_callbacks_t::on_rx_done` before starting the controller. This enables the controller to deliver received messages via the callback when events occur. The following code snippets demonstrate how to register the receive event callback and how to handle message reception inside the callback:
Registering the receive event callback (before starting the controller):
.. code:: c
twai_event_callbacks_t user_cbs = {
.on_rx_done = twai_rx_cb,
};
ESP_ERROR_CHECK(twai_node_register_event_callbacks(node_hdl, &user_cbs, NULL));
Receiving messages inside the callback:
.. code:: c
static bool twai_rx_cb(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx)
{
uint8_t recv_buff[8];
twai_frame_t rx_frame = {
.buffer = recv_buff,
.buffer_len = sizeof(recv_buff),
};
if (ESP_OK == twai_node_receive_from_isr(handle, &rx_frame)) {
// receive ok, do something here
}
return false;
}
Similarly, since the driver uses pointers for message passing, you must configure the pointer :cpp:member:`twai_frame_t::buffer` and its memory length :cpp:member:`twai_frame_t::buffer_len` before receiving.
Stopping and Deleting the Node
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When the TWAI node is no longer needed, you should call :cpp:func:`twai_node_delete` to release software and hardware resources. Make sure the TWAI controller is stopped before deleting the node.
Advanced Features
-----------------
After understanding the basic usage, you can further explore more advanced capabilities of the TWAI driver. The driver supports more detailed controller configuration and error feedback features. The complete driver feature diagram is shown below:
.. image:: ../../../_static/diagrams/twai/full_flow.drawio.svg
:align: center
Bit Timing Customization
^^^^^^^^^^^^^^^^^^^^^^^^
Unlike other asynchronous communication protocols, the TWAI controller performs counting and sampling within one bit time in units of **Time Quanta (Tq)**. The number of time quanta per bit determines the final baud rate and the sample point position. When signal quality is poor, you can manually fine-tune these timing segments to meet specific requirements. The time quanta within a bit time are divided into different segments, as illustrated below:
.. image:: ../../../_static/diagrams/twai/bit_timing.svg
:alt: Bit timing configuration
:align: center
The synchronization segment (sync) is fixed at 1 Tq. The sample point lies between time segments tseg1 and tseg2. The Synchronization Jump Width (SJW) defines the maximum number of time quanta by which a bit time can be lengthened or shortened for synchronization purposes, ranging from [1 : tseg2]. The clock source divided by the baud rate prescaler (BRP) equals the time quantum. The total sum of all segments equals one bit time. Therefore, the following formula applies:
- Baud rate (bitrate):
.. math::
\text{bitrate} = \frac{f_{\text{src}}}{\text{brp} \cdot (1 + \text{prop_seg} + \text{tseg}_1 + \text{tseg}_2)}
- Sample point:
.. math::
\text{sample_point} = \frac{1 + \text{prop_seg} + \text{tseg}_1}{1 + \text{prop_seg} + \text{tseg}_1 + \text{tseg}_2}
The following code demonstrates how to configure a baud rate of 500 Kbit/s with a sample point at 75% when using an 80 MHz clock source:
.. code:: c
twai_timing_advanced_config_t timing_cfg = {
.brp = 8, // Prescaler set to 8, time quantum = 80M / 8 = 10 MHz (1M Tq)
.prop_seg = 10, // Propagation segment
.tseg_1 = 4, // Phase segment 1
.tseg_2 = 5, // Phase segment 2
.sjw = 3, // Synchronization Jump Width
};
ESP_ERROR_CHECK(twai_node_reconfig_timing(node_hdl, &timing_cfg, NULL)); // Configure arbitration phase timing; NULL means FD data phase timing is not configured
When manually configuring these timing segments, it is important to pay attention to the supported range of each segment according to the specific hardware. The timing configuration function :cpp:func:`twai_node_reconfig_timing` can configure the timing parameters for both the arbitration phase and the FD data phase either simultaneously or separately. When the controller does not support FD format, the data phase configuration is ignored. The timing parameter struct :cpp:type:`twai_timing_advanced_config_t` also includes the following additional configuration fields:
- :cpp:member:`twai_timing_advanced_config_t::clk_src` — The clock source.
- :cpp:member:`twai_timing_advanced_config_t::ssp_offset` — The number of time quanta by which the secondary sample point (SSP) is offset relative to the synchronization segment.
.. note::
Different combinations of ``brp``, ``prop_seg``, ``tseg_1``, ``tseg_2``, and ``sjw`` can achieve the same baud rate. Users should consider factors such as **propagation delay, node processing time, and phase errors**, and adjust the timing parameters based on the physical characteristics of the bus.
Filter Configuration
^^^^^^^^^^^^^^^^^^^^^
Mask Filters
""""""""""""
The TWAI controller hardware can filter messages based on their ID to reduce software and hardware overhead, thereby improving node efficiency. Nodes that filter out certain messages will **not receive those messages, but will still send acknowledgments (ACKs)**.
{IDF_TARGET_NAME} includes {IDF_TARGET_CONFIG_SOC_TWAI_MASK_FILTER_NUM} mask filters. A message passing through any one of these filters will be received by the node. A typical TWAI mask filter is configured with an ID and a MASK, where:
- ID: represents the expected message ID, either the standard 11-bit or extended 29-bit format.
- MASK: defines the filtering rules for each bit of the ID:
- '0' means the corresponding bit is ignored (any value passes).
- '1' means the corresponding bit must match exactly to pass.
- When both ID and MASK are `0`, the filter ignores all bits and accepts all frames.
- When both ID and MASK are set to the maximum `0xFFFFFFFF`, the filter accepts no frames.
The following code demonstrates how to calculate the MASK and configure a filter:
.. code:: c
twai_mask_filter_config_t mfilter_cfg = {
.id = 0x10, // 0b 000 0001 0000
.mask = 0x7f0, // 0b 111 1111 0000 — the upper 7 bits must match strictly, the lower 4 bits are ignored, accepts IDs of the form
// 0b 000 0001 xxxx (hex 0x01x)
.is_ext = false, // Accept only standard IDs, not extended IDs
};
ESP_ERROR_CHECK(twai_node_config_mask_filter(node_hdl, 0, &mfilter_cfg)); // Configure on filter 0
.. only:: not SOC_TWAI_SUPPORT_FD
Dual Filter Mode
""""""""""""""""
{IDF_TARGET_NAME} supports dual filter mode, which allows the hardware to be configured as two parallel independent 16-bit mask filters. By enabling this, more IDs can be received. Note that when filtering 29-bit extended IDs, each filter can only filter the upper 16 bits of the ID, while the remaining 13 bits are not filtered. The following code demonstrates how to configure dual filter mode using the function :cpp:func:`twai_make_dual_filter`:
.. code:: c
// filter 1 id/mask 0x020, 0x7f0, receive only std id 0x02x
// filter 2 id/mask 0x013, 0x7f8, receive only std id 0x010~0x017
twai_mask_filter_config_t dual_config = twai_make_dual_filter(0x020, 0x7f0, 0x013, 0x7f8, false); // id1, mask1, id2, mask2, no extend ID
ESP_ERROR_CHECK(twai_node_config_mask_filter(node_hdl, 0, &dual_config));
.. only:: SOC_TWAI_SUPPORT_FD
Range Filter
""""""""""""
{IDF_TARGET_NAME} also includes 1 range filter, which exists alongside the mask filters. You can configure the desired ID reception range directly using the function :cpp:func:`twai_node_config_range_filter`. The details are as follows:
- Setting :cpp:member:twai_range_filter_config_t::range_low to the minimum value 0, and :cpp:member:twai_range_filter_config_t::range_high to the maximum value 0xFFFFFFFF means receiving all messages.
- Configuring an invalid range means no messages will be received.
Bus Errors and Recovery
^^^^^^^^^^^^^^^^^^^^^^^
The TWAI controller can detect errors caused by bus interference or corrupted frames that do not conform to the frame format. It implements a fault isolation mechanism using transmit and receive error counters (TEC and REC). The values of these counters determine the node's error state: Error Active, Error Warning, Error Passive, and Bus Off. This mechanism ensures that nodes with persistent errors eventually disconnect themselves from the bus.
- **Error Active**: When both TEC and REC are less than 96, the node is in the active error state, meaning normal operation. The node participates in bus communication and sends **active error flags** when errors are detected to actively report them.
- **Error Warning**: When either TEC or REC is greater than or equal to 96 but both are less than 128, the node is in the warning error state. Errors may exist but the node behavior remains unchanged.
- **Error Passive**: When either TEC or REC is greater than or equal to 128, the node enters the passive error state. It can still communicate on the bus but sends only one **passive error flag** when detecting errors.
- **Bus Off**: When **TEC** is greater than or equal to 256, the node enters the bus off (offline) state. The node is effectively disconnected and does not affect the bus. It remains offline until recovery is triggered by software.
Software can retrieve the node status anytime via the function :cpp:func:`twai_node_get_info`. When the controller detects errors, it triggers the :cpp:member:`twai_event_callbacks_t::on_error` callback, where the error data provides detailed information.
When the nodes error state changes, the :cpp:member:`twai_event_callbacks_t::on_state_change` callback is triggered, allowing the application to respond to the state transition. If the node is offline and needs recovery, call :cpp:func:`twai_node_recover` from a task context. **Note that recovery is not immediate; the controller will automatically reconnect to the bus only after detecting 129 consecutive recessive bits (11 bits each).**
When recovery completes, the :cpp:member:`twai_event_callbacks_t::on_state_change` callback will be triggered again, the node changes its state from :cpp:enumerator:`TWAI_ERROR_BUS_OFF` to :cpp:enumerator:`TWAI_ERROR_ACTIVE`. A recovered node can immediately resume transmissions; if there are pending tasks in the transmit queue, the driver will start transmitting them right away.
Power Management
^^^^^^^^^^^^^^^^
When power management is enabled via :ref:`CONFIG_PM_ENABLE`, the system may adjust or disable clock sources before entering sleep mode, which could cause TWAI to malfunction. To prevent this, the driver manages a power management lock internally. This lock is acquired when calling :cpp:func:`twai_node_enable`, ensuring the system does not enter sleep mode and TWAI remains functional. To allow the system to enter a low-power state, call :cpp:func:`twai_node_disable` to release the lock. During sleep, the TWAI controller will also stop functioning.
Cache Safety
^^^^^^^^^^^^
During Flash write operations, the system temporarily disables cache to prevent instruction and data fetch errors from Flash. This can cause interrupt handlers stored in Flash to become unresponsive. If you want interrupt routines to remain operational during cache-disabled periods, enable the :ref:`CONFIG_TWAI_ISR_CACHE_SAFE` option.
.. note::
When this option is enabled, **all interrupt callback functions and their context data must reside in internal memory**, because the system cannot fetch instructions or data from Flash while the cache is disabled.
Thread Safety
^^^^^^^^^^^^^
TWAI Messages are split into Data Frames and Remote Frames. Data Frames are used to deliver a data payload to other nodes, whereas a Remote Frame is used to request a Data Frame from other nodes, and other nodes can optionally respond with a Data Frame. Data and Remote Frames have two frame formats known as **Extended Frame** and **Standard Frame** which contain a 29-bit ID and an 11-bit ID respectively. A TWAI message consists of the following fields:
The driver guarantees thread safety for all public TWAI APIs. You can safely call these APIs from different RTOS tasks without requiring additional synchronization or locking mechanisms.
- 29-bit or 11-bit ID: Determines the priority of the message, and lower value has higher priority.
- Data Length Code (DLC) between 0 to 8: Indicates in bytes the size of the data payload for a Data Frame, or the amount of data to request for a Remote Frame.
- Up to 8 bytes of data for a Data Frame, which should match DLC.
Performance
^^^^^^^^^^^
Error States and Counters
^^^^^^^^^^^^^^^^^^^^^^^^^
The TWAI protocol implements a feature known as "fault confinement" where a persistently erroneous node will eventually eliminate itself from the bus. This is implemented by requiring every node to maintain two internal error counters known as the **Transmit Error Counter (TEC)** and the **Receive Error Counter (REC)**. The two error counters are incremented and decremented according to a set of rules where the counters increase on an error, and decrease on a successful message transmission/reception. The values of the counters are used to determine a node's **error state**, namely **Error Active**, **Error Passive**, and **Bus-Off**.
**Error Active:** A node is Error Active when **both TEC and REC are less than 128** and indicates that the node is operating normally. Error Active nodes are allowed to participate in bus communications, and will actively signal the detection of any errors by automatically transmitting an **Active Error Flag** over the bus.
**Error Passive:** A node is Error Passive when **either the TEC or REC becomes greater than or equal to 128**. Error Passive nodes are still able to take part in bus communications, but will instead transmit a **Passive Error Flag** upon detection of an error.
**Bus-Off:** A node becomes Bus-Off when the **TEC becomes greater than or equal to 256**. A Bus-Off node is unable to influence the bus in any manner, essentially disconnected from the bus, thus eliminating itself from the bus. A node will remain in the Bus-Off state until it undergoes bus-off recovery.
.. ---------------------- Signal Lines and Transceiver -------------------------
Signal Lines and Transceiver
----------------------------
The TWAI controller does not contain an integrated transceiver. Therefore, to connect the TWAI controller to a TWAI bus, **an external transceiver is required**. The type of external transceiver used should depend on the application's physical layer specification. E.g., using SN65HVD23x transceivers for ISO 11898-2 compatibility.
The TWAI controller's interface consists of four signal lines known as **TX, RX, BUS-OFF, and CLKOUT**. These four signal lines can be routed through the GPIO Matrix to the {IDF_TARGET_NAME}'s GPIO pads.
.. blockdiag:: ../../../_static/diagrams/twai/controller_signals.diag
:caption: Signal lines of the TWAI controller
:align: center
**TX and RX:** The TX and RX signal lines are required to interface with an external transceiver. Both signal lines represent/interpret a dominant bit as a low logic level (0 V) and a recessive bit as a high logic level (3.3 V).
**BUS-OFF:** The BUS-OFF signal line is **optional** and is set to a low logic level (0 V) whenever the TWAI controller reaches a bus-off state. The BUS-OFF signal line is set to a high logic level (3.3 V) otherwise.
**CLKOUT:** The CLKOUT signal line is **optional** and outputs a prescaled version of the controller's source clock.
To improve the real-time performance of interrupt handling, the driver provides the :ref:`CONFIG_TWAI_ISR_IN_IRAM` option. When enabled, the TWAI ISR (Interrupt Service Routine) is placed in internal RAM, reducing latency caused by instruction fetching from Flash.
.. note::
An external transceiver **must internally loop back the TX to RX** such that a change in logic level to the TX signal line can be observed on the RX line. Failing to do so will cause the TWAI controller to interpret differences in logic levels between the two signal lines as a loss in arbitration or a bit error.
However, user-defined callback functions and context data invoked by the ISR may still reside in Flash. To fully eliminate Flash latency, users must place these functions and data into internal RAM using macros such as :c:macro:`IRAM_ATTR` for functions and :c:macro:`DRAM_ATTR` for data.
Resource Usage
^^^^^^^^^^^^^^
.. ------------------------------ Configuration --------------------------------
You can inspect the Flash and memory usage of the TWAI driver using the :doc:`/api-guides/tools/idf-size` tool. Below are the test conditions (based on the ESP32-C6 as an example):
API Naming Conventions
----------------------
- Compiler optimization level is set to ``-Os`` to minimize code size.
- Default log level is set to ``ESP_LOG_INFO`` to balance debugging information and performance.
- The following driver optimization options are disabled:
.. note::
- :ref:`CONFIG_TWAI_ISR_IN_IRAM` ISR is not placed in IRAM.
- :ref:`CONFIG_TWAI_ISR_CACHE_SAFE` Cache safety option is disabled.
The TWAI driver provides two sets of API. One is handle-free and is widely used in IDF versions earlier than v5.2, but it can only support one TWAI hardware controller. The other set is with handles, and the function name is usually suffixed with "v2", which can support any number of TWAI controllers. These two sets of API can be used at the same time, but it is recommended to use the "v2" version in your new projects.
**The following resource usage data is for reference only. Actual values may vary across different target chips.**
Driver Configuration
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash | .rodata | .text |
+=================+============+=======+======+=======+=======+=======+=========+=======+
| driver | 7262 | 12 | 12 | 0 | 0 | 7250 | 506 | 6744 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| hal | 1952 | 0 | 0 | 0 | 0 | 0 | 0 | 1952 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| soc | 64 | 0 | 0 | 0 | 0 | 64 | 64 | 0 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
Resource Usage with :ref:`CONFIG_TWAI_ISR_IN_IRAM` Enabled:
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash | .rodata | .text |
+=================+============+=======+======+=======+=======+=======+=========+=======+
| driver | 7248 | 692 | 12 | 0 | 680 | 6556 | 506 | 6050 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| hal | 1952 | 1030 | 0 | 0 | 1030 | 922 | 0 | 922 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| soc | 64 | 0 | 0 | 0 | 0 | 0 | 64 | 0 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
Additionally, each TWAI handle dynamically allocates approximately ``168`` + 4 * :cpp:member:`twai_onchip_node_config_t::tx_queue_depth` bytes of memory from the heap.
Other Kconfig Options
^^^^^^^^^^^^^^^^^^^^^
- :ref:`CONFIG_TWAI_ENABLE_DEBUG_LOG`: This option forces all debug logs of the TWAI driver to be enabled regardless of the global log level settings. Enabling this can help developers obtain more detailed log information during debugging, making it easier to locate and resolve issues.
Application Examples
--------------------
This section covers how to configure the TWAI driver.
Operating Modes
^^^^^^^^^^^^^^^
The TWAI driver supports the following modes of operation:
**Normal Mode:** The normal operating mode allows the TWAI controller to take part in bus activities such as transmitting and receiving messages/error frames. Acknowledgment from another node is required when transmitting a message.
**No Ack Mode:** The No Acknowledgement mode is similar to normal mode, however, acknowledgments are not required for a message transmission to be considered successful. This mode is useful when self-testing the TWAI controller (loopback of transmissions).
**Listen Only Mode:** This mode prevents the TWAI controller from influencing the bus. Therefore, the transmission of messages/acknowledgment/error frames will be disabled. However, the TWAI controller is still able to receive messages but will not acknowledge the message. This mode is suited for bus monitor applications.
Alerts
^^^^^^
The TWAI driver contains an alert feature that is used to notify the application layer of certain TWAI controller or TWAI bus events. Alerts are selectively enabled when the TWAI driver is installed, but can be reconfigured during runtime by calling :cpp:func:`twai_reconfigure_alerts`. The application can then wait for any enabled alerts to occur by calling :cpp:func:`twai_read_alerts`. The TWAI driver supports the following alerts:
.. list-table:: TWAI Driver Alerts
:widths: 40 60
:header-rows: 1
* - Alert Flag
- Description
* - ``TWAI_ALERT_TX_IDLE``
- No more messages queued for transmission
* - ``TWAI_ALERT_TX_SUCCESS``
- The previous transmission was successful
* - ``TWAI_ALERT_RX_DATA``
- A frame has been received and added to the RX queue
* - ``TWAI_ALERT_BELOW_ERR_WARN``
- Both error counters have dropped below the error warning limit
* - ``TWAI_ALERT_ERR_ACTIVE``
- TWAI controller has become error-active
* - ``TWAI_ALERT_RECOVERY_IN_PROGRESS``
- TWAI controller is undergoing bus recovery
* - ``TWAI_ALERT_BUS_RECOVERED``
- TWAI controller has successfully completed bus recovery
* - ``TWAI_ALERT_ARB_LOST``
- The previous transmission lost arbitration
* - ``TWAI_ALERT_ABOVE_ERR_WARN``
- One of the error counters has exceeded the error warning limit
* - ``TWAI_ALERT_BUS_ERROR``
- A (Bit, Stuff, CRC, Form, ACK) error has occurred on the bus
* - ``TWAI_ALERT_TX_FAILED``
- The previous transmission has failed
* - ``TWAI_ALERT_RX_QUEUE_FULL``
- The RX queue is full causing a received frame to be lost
* - ``TWAI_ALERT_ERR_PASS``
- TWAI controller has become error-passive
* - ``TWAI_ALERT_BUS_OFF``
- Bus-off condition occurred. TWAI controller can no longer influence the bus
.. note::
The TWAI controller's **error warning limit** is used to preemptively warn the application of bus errors before the error passive state is reached. By default, the TWAI driver sets the **error warning limit** to **96**. The ``TWAI_ALERT_ABOVE_ERR_WARN`` is raised when the TEC or REC becomes larger than or equal to the error warning limit. The ``TWAI_ALERT_BELOW_ERR_WARN`` is raised when both TEC and REC return to values below **96**.
.. note::
When enabling alerts, the ``TWAI_ALERT_AND_LOG`` flag can be used to cause the TWAI driver to log any raised alerts to UART. However, alert logging is disabled and ``TWAI_ALERT_AND_LOG`` if the :ref:`CONFIG_TWAI_ISR_IN_IRAM` option is enabled. See :ref:`placing-isr-into-iram`.
.. note::
The ``TWAI_ALERT_ALL`` and ``TWAI_ALERT_NONE`` macros can also be used to enable or disable all alerts during configuration or reconfiguration.
Bit Timing
^^^^^^^^^^
The operating bit rate of the TWAI driver is configured using the :cpp:type:`twai_timing_config_t` structure. The period of each bit is made up of multiple **time quanta**, and the period of a **time quantum** is determined by a pre-scaled version of the TWAI controller's source clock. A single bit contains the following segments in the following order:
1. The **Synchronization Segment** consists of a single time quantum
2. **Timing Segment 1** consists of 1- to 16-time quanta before the sample point
3. **Timing Segment 2** consists of 1- to 8-time quanta after the sample point
{IDF_TARGET_MAX_BRP:default="32768", esp32="128", esp32s3="16384", esp32c3="16384"}
The **Baud Rate Prescaler (BRP)** is used to determine the period of each time quantum by dividing the TWAI controller's source clock. On the {IDF_TARGET_NAME}, the ``brp`` can be **any even number from 2 to {IDF_TARGET_MAX_BRP}**. Alternatively, you can decide the resolution of each quantum, by setting :cpp:member:`twai_timing_config_t::quanta_resolution_hz` to a non-zero value. In this way, the driver can calculate the underlying ``brp`` value for you. It is useful when you set different clock sources but want the bitrate to keep the same.
The supported clock source for a TWAI controller is listed in the :cpp:type:`twai_clock_source_t` and can be specified in :cpp:member:`twai_timing_config_t::clk_src`.
.. only:: esp32
If the ESP32 is a v2.0 or later chip, the ``brp`` will **also support any multiple of 4 from 132 to 256**, and can be enabled by setting the :ref:`CONFIG_ESP32_REV_MIN` to v2.0 or higher.
.. packetdiag:: ../../../_static/diagrams/twai/bit_timing.diag
:caption: Bit timing configuration for 500kbit/s given BRP = 8, clock source frequency is 80MHz
:align: center
The sample point of a bit is located at the intersection of Timing Segments 1 and 2. Enabling **Triple Sampling** causes 3-time quanta to be sampled per bit instead of 1, and extra samples are located at the tail end of Timing Segment 1.
The **Synchronization Jump Width (SJW)** is used to determine the maximum number of time quanta a single bit time can be lengthened/shortened for synchronization purposes. ``sjw`` can **range from 1 to 4**.
.. note::
Multiple combinations of ``brp``, ``tseg_1``, ``tseg_2``, and ``sjw`` can achieve the same bit rate. Users should tune these values to the physical characteristics of their bus by taking into account factors such as **propagation delay, node information processing time, and phase errors**.
Bit timing **macro initializers** are also available for commonly used bit rates. The following macro initializers are provided by the TWAI driver.
.. list::
- :c:macro:`TWAI_TIMING_CONFIG_1MBITS`
- :c:macro:`TWAI_TIMING_CONFIG_800KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_500KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_250KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_125KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_100KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_50KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_25KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_20KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_16KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_12_5KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_10KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_5KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_1KBITS`
.. only:: esp32
v2.0 or later of the ESP32 also supports the following bit rates:
- :c:macro:`TWAI_TIMING_CONFIG_20KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_16KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_12_5KBITS`
Acceptance Filter
^^^^^^^^^^^^^^^^^
The TWAI controller contains a hardware acceptance filter which can be used to filter messages of a particular ID. A node that filters out a message **does not receive the message, but will still acknowledge it**. Acceptance filters can make a node more efficient by filtering out messages sent over the bus that are irrelevant to the node. The acceptance filter is configured using two 32-bit values within :cpp:type:`twai_filter_config_t` known as the **acceptance code** and the **acceptance mask**.
The **acceptance code** specifies the bit sequence in which a message's ID, RTR, and data bytes must match in order for the message to be received by the TWAI controller. The **acceptance mask** is a bit sequence specifying which bits of the acceptance code can be ignored. This allows for messages of different IDs to be accepted by a single acceptance code.
The acceptance filter can be used under **Single or Dual Filter Mode**. Single Filter Mode uses the acceptance code and mask to define a single filter. This allows for the first two data bytes of a standard frame to be filtered or the entirety of an extended frame's 29-bit ID. The following diagram illustrates how the 32-bit acceptance code and mask are interpreted under Single Filter Mode. Note: The yellow and blue fields represent standard and extended frame formats respectively.
.. packetdiag:: ../../../_static/diagrams/twai/acceptance_filter_single.diag
:caption: Bit layout of single filter mode (Right side MSBit)
:align: center
**Dual Filter Mode** uses the acceptance code and mask to define two separate filters allowing for increased flexibility of IDs to accept, but does not allow for all 29 bits of an extended ID to be filtered. The following diagram illustrates how the 32-bit acceptance code and mask are interpreted under **Dual Filter Mode**. Note: The yellow and blue fields represent standard and extended frame formats respectively.
.. packetdiag:: ../../../_static/diagrams/twai/acceptance_filter_dual.diag
:caption: Bit layout of dual filter mode (Right side MSBit)
:align: center
Disabling TX Queue
^^^^^^^^^^^^^^^^^^
The TX queue can be disabled during configuration by setting the ``tx_queue_len`` member of :cpp:type:`twai_general_config_t` to ``0``. This allows applications that do not require message transmission to save a small amount of memory when using the TWAI driver.
.. _placing-isr-into-iram:
Placing ISR into IRAM
^^^^^^^^^^^^^^^^^^^^^
The TWAI driver's ISR (Interrupt Service Routine) can be placed into IRAM so that the ISR can still run whilst the cache is disabled. Placing the ISR into IRAM may be necessary to maintain the TWAI driver's functionality during lengthy cache-disabling operations (such as SPI Flash writes, OTA updates, etc.). Whilst the cache is disabled, the ISR continues to:
- Read received messages from the RX buffer and place them into the driver's RX queue.
- Load messages pending transmission from the driver's TX queue and write them into the TX buffer.
To place the TWAI driver's ISR, users must do the following:
- Enable the :ref:`CONFIG_TWAI_ISR_IN_IRAM` option using ``idf.py menuconfig``.
- When calling :cpp:func:`twai_driver_install`, the ``intr_flags`` member of :cpp:type:`twai_general_config_t` should set :c:macro:`ESP_INTR_FLAG_IRAM`.
.. note::
When the :ref:`CONFIG_TWAI_ISR_IN_IRAM` option is enabled, the TWAI driver will no longer log any alerts, i.e., the ``TWAI_ALERT_AND_LOG`` flag will not have any effect.
.. only:: esp32
ESP32 Errata Workarounds
^^^^^^^^^^^^^^^^^^^^^^^^
The ESP32's TWAI controller contains multiple hardware errata (more details about the errata can be found in the `ESP32's ECO document <https://www.espressif.com/sites/default/files/documentation/eco_and_workarounds_for_bugs_in_esp32_en.pdf>`_). Some of these errata are critical, and under specific circumstances, can place the TWAI controller into an unrecoverable state (i.e., the controller gets stuck until it is reset by the CPU).
The TWAI driver contains software workarounds for these critical errata. With these workarounds, the ESP32 TWAI driver can operate normally, albeit with degraded performance. The degraded performance will affect users in the following ways depending on what particular errata conditions are encountered:
- The TWAI driver can occasionally drop some received messages.
- The TWAI driver can be unresponsive for a short period of time, i.e., will not transmit or ACK for 11-bit times or longer.
- If :ref:`CONFIG_TWAI_ISR_IN_IRAM` is enabled, the workarounds will increase IRAM usage by approximately 1 KB.
The software workarounds are enabled by default and it is recommended that users keep these workarounds enabled.
.. ------------------------------- TWAI Driver ---------------------------------
Driver Operation
----------------
The TWAI driver is designed with distinct states and strict rules regarding the functions or conditions that trigger a state transition. The following diagram illustrates the various states and their transitions.
.. blockdiag:: ../../../_static/diagrams/twai/state_transition.diag
:caption: State transition diagram of the TWAI driver (see table below)
:align: center
.. list-table::
:widths: 20 40 40
:header-rows: 1
* - Label
- Transition
- Action/Condition
* - A
- Uninstalled -> Stopped
- :cpp:func:`twai_driver_install`
* - B
- Stopped -> Uninstalled
- :cpp:func:`twai_driver_uninstall`
* - C
- Stopped -> Running
- :cpp:func:`twai_start`
* - D
- Running -> Stopped
- :cpp:func:`twai_stop`
* - E
- Running -> Bus-Off
- Transmit Error Counter >= 256
* - F
- Bus-Off -> Uninstalled
- :cpp:func:`twai_driver_uninstall`
* - G
- Bus-Off -> Recovering
- :cpp:func:`twai_initiate_recovery`
* - H
- Recovering -> Stopped
- 128 occurrences of 11 consecutive recessive bits
Driver States
^^^^^^^^^^^^^
**Uninstalled**: In the uninstalled state, no memory is allocated for the driver, and the TWAI controller is powered OFF.
**Stopped**: In this state, the TWAI controller is powered ON and the TWAI driver has been installed. However, the TWAI controller is unable to take part in any bus activities such as transmitting, receiving, or acknowledging messages.
**Running**: In the running state, the TWAI controller is able to take part in bus activities. Therefore messages can be transmitted/received/acknowledged. Furthermore, the TWAI controller is able to transmit error frames upon detection of errors on the bus.
**Bus-Off**: The bus-off state is automatically entered when the TWAI controller's Transmit Error Counter becomes greater than or equal to 256. The bus-off state indicates the occurrence of severe errors on the bus or in the TWAI controller. Whilst in the bus-off state, the TWAI controller is unable to take part in any bus activities. To exit the bus-off state, the TWAI controller must undergo the bus recovery process.
**Recovering**: The recovering state is entered when the TWAI controller undergoes bus recovery. The TWAI controller/TWAI driver remains in the recovering state until 128 occurrences of 11 consecutive recessive bits are observed on the bus.
Message Fields and Flags
^^^^^^^^^^^^^^^^^^^^^^^^
The TWAI driver distinguishes different types of messages by using the various bit field members of the :cpp:type:`twai_message_t` structure. These bit field members determine whether a message is in standard or extended format, a remote frame, and the type of transmission to use when transmitting such a message.
These bit field members can also be toggled using the ``flags`` member of :cpp:type:`twai_message_t` and the following message flags:
.. list-table::
:widths: 30 70
:header-rows: 1
* - Message Flag
- Description
* - ``TWAI_MSG_FLAG_EXTD``
- Message is in Extended Frame Format (29bit ID)
* - ``TWAI_MSG_FLAG_RTR``
- Message is a Remote Frame (Remote Transmission Request)
* - ``TWAI_MSG_FLAG_SS``
- Transmit message using Single Shot Transmission, i.e., message will not be retransmitted upon error or loss of arbitration (Unused for the received message)
* - ``TWAI_MSG_FLAG_SELF``
- Transmit message using Self Reception Request, i.e., transmitted message will also received by the same node (Unused for the received message)
* - ``TWAI_MSG_FLAG_DLC_NON_COMP``
- Message's Data length code is larger than 8, which breaks compliance with TWAI
* - ``TWAI_MSG_FLAG_NONE``
- Clears all bit fields, and the flag is equivalent to a Standard Frame Format (11bit ID) Data Frame
.. -------------------------------- Examples -----------------------------------
Examples
--------
Configuration & Installation
^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following code snippet demonstrates how to configure, install, and start the TWAI driver via the use of the various configuration structures, macro initializers, the :cpp:func:`twai_driver_install` function, and the :cpp:func:`twai_start` function.
.. code-block:: c
#include "driver/gpio.h"
#include "driver/twai.h"
void app_main()
{
// Initialize configuration structures using macro initializers
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_21, GPIO_NUM_22, TWAI_MODE_NORMAL);
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// Install TWAI driver
if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) {
printf("Driver installed\n");
} else {
printf("Failed to install driver\n");
return;
}
// Start TWAI driver
if (twai_start() == ESP_OK) {
printf("Driver started\n");
} else {
printf("Failed to start driver\n");
return;
}
...
}
The usage of macro initializers is not mandatory and each of the configuration structures can be done manually.
Install Multiple TWAI Instances
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
**Note:** You can create {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} functional TWAI instance(s) because the {IDF_TARGET_NAME} has {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} physical TWAI controller(s).
The following code snippet demonstrates how to install multiple TWAI instances via the use of the :cpp:func:`twai_driver_install_v2` function.
.. code-block:: c
#include "driver/gpio.h"
#include "driver/twai.h"
void app_main()
{
twai_handle_t twai_bus_0;
twai_handle_t twai_bus_1;
// Initialize configuration structures using macro initializers
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_0, GPIO_NUM_1, TWAI_MODE_NORMAL);
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// Install driver for TWAI bus 0
g_config.controller_id = 0;
if (twai_driver_install_v2(&g_config, &t_config, &f_config, &twai_bus_0) == ESP_OK) {
printf("Driver installed\n");
} else {
printf("Failed to install driver\n");
return;
}
// Start TWAI driver
if (twai_start_v2(twai_bus_0) == ESP_OK) {
printf("Driver started\n");
} else {
printf("Failed to start driver\n");
return;
}
// Install driver for TWAI bus 1
g_config.controller_id = 1;
g_config.tx_io = GPIO_NUM_2;
g_config.rx_io = GPIO_NUM_3;
if (twai_driver_install_v2(&g_config, &t_config, &f_config, &twai_bus_1) == ESP_OK) {
printf("Driver installed\n");
} else {
printf("Failed to install driver\n");
return;
}
// Start TWAI driver
if (twai_start_v2(twai_bus_1) == ESP_OK) {
printf("Driver started\n");
} else {
printf("Failed to start driver\n");
return;
}
// Other Driver operations must use version 2 API as well
...
}
Message Transmission
^^^^^^^^^^^^^^^^^^^^
The following code snippet demonstrates how to transmit a message via the usage of the :cpp:type:`twai_message_t` type and :cpp:func:`twai_transmit` function.
.. code-block:: c
#include "driver/twai.h"
...
// Configure message to transmit
twai_message_t message = {
// Message type and format settings
.extd = 1, // Standard vs extended format
.rtr = 0, // Data vs RTR frame
.ss = 0, // Whether the message is single shot (i.e., does not repeat on error)
.self = 0, // Whether the message is a self reception request (loopback)
.dlc_non_comp = 0, // DLC is less than 8
// Message ID and payload
.identifier = 0xAAAA,
.data_length_code = 4,
.data = {0, 1, 2, 3},
};
// Queue message for transmission
if (twai_transmit(&message, pdMS_TO_TICKS(1000)) == ESP_OK) {
printf("Message queued for transmission\n");
} else {
printf("Failed to queue message for transmission\n");
}
Message Reception
^^^^^^^^^^^^^^^^^
The following code snippet demonstrates how to receive a message via the usage of the :cpp:type:`twai_message_t` type and :cpp:func:`twai_receive` function.
.. code-block:: c
#include "driver/twai.h"
...
// Wait for the message to be received
twai_message_t message;
if (twai_receive(&message, pdMS_TO_TICKS(10000)) == ESP_OK) {
printf("Message received\n");
} else {
printf("Failed to receive message\n");
return;
}
// Process received message
if (message.extd) {
printf("Message is in Extended Format\n");
} else {
printf("Message is in Standard Format\n");
}
printf("ID is %d\n", message.identifier);
if (!(message.rtr)) {
for (int i = 0; i < message.data_length_code; i++) {
printf("Data byte %d = %d\n", i, message.data[i]);
}
}
Reconfiguring and Reading Alerts
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The following code snippet demonstrates how to reconfigure and read TWAI driver alerts via the use of the :cpp:func:`twai_reconfigure_alerts` and :cpp:func:`twai_read_alerts` functions.
.. code-block:: c
#include "driver/twai.h"
...
// Reconfigure alerts to detect Error Passive and Bus-Off error states
uint32_t alerts_to_enable = TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_OFF;
if (twai_reconfigure_alerts(alerts_to_enable, NULL) == ESP_OK) {
printf("Alerts reconfigured\n");
} else {
printf("Failed to reconfigure alerts");
}
// Block indefinitely until an alert occurs
uint32_t alerts_triggered;
twai_read_alerts(&alerts_triggered, portMAX_DELAY);
Stop and Uninstall
^^^^^^^^^^^^^^^^^^
The following code demonstrates how to stop and uninstall the TWAI driver via the use of the :cpp:func:`twai_stop` and :cpp:func:`twai_driver_uninstall` functions.
.. code-block:: c
#include "driver/twai.h"
...
// Stop the TWAI driver
if (twai_stop() == ESP_OK) {
printf("Driver stopped\n");
} else {
printf("Failed to stop driver\n");
return;
}
// Uninstall the TWAI driver
if (twai_driver_uninstall() == ESP_OK) {
printf("Driver uninstalled\n");
} else {
printf("Failed to uninstall driver\n");
return;
}
Multiple ID Filter Configuration
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
The acceptance mask in :cpp:type:`twai_filter_config_t` can be configured such that two or more IDs are accepted for a single filter. For a particular filter to accept multiple IDs, the conflicting bit positions amongst the IDs must be set in the acceptance mask. The acceptance code can be set to any one of the IDs.
The following example shows how to calculate the acceptance mask given multiple IDs:
.. code-block::
ID1 = 11'b101 1010 0000
ID2 = 11'b101 1010 0001
ID3 = 11'b101 1010 0100
ID4 = 11'b101 1010 1000
// Acceptance Mask
MASK = 11'b000 0000 1101
Application Examples
^^^^^^^^^^^^^^^^^^^^
**Network Example:** :example:`peripherals/twai/twai_network` demonstrates communication between two {IDF_TARGET_NAME}s using the TWAI driver API. One TWAI node acts as a network master that initiates and ceases the transfer of data from another node acting as a network slave.
**Alert and Recovery Example:** :example:`peripherals/twai/twai_alert_and_recovery` demonstrates how to use the TWAI driver's alert and bus recovery features on {IDF_TARGET_NAME}, by initializing the driver, creating tasks for message transmission and alert handling, triggering bit errors to enter the Bus-Off state, and initiating the Bus-Off recovery process.
**Self-Test Example:** :example:`peripherals/twai/twai_self_test` demonstrates how a node can transmit TWAI messages to itself using the TWAI driver's "No Acknowledgement" mode and Self Reception Requests, testing the proper connection of a target to a working external transceiver.
.. only:: SOC_TWAI_SUPPORT_SLEEP_RETENTION
Sleep Retention
^^^^^^^^^^^^^^^
{IDF_TARGET_NAME} supports to retain the TWAI register context before entering **light sleep** and restore them after waking up. This means you don't have to re-init the TWAI driver after the light sleep.
This feature can be enabled by setting the flag :cpp:member:`twai_general_config_t::sleep_allow_pd`. It will allow the system to power down the TWAI in light sleep, meanwhile saving the register context. It can help save more power consumption with some extra cost of the memory.
.. ---------------------------- API Reference ----------------------------------
- Temporary no.
API Reference
-------------
.. include-build-file:: inc/twai_types_deprecated.inc
.. include-build-file:: inc/twai.inc
.. include-build-file:: inc/esp_twai_onchip.inc
.. include-build-file:: inc/esp_twai.inc
.. include-build-file:: inc/esp_twai_types.inc
.. include-build-file:: inc/twai_types.inc

View File

@ -25,3 +25,37 @@ Peripherals
- The new touch driver implemented Finite-State Machine (FSM) to ensure a correct touch sensor operation. Please refer to the programming guide :doc:`Capacitive Touch Sensor <../../../api-reference/peripherals/cap_touch_sens>` for more details.
- All new APIs are guaranteed to be thread-safe.
- Implemented a software filter for the touch sensor V1 (ESP32), and open for customization.
.. only:: SOC_TWAI_SUPPORTED
Two-Wire Automotive Interface (TWAI)
------------------------------------
TWAI provides a brand-new driver interface that supports a wider range of convenient features. Please refer to the programming guide: :doc:`Two-Wire Automotive Interface (TWAI)<../../../api-reference/peripherals/twai>`.
Compared to the legacy driver, the new driver offers several major improvements:
- Event-driven architecture, supporting registration of multiple callback functions
- Runtime configuration changes for bitrate and filter settings
- More intuitive and user-friendly APIs for bitrate and filter configuration
- Support for ESP32-C5 and its FD (Flexible Datarate) capabilities
- Support using multiple TWAI controllers simultaneously
The new driver is located in the :component:`esp_driver_twai` component. To use it, simply add the component dependency. The relevant header files are :component_file:`esp_driver_twai/include/esp_twai.h` and :component_file:`esp_driver_twai/include/esp_twai_onchip.h`.
+---------------------------+----------------------------+-----------------------------------------------------------------------------------------------+
| Deprecated/Removed | Replacement | Notes |
+===========================+============================+===============================================================================================+
| twai_read_alerts | None | Use ``on_state_change`` and ``on_error`` callbacks to receive status and error notifications. |
+---------------------------+----------------------------+-----------------------------------------------------------------------------------------------+
| twai_receive | twai_node_receive_from_isr | Can only be called inside the on_rx_done callback. |
+---------------------------+----------------------------+-----------------------------------------------------------------------------------------------+
| twai_clear_transmit_queue | None | Not supported at the moment. |
+---------------------------+----------------------------+-----------------------------------------------------------------------------------------------+
| twai_clear_receive_queue | None | The new driver uses event-driven receive; blocking receive APIs are not currently provided. |
+---------------------------+----------------------------+-----------------------------------------------------------------------------------------------+
Although we recommend using the new TWAI driver APIs, the legacy driver is still available. To use the legacy driver, include the header file ``driver/twai.h``. When using the legacy driver, please note the following:
- The new and legacy drivers are not compatible and must not be used together. Mixing them will trigger warnings during startup, and may even cause crashes and system reboots. To suppress this compatibility check, you may enable the configuration option :ref:`CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK`.
- The legacy driver will no longer receive new features, such as TWAI FD (Flexible Data-rate) support.

View File

@ -293,6 +293,16 @@ If declarator-id:
struct rmt_symbol_word_t
------------------------^
twai_types.inc:line: WARNING: Error in declarator or parameters-and-qualifiers
If pointer to member declarator:
Invalid C++ declaration: Expected identifier in nested name. [error at 25]
struct twai_error_flags_t
-------------------------^
If declarator-id:
Invalid C++ declaration: Expected identifier in nested name. [error at 25]
struct twai_error_flags_t
-------------------------^
mqtt_client.inc:line: WARNING: Duplicate C++ declaration, also defined at api-reference/protocols/mqtt:line.
Declaration is '.. cpp:type:: struct esp_mqtt_event_t esp_mqtt_event_t'.
mqtt_client.inc:line: WARNING: Duplicate C++ declaration, also defined at api-reference/protocols/mqtt:line.

View File

@ -82,6 +82,7 @@
- :cpp:member:`gptimer_config_t::resolution_hz` 设置内部计数器的分辨率。计数器每滴答一次相当于 **1 / resolution_hz** 秒。
- :cpp:member:`gptimer_config_t::intr_priority` 设置中断的优先级。如果设置为 ``0``,则会分配一个默认优先级的中断,否则会使用指定的优先级。
- :cpp:member:`gptimer_config_t::flags` 通常用来微调驱动的一些行为,包括以下选项:
- :cpp:member:`gptimer_config_t::flags::allow_pd` 配置驱动程序是否允许系统在睡眠模式下关闭外设电源。在进入睡眠之前,系统将备份 GPTimer 寄存器上下文,当系统从睡眠唤醒时时,这些上下文将被恢复。请注意,关闭外设可以节省功耗,但会消耗更多内存来保存寄存器上下文。你需要在功耗和内存消耗之间做权衡。此配置选项依赖于特定的硬件功能,如果在不支持的芯片上启用它,你将看到类似 ``not able to power down in light sleep`` 的错误消息。
.. note::

View File

@ -3,617 +3,374 @@
:link_to_translation:`en:[English]`
编程指南包含以下部分
文介绍了 ESP-IDF 中的双线汽车接口Two-Wire Automotive Interface控制器驱动的功能章节目录如下
.. contents::
:local:
:depth: 1
.. -------------------------------- Overview -----------------------------------
:local:
:depth: 2
概述
----
TWAI 是一种适用于汽车和工业应用的高可靠性的多主机实时串行异步通信协议。它兼容 ISO11898-1 标准定义的帧结构,可以支持 11 位 ID 的标准帧和 29 位 ID 的扩展帧。支持报文优先级和无损仲裁,支持自动重传和故障自动隔离机制。{IDF_TARGET_NAME} 包含 {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} 个 TWAI 控制器,可以创建 {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} 个驱动实例。
.. only:: SOC_TWAI_SUPPORT_FD
{IDF_TARGET_NAME} TWAI 控制器兼容 ISO11898-1 FD 格式帧,可以发送和接收经典格式和 FD 格式帧。
.. only:: not SOC_TWAI_SUPPORT_FD
{IDF_TARGET_NAME} TWAI 控制器 **不兼容 ISO11898-1 FD 格式帧,并会将这些帧解析为错误。**
基于硬件的高容错、多主机特性,该驱动的主要应用场景包括:
- 作为复杂干扰环境的通信总线,为设备提供可靠通信
- 作为远距离多传感器/执行器总线,单节点故障不影响总线运行
- 搭建去中心化分布式局域网络,避免单一主节点机制的不确定性
- 配合其他通信协议作为桥接设备
快速入门
--------
双线汽车接口 (TWAI) 是一种适用于汽车和工业应用的实时串行通信协议。它兼容 ISO11898-1 经典帧因此可以支持标准帧格式11 位 ID和扩展帧格式29 位 ID。{IDF_TARGET_NAME} 包含 {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} 个 TWAI 控制器,经配置可以在 TWAI 总线上使用外部收发器通信。
本节将带你快速了解如何使用 TWAI 驱动。通过简单的示例,展示如何创建一个 TWAI 节点实例,如何发送和接收总线上的报文,以及如何安全停止和删除驱动。一般的使用流程如下:
.. warning::
TWAI 控制器不兼容 ISO11898-1 FD 格式帧,并会将这些帧解析为错误。
.. --------------------------- Basic TWAI Concepts -----------------------------
TWAI 协议概述
-------------
TWAI 是一种多主机、多播、异步、串行通信协议,该协议还支持错误检测和通报,并具有内置报文优先级。
**多主机:** 总线上的任何节点都可以发起报文传输。
**多播:** 节点传输报文时,总线上的所有节点都会接收该报文(即广播),确保所有节点数据一致。但通过接收过滤,某些节点可以选择性地接收报文(多播)。
**异步:** 总线不包含时钟信号。总线上的所有节点以相同的位速率运行,并使用在总线上传输位的边沿进行同步。
**错误检测和通报:** 每个节点不断监视总线。节点检测到错误时,通过传输错误帧通报检测到的错误。其他节点会接收错误帧,并传输自己的错误帧作为回应,这样一来检测的错误即可传播到总线上的所有节点。
**报文优先级:** 每个报文包含唯一的 ID 字段。如果两个或多个节点尝试同时传输ID 小的节点将获得总线的控制权,而其他节点将自动转为接收器,确保无论何时最多只有一个发射器。
TWAI 报文
^^^^^^^^^
TWAI 报文分为数据帧和远程帧。数据帧用于向其他节点传递数据载荷,远程帧用于请求其他节点的数据帧,其他节点可以选择性地用数据帧响应。数据帧和远程帧有两种帧格式,称为 **扩展帧****标准帧**,分别包含了 29 位 ID 和 11 位 ID。TWAI 报文包括以下字段:
- 29 位或 11 位的 ID确定报文优先级值越小优先级越高。
- 0 到 8 之间的数据长度代码 (DLC):以字节为单位,表示数据帧的数据载荷大小,或者远程帧请求的数据量。
- 数据帧数据,最多为 8 个字节,应与 DLC 匹配。
错误状态和计数器
^^^^^^^^^^^^^^^^
TWAI 协议具备“故障隔离”功能,它可以使持续存在错误的节点最终自行断开总线。该功能通过要求每个节点维护两个内部错误计数器实现,这两个计数器分别称为 **发送错误计数器 (TEC)****接收错误计数器 (REC)**。根据一组规则,这两个错误计数器在发生错误时递增,在报文发送/接收成功时递减。计数器值决定节点的 **错误状态**,即 **主动错误****被动错误****离线**
**主动错误:****TEC 和 REC 都小于 128** 时,节点处于主动错误状态,表示节点正常运行。主动错误节点可以参与总线通信,并会自动通过总线发送 **主动错误标志**,主动报告检测到的错误。
**被动错误:****TEC 或 REC 中的一个大于或等于 128** 时,节点处于被动错误状态。被动错误的节点仍可以参与总线通信,但在检测到错误时,只能发送一次 **被动错误标志**
**离线:****TEC 大于或等于 256** 时,节点进入离线状态。离线的节点无法对总线产生任何影响,相当于断开连接,进而从总线自行清除。节点将保持离线状态,直到触发离线恢复。
.. ---------------------- Signal Lines and Transceiver -------------------------
信号线和收发器
--------------
TWAI 控制器不含集成收发器。因此,要将 TWAI 控制器连接到 TWAI 总线,**需要外部收发器**。所使用的外部收发器类型应根据应用的物理层规范而定。例如,使用 SN65HVD23x 收发器以兼容 ISO 11898-2。
TWAI 控制器的接口由四条信号线组成,分别为 **TX、RX、BUS-OFF 和 CLKOUT**。这四条信号线可以通过 GPIO 矩阵连接到 {IDF_TARGET_NAME} 的 GPIO 管脚上。
.. blockdiag:: ../../../_static/diagrams/twai/controller_signals.diag
:caption: TWAI控制器的信号线
.. image:: ../../../_static/diagrams/twai/base_flow.drawio.svg
:align: center
**TX 和 RX:** TX 和 RX 信号线用于与外部收发器通信。这两条信号线将显性位表示/解析为低逻辑电平 (0 V),将隐性位表示/解析为高逻辑电平 (3.3 V)。
硬件连接
^^^^^^^^
**BUS-OFF:** BUS-OFF 信号线是 **可选** 的,在 TWAI 控制器进入离线状态时为低逻辑电平 (0 V)。否则BUS-OFF 信号线将设置为高逻辑电平 (3.3 V)
{IDF_TARGET_NAME} 内部没有集成 TWAI 收发器。因此你需要外接一个收发器才能加入 TWAI 总线。外部收发器的型号取决于具体应用遵循的物理层规范。例如,使用 TJA105x 收发器以兼容 ISO 11898-2 标准
**CLKOUT:** CLKOUT 信号线是 **可选** 的,会输出控制器源时钟的分频时钟。
.. image:: ../../../_static/diagrams/twai/hw_connection.svg
:alt: ESP32 to Transceiver Wiring
:align: center
其中:
- 在做单节点测试时,可以直接短接 TX 和 RX 引脚以省略收发器。
- BUS_OFF (可选),在 TWAI 控制器进入离线状态时为低逻辑电平 (0 V)。否则为高逻辑电平 (3.3 V)。
- CLK_OUT (可选),输出控制器时间量子时钟,即源时钟的分频时钟。
创建和启动 TWAI 节点
^^^^^^^^^^^^^^^^^^^^^
首先,我们需要创建一个 TWAI 实例。以下代码展示了如何创建一个波特率为 200kHz 的 TWAI 节点:
.. code:: c
#include "esp_twai.h"
#include "esp_twai_onchip.h"
twai_node_handle_t node_hdl = NULL;
twai_onchip_node_config_t node_config = {
.io_cfg.tx = 4, // twai tx GPIO
.io_cfg.rx = 5, // twai rx GPIO
.bit_timing.bitrate = 200000, // 200k 波特率
.tx_queue_depth = 5,// 发送队列深度为5
};
// 创建 TWAI 控制器驱动实例
ESP_ERROR_CHECK(twai_new_node_onchip(&node_config, &node_hdl));
// 启动 TWAI 控制器
ESP_ERROR_CHECK(twai_node_enable(node_hdl));
当创建 TWAI 实例时,我们需要通过 :cpp:type:`twai_onchip_node_config_t` 配置 GPIO 引脚、波特率等参数。这些参数将决定 TWAI 的工作方式。然后调用 :cpp:func:`twai_new_node_onchip` 函数创建一个新的 TWAI 实例,该函数将返回一个指向新实例的句柄。 TWAI 的句柄实际上是一个指向 TWAI 内存对象的指针,类型为 :cpp:type:`twai_node_handle_t`
以下是 :cpp:type:`twai_onchip_node_config_t` 结构体的其他配置参数及其解释:
- :cpp:member:`twai_onchip_node_config_t::clk_src` 指定控制器使用的时钟源,支持的时钟源列表见 :cpp:type:`twai_clock_source_t`
- :cpp:member:`twai_onchip_node_config_t::bit_timing::sp_permill` 指定采样点位置ssp_permill 指定二次采样点位置,可用于低信噪比下的时序微调。
- :cpp:member:`twai_onchip_node_config_t::data_timing` 指定 FD 格式时数据段的波特率及采样点,如果控制器不兼容 FD 格式,此配置无效。
- :cpp:member:`twai_onchip_node_config_t::fail_retry_cnt` 失败重传次数,-1 表示无限重传直到成功或 BUS_OFF 0 表示失败后重传 0 次,即单次模式; 1 :重传 1 次,以此类推。
- :cpp:member:`twai_onchip_node_config_t::intr_priority` 中断优先级,范围 [0:3],值越大优先级越高。
- :cpp:member:`twai_onchip_node_config_t::flags` 通常用来微调驱动的一些行为,包括以下选项:
- :cpp:member:`twai_onchip_node_config_t::flags::enable_self_test` 使能自测模式发送报文时不检查ACK接收可用于单节点测试。
- :cpp:member:`twai_onchip_node_config_t::flags::enable_loopback` 使能自收发模式,节点会收到自己发送的报文(如果配置了过滤器则还需要符合过滤规则),同时也会发送到总线。
- :cpp:member:`twai_onchip_node_config_t::flags::enable_listen_only` 配置为监听模式,节点只接收,不发送任何显性位,包括 ACK 和错误帧。
- :cpp:member:`twai_onchip_node_config_t::flags::no_receive_rtr` 使用过滤器时是否同时过滤掉符合 ID 规则的远程帧。
函数 :cpp:func:`twai_node_enable` 将启动 TWAI 控制器,此时 TWAI 控制器就连接到了总线,可以向总线发送报文。如果收到了总线上其他节点发送的报文,或者检测到了总线错误,也将产生相应事件。
与之对应的函数是 :cpp:func:`twai_node_disable`,该函数将立即停止节点工作并与总线断开,正在进行的传输将被中止。当下次重新启动时,如果发送队列中有未完成的任务,驱动将立即发起新的传输。
发送报文
^^^^^^^^
TWAI 报文有多种类型,由报头指定。一个典型的数据帧报文主要包括报头和数据,大概结构如下:
.. image:: ../../../_static/diagrams/twai/frame_struct.svg
:align: center
为减少拷贝带来的性能损失TWAI 驱动使用指针进行传递。以下代码展示了如何发送一条典型的数据帧报文:
.. code:: c
uint8_t send_buff[8] = {0};
twai_frame_t tx_msg = {
.header.id = 0x1, // 报文ID
.header.ide = true, // 29 位扩展ID格式
.buffer = send_buff, // 发送数据的地址
.buffer_len = sizeof(send_buff), // 发送数据的长度
};
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_msg, 0)); // 超时为0队列满则直接返回超时
其中 :cpp:member:`twai_frame_t::header::id` 指示了该文的 ID 为 0x01。报文的 ID 通常用于表示报文在应用中的类型,并在发送过程中起到总线竞争仲裁的作用,其数值越小,在总线上的优先级越高。:cpp:member:`twai_frame_t::buffer` 则指向要发送数据所在的内存地址,并由 :cpp:member:`twai_frame_t::buffer_len` 给出数据长度。
需要注意的是 :cpp:member:`twai_frame_t::header::dlc` 同样可以指定一个数据帧中数据的长度dlc(data length code) 与具体长度的对应兼容 ISO11898-1 规定。可使用 :cpp:func:`twaifd_dlc2len` / :cpp:func:`twaifd_len2dlc` 进行转换,选择其一即可,如果 dlc 和 buffer_len 都不为 0 ,那他们所代表的长度必须一致。
报文类型 :cpp:type:`twai_frame_t` 中还包括其他的配置参数,如下:
- :cpp:member:`twai_frame_t::dlc` 数据长度代码,经典帧 [0:8] 代表长度 [0:8]FD 格式 [0:15] 代表长度 [0:64]。
- :cpp:member:`twai_frame_t::header::ide` 使用 29 位扩展ID格式。
- :cpp:member:`twai_frame_t::header::rtr` 报文为远程帧,不包含数据段。
- :cpp:member:`twai_frame_t::header::fdf` 报文为 FD 格式,支持最大数据长度 64 字节。
- :cpp:member:`twai_frame_t::header::brs` 发送报文时在数据段使用独立的波特率。
- :cpp:member:`twai_frame_t::header::esi` 对于收到的报文,指示发送节点的错误状态。
接收报文
^^^^^^^^
接收报文必须在接收事件回调中进行,因此,要接收报文需要在控制器启动前注册接收事件回调 :cpp:member:`twai_event_callbacks_t::on_rx_done` ,从而在事件发生时接收报文。以下代码分别展示了如何注册接收事件回调,以及如何在回调中接收报文:
注册接收事件回调(在控制器启动前):
.. code:: c
twai_event_callbacks_t user_cbs = {
.on_rx_done = twai_rx_cb,
};
ESP_ERROR_CHECK(twai_node_register_event_callbacks(node_hdl, &user_cbs, NULL));
在事件中接收报文:
.. code:: c
static bool twai_rx_cb(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx)
{
uint8_t recv_buff[8];
twai_frame_t rx_frame = {
.buffer = recv_buff,
.buffer_len = sizeof(recv_buff),
};
if (ESP_OK == twai_node_receive_from_isr(handle, &rx_frame)) {
// receive ok, do something here
}
return false;
}
同样,驱动使用指针进行传递,因此需要在接收前配置 :cpp:member:`twai_frame_t::buffer` 的指针及其内存长度 :cpp:member:`twai_frame_t::buffer_len`
停止和删除节点
^^^^^^^^^^^^^^
当不再需要使用 TWAI 时,应该调用 :cpp:func:`twai_node_delete` 函数来释放软硬件资源。删除前请确保 TWAI 已经处于停止状态。
进阶功能
--------
在了解了基本用法后,我们可以进一步探索 TWAI 驱动的更多玩法。驱动支持更详细的控制器配置和错误反馈功能,完整的驱动功能图如下:
.. image:: ../../../_static/diagrams/twai/full_flow.drawio.svg
:align: center
位时序自定义
^^^^^^^^^^^^^
和其他异步通信不同的是TWAI 控制器在一个位时间里实际上在进行以 **时间量子Tq** 为单位的计数 / 采样,一个位里的时间量子的数量决定了最终的波特率以及采样点位置。在信号质量较低时时,可以手动更加精准的配置这些时序段以满足要求。位时间里的时间量子分为不同的段,如图所示:
.. image:: ../../../_static/diagrams/twai/bit_timing.svg
:alt: Bit timing configuration
:align: center
其中同步段 sync 固定为 1 ,采样点位于 tseg1 和 tseg2 中间,同步跳变宽度 sjw 确定单个位时间可以为了同步而延长/缩短的最大时间量子数,范围为 [1:tseg2]。时钟源除以预分频 BRP 即为时间量子,所有段的时间总和即为一个位时间。故有如下公式:
- 波特率:
.. math::
\text{bitrate} = \frac{f_{\text{src}}}{\text{brp} \cdot (1 + \text{prop_seg} + \text{tseg}_1 + \text{tseg}_2)}
- 采样点:
.. math::
\text{sample_point} = \frac{1 + \text{prop_seg} + \text{tseg}_1}{1 + \text{prop_seg} + \text{tseg}_1 + \text{tseg}_2}
以下代码展示了在时钟源 80M 时,配置波特率为 500Kbit/s ,采样点为 75% 的具体配置。
.. code:: c
twai_timing_advanced_config_t timing_cfg = {
.brp = 8, // 预分频为 8时间量子 80M/8=1M
.prop_seg = 10,
.tseg_1 = 4,
.tseg_2 = 5,
.sjw = 3,
};
ESP_ERROR_CHECK(twai_node_reconfig_timing(node_hdl, &timing_cfg, NULL)); // 配置仲裁段波特率NULL 表示不配置 FD 数据段波特率
当手动配置这些段时,需要根据具体硬件留意每个段所支持的范围大小。时序配置函数 :cpp:func:`twai_node_reconfig_timing` 可以同时或单独对仲裁段和 FD 数据段时序进行配置,当控制器不支持 FD 格式时,对数据段的配置无效。时序参数 :cpp:type:`twai_timing_advanced_config_t` 中还有一些别的配置参数:
- :cpp:member:`twai_timing_advanced_config_t::clk_src` 时钟源。
- :cpp:member:`twai_timing_advanced_config_t::ssp_offset` 二次采样点相对同步段偏移的时间量子数。
.. note::
外部收发器 **必须在内部连接 TX 与 RX**,以便观察 TX 信号线上的逻辑电平变化。如果没有内部回环TWAI 控制器将会将两个信号线上的逻辑电平差异解析为仲裁丢失或位错误
``brp````prop_seg````tseg_1````tseg_2````sjw`` 的不同组合可以实现相同波特率。用户应考虑 **传播延迟、节点信息处理时间和相位误差** 等因素,根据总线的物理特性进行调整
过滤器配置
^^^^^^^^^^
.. ------------------------------ Configuration --------------------------------
掩码过滤器
""""""""""
API 命名规范
------------
TWAI 控制器硬件可以根据 ID 对报文进行过滤,从而减少软硬件开销使节点更加高效。过滤掉报文的节点 **不会接收到该报文,但仍会应答**
.. note::
{IDF_TARGET_NAME} 包含 {IDF_TARGET_CONFIG_SOC_TWAI_MASK_FILTER_NUM} 个掩码过滤器,报文通过任意一个过滤器即能收到改报文。典型的 TWAI 掩码过滤器通过 ID 和 MASK 配置,其中:
TWAI 驱动程序提供了两套 API。其中一套是无句柄的广泛适用于 IDF v5.2 之前的版本,但仅支持单个 TWAI 硬件控制器;另一套是带句柄的,其函数名称通常以 "v2" 为后缀,并支持任意数量的 TWAI 控制器。这两套 API 可以同时使用,但建议在新项目中使用 "v2" 版本的 API。
- ID 表示期望接收的报文的标准11位或扩展29位ID
- MASK 表示对ID的过滤规则
配置 TWAI 驱动
--------------
- '0' 表示该位忽略,任意值都通过。
- '1' 表示该位需要相等才能通过。
- ID 和 MASK 都为 0 时,即忽略所有位,过滤器接收所有的帧。
- ID 和 MASK 都为最大值 0xFFFFFFFF 表示不接收任何帧。
本节描述了如何配置 TWAI 驱动。
下面代码展示了如何计算 MASK 和配置过滤器:
操作模式
.. code:: c
twai_mask_filter_config_t mfilter_cfg = {
.id = 0x10, // 0b 000 0001 0000
.mask = 0x7f0, // 0b 111 1111 0000 表示高7位严格匹配低4位忽略接收ID为
// 0b 000 0001 xxxx (16进制0x01x)
.is_ext = false, // 不接收扩展ID只接收标准ID
};
ESP_ERROR_CHECK(twai_node_config_mask_filter(node_hdl, 0, &mfilter_cfg)); //配置过滤器0
.. only:: not SOC_TWAI_SUPPORT_FD
双过滤器模式
""""""""""""
{IDF_TARGET_NAME} 支持双过滤器模式,可将硬件配置为并列的两个独立的 16 位掩码过滤器,支持接收更多 ID但当配置为过滤 29 位扩展ID时每个过滤器只能过滤其ID的高 16 位剩余13位不做过滤。以下代码展示了如何借助 :cpp:func:`twai_make_dual_filter` 配置双过滤器模式。
.. code:: c
// filter 1 id/mask 0x020, 0x7f0, receive only std id 0x02x
// filter 2 id/mask 0x013, 0x7f8, receive only std id 0x010~0x017
twai_mask_filter_config_t dual_config = twai_make_dual_filter(0x020, 0x7f0, 0x013, 0x7f8, false); // id1, mask1, id2, mask2, 不接收扩展ID
ESP_ERROR_CHECK(twai_node_config_mask_filter(node_hdl, 0, &dual_config));
.. only:: SOC_TWAI_SUPPORT_FD
范围过滤器
""""""""""
{IDF_TARGET_NAME} 还包含 1 个范围过滤器,与掩码过滤器属并列关系。可以通过 :cpp:func:`twai_node_config_range_filter` 函数直接配置希望接收的 ID 范围。其中:
- 当配置 :cpp:member:`twai_range_filter_config_t::range_low` 为最小值 0 :cpp:member:`twai_range_filter_config_t::range_high` 为最大值 0xFFFFFFFF 表示接收所有报文。
- 配置为无效区间则表示不接收任何报文。
总线错误和恢复
^^^^^^^^^^^^^^
TWAI控制器能够检测由于总线干扰产生的/损坏的不符合帧格式的错误,并规定了一套由发送/接收错误计数器(TEC/REC)实现的故障隔离机制。计数器值决定节点的错误状态,即主动错误、错误警告、被动错误和离线,它可以使持续存在错误的节点最终自行断开与总线的连接。
- **主动错误:** 当 TEC 和 REC 都小于 96 时,节点处于主动错误状态,表示正常运行。可以参与总线通信,检测到错误时发送 **主动错误标志**,主动报告检测到的错误。
- **错误警告:** 当 TEC 或 REC 中的一个大于或等于 96 时,且两个都小于 128 ,节点处于错误警告状态,表示可能存在错误,但行为不变。
- **被动错误:** 当 TEC 或 REC 中的一个大于或等于 128 时,节点处于被动错误状态。仍可以参与总线通信,但在检测到错误时,只能发送一次 **被动错误标志**
- **离线:****TEC** 大于或等于 256 时,节点进入离线状态。离线的节点相当于断开连接,不会对总线产生任何影响。节点将保持离线状态,直到软件触发恢复操作。
软件可随时使用函数 :cpp:func:`twai_node_get_info` 获取节点状态。或当控制器检测到错误时,会产生 :cpp:member:`twai_event_callbacks_t::on_error` 回调,可通过传参中的错误数据查看错误原因。
当错误导致节点状态变化时,会进入 :cpp:member:`twai_event_callbacks_t::on_state_change` 回调可在回调中查看节点的状态变化。若节点已经离线且需要恢复需要在task中调用 :cpp:func:`twai_node_recover`**但注意,控制器不会立即恢复** ,需要在检测到 129 次连续 11 个隐性位后才会自动重新连接到总线。
节点恢复完成时同样进入 :cpp:member:`twai_event_callbacks_t::on_state_change` 回调,状态由 :cpp:enumerator:`TWAI_ERROR_BUS_OFF` 变为 :cpp:enumerator:`TWAI_ERROR_ACTIVE`。恢复完成的节点可以立即进行传输,如果发送队列中有未完成的任务,驱动将立即发起新的传输。
关于低功耗
^^^^^^^^^^
当启用电源管理 :ref:`CONFIG_PM_ENABLE` 时,系统在进入睡眠模式前可能会调整或关闭时钟源,从而导致 TWAI 出错。为了防止这种情况发生,驱动内部使用电源锁管理。当调用 :cpp:func:`twai_node_enable` 函数后,该锁将被激活,确保系统不会进入睡眠模式,从而保持 TWAI 功能正常。如果需要降低功耗,可以调用 :cpp:func:`twai_node_disable` 函数来释放电源管理锁,使系统能够进入睡眠模式,睡眠期间 TWAI 控制器也将停止工作。
关于 Cache 安全
^^^^^^^^^^^^^^^
TWAI 驱动支持以下操作模式:
**正常模式:** 正常模式支持 TWAI 控制器参与总线活动,如传输和接收报文/错误帧。发送报文时需要来自另一个节点的应答。
**无应答模式:** 无应答模式与正常模式类似,但不需要接收方发送应答信号,即使没有应答信号也会视为成功传输。这种模式在 TWAI 控制器(如传输回环)自测时非常有用。
**只听模式:** 此模式防止 TWAI 控制器干扰总线,因此会禁用报文/应答信号/错误帧的传输。但 TWAI 控制器仍然能够接收报文,只是不会应答。这种模式适用于总线监视应用。
报警
^^^^
TWAI 驱动程序包含报警功能,可以对应用层发起特定 TWAI 控制器或 TWAI 总线事件通知。在安装 TWAI 驱动程序时,可以选择启用报警,也可以在运行时通过调用 :cpp:func:`twai_reconfigure_alerts` 重新配置。随后,应用程序可以通过调用 :cpp:func:`twai_read_alerts` 等待任何已启用的报警发生。TWAI 驱动程序支持以下报警:
.. list-table:: TWAI Driver Alerts
:widths: 40 60
:header-rows: 1
* - 报警标志
- 描述
* - ``TWAI_ALERT_TX_IDLE``
- 队列中无待传输报文
* - ``TWAI_ALERT_TX_SUCCESS``
- 上一次传输成功
* - ``TWAI_ALERT_RX_DATA``
- 收到一帧数据并添加到 RX 队列
* - ``TWAI_ALERT_BELOW_ERR_WARN``
- 两个错误计数器都低于错误报警限制
* - ``TWAI_ALERT_ERR_ACTIVE``
- TWAI 控制器已进入主动错误状态
* - ``TWAI_ALERT_RECOVERY_IN_PROGRESS``
- TWAI 控制器正在进行离线恢复
* - ``TWAI_ALERT_BUS_RECOVERED``
- TWAI 控制器已成功完成离线恢复
* - ``TWAI_ALERT_ARB_LOST``
- 上一次传输丢失仲裁
* - ``TWAI_ALERT_ABOVE_ERR_WARN``
- 有错误计数器超过了错误报警限制
* - ``TWAI_ALERT_BUS_ERROR``
- 总线上发生了位、填充、CRC、格式、ACK错误
* - ``TWAI_ALERT_TX_FAILED``
- 上一次传输失败
* - ``TWAI_ALERT_RX_QUEUE_FULL``
- RX 队列已满,接收到的帧丢失
* - ``TWAI_ALERT_ERR_PASS``
- TWAI 控制器已进入被动错误状态
* - ``TWAI_ALERT_BUS_OFF``
- 离线条件已触发TWAI 控制器无法干扰总线
在进行 Flash 写操作时,为了避免 Cache 从 Flash 加载指令和数据时出现错误,系统会暂时禁用 Cache 功能。这会导致存放在 Flash 上的中断处理程序在此期间无法响应。如果希望在 Cache 被禁用期间,中断处理程序仍能正常运行,可以启用 :ref:`CONFIG_TWAI_ISR_CACHE_SAFE` 选项。
.. note::
TWAI 控制器的 **错误报警限制** 用于在被动错误状态之前预先提醒应用程序发生了总线错误。TWAI 驱动程序将 **错误报警限制** 默认设置为 **96**。当 TEC 或 REC 大于或等于错误报警限制时,将引发报警 ``TWAI_ALERT_ABOVE_ERR_WARN``。当 TEC 和 REC 都返回到小于 **96** 的值时,将引发报警 ``TWAI_ALERT_BELOW_ERR_WARN``
请注意,在启用该选项后,所有的中断回调函数及其上下文数据 **必须存放在内部存储空间** 中。因为在 Cache 被禁用时,系统无法从 Flash 中加载数据和指令。
关于线程安全
^^^^^^^^^^^^^
驱动程序可保证所有公开的 TWAI API 的线程安全,使用时,可以直接从不同的 RTOS 任务中调用此类 API无需额外锁保护。
关于性能
^^^^^^^^
为了提升中断处理的实时响应能力, 驱动提供了 :ref:`CONFIG_TWAI_ISR_IN_IRAM` 选项。启用该选项后,中断处理程序将被放置在内部 RAM 中运行,从而减少了从 Flash 加载指令带来的延迟。
.. note::
启用错误报警时,可以使用 ``TWAI_ALERT_AND_LOG`` 标志,让 TWAI 驱动程序把所有报警都记录到 UART。但是如果启用了 :ref:`CONFIG_TWAI_ISR_IN_IRAM` 选项,则会禁用报警记录和 ``TWAI_ALERT_AND_LOG``。请参阅 :ref:`placing-isr-into-iram`
但是,中断处理程序调用的用户回调函数和用户上下文数据仍然可能位于 Flash 中,延迟问题还是会存在,这需要用户自己将回调函数和数据放入内部 RAM 中,比如使用 :c:macro:`IRAM_ATTR`:c:macro:`DRAM_ATTR`
.. note::
关于资源消耗
^^^^^^^^^^^^
``TWAI_ALERT_ALL````TWAI_ALERT_NONE`` 宏也可在配置或重新配置期间,启用或禁用所有报警。
使用 :doc:`/api-guides/tools/idf-size` 工具可以查看 TWAI 驱动的 Flash 和内存空间消耗。以下是测试条件(以 ESP32-C6 为例):
位时序
^^^^^^
- 编译器优化等级设置为 ``-Os``,以确保代码尺寸最小化。
- 默认日志等级设置为 ``ESP_LOG_INFO``,以平衡调试信息和性能。
- 关闭以下驱动优化选项:
可以使用结构体 :cpp:type:`twai_timing_config_t` 配置 TWAI 驱动程序运行的位速率,每个位的周期由多个 **时间定额** 组成TWAI 控制器源时钟的预分频时钟确定 **时间定额** 的周期。单个位按顺序包含以下部分:
- :ref:`CONFIG_TWAI_ISR_IN_IRAM` - 中断处理程序不放入 IRAM。
- :ref:`CONFIG_TWAI_ISR_CACHE_SAFE` - 不启用 Cache 安全选项。
1. **同步段** 由一个时间定额组成
2. **时序段 1** 在采样点之前由 1 到 16 个时间定额组成
3. **时序段 2** 在采样点之后由 1 到 8 个时间定额组成
**注意,以下数据仅供参考,不是精确值,在不同芯片上会有所出入。**
{IDF_TARGET_MAX_BRP:default="32768", esp32="128", esp32s3="16384", esp32c3="16384"}
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash | .rodata | .text |
+=================+============+=======+======+=======+=======+=======+=========+=======+
| driver | 7262 | 12 | 12 | 0 | 0 | 7250 | 506 | 6744 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| hal | 1952 | 0 | 0 | 0 | 0 | 0 | 0 | 1952 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| soc | 64 | 0 | 0 | 0 | 0 | 64 | 64 | 0 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
**波特率分频器 (BRP)** 通过对 TWAI 控制器的源时钟分频,确定每个时间定额的周期。在 {IDF_TARGET_NAME} 上,``brp`` 可以是 **从 2 到 {IDF_TARGET_MAX_BRP} 的任何偶数**。也可以将 :cpp:member:`twai_timing_config_t::quanta_resolution_hz` 设置为非零值,决定各时间定额的分辨率。此时,驱动程序即可计算底层 ``brp`` 值。此方法适用于需要设置不同的时钟源,但希望位速率保持不变的情况
打开 :ref:`CONFIG_TWAI_ISR_IN_IRAM` 优化选项的消耗情况
TWAI 控制器支持的时钟源请参阅 :cpp:type:`twai_clock_source_t`,可以在 :cpp:member:`twai_timing_config_t::clk_src` 中指定时钟源。
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| Component Layer | Total Size | DIRAM | .bss | .data | .text | Flash | .rodata | .text |
+=================+============+=======+======+=======+=======+=======+=========+=======+
| driver | 7248 | 692 | 12 | 0 | 680 | 6556 | 506 | 6050 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| hal | 1952 | 1030 | 0 | 0 | 1030 | 922 | 0 | 922 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
| soc | 64 | 0 | 0 | 0 | 0 | 0 | 64 | 0 |
+-----------------+------------+-------+------+-------+-------+-------+---------+-------+
.. only:: esp32
此外,每一个 TWAI 句柄会从 heap 中动态申请约 ``168`` + 4 * :cpp:member:`twai_onchip_node_config_t::tx_queue_depth` 字节的内存。
对于 v2.0 及更高芯片版本的 ESP32``brp`` **还支持 132 到 256 之间的任何 4 的倍数**,可以将 :ref:`CONFIG_ESP32_REV_MIN` 设置为 v2.0 或更高版本以启用。
其他 Kconfig 选项
^^^^^^^^^^^^^^^^^
.. packetdiag:: ../../../_static/diagrams/twai/bit_timing.diag
:caption: BRP = 8、时钟源频率为 80 MHz 时,位时序配置为 500 Kbit/s
:align: center
- :ref:`CONFIG_TWAI_ENABLE_DEBUG_LOG` 选项允许强制启用 TWAI 驱动的所有调试日志,无论全局日志级别设置如何。启用此选项可以帮助开发人员在调试过程中获取更详细的日志信息,从而更容易定位和解决问题。
数据位的采样点位于时序段 1 和时序段 2 的交汇处,启用 **三重采样** 会导致每个位采样 3 个时间定额,而不是 1 个,额外的采样点位于时序段 1 尾部。
**同步跳变宽度 (SJW)** 用于确定单个位时间可以为了同步而延长/缩短的最大时间定额数,``sjw`` 可以在 1 到 4 之间。
.. note::
``brp````tseg_1````tseg_2````sjw`` 的不同组合可以实现相同位速率。用户应考虑 **传播延迟、节点信息处理时间和相位误差** 等因素,根据总线的物理特性进行调整。
常用的位速率时序可以使用 **初始化宏**。以下是 TWAI 驱动程序提供的一些初始化宏。
应用示例
--------
.. list::
- :c:macro:`TWAI_TIMING_CONFIG_1MBITS`
- :c:macro:`TWAI_TIMING_CONFIG_800KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_500KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_250KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_125KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_100KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_50KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_25KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_20KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_16KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_12_5KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_10KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_5KBITS`
:not esp32: - :c:macro:`TWAI_TIMING_CONFIG_1KBITS`
.. only:: esp32
v2.0 或更高芯片版本的 ESP32 还支持以下位速率:
- :c:macro:`TWAI_TIMING_CONFIG_20KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_16KBITS`
- :c:macro:`TWAI_TIMING_CONFIG_12_5KBITS`
接收过滤器
^^^^^^^^^^
TWAI 控制器内置硬件接收过滤器,可以过滤特定 ID 的报文。过滤掉报文的节点 **不会接收到该报文,但仍会应答**。接收过滤器通过过滤掉总线上与节点无关的报文,使节点更加高效。接收过滤器使用在 :cpp:type:`twai_filter_config_t` 中的两个 32 位值配置,分别称为 **接收码****接收掩码**
**接收码** 指定报文的 ID、RTR 和数据字节必须匹配的位序列,使报文可以由 TWAI 控制器接收。**接收掩码** 是一个位序列,指定接受码中可以忽略的位,从而实现用单个接收码接受不同 ID 的报文。
接收过滤器可以在 **单过滤器模式或双过滤器模式** 下使用。单过滤器模式使用接收代码和掩码定义一个过滤器,支持筛选标准帧的前两个数据字节,或扩展帧的 29 位 ID 的全部内容。以下图示说明了在单过滤器模式下解析 32 位接收代码和掩码的方式。注意:黄色和蓝色字段分别表示标准和扩展帧格式。
.. packetdiag:: ../../../_static/diagrams/twai/acceptance_filter_single.diag
:caption: 单过滤器模式的位布局(右侧为最高有效位)
:align: center
**双过滤器模式** 使用接收代码和掩码定义两个单独的过滤器,支持接收更多 ID但不支持筛选扩展 ID 的全部 29 位。以下图示说明了在 **双过滤器模式** 下解析 32 位接收代码和掩码的方式。注意:黄色和蓝色字段分别表示标准和扩展帧格式。
.. packetdiag:: ../../../_static/diagrams/twai/acceptance_filter_dual.diag
:caption: 双过滤器模式的位布局(右侧为最高有效位)
:align: center
禁用 TX 队列
^^^^^^^^^^^^
可以将 :cpp:type:`twai_general_config_t` 结构体的 ``tx_queue_len`` 成员设置为 ``0``,在配置期间禁用 TX 队列。使用 TWAI 驱动程序时,禁用 TX 队列可以为不需要报文传输的应用程序节省一小部分内存。
.. _placing-isr-into-iram:
将 ISR 存入 IRAM
^^^^^^^^^^^^^^^^
TWAI 驱动程序的 ISR中断服务程序可以存入 IRAM这样可以在禁用 cache 时运行 ISR。长时间禁用 cache 时(例如 SPI flash 写操作、OTA 更新等),可能需要将 ISR 存入 IRAM才能确保 TWAI 驱动程序的功能。禁用 cache 时ISR 继续执行以下操作:
- 从 RX buffer 读取接收到的报文,并将它们存入驱动程序的 RX 队列。
- 从驱动程序的 TX 队列中加载待传输的报文,并将它们写入 TX buffer。
将 TWAI 驱动程序的 ISR 存入 IRAM必须执行以下操作
- 使用 ``idf.py menuconfig`` 启用 :ref:`CONFIG_TWAI_ISR_IN_IRAM` 选项。
- 调用 :cpp:func:`twai_driver_install` 时,:cpp:type:`twai_general_config_t` 的成员 ``intr_flags`` 应设置为 :c:macro:`ESP_INTR_FLAG_IRAM`
.. note::
启用 :ref:`CONFIG_TWAI_ISR_IN_IRAM` 选项时TWAI 驱动程序将不再记录报警,即 ``TWAI_ALERT_AND_LOG`` 标志失效。
.. only:: esp32
ESP32 芯片错误变通方案
^^^^^^^^^^^^^^^^^^^^^^
ESP32 的 TWAI 控制器有多个硬件错误,详情请参阅 `ESP32 系列芯片勘误表 <https://www.espressif.com/sites/default/files/documentation/eco_and_workarounds_for_bugs_in_esp32_cn.pdf>`_。其中一些错误至关重要,在特定情况下,可能会将 TWAI 控制器置于不可逆转的状态,即控制器在 CPU 重置它前一直卡住。
TWAI 驱动程序为这些关键错误提供了变通方案,尽管可能降低性能,但可以使 ESP32 TWAI 驱动程序正常运行。性能下降可能造成以下影响,具体取决于遇到的错误:
- TWAI 驱动程序间歇丢弃收到的报文。
- TWAI 驱动程序可能在短时间内无响应,即不会在 11 位时间或更长时间内传输或发送应答信号。
- 如果启用了 :ref:`CONFIG_TWAI_ISR_IN_IRAM`,该变通方案可能增加约 1 KB 的 IRAM 使用量。
此软件变通方案默认启用,建议保持其启用状态。
.. ------------------------------- TWAI Driver ---------------------------------
驱动程序操作
------------
TWAI 驱动程序经设计,具有明确定义的状态和严格的规则,规定了触发状态转换的函数或条件。下图展示了各种状态及其转换。
.. blockdiag:: ../../../_static/diagrams/twai/state_transition.diag
:caption: TWAI 驱动程序状态转换图(请参阅下表)
:align: center
.. list-table::
:widths: 20 40 40
:header-rows: 1
* - 标签
- 转换
- 行为/条件
* - A
- 未安装 -> 已停止
- :cpp:func:`twai_driver_install`
* - B
- 已停止 -> 未安装
- :cpp:func:`twai_driver_uninstall`
* - C
- 已停止 -> 运行中
- :cpp:func:`twai_start`
* - D
- 运行中 -> 已停止
- :cpp:func:`twai_stop`
* - E
- 运行中 -> 离线
- 传输错误计数 >= 256
* - F
- 离线 -> 未安装
- :cpp:func:`twai_driver_uninstall`
* - G
- 离线 -> 恢复中
- :cpp:func:`twai_initiate_recovery`
* - H
- 恢复中 -> 已停止
- 11 个连续的隐性位出现了 128 次
驱动程序状态
^^^^^^^^^^^^
**未安装**:在此状态下,不会为驱动程序分配任何内存,且 TWAI 控制器处于掉电状态。
**已停止**在此状态下TWAI 控制器已上电,且 TWAI 驱动程序已安装。但 TWAI 控制器无法参与任何总线活动,如传输、接收或确认报文。
**运行中**在此状态下TWAI 控制器能够参与总线活动,因此可以传输/接收/应答报文。此外TWAI 控制器能够在检测到总线上的错误时传输错误帧。
**离线**TWAI 控制器的传输错误计数器计数大于或等于 256 时,将自动进入离线状态。离线状态表示总线或 TWAI 控制器上发生严重错误。在离线状态下TWAI 控制器无法参与任何总线活动。退出离线状态前TWAI 控制器必须进行离线恢复。
**恢复中**TWAI 控制器进行离线恢复时将进入恢复中状态。在此状态下TWAI 控制器/TWAI 驱动程序将保持状态,直到在总线上检测到 128 次连续 11 个隐性位。
报文字段和标志
^^^^^^^^^^^^^^
TWAI 驱动程序通过 :cpp:type:`twai_message_t` 结构体的不同位字段成员区分不同类型的报文。这些位字段成员决定了报文是标准格式还是扩展格式、是否是远程帧以及在传输时要使用的传输类型。
这些位字段成员还可以使用 :cpp:type:`twai_message_t```flags`` 成员以及以下报文标志切换:
.. list-table::
:widths: 30 70
:header-rows: 1
* - 报文标志
- 描述
* - ``TWAI_MSG_FLAG_EXTD``
- 报文采用扩展帧格式29 位 ID
* - ``TWAI_MSG_FLAG_RTR``
- 报文为远程帧(远程传输请求)
* - ``TWAI_MSG_FLAG_SS``
- 使用单次发送传输报文,即报文不会在出现错误或仲裁丢失时重新传输(不用于接收报文)
* - ``TWAI_MSG_FLAG_SELF``
- 使用自接收请求传输报文,即传输的报文将由同一节点接收(不用于接收报文)
* - ``TWAI_MSG_FLAG_DLC_NON_COMP``
- 报文的数据长度代码大于 8不符合 TWAI 的规定
* - ``TWAI_MSG_FLAG_NONE``
- 清除所有位字段等同于标准帧格式11 位 ID数据帧
.. -------------------------------- Examples -----------------------------------
示例
----
配置及安装
^^^^^^^^^^
以下代码片段展示了如何使用各种配置结构体、初始化宏、:cpp:func:`twai_driver_install` 函数和 :cpp:func:`twai_start` 函数,来配置、安装和启动 TWAI 驱动程序。
.. code-block:: c
#include "driver/gpio.h"
#include "driver/twai.h"
void app_main()
{
// 使用初始化宏初始化配置结构体
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_21, GPIO_NUM_22, TWAI_MODE_NORMAL);
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// 安装 TWAI 驱动程序
if (twai_driver_install(&g_config, &t_config, &f_config) == ESP_OK) {
printf("Driver installed\n");
} else {
printf("Failed to install driver\n");
return;
}
// 启动 TWAI 驱动程序
if (twai_start() == ESP_OK) {
printf("Driver started\n");
} else {
printf("Failed to start driver\n");
return;
}
...
}
初始化宏并非强制的,每个配置结构体都可以手动完成。
安装多个 TWAI 实例
^^^^^^^^^^^^^^^^^^
**注意:** {IDF_TARGET_NAME} 包含 {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} 个 TWAI 控制器,因此可创建 {IDF_TARGET_CONFIG_SOC_TWAI_CONTROLLER_NUM} 个 TWAI 实例。
以下代码片段演示了如何使用 :cpp:func:`twai_driver_install_v2` 函数来安装多个 TWAI 实例。
.. code-block:: c
#include "driver/gpio.h"
#include "driver/twai.h"
void app_main()
{
twai_handle_t twai_bus_0;
twai_handle_t twai_bus_1;
// 使用宏初始化器初始化配置结构体
twai_general_config_t g_config = TWAI_GENERAL_CONFIG_DEFAULT(GPIO_NUM_0, GPIO_NUM_1, TWAI_MODE_NORMAL);
twai_timing_config_t t_config = TWAI_TIMING_CONFIG_500KBITS();
twai_filter_config_t f_config = TWAI_FILTER_CONFIG_ACCEPT_ALL();
// 安装 TWAI 总线 0 的驱动程序
g_config.controller_id = 0;
if (twai_driver_install_v2(&g_config, &t_config, &f_config, &twai_bus_0) == ESP_OK) {
printf("Driver installed\n");
} else {
printf("Failed to install driver\n");
return;
}
// 启动 TWAI 驱动程序
if (twai_start_v2(twai_bus_0) == ESP_OK) {
printf("Driver started\n");
} else {
printf("Failed to start driver\n");
return;
}
// 安装 TWAI 总线 1 的驱动程序
g_config.controller_id = 1;
g_config.tx_io = GPIO_NUM_2;
g_config.rx_io = GPIO_NUM_3;
if (twai_driver_install_v2(&g_config, &t_config, &f_config, &twai_bus_1) == ESP_OK) {
printf("Driver installed\n");
} else {
printf("Failed to install driver\n");
return;
}
// 启动 TWAI 驱动程序
if (twai_start_v2(twai_bus_1) == ESP_OK) {
printf("Driver started\n");
} else {
printf("Failed to start driver\n");
return;
}
// 其他驱动程序操作也必须使用 _v2 版本的 API
...
}
报文传输
^^^^^^^^
以下代码片段展示了如何使用 :cpp:type:`twai_message_t` 类型和 :cpp:func:`twai_transmit` 函数传输报文。
.. code-block:: c
#include "driver/twai.h"
...
// 配置要传输的报文
twai_message_t message = {
// 设置报文类型及格式
.extd = 1, // 标准格式或是扩展格式
.rtr = 0, // 数据帧或是远程传输请求帧
.ss = 0, // 报文是否为单次发送(即,在报错时不重复发送)
.self = 0, // 报文是否为自收发(回环)
.dlc_non_comp = 0, // 数据长度代码小于 8
// 报文 ID 及有效载荷
.identifier = 0xAAAA,
.data_length_code = 4,
.data = {0, 1, 2, 3},
};
// 报文排队等待传输
if (twai_transmit(&message, pdMS_TO_TICKS(1000)) == ESP_OK) {
printf("Message queued for transmission\n");
} else {
printf("Failed to queue message for transmission\n");
}
报文接收
^^^^^^^^
以下代码片段展示了如何使用 :cpp:type:`twai_message_t` 类型和 :cpp:func:`twai_receive` 函数接收报文。
.. code-block:: c
#include "driver/twai.h"
...
// 等待报文接收
twai_message_t message;
if (twai_receive(&message, pdMS_TO_TICKS(10000)) == ESP_OK) {
printf("Message received\n");
} else {
printf("Failed to receive message\n");
return;
}
// 处理接收到的报文
if (message.extd) {
printf("Message is in Extended Format\n");
} else {
printf("Message is in Standard Format\n");
}
printf("ID is %d\n", message.identifier);
if (!(message.rtr)) {
for (int i = 0; i < message.data_length_code; i++) {
printf("Data byte %d = %d\n", i, message.data[i]);
}
}
重新配置并读取报警
^^^^^^^^^^^^^^^^^^
以下代码片段展示了如何使用 :cpp:func:`twai_reconfigure_alerts`:cpp:func:`twai_read_alerts` 函数重新配置和读取 TWAI 驱动程序的报警。
.. code-block:: c
#include "driver/twai.h"
...
// 重新配置报警,检测被动错误和离线状态
uint32_t alerts_to_enable = TWAI_ALERT_ERR_PASS | TWAI_ALERT_BUS_OFF;
if (twai_reconfigure_alerts(alerts_to_enable, NULL) == ESP_OK) {
printf("Alerts reconfigured\n");
} else {
printf("Failed to reconfigure alerts");
}
// 阻塞直到发生报警
uint32_t alerts_triggered;
twai_read_alerts(&alerts_triggered, portMAX_DELAY);
停止和卸载
^^^^^^^^^^
以下代码片段展示了如何使用 :cpp:func:`twai_stop`:cpp:func:`twai_driver_uninstall` 函数停止和卸载 TWAI 驱动程序。
.. code-block:: c
#include "driver/twai.h"
...
// 停止 TWAI 驱动程序
if (twai_stop() == ESP_OK) {
printf("Driver stopped\n");
} else {
printf("Failed to stop driver\n");
return;
}
// 卸载 TWAI 驱动程序
if (twai_driver_uninstall() == ESP_OK) {
printf("Driver uninstalled\n");
} else {
printf("Failed to uninstall driver\n");
return;
}
配置多个 ID 过滤器
^^^^^^^^^^^^^^^^^^
:cpp:type:`twai_filter_config_t` 中的接收掩码可以配置,使单过滤器接收两个或多个 ID。为了使特定过滤器接受多个 ID必须在接收掩码中设置不同 ID 的冲突位。接收代码可以设置为这些 ID 中的任何一个。
以下示例展示了如何计算多个 ID 的接收掩码:
.. code-block::
ID1 = 11'b101 1010 0000
ID2 = 11'b101 1010 0001
ID3 = 11'b101 1010 0100
ID4 = 11'b101 1010 1000
// 接收掩码
MASK = 11'b000 0000 1101
应用示例
^^^^^^^^
**网络示例:** :example:`peripherals/twai/twai_network` 演示了如何通过 TWAI 驱动程序 API 在两个 {IDF_TARGET_NAME} 之间进行通信。其中一个 TWAI 节点为网络主节点,负责启动和终止与另一个网络从节点之间的数据传输。
**报警和恢复示例:** :example:`peripherals/twai/twai_alert_and_recovery` 演示了如何在 {IDF_TARGET_NAME} 上使用 TWAI 驱动程序的报警和离线恢复 API。通过初始化驱动程序该示例创建消息传输和警报处理任务触发比特错误进入 Bus-Off 离线状态,报警检测离线状态,并触发 Bus-Off 离线恢复过程。
**自测示例:** :example:`peripherals/twai/twai_self_test` 演示了节点如何使用 TWAI 驱动程序的无应答模式和自接收请求,向自身传输 TWAI 消息。此示例可用于测试目标芯片与外部收发器之间的连接是否正常。
.. only:: SOC_TWAI_SUPPORT_SLEEP_RETENTION
睡眠保留
^^^^^^^^
{IDF_TARGET_NAME} 支持在进入 **Light Sleep** 之前保留 TWAI 寄存器中的内容,并在唤醒后恢复。即程序不需要在 **Light Sleep** 唤醒后重新配置 TWAI
该特性可以通过置位配置中的 :cpp:member:`twai_general_config_t::sleep_allow_pd` 标志位启用。启用后驱动允许系统在 Light Sleep 时对 TWAI 掉电,同时保存寄存器配置。它可以帮助降低轻度睡眠时的功耗,但需要花费一些额外的存储来保存寄存器的配置。
.. ---------------------------- API Reference ----------------------------------
- 暂无
API 参考
-------------
--------
.. include-build-file:: inc/twai_types_deprecated.inc
.. include-build-file:: inc/twai.inc
.. include-build-file:: inc/esp_twai_onchip.inc
.. include-build-file:: inc/esp_twai.inc
.. include-build-file:: inc/esp_twai_types.inc
.. include-build-file:: inc/twai_types.inc

View File

@ -25,3 +25,37 @@ Peripherals
- 新的触摸驱动实现了有限状态机FSM以确保正确的触摸传感器操作。请参阅编程指南 :doc:`电容式触摸传感器 <../../../api-reference/peripherals/cap_touch_sens>` 了解更多详情。
- 所有新的API都保证是线程安全的。
- 为触摸传感器 V1ESP32实现了软件滤波器并开放自定义滤波器接口。
.. only:: SOC_TWAI_SUPPORTED
双线汽车接口 (TWAI)
--------------------
TWAI 提供一套全新驱动接口以支持更多更方便的功能,请参考编程指南 :doc:`双线汽车接口 (TWAI)<../../../api-reference/peripherals/twai>`.
相较于老驱动,新版驱动主要亮点有:
- 事件驱动,支持注册多种回调函数
- 支持运行时更改波特率、过滤器配置
- 提供更友好直观的波特率配置、过滤器配置方法
- 支持 ESP32C5 及其 FDFlexible Datarate, 灵活数据速率)功能
- 支持多个 TWAI 控制器同时使用
新驱动位于 :component:`esp_driver_twai` 组件中,添加该组件依赖即可使用。包含头文件为 :component_file:`esp_driver_twai/include/esp_twai.h` 和 :component_file:`esp_driver_twai/include/esp_twai_onchip.h`
+---------------------------+----------------------------+---------------------------------------------------------------------+
| 删除/弃用项目 | 替代 | 备注 |
+===========================+============================+=====================================================================+
| twai_read_alerts | 无 | 需要注册 on_state_change 和 on_error 回调来获取状态转换和在错误信息 |
+---------------------------+----------------------------+---------------------------------------------------------------------+
| twai_receive | twai_node_receive_from_isr | 只能在 on_rx_done 回调中使用该函数 |
+---------------------------+----------------------------+---------------------------------------------------------------------+
| twai_clear_transmit_queue | 无 | 暂不支持 |
+---------------------------+----------------------------+---------------------------------------------------------------------+
| twai_clear_receive_queue | 无 | receive 是事件驱动的,暂不提供阻塞版本的函数 |
+---------------------------+----------------------------+---------------------------------------------------------------------+
尽管我们推荐使用新的驱动 API但旧版驱动仍然可用其头文件引用路径为 ``driver/twai.h``。继续使用旧版驱动请注意:
- 新旧驱动不互相兼容,不可混合使用,否则将在启动时打印警告,甚至崩溃并重启。可打开配置项 :ref:`CONFIG_TWAI_SKIP_LEGACY_CONFLICT_CHECK` 以屏蔽该检查。
- 旧版驱动将不再添加新特性(如 TWAIFD的支持。