I didn’t quite understand your solution.
I tested two alternatives below and they didn’t work.
My alternatives maintain the default freertos termination using vTaskDelete(NULL) or vTaskDelete(task_handle).
First alternative:
////////////////////////////////////////////////////////////////////////////////////////////
// Private Variables definition
// Estrutura para salvar os endereços alocados da Task
typedef struct
{
void* tcb;
void* stack;
} task_cleanup_t;
///////////////////////////////// Private Functions ////////////////////////////////////////
// Callback acionado automaticamente pelo FreeRTOS ANTES da task sumir da memória
static void vTaskStaticCleanupCallback(int idx, void* pvData)
{
(void)idx;
task_cleanup_t* res = (task_cleanup_t*)pvData;
if (res != NULL)
{
free(res->stack); // Libera a Stack (seja ela na SPIRAM ou INTERNAL)
free(res->tcb); // Libera o TCB da SRAM interna
free(res); // Libera a própria estrutura de controle
}
}
//////////////////////////////// Public Functions //////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////////
//
BaseType_t xTaskCreatePinnedToCorePrefer(TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
const BaseType_t xCoreID,
size_t num_caps,
...)
{
StackType_t* pxStackBuffer = NULL;
size_t stack_final_size = usStackDepth;
// 1. Aloca o Bloco de Controle (TCB) OBRIGATORIAMENTE na SRAM interna rápida
StaticTask_t* tcb_TaskBuffer = heap_caps_calloc(sizeof(StaticTask_t), sizeof(uint8_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (tcb_TaskBuffer == NULL)
{
return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
// 2. Loop varrendo as capacidades passadas no variadic (...) para a Stack
if (num_caps > 0)
{
va_list argp;
va_start(argp, num_caps);
for (size_t i = 0 ; i < num_caps ; i++)
{
uint32_t cap_atual = va_arg(argp, uint32_t);
if ((cap_atual & MALLOC_CAP_SPIRAM) == MALLOC_CAP_SPIRAM)
{
size_t size_mask = DATA_CACHE_ALIGNMENT - 1;
size_t aligned_bytes = (usStackDepth + size_mask) & ~size_mask; // if DATA_CACHE_ALIGNMENT = 64, aligned_bytes = ((usStackDepth + 63) / 64) * 64;
// PSRAM Totalmente blindada na entrada (64) e na saída (64 com safe_items_count)
pxStackBuffer = heap_caps_aligned_calloc(DATA_CACHE_ALIGNMENT, aligned_bytes, sizeof(uint8_t), cap_atual | MALLOC_CAP_8BIT);
if (pxStackBuffer != NULL)
{
stack_final_size = aligned_bytes;
break; // Achou uma memória disponível! Interrompe o loop.
}
}
else
{
// Arredonda o tamanho para o próximo múltiplo de 16 bytes
size_t sram_mask = 16 - 1;
stack_final_size = (stack_final_size + sram_mask) & ~sram_mask;
// Aloca garantindo o endereço inicial alinhado em 16 bytes
pxStackBuffer = heap_caps_aligned_calloc(16, stack_final_size, sizeof(uint8_t), cap_atual | MALLOC_CAP_8BIT);
if (pxStackBuffer != NULL)
{
break; // Achou uma memória disponível! Interrompe o loop.
}
}
}
va_end(argp);
}
// Se varreu todas as opções e não conseguiu alocar a Stack, limpa o TCB e aborta
if (pxStackBuffer == NULL)
{
free(tcb_TaskBuffer);
return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
// ====================================================================
// REGISTRO DO COLETOR DE LIXO AUTOMÁTICO
// ====================================================================
task_cleanup_t* res = heap_caps_malloc(sizeof(task_cleanup_t), MALLOC_CAP_INTERNAL);
if (res != NULL)
{
res->tcb = tcb_TaskBuffer;
res->stack = pxStackBuffer;
}
else
{
free(tcb_TaskBuffer);
free(pxStackBuffer);
return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
// ====================================================================
// 3. Cria a Task estática seguindo as regras de Core e assinaturas do ESP-IDF
TaskHandle_t xHandle = xTaskCreateStaticPinnedToCore(pxTaskCode, pcName, stack_final_size,
pvParameters, uxPriority,
pxStackBuffer, tcb_TaskBuffer,
xCoreID);
// Se o FreeRTOS rejeitar por qualquer outro motivo de contexto interno
if (xHandle == NULL)
{
free(res);
free(tcb_TaskBuffer);
free(pxStackBuffer);
return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
// Vincula a estrutura ao indice 1 do TLS (Thread Local Storage) e passa o nosso callback de exclusão.
// KCONFIG Name: FREERTOS_THREAD_LOCAL_STORAGE_POINTERS
// Set the number of thread local storage pointers in each task (see configNUM_THREAD_LOCAL_STORAGE_POINTERS documentation for more details).
// Note: In ESP-IDF, this value must be at least 1. Index 0 is reserved for use by the pthreads API thread-local-storage. Other indexes can be used for any desired purpose.
// configNUM_THREAD_LOCAL_STORAGE_POINTERS = 2, setado no menuconfig.
vTaskSetThreadLocalStoragePointerAndDelCallback(xHandle, 1, (void*)res, vTaskStaticCleanupCallback);
// Passa o ponteiro da Task criada de volta se o ponteiro não for NULL
if (pxCreatedTask != NULL)
{
*pxCreatedTask = xHandle;
}
return pdPASS;
}
Second alternative:
////////////////////////////////////////////////////////////////////////////////////////////
// Private Variables definition
typedef struct
{
TaskHandle_t xHandle; // Opcional, mas útil para validação dupla se quiser
void *tcb_ptr; // Endereço do heap onde o TCB foi alocado (SRAM)
void *stack_ptr; // Endereço do heap onde a Stack foi alocada (SRAM/PSRAM)
} Task_cleanup_t;
///////////////////////////////// Private Functions ////////////////////////////////////////
// Esse hook é chamado globalmente pela Idle Task para TODA tarefa deletada no sistema.
// O parâmetro void* pxTCB é exatamente o mesmo valor e tipo que o TaskHandle_t. Eles apontam para o mesmíssimo byte na SRAM.
void vTaskPreDeletionHook(void* pxTCB)
{
if (pxTCB != NULL)
{
// Usa o pxTCB (que é o handle da task) para buscar a nossa struct no índice 1 do TLS
Task_cleanup_t* mem = (Task_cleanup_t*)pvTaskGetThreadLocalStoragePointer((TaskHandle_t)pxTCB, 1);
esp_rom_printf("Hook disparado para o TCB: %p\n", pxTCB);
esp_rom_printf("xHandle: %p\n", mem->xHandle);
// Se retornar um ponteiro válido, encontramos uma das nossas tarefas estáticas genéricas
if (mem != NULL)
{
// 1. Libera a Stack (SRAM ou PSRAM)
if (mem->stack_ptr != NULL)
{
free(mem->stack_ptr);
}
// 2. Libera o TCB (SRAM interna)
if (mem->tcb_ptr != NULL)
{
free(mem->tcb_ptr);
}
// 3. Por fim, libera a própria struct de controle que foi alocada dinamicamente
free(mem);
}
}
}
//////////////////////////////// Public Functions //////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////////////////
// Precisa testar
// O codigo roda normal... Se precisar matar a tarefa de fora ou de dentro dela:
// vTaskDelete(meu_task_handle);
BaseType_t xTaskCreatePinnedToCorePrefer(TaskFunction_t pxTaskCode,
const char * const pcName,
const uint32_t usStackDepth,
void * const pvParameters,
UBaseType_t uxPriority,
TaskHandle_t * const pxCreatedTask,
const BaseType_t xCoreID,
size_t num_caps,
...)
{
StackType_t* pxStackBuffer = NULL;
size_t stack_final_size = usStackDepth;
// 1. Aloca o Bloco de Controle (TCB) OBRIGATORIAMENTE na SRAM interna rápida
StaticTask_t* tcb_TaskBuffer = heap_caps_calloc(sizeof(StaticTask_t), sizeof(uint8_t), MALLOC_CAP_INTERNAL | MALLOC_CAP_8BIT);
if (tcb_TaskBuffer == NULL)
{
return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
// 2. Loop varrendo as capacidades passadas no variadic (...) para a Stack
if (num_caps > 0)
{
va_list argp;
va_start(argp, num_caps);
for (size_t i = 0 ; i < num_caps ; i++)
{
uint32_t cap_atual = va_arg(argp, uint32_t);
if ((cap_atual & MALLOC_CAP_SPIRAM) == MALLOC_CAP_SPIRAM)
{
size_t size_mask = DATA_CACHE_ALIGNMENT - 1;
size_t aligned_bytes = (usStackDepth + size_mask) & ~size_mask; // if DATA_CACHE_ALIGNMENT = 64, aligned_bytes = ((usStackDepth + 63) / 64) * 64;
// PSRAM Totalmente blindada na entrada (64) e na saída (64 com safe_items_count)
pxStackBuffer = heap_caps_aligned_calloc(DATA_CACHE_ALIGNMENT, aligned_bytes, sizeof(uint8_t), cap_atual | MALLOC_CAP_8BIT);
if (pxStackBuffer != NULL)
{
stack_final_size = aligned_bytes;
break; // Achou uma memória disponível! Interrompe o loop.
}
}
else
{
// Arredonda o tamanho para o próximo múltiplo de 16 bytes
size_t sram_mask = 16 - 1;
stack_final_size = (stack_final_size + sram_mask) & ~sram_mask;
// Aloca garantindo o endereço inicial alinhado em 16 bytes
pxStackBuffer = heap_caps_aligned_calloc(16, stack_final_size, sizeof(uint8_t), cap_atual | MALLOC_CAP_8BIT);
if (pxStackBuffer != NULL)
{
break; // Achou uma memória disponível! Interrompe o loop.
}
}
}
va_end(argp);
}
// Se varreu todas as opções e não conseguiu alocar a Stack, limpa o TCB e aborta
if (pxStackBuffer == NULL)
{
free(tcb_TaskBuffer);
return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
// ====================================================================
// REGISTRO DO COLETOR DE LIXO AUTOMÁTICO
// ====================================================================
Task_cleanup_t* res = heap_caps_malloc(sizeof(Task_cleanup_t), MALLOC_CAP_INTERNAL);
if (res != NULL)
{
res->tcb_ptr = tcb_TaskBuffer;
res->stack_ptr = pxStackBuffer;
}
else
{
free(tcb_TaskBuffer);
free(pxStackBuffer);
return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
// ====================================================================
// 3. Cria a Task estática seguindo as regras de Core e assinaturas do ESP-IDF
TaskHandle_t xHandle = xTaskCreateStaticPinnedToCore(pxTaskCode, pcName, stack_final_size,
pvParameters, uxPriority,
pxStackBuffer, tcb_TaskBuffer,
xCoreID);
// Se o FreeRTOS rejeitar por qualquer outro motivo de contexto interno
if (xHandle == NULL)
{
free(res);
free(tcb_TaskBuffer);
free(pxStackBuffer);
return errCOULD_NOT_ALLOCATE_REQUIRED_MEMORY;
}
res->xHandle = xHandle;
// Vincula a estrutura ao indice 1 do TLS (Thread Local Storage) e passa o nosso callback de exclusão.
// KCONFIG Name: FREERTOS_THREAD_LOCAL_STORAGE_POINTERS
// Set the number of thread local storage pointers in each task (see configNUM_THREAD_LOCAL_STORAGE_POINTERS documentation for more details).
// Note: In ESP-IDF, this value must be at least 1. Index 0 is reserved for use by the pthreads API thread-local-storage. Other indexes can be used for any desired purpose.
// configNUM_THREAD_LOCAL_STORAGE_POINTERS = 2, setado no menuconfig.
vTaskSetThreadLocalStoragePointer(xHandle, 1, (void*)res);
// Passa o ponteiro da Task criada de volta se o ponteiro não for NULL
if (pxCreatedTask != NULL)
{
*pxCreatedTask = xHandle;
}
return pdPASS;
}