NetBurner 3.5.8
PDF Version
NetBurner RTOS

Introduction

The NetBurner Real-Time Operating System (NBRTOS) is a full-featured preemptive multitasking RTOS supporting:

  • Multiple tasks with 255 priority levels
  • Semaphores for resource protection
  • Mailboxes for message passing
  • FIFOs for flexible queuing
  • Message queues for structured communication
  • Critical sections for mutual exclusion

The RTOS comes pre-configured as part of the NetBurner development package. Your application begins execution in the UserMain() task.

Priority Levels

To conserve system resources, the default number of tasks is 64, with 64 being the Idle task that will run when all other higher priority tasks are blocked. You can increase the number of tasks up to 255. There are a number of system tasks running at priorities defined in constants.h. The recommended UserMain() priority is MAIN_PRIO, which is a value of 50. When creating additonal tasks:

  • Add or subtract from MAIN_PRIO for readability. For example, MAIN_PRIO-1 is a higher priority task, which MAIN_PRIO+1 is a lower priority task.
  • Use the helper task function: OSGetNextPrio() to choose. For example, OSGetNextPrio(OSNextPrio::Above)
    enum class OSNextPrio {
    Maximum = -2, // Highest priority currently unused, 0->OS_LO_PRIO
    Above = -1, // Lowest priority that is higher than the current task, OSTaskID()->0
    Below = 0, // Highest priority that is lower than the current task, OSTaskID()->OS_LO_PRIO
    Next = Below, // Highest priority that is lower than the current task, OSTaskID()->OS_LO_PRIO
    Minimum = 1 // Lowest priority currently unused, OS_LO_PRIO->0
    };
    OSNextPrio
    What to consider as the 'next' priority when looking for an available priority for task creation.
    Definition nbrtos.h:2374
    @ Next
    Highest priority that is lower than the current task, OSTaskID()->OS_LO_PRIO.
    Definition nbrtos.h:2378
    @ Above
    Lowest priority that is higher than the current task, OSTaskID()->0.
    Definition nbrtos.h:2376
    @ Maximum
    Highest priority currently unused, 0->OS_LO_PRIO.
    Definition nbrtos.h:2375
    @ Minimum
    Lowest priority currently unused, OS_LO_PRIO->0.
    Definition nbrtos.h:2379
    @ Below
    Highest priority that is lower than the current task, OSTaskID()->OS_LO_PRIO.
    Definition nbrtos.h:2377

Task Priority Range:

Priority Range: 1 - 255 (Default maximum is 64).
┌─────────────────────────────────────┐
│ Lower Number = Higher Priority │
│ │
│ 1 <--- Highest Priority │
│ 2 │
│ . │
│ . Application Tasks │
│ . │
│ 64 <--- Lowest Priority (Idle) │
│ . (Default Maximum) │
│ . │
│ . │
│ . │
│ 254 │
│ 255 <--- Lowest Priority │
└─────────────────────────────────────┘

Important**: Each priority level can only be used by ONE task at a time. Always check return values when creating tasks or changing priorities.


What is a Preemptive RTOS?

Core Principle

The highest priority task ready to run will ALWAYS run.**

This is fundamentally different from time-sliced operating systems like Windows or Unix. In NBRTOS:

  • No round-robin scheduling
  • No time slices
  • Priority-based execution only

Task Execution Flow

Task A (Priority 50) Task B (Priority 51)
┌──────────────┐ ┌──────────────┐
│ Running │ │ Blocked │
│ │ │ │
└──────┬───────┘ └──────────────┘
│
│ Calls blocking function
│ (OSTimeDly, OSSemPend, etc.)
v
┌──────────────┐ ┌──────────────┐
│ Blocked │ │ Running │
│ │ │ │
└──────────────┘ └──────┬───────┘
│
│ Task A unblocks
v
┌──────────────┐ ┌──────────────┐
│ Running │ │ Blocked │
│ (Higher │ │ (Lower │
│ Priority) │ │ Priority) │
└──────────────┘ └──────────────┘
uint8_t OSSemPend(OS_SEM *psem, uint16_t timeout)
Wait timeout ticks for the value of the semaphore to be non zero. Note: A timeout value of 0 (zero) w...
Definition nbrtos.h:1992
void OSTimeDly(uint32_t to_count)
Delay the task until the specified value of the system timer ticks. The number of system ticks per se...
Definition nbrtos.h:1855

Blocking Scenarios

A higher priority task MUST block for lower priority tasks to execute:

