Skip to main content How to Split RTOS Tasks | IoT Worker

How to Split RTOS Tasks

In an RTOS project, task count can grow from two to a dozen quickly.

A common split looks natural:

  • one UART task
  • one sensor task
  • one cloud task
  • one button task
  • one storage task
  • one display task

This is not always wrong, but it often splits code by module name. Tasks actually affect scheduling, blocking, stacks, priorities, synchronization, and resource ownership. Bad task boundaries later look like concurrency bugs.

The first model is:

task boundary = response path + blocking condition + resource ownership

If a task does not make response, blocking, or ownership clearer, it may only be a function running in another thread.

Do Not Start From Source Modules

A source module and a task are different things.

A sensor module may include:

  • periodic sampling
  • I2C access
  • filtering
  • threshold checks
  • calibration loading
  • report generation

These do not necessarily belong in one task, and each does not necessarily need its own task.

The real questions are:

  • which operations block
  • which events need fast response
  • which resources must be serialized
  • which data must move to other paths
  • which failures should not hold the whole system

If sensor sampling is slow but control response must be fast, control logic should not wait behind sampling. If an I2C bus is shared, bus ownership must be explicit.

Split by Response Path

The most natural task boundary is a distinct response path.

For example:

peripheral interrupt -> high-priority task -> drain FIFO
periodic sampling -> processing -> report queue
cloud command -> parsing -> control action
background storage -> flash write -> state update

These paths have different timing requirements. Draining a hardware FIFO may need to be fast. Flash writes can be slower. Cloud commands should not block sampling. Log upload can be lower priority.

Task boundaries should preserve those differences. If a fast response path and a slow I/O path are placed in the same task, the scheduler cannot fix the design.

A useful principle is: keep critical response paths independent, move slow paths to background tasks, and hand off through queues or events.

Split by Blocking Condition

One of the most important RTOS task capabilities is blocking wait.

So task design should inspect blocking conditions:

  • waiting for queue messages
  • waiting for semaphores
  • waiting for DMA completion
  • waiting for I/O
  • waiting for timeouts
  • waiting for mutexes

If two pieces of logic block for different reasons, putting them in one task can make them delay each other.

For example, a communication task may both handle receive data and perform TLS handshake and cloud reconnection. Reconnection can block for a long time. If local control messages share the same task, local control becomes slow.

A better split may be:

receive task: remove data from lower layers quickly
protocol task: parse and run state machine
connection task: reconnect, authenticate, recover network
business task: handle concrete commands

Not every project needs this much separation. The decision should come from blocking behavior, not file names.

Split by Resource Ownership

Tasks can also express resource ownership.

Some resources should not be accessed by many tasks directly:

  • flash
  • filesystem
  • cellular modem AT channel
  • one I2C or SPI bus
  • display framebuffer
  • non-thread-safe protocol objects

One task can own the resource while other tasks submit requests through a queue:

other tasks -> storage queue -> storage task -> flash/filesystem

This serializes access naturally, reduces locks, and makes field debugging easier. Queue depth and request types also show pressure.

The downside is that the owner task can become a bottleneck. It needs request priority, full-queue policy, timeout, and error handling.

Owner tasks fit slow, fragile, non-reentrant, or centrally scheduled resources. They should not turn the whole system into a central dispatcher, or the design becomes a superloop wearing RTOS clothing.

Do Not Create Tasks Just for Concurrency

More tasks do not mean more real-time behavior.

Each task has cost:

  • an independent stack
  • a scheduling entity
  • more context switches
  • more synchronization relationships
  • more priority combinations
  • more deadlock and priority inversion possibilities

If two pieces of logic always run sequentially, have no different blocking condition, have no independent response requirement, and do not protect complex shared resources, one task may be simpler.

An RTOS does not mean every module gets a thread. It means paths that truly need independent waiting and response get scheduling boundaries.

A Practical Design Flow

Use these questions:

  1. List external events: interrupts, timers, network messages, buttons, sensors, cloud commands.
  2. Mark response-time requirements for each event.
  3. Mark where each path can block.
  4. Mark resources that must be serialized.
  5. Mark where data is produced and consumed.
  6. Separate fast response paths, slow blocking paths, and resource owners.
  7. Merge tasks that do not add independent blocking or response value.

Then check:

  • what each task waits for
  • which resource each task owns
  • why each task has its priority
  • what happens when each queue is full
  • how each task stack is estimated and observed
  • which paths may drop events and which must not

If these questions cannot be answered, task design is not stable yet.

Good Task Boundaries

Good task boundaries usually have:

  • clear waiting conditions
  • clear inputs and outputs
  • explicit resource ownership
  • justified priorities
  • estimable stack usage
  • defined overload behavior
  • debugging paths through logs, queue depth, and task state

RTOS task design is not about creating more concurrent blocks. It is about turning response paths, resources, and blocking relationships into schedulable, observable, maintainable structure.