NuttX RTOS Porting Guide

Last Updated: May 8, 2014



Table of Contents

1.0 Introduction

Overview This document provides and overview of the NuttX build and configuration logic and provides hints for the incorporation of new processor/board architectures into the build.

See also arch/README.txt and configs/README.txt.

2.0 Directory Structure

Directory Structure. The general directly layout for NuttX is very similar to the directory structure of the Linux kernel -- at least at the most superficial layers. At the top level is the main makefile and a series of sub-directories identified below and discussed in the following paragraphs:

Configuration Files. The NuttX configuration consists of:

2.1 Documentation

General documentation for the NuttX OS resides in this directory.

2.2 nuttx/arch

2.2.1 Subdirectory Structure

This directory contains several sub-directories, each containing architecture-specific logic. The task of porting NuttX to a new processor consists of add a new subdirectory under arch/ containing logic specific to the new architecture. The complete board port in is defined by the architecture-specific code in this directory (plus the board-specific configurations in the config/ subdirectory). Each architecture must provide a subdirectory, <arch-name> under arch/ with the following characteristics:

2.2.2 Summary of Files

2.2.3 Supported Architectures

Architecture- and Chip-Specific Directories. All processor architecture-specific directories are maintained in sub-directories of the arch/ directory. Different chips or SoC's may implement the same processor core. Chip-specific logic can be found in sub-directories under the architecture directory. Current architecture/chip directories are summarized below:

Deprecated Architecture Directories. The following architecture directories are deprecated. They have been replaced by the logic in arm/arm and will deleted when arch/arm is fully verified.

Other ports for the for the TI TMS320DM270 and for MIPS are in various states of progress

2.3 nuttx/binfmt

The binfmt/ subdirectory contains logic for loading binaries in the file system into memory in a form that can be used to execute them.

2.4 nuttx/configs

The configs/ subdirectory contains configuration data for each board. These board-specific configurations plus the architecture-specific configurations in the arch/ subdirectory complete define a customized port of NuttX.

2.4.1 Subdirectory Structure

The configs directory contains board specific configuration files. Each board must provide a subdirectory <board-name> under configs/ with the following characteristics:

2.4.2 Summary of Files

2.4.2.1 Board Specific Logic

2.4.2.2 Board Specific Configuration Sub-Directories

The configs/<board-name>/ sub-directory holds all of the files that are necessary to configure NuttX for the particular board. A board may have various different configurations using the common source files. Each board configuration is described by three files: Make.defs, defconfig, and setenv.sh. Typically, each set of configuration files is retained in a separate configuration sub-directory (<config1-dir>, <config2-dir>, .. in the above diagram). The procedure for configuring NuttX is described below, This paragraph will describe the contents of these configuration files.

2.4.3 Supported Boards

All of the specific boards supported by NuttX are identified below. These are the specific <board-name>'s that may be used to configure NuttX as described below.

* A customized version of the buildroot is available to build these toolchains under Linux or Cygwin.

2.4.4 Adding a New Board Configuration

Okay, so you have created a new board configuration directory. Now, how do you hook this board into the configuration system so that you can select with make menuconfig?

You will need modify the file configs/Kconfig. Let's look at the STM32F4-Discovery configuration in the Kconfig file and see how we would add a new board directory to the configuration. For this configuration let's say that you new board resides in the directory configs/myboard; It uses an MCU selected with CONFIG_ARCH_CHIP_MYMCU; and you want the board to be selected with CONFIG_ARCH_BOARD_MYBOARD. Then here is how you can clone the STM32F4-Discovery configuration in configs/Kconfig to support your new board configuration.

In configs/Kconfig for the stm32f4-discovery, you will see a configuration definition like this:

The above selects the STM32F4-Discovery board. The select lines say that the board has both LEDs and buttons and that the board can generate interrupts from the button presses. You can just copy the above configuration definition to a new location (notice that they the configurations are in alphabetical order). Then you should edit the configuration to support your board. The final configuration definition might look something like:

Later in the configs/Kconfig file, you will see a long, long string configuration with lots of defaults like this:

This logic will assign string value to a configuration variable called CONFIG_ARCH_BOARD that will name the directory where the board-specific files reside. In our case, these files reside in configs/myboard and we add the following to the long list of defaults (again in alphabetical order):

Now the build system knows where to find your board configuration!

And finally, add something like this near the bottom of configs/myboard:

This includes additional, board-specific configuration variable definitions in configs/myboard/Kconfig.

2.5 nuttx/drivers

This directory holds architecture-independent device drivers.

2.6 nuttx/fs

This directory contains the NuttX file system. This file system is described below.

2.7 nuttx/graphics

This directory contains files for graphics/video support under NuttX.

2.8 nuttx/include

This directory holds NuttX header files. Standard header files file retained in can be included in the normal fashion:

Directory structure:

2.9 nuttx/libc