┌─────────────────────────────────────────────┐
│ When Tasks Block │
├─────────────────────────────────────────────┤
│ │
│ 1. Time Delay │
│ │ │
│ v │
│ [Task sleeps for specified time] │
│ │
│ 2. Resource Wait │
│ OSSemPend(&sem, timeout) │
│ │ │
│ v │
│ [Task waits until resource available] │
│ │
│ 3. I/O Operation │
│ read(fd, buf, len) │
│ │ │
│ v │
│ [Task waits for data] │
│ │
└─────────────────────────────────────────────┘
#define TICKS_PER_SECOND
System clock ticks per second.
Definition constants.h:49
int read(int fd, char *buf, int nbytes)
Read data from a file descriptor (fd).
time_t time(time_t *pt)
Gets the current system GMT time.

Function Categories

RTOS Blocking Functions

Functions that pend on resources or create time delays:

I/O Functions That Block

Functions that perform read operations or pend on file descriptors:

  • select() - Wait for I/O readiness
  • read() - Read data (blocks until data available)
  • write() - Write data (blocks until space available)
  • gets() - Get string from input
  • getchar() - Get character
  • fgets() - Get string from file

Network Functions That Block

Functions that wait for connections or data:

  • accept() - Wait for incoming connection
  • UDPPacket() - Wait to receive UDP packet (when configured for receive)

Functions That Unblock Tasks

Functions that post to pending resources:

System Task Priorities

System tasks use predefined priority levels defined in nbrtos/include/constants.h. The exact tasks depend on:

  • Your platform (MOD5441X, SOMRT1061, etc.)
  • Enabled system features (HTTP server, network stack, etc.)

    Example**: Calling StartHTTP() creates a system task for web server request handling.


Task Creation

Entry Point: UserMain()

Every NetBurner application begins in the UserMain() task, which is created automatically by the system.

System Boot
│
v
┌─────────────────────┐
│ System Init │
│ - Hardware setup │
│ - RTOS startup │
└──────────┬──────────┘
│
v
┌─────────────────────┐
│ UserMain() starts │
│ - init() │
│ - StartHttp() │
│ - Create tasks │
│ - Main loop │
└─────────────────────┘
void StartHttp(uint16_t port, bool RunConfigMirror)
Start the HTTP web server. Further documentation in the Initialization section Initialization - Syste...
void init()
System initialization. Ideally called at the beginning of all applications, since the easiest Recover...

Task Creation Methods

Method 1: OSTaskCreatewName() - Full Control

Provides complete control over all task parameters with status return value.

Parameters**:

  1. Task function name
  2. Optional parameter to pass (NULL if unused)
  3. Top of task stack
  4. Bottom of task stack
  5. Task priority
  6. Task name (string)

    Returns**: Status code (OS_NO_ERR on success)

Method 2: OSSimpleTaskCreatewName() - Simplified

Automatically allocates stack space, requires only:

  1. Task function name
  2. Task priority
  3. Task name (string)

    Note**: Does not return status code

Priority Selection Helper

Use OSGetNextPrio() to automatically find an available priority, but ensure:

  • Not higher than critical system tasks (e.g., ETHER_TASK_PRIO)
  • Not too low for your real-time requirements
  • Refer to constants.h for system priority definitions

Complete Example

/*
Task Creation Example
Demonstrates both OSTaskCreatewName() and OSSimpleTaskCreatewName()
*/
#include <init.h>
#include <stdlib.h>
#include <nbrtos.h>
#include <system.h>
#include <utils.h>
const char *AppName = "Task Creation Example";
// Stack allocation for full task creation method
uint32_t TaskAllParamsStack[USER_TASK_STK_SIZE];
/*
Task created with OSTaskCreatewName()
Demonstrates passing parameters and full control
*/
void TaskAllParams(void *pd)
{
uint32_t loopCount = 0;
uint32_t delayTime = (uint32_t)pd; // Cast parameter
printf("TaskAllParams delay: %ld seconds\r\n", delayTime);
while (1)
{
printf("TaskAllParams iteration: %ld\r\n", loopCount);
loopCount++;
}
}
/*
Task created with OSSimpleTaskCreatewName()
Simplified creation with automatic stack allocation
*/
void TaskSimple(void *pd)
{
uint32_t loopCount = 0;
uint32_t delayTime = 6;
printf("TaskSimple delay: %ld seconds\r\n", delayTime);
while (1)
{
printf("TaskSimple iteration: %ld\r\n", loopCount);
loopCount++;
}
}
/*
Main entry point
*/
void UserMain(void *pd)
{
uint32_t delayTime = 3;
int returnCode;
init(); // Initialize network stack
WaitForActiveNetwork(TICKS_PER_SECOND * 5); // Wait for DHCP
// Create task with full parameter control
printf("Creating TaskAllParams...");
returnCode = OSTaskCreatewName(
TaskAllParams, // Task function
(void *)delayTime, // Parameter to pass
&TaskAllParamsStack[USER_TASK_STK_SIZE], // Stack top
TaskAllParamsStack, // Stack bottom
MAIN_PRIO - 1, // Priority
"TaskAllParams" // Name
);
if (returnCode == OS_NO_ERR)
printf("Success\r\n");
else
printf("*** Error: %d\r\n", returnCode);
// Create task with simplified method
printf("Creating TaskSimple\r\n");
OSSimpleTaskCreatewName(TaskSimple, MAIN_PRIO - 2, "TaskSimple");
while (1)
{
}
}
#define MAIN_PRIO
Recommend UserMain priority.
Definition constants.h:130
uint8_t OSTaskCreatewName(void(*task)(void *dptr), void *data, void *pstktop, void *pstkbot, uint8_t prio, const char *name, OS_TCB **pRetHandle=NULL)
Create a new task.
#define OSSimpleTaskCreatewName(x, p, n)
Simpler form of creating a new task. Will automatically allocate the default task stack size.
Definition nbrtos.h:1763
#define OS_NO_ERR
No error.
Definition nbrtos.h:58
bool WaitForActiveNetwork(uint32_t ticks_to_wait=120 *TICKS_PER_SECOND, int interface=-1)
Wait for an active network connection on at least one interface.

