nano RTOS

realtime operating system for time-critical applications


nano RTOS can be integrated in Arduino to provide tasks, synchronisation primitives, enhanced drivers, printf, etc. Some Arduino libraries utilise Arduino timing functions, that might not function optimally when an operating system is in control of the SysTick timer. The following compatibility macros affect the behaviour of Arduino timing functions.

☂︎ Default configuration
The default configuration uses a dynamic (tickless) scheduler, where the SysTick period is adjusted to reduce unnecessary interrupts, while also providing very precise wake-up events with microsecond accuracy. In this mode, forwarding events to an external handler, e.g. Arduino is still possible, but is disabled to reduce overhead, since the updates may take up to one second. This can be resolved by patching the Arduino board libraries to utilise the nano RTOS timing functions, see below.


☂︎ Compatibility mode
In compatibility mode, the SysTick period is fixed. Timer events can be forwarded to Arduino, which makes the Arduino timing functions fully functional. In this mode however, the precision of wake-up events is reduced to a millisecond. For optimal performance, please use the dynamic scheduler, which is enabled in the default configuration.


Prerequisites for nano RTOS integration in Arduino

nano RTOS Arduino library

☂︎ Download and extract in ~/Documents/Arduino/libraries
☂︎ Navigate to nanoRTOS/src/cortex-m3 and create a symlink to nanoRTOS.a

☂︎ macOS

cd ~/Documents/Arduino/libraries/nanoRTOS/src/cortex-m3
ln -s ~/nano/lib/nanoRTOS.a nanoRTOS.a

☂︎ Windows

cd %USERPROFILE%\Documents\Arduino\libraries\nanoRTOS\src\cortex-m3
set NANO_RTOS=c:\nano
mklink nanoRTOS.a %NANO_RTOS%\lib\nanoRTOS.a

Install the Arm GNU Toolchain

☂︎ macOS: install from brew and create a link

brew install cask gcc-arm-embedded
cd ~/Library/Arduino15/packages/arduino/tools/arm-none-eabi-gcc
ln -s /Applications/ArmGNUToolchain/*/arm-none-eabi ARM
rm -rf 4.8.3-2014q1

☂︎ Or download the Arm GNU Toolchain and extract to

☂︎ macOS

☂︎ Windows

Fix the return type of spiRec

The return type of spiRec is uint8_t, which is incompatible with the declaration of byte in new versions of arm-none-eabi-gcc

☂︎ macOS

☂︎ Windows
%ProgramFiles% (x86)\Arduino\Java\libraries\SD\src\utility\Sd2Card.cpp

--- Sd2Card.cpp
+++ Sd2Card.cpp
@@ -770,7 +770,7 @@
 uint8_t Sd2Card::isBusy(void) {
-  byte b = spiRec();
+  uint8_t b = spiRec();

   return (b != 0XFF);

Add a menu to select between nano RTOS and bare metal in Arduino

☂︎ When bare metal is selected, the original behaviour is preserved
☂︎ When nano RTOS is selected, the Arduino timing functions are replaced

☂︎ macOS

☂︎ Windows

--- boards.txt
+++ boards.txt
@@ -8,6 +8,7 @@
 menu.virtio=Virtual serial support

 menu.dbg=Debug symbols and core logs
 menu.rtlib=C Runtime Library
 menu.upload_method=Upload method
@@ -2159,6 +2160,9 @@ RTOS metal F103CB (or C8 with 128k)
@@ -2167,6 +2171,9 @@ RTOS metal

 # BLACKPILL_F103C8 board F103C8

Add build.flags.rtos to the build process

☂︎ macOS

☂︎ Windows

--- platform.txt
+++ platform.txt
@@ -30,11 +30,11 @@

 compiler.extra_flags=-mcpu={build.mcu} {build.fpu} {build.float-abi} -DVECT_TAB_OFFSET={build.flash_offset} -DUSE_FULL_LL_DRIVER -mthumb "@{build.opt.path}"

-compiler.S.flags={compiler.extra_flags} -c -x assembler-with-cpp {compiler.stm.extra_include}
+compiler.S.flags={compiler.extra_flags} -c -x assembler-with-cpp {compiler.stm.extra_include} {build.flags.rtos}

-compiler.c.flags={compiler.extra_flags} -c {build.flags.optimize} {build.flags.debug} {compiler.warning_flags} -std={compiler.c.std} -ffunction-sections -fdata-sections --param max-inline-insns-single=500 -MMD {compiler.stm.extra_include}
+compiler.c.flags={compiler.extra_flags} -c {build.flags.optimize} {build.flags.debug} {compiler.warning_flags} -std={compiler.c.std} -ffunction-sections -fdata-sections --param max-inline-insns-single=500 -MMD {compiler.stm.extra_include} {build.flags.rtos}

-compiler.cpp.flags={compiler.extra_flags} -c {build.flags.optimize} {build.flags.debug} {compiler.warning_flags} -std={compiler.cpp.std} -ffunction-sections -fdata-sections -fno-threadsafe-statics --param max-inline-insns-single=500 -fno-rtti -fno-exceptions -fno-use-cxa-atexit -MMD {compiler.stm.extra_include}
+compiler.cpp.flags={compiler.extra_flags} -c {build.flags.optimize} {build.flags.debug} {compiler.warning_flags} -std={compiler.cpp.std} -ffunction-sections -fdata-sections -fno-threadsafe-statics --param max-inline-insns-single=500 -fno-rtti -fno-exceptions -fno-use-cxa-atexit -MMD {compiler.stm.extra_include} {build.flags.rtos}

@@ -105,6 +105,7 @@

Conditionally patch the Arduino timing functions

☂︎ When bare metal is selected, the original behaviour is preserved
☂︎ When nano RTOS is selected, the Arduino timing functions are replaced

☂︎ macOS

☂︎ Windows

--- wiring_time.c
+++ wiring_time.c
@@ -22,6 +22,28 @@
 extern "C" {

+extern uint64_t get_timestamp_ms(void);
+extern uint64_t get_timestamp_us(void);
+extern uint64_t task_sleep(uint64_t us);
+uint32_t millis(void)
+  // ToDo: ensure no interrupts
+  return (uint32_t)get_timestamp_ms();
+// Interrupt-compatible version of micros
+uint32_t micros(void)
+  return (uint32_t)get_timestamp_us();
+void delay(uint32_t ms)
+  task_sleep(ms * 1000);
 uint32_t millis(void)
   // ToDo: ensure no interrupts
@@ -43,6 +65,7 @@
     } while (getCurrentMillis() - start < ms);

 #ifdef __cplusplus

© 2022-2023 Georgi Valkov