Nil RTOS
Examples

Nil RTOS Examples

Understanding the Examples

This section describes features common to all Nil RTOS examples.

Definition of Threads

A thread is defined by three macros, NIL_WORKING_AREA(), NIL_THREAD(), and NIL_THREADS_TABLE_ENTRY().

The working area for a thread is declared by this statement. The name of the working area is "waThread1" and its stack has 128 bytes beyond context switch and interrupt needs.

NIL_WORKING_AREA(waThread1, 128);

The entry point for a thread named Thread1 is declared by this statement. The thread function, Thread1, will be started with the argument "void *arg".

NIL_THREAD(Thread1, arg)

The NIL_THREADS_TABLE_ENTRY macro defines a thread table entry. The thread table is used by the Nil RTOS scheduler.

The priority of a thread is determined by its position in the thread table with highest priority first.

This is the thread table entry for a thread named "thread1" with entry point "Thread1". The thread is started with a NULL argument and has "waThread1" as its working area.

NIL_THREADS_TABLE_ENTRY("thread1", Thread1, NULL, waThread1, sizeof(waThread1))

The Idle Thread

Nil RTOS runs a special thread called the idle thread when all user defined threads are blocked. The idle thread is therefore the lowest priority thread.

The idle thread must not invoke any kernel primitive able to change a thread's state to not runnable.

The NilRTOS Arduino library runs the loop() function in the idle thread when all other threads are blocked.

The Nil Scheduler

The Nil RTOS scheduler is a fixed priority preemptive scheduler.

The scheduling strategy is very simple, the currently ready thread with the highest priority is executed.

A thread's priority is determined by its position in the thread table with highest priority first.

Higher priority threads must block to allow lower priority threads to execute. A thread can block by waiting on a semaphore or sleeping.

Stack Usage Messages

The nilSysBegin() function fills stack areas with a 0X55 pattern so that the "high water mark" can be determined for all stacks.

The nilPrintUnusedStack() function prints a message of this form.

Unused Stack: 57 28 1663

This message indicates that the first thread in the thread table has 57 unused bytes of stack, the second thread has 28 bytes of unused stack, and the idle thread has 1663 bytes of unused stack.

The nilPrintStackSizes() function prints a message of this form.

Stack Sizes: 133 101 85 1614

This message indicates that the first thread in the thread table started with a total stack size of 133 bytes, the second thread started with 101 bytes, the third thread started with 85 bytes, and the total stack space for the idle thread is 1614 bytes.

Examples

The following examples are in the NilRTOS/examples folder.

Two Thread Blink

The nilBlink.ino example demonstrates thread definition, semaphores, and thread sleep.

Thread 1 waits on a semaphore and turns the LED off when signalled by thread 2.

Thread 2 turns the LED on, sleeps for a period, signals thread 1 to turn the LED off, and sleeps for another period.

Blink Print

The nilBlinkPrint.ino example demonstrates thread definition, thread sleep, the idle thread, concurrent access to a variable, and NilSerial.

Thread 1 blinks the LED and sleeps between LED state changes.

Thread 2 prints a message once a second. The message has the value of a counter that is incremented in the idle thread and the amount of unused stack for each thread.

The idle thread increments a counter in a critical section.

Scope Context Switch Test

The nilContext.ino example demonstrates the time require for a semaphore signal plus a thread context switch.

To use this example, connect a scope to pin 13.

Measure the difference in time between the first pulse with no context switch and the second pulse started in thread 2 and ended in thread 1. The difference should be about 12 usec on a 16 MHz 328 Arduino.

SD Data Logger with FIFO

This is an early example and is included since it shows the details of how a FIFO works. See FIFO Class Based Data Logger for a simpler implementation using the NilFIFO template class.

The nilFifoDataLogger.ino example is a data logger based on a FIFO to decouple SD write latency from data acquisition timing.

This example is more complex than the other examples since it is a true general purpose data logger for the Arduino analog pins.

Fortunately Arduino forum member pito provided the following excellent diagrams that illustrate major features of of the nilFifoDataLogger example.

Shortly after the Data logger starts, its state is like the following diagram. A description of the objects in this diagram follows.

fifo1.jpg

Two threads are used to implement the example, a Producer thread and a Consumer thread. These threads communicate through a FIFO and are synchronized by two semaphores.

Thread1 implements the Producer, represented by the left side of the diagrams. The Producer reads the ADC and stores data in the FIFO.

The Producer maintains an index, "fifoHead", to access records. This index is not shared with the Consumer and initially fifoHead is zero. The fifoHead index is incremented after each record is filled by the Producer unless the value of fifoHead is FIFO_SIZE -1. In that case fifoHead is set to zero.

The idle thread implements the Consumer, represented by the right side of the diagrams. The Consumer reads data from the FIFO, formats the data, and writes the data to the SD.