Protecting Shared Data

Mechanism Selection Guide

Choose the appropriate protection mechanism based on your needs:

┌────────────────────────────────────────────────────┐
│ Data Protection Mechanism │
│ │
│ Need to signal event only (no data)? │
│ │ │
│ └──> Use Semaphore │
│ (32-bit counter) │
│ │
│ Need to pass single 32-bit value/pointer? │
│ │ │
│ └──> Use Mailbox or Queue │
│ (pointer or integer) │
│ │
│ Need to pass structures/objects? │
│ │ │
│ └──> Use FIFO │
│ (linked list of structures) │
│ │
│ Need temporary exclusive access? │
│ │ │
│ └──> Use Critical Section (OSCrit) │
│ (mutex-like protection) │
│ │
│ Need to block all task switching? │
│ │ │
│ └──> Use OSLock/OSUnlock │
│ (tasks disabled, IRQs enabled) │
│ │
│ Need to block tasks AND interrupts? │
│ │ │
│ └──> Use USER_ENTER/EXIT_CRITICAL │
│ (complete system lock) │
└────────────────────────────────────────────────────┘
void OSUnlock(void)
This function unlocks the OS.
void OSLock(void)
Calling the OSLock function will prevent the OS from changing tasks.

Protection Mechanisms (Ordered by System Impact)

System Impact: Low ──────────────────────────> High
│ │
│ │
┌───────────────┼─────────────────────────────────┼─────┐
│ │ │ │
│ Semaphore │ OSCrit/OSLock │ CRITICAL
│ Mailbox │ │ SECTION
│ Queue │ │
│ FIFO │ │
│ │ │
└───────────────┴─────────────────────────────────┴─────┘
(Task waits) (Task blocks only) (All blocked)

Detailed Mechanism Comparison

Mechanism Purpose Blocks IRQ Impact Best For
OSSemPend()/Post() Signal/protect resource Task only None Event signaling, simple resource protection
OSMboxPend()/Post() Pass pointer message Task only None Passing single message/pointer between tasks
OSQPend()/Post() Message queue (FIFO) Task only None Multiple messages, bounded queue
OSFifoPend()/Post() Linked list queue Task only None Unbounded message passing, complex structures
OSCritEnter()/Exit() Counted mutex Task only None Multi-task resource protection
OSLock()/Unlock() Disable task switch All tasks None Short critical sections
USER_ENTER/EXIT_CRITICAL() Disable all Tasks + IRQs All disabled Absolute protection (use sparingly)

Important Considerations

Queue vs. FIFO

Queue**:

  • Fixed size (declared at compile time)
  • Bounded memory usage
  • Predictable behavior
  • Good for embedded systems

    FIFO**:

  • Dynamic size (linked list)
  • Memory allocated by application
  • Flexible but requires memory management
  • Avoid dynamic allocation in embedded systems

    Recommendation**: Use static memory pools for FIFO structures to avoid fragmentation.


Semaphores

Concept

A semaphore is a protected counter used for:

  • Controlling access to shared resources
  • Signaling event occurrence
  • Task synchronization

Operation Flow

Task A Semaphore Task B
(Counter)
│ │
│ OSSemPost() │
├──────────────────> [Count: 0 -> 1] │
│ │
│ [Count: 1] │
│ │
│ │ OSSemPend()
│ [Count: 1 -> 0] <─────────┤
│ │
│ │ (Gets semaphore)
│ │
│ OSSemPost() │
├──────────────────> [Count: 0 -> 1] │
│ │
uint8_t OSSemPost(OS_SEM *psem)
Increases the value of the semaphore by one. Note: If any higher priority tasks were waiting on the s...
Definition nbrtos.h:1973

