NetBurner 3.5.6
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
    };

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) │
└──────────────┘ └──────────────┘

Blocking Scenarios

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

┌─────────────────────────────────────────────┐
│ When Tasks Block │
├─────────────────────────────────────────────┤
│ │
│ 1. Time Delay │
│ OSTimeDly(TICKS_PER_SECOND) │
│ │ │
│ 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:

  • OSTimeDly() - Time delay
  • OSSemPend() - Semaphore wait
  • OSMboxPend() - Mailbox wait
  • OSQPend() - Queue wait
  • OSFlagPendAny() - Flag wait (any)
  • OSFlagPendAll() - Flag wait (all)

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:

  • OSMboxPost() - Post message to mailbox
  • OSQPost() - Post message to queue
  • OSSemPost() - Post to semaphore

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++;
OSTimeDly(TICKS_PER_SECOND * delayTime);
}
}
/*
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++;
OSTimeDly(TICKS_PER_SECOND * delayTime);
}
}
/*
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)
{
OSTimeDly(TICKS_PER_SECOND);
}
}
#define MAIN_PRIO
Recommend UserMain priority.
Definition constants.h:130
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) │
└────────────────────────────────────────────────────┘

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] │
│ │

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

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
OSTimeDly(TICKS_PER_SECOND);
}
}

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
(OSQPend) (OSQPost)

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);
OSTimeDly(TICKS_PER_SECOND);
}
}
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);
}
}

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() │
└──────────────────────────────────────────────┘

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

  • OSFifoInit() - Initialize FIFO
  • OSFifoPost() - Add to end of FIFO
  • OSFifoPostFirst() - Add to front of FIFO (priority)
  • OSFifoPend() - Wait for and remove first item
  • OSFifoPendNoWait() - Check and remove without waiting

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)

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
OSTimeDly(TICKS_PER_SECOND);
}
}

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)
OSTimeDly(TICKS_PER_SECOND);
}
}

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
}

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] │
│ │
└────────────────────────────────────────┘

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 │
└─────────────────────────────────────┘

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);
}
OSTimeDly(TICKS_PER_SECOND);
}
}
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);
}
OSTimeDly(TICKS_PER_SECOND / 10);
}
}

System Configuration

Predef.h and Predef-overload.h

The NetBurner system uses predef.h for base-level system configuration through #define statements.

Configuration Architecture

System Configuration Hierarchy:
┌────────────────────────────────────────┐
│ predef.h (System Default) │
│ - Core system settings │
│ - Platform defaults │
│ - Network stack config │
│ - Changes between NNDK releases │
└─────────────────┬──────────────────────┘
│ Overridden by
v
┌────────────────────────────────────────┐
│ predef-overload.h (User Custom) │
│ - Only settings you want to change │
│ - Survives NNDK updates │
│ - Recommended method │
└────────────────────────────────────────┘

Why Use Predef-overload.h?

Problems with modifying predef.h directly**:

  1. File changes frequently between NNDK releases
  2. Difficult to track your custom changes
  3. Merge conflicts during updates
  4. Confusing compilation errors after upgrades

    Benefits of predef-overload.h**:

  1. Only contains YOUR changes
  2. Survives NNDK updates cleanly
  3. Easy to review and maintain
  4. No merge conflicts

Setup Methods

NBEclipse Setup

Refer to NBEclipse Getting Started Guide - Overload Directory section

Command Line Tools Setup

Refer to Command Line Tools - Overload section

Predef-overload.h Best Practices

/*
predef-overload.h
Custom system configuration overrides
*/
// Best practice: #undef before #define
// Prevents duplicate definition warnings
// Example 1: Disable IPv6
#undef IPV6
#undef IPV4ONLY
#define IPV4ONLY (1)
// Example 2: Custom buffer sizes
#undef TCP_BUFFER_SIZE
#define TCP_BUFFER_SIZE (8192)
// Example 3: Enable debug features
#undef DEBUG_LEVEL
#define DEBUG_LEVEL (2)
// Example 4: Stack sizes
#undef USER_TASK_STK_SIZE
#define USER_TASK_STK_SIZE (4096)

Configuration Workflow

Development Process:
┌───────────────────────────────────────────┐
│ 1. Identify setting to change │
│ - Check predef.h for current value │
│ - Read documentation comments │
└────────────────┬──────────────────────────┘
v
┌───────────────────────────────────────────┐
│ 2. Create/edit predef-overload.h │
│ - Start with blank file │
│ - Add only needed changes │
└────────────────┬──────────────────────────┘
v
┌───────────────────────────────────────────┐
│ 3. Add override
│ #undef SETTING_NAME │
│ #define SETTING_NAME (new_value) │
└────────────────┬──────────────────────────┘
v
┌───────────────────────────────────────────┐
│ 4. Rebuild application │
│ make clean && make │
└────────────────┬──────────────────────────┘
v
┌───────────────────────────────────────────┐
│ 5. Test changes │
└───────────────────────────────────────────┘

Common Configuration Overrides

// Network Configuration
#undef IPV4ONLY
#define IPV4ONLY (1) // IPv4-only mode
#undef DEFAULT_NET_TIMEOUT
#define DEFAULT_NET_TIMEOUT (30) // 30 second timeout
// Task Configuration
#undef USER_TASK_STK_SIZE
#define USER_TASK_STK_SIZE (8192) // 8KB stack per task
#undef MAX_USER_TASKS
#define MAX_USER_TASKS (20) // Support 20 user tasks
// System Resources
#undef NUM_OS_FIFOS
#define NUM_OS_FIFOS (32) // 32 FIFO objects
#undef NUM_OS_SEMS
#define NUM_OS_SEMS (64) // 64 semaphores
// Debug Configuration
#undef DEBUG_BUILD
#define DEBUG_BUILD (1) // Enable debug features
#undef ENABLE_REMOTE_CONSOLE
#define ENABLE_REMOTE_CONSOLE (1) // Enable remote console

Verification After Changes

# Clean build to ensure all changes applied
make clean
# Rebuild with new configuration
make
# Verify settings at runtime
# Check console output or use system diagnostics

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
    • Entry point: UserMain()
    • Full control: OSTaskCreatewName()
    • Simplified: OSSimpleTaskCreatewName()
  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 predef.h directly
    • Use predef-overload.h for customization
    • Always #undef before #define
    • Clean rebuild after configuration changes

Quick Reference

Common RTOS Calls:
├── Timing
│ └── OSTimeDly(TICKS_PER_SECOND)
├── 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