© Harry Broeders.
Deze pagina is bestemd voor studenten van de THRijswijk.
De GNU 68HC11 toolchain bevat een implementatie van
Newlib. Newlib is een standaard
C library speciaal voor embedded systems. Deze library bevat functies uit:
stdlib.h, ctype.h, stdio.h, string.h, signal.h, time.h, locale.h, stdarg.h,
math.h, enz. Maar functies zoals printf
en malloc
hebben een OS (Operating System) nodig (wij gebruiken geen OS).
De documentatie van Newlib kun vinden op:
Als voorbeeld zal ik laten zien hoe de functie siprintf
, waarmee
een integer in een string omgezet kan worden, kan worden gebruikt.
In de les is een programma behandeld dat met behulp van de timer en output
compare (OC1) een stappenmotor aanstuurt. In dit
programma is ook een functie
display
opgenomen waarmee de globale variabelen
snelheid
en rechts
op het LCD
van de EVM kast kunnen worden weergegeven.
void display(void) { lcd_setpos(0,0); lcd_puts("Snelheid = "); lcd_putc('0'+snelheid/10); lcd_putc('0'+snelheid%10); lcd_setpos(2,0); lcd_puts("Richting = "); if (rechts) lcd_puts("rechts"); else lcd_puts("links "); }
De maximale snelheid is 15 dus dit getal kan altijd met 2 digits worden
weergegeven. Het omzetten van een integer getal van maximaal 2 digits naar
ASCII is heel simpel. Het most significant digit is gelijk aan het quotiënt
van getal gedeeld door 10 en het least significant digit is gelijk aan de
rest van getal gedeeld door 10.
sprintf
en siprintf
De functie sprintf
is gedefinieerd in de ANSI C standaard. De
sprintf
functie is een speciale vorm van de bekende
printf
functie die niet naar stdout
(het beeldscherm) schrijft maar
naar een string in het geheugen. Newlib bevat ook een simpele versie van
sprintf waarbij geen floating point getallen gebruikt kunnen worden:
siprintf
.
Deze functie kan als volgt gebruik worden om de globale variabelen
snelheid
en rechts
op het LCD
van de EVM kast weer te geven.
#include <stdio.h> void display(void) { char buffer[14]; lcd_setpos(0,0); siprintf(buffer, "Snelheid = %2d", snelheid); lcd_puts(buffer); lcd_setpos(2,0); lcd_puts("Richting = "); if (rechts) lcd_puts("rechts"); else lcd_puts("links "); }
Als het stappenmotor programma met de bovenstaande
display
functie wordt gecompileerd wordt, bij het linken, de
volgende foutmelding gegeven:
stappenmotor.o: undefined reference to `sprintf'
Het is vreemd dat in de foutmelding sprintf
wordt genoemd terwijl
siprintf
in het programma gebruikt wordt. De
foutmelding wordt veroorzaakt doordat de linker de machinecode van
siprintf
niet kan vinden. De standaard C library moet namelijk
bij gcc voor de 68HC11 expliciet worden meegelinkt door middel van
de optie
-lc
.
Bij de gcc waarmee programma's voor op de PC ontwikkeld kunnen worden is
dat niet nodig, daar wordt de standaard C library impliciet meegelinkt. In
de makefile moet de volgende regel worden aangepast:
LIBS = -L. -llcd -lc
Als het programma opnieuw wordt gelinkt worden de volgende foutmeldingen gegeven:
C:\Program files\THRSim11\gcc\m6811-elf\bin\ld.exe: region rom is full (a.out section .text) C:\Program files\THRSim11\gcc\m6811-elf\bin\ld.exe: region ram is full (a.out section .data) ...
Dit programma past dus niet in het geheugen van de EVM kast. Het gebruik
van de gcc optimalisatie opties
-Os
en
-fomit-frame-pointer
help niet. Het programma blijft te groot voor de EVM kast. Het programma
kan echter wel in de THRSim11 simulator getest worden. Maak een kopietje
van evm.ld, noem dit sim.ld en pas daarin de volgende regels aan:
MEMORY { ram (rwx) : ORIGIN = 0x0000, LENGTH = 0x1000 rom (rx) : ORIGIN = 0x4000, LENGTH = 0xc000 } /* Setup the stack on the top of the internal RAM. */ PROVIDE (_stack = 0x0fff);
In de makefile moet de volgende regel worden aangepast:
LDSCRIPT = sim.ld
Als het programma opnieuw wordt gelinkt worden de volgende foutmeldingen gegeven:
C:\Program files\THRSim11\gcc\m6811-elf\lib\libc.a(makebuf.o): undefined reference to `isatty' C:\Program files\THRSim11\gcc\m6811-elf\lib\libc.a(sbrkr.o): undefined reference to `sbrk' C:\Program files\THRSim11\gcc\m6811-elf\lib\libc.a(writer.o): undefined reference to `write' C:\Program files\THRSim11\gcc\m6811-elf\lib\libc.a(closer.o): undefined reference to `close' C:\Program files\THRSim11\gcc\m6811-elf\lib\libc.a(fstatr.o): undefined reference to `fstat' C:\Program files\THRSim11\gcc\m6811-elf\lib\libc.a(lseekr.o): undefined reference to `lseek' C:\Program files\THRSim11\gcc\m6811-elf\lib\libc.a(readr.o): undefined reference to `read'
Waarom deze functies (die helemaal niet nodig zijn voor de implementatie
van siprintf
) meegelinkt worden is een raadsel. Dat deze functies
"gebruikt" worden door siprintf
is overigens wel
gedocumenteerd.
Dat de machinecode van deze functies niet gevonden kan worden is niet zo
vreemd aangezien al deze functies
(isatty
,
sbrk
,
write
,
close
,
fstat
,
lseek
en
read
)
system calls zijn van het Operating System UNIX (of Linux).
De standaard C library libc.a wordt met de gcc compiler voor de 68HC11 meegeleverd. In hetzelfde directory bevindt zich ook de niet gedocumenteerde library libnosys.a. De inhoud van deze library kan zichbaar worden gemaakt door in het command window van THRSim11 het volgende commando in te typen:
!ar -t "C:\Program Files\THRSim11\gcc\m6811-elf\lib\libnosys.a"
Het blijkt dat deze library dummy implementaties van de benodigde system calls bevat. Deze library moet dus ook worden meegelinkt.
In de makefile moet de volgende regel worden aangepast:
LIBS = -L. -llcd -lc -lnosys
Als het programma opnieuw wordt gelinkt worden de volgende foutmelding gegeven:
C:\Program files\THRSim11\gcc\m6811-elf\lib\libnosys.a(sbrk.o): undefined reference to `end'
Dit heb ik uiteindelijk opgelost door in de linkerscript sim.ld het symbool
end
te definiëren. In dit linkerscript was al wel het symbool
_end gedefinieerd. In veel UNIX (Linux) systemen is het gebruikelijk dat
symbolen in uit een C programma door de compiler voorzien worden van een
_ aan het begin van het symbool. Hoogstwaarschijnlijk is deze "leading
underscore" ten onrechte in het linkerscript geplaatst.
In de file sim.ld moet de volgende regel worden toegevoegd:
PROVIDE (_end = .); PROVIDE (end = .); /* HACK! om -lnosys te kunnen gebruiken */
Eindelijk! Het programma wordt foutloos gecompileerd en gelinkt. Om het programma in THRSim11 te kunnen draaien moet de memory map worden aangepast. Kies voor de menu optie: File, Options, Memory Configuration... en pas de instellingen als volgt aan:
Het programma kan nu zonder problemen gesimuleerd worden.
Nadat het programma in THRSim11 geladen is kan het geheugengebruik zichbaar gemaakt worden door in het command window van THRSim11 het volgende commando in te typen:
!readelf -l a.out
Met behulp van dit commando is de volgende tabel opgesteld:
programma | optimalisatie | RAM gebruik | ROM gebruik |
---|---|---|---|
orgineel stappenmotor programma | geen | 23 | 1514 |
orgineel stappenmotor programma | -Os -fomit-frame-pointer | 23 | 1330 |
stappenmotor programma met
siprintf |
geen | 1132 | 18669 |
stappenmotor programma met siprintf |
-Os -fomit-frame-pointer | 1132 | 18499 |
De gcc compiler voor de 68HC11 kent ook de optie
-mshort
.
Deze optie is als volgt gedocumenteerd:
-mshort
Consider type int
to be 16 bits
wide, like short int
.
Deze optie heeft echter ver strekkende gevolgen die niet meteen duidelijk worden uit deze documentatie. In C wordt bij veel berekeningen zogenaamde integer promotion toegepast. Citaat van http://www.embedded.com/98/9811/9811fe3.htm:
The integer promotion rules of ANSI C are probably the most heinous crime committed against those of us who labor in the eight-bit world. I have no doubt that the standard is quite detailed in this area. However, the two most important rules in practice are the following:
- Any expression involving integral types smaller than an int have all the variables automatically promoted to int
- Any function call that passes an integral type smaller than an int automatically promotes the variable to an int, if the function is not prototyped. (Yet another reason for using function prototyping)
The key word here is automatically. Unless you take explicit steps, the compiler is unlikely to do what you want. Consider the following code fragment:
typedef unsigned char byte; byte a,b,res;
res=a+b;The compiler will promote
a
andb
to integers, perform a 32-bit addition, and then assign the lower eight bits of the result tores
. Several ways around this problem exist. First, many compiler vendors have seen the light, and allow you to disable the ANSI automatic integer promotion rules. However, youre then stuck with compiler-dependant code.Alternatively, you can resort to very clumsy casting, and hope that the compilers optimizer works out what you really want to do. The extent of the casting required seems to vary among compiler vendors. As a result, I tend to go overboard:
res=(byte)((byte)a+(byte)b);With complex expressions, the result can be hideous.
Bij de gcc compiler voor de 68HC11 lijkt dit echter geen problemen te geven:
Bij het compileren van bovenstaande code is zelf geen enkele optimalisatie
toegepast. Als het programma geoptimaliseerd wordt met -Os
-fomit-frame-pointer
blijft de optelling een 8 bits bewerking:
Ook bij het gebruik van short
(16-bits) variabelen wordt de
integer promotion rule van ANSI C door gcc voor de 68HC11 niet toegepast.
Er wordt een 16 bits optelling uitgevoerd:
Als er echter een deling met short
(16-bits) variabelen
wordt toegepast dan roept de gcc compiler voor de 68HC11 de library functie
__divsi3
aan. Dit is een 32 bits deelroutine. Hier wordt
dus de integer promotion rule wel toegepast!
![]() |
![]() |
Het bovenstaande programma gecompileerd met de opties -Os
-fomit-frame-pointer
beslaat 1848 bytes ROM.
Dit programma kan met de optie -mshort
wordt gecompileerd en
gelinkt door in de makefile de volgende regels aan te passen:
GCC_OPTIONS = -mshort -Os -fomit-frame-pointer
LD_OPTIONS = -mshort
Dit levert de volgende code op:
![]() |
![]() |
In dit geval wordt de library functie __divmodhi4
aangeroepen.
Dit is een 16 bits deelroutine. Het programma neemt nu nog maar 169 bytes
ROM in beslag. Het programma is meer dan 10x zo klein!
Als de optie -mshort
toegepast wordt bij het compileren en linken
van het stappenmotor programma wordt de volgende foutmelding gegegeven:
C:\Program files\THRSim11\gcc\m6811-elf\bin\ld.exe: linking files compiled for 16-bit integers (-mshort) and others for 32-bit integers
Deze fout wordt veroorzaakt doordat de libsimlcd.a
library niet
met -mshort
is gecompileerd en gelinkt. Een met
-mshort
gecompileerde en gelinkte versie van
libsimlcd.a
kun je hier vinden:
lcd_mshort/libsimlcd.a. Als deze versie
wordt gebruikt kunnen de stappenmotor weer gesimuleerd worden.
De tabel waarin de verschillende stappenmotor programma's en de verschillende opties met elkaar worden vergeleken kan nu als volgt worden uitgebreid:
programma | optimalisatie | RAM gebruik | ROM gebruik | |
---|---|---|---|---|
A | orgineel stappenmotor programma | geen | 23 | 1514 |
B | orgineel stappenmotor programma | -Os -fomit-frame-pointer | 23 | 1330 |
C | orgineel stappenmotor programma | -mshort -Os -fomit-frame-pointer | 17 | 620 |
D | stappenmotor programma met
siprintf |
geen | 1132 | 18669 |
E | stappenmotor programma met siprintf |
-Os -fomit-frame-pointer | 1132 | 18499 |
F | stappenmotor programma met siprintf |
-mshort -Os -fomit-frame-pointer | 980 | 14053 |
In een grafiek kun je de verschillen goed zien.
siprintf
uit Newlib levert programma's
op die te groot zijn voor de EVM. Het gebruik van andere functies zoals
isdigit()
en strcmp()
moet nog onderzocht worden.
-mshort
levert aanzienlijk kortere
programma's op. Het gebruik van deze optie kan dus sterk worden aangeraden.
Er moet daarbij wel op gelet worden dat een int
variabele maar
16 bits is. Als een 32 bits variabele nodig is moet het type
long
worden gebruikt.