Usage Pattern

// 1. Declare semaphore
OS_SEM MySemaphore;
// 2. Initialize semaphore (typically in UserMain)
OSSemInit(&MySemaphore, 0); // Initial value: 0
// 3. Post to semaphore (signal/release)
OSSemPost(&MySemaphore);
// 4. Pend on semaphore (wait/acquire)
OSSemPend(&MySemaphore, 0); // 0 = wait forever
// 5. Pend with timeout
OSSemPend(&MySemaphore, TICKS_PER_SECOND * 5); // Wait 5 seconds
uint8_t OSSemInit(OS_SEM *psem, long value)
Initializes a semaphore.
Definition nbrtos.h:1955
Semaphores are used to control access to shared resources or or to communicate between tasks in a mul...
Definition nbrtos.h:407

Timeout Specification

Timeout Parameter:
┌──────────────────────────────────────┐
│ 0 = Wait forever │
│ TICKS_PER_SECOND = 1 second │
│ TICKS_PER_SECOND * 5 = 5 seconds │
│ TICKS_PER_SECOND / 2 = 500 milliseconds │
└──────────────────────────────────────┘

Resource Protection Example

OS_SEM SerialPortSem;
void UserMain(void *pd) {
init();
OSSemInit(&SerialPortSem, 1); // Initial count: 1 (available)
// Create tasks that share serial port...
}
void TaskA(void *pd) {
while (1) {
OSSemPend(&SerialPortSem, 0); // Acquire
// Protected: exclusive access to serial port
SerialPort.Write("Task A\r\n");
OSSemPost(&SerialPortSem); // Release
}
}

Message Queues

Concept

Message queues enable tasks and ISRs to send and receive pointer-sized messages. The pointers typically reference structures or objects containing actual data.

Queue Structure

┌─────────────────────────────────────────┐
│ Message Queue │
│ │
│ Entry 0: [ptr] ──> [Message Data] │
│ │ │
│ Entry 1: [ptr] ──> [Message Data] │
│ │ │
│ Entry 2: [ptr] ──> [Message Data] │
│ │ │
│ Entry 3: [empty] │
│ │ │
│ Entry N: [empty] │
│ │ │
│ v │
│ [Max Size: Defined at Creation] │
└─────────────────────────────────────────┘
^ ^
│ │
Dequeue Enqueue
uint8_t OSQPost(OS_Q *pq, void *msg)
This function posts a message to a Queue.
Definition nbrtos.h:2113
void * OSQPend(OS_Q *pq, uint16_t timeout, uint8_t *err)
Wait timeout ticks for another task to post to the queue.
Definition nbrtos.h:2187

Message Flow

Producer Task Queue Consumer Task
(FIFO)
│ │
│ OSQPost(msg1) │
├─────────────> [msg1] │
│ │
│ OSQPost(msg2) │
├─────────────> [msg1][msg2] │
│ │
│ │ OSQPend()
│ [msg2] <─────────────────┤
│ │
│ │ (Receives msg1)
│ │
│ OSQPost(msg3) │
├─────────────> [msg2][msg3] │
│ │

Usage Example

// Define message structure
typedef struct {
uint32_t sensorId;
float temperature;
uint32_t timestamp;
} SensorReading;
// Declare queue
OS_Q SensorQueue;
void *SensorQueueStorage[10]; // Storage for 10 messages
void UserMain(void *pd) {
init();
// Initialize queue with 10 entries
OSQInit(&SensorQueue, SensorQueueStorage, 10);
// Create producer and consumer tasks...
}
void SensorTask(void *pd) {
SensorReading reading;
while (1) {
// Read sensor
reading.sensorId = 1;
reading.temperature = ReadSensor();
reading.timestamp = Secs;
// Post to queue
OSQPost(&SensorQueue, (void *)&reading);
}
}
void ProcessingTask(void *pd) {
while (1) {
// Wait for message
SensorReading *msg = (SensorReading *)OSQPend(&SensorQueue, 0);
// Process message
printf("Sensor %lu: %.2fC at %lu\r\n",
msg->sensorId, msg->temperature, msg->timestamp);
}
}
uint8_t OSQInit(OS_Q *pq, void **start, uint8_t size)
A queue functions as a fixed size FIFO for communication between tasks. This function initializes an ...
Definition nbrtos.h:2095
A message queue is an object that enables tasks and interrupt service routines to pend and post point...
Definition nbrtos.h:693

FIFOs

Concept

FIFOs are similar to queues but specifically designed for linked-list-based message passing. Unlike queues with fixed size, FIFOs grow dynamically within available memory.

Structure Requirements

The first element of any FIFO structure MUST be a void * pointer for OS internal linking:

