mirror of
https://github.com/espressif/esp-idf.git
synced 2025-10-02 18:10:57 +02:00
Merge branch 'features/twai_send_isr' into 'master'
features(twai): Added support for calling twai_node_transmit() from ISR contexts See merge request espressif/esp-idf!41383
This commit is contained in:
@@ -8,6 +8,13 @@ menu "ESP-Driver:TWAI Configurations"
|
||||
help
|
||||
Place the TWAI ISR in to IRAM to reduce latency and increase performance
|
||||
|
||||
config TWAI_IO_FUNC_IN_IRAM
|
||||
bool "Place TWAI I/O functions in IRAM"
|
||||
select TWAI_OBJ_CACHE_SAFE
|
||||
default n
|
||||
help
|
||||
Place certain TWAI I/O functions (like twai_transmit) in IRAM to reduce latency
|
||||
|
||||
config TWAI_ISR_CACHE_SAFE
|
||||
bool "Allow TWAI ISR execute when cache disabled" if !SPI_FLASH_AUTO_SUSPEND
|
||||
select TWAI_ISR_IN_IRAM
|
||||
|
@@ -136,8 +136,8 @@ esp_err_t twai_node_get_info(twai_node_handle_t node, twai_node_status_t *status
|
||||
|
||||
esp_err_t twai_node_transmit(twai_node_handle_t node, const twai_frame_t *frame, int timeout_ms)
|
||||
{
|
||||
ESP_RETURN_ON_FALSE(node && frame, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null");
|
||||
ESP_RETURN_ON_FALSE(node->transmit, ESP_ERR_NOT_SUPPORTED, TAG, "transmit is not supported");
|
||||
ESP_RETURN_ON_FALSE_ISR(node && frame, ESP_ERR_INVALID_ARG, TAG, "invalid argument: null");
|
||||
ESP_RETURN_ON_FALSE_ISR(node->transmit, ESP_ERR_NOT_SUPPORTED, TAG, "transmit is not supported");
|
||||
|
||||
return node->transmit(node, frame, timeout_ms);
|
||||
}
|
||||
|
@@ -582,15 +582,15 @@ static esp_err_t _node_queue_tx(twai_node_handle_t node, const twai_frame_t *fra
|
||||
{
|
||||
twai_onchip_ctx_t *twai_ctx = __containerof(node, twai_onchip_ctx_t, api_base);
|
||||
if (frame->header.dlc && frame->buffer_len) {
|
||||
ESP_RETURN_ON_FALSE(frame->header.dlc == twaifd_len2dlc(frame->buffer_len), ESP_ERR_INVALID_ARG, TAG, "unmatched dlc and buffer_len");
|
||||
ESP_RETURN_ON_FALSE_ISR(frame->header.dlc == twaifd_len2dlc(frame->buffer_len), ESP_ERR_INVALID_ARG, TAG, "unmatched dlc and buffer_len");
|
||||
}
|
||||
#if !SOC_TWAI_SUPPORT_FD
|
||||
ESP_RETURN_ON_FALSE(!frame->header.fdf || frame->buffer_len <= TWAI_FRAME_MAX_LEN, ESP_ERR_INVALID_ARG, TAG, "fdf flag or buffer_len not supported");
|
||||
ESP_RETURN_ON_FALSE_ISR(!frame->header.fdf || frame->buffer_len <= TWAI_FRAME_MAX_LEN, ESP_ERR_INVALID_ARG, TAG, "fdf flag or buffer_len not supported");
|
||||
#endif
|
||||
ESP_RETURN_ON_FALSE(frame->buffer_len <= (frame->header.fdf ? TWAIFD_FRAME_MAX_LEN : TWAI_FRAME_MAX_LEN), ESP_ERR_INVALID_ARG, TAG, "illegal transfer length (buffer_len %ld)", frame->buffer_len);
|
||||
ESP_RETURN_ON_FALSE((!frame->header.brs) || (twai_ctx->valid_fd_timing), ESP_ERR_INVALID_ARG, TAG, "brs can't be used without config data_timing");
|
||||
ESP_RETURN_ON_FALSE(!twai_ctx->hal->enable_listen_only, ESP_ERR_NOT_SUPPORTED, TAG, "node is config as listen only");
|
||||
ESP_RETURN_ON_FALSE(atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "node is bus off");
|
||||
ESP_RETURN_ON_FALSE_ISR(frame->buffer_len <= (frame->header.fdf ? TWAIFD_FRAME_MAX_LEN : TWAI_FRAME_MAX_LEN), ESP_ERR_INVALID_ARG, TAG, "illegal transfer length (buffer_len %ld)", frame->buffer_len);
|
||||
ESP_RETURN_ON_FALSE_ISR((!frame->header.brs) || (twai_ctx->valid_fd_timing), ESP_ERR_INVALID_ARG, TAG, "brs can't be used without config data_timing");
|
||||
ESP_RETURN_ON_FALSE_ISR(!twai_ctx->hal->enable_listen_only, ESP_ERR_NOT_SUPPORTED, TAG, "node is config as listen only");
|
||||
ESP_RETURN_ON_FALSE_ISR(atomic_load(&twai_ctx->state) != TWAI_ERROR_BUS_OFF, ESP_ERR_INVALID_STATE, TAG, "node is bus off");
|
||||
TickType_t ticks_to_wait = (timeout == -1) ? portMAX_DELAY : pdMS_TO_TICKS(timeout);
|
||||
|
||||
xEventGroupClearBits(twai_ctx->event_group, TWAI_IDLE_EVENT_BIT); //going to send, clear the idle event
|
||||
@@ -599,15 +599,38 @@ static esp_err_t _node_queue_tx(twai_node_handle_t node, const twai_frame_t *fra
|
||||
twai_ctx->p_curr_tx = frame;
|
||||
_node_start_trans(twai_ctx);
|
||||
} else {
|
||||
//options in following steps (in_queue->2nd_check->pop_queue) should exec ASAP
|
||||
//within about 50us (minimum time for one msg), to ensure data safe
|
||||
// Hardware busy, need to queue the frame
|
||||
BaseType_t is_isr_context = xPortInIsrContext();
|
||||
BaseType_t yield_required = pdFALSE;
|
||||
|
||||
if (is_isr_context) {
|
||||
// In ISR context - use ISR-safe queue operations
|
||||
ESP_RETURN_ON_FALSE_ISR(xQueueSendFromISR(twai_ctx->tx_mount_queue, &frame, &yield_required), ESP_ERR_TIMEOUT, TAG, "tx queue full");
|
||||
} else {
|
||||
// In task context - use normal queue operations
|
||||
ESP_RETURN_ON_FALSE(xQueueSend(twai_ctx->tx_mount_queue, &frame, ticks_to_wait), ESP_ERR_TIMEOUT, TAG, "tx queue full");
|
||||
}
|
||||
|
||||
// Second chance check for hardware availability
|
||||
false_var = false;
|
||||
if (atomic_compare_exchange_strong(&twai_ctx->hw_busy, &false_var, true)) {
|
||||
if (xQueueReceive(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, 0) != pdTRUE) {
|
||||
BaseType_t dequeue_result;
|
||||
if (is_isr_context) {
|
||||
dequeue_result = xQueueReceiveFromISR(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, &yield_required);
|
||||
} else {
|
||||
dequeue_result = xQueueReceive(twai_ctx->tx_mount_queue, &twai_ctx->p_curr_tx, 0);
|
||||
}
|
||||
|
||||
if (dequeue_result == pdTRUE) {
|
||||
_node_start_trans(twai_ctx);
|
||||
} else {
|
||||
assert(false && "should always get frame at this moment");
|
||||
}
|
||||
_node_start_trans(twai_ctx);
|
||||
}
|
||||
|
||||
// Handle ISR yield if required
|
||||
if (is_isr_context && yield_required) {
|
||||
portYIELD_FROM_ISR();
|
||||
}
|
||||
}
|
||||
return ESP_OK;
|
||||
|
@@ -7,6 +7,10 @@ entries:
|
||||
esp_twai_onchip: _node_start_trans (noflash)
|
||||
esp_twai_onchip: _node_parse_rx (noflash)
|
||||
|
||||
if TWAI_IO_FUNC_IN_IRAM = y:
|
||||
esp_twai_onchip: _node_queue_tx (noflash)
|
||||
esp_twai: twai_node_transmit (noflash)
|
||||
|
||||
[mapping:twai_hal]
|
||||
archive: libhal.a
|
||||
entries:
|
||||
|
@@ -48,6 +48,8 @@ TEST_CASE("twai install uninstall (loopback)", "[twai]")
|
||||
twai_onchip_node_config_t node_config = {};
|
||||
node_config.io_cfg.tx = TEST_TX_GPIO;
|
||||
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
|
||||
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
|
||||
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
|
||||
node_config.bit_timing.bitrate = 1000000;
|
||||
node_config.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH;
|
||||
node_config.flags.enable_self_test = true;
|
||||
@@ -126,7 +128,7 @@ static void test_twai_baudrate_correctness(twai_clock_source_t clk_src, uint32_t
|
||||
TEST_ESP_OK(uart_detect_bitrate_start(UART_NUM_1, &detect_config));
|
||||
|
||||
twai_frame_t tx_frame = {};
|
||||
tx_frame.header.id = 0x55555;
|
||||
tx_frame.header.id = 0x15555555;
|
||||
tx_frame.header.dlc = 8;
|
||||
tx_frame.header.ide = true;
|
||||
tx_frame.buffer = (uint8_t []) {
|
||||
@@ -183,6 +185,8 @@ TEST_CASE("twai transmit stop resume (loopback)", "[twai]")
|
||||
twai_onchip_node_config_t node_config = {};
|
||||
node_config.io_cfg.tx = TEST_TX_GPIO;
|
||||
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
|
||||
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
|
||||
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
|
||||
node_config.bit_timing.bitrate = 200000;
|
||||
node_config.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH;
|
||||
node_config.flags.enable_self_test = true;
|
||||
@@ -280,6 +284,8 @@ TEST_CASE("twai mask filter (loopback)", "[twai]")
|
||||
twai_onchip_node_config_t node_config = {};
|
||||
node_config.io_cfg.tx = TEST_TX_GPIO;
|
||||
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
|
||||
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
|
||||
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
|
||||
node_config.bit_timing.bitrate = 1000000;
|
||||
node_config.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH;
|
||||
node_config.flags.enable_self_test = true;
|
||||
@@ -363,6 +369,8 @@ TEST_CASE("twai dual 16bit mask filter (loopback)", "[twai]")
|
||||
twai_onchip_node_config_t node_config = {};
|
||||
node_config.io_cfg.tx = TEST_TX_GPIO;
|
||||
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
|
||||
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
|
||||
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
|
||||
node_config.bit_timing.bitrate = 1000000;
|
||||
node_config.tx_queue_depth = TEST_TWAI_QUEUE_DEPTH;
|
||||
node_config.flags.enable_self_test = true;
|
||||
@@ -436,6 +444,8 @@ TEST_CASE("twai driver cache safe (loopback)", "[twai]")
|
||||
twai_onchip_node_config_t node_config = {};
|
||||
node_config.io_cfg.tx = TEST_TX_GPIO;
|
||||
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
|
||||
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
|
||||
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
|
||||
node_config.bit_timing.bitrate = 50000; //slow bitrate to ensure cache disabled before tx_queue finish
|
||||
node_config.tx_queue_depth = TEST_FRAME_NUM;
|
||||
node_config.flags.enable_loopback = true;
|
||||
@@ -577,3 +587,130 @@ TEST_CASE("twai tx_wait_all_done thread safe", "[twai]")
|
||||
TEST_ESP_OK(twai_node_disable(node_hdl));
|
||||
TEST_ESP_OK(twai_node_delete(node_hdl));
|
||||
}
|
||||
|
||||
// Test data for ISR send functionality
|
||||
typedef struct {
|
||||
twai_node_handle_t node;
|
||||
uint32_t rx_count;
|
||||
uint32_t tx_isr_send_count;
|
||||
uint32_t rx_isr_send_count;
|
||||
bool test_completed;
|
||||
} isr_send_test_ctx_t;
|
||||
|
||||
static IRAM_ATTR bool test_tx_isr_send_cb(twai_node_handle_t handle, const twai_tx_done_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
isr_send_test_ctx_t *ctx = (isr_send_test_ctx_t *)user_ctx;
|
||||
|
||||
// Test sending from TX ISR context
|
||||
if (ctx->tx_isr_send_count < 3) {
|
||||
twai_frame_t isr_frame = {};
|
||||
isr_frame.header.id = 0x200 + ctx->tx_isr_send_count;
|
||||
isr_frame.header.dlc = 1;
|
||||
isr_frame.buffer = (uint8_t*)(&ctx->tx_isr_send_count);
|
||||
isr_frame.buffer_len = 1;
|
||||
|
||||
esp_err_t err = twai_node_transmit(handle, &isr_frame, 0); // timeout must be 0 in ISR
|
||||
if (err == ESP_OK) {
|
||||
ctx->tx_isr_send_count++;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static IRAM_ATTR bool test_rx_isr_send_cb(twai_node_handle_t handle, const twai_rx_done_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
isr_send_test_ctx_t *ctx = (isr_send_test_ctx_t *)user_ctx;
|
||||
twai_frame_t rx_frame = {};
|
||||
uint8_t buffer[8];
|
||||
rx_frame.buffer = buffer;
|
||||
rx_frame.buffer_len = sizeof(buffer);
|
||||
|
||||
if (ESP_OK == twai_node_receive_from_isr(handle, &rx_frame)) {
|
||||
ctx->rx_count++;
|
||||
|
||||
// Test sending from RX ISR context (response pattern)
|
||||
if ((rx_frame.header.id >= 0x100) && (rx_frame.header.id < 0x103) && (ctx->rx_isr_send_count < 3)) {
|
||||
twai_frame_t response_frame = {};
|
||||
response_frame.header.id = 0x300 + ctx->rx_isr_send_count;
|
||||
response_frame.header.dlc = 1;
|
||||
response_frame.buffer = (uint8_t*)(&ctx->rx_isr_send_count);
|
||||
response_frame.buffer_len = 1;
|
||||
|
||||
esp_err_t err = twai_node_transmit(handle, &response_frame, 0); // timeout must be 0 in ISR
|
||||
if (err == ESP_OK) {
|
||||
ctx->rx_isr_send_count++;
|
||||
}
|
||||
}
|
||||
|
||||
// Mark test completed when we receive expected frames
|
||||
if (ctx->rx_count >= 9) { // 3 initial + 3 tx_isr + 3 rx_isr responses
|
||||
ctx->test_completed = true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
TEST_CASE("twai send from ISR context (loopback)", "[twai]")
|
||||
{
|
||||
isr_send_test_ctx_t test_ctx = {};
|
||||
|
||||
twai_onchip_node_config_t node_config = {};
|
||||
node_config.io_cfg.tx = TEST_TX_GPIO;
|
||||
node_config.io_cfg.rx = TEST_TX_GPIO; // Using same pin for test without transceiver
|
||||
node_config.io_cfg.quanta_clk_out = GPIO_NUM_NC;
|
||||
node_config.io_cfg.bus_off_indicator = GPIO_NUM_NC;
|
||||
node_config.bit_timing.bitrate = 500000;
|
||||
node_config.tx_queue_depth = 10; // Larger queue for ISR sends
|
||||
node_config.flags.enable_self_test = true;
|
||||
node_config.flags.enable_loopback = true;
|
||||
|
||||
TEST_ESP_OK(twai_new_node_onchip(&node_config, &test_ctx.node));
|
||||
|
||||
twai_event_callbacks_t user_cbs = {};
|
||||
user_cbs.on_rx_done = test_rx_isr_send_cb;
|
||||
user_cbs.on_tx_done = test_tx_isr_send_cb;
|
||||
TEST_ESP_OK(twai_node_register_event_callbacks(test_ctx.node, &user_cbs, &test_ctx));
|
||||
TEST_ESP_OK(twai_node_enable(test_ctx.node));
|
||||
|
||||
printf("Testing ISR context sending...\n");
|
||||
|
||||
// Send initial frames to trigger RX ISR responses
|
||||
for (int i = 0; i < 3; i++) {
|
||||
twai_frame_t trigger_frame = {};
|
||||
trigger_frame.header.id = 0x100 + i;
|
||||
trigger_frame.header.dlc = 1;
|
||||
trigger_frame.buffer = (uint8_t[]) {
|
||||
(uint8_t)i
|
||||
};
|
||||
trigger_frame.buffer_len = 1;
|
||||
|
||||
TEST_ESP_OK(twai_node_transmit(test_ctx.node, &trigger_frame, 500));
|
||||
printf("Sent trigger frame 0x%" PRIx32 "\n", trigger_frame.header.id);
|
||||
vTaskDelay(pdMS_TO_TICKS(50)); // Allow ISR processing
|
||||
}
|
||||
|
||||
// Wait for test completion
|
||||
int timeout_count = 0;
|
||||
while (!test_ctx.test_completed && timeout_count < 100) {
|
||||
vTaskDelay(pdMS_TO_TICKS(10));
|
||||
timeout_count++;
|
||||
}
|
||||
|
||||
printf("Test results:\n");
|
||||
printf(" RX count: %" PRIu32 "\n", test_ctx.rx_count);
|
||||
printf(" TX ISR sends: %" PRIu32 "\n", test_ctx.tx_isr_send_count);
|
||||
printf(" RX ISR sends: %" PRIu32 "\n", test_ctx.rx_isr_send_count);
|
||||
printf(" Test completed: %s\n", test_ctx.test_completed ? "YES" : "NO");
|
||||
|
||||
// Verify test results
|
||||
TEST_ASSERT_TRUE(test_ctx.test_completed);
|
||||
TEST_ASSERT_EQUAL_UINT32(3, test_ctx.tx_isr_send_count); // 3 sends from TX ISR
|
||||
TEST_ASSERT_EQUAL_UINT32(3, test_ctx.rx_isr_send_count); // 3 sends from RX ISR
|
||||
TEST_ASSERT_GREATER_OR_EQUAL_UINT32(9, test_ctx.rx_count); // At least 9 received frames
|
||||
|
||||
TEST_ESP_OK(twai_node_disable(test_ctx.node));
|
||||
TEST_ESP_OK(twai_node_delete(test_ctx.node));
|
||||
|
||||
printf("ISR send test passed!\n");
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
CONFIG_COMPILER_DUMP_RTL_FILES=y
|
||||
CONFIG_TWAI_ISR_CACHE_SAFE=y
|
||||
CONFIG_FREERTOS_IN_IRAM=y
|
||||
CONFIG_TWAI_IO_FUNC_IN_IRAM=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_NONE=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_CHECKS_SILENT=y
|
||||
CONFIG_COMPILER_OPTIMIZATION_ASSERTIONS_DISABLE=y
|
||||
|
@@ -122,7 +122,7 @@ To reduce performance overhead caused by memory copying, the TWAI driver uses po
|
||||
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_msg, 0)); // Timeout = 0: returns immediately if queue is full
|
||||
ESP_ERROR_CHECK(twai_node_transmit_wait_all_done(node_hdl, -1)); // Wait for transmission to finish
|
||||
|
||||
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.
|
||||
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. The :cpp:func:`twai_node_transmit` function is thread-safe and can also be called from an ISR. When called from an ISR, the ``timeout`` parameter is ignored, and the function will not block.
|
||||
|
||||
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.
|
||||
|
||||
@@ -181,6 +181,32 @@ After understanding the basic usage, you can further explore more advanced capab
|
||||
.. image:: ../../../_static/diagrams/twai/full_flow.drawio.svg
|
||||
:align: center
|
||||
|
||||
Transmit from ISR
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
The TWAI driver supports transmitting messages from an Interrupt Service Routine (ISR). This is particularly useful for applications requiring low-latency responses or periodic transmissions triggered by hardware timers. For example, you can trigger a new transmission from within the ``on_tx_done`` callback, which is executed in an ISR context.
|
||||
|
||||
.. code:: c
|
||||
|
||||
static bool twai_tx_done_cb(twai_node_handle_t handle, const twai_tx_done_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
// A frame has been successfully transmitted. Queue another one.
|
||||
// The frame and its data buffer must be valid until transmission is complete.
|
||||
static const uint8_t data_buffer[] = {1, 2, 3, 4};
|
||||
static const twai_frame_t tx_frame = {
|
||||
.header.id = 0x2,
|
||||
.buffer = (uint8_t *)data_buffer,
|
||||
.buffer_len = sizeof(data_buffer),
|
||||
};
|
||||
|
||||
// The `twai_node_transmit` is safe to be called in an ISR context
|
||||
twai_node_transmit(handle, &tx_frame, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
.. note::
|
||||
When calling :cpp:func:`twai_node_transmit` from an ISR, the ``timeout`` parameter is ignored, and the function will not block. If the transmit queue is full, the function will return immediately with an error. It is the application's responsibility to handle cases where the queue is full.
|
||||
|
||||
Bit Timing Customization
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
@@ -318,7 +344,9 @@ The driver guarantees thread safety for all public TWAI APIs. You can safely cal
|
||||
Performance
|
||||
^^^^^^^^^^^
|
||||
|
||||
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.
|
||||
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) and receive operations are placed in internal RAM, reducing latency caused by instruction fetching from Flash.
|
||||
|
||||
For applications that require high-performance transmit operations, the driver provides the :ref:`CONFIG_TWAI_IO_FUNC_IN_IRAM` option to place transmit functions in IRAM. This is particularly beneficial for time-critical applications that frequently call :cpp:func:`twai_node_transmit` from user tasks.
|
||||
|
||||
.. note::
|
||||
|
||||
|
@@ -122,7 +122,7 @@ TWAI 报文有多种类型,由报头指定。一个典型的数据帧报文主
|
||||
ESP_ERROR_CHECK(twai_node_transmit(node_hdl, &tx_msg, 0)); // 超时为0,队列满则直接返回超时
|
||||
ESP_ERROR_CHECK(twai_node_transmit_wait_all_done(node_hdl, -1)); // 等待发送完成
|
||||
|
||||
其中 :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::id` 指示了该文的 ID 为 0x01。报文的 ID 通常用于表示报文在应用中的类型,并在发送过程中起到总线竞争仲裁的作用,其数值越小,在总线上的优先级越高。:cpp:member:`twai_frame_t::buffer` 则指向要发送数据所在的内存地址,并由 :cpp:member:`twai_frame_t::buffer_len` 给出数据长度。:cpp:func:`twai_node_transmit` 函数是线程安全的,并且也可以在 ISR 中调用。当从 ISR 调用时,``timeout`` 参数将被忽略,函数不会阻塞。
|
||||
|
||||
需要注意的是 :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 ,那他们所代表的长度必须一致。
|
||||
|
||||
@@ -181,6 +181,32 @@ TWAI 报文有多种类型,由报头指定。一个典型的数据帧报文主
|
||||
.. image:: ../../../_static/diagrams/twai/full_flow.drawio.svg
|
||||
:align: center
|
||||
|
||||
在 ISR 中发送
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
TWAI 驱动支持在中断服务程序 (ISR) 中发送报文。这对于需要低延迟响应或由硬件定时器触发的周期性传输的应用特别有用。例如,你可以在 ``on_tx_done`` 回调中触发一次新的传输,该回调在 ISR 上下文中执行。
|
||||
|
||||
.. code:: c
|
||||
|
||||
static bool twai_tx_done_cb(twai_node_handle_t handle, const twai_tx_done_event_data_t *edata, void *user_ctx)
|
||||
{
|
||||
// 一帧已成功发送。排队另一帧。
|
||||
// 帧及其数据缓冲区必须在传输完成之前保持有效。
|
||||
static const uint8_t data_buffer[] = {1, 2, 3, 4};
|
||||
static const twai_frame_t tx_frame = {
|
||||
.header.id = 0x2,
|
||||
.buffer = (uint8_t *)data_buffer,
|
||||
.buffer_len = sizeof(data_buffer),
|
||||
};
|
||||
|
||||
// `twai_node_transmit` 在 ISR 上下文中调用是安全的
|
||||
twai_node_transmit(handle, &tx_frame, 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
.. note::
|
||||
在 ISR 中调用 :cpp:func:`twai_node_transmit` 时,``timeout`` 参数将被忽略,函数不会阻塞。如果发送队列已满,函数将立即返回错误。应用程序需要自行处理队列已满的情况。
|
||||
|
||||
位时序自定义
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
@@ -318,7 +344,9 @@ TWAI控制器能够检测由于总线干扰产生的/损坏的不符合帧格式
|
||||
关于性能
|
||||
^^^^^^^^
|
||||
|
||||
为了提升中断处理的实时响应能力, 驱动提供了 :ref:`CONFIG_TWAI_ISR_IN_IRAM` 选项。启用该选项后,中断处理程序将被放置在内部 RAM 中运行,从而减少了从 Flash 加载指令带来的延迟。
|
||||
为了提升中断处理的实时响应能力, 驱动提供了 :ref:`CONFIG_TWAI_ISR_IN_IRAM` 选项。启用该选项后,中断处理程序和接收操作将被放置在内部 RAM 中运行,从而减少了从 Flash 加载指令带来的延迟。
|
||||
|
||||
对于需要高性能发送操作的应用,驱动还提供了 :ref:`CONFIG_TWAI_IO_FUNC_IN_IRAM` 选项,用于将发送函数放置在 IRAM 中。这对于在用户任务中频繁调用 :cpp:func:`twai_node_transmit` 的时间关键应用特别有效。
|
||||
|
||||
.. note::
|
||||
|
||||
|
Reference in New Issue
Block a user