This directory holds a collection of standard libc-like functions with custom interfaces into NuttX.

Normally the logic in this file builds to a single library (libc.a). However, if NuttX is built as a separately compiled kernel (with CONFIG_NUTTX_KERNEL=y), then the contents of this directory are built as two libraries: One for use by user programs (libuc.a) and one for use only within the <kernel> space (libkc.a).

These user/kernel space libraries (along with the sycalls of nuttx/syscall) are needed to support the two differing protection domains.

Directory structure:

2.10 nuttx/libxx

This directory holds a tiny, minimal standard std C++ that can be used to build some, simple C++ applications in NuttX.

2.11 nuttx/mm

This is the NuttX memory manager.

2.12 nuttx/net

This directory contains the implementation of the NuttX internal socket APIs. The subdirectory, uip contains the uIP port.

2.13 nuttx/sched

The files forming core of the NuttX RTOS reside here.

2.14 nuttx/syscall

If NuttX is built as a separately compiled kernel (with CONFIG_NUTTX_KERNEL=y), then the contents of this directory are built. This directory holds a syscall interface that can be used for communication between user-mode applications and the kernel-mode RTOS.

2.15 nuttx/tools

This directory holds a collection of tools and scripts to simplify configuring, building and maintaining NuttX.

Refer to the README file in the tools directory for more information about the individual files. Some of these tools are discussed below as well in the discussion of configuring and building NuttX.

2.16 nuttx/Makefile

The top-level Makefile in the ${TOPDIR} directory contains all of the top-level control logic to build NuttX. Use of this Makefile to build NuttX is described below.

2.17 apps/netutils

This directory contains most of the network applications. Some of these are original with NuttX (like tftpc and dhcpd) and others were leveraged from the uIP-1.0 apps directory. As the uIP apps/README says, these applications "are not all heavily tested."

2.18 apps/nshlib

This directory contains for the core of the NuttShell (NSH) application.

2.19 apps/examples

Example and test programs to build against.

3.0 Configuring and Building

3.1 Configuring NuttX

Manual Configuration. Configuring NuttX requires only copying the board-specific configuration files into the top level directory which appears in the make files as the make variable, ${TOPDIR}. This could be done manually as follows:

Where <board-name> is the name of one of the sub-directories of the NuttX configs/ directory. This sub-directory name corresponds to one of the supported boards identified above. <config-dir> is the optional, specific configuration directory for the board. And <app-dir> is the location of the optional application directory.

Automated Configuration. There is a script that automates these steps. The following steps will accomplish the same configuration:

There is an alternative Windows batch file, configure.bat, that can be used instead of configure.sh in the windows native environment like:

See tools/README.txt for more information about these scripts.

If your application directory is not in the standard location (../apps or ../apps-<version>), then you should also specify the location of the application directory on the command line like:

Version Files. The NuttX build expects to find a version file located in the top-level NuttX build directory. That version file is called .version. The correct version file is installed in each versioned NuttX released. However, if you are working from an GIT snapshot, then there will be no version file. If there is no version file, the top-level Makefile will create a dummy .version file on the first make. This dummy version file will contain all zeroes for version information. If that is not what you want, they you should run the version.sh script to create a better .version file.

You can get help information from the version.sh script using the -h option. For example:

As an example, the following command will generate a version file for version 6.1 using the current GIT revision number:

The .version file is also used during the build process to create a C header file at include/nuttx/version.h that contains the same version information. That version file may be used by your C applications for, as an example, reporting version information.

Additional Configuration Steps. The remainder of configuration steps will be performed by ${TOPDIR}/Makefile the first time the system is built as described below.

3.2 Building NuttX

Building NuttX. Once NuttX has been configured as described above, it may be built as follows:

The ${TOPDIR} directory holds:

That directory also holds:

The setenv.sh contains Linux/Cygwin environmental settings that are needed for the build. The specific environmental definitions are unique for each board but should include, as a minimum, updates to the PATH variable to include the full path to the architecture-specific toolchain identified in Make.defs. The setenv.sh only needs to be source'ed at the beginning of a session. The system can be re-made subsequently by just typing make.

First Time Make. Additional configuration actions will be taken the first time that system is built. These additional steps include:

4.0 Architecture APIs

The file include/nuttx/arch.h identifies by prototype all of the APIs that must be provided by the architecture specific logic. The internal OS APIs that architecture-specific logic must interface with also also identified in include/nuttx/arch.h or in other header files.

4.1 Naming and Header File Conventions

4.2 APIs Exported by Architecture-Specific Logic to NuttX

4.2.1 up_initialize()

Prototype: void up_initialize(void);

Description. up_initialize() will be called once during OS initialization after the basic OS services have been initialized. The architecture specific details of initializing the OS will be handled here. Such things as setting up interrupt service routines, starting the clock, and registering device drivers are some of the things that are different for each processor and hardware platform.