typedef struct MyFifoStruct {
void *next; // REQUIRED: Must be first element
uint32_t data1; // Your data
float data2; // Your data
char message[64]; // Your data
} MyFifoStruct;

FIFO Architecture

┌──────────────────────────────────────────────┐
│ OS_FIFO Linked List │
│ │
│ ┌─────────┐ ┌─────────┐ ┌─────────┐ │
│ │ [next]──┼───>│ [next]──┼───>│ [next]──┼─>NULL
│ │ data1 │ │ data1 │ │ data1 │ │
│ │ data2 │ │ data2 │ │ data2 │ │
│ │ ... │ │ ... │ │ ... │ │
│ └─────────┘ └─────────┘ └─────────┘ │
│ ^ │
│ │ │
│ OSFifoPost() │
│ │
│ │ │
│ v │
│ OSFifoPend() │
└──────────────────────────────────────────────┘
uint8_t OSFifoPost(OS_FIFO *pFifo, OS_FIFO_EL *pToPost)
This function posts to a FIFO.
Definition nbrtos.h:2236
OS_FIFO_EL * OSFifoPend(OS_FIFO *pFifo, uint16_t timeout)
This function pends on a FIFO.
Definition nbrtos.h:2267
Definition nbrtos.h:928

Memory Management Strategies

Strategy 1: Static Pool (Recommended)

#define FIFO_POOL_SIZE 20
// Create static pool of FIFO structures
MyFifoStruct fifoPool[FIFO_POOL_SIZE];
bool poolInUse[FIFO_POOL_SIZE] = {false};
// Allocate from pool
MyFifoStruct* AllocateFifoStruct() {
for (int i = 0; i < FIFO_POOL_SIZE; i++) {
if (!poolInUse[i]) {
poolInUse[i] = true;
return &fifoPool[i];
}
}
return NULL; // Pool exhausted
}
// Return to pool
void FreeFifoStruct(MyFifoStruct *ptr) {
int index = ptr - fifoPool;
if (index >= 0 && index < FIFO_POOL_SIZE) {
poolInUse[index] = false;
}
}

Strategy 2: Dynamic Allocation (Not Recommended)

// Avoid in embedded systems due to fragmentation risk
MyFifoStruct *ptr = (MyFifoStruct *)malloc(sizeof(MyFifoStruct));
// ... use ...
free(ptr);

Queue vs. FIFO Decision

Choose Queue When: Choose FIFO When:
┌────────────────────┐ ┌────────────────────┐
│ Fixed message count│ │ Variable msg count │
│ Predictable memory │ │ Complex structures │
│ Simple data types │ │ Need flexibility │
│ Bounded behavior │ │ Can manage memory │
└────────────────────┘ └────────────────────┘

FIFO Functions


Critical Sections

OSCrit - Counted Critical Section

A counting mutex that restricts resource access to one task at a time.

Operation Flow

Task A OSCrit Object Task B
(Resource Guard)
│ │
│ OSCritEnter() │
├────────────> [Lock: Task A] │
│ │
│ (Accessing resource) │
│ │
│ │ OSCritEnter()
│ [Lock: Task A] <─────────────┤
│ │
│ │ (BLOCKED)
│ │
│ OSCritExit() │
├────────────> [Lock: Released] │
│ │
│ │ (UNBLOCKED)
│ [Lock: Task B] <─────────────┤
│ │
│ │ (Accessing resource)
uint8_t OSCritEnter(OS_CRIT *pCrit, uint16_t timeout)
This function tries to enter or claim the critical section.
Definition nbrtos.h:2320

Counting Behavior

Critical Section Counter:
┌─────────────────────────────────────┐
│ │
│ OSCritEnter() -> Count++ │
│ OSCritExit() -> Count-- │
│ │
│ When Count reaches 0: │
│ -> Resource released │
│ -> Next task can enter │
│ │
└─────────────────────────────────────┘

Usage Examples

Manual Enter/Exit

OSCritObj linkedListGuard;
void TaskA(void *pd) {
while (1) {
OSCritEnter(&linkedListGuard, 0); // Enter critical section
// Protected: manipulate linked list
linkedList.Add(data);
OSCritExit(&linkedListGuard); // Exit critical section
}
}

C++ Scoped Object (Recommended)

OSCritObj linkedListGuard;
void TaskB(void *pd) {
while (1) {
{
// Automatic enter on construction
OSCritObj lock(&linkedListGuard);
// Protected: manipulate linked list
linkedList.Remove(data);
} // Automatic exit on destruction (scope end)
}
}

OSCrit vs. OSLock

