Het gebruik van Newlib library functies voor de 68HC11.

© 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:

Voorbeeld.

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 ");
}

Problemen, problemen en nog meer problemen.

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.

Hoe groot is het programma nu?

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:

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 and b to integers, perform a 32-bit addition, and then assign the lower eight bits of the result to res. 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, you’re then stuck with compiler-dependant code.

Alternatively, you can resort to very clumsy casting, and hope that the compiler’s 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.

Conclusies en aanbevelingen.