Documents/NTN-4 Floats

From Nutwiki
Revision as of 08:49, 13 July 2017 by Harald (Talk | contribs) (Created page with "<div id="content"> = Floating Point Support = You may ask yourself "Why should an RTOS care about floating point?" Indeed, the Nut/OS kernel doesn't use any floati...")

(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to: navigation, search

Floating Point Support

You may ask yourself "Why should an RTOS care about floating point?" Indeed, the Nut/OS kernel doesn't use any floating point operations. And as long as the supported CPUs don't provide any floating point hardware, the kernel is not involved. However, Nut/OS is more than just a kernel and offers a rich set of standard I/O routines. Applications may want to use these routines to read floating point values from a TCP socket or display them on an LCD.

Be aware that dealing with floating point values will significantly blow up your code. When programming for tiny embedded devices it is recommended to avoid them. Thus, floating point support in Nut/OS is disabled by default. You have to start the Configurator, enable it, re-create the build tree and re-build the system.

Enabling Floating Point Support

Start the Nut/OS Configurator and load the configuration of your board. Make sure that all settings are OK (press Crtl+T) and that the right compiler is selected in the Tools section of the component tree. If unsure, consult the [[../../pdf/enswm28e.pdf|Nut/OS Software Manual]].

To enable floating point I/O, check the option Floating Point below C Runtime (Target Specific) -> File Streams in the module tree on the left side of the Configurator's main window.

[[File:../../img/config-fp.png|Nut/OS Floating Point Enable]]

After selecting Generate Build Tree from the build menu, the Configurator will create or re-write a header file named crt.h in the subdirectory include/cfg of your build tree.

#ifndef _INCLUDE_CFG_CRT_H_
#define _INCLUDE_CFG_CRT_H_

/*
 * Do not edit! Automatically generated on Mon May 09 19:34:19 2005
 */
 
#ifndef STDIO_FLOATING_POINT
#define STDIO_FLOATING_POINT
#endif

#endif

When rebuilding Nut/OS by selecting Build Nut/OS from the build menu, then this header file will be used instead of the original one in the source tree.

If you prefer to build Nut/OS on the command line within the source tree, then you need to edit the original file before running make install.

Sample Application

The sample code in app/uart, which is included in the Ethernut distribution, demonstrates floating point output, if floating point support had been enabled in the Configurator. The following code fragments show the relevant parts:

#include <cfg/crt.h>
#include <stdio.h>

...

#ifdef STDIO_FLOATING_POINT
    double dval = 0.0;
#endif

...

int main(void)
{

...

    for (;;) {

...

#ifdef STDIO_FLOATING_POINT
        dval += 1.0125;
        fprintf(uart, "FP %f\n", dval);
#endif

...

    }
}

Floating Point Internals

Nut/OS supports floating point input and output, which means, that it is able to convert ASCII representations of floating point values to their binary representations for input and vice versa for output. In other words, the Nut/OS standard I/O functions can read ASCII digits to store them in floating point variables or they can be used to print out the values of floating point numbers in ASCII digits.

Nut/OS does not provide floating point routines by itself, but depends on external floating point libraries. The ImageCraft AVR Compiler comes with build in libraries, while avrlibc provides this support for GCCAVR. Just recently (June 2008), floating point support had been added for ARM targets using newlib.

Reading floating point values is done inside the internal function

int _getf(int _getb(int, void *, size_t), int fd, CONST char *fmt, va_list ap)

defined in crt/getf.c. This function calls strtod() to convert the ASCII representation to the binary format. Fortunately this function is supported by most C libraries. Printing floating point values is a different story. It is actually done in function

int _putf(int _putb(int, CONST void *, size_t), int fd, CONST char *fmt, va_list ap)

which is defined in crt/putf.c. While ImageCraft's library nowadays offers ftoa() (had been FormatFP_1() before), avrlibc provides dtostre() for converting into the exponential format and dtostrf() for converting into the non-exponential format. The newlib library, used for building Nut/OS applications running on ARM CPUs, offers the function _dtoa_r to convert the binary representation to ASCII strings. However, things are more complicated here, because the fuction uses unique internal routines to allocate heap memory. These routines are not provided by Nut/OS and, even worse, the newlib memory management conficts with the one provided by Nut/OS. TO solve this issue, a new function _sbrk had been added to the Nut/OS libraries, which is used by newlib to request heap space. This way, a part of the Nut/OS heap is assigned to the newlib memory management. Since newlib calls _sbrk every time it wants to increase its heap space and because it expects a continous memory area for the total heap memory, a hard coded number of bytes will be allocated by Nut/OS on the first call. This value is specified in crt/sbrk.c:

#ifndef LIB_HEAPSIZE
#define LIB_HEAPSIZE     16384
#endif

Currently you can't change this value in the Configurator. Instead you may add the following line to the file UserConf.mk in the build tree prior to building the Nut/OS libraries:

HWDEF += -DLIB_HEAPSIZE=8192

Note, that floating point I/O for ARM targets is still experimental and may not work as expected.

Not much additional code is added by Nut/OS, but the amount of code added by the external libraries will be significant. If you are using the GNU compiler, do not forget to add -lm to the LIBS= entry in your application's Makefile.

Runtime Libraries and stdio

Today's C libraries for embedded systems are distributed with a rich set of stdio function, which partly may be more advanced than those provided by Nut/OS. Typically they offer full floating point support. So why not use them?

The main reason is, that they are less well connected to the hardware. Typically they pass output or expect input on a character by character base, which is slow. Further, they are not fully compatible among each other, which transfers the burden of porting from one platform to another to the application programmer. In opposite to desktop computers, embedded systems do not come with predefined standard devices. C libraries for embedded systems handle this in different ways. Another problem is network support. Some libraries even provide rich file system access, but not much is offered when it comes to networking. On the other hand, Nut/OS provides almost all stdio functions on all platforms for almost all I/O devices including TCP streams in a consistent way.

Both, Nut/OS and the C runtime library, offer a large number of stdio functions with equal names. Sometimes this results in conflicts while linking application codes, or worse, while the application code is running. If an application with stdio calls acts strange, you should inspect the cross reference list in the linker map file first. Make sure, that all stdio calls are linked to Nut/OS libraries.

The following extract from a GCC linker map file shows, that fprintf is located in the Nut/OS library libnutcrt.a and referenced in the application object file uart.o.

Cross Reference Table

Symbol  File
fprintf ../../nutbld-enut30d-gcc/lib\libnutcrt.a(fprintf.o)
        uart.o

When removing -lnutcrt from the LIBS entry in the application's Makefile, the linker will take fprintf from C library instead. In this specific case, using newlib for ARM, it will additionally result in several linker errors.

Cross Reference Table

Symbol  File
fprintf c:/programme/yagarto/bin/../lib/gcc/arm-elf/4.2.2/../../../../arm-elf/lib\libc.a
        uart.o

If you are using YAGARTO, which includes newlib, another problem appears. Actually the same problem exists with all libraries, which had been build with syscall support. You will end up with a number of undefined references.

To remove the syscalls module from YAGARTO's newlib, change to arm-elf/lib within the YAGARTO installation directory and run

arm-elf-ar -d libc.a lib_a-syscalls.o

This had been tested with newlib 1.16 in YAGARTO 20080408. For previous releases try

arm-elf-ar -d libc.a syscalls.o

Some History

In early releases Nut/OS simply ignored floating point values. Until today the author never needed it and is almost sure, that he will never need it. Unless you have to handle very large ranges, everything can be done with integers. Keep in mind, that floating point calculations are slow, consume a lot of CPU power and worse, they may result in significant rounding errors.

Anyway, it had been added. Early releases of Nut/OS offered two libraries, nutcrtf and nutcrt. The first one included floating point I/O while the latter didn't. These libraries were build by compiling either getff.c and putff.c for the floating point version or getf.c and putf.c for the library without floating point support. Internally the first two simply include the latter two source files after defining STDIO_FLOATING_POINT. What a crap! :-)

If an application required floating point I/O, the default library nutcrt had been replaced by nutcrtf in the list of libraries to be linked to the application code. This way the user wasn't forced to change any original source code. After the introduction of the Configurator, customizing and rebuilding Nut/OS became much more simple. Furthermore, by separating the build directory from the source tree, several differently configured systems can easily coexist. Thus, there is no specific floating point version of any library required any more.

Floating point I/O for ARM targets is available in Nut/OS version 4.5.5 and above.


Harald Kipp
Castrop-Rauxel, June 28th, 2008.