C Programming for deeply embedded applications


About this recipe

In this recipe you will learn about the standalone runtime support system for C programming in deeply embedded applications. In particular you will discover:

Introduction

The semi hosted ANSI C library provides all the standard C library facilities (and thus is quite large). This is acceptable when running under emulation with plenty of memory available, or maybe even when running on development hardware with access to a real debugging channel and plenty of memory. However, in a deeply embedded application many of the facilities of the C library may no longer be relevent, eg. file access functions, time and date functions, and the size of the semi hosted ANSI C library may be prohibitive if the memory available is severely limited.

For deeply embedded applications a minimal C runtime system is needed which takes up as little memory as possible, is easily portable to the target hardware, and only supports those functions required for such an application.

The ARM Software Development Toolkit comes with a minimal runtime system in source form. The 'behind the scenes' jobs which it performs are:

The source code rtstand.s documents the options which you may want to change for your target. These are not covered in this recipe. The header file rtstand.h documents the functions which rtstand.s provides to the C programmer.

Note that no support is provided for outputting data down the debugging channel. This can be done, but is specific to the target application. The example C programs described below use the ARM Debug Monitor available under armsd to output messages using in-line SWIs.

Using the standalone runtime system

In this section the main features of the standalone runtime system are demonstrated by example programs.

Before attempting any of the demonstrations below create a working directory, and set this up as your current directory. Copy the contents of the clstand directory into your working directory, and also copy the files fpe*.o from the fpe340 directory of the cl directory into your working directory. You are now ready to experiment with the C standalone runtime system.

In the examples below, the following options are passed to armcc, armasm, and in the first case armsd:

--------------------------------------------------------
Option          |Description                            
--------------------------------------------------------
-li             |Specifies that the the target is a     
                |little endian ARM.                     
--------------------------------------------------------
-apcs 3/32bit   |This specifies that the 32 bit variant 
                |of APCS 3 should be used.  For armasm  
                |this is used to set the built in       
                |variable {CONFIG} to 32.               
--------------------------------------------------------

These arguments can be changed if the target hardware differs from this configuration. If the ARM Software Tools have been configured as desired then these options may be omitted, as the tools will default to the configuration time values.

These demonstrations are likely to be most useful if the sources rtstand.s, errtest.c and memtest.c are studied in conjunction with this recipe.

A simple program

Let us compile the example program errtest.c, and assemble the standalone runtime system. These can then be linked together to provide an executable image, errtest:

armcc -c errtest.c -li -apcs 3/32bit
armasm rtstand.s -o rtstand.o -li -apcs 3/32bit
armlink -o errtest errtest.o rtstand.o

We can then execute this image under the armsd as follows:

> armsd -li errtest
A.R.M. Source-level Debugger, version 4.10 (A.R.M.) [Aug 26 1992]
ARMulator V1.20, 512 Kb RAM, MMU present, Demon 1.01, FPE, Little 
endian.
Object program file errtest
armsd: go
(the floating point instruction-set is not available)
Using integer arithmetic ...
10000 / 0X0000000A = 0X000003E8
10000 / 0X00000009 = 0X00000457
10000 / 0X00000008 = 0X000004E2
10000 / 0X00000007 = 0X00000594
10000 / 0X00000006 = 0X00000682
10000 / 0X00000005 = 0X000007D0
10000 / 0X00000004 = 0X000009C4
10000 / 0X00000003 = 0X00000D05
10000 / 0X00000002 = 0X00001388
10000 / 0X00000001 = 0X00002710
Program terminated normally at PC = 0x00008550
      0x00008550: 0xef000011 .... : >  swi     0x11
armsd: quit
Quitting
> 

The '>' prompt is the Operating System prompt, and the 'armsd:' prompt is output by armsd to indicate that user input is required.

Already several of the standalone runtime system's facilities have been demonstrated:

Error handling

The same program, errtest, can also be used to demonstrate error handling, by recompiling errtest.c and predefining the DIVIDE_ERROR macro:

armcc -c errtest.c -li -apcs 3/32bit -DDIVIDE_ERROR
armlink -o errtest errtest.o rtstand.o

Again, we can now execute this image under the armsd as follows:

> armsd -li errtest
A.R.M. Source-level Debugger, version 4.10 (A.R.M.) [Aug 26 1992]
ARMulator V1.20, 512 Kb RAM, MMU present, Demon 1.01, FPE, Little 
endian.
Object program file errtest
armsd: go
(the floating point instruction-set is not available)
Using integer arithmetic ...
10000 / 0X0000000A = 0X000003E8
10000 / 0X00000009 = 0X00000457
10000 / 0X00000008 = 0X000004E2
10000 / 0X00000007 = 0X00000594
10000 / 0X00000006 = 0X00000682
10000 / 0X00000005 = 0X000007D0
10000 / 0X00000004 = 0X000009C4
10000 / 0X00000003 = 0X00000D05
10000 / 0X00000002 = 0X00001388
10000 / 0X00000001 = 0X00002710
10000 / 0X00000000 = errhandler called: code = 0X00000001: divide by 0
caller's pc = 0X00008304
returning...