up_initialize() is called after the OS initialized but before the init process has been started and before the libraries have been initialized. OS services and driver services are available.

4.2.2 up_idle()

Prototype: void up_idle(void);

Description. up_idle() is the logic that will be executed when their is no other ready-to-run task. This is processor idle time and will continue until some interrupt occurs to cause a context switch from the idle task.

Processing in this state may be processor-specific. e.g., this is where power management operations might be performed.

4.2.3 up_initial_state()

Prototype: void up_initial_state(FAR struct tcb_s *tcb);

Description. A new thread is being started and a new TCB has been created. This function is called to initialize the processor specific portions of the new TCB.

This function must setup the initial architecture registers and/or stack so that execution will begin at tcb->start on the next context switch.

This function may also need to set up processor registers so that the new thread executes with the correct privileges. If CONFIG_NUTTX_KERNEL has been selected in the NuttX configuration, then special initialization may need to be performed depending on the task type specified in the TCB's flags field: Kernel threads will require kernel-mode privileges; User tasks and pthreads should have only user-mode privileges. If CONFIG_NUTTX_KERNEL has not been selected, then all threads should have kernel-mode privileges.

4.2.4 up_create_stack()

Prototype: STATUS up_create_stack(FAR struct tcb_s *tcb, size_t stack_size, uint8_t ttype);

Description. Allocate a stack for a new thread and setup up stack-related information in the TCB.

The following TCB fields must be initialized:

This API is NOT required if CONFIG_CUSTOM_STACK is defined.

Input Parameters:

4.2.5 up_use_stack()

Prototype: STATUS up_use_stack(FAR struct tcb_s *tcb, FAR void *stack, size_t stack_size);

Description. Setup up stack-related information in the TCB using pre-allocated stack memory. This function is called only from task_init() when a task or kernel thread is started (never for pthreads).

The following TCB fields must be initialized:

This API is NOT required if CONFIG_CUSTOM_STACK is defined.

Input Parameters:

NOTE: Unlike up_stack_create() and up_stack_release, this function does not require the task type (ttype) parameter. The TCB flags will always be set to provide the task type to up_use_stack() if the information needs that information.

4.2.6 up_stack_frame()

Prototype: FAR void *up_stack_frame(FAR struct tcb_s *tcb, size_t frame_size);

Description. Allocate a stack frame in the TCB's stack to hold thread-specific data. This function may be called anytime after up_create_stack() or up_use_stack() have been called but before the task has been started.

Thread data may be kept in the stack (instead of in the TCB) if it is accessed by the user code directly. This includes such things as argv[]. The stack memory is guaranteed to be in the same protection domain as the thread.

The following TCB fields will be re-initialized:

This API is NOT required if CONFIG_NUTTX_KERNEL is undefined or if CONFIG_CUSTOM_STACK is defined.

Input Parameters:

Returned Value: A pointer to bottom of the allocated stack frame. NULL will be returned on any failures. The alignment of the returned value is the same as the alignment of the stack itself

4.2.7 up_release_stack()

Prototype: void up_release_stack(FAR struct tcb_s *dtcb);

Description. A task has been stopped. Free all stack related resources retained int the defunct TCB.

This API is NOT required if CONFIG_CUSTOM_STACK is defined.

Input Parameters:

4.2.8 up_unblock_task()

Prototype: void up_unblock_task(FAR struct tcb_s *tcb);

Description. A task is currently in an inactive task list but has been prepped to execute. Move the TCB to the ready-to-run list, restore its context, and start execution.

This function is called only from the NuttX scheduling logic. Interrupts will always be disabled when this function is called.

Input Parameters:

4.2.9 up_block_task()

Prototype: void up_block_task(FAR struct tcb_s *tcb, tstate_t task_state);

Description. The currently executing task at the head of the ready to run list must be stopped. Save its context and move it to the inactive list specified by task_state. This function is called only from the NuttX scheduling logic. Interrupts will always be disabled when this function is called.

Input Parameters:

4.2.10 up_release_pending()

Prototype: void up_release_pending(void);

Description. When tasks become ready-to-run but cannot run because pre-emption is disabled, they are placed into a pending task list. This function releases and makes ready-to-run all of the tasks that have collected in the pending task list. This can cause a context switch if a new task is placed at the head of the ready to run list.

This function is called only from the NuttX scheduling logic when pre-emption is re-enabled. Interrupts will always be disabled when this function is called.

4.2.11 up_reprioritize_rtr()

Prototype: void up_reprioritize_rtr(FAR struct tcb_s *tcb, uint8_t priority);

Description. Called when the priority of a running or ready-to-run task changes and the reprioritization will cause a context switch. Two cases:

  1. The priority of the currently running task drops and the next task in the ready to run list has priority.
  2. An idle, ready to run task's priority has been raised above the the priority of the current, running task and it now has the priority.