┌─────────────────────────────────────────────────┐
│ OSCrit vs OSLock │
├─────────────────────────────────────────────────┤
│ │
│ OSCrit (Resource Mutex): │
│ - Blocks only tasks accessing same resource │
│ - Other tasks can still run │
│ - IRQs not affected │
│ - Good for: Protecting shared data │
│ │
│ OSLock (Task Switch Disable): │
│ - Blocks ALL task switching │
│ - No tasks can preempt │
│ - IRQs still run │
│ - Good for: Brief critical operations │
│ │
└─────────────────────────────────────────────────┘

OSLock Usage Pattern

void QuickOperation() {
OSLock(); // Disable task switching
// Very brief critical operation
// (keep this SHORT!)
globalCounter++;
OSUnlock(); // Enable task switching
}
// C++ scoped version
void QuickOperationCpp() {
{
OSLockObj lock; // Auto OSLock() on creation
// Brief critical operation
globalCounter++;
} // Auto OSUnlock() on destruction
}
A simple wrapper class that helps use OS locks effectively.
Definition nbrtos.h:2510

Warning**: Keep OSLock sections as short as possible to maintain system responsiveness.


OS Flags

Concept

OSFlags enable monitoring multiple events simultaneously using a 32-bit bitmap where each bit represents a distinct flag or event.

Flag Bitmap Structure

32-bit Flag Register:
┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐
│31│30│29│...│ 7│ 6│ 5│ 4│ 3│ 2│ 1│ 0│
└─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘
│ │ │ │ │ │ │ │ │ │ │
│ │ │ │ │ │ │ │ │ │ └─> Event 0
│ │ │ │ │ │ │ │ │ └───> Event 1
│ │ │ │ │ │ │ │ └─────> Event 2
│ │ │ │ │ │ │ └───────> Event 3
│ │ │ │ │ │ └─────────> Event 4
...

Flag Operations

Flag Manipulation:
┌────────────────────────────────────────┐
│ │
│ OSFlagSet(flags, 0x0001) │
│ ──────────────────────────────> │
│ [Bit 0 = 1] │
│ │
│ OSFlagSet(flags, 0x0004) │
│ ──────────────────────────────> │
│ [Bit 0 = 1, Bit 2 = 1] │
│ │
│ OSFlagClear(flags, 0x0001) │
│ ──────────────────────────────> │
│ [Bit 2 = 1] │
│ │
└────────────────────────────────────────┘
void OSFlagSet(OS_FLAGS *flags, uint32_t bits_to_set)
This function sets the corresponding bits asserted in bits_to_set of an OS_FLAGS object pointed to by...
Definition nbrtos.h:1480
void OSFlagClear(OS_FLAGS *flags, uint32_t bits_to_clr)
This function clears the bits asserted in bits_to_clr of an OS_FLAGS object pointed to by *flags....
Definition nbrtos.h:1495

Pending Modes

Wait for ANY flag (OR):
┌─────────────────────────────────────┐
│ Mask: 0x0007 (bits 0, 1, 2) │
│ │
│ OSFlagPendAny(flags, 0x0007, ...) │
│ │
│ Unblocks when: │
│ Bit 0 = 1 OR │
│ Bit 1 = 1 OR │
│ Bit 2 = 1 │
└─────────────────────────────────────┘
Wait for ALL flags (AND):
┌─────────────────────────────────────┐
│ Mask: 0x0007 (bits 0, 1, 2) │
│ │
│ OSFlagPendAll(flags, 0x0007, ...) │
│ │
│ Unblocks when: │
│ Bit 0 = 1 AND │
│ Bit 1 = 1 AND │
│ Bit 2 = 1 │
└─────────────────────────────────────┘
uint8_t OSFlagPendAll(OS_FLAGS *flags, uint32_t bit_mask, uint16_t timeout)
This function waits a number of time ticks specified by timeout until all the flags indicated by bit_...
Definition nbrtos.h:1551
uint8_t OSFlagPendAny(OS_FLAGS *flags, uint32_t bit_mask, uint16_t timeout)
This function waits a number of time ticks specified by timeout until any of the flags indicated by b...
Definition nbrtos.h:1514

Flag Functions Summary

Function Description
OSFlagCreate() Create flag object
OSFlagSet() Set specified bits
OSFlagClear() Clear specified bits
OSFlagState() Read current flag value
OSFlagPendAll() Wait until ALL specified flags set
OSFlagPendAny() Wait until ANY specified flag set
OSFlagPendNoWait() Check ALL flags without waiting
OSFlagPendAnyNoWait() Check ANY flag without waiting

Usage Example