run time error: divide by 0
program terminated

Program terminated normally at PC = 0x0000854c
      0x0000854c: 0xef000011 .... : >  swi     0x11
armsd: quit
Quitting
> 

This time an integer division by zero has been detected by the standalone runtime system, which called __err_handler. __err_hander output the first set of error messages in the above output. Control was then returned to the runtime system which output the second set of error messages and terminated execution.

longjmp and setjmp

A further demonstration can be made using errtest by predefining the macro LONGJMP to perform a longjmp out of __err_handler back into the user program, thus catching and dealing with the error. First recompile and link errtest:

armcc -c errtest.c -li -apcs 3/32bit -DDIVIDE_ERROR -DLONGJMP
armlink -o errtest errtest.o rtstand.o

Then rerun errtest under armsd. We expect the integer divide by zero to occur once again:

> armsd -li errtest
A.R.M. Source-level Debugger, version 4.10 (A.R.M.) [Aug 26 1992]
ARMulator V1.20, 512 Kb RAM, MMU present, Demon 1.01, FPE, Little 
endian.
Object program file errtest
armsd: go
(the floating point instruction-set is not available)
Using integer arithmetic ...
10000 / 0X0000000A = 0X000003E8
10000 / 0X00000009 = 0X00000457
10000 / 0X00000008 = 0X000004E2
10000 / 0X00000007 = 0X00000594
10000 / 0X00000006 = 0X00000682
10000 / 0X00000005 = 0X000007D0
10000 / 0X00000004 = 0X000009C4
10000 / 0X00000003 = 0X00000D05
10000 / 0X00000002 = 0X00001388
10000 / 0X00000001 = 0X00002710
10000 / 0X00000000 = errhandler called: code = 0X00000001: divide by 0
caller's pc = 0X00008310
returning...

Returning from __err_handler() with errnum = 0X00000001

Program terminated normally at PC = 0x00008558
      0x00008558: 0xef000011 .... : >  swi     0x11
armsd: quit
Quitting
> 

The runtime system detected the integer divide by zero, and as before __err_handler was called, which produced the error messages. However, this time __err_handler used longjmp to return control to the program, rather than the runtime system.

Floating point support

Using errtest we can also demonstrate floating point support. You should already have copied the appropriate floating point emulator object code into your working directory. For the configuration used in this example fpe_32l.o is the correct object file.

However, in addition to this it is also necessary to link with an fpe stub, which we must compile from the source given (fpestub.s).

armasm fpestub.s -o fpestub.o -li -apcs 3/32bit
armlink -o errtest errtest.o rtstand.o fpestub.o fpe_32l.o -d

The resulting executable, errtest, can be run under armsd as before:

> armsd -li errtest
A.R.M. Source-level Debugger, version 4.10 (A.R.M.) [Aug 26 1992]
ARMulator V1.20, 512 Kb RAM, MMU present, Demon 1.01, FPE, Little 
endian.
Object program file errtest
armsd: go
(the floating point instruction-set is available)
Using Floating point, but casting to int ...
10000 / 0X0000000A = 0X000003E8
10000 / 0X00000009 = 0X00000457
10000 / 0X00000008 = 0X000004E2
10000 / 0X00000007 = 0X00000594
10000 / 0X00000006 = 0X00000682
10000 / 0X00000005 = 0X000007D0
10000 / 0X00000004 = 0X000009C4
10000 / 0X00000003 = 0X00000D05
10000 / 0X00000002 = 0X00001388
10000 / 0X00000001 = 0X00002710
10000 / 0X00000000 = errhandler called: code = 0X80000202: Floating 
Point
Exception : Divide By Zero

caller's pc = 0XE92DE000
returning...

Returning from __err_handler() with errnum = 0X80000202

Program terminated normally at PC = 0x00008558 (__rt_exit + 0x10)
+0010 0x00008558: 0xef000011 .... : >  swi     0x11
armsd: quit
Quitting
> 

This time the floating point instruction set is found to be available, and when a floating point division by zero is attempted, __err_handler is called with the details of the floating point divide by zero exception.

Note that if you have compiled errtest.c other than as in longjmp and setjmp, you will not see precisely this dialogue with armsd.

Running out of heap

A second example program, memtest.c demonstrates how the standalone runtime system copes with allocating stack space, and also demonstrates the simple memory allocation function __rt_alloc. Let us first compile this program so that it should repeatedly request more memory, until there is none left:

armcc -li -apcs 3/32bit memtest.c -c
armlink -o memtest memtest.o rtstand.o

This can be run under armsd in the usual way:

> armsd -li memtest
A.R.M. Source-level Debugger, version 4.10 (A.R.M.) [Aug 26 1992]
ARMulator V1.20, 512 Kb RAM, MMU present, Demon 1.01, FPE, Little 
endian.
Object program file memtest
armsd: go
kernel memory management test
force stack to 4KB
request 0 words of heap - allocate 256 words at 0X000085A0
force stack to 8KB
..
force stack to 60KB
request 33211 words of heap - allocate 33211 words at 0X00049388
force stack to 64KB
request 49816 words of heap - allocate 5739 words at 0X00069A74
memory exhausted, 105376 words of heap, 64KB of stack
Program terminated normally at PC = 0x0000847c
      0x0000847c: 0xef000011 .... : >  swi     0x11
armsd: quit
Quitting
> 

This demonstrates that allocating space on the stack is working correctly, and also that the __rt_alloc routine is working as expected. The program terminated because in the end __rt_alloc could not allocate the requested amount of memory.

Stack overflow checking

memtest can also be used to demonstrate stack overflow checking by recompiling with the macro STACK_OVERFLOW defined. In this case the amount of stack required is increased until there is not enough stack available, and stack overflow detection causes the program to be aborted.

To recompile and link memtest.c issue the following commands:

armcc -li -apcs 3/32bit memtest.c -c -DSTACK_OVERFLOW
armlink -o memtest memtest.o rtstand.o

Running this program under armsd produces the following output:

> armsd -li memtest
A.R.M. Source-level Debugger, version 4.10 (A.R.M.) [Aug 26 1992]
ARMulator V1.20, 512 Kb RAM, MMU present, Demon 1.01, FPE, Little 
endian.
Object program file memtest
armsd: go
kernel memory management test
force stack to 4KB
...
force stack to 256KB
request 1296 words of heap - allocate 1296 words at 0X0000AE20
force stack to 512KB

run time error: stack overflow
program terminated

Program terminated normally at PC = 0x0000847c
      0x0000847c: 0xef000011 .... : >  swi     0x11
armsd: quit
Quitting
> 

Clearly stack overlfow checking did indeed catch the case where too much stack was required, and caused the runtime system to terminate the program after giving an appropriate diagnostic.

Extending the standalone runtime system

For a many applications it may be desirable to have access to more of the standard C library than just the minimal runtime system provides. This section demonstrates how to take out a part of the standard C library and plug it into the standalone runtime system.

The function which we will add to rtstand is memmove. Although this is small, and easily extracted from the C library source, the same methodology can be applied to larger sections of the C library, eg. the dynamic memory allocation system (malloc, free, etc).

The source of the C library can be found in the cl directory. The source for the memmove function is in string.c. The extracted source for memmove has been put into memmove.c, and the compile time option _copywords has been removed. The function declaration for memmove and a typedef for size_t (extracted from include/stddef.h) have been put into memmove.h.

Our memmove module can be compiled as follows.

armcc -c memmove.c -li -apcs 3/32bit

The output, memmove.o can be linkedwith the user's other object modules together with rtstand.o in the normal way (see previous examples in this section).

The size of the standalone runtime library

rtstand.s has been separated into several code Areas. The advantage of this is that armlink can detect if any Areas are unreferenced, and then eliminate them from the output image.

The table below shows the typical size of the Areas in rtstand.o:

---------------------------------------------------------
Area                 |Size   |Functions                  
                     |(bytes)|                           
---------------------------------------------------------
C$$data              |4      |                           
---------------------------------------------------------
C$$code$$__main      |96     |__main, __rt_exit          
---------------------------------------------------------
C$$code$$__rt_fpavail|8      |__rt_fpavailable           
able                 |       |                           
---------------------------------------------------------
C$$code$$__rt_trap   |128    |__rt_trap                  
---------------------------------------------------------
C$$code$$__rt_alloc  |68     |__rt_alloc                 
---------------------------------------------------------
C$$code$$__rt_stkovf |76     |__rt_stkovf_split_*        
---------------------------------------------------------
C$$code$$__jmp       |100    |longjmp, setjmp            
---------------------------------------------------------
C$$code$$__divide    |256    |__rt_sdiv, __rt_udiv,      
                     |       |__rt_udiv10,               
---------------------------------------------------------
All Areas            |736    |__rt_sdiv10, __rt_divtest  
---------------------------------------------------------

If floating point support is definitely not required, then the EnsureNoFPSupport variable can be set to {TRUE}, and some extra space will be saved. After making any modifications to rtstand.s, the size of the various areas can be found by using the command:

decaof -b rtstand.o

From the above table it is clear that for many applications the standalone runtime library will be roughly 0.5Kb.

Related topics