This function is called only from the NuttX scheduling logic. Interrupts will always be disabled when this function is called.

Input Parameters:

4.2.12 _exit()

Prototype: void _exit(int status) noreturn_function;

Description. This function causes the currently executing task to cease to exist. This is a special case of task_delete().

Unlike other UP APIs, this function may be called directly from user programs in various states. The implementation of this function should disable interrupts before performing scheduling operations.

4.2.13 up_assert()

Prototype:
void up_assert(FAR const uint8_t *filename, int linenum);

Description. Assertions may be handled in an architecture-specific way.

4.2.14 up_schedule_sigaction()

Prototype: void up_schedule_sigaction(FAR struct tcb_s *tcb, sig_deliver_t sigdeliver);

Description. This function is called by the OS when one or more signal handling actions have been queued for execution. The architecture specific code must configure things so that the 'sigdeliver' callback is executed on the thread specified by 'tcb' as soon as possible.

This function may be called from interrupt handling logic.

This operation should not cause the task to be unblocked nor should it cause any immediate execution of sigdeliver. Typically, a few cases need to be considered:

  1. This function may be called from an interrupt handler During interrupt processing, all xcptcontext structures should be valid for all tasks. That structure should be modified to invoke sigdeliver() either on return from (this) interrupt or on some subsequent context switch to the recipient task.
  2. If not in an interrupt handler and the tcb is NOT the currently executing task, then again just modify the saved xcptcontext structure for the recipient task so it will invoke sigdeliver when that task is later resumed.
  3. If not in an interrupt handler and the tcb IS the currently executing task -- just call the signal handler now.

This API is NOT required if CONFIG_DISABLE_SIGNALS is defined.

4.2.15 up_allocate_heap()

Prototype: void up_allocate_heap(FAR void **heap_start, size_t *heap_size);

Description. This function will be called to dynamically set aside the heap region.

For the kernel build (CONFIG_NUTTX_KERNEL=y) with both kernel- and user-space heaps (CONFIG_MM_KERNEL_HEAP=y), this function provides the size of the unprotected, user-space heap. If a protected kernel-space heap is provided, the kernel heap must be allocated (and protected) by an analogous up_allocate_kheap().

4.2.16 up_interrupt_context()

Prototype: bool up_interrupt_context(void)

Description. Return true if we are currently executing in the interrupt handler context.

4.2.17 up_disable_irq()

Prototype:

Description. Disable the IRQ specified by 'irq' On many architectures, there are three levels of interrupt enabling: (1) at the global level, (2) at the level of the interrupt controller, and (3) at the device level. In order to receive interrupts, they must be enabled at all three levels.

This function implements enabling of the device specified by 'irq' at the interrupt controller level if supported by the architecture (irqsave() supports the global level, the device level is hardware specific).

If the architecture does not support up_disable_irq, CONFIG_ARCH_NOINTC should be defined in the NuttX configuration file. Since this API cannot be supported on all architectures, it should be avoided in common implementations where possible.

4.2.18 up_enable_irq()

Prototype:

Description. This function implements disabling of the device specified by 'irq' at the interrupt controller level if supported by the architecture (irqrestore() supports the global level, the device level is hardware specific).

If the architecture does not support up_disable_irq, CONFIG_ARCH_NOINTC should be defined in the NuttX configuration file. Since this API cannot be supported on all architectures, it should be avoided in common implementations where possible.

4.2.19 up_prioritize_irq()

Prototype:

Description. Set the priority of an IRQ.

If the architecture supports up_enable_irq, CONFIG_ARCH_IRQPRIO should be defined in the NuttX configuration file. Since this API cannot be supported on all architectures, it should be avoided in common implementations where possible.

4.2.20 up_putc()

Prototype: int up_putc(int ch);

Description. This is a debug interface exported by the architecture-specific logic. Output one character on the console

4.2.21 System Time and Clock

4.2.21.1 Basic System Timer

System Timer In most implementations, system time is provided by a timer interrupt. That timer interrupt runs at rate determined by CONFIG_MSEC_PER_TICKS (default 10 or 100Hz). The timer generates an interrupt each CONFIG_MSEC_PER_TICKS milliseconds and increments a counter called g_system_timer. g_system_timer then provides a time-base for calculating up-time and elapsed time intervals in units of CONFIG_MSEC_PER_TICKS. The range of g_system_timer is, by default, 32-bits. However, if the MCU supports type long long and CONFIG_SYSTEM_TIME16 is selected, a 64-bit system timer will be supported instead.

System Timer Accuracy On many system, the exact timer interval specified by CONFIG_MSEC_PER_TICKS cannot be achieved due to limitations in frequencies or in dividers. As a result, the time interval specified by CONFIG_MSEC_PER_TICKS may only be approximate and there may be small errors in the apparent up-time time. These small errors, however, will accumulate over time and after a long period of time may have an unacceptably large error in the apparent up-time of the MCU.