OS_FLAGS systemFlags;
// Define flag bits
#define FLAG_SENSOR_READY 0x0001 // Bit 0
#define FLAG_NETWORK_UP 0x0002 // Bit 1
#define FLAG_DATA_AVAILABLE 0x0004 // Bit 2
#define FLAG_ERROR 0x0008 // Bit 3
void UserMain(void *pd) {
init();
OSFlagCreate(&systemFlags);
// Create tasks...
}
void SensorTask(void *pd) {
while (1) {
// Read sensor
if (SensorValid()) {
OSFlagSet(&systemFlags, FLAG_SENSOR_READY);
}
}
}
void ProcessingTask(void *pd) {
while (1) {
// Wait for sensor AND network
uint32_t flags = OSFlagPendAll(
&systemFlags,
FLAG_SENSOR_READY | FLAG_NETWORK_UP,
);
if (flags) {
// Both conditions met, process data
ProcessSensorData();
// Clear flags
OSFlagClear(&systemFlags, FLAG_SENSOR_READY);
}
}
}
void MonitorTask(void *pd) {
while (1) {
// Check for any error condition (non-blocking)
uint32_t flags = OSFlagPendAnyNoWait(
&systemFlags,
FLAG_ERROR
);
if (flags & FLAG_ERROR) {
printf("Error detected!\r\n");
OSFlagClear(&systemFlags, FLAG_ERROR);
}
}
}
void OSFlagCreate(OS_FLAGS *pf)
Definition nbrtos.h:1465
uint8_t OSFlagPendAnyNoWait(OS_FLAGS *flags, uint32_t bit_mask)
This function immediately checks to see if any of the flag bits indicated by bit_mask are set; it doe...
Definition nbrtos.h:1532
OSFlags enables a function or task to pend on multiple flags or events.
Definition nbrtos.h:1285

System Configuration

The NetBurner system reads its base configuration from #define statements in nbrtos/include/predef.h and nbrtos/include/constants.h. You change that configuration for one project without editing either shared header.

Where Your Settings Go

Put your definitions in a project-local copy of the matching hook header:

Setting defined in Your project file
nbrtos/include/predef.h overload/nbrtos/include/predef-overload.h
nbrtos/include/constants.h overload/nbrtos/include/constants-overload.h
libraries/include/crypto/platform/<PLATFORM>/user_settings.h (wolfSSL) overload/libraries/include/crypto/platform/user_settings-overload.h

The SDK ships all three hook headers empty. They exist only so a project can replace them. The build system searches the project's overload directory before the SDK, so your copy wins. See Modifying System Files with Overload for how the build finds it.

Why Not Edit predef.h Directly

The overload directory can hold a full copy of predef.h, and before the hook headers existed that was the only way. It has real costs:

  1. predef.h changes between NNDK releases, so your copy goes stale.
  2. Your own changes are buried in a 400-line file.
  3. Every upgrade means a merge.

A hook header holds only your changes, survives an upgrade cleanly, and is short enough to review at a glance. Use it.

Note
This shortcut applies to system configuration only. To overload any other system file, nbrtos/source/timezones.cpp for example, you still copy the real system file into the matching path under overload and edit the copy. See Overload Directory & System Files.

Include Order

Order decides whether a definition takes effect. The hook headers sit in different places on purpose:

  • predef.h includes predef-overload.h on its last line, after every conditional block in the file has already been evaluated.
  • constants.h includes constants-overload.h near the top, before its #ifndef X / #define X default blocks. It includes constants-overload-undefs.h after the header guard closes, for #undef work.
  • Each platform user_settings.h includes user_settings-overload.h after all of its settings. Its switches, such as NB_TLS_KEYLOG and the crypto profiles, take effect in user_settings_switches.h, which comes after the hook. So a #define or #undef in user_settings-overload.h always takes effect, switches included. Use #undef first when you change a value that the file already sets.

So in constants-overload.h a plain #define always wins:

// overload/nbrtos/include/constants-overload.h
#define USER_TASK_STK_SIZE (4096) // constants.h guards its default with #ifndef

In predef-overload.h a plain #define wins for any macro predef.h leaves undefined:

// overload/nbrtos/include/predef-overload.h
#define NBRTOS_STACKCHECK (1) // predef.h ships this line commented out
#define NBRTOS_TIME (1)
#define ENABLE_SNMP (1)
Warning
A macro defined in predef-overload.h cannot switch on an #ifdef block that appears earlier in predef.h. That block has already been skipped.

Working Around the Include Order

When predef.h sets sub-options inside a conditional block, set those sub-options yourself. Multihoming is the clearest case. predef.h contains:

//#define MULTIHOME
#ifdef MULTIHOME
#define NUM_MULTI_INTERFACES (10)
#else
#define NUM_MULTI_INTERFACES (0)
#endif

By the time predef-overload.h is read, NUM_MULTI_INTERFACES is already (0). Defining MULTIHOME alone does nothing useful. Undefine the sub-option and set it yourself:

// overload/nbrtos/include/predef-overload.h
#define MULTIHOME
#undef NUM_MULTI_INTERFACES
#define NUM_MULTI_INTERFACES (10)