The Consumer maintains an index, "fifoTail", to access records. This index is not shared with the Producer and initially fifoTail is zero. The fifoTail index is incremented after each record is read by the Consumer unless the value of fifoTail is FIFO_SIZE -1. In that case fifoTail is set to zero.

The FIFO is implemented by the array "fifoArray" of data records. Each record is a structure of type "FifoItem_t".

The state of the FIFO is maintained by two counting semaphores, "fifoData", and "fifoSpace". The Consumer and Producer both access these semaphores.

The fifoSpace semaphore maintains a count of free records in the FIFO. Initially all records are free and the value of the fifoSpace counter is FIFO_SIZE.

The Producer acquires an empty record by calling nilSemWaitTimeout(&fifoSpace). If the call is succesful, the fifoSpace counter will be decremented and the Producer has reserved access to the free record. If there are no free records nilSemWaitTimeout(&fifoSpace) fails and a data overrun error has occurred.

The Producer calls nilSemSignal(&fifoData) after it has stored the ADC data in the record. This causes the fifoData counter to be incremented and signals the Consumer that another data record is available.

The fifoData semaphore maintains a count of data records that have been filled by the Producer thread but not yet read by the Consumer. Initially the fifoData counter is zero.

The Consumer checks for data by calling nilSemWaitTimeout(&fifoData). If the call is succesful, the fifoData counter will be decremented and the Consumer has reserved access to the data record and can write it to the SD.

The Consumer calls nilSemSignal(&fifoSpace) after it has written the data record to the SD. This causes the fifoSpace counter to be incremented and signals the Producer that another free record is available.

If there are no data records nilSemWaitTimeout(&fifoSpace) fails and the Consumer checks for data on the next pass through loop().

The above diagram represents the state of the data logger when the fifoHead index is greater than the fifoTail in this case there are four filled data records.

fifo2.jpg

After the application has run for a while, its state may be as shown in above diagram. In this case fifoTail is greater than fifoHead. Most of the records have been filled. There are only three free records in this case.

This example can be adapted to other sensors by modifying this code.

// Read ADC data.
for (int i = 0; i < NUM_ADC; i++) {
// nilAnalogRead() sleeps during ADC conversion.
p->value[i] = nilAnalogRead(i);
}

Interrupt Service Routine

The nilInterrupt.ino example demonstrates a handler thread triggered from an ISR by using a semaphore.

The a pin change interrupt is generated by thread 2. Thread 2 prints a "High" message, set the pin high, prints a "Low" message and sets the pin low.

The pin going high triggers an interrupt that invokes the pin change ISR. The pin change ISR stores the time in micros() and signals a handler thread with a semaphore.

Thread 1, the handler thread prints message with the interrupt response time. This message should occur between the "High" and "Low" messages from thread 2.

FIFO Class Based Data Logger

The nilSdLogger.ino example is a complete SD data logger using NilStatsFIFO, nilTimer1Wait(), and nilAnalogRead().

It is derived from the nilFifoDataLogger.ino example and structured as a starting point for a custom data logger.

A quality SD card is required for best performance. Adjust these constants to match your SD card.

// Time between points in microseconds.
// Maximum value is 4,194,304 (2^22) usec.
const uint32_t PERIOD_USEC = 1000;
// Number of ADC channels to log
const uint8_t NADC = 2;

Counting Semaphore

The nilSemaphore.ino demonstrates limiting access to a region of code by using a counting semaphore.

The example has three threads but only allows two threads to access the restricted region.

Template for One Thread

The nilTemplate1.ino example is a template for one normal thread plus the idle thread. The template prints stack sizes and unused stack space.

Template for Two Threads

The nilTemplate1.ino example is a template for two normal threads plus the idle thread. The template prints stack sizes and unused stack space.

Template for Three Threads

The nilTemplate1.ino example is a template for three normal threads plus the idle thread. The template prints stack sizes and unused stack space.

Test of NilAnalog

the nilTestAnalog.ino example is a simple test that compares Arduino analogRead() with nilAnalogRead().

The test verifies that nilAnalogRead() sleeps while ADC conversion is in progress.

nilAnalogRead() allows lower priority threads to execute while the ADC is busy.

Test of the FIFO Template Class

The nilTestFifo.ino example is a very simple demonstration of the FIFO template class.

Thread 1 stores a counter in the FIFO and the idle thread prints the counter.

Test of Semaphore Functions

The nilSemTest.ino example tests semaphore functions. It a quality assurance tests for the Nil RTOS library and can be ignored by most users.

Test of NilTimer1

nilTestTimer1.ino is a very simple test to verify NilTimer1 works as expected.

TwiMaster Sleep Test

nilTwiSleepTest.ino is a test to verify that threads sleep while I2C transfers occur. It prints the percent of time save for other threads by using the TwiMaster library in place of the standard Wire library.