If the timer tick period generated by the hardware is not exactly CONFIG_MSEC_PER_TICKS and if there you require accurate up-time for the MCU, then there are measures that you can take:

Delta-Sigma Modulation Example. Consider this case: The system timer is a count-up timer driven at 32.768KHz. There are dividers that can be used, but a divider of one yields the highest accuracy. This counter counts up until the count equals a match value, then a timer interrupt is generated. The desire frequency is 100Hz (CONFIG_MSEC_PER_TICKS is 10).

This exact frequency of 100Hz cannot be obtained in this case. In order to obtain that exact frequency a match value of 327.68 would have to be provided. The closest integer value is 328 but the ideal match value is between 327 and 328. The closest value, 328, would yield an actual timer frequency of 99.9Hz! That will may cause significant timing errors in certain usages.

Use of Delta-Sigma Modulation can eliminate this error in the long run. Consider this example implementation:

  1. Initially an accumulator is zero an the match value is programmed to 328:
  2. On each timer interrupt, accumulator is updated with difference that, in this reflects, 100* the error in interval that just passed. So on the first timer interrupt, the accumulator would be updated like:
  3. And on that same timer interrupt a new match value would be programmed:

In this way, the timer interval is controlled from interrupt-to-interrupt to produce an average frequency of exactly 100Hz.

4.2.21.2 Hardware

To enable hardware module use the following configuration options:

which requires the following base functions to read and set time:

4.2.21.3 System Tick and Time

The system tick is represented by::

Running at rate of system base timer, used for time-slicing, and so forth.

If hardware RTC is present (CONFIG_RTC) and and high-resolution timing is enabled (CONFIG_RTC_HIRES), then after successful initialization variables are overridden by calls to up_rtc_gettime() which is running continuously even in power-down modes.

In the case of CONFIG_RTC_HIRES is set the g_system_timer keeps counting at rate of a system timer, which however, is disabled in power-down mode. By comparing this time and RTC (actual time) one may determine the actual system active time. To retrieve that variable use:

4.2.22 Address Environments

CPUs that support memory management units (MMUs) may provide address environments within which tasks and their child threads execute. The configuration indicates the CPUs ability to support address environments by setting the configuration variable CONFIG_ADDRENV=y. These address environments are created only when tasks are created via exec() or exec_module() (see include/nuttx/binfmt/binfmt.h).

When CONFIG_ADDRENV=y is set in the board configuration, the CPU-specific logic must provide a set of interfaces as defined in the header file include/nuttx/arch.h. These interfaces are listed below and described in detail in the following paragraphs.

The CPU-specific logic must provide two categories in interfaces:

  1. Binary Loader Support. These are low-level interfaces used in binfmt/ to instantiate tasks with address environments. These interfaces all operate on type task_addrenv_t which is an abstract representation of a asks's address environment and must be defined in arch/arch.h if CONFIG_ADDRENV is defined. These low-level interfaces include:

  2. Tasking Support. Other interfaces must be provided to support higher-level interfaces used by the NuttX tasking logic. These interfaces are* used by the functions in sched/ and all operate on the TCB which as been assigned an address environment by up_addrenv_assign().

4.2.22.1 up_addrenv_create()

Prototype:

Description:

Input Parameters:

Returned Value:

4.2.22.2 up_addrenv_vaddr()

Prototype:

Description:

Input Parameters:

Returned Value:

4.2.22.3 up_addrenv_select()

Prototype:

Description:

Input Parameters:

Returned Value:

4.2.22.4 up_addrenv_restore()

Prototype:

Description:

Input Parameters:

Returned Value:

4.2.22.5 up_addrenv_destroy()

Prototype:

Description:

Input Parameters:

Returned Value:

4.2.22.6 up_addrenv_assign()

Prototype:

Description:

Input Parameters:

Returned Value:

4.2.22.7 up_addrenv_share()

Prototype:

Description:

Input Parameters:

Returned Value:

4.2.22.8 up_addrenv_release()

Prototype:

Description:

Input Parameters:

Returned Value:

4.3 APIs Exported by NuttX to Architecture-Specific Logic

These are standard interfaces that are exported by the OS for use by the architecture specific logic.

4.3.1 os_start()

To be provided

4.3.2 OS List Management APIs

To be provided

4.3.3 sched_process_timer()

Prototype: void sched_process_timer(void);

Description. This function handles system timer events. The timer interrupt logic itself is implemented in the architecture specific code, but must call the following OS function periodically -- the calling interval must be MSEC_PER_TICK.

4.3.4 irq_dispatch()

Prototype: void irq_dispatch(int irq, FAR void *context);

Description. This function must be called from the architecture- specific logic in order to display an interrupt to the appropriate, registered handling logic.

4.4 On-Demand Paging

