diff --git a/Kconfig b/Kconfig index 5397f70..d6866ab 100644 --- a/Kconfig +++ b/Kconfig @@ -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 diff --git a/docs/en/index.rst b/docs/en/index.rst index 872b48a..0f0de64 100644 --- a/docs/en/index.rst +++ b/docs/en/index.rst @@ -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 ` and + :cpp:member:`buffer.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. diff --git a/docs/sphinx-known-warnings.txt b/docs/sphinx-known-warnings.txt index 71339b2..81ce6c4 100644 --- a/docs/sphinx-known-warnings.txt +++ b/docs/sphinx-known-warnings.txt @@ -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' diff --git a/docs/zh_CN/index.rst b/docs/zh_CN/index.rst index 2c7d394..b32b995 100644 --- a/docs/zh_CN/index.rst +++ b/docs/zh_CN/index.rst @@ -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 ` 和 + :cpp:member:`buffer.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 +任务分发的事件处理函数中运行的用户代码。 + 事件 ------------ diff --git a/lib/include/mqtt_config.h b/lib/include/mqtt_config.h index 9224557..ff43557 100644 --- a/lib/include/mqtt_config.h +++ b/lib/include/mqtt_config.h @@ -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 diff --git a/lib/mqtt_msg.c b/lib/mqtt_msg.c index 9bd01d4..0fdc4d8 100644 --- a/lib/mqtt_msg.c +++ b/lib/mqtt_msg.c @@ -4,6 +4,7 @@ * SPDX-License-Identifier: BSD-3-Clause */ #include +#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; diff --git a/mqtt_client.c b/mqtt_client.c index da35277..d1c711d 100644 --- a/mqtt_client.c +++ b/mqtt_client.c @@ -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; @@ -2186,7 +2186,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) @@ -2207,18 +2211,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; }