NilRTOS - A Fast Tiny Preemptive RTOS

How was this run? Was any data recorded?

type any character to begin type any character to end Done Unused Stack: 141 342

** overrun errors **

with: Code:

// SD chip select pin. const uint8_t sdChipSelect = 10; //SS;

The output is from the old example. It should look like this from today's file.

type any character to begin type any character to end Done Maximum SD write latency: 3748 usec Unused Stack: 57 407

Yes, when ran the old one I got csv data (100-200kB).

I cannot get it working with the new version yet.. Still the same issue, with =10 or =SS.. It works with "continue;" only:

type any character to begin
type any character to end
Maximum SD write latency: 0 usec
Unused Stack: 57 480

No idea what to do in order to get it working..

With new one I get now this

type any character to begin
type any character to end
Maximum SD write latency: 48548 usec
Unused Stack: 141 372


// Read ADC data.
    p->value = 555; //analogRead(0);


It seems this does the trick (the original example, 115k2) :

void setup() {



type any character to begin
type any character to end
Maximum SD write latency: 19736 usec
Unused Stack: 57 456
NA  252 0
1024    41  0
1024    47  0
1024    48  0
1024    46  0
1024    45  0
1024    36  0
1024    33  0

I need to think about this. I don’t touch the ADC stuff.

I must go out with my wife now.

Another issue I've found - I have to use:

struct FifoItem_t {
  uint16_t usec;        // Low 16-bit of time in usec.
  uint16_t value;  // ADC value.
  uint8_t error;   // Overrun count since last point.

otherwise the adc value read is not correctly passed to p->value. It seems our compiler has got a problem with passing values to bitfields ..

I don’t understand. I store this counter in thread 1

  //  p->value = analogRead(0);
     p->value = nr;
     nr = nr < 1023 ? nr + 1: 0;

and the correct result is written to the SD. The bit field works fine. I ran it for many cycles of 0 - 1023.

Why can’t I reproduce your results?

Edit: I now can show that only 8-bit of the ADC value get stored even though the counter works.

Edit: I got rid of the bit fields and now the ADC values in the file match a fixed voltage applied to pin A0.

.. you may see for example (when p->value is a bitfield):

p->value = 1023;      //this works
p->value = analogRead(0); //this does not work, only 8bits passed
p->value = temp;      //this does not work, only 8bits passed

PS: now it seems to me all above issues I saw here were caused by the bitfield issue..


Do you still need the call to analogReference() after changing FifoItem_t?

I really would like to get to the bottom of why you need the analogReference() call. All it does is set the global analog_reference. My experience has been that things like this are a symptom of a nasty bug.

uint8_t analog_reference = DEFAULT;

void analogReference(uint8_t mode)
    // can't actually set the register here because the default setting
    // will connect AVCC and the AREF pin, which would cause a short if
    // there's something connected to AREF.
    analog_reference = mode;

I have more cases where bit fields work and a lot where they don't. Clearly they are to be avoided.

I do not need analogreference when not using bitfields.

I modified the program to log three ADC channels every 1024 microseconds.

I added another performance measure, the minimum number of free data records. For the the three ADC test the result is:

Maximum SD write latency: 48364 usec
Minimum free record count: 89
Unused Stack: 57 152

I ran for about 15 minutes with a high quality industrial SD. I allocated 150 FIFO records and the minimum number of free records was 89 so I was never close to an overrun.

The log file size was about 10 MB.


Thanks for your effort to find the compiler bit field bug. Compiler bugs can be nasty.

I've tried with up to 5 channels, but getting overrun errors (with much smaller fifo_sizes, and cheap sdcard). It seems to me we have to find a way how to work with bitfields, though. Otherwise we waste the fifo space we desperately need.. :)

I tried a Mega and five is the limit. The problem is that each ADC read takes 110 microseconds so there isn't enough time left in the SD thread to format the data.

You don't lose much accuracy by speeding up the ADC clock so that could help.

Finally you could trigger the ADC with a timer compare and read it in an ISR. You could write binary...

But all of this is way beyond an example for NilRTOS. Right now I want to improve all the RTOS ports.

I plan to post improved versions of ChibiOS/RT, FreeRTOS, and NilRTOS on

I was thinking about porting NilRTOS to ARM boards but I decided ChibiOS/RT is a better fit.

Here is a sample of 16 ADC channels logged every 4096 microseconds on a Mega:

Period,ADC0,ADC1,ADC2,ADC3,ADC4,ADC5,ADC6,ADC7,ADC8,ADC9,ADC10,ADC11,ADC12,ADC13,ADC14,ADC15 NA,507,448,413,388,375,360,347,337,362,346,352,342,333,345,334,335 4096,399,406,402,391,382,370,357,344,347,340,343,337,329,334,329,329 4096,350,364,372,372,370,364,355,341,334,330,331,329,322,324,321,323 4096,331,340,349,353,356,356,351,339,329,327,327,326,321,322,320,322 4096,325,329,335,340,344,346,345,335,326,325,325,324,320,321,319,320 4096,318,318,320,323,327,330,331,323,316,315,315,314,311,312,309,310 4096,289,290,294,297,302,305,308,304,299,296,294,293,290,291,291,293 4096,305,301,299,298,298,300,301,297,295,296,298,298,296,297,295,293 4096,296,297,296,295,295,296,297,293,291,292,293,294,293,294,293,290

For example it works here with:

struct FifoItem_t {
  uint16_t usec : 16;        // Low 16-bit of time in usec.
  uint16_t value0 : 10;  // ADC value.
  uint16_t value1 : 10;  // ADC value.
  uint16_t value2 : 10;  // ADC value.
  uint16_t value3 : 10;  // ADC value.
  uint16_t value4 : 10;  // ADC value.
  uint8_t error : 6;   // Overrun count since last point.

fifo_size 100, intervalTicks=2, no analogreference, no overrun for 30secs

PS: not sure we can do this (the original struct causing troubles):

struct FifoItem_t {
  uint16_t usec;        // Low 16-bit of time in usec. 
  unsigned value : 10;  // ADC value.
  unsigned error : 6;   // Overrun count since last point.

within a struct - mixing int and bitfields - maybe within an union? …


I started using bit fields in this example because I have used them before on AVR.

The original structure worked with some assignment statements and failed with others. I tested it with a counter and that worked OK.

I don’t trust bit fields now. I wonder if the avr-gcc people have a description of the bug.

You could store the low 8-bits of each ADC in a byte and the upper 2-bits packed by using shifts and masks with four to a byte. The inverse could be used in the SD print thread.

I was just at the gym exercising and I started to think about the ADC read problem. I think I will do an ISR/semaphore replacement for analogRead(). I will setup the ADC so it interrupts when done. I will sleep on a semaphore that will be signaled by the ADC ISR like the nilInterrupt example.

Edit: That was easy. I now have nilAnalogRead(pin). It takes 126 microseconds 110 is sleep while the ADC is doing the conversion and 16 is ISR, semaphore, and context switch overhead. I get back 94 microseconds of CPU time per read but a read take 16 microseconds longer.

I can now log all six ADC channels on a Uno using a period of 1024 microseconds with nilAnalogRead().

Few pictures for you tutorial :slight_smile:
fifo_b - …basic situation…
fifo5 - …adc maybe faster than sdcard…
fifo6 - …till overrun error…

fifo_a - …counting semaphores handshaking…


Wow these are great. I was about to post an update but I will put your pictures in the Examples explanation.

I also found that I must restructure Nil into several libraries. The ISR for nilAnalogRead() is always linked. I guess I will need separate libraries for some parts of Nil. I can't think of a workaround.

I have posted an updated version of Nil RTOS as

It has new features including an ADC library that sleeps while conversions are in progress. This saves CPU time and allows other threads to execute.

A new version of SdFat is included with a function that formats uint16_t fields three times faster than the standard Arduino Print.

A data logging example is included that can log five analog pins on an Uno to a csv file every tick (1024 usec) which is 976 Hz. All 16 analog pins on a Mega can be logged to a csv file every 3 ticks which is 325 Hz. A quality SD card is required to achieve these results.

Documentation has been improved and two bugs have been fixed.

Works nice! :) (ie. Uno, 5 adc channels, 2048usec period, cheap card, 7 minutes logging time, 5.5MB data.csv):

type any character to begin
type any character to end
FIFO record count: 75
Minimum free record count: 54
Maximum SD write latency: 41064 usec
Unused Stack: 51 125

Fyi - here is the 1024usec period, 4 ADC channels, Latency and FreeFifo stored to the sdcard. You may see Latency and FreeFifo chart over the time (32k measurements shown).
Time is in msec, Latency in usec, FreeFifo in free records…

type any character to begin
type any character to end
FIFO record count: 90
Minimum free record count: 0
Maximum SD write latency: 35072 usec
Unused Stack: 51 144

** overrun errors **
Maximum overrun count: 32