The NuttX On-Demand Paging feature permits embedded MCUs with some limited RAM space to execute large programs from some non-random access media. If the platform meets certain requirements, then NuttX can provide on-demand paging: It can copy .text from the large program in non-volatile media into RAM as needed to execute a huge program from the small RAM. Design and porting issues for this feature are discussed in a separate document. Please see the NuttX Demand Paging design document for further information.

4.5 LED Support

A board architecture may or may not have LEDs. If the board does have LEDs, then most architectures provide similar LED support that is enabled when CONFIG_ARCH_LEDS is selected in the NuttX configuration file. This LED support is part of architecture-specific logic and is not managed by the core NuttX logic. However, the support provided by each architecture is sufficiently similar that it can be documented here.

4.5.1 Header Files

LED-related definitions are provided in two header files:

4.5.2 LED Definitions

The implementation of LED support is very specific to a board architecture. Some boards have several LEDS, others have only one or two. Some have none. Others LED matrices and show alphanumeric data, etc. The NuttX logic does not refer to specific LEDS, rather, it refers to an event to be shown on the LEDS in whatever manner is appropriate for the board; the way that this event is presented depends upon the hardware available on the board.

The model used by NuttX is that the board can show 8 events defined as follows in <board-name>/include/board.h:

The specific value assigned to each pre-processor variable can be whatever makes the implementation easiest for the board logic. The meaning associated with each definition is as follows:

4.5.3 Common LED interfaces

The <arch-name>/src/common/up_internal.h probably has definitions like:

Where:

5.0 NuttX File System

Overview. NuttX includes an optional, scalable file system. This file-system may be omitted altogether; NuttX does not depend on the presence of any file system.

Pseudo Root File System. Or, a simple in-memory, pseudo file system can be enabled. This simple file system can be enabled setting the CONFIG_NFILE_DESCRIPTORS option to a non-zero value (see Appendix A). This is an in-memory file system because it does not require any storage medium or block driver support. Rather, file system contents are generated on-the-fly as referenced via standard file system operations (open, close, read, write, etc.). In this sense, the file system is pseudo file system (in the same sense that the Linux /proc file system is also referred to as a pseudo file system).

Any user supplied data or logic can be accessed via the pseudo-file system. Built in support is provided for character and block drivers in the /dev pseudo file system directory.

Mounted File Systems The simple in-memory file system can be extended my mounting block devices that provide access to true file systems backed up via some mass storage device. NuttX supports the standard mount() command that allows a block driver to be bound to a mountpoint within the pseudo file system and to a file system. At present, NuttX supports the standard VFAT and ROMFS file systems, a special, wear-leveling NuttX FLASH File System (NXFFS), as well as a Network File System client (NFS version 3, UDP).

Comparison to Linux From a programming perspective, the NuttX file system appears very similar to a Linux file system. However, there is a fundamental difference: The NuttX root file system is a pseudo file system and true file systems may be mounted in the pseudo file system. In the typical Linux installation by comparison, the Linux root file system is a true file system and pseudo file systems may be mounted in the true, root file system. The approach selected by NuttX is intended to support greater scalability from the very tiny platform to the moderate platform.

6.0 NuttX Device Drivers

NuttX supports a variety of device drivers including:

These different device driver types are discussed in the following paragraphs. Note: device driver support requires that the in-memory, pseudo file system is enabled by setting the CONFIG_NFILE_DESCRIPTORS in the NuttX configuration file to a non-zero value.

6.1 Character Device Drivers

Character device drivers have these properties:

6.2 Block Device Drivers

Block device drivers have these properties:

6.3 Specialized Device Drivers

6.3.1 Ethernet Device Drivers

6.3.2 SPI Device Drivers

6.3.3 I2C Device Drivers

6.3.4 Serial Device Drivers

6.3.5 Frame Buffer Drivers

6.3.6 LCD Drivers

6.3.7 Memory Technology Device Drivers

6.3.8 SDIO Device Drivers

6.3.9 USB Host-Side Drivers

6.3.10 USB Device-Side Drivers

6.3.11 Analog (ADC/DAC) Drivers

The NuttX analog drivers are split into two parts:

  1. An "upper half", generic driver that provides the common analog interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level controls to implement the analog functionality.

6.3.11.1 ADC Drivers

6.3.11.2 DAC Drivers

6.3.12 PWM Drivers

For the purposes of this driver, a PWM device is any device that generates periodic output pulses of controlled frequency and pulse width. Such a device might be used, for example, to perform pulse-width modulated output or frequency/pulse-count modulated output (such as might be needed to control a stepper motor).

The NuttX PWM driver is split into two parts:

  1. An "upper half", generic driver that provides the common PWM interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level timer controls to implement the PWM functionality.

Files supporting PWM can be found in the following locations:

6.3.13 CAN Drivers

