Skip to main content

Thread and Timer Patterns

The TAL OS abstraction gives you portable threads, software timers, mutexes, semaphores, and queues across every TuyaOpen platform. This page collects the common patterns for multithreading, periodic work, and synchronization.

Prerequisitesโ€‹

  • Basic understanding of RTOS concepts (threads, mutexes, semaphores)
  • Completed Environment Setup

Creating Threadsโ€‹

#include "tal_thread.h"

static void my_task(void *arg)
{
while (1) {
/* periodic work */
tal_system_sleep(1000);
}
}

THREAD_HANDLE handle;
THREAD_CFG_T cfg = {
.thrdname = "my_task",
.stackDepth = 4096,
.priority = THREAD_PRIO_3,
};
tal_thread_create_and_start(&handle, NULL, NULL, my_task, NULL, &cfg);

Thread Priorityโ€‹

PriorityUse case
THREAD_PRIO_1Highest -- audio, real-time control
THREAD_PRIO_3Normal -- sensor reading, business logic
THREAD_PRIO_5Low -- logging, background sync

Thread Deletionโ€‹

Delete a thread from within itself:

static void self_terminating_task(void *arg)
{
for (int i = 0; i < 10; i++) {
tal_system_sleep(1000);
}
tal_thread_delete(NULL);
}
warning

Always delete threads from within themselves. Deleting a thread from another context may leave resources in an inconsistent state. The thread is not deleted instantly -- it completes its current execution cycle first.

Software Timersโ€‹

For periodic or one-shot callbacks without creating a dedicated thread:

#include "tal_sw_timer.h"

static void timer_callback(TIMER_ID timer_id, void *arg)
{
TAL_PR_INFO("timer fired");
}

TIMER_ID my_timer;
tal_sw_timer_create(timer_callback, NULL, &my_timer);
tal_sw_timer_start(my_timer, 5000, TAL_TIMER_CYCLE);

Timer types:

  • TAL_TIMER_CYCLE -- repeats every N ms
  • TAL_TIMER_ONCE -- fires once then stops

Stop and delete:

tal_sw_timer_stop(my_timer);
tal_sw_timer_delete(my_timer);

Mutex (Mutual Exclusion)โ€‹

Protect shared data between threads:

#include "tal_mutex.h"

MUTEX_HANDLE mutex;
tal_mutex_create_init(&mutex);

/* In thread A and B: */
tal_mutex_lock(mutex);
/* access shared data */
tal_mutex_unlock(mutex);

/* Cleanup: */
tal_mutex_release(mutex);

Semaphoreโ€‹

Signal between threads or from ISR to thread:

#include "tal_semaphore.h"

SEM_HANDLE sem;
tal_semaphore_create_init(&sem, 0, 1);

/* Producer (or ISR): */
tal_semaphore_post(sem);

/* Consumer thread: */
tal_semaphore_wait(sem, TIMEOUT_MS);

tal_semaphore_release(sem);

Message Queueโ€‹

Pass data between threads:

#include "tal_queue.h"

QUEUE_HANDLE queue;
tal_queue_create_init(&queue, sizeof(sensor_data_t), 10);

/* Producer: */
sensor_data_t data = { .temp = 25.0 };
tal_queue_post(queue, &data, 0);

/* Consumer: */
sensor_data_t received;
tal_queue_fetch(queue, &received, TIMEOUT_MS);

Common Pattern: Sensor + Cloud Reporterโ€‹

static QUEUE_HANDLE s_data_queue;

static void sensor_thread(void *arg)
{
while (1) {
float temp, humi;
sht3x_read(&temp, &humi);
sensor_data_t data = { .temp = temp, .humi = humi };
tal_queue_post(s_data_queue, &data, 0);
tal_system_sleep(5000);
}
}

static void cloud_thread(void *arg)
{
sensor_data_t data;
while (1) {
if (tal_queue_fetch(s_data_queue, &data, 10000) == OPRT_OK) {
report_to_cloud(data.temp, data.humi);
}
}
}

Platform Notesโ€‹

  • ESP32-S3: Stack depth is automatically increased by 1024 bytes internally.
  • Minimum sleep: tal_system_sleep() enforces a minimum of 10 ms.
  • tal_thread_set_self_name() is a no-op on ESP32 -- the name is fixed at creation.
  • Recursive mutex: Available when configUSE_RECURSIVE_MUTEXES is enabled (default on most platforms).

Referencesโ€‹