feat: Allows esp-mqtt task and work buffers to be allocated in external memory

This commit is contained in:
Euripedes Rocha Filho
2026-07-27 16:14:58 +02:00
parent 827875a087
commit b21e6bb3d4
7 changed files with 104 additions and 8 deletions
+25
View File
@@ -159,6 +159,31 @@ menu "ESP-MQTT Configurations"
help
Set to true to use external memory for outbox data.
config MQTT_BUFFERS_ON_EXTERNAL_MEMORY
bool "Use external memory for the client buffers"
default n
depends on SPIRAM
help
Set to true to allocate the client input and output buffers (see buffer.size and
buffer.out_size in the client configuration) in external memory.
config MQTT_TASK_STACK_ON_EXTERNAL_MEMORY
bool "Use external memory for the MQTT task stack"
default n
depends on SPIRAM
# FREERTOS_TASK_CREATE_ALLOW_EXT_MEM is the name from IDF v5.4 onwards, the other one is
# used by IDF v5.3.
depends on FREERTOS_TASK_CREATE_ALLOW_EXT_MEM || SPIRAM_ALLOW_STACK_EXTERNAL_MEMORY
help
Set to true to allocate the MQTT task stack in external memory. The task control block
is always kept in internal memory.
External memory is inaccessible while the flash cache is disabled, so with this option
the MQTT task must not run any code that disables the cache. In particular, flash and
NVS operations, and entering deep or light sleep, are not allowed from the MQTT task
context. This includes user code running in an event handler dispatched by the MQTT
task.
config MQTT_CUSTOM_OUTBOX
bool "Enable custom outbox implementation"
default n
+22
View File
@@ -236,6 +236,28 @@ The following settings are available:
- :ref:`CONFIG_MQTT_CUSTOM_OUTBOX`: disable default implementation of mqtt_outbox, so a specific implementation can be supplied
Memory placement
^^^^^^^^^^^^^^^^
On targets with external RAM (PSRAM), the biggest allocations of the client can be moved out of
internal RAM:
- :ref:`CONFIG_MQTT_OUTBOX_DATA_ON_EXTERNAL_MEMORY`: place the payloads of the messages kept in the
outbox in external memory.
- :ref:`CONFIG_MQTT_BUFFERS_ON_EXTERNAL_MEMORY`: place the client input and output buffers (sized by
:cpp:member:`buffer.size <esp_mqtt_client_config_t::buffer_t::size>` and
:cpp:member:`buffer.out_size <esp_mqtt_client_config_t::buffer_t::out_size>`) in external memory.
- :ref:`CONFIG_MQTT_TASK_STACK_ON_EXTERNAL_MEMORY`: place the MQTT task stack in external memory. The
task control block always stays in internal RAM. This option requires
:ref:`CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM`.
External memory is inaccessible while the flash cache is disabled. With the task stack in external
memory, the MQTT task must therefore not run any code that disables the cache: flash and NVS
operations, and entering deep or light sleep, are not allowed from the MQTT task context. This
includes user code running in an event handler dispatched by the MQTT task.
Considerations when using ESP-MQTT with unstable network connection
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
When using ESP-MQTT with QoS>0 it does not send the message immediately, but keeps in in the outbox while it's being processed.
+4
View File
@@ -37,3 +37,7 @@ index.rst:line: WARNING: undefined label: 'config_mqtt_protocol_311'
index.rst:line: WARNING: undefined label: 'config_mqtt_transport_ssl'
index.rst:line: WARNING: undefined label: 'config_mqtt_transport_websocket'
index.rst:line: WARNING: undefined label: 'config_mqtt_custom_outbox'
index.rst:line: WARNING: undefined label: 'config_mqtt_outbox_data_on_external_memory'
index.rst:line: WARNING: undefined label: 'config_mqtt_buffers_on_external_memory'
index.rst:line: WARNING: undefined label: 'config_mqtt_task_stack_on_external_memory'
index.rst:line: WARNING: undefined label: 'config_freertos_task_create_allow_ext_mem'
+18
View File
@@ -187,6 +187,24 @@ ESP-MQTT 库将始终重新传输未确认的 QoS 1 和 2 发布消息,以避
- :ref:`CONFIG_MQTT_CUSTOM_OUTBOX`:禁用 mqtt_outbox 默认实现,因此可以提供特定实现
内存分配位置
^^^^^^^^^^^^^^^^
在具有外部 RAM(PSRAM)的芯片上,可以将客户端占用内存最多的部分从内部 RAM 移出:
- :ref:`CONFIG_MQTT_OUTBOX_DATA_ON_EXTERNAL_MEMORY`:将 outbox 中保存的消息负载分配在外部内存中。
- :ref:`CONFIG_MQTT_BUFFERS_ON_EXTERNAL_MEMORY`:将客户端的输入和输出缓冲区(大小由
:cpp:member:`buffer.size <esp_mqtt_client_config_t::buffer_t::size>`
:cpp:member:`buffer.out_size <esp_mqtt_client_config_t::buffer_t::out_size>` 决定)分配在外部内存中。
- :ref:`CONFIG_MQTT_TASK_STACK_ON_EXTERNAL_MEMORY`:将 MQTT 任务栈分配在外部内存中。任务控制块始终位于内部
RAM 中。该选项需要启用 :ref:`CONFIG_FREERTOS_TASK_CREATE_ALLOW_EXT_MEM`
flash cache 被禁用时无法访问外部内存。因此,当任务栈位于外部内存中时,MQTT 任务不能执行任何会禁用 cache 的代码:
不允许在 MQTT 任务上下文中进行 flash 和 NVS 操作,也不允许进入 deep sleep 或 light sleep。这也包括由 MQTT
任务分发的事件处理函数中运行的用户代码。
事件
------------
+19 -1
View File
@@ -1,5 +1,5 @@
/*
* SPDX-FileCopyrightText: 2025 Espressif Systems (Shanghai) CO LTD
* SPDX-FileCopyrightText: 2025-2026 Espressif Systems (Shanghai) CO LTD
*
* SPDX-License-Identifier: Apache-2.0
*/
@@ -96,6 +96,12 @@
#endif
#endif
#if MQTT_CORE_SELECTION_ENABLED
#define MQTT_TASK_AFFINITY MQTT_TASK_CORE
#else
#define MQTT_TASK_AFFINITY tskNO_AFFINITY
#endif
#ifdef CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS
#define OUTBOX_EXPIRED_TIMEOUT_MS CONFIG_MQTT_OUTBOX_EXPIRED_TIMEOUT_MS
#else
@@ -119,5 +125,17 @@
#define MQTT_OUTBOX_MEMORY MALLOC_CAP_DEFAULT
#endif
#ifdef CONFIG_MQTT_BUFFERS_ON_EXTERNAL_MEMORY
#define MQTT_BUFFER_MEMORY MALLOC_CAP_SPIRAM
#else
#define MQTT_BUFFER_MEMORY MALLOC_CAP_DEFAULT
#endif
#ifdef CONFIG_MQTT_TASK_STACK_ON_EXTERNAL_MEMORY
#define MQTT_TASK_STACK_ON_EXTERNAL_MEMORY 1
#else
#define MQTT_TASK_STACK_ON_EXTERNAL_MEMORY 0
#endif
#define OUTBOX_MAX_SIZE (4*1024)
#endif
+2 -1
View File
@@ -4,6 +4,7 @@
* SPDX-License-Identifier: BSD-3-Clause
*/
#include <string.h>
#include "esp_heap_caps.h"
#include "mqtt_client.h"
#include "mqtt_msg.h"
#include "mqtt_config.h"
@@ -633,7 +634,7 @@ int mqtt_has_valid_msg_hdr(uint8_t *buffer, size_t length)
esp_err_t mqtt_msg_buffer_init(mqtt_connection_t *connection, int buffer_size)
{
memset(&connection->outbound_message, 0, sizeof(mqtt_message_t));
connection->buffer = (uint8_t *)calloc(buffer_size, sizeof(uint8_t));
connection->buffer = (uint8_t *)heap_caps_calloc(buffer_size, sizeof(uint8_t), MQTT_BUFFER_MEMORY);
if (!connection->buffer) {
return ESP_ERR_NO_MEM;
+14 -6
View File
@@ -488,7 +488,7 @@ esp_err_t esp_mqtt_set_config(esp_mqtt_client_handle_t client, const esp_mqtt_cl
}
free(client->mqtt_state.in_buffer);
client->mqtt_state.in_buffer = (uint8_t *)malloc(buffer_size);
client->mqtt_state.in_buffer = (uint8_t *)heap_caps_malloc(buffer_size, MQTT_BUFFER_MEMORY);
ESP_MEM_CHECK(TAG, client->mqtt_state.in_buffer, goto _mqtt_set_config_failed);
client->mqtt_state.in_buffer_length = buffer_size;
client->config->message_retransmit_timeout = config->session.message_retransmit_timeout;
@@ -2136,7 +2136,11 @@ static void esp_mqtt_task(void *pv)
outbox_delete_all_items(client->outbox);
client->state = MQTT_STATE_DISCONNECTED;
xEventGroupSetBits(client->status_bits, STOPPED_BIT);
#if MQTT_TASK_STACK_ON_EXTERNAL_MEMORY
vTaskDeleteWithCaps(NULL);
#else
vTaskDelete(NULL);
#endif
}
esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client)
@@ -2157,18 +2161,22 @@ esp_err_t esp_mqtt_client_start(esp_mqtt_client_handle_t client)
esp_err_t err = ESP_OK;
#if MQTT_CORE_SELECTION_ENABLED
ESP_LOGD(TAG, "Core selection enabled on %u", MQTT_TASK_CORE);
#else
ESP_LOGD(TAG, "Core selection disabled");
#endif
#if MQTT_TASK_STACK_ON_EXTERNAL_MEMORY
if (xTaskCreatePinnedToCore(esp_mqtt_task, "mqtt_task", client->config->task_stack, client, client->config->task_prio,
&client->task_handle, MQTT_TASK_CORE) != pdTRUE) {
if (xTaskCreatePinnedToCoreWithCaps(esp_mqtt_task, "mqtt_task", client->config->task_stack, client,
client->config->task_prio, &client->task_handle, MQTT_TASK_AFFINITY,
MALLOC_CAP_SPIRAM) != pdTRUE) {
ESP_LOGE(TAG, "Error create mqtt task");
err = ESP_FAIL;
}
#else
ESP_LOGD(TAG, "Core selection disabled");
if (xTaskCreate(esp_mqtt_task, "mqtt_task", client->config->task_stack, client, client->config->task_prio,
&client->task_handle) != pdTRUE) {
if (xTaskCreatePinnedToCore(esp_mqtt_task, "mqtt_task", client->config->task_stack, client,
client->config->task_prio, &client->task_handle, MQTT_TASK_AFFINITY) != pdTRUE) {
ESP_LOGE(TAG, "Error create mqtt task");
err = ESP_FAIL;
}