NuttX supports only a very low-level CAN driver. This driver supports only the data exchange and does not include any high-level CAN protocol. The NuttX CAN driver is split into two parts:

  1. An "upper half", generic driver that provides the common CAN interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level timer controls to implement the CAN functionality.

Files supporting CAN can be found in the following locations:

6.3.14 Quadrature Encoder Drivers

NuttX supports a low-level, two-part Quadrature Encoder driver.

  1. An "upper half", generic driver that provides the common Quadrature Encoder interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level timer controls to implement the Quadrature Encoder functionality.

Files supporting the Quadrature Encoder can be found in the following locations:

6.3.15 Watchdog Timer Drivers

NuttX supports a low-level, two-part watchdog timer driver.

  1. An "upper half", generic driver that provides the common watchdog timer interface to application level code, and
  2. A "lower half", platform-specific driver that implements the low-level timer controls to implement the watchdog timer functionality.

Files supporting the watchdog timer can be found in the following locations:

6.3.16 Keyboard/Keypad Drivers

Keypads vs. Keyboards Keyboards and keypads are really the same devices for NuttX. A keypad is thought of as simply a keyboard with fewer keys.

Special Commands. In NuttX, a keyboard/keypad driver is simply a character driver that may have an (optional) encoding/decoding layer on the data returned by the character driver. A keyboard may return simple text data (alphabetic, numeric, and punctuation) or control characters (enter, control-C, etc.) when a key is pressed. We can think about this the "normal" keyboard data stream. However, in addition, most keyboards support actions that cannot be represented as text or control data. Such actions include things like cursor controls (home, up arrow, page down, etc.), editing functions (insert, delete, etc.), volume controls, (mute, volume up, etc.) and other special functions. In this case, some special encoding may be required to multiplex the normal text data and special command key press data streams.

Key Press and Release Events Sometimes the time that a key is released is needed by applications as well. Thus, in addition to normal and special key press events, it may also be necessary to encode normal and special key release events.

Encoding/Decoding Layer. An optional encoding/decoding layer can be used with the basic character driver to encode the keyboard events into the text data stream. The function interfaces that comprise that encoding/decoding layer are defined in the header file include/nuttx/input/kbd_code.h. These functions provide an matched set of (a) driver encoding interfaces, and (b) application decoding interfaces.

  1. Driver Encoding Interfaces. These are interfaces used by the keyboard/keypad driver to encode keyboard events and data.

  2. Application Decoding Interfaces. These are user interfaces to decode the values returned by the keyboard/keypad driver.

I/O Streams. Notice the use of the abstract I/O streams in these interfaces. These stream interfaces are defined in include/nuttx/streams.h.

6.4 Power Management

6.4.1 Overview

NuttX supports a simple power management (PM) sub-system. This sub-system:

The PM sub-system integrates the MCU idle loop with a collection of device drivers to support:

Various "sleep" and low power consumption states have various names and are sometimes used in conflicting ways. In the NuttX PM logic, we will use the following terminology:

NORMAL
The normal, full power operating mode.
IDLE
This is still basically normal operational mode, the system is, however, IDLE and some simple simple steps to reduce power consumption provided that they do not interfere with normal Operation. Simply dimming the a backlight might be an example somethat that would be done when the system is idle.
STANDBY
Standby is a lower power consumption mode that may involve more extensive power management steps such has disabling clocking or setting the processor into reduced power consumption modes. In this state, the system should still be able to resume normal activity almost immediately.
SLEEP
The lowest power consumption mode. The most drastic power reduction measures possible should be taken in this state. It may require some time to get back to normal operation from SLEEP (some MCUs may even require going through reset).

These various states are represented with type enum pm_state_e in include/nuttx/power/pm.h.

6.4.2 Interfaces

All PM interfaces are declared in the file include/nuttx/power/pm.h.

6.4.2.1 pm_initialize()

Function Prototype:

Description: This function is called by MCU-specific one-time at power on reset in order to initialize the power management capabilities. This function must be called very early in the initialization sequence before any other device drivers are initialize (since they may attempt to register with the power management subsystem).

Input Parameters: None

Returned Value: None

6.4.2.2 pm_register()

Function Prototype:

Description: This function is called by a device driver in order to register to receive power management event callbacks. Refer to the PM Callback section for more details.

Input Parameters:

callbacks
An instance of struct pm_callback_s providing the driver callback functions.

Returned Value: Zero (OK) on success; otherwise a negated errno value is returned.

6.4.2.3 pm_activity()

Function Prototype:

Description: This function is called by a device driver to indicate that it is performing meaningful activities (non-idle). This increment an activity count and/or will restart a idle timer and prevent entering reduced power states.

Input Parameters:

priority
Activity priority, range 0-9. Larger values correspond to higher priorities. Higher priority activity can prevent the system from entering reduced power states for a longer period of time. As an example, a button press might be higher priority activity because it means that the user is actively interacting with the device.