The VLAN example ships exactly this file. The On-board Cert Generation example does the same for ENABLE_AUTOCERT_REGEN and its AUTO_CERT_GEN_CHECK interval.

The same rule applies to the IPv4-only mode. predef.h reads:

//#define IPV4ONLY (1)
#ifndef IPV4ONLY
#define IPV6 (1)
#define IPV6_COUNTERS (1)
#endif

Defining IPV4ONLY in predef-overload.h will not disable IPv6, because IPV6 is already defined. Undefine it too:

// overload/nbrtos/include/predef-overload.h
#define IPV4ONLY (1)
#undef IPV6
#undef IPV6_COUNTERS

When to Use #undef

Use a plain #define when the SDK header leaves the macro undefined, which covers most feature switches. Use #undef first only when the SDK header has already defined the macro, as in the three cases above. An unnecessary #undef is harmless, but a missing one produces a redefinition warning or a setting that silently does nothing.

Setup Methods

The build system finds the overload directory by name. Command line builds need no makefile change. NBEclipse projects need the overload include directory added to the compiler include paths, above the $(NNDK_ROOT)/ entries: overload/nbrtos/include for predef-overload.h and constants-overload.h, and overload/libraries/include, in both the C and the C++ include paths, for user_settings-overload.h.

Common Configuration Overrides

Each macro below is real, and each example ships it in its own overload directory:

// overload/nbrtos/include/predef-overload.h
#define NBRTOS_STACKCHECK (1) // Stack tracking and OSDumpTCBStacks()
#define NBRTOS_STACKOVERFLOW (1) // Run-time stack overflow protection
#define NBRTOS_STACKUNDERFLOW (1) // Run-time stack underflow protection (ARM only)
#define NBRTOS_TASKLIST (1) // Task diagnostics, debug builds only
#define NBRTOS_TIME (1) // Per-task run-time profiling
#define NBRTOS_PRIO_PROMOTION (1) // Priority inheritance on OS_CRIT inversion
#define ENABLE_SNMP (1) // SNMP agent
#define NB_SSH_SUPPORTED (1) // SSH client and server

Read the comment blocks in predef.h for the full list and for what each macro costs. The file groups them under Features, Debugging, and per-subsystem headings.

Verification After Changes

The first time you add a file to the overload directory, clean the build so the system library is rebuilt against it. After that, editing an overloaded file rebuilds the necessary files automatically.

make clean
make

In NBEclipse, select "Clean NetBurner System Library" under the project's "Build Targets", then rebuild.

To confirm a macro took effect, use something it gates. NBRTOS_STACKCHECK, for instance, is what makes OSDumpTCBStacks() link at all: if the definition did not reach the compiler, the build fails with "OSDumpTCBStacks was not declared".


Summary

Key Takeaways

  1. RTOS Fundamentals
    • Highest priority ready task always runs
    • Tasks must block for lower priority tasks to execute
    • 255 priority levels (1 = highest, 255 = lowest)
  2. Task Creation
  3. Data Protection Hierarchy
    Semaphore < Mailbox < Queue < FIFO < OSCrit < OSLock < Critical Section
    (Least impact) ──────────────────────────────────> (Most impact)
  4. Best Practices
    • Use predef-overload.h for configuration
    • Prefer static memory over dynamic allocation
    • Keep critical sections short
    • Check return values from task creation
    • Match enter/exit calls for counted resources
  5. Configuration Management
    • Never modify the SDK's predef.h directly
    • Put your defines in overload/nbrtos/include/predef-overload.h
    • Use constants-overload.h for settings that live in constants.h
    • #undef first only when the SDK header already defines the macro
    • predef.h reads its own conditionals before the overload, so set feature sub-options yourself
    • Clean rebuild after adding a file to the overload directory

Quick Reference

Common RTOS Calls:
├── Timing
├── Semaphores
│ ├── OSSemInit(&sem, 0)
│ ├── OSSemPend(&sem, timeout)
│ └── OSSemPost(&sem)
├── Queues
│ ├── OSQInit(&queue, storage, size)
│ ├── OSQPend(&queue, timeout)
│ └── OSQPost(&queue, msg)
├── Flags
│ ├── OSFlagCreate(&flags)
│ ├── OSFlagSet(&flags, bits)
│ ├── OSFlagPendAny(&flags, mask, timeout)
│ └── OSFlagPendAll(&flags, mask, timeout)
└── Critical Sections
├── OSCritEnter(&crit, timeout)
├── OSCritExit(&crit)
├── OSLock()
└── OSUnlock()

Further Reading

Refer to the following documentation:

  • constants.h - System task priorities and constants
  • API reference documentation - Complete function details
  • Platform-specific guides - Hardware integration
  • Example applications - Working implementations