Playing the Nut/OS Configuration Game
Initially Nut/OS had been written for a fixed hardware, the Ethernut 1 with an ATmega103 MCU and an RTL8019AS Ethernet controller. And the only supported development environment had been AVRGCC running on the Windows PC. Good old days.
Today we have to support different targets with different CPUs, Ethernet controllers, memory layouts and I/O hardware as well as different development platforms with different compilers. Thus, system configuration became an important part.
Since Nut/OS 3.9, a GUI became available to simplify the configuration task. The Nut/OS Software Manual provides a step by step guide on how to use this tool.
In this paper I will present some more details about the possibilities to configure the system and how the Configurator uses these capabibilities.
The Make Tool
Regardless of which development environment is used to build Nut/OS, GNU Make takes over the top level control. Explaining this tool in detail is beyond the scope of this paper and to many developers of embedded systems it looks like C to PASCAL programmers, quite cryptic. But as often, the number of options make a tool look complicated while the main function is quite simple.
When started without any option, Make will look for a file named Makefile, interpret it and perform the related actions. Some Makefiles may contain weird sequencies of characters, but all of them contain something like
target: dependencies actions
target
, dependencies
and actions
are required. The three items used here are just placeholders. A
real example may look like
simple.hex: simple.c simple.h avr-gcc -c -mmcu=atmega128 simple.c -o simple.o avr-gcc simple.o -mmcu=atmega128 -lnutdev -lnutos -o simple.elf avr-objcopy -O ihex simple.elf simple.hex
You may now argue, that this could be done by a simple batch file or shell script. You are probably right. Now imagine, what a large number of actions would be required to compile all Nut/OS source files for all supported platforms, if we would do it this way. Make simplifies this by allowing us to specify general rules like
%o : %c avr-gcc -c -mmcu=atmega128 $< -o $@ %elf: %o avr-gcc $< -mmcu=atmega128 -lnutdev -lnutos -o $@ %hex: %elf avr-objcopy -O ihex $< $@
simple.hex: simple.c simple.h
simple.hex
from simple.c
and simple.h
. That's
cool, isn't it?
Another nice feature of Make is, that it checks automatically,
which actions are required after you modified something. In other
words, if you edit simple.h
, save your changes and
run Make again, it will run all three steps again. If you modified
anything else but neither simple.c
or simple.h
,
then Make will keep the already existing simple.hex
.
However, the Nut/OS Makefiles are incomplete here, because they do not list all dependencies. All header files are excluded. Thus, if any header file is changed, you need to run
make clean make
clean
is an explicit
target given on the command line. Somewhere in the Makefile
there is something found like
clean: rm *.o *.hex *.elf
rm
is similar
to the DOS command del
. To avoid complete Makefile
madness, some basic UNIX commands are included in the Windows
distribution of Nut/OS as executables. You can find them in the
directory tools/win32
. When running Make in a DOS
window, make sure that your PATH points to this directory.
When the Configurator builds Nut/OS, it will set the correct path
and will always run make clean
first. The next chapter
will show more details about how the Configurator uses Make.
Make and the Nut/OS Configurator
We assume, that all Configurator settings have been specified by following the Nut/OS Software Manual and that we are ready to create a new Nut/OS build tree.
When selecting Generate Build Tree
from the Configurators
Build
menu, then the directory for building Nut/OS
will be created with all required subdirectories. In the top level
directory of the build tree, the Configurator will additionally create
three files, which will be used by the Make tool.
The first of these files is named Makefile. It's a very simple
Makefile with three targets: all
, install
and clean
.
all: $(MAKE) -C arch ... install: $(MAKE) -C arch install ... clean: $(MAKE) -C arch clean ...
$(MAKE)
. That's how
Make understands definitions similar to C makros. It may be
defined somewhere else as MAKE=make
, however,
specially $(MAKE)
is internally predefined. The
second feature is the command line option -C directory
.
This instructs make to change to the specified directory before
starting to read the Makefile.
As you can see, for each target Make is called for each Nut/OS module subdirectory with the same target again. Of course, the Configurator also creates the Makefiles in these subdirectories, which are much more interesting than the top level Makefile.
Let's stay at the top level, where the Configurator created
two additional files, NutConf.mk
and UserConf.mk
.
NutConf.mk
defines several macros, similar to
the internally defined $(MAKE)
, which I explained
above. When building Nut/OS for the ATmega128 using AVRGCC, this
file contains
MCU_ATMEGA128=atmega128 MCU_ATMEGA103=atmega103 MCU=$(MCU_ATMEGA128) HWDEF=-D__HARVARD_ARCH__ CRUROM=crurom include $(top_blddir)/UserConf.mk
$(MCU)
appears in a Makefile, Make will
replace it with $(MCU_ATMEGA128)
, which in turn is
replaced by atmega128
.
The last line of NutConf.mk instructs Make to include
UserConf.mk
, which initially contains a
single line
HWDEF += -DETHERNUT2
NutConf.mk
, $(HWDEF)
will be
finally expanded to -D__HARVARD_ARCH__ -DETHERNUT2
.
As long as you are using the Ethernut 2 hardware, this is
just fine. When building Nut/OS, this compile option is simply
ignored. However, the same entry is added by the Configurator
when creating the sample directory and should be removed
before compiling any of the samples. Hey, wait, we are
building Nut/OS. More on the sample applications later.
Each time, when you re-create the build tree, the Configurator
will overwrite existing files in the build tree. Thus, do not
edit them, because your changes will get lost. UserConf.mk
is an exception. It is initially created, if it doesn't exists. If it
exists, the Configurator will not touch it. You can use it to add
additional compile options, for example.
HWDEF += -g3 -gdwarf-2
Now let's dive into one of the subdirectories and see, what the Configurator created there. We choose subdirectory os, which mostly contains the Nut/OS kernel code. Wonder! It contains nothing but another Makefile.
PROJ = libnutos top_srcdir = c:/ethernut/nut top_blddir = c:/ethernut/nutbld VPATH = $(top_srcdir)/os SRCS = heap.c bankmem.c thread.c timer.c event.c devreg.c confos.c version.c \ semaphore.c mutex.c msg.c osdebug.c tracer.c OBJ1 = nutinit.o OBJS = $(SRCS:.c=.o) include $(top_blddir)/NutConf.mk include $(top_srcdir)/Makedefs.avr-gcc INCFIRST=$(INCPRE)$(top_blddir)/include all: $(PROJ).a $(OBJS) $(OBJ1) install: $(PROJ).a $(OBJ1) $(CP) $(PROJ).a c:/ethernut/nutbld/lib/$(PROJ).a $(CP) $(OBJ1) c:/ethernut/nutbld/lib/$(notdir $(OBJ1)) include $(top_srcdir)/Makerules.avr-gcc .PHONY: clean clean: cleancc cleanedit -rm -f $(PROJ).a -rm -f $(OBJ1)
OBJS = $(SRCS:.c=.o)
,
which is hated so much by Makefile ignorants and which we will not explain
here either. Instead we concentrate on those parts, which are relevant
to the Nut/OS configuration. Most important is the list of source files
SRCS = heap.c bankmem.c thread.c timer.c event.c devreg.c confos.c version.c \ semaphore.c mutex.c msg.c osdebug.c tracer.c
SRCS
line. As a result, the final
Nut/OS libraries will contain only those modules, which are
available on your target platform.
Not less important lines in os/Makefile
are
... include $(top_blddir)/NutConf.mk ... include $(top_srcdir)/Makedefs.avr-gcc ... include $(top_srcdir)/Makerules.avr-gcc ...
NutConf.mk
, which we discussed
above. The other two include two files from the Nut/OS source tree.
If you look into the top level source directory, you will see a
number of files named Makedefs
and Makerules
with different file extensions. When building with AVRGCC, the
Configurator will add includes of Makedefs.avr-gcc
and
Makerules.avr-gcc
. When building with ARMGCC, it
will use Makedefs.arm-gcc
and Makerules.arm-gcc
instead. Again, newbies may consider the contents of these files
weird stuff. That's just fine. Under normal circumstances you
will have no reason to deal with these. Simply note, that they
contain nothing but special definitions for the compiler you are
using. If one of the gurus ever passes you a modified version, for
example with JTAG debug options, then copy them into the top level
source directory with a different extension. The Configurator
automatically scans the source directory for such files. Next time you
open the second page in the Configurator's settings dialog, you can
select the new extension in the platform drop down box.
An important final advice: Each time you changed anything in the Configurator settings, you must recreate the build tree.
Nut/OS Options
Up to this point, configurations were mainly related to the compiler and the target CPU. The majority of configurations is related to hardware specifications and various other build options, though. We will now see, how the Configurator uses C header files to pass these setting to the compiler.
Let's assume, we want to enable floating point support for
printf
and scanf
. In the module tree
we expand C Runtime (Traget Specific)
and
File Streams
below that. Then we can enable
Floating Point
by clicking on the related check box.
If we create a new or re-create an existing build tree, then
the Configurator adds a new header file named crt.h
in the subdirectory include/cfg
of your build tree,
which contains:
#define STDIO_FLOATING_POINT
include/cfg/crt.h
in the source
directory does not contain this entry.
Here's a snippet form the Nut/OS source code:
#include... #ifdef STDIO_FLOATING_POINT /* This is floating point stuff. */ .... #endif ...
include/cfg/crt.h
defines STDIO_FLOATING_POINT
.
The one in the build tree does, the one in the source tree doesn't.
Now we are back to the Makefiles of the build tree, in our case
crt/Makefile
.
top_blddir = c:/ethernut/icnutbld ... INCFIRST=$(INCPRE)$(top_blddir)/include
Makerules.avr-gcc
:
%o : %c $(CC) -c $(CPFLAGS) $(INCFIRST) -I$(INCDIR) $(INCLAST) $< -o $@
include/cfg/crt.h
in the build tree first.
If it couldn't be found there, the search will continue in
the source tree. This way the Configurator is able to overwrite
existing default header files in the source tree without
any modification of the source tree. This is a real advantage,
if you want to build Nut/OS for different targets. You can
use the same source tree for each target. And if you later upgrade
to a new version of Nut/OS, you can replace the source tree
whithout losing your specific configurations, because all of them
are located in the build tree.
The Application Tree
Beside the source and the build tree, a third one is used to
build Nut/OS applications. It it created by selecting
Create Sample Directory
in the Configurator's
Build
menu. There's not much mystery in here,
but it comes quite handy for creating your own applications.
Users of the ImageCraft IDE are lucky people. They can use the GUI to create new project files in the sample directory. Please follow the steps in the Nut/OS Software Manual.
For using GCC on the command line, just copy one of the subdirectories to a new one with a new name within the sample directory. When adding new or removing existing source files, make sure to update the Makefile accordingly. Even if you don't understand Makefiles, this is quite simple. Be aware, that actions in Makefiles are preceded with a TAB character, spaces won't work. Your text editor should preserve tabs.
Remember UserConf.mk
? When running the compiler on
the command line, the default contents created by the Configurator
may hurt now, if we do not intend to run the application on Ethernut 2.
All samples have been written for Ethernut 1 (including Charon II and
other boards using the Realtek Ethernet Controller) and Ethernut 2.
All samples with network code contain the following lines:
#ifdef ETHERNUT2 #include <dev/lanc111.h> #else #include <dev/nicrtl.h> #endif
UserConf.mk
, then the
application will register the LAN91C111 driver. Otherwise the
driver for the RTL8019AS is used. If you want the latter, do not
forget to remove the related line in UserConf.mk
.
In newer releases WOLF
is another option and uses
the AX88796 driver. For this controller simply replace ETHERNUT2
by WOLF
in UserConf.mk
.
Under the Hood
As stated above, the GNU Make Tool controls the top level. The bottom level is controlled by Lua, an interpreter language. Mainly because of compatibility problems with Lua libraries on various Linux platforms, the decision to use Lua had been criticized sometimes. But the author is still convinced, that this had been a good decision. The Lua interpreter is extremely small and the language is extremely powerfull. Right now this is hard to believe, because the Configurator uses Lua in a static way. But one day it will shine brighter than everything else...:-)
Neither Lua nor all Nut/OS options will be described here. Just a few details will be discussed.
All Lua scripts are located in the subdirectory conf
within the source tree. When the Configurator starts, it will
read repository.nut
first. This file specifies
additional scripts.
{ name = "nutarch", brief = "Target", description = "Select one only.", subdir = "arch", script = "arch/arch.nut" },
arch/arch.nut
is specified as an additional
script to be included, which contains:
{ macro = "MCU_ATMEGA128", brief = "Atmel ATmega 128", description = "8-bit RISC microcontroller with 128K bytes flash, 4K bytes RAM, ".. "4K bytes EEPROM, 64K bytes data memory space, 2 USARTs, 4 timers, ".. "8-channel ADC, SPI and TWI.", requires = { "TOOL_CC_AVR" }, provides = { "HW_TARGET", "HW_MCU_AVR", "HW_MCU_AVR_ENHANCED", "HW_MCU_ATMEGA128", "HW_NVMEM", "HW_TIMER_AVR", "HW_UART_AVR" }, flavor = "boolean", file = "include/cfg/arch.h", makedefs = { "MCU=$(MCU_ATMEGA128)", "HWDEF=-D__HARVARD_ARCH__" } },
Atmel ATmega 128
is activated.
In addition, the Configurator will #define MCU_ATMEGA128
in this file.
The last definition makedefs =
looks familiar,
doesn't it? Indeed it specifies a list of entries, which
will be added to NutConf.mk
.
However, before you are able to access the module tree
in the Configurator's main window, you will be asked to
select a configuration file. These files got the extension
.conf
and are also located in the conf
subdirectory of the Nut/OS source tree. They contain simple
assignments. Here's the one, which specifies options for running
Nut/OS on the STK501 with an ATmega128 and the AVRGCC:
AVR_GCC = "" MCU_ATMEGA128 = "" NUTMEM_SIZE = "4096" NUTMEM_START = "0x100" NUTMEM_RESERVED = "64"
macro =
entries
in the Lua scripts. The first two do not assign any values. The
fact, that they appear in this file, sets the related boolean
Lua macro definitions to true.
There are other options handled by Lua, but you should have
got the picture. In the current state of the Configurator
this is quite helpful, because this tool is far from being
perfect. For example, radio boxes are not implemented and the
user must take care to enable one CPU or one compiler only and
disable all others. One annoying thing is, that the compiler
needs to be specified at two places, in the Configurator's
setting notebook as well as in the component tree. Furthermore,
not all options seem to make their way into the final build.
If something seems to go wrong, check the include/cfg
directory in the build tree and further check, wether these
files are actually included in the related Nut/OS code. But before
panic breaks out: The mainstream works fine, exotic options may fail.
Changing Environments
In the previous chapter we learned, that the configuration of the Configurator's settings notebook isn't always syncronized with the Configurators component tree. That's because they are kept in different places.
All modifications of the component tree are manually stored in
platform configuration files with extension .conf
.
All modifications of the settings notebook are automatically
stored in the Windows registry or in nutconf.ini
when running Linux. This could be quite annoying, when you
regularly have to change your environment. The problem isn't
finally solved, but for now it helps to call the Configurator
with command line option -s
followed by a keyword
of your choice. For example, on Windows you can call
nut\nutconf
nut\nutconf -s arm
More Hints
1. After modifying Lua scripts the component tree never appears again. Check for missing commas. The configurator displays the name of the script and the line number of the error.
2. You can use UserConf.mk
to create a debug
enabled binary with AVRGCC by appending
HWDEF += -g3 -gdwarf-2
3. If you need additional include files to be used by
several of your applications, then create an include directory in
your application tree and add the following line to UserConf.mk
INCFIRST += $(INCPRE)../include
4. Building Nut/OS fails with something like Unsupported target
.
Probably the settings in the Configurator's notebook and the
component tree specify different compilers.
Harald Kipp
Herne, May 12th, 2005.