Returned Value: None

Assumptions: This function may be called from an interrupt handler (this is the ONLY PM function that may be called from an interrupt handler!).

6.4.2.4 pm_checkstate()

Function Prototype:

Description: This function is called from the MCU-specific IDLE loop to monitor the power management conditions. This function returns the "recommended" power management state based on the PM configuration and activity reported in the last sampling periods. The power management state is not automatically changed, however. The IDLE loop must call pm_changestate() in order to make the state change.

These two steps are separated because the platform-specific IDLE loop may have additional situational information that is not available to the PM sub-system. For example, the IDLE loop may know that the battery charge level is very low and may force lower power states even if there is activity.

NOTE: That these two steps are separated in time and, hence, the IDLE loop could be suspended for a long period of time between calling pm_checkstate() and pm_changestate(). The IDLE loop may need to make these calls atomic by either disabling interrupts until the state change is completed.

Input Parameters: None

Returned Value: The recommended power management state.

6.4.2.5 pm_changestate()

Function Prototype:

Description: This function is used by platform-specific power management logic. It will announce the power management power management state change to all drivers that have registered for power management event callbacks.

Input Parameters:

newstate
Identifies the new PM state

Returned Value: 0 (OK) means that the callback function for all registered drivers returned OK (meaning that they accept the state change). Non-zero means that one of the drivers refused the state change. In this case, the system will revert to the preceding state.

Assumptions: It is assumed that interrupts are disabled when this function is called. This function is probably called from the IDLE loop... the lowest priority task in the system. Changing driver power management states may result in renewed system activity and, as a result, can suspend the IDLE thread before it completes the entire state change unless interrupts are disabled throughout the state change.

6.4.3 Callbacks

The struct pm_callback_s includes the pointers to the driver callback functions. This structure is defined include/nuttx/power/pm.h. These callback functions can be used to provide power management information to the driver.

6.4.3.1 prepare()

Function Prototype:

Description: Request the driver to prepare for a new power state. This is a warning that the system is about to enter into a new power state. The driver should begin whatever operations that may be required to enter power state. The driver may abort the state change mode by returning a non-zero value from the callback function.

Input Parameters:

cb
Returned to the driver. The driver version of the callback structure may include additional, driver-specific state data at the end of the structure.
pmstate
Identifies the new PM state

Returned Value: Zero (OK) means the event was successfully processed and that the driver is prepared for the PM state change. Non-zero means that the driver is not prepared to perform the tasks needed achieve this power setting and will cause the state change to be aborted. NOTE: The prepare() method will also be called when reverting from lower back to higher power consumption modes (say because another driver refused a lower power state change). Drivers are not permitted to return non-zero values when reverting back to higher power consumption modes!

6.4.3.1 notify()

Function Prototype:

Description: Notify the driver of new power state. This callback is called after all drivers have had the opportunity to prepare for the new power state.

Input Parameters:

cb
Returned to the driver. The driver version of the callback structure may include additional, driver-specific state data at the end of the structure.
pmstate
Identifies the new PM state

Returned Value: None. The driver already agreed to transition to the low power consumption state when when it returned OK to the prepare() call.

Appendix A: NuttX Configuration Settings

At one time, this section provided a list of all NuttX configuration variables. However, NuttX has since converted to use the kconfig-frontends tools. Now, the NuttX configuration is determined by a self-documenting set of Kconfig files.

The current NuttX configuration variables are also documented in separate, auto-generated configuration variable document. That configuration variable document is generated using the kconfig2html tool that can be found in the nuttx/tools directory. That tool analyzes the NuttX Kconfig files and generates excruciatingly boring HTML document.

The latest boring configuration variable documentation can be regenerated at any time using that tool or, more appropriately, the wrapper script at nuttx/tools/mkconfigvars.sh. That script will generate the file nuttx/Documentation/NuttXConfigVariables.html.

The version of NuttXConfigVariables.html for the last released version of NuttX can also be found online.

Appendix B: Trademarks

  • ARM, ARM7 ARM7TDMI, ARM9, ARM920T, ARM926EJS, Cortex-M3 are trademarks of Advanced RISC Machines, Limited.
  • Cygwin is a trademark of Red Hat, Incorporated.
  • Linux is a registered trademark of Linus Torvalds.
  • Eagle-100 is a trademark of Micromint USA, LLC.
  • LPC2148 is a trademark of NXP Semiconductors.
  • TI is a trade name of Texas Instruments Incorporated.
  • UNIX is a registered trademark of The Open Group.
  • VxWorks is a registered trademark of Wind River Systems, Incorporated.
  • ZDS, ZNEO, Z16F, Z80, and Zilog are a registered trademark of Zilog, Inc.
  • NOTE: NuttX is not licensed to use the POSIX trademark. NuttX uses the POSIX standard as a development guideline only.