Mikrocontroller effektiv in C programmieren - ein noch...

7
Mikrocontroller effektiv in C programmieren- ein noch unbekanntes Land _________________________________________________________________________________________ Mikrocontroller effektiv in C programmieren - ein noch unbekanntes Land HS Pforzheim Fakultät Technik Mikrocontroller-Labor Tiefenbronner Str. 65 75175 Pforzheim Andreas Reber e-mail: [email protected] www: http//eiti.fh-pforzheim.de/

Transcript of Mikrocontroller effektiv in C programmieren - ein noch...

Page 1: Mikrocontroller effektiv in C programmieren - ein noch ...eitidaten.fh-pforzheim.de/daten/labore/mclt/pdf/C.pdf · Eine Lösung in C wird es an dieser Stelle nicht geben, denn die

Mikrocontroller effektiv in C programmieren- ein noch unbekanntes Land _________________________________________________________________________________________

Mikrocontroller effektiv in C programmieren - ein noch unbekanntes Land

HS Pforzheim Fakultät Technik

Mikrocontroller-Labor Tiefenbronner Str. 65

75175 Pforzheim

Andreas Reber e-mail: [email protected]

www: http//eiti.fh-pforzheim.de/

Page 2: Mikrocontroller effektiv in C programmieren - ein noch ...eitidaten.fh-pforzheim.de/daten/labore/mclt/pdf/C.pdf · Eine Lösung in C wird es an dieser Stelle nicht geben, denn die

Mikrocontroller in C programmieren – ein noch unbekanntes Land ___________________________________________________________________________________________

__________________________________________________________________________________________ Rev. 1.2 20.04.12 Seite 2 von 7

Vorwort Es gibt viele Quellen über die Programmierung von Mikrocontrollern in C. Leider vermitteln die wenigs-ten davon eine Systematik, wie man C-Code schreibt, der in effizienten Assembler übersetzt werden kann. Dieses kleine Tutorial ist aus der Erfahrung aus vielen Projekten entstanden und soll einen Denkanstoß für eigene Programme sein. Die Beispiele sind alle mit der µVision 3 von Keil für 805x ent-standen, was auch gleich zu der Einschränkung führt, dass andere Compiler einen anderen und even-tuell besseren Assembler Code generieren. Für andere Mikrocontroller gilt ähnliches, vor allem für die neueren Derivate, die auf C-Code hin optimiert sind (neue AVRs und PICs). Trotzdem lässt sich auch hier durch Umsetzung der vorgeschlagenen Strukturen hier und da ein bisschen Code einsparen.

Page 3: Mikrocontroller effektiv in C programmieren - ein noch ...eitidaten.fh-pforzheim.de/daten/labore/mclt/pdf/C.pdf · Eine Lösung in C wird es an dieser Stelle nicht geben, denn die

Mikrocontroller in C programmieren – ein noch unbekanntes Land ___________________________________________________________________________________________

__________________________________________________________________________________________ Rev. 1.2 20.04.12 Seite 3 von 7

1.1 Zählschleifen Der erste Gedanke ist recht einfach, wir wollen eine Aktion 10 mal ausführen: int16_t i; for(i=0;i <= 9;i++) {} Und schon fangen die Probleme an, die Variable i ist vorzeichenbehaftet, also kann unter Umständen das Zählergebnis nie erreicht werden und die Schleife zählt hoch. Außerdem wird auch noch auf <= abgefragt. Wird Problem 1 & 3 beseitig, sieht es so aus: uint16_t ui; for(ui=0;ui < 10;ui++) {} Viel hat sich nicht geändert, also warum soll man nicht hoch zählen. Wird obige Schleife übersetzt, so kommt folgendes als Assembler heraus:

CLR A MOV i,A MOV i+01H,A C0012: INC i+01H MOV A,i+01H JNZ C0028 INC i C0028: XRL A,#0AH ORL A,i JNZ C0012

Ohne die einzelnen Zeilen zu kommentieren, der Code ist recht lang geworden. Das hängt mit dem Be-fehlssatz zusammen und der Tatsache, dass Compiler nur selten für eine Prozessorfamilie geschrie-ben werden. Außerdem haben die 805x eine sehr eingeschränkte Funktionalität für Vergleiche. Doch bauen wir die Schleife einfach mal um: uint16_t ui; for(ui = 10;ui > 0;ui--) {}

MOV i,#00H MOV i+01H,#0AH C0014: MOV A,i+01H DEC i+01H JNZ C0028 DEC i C0028: MOV A,i+01H ORL A,i JNZ C0014

Gut, es ist nur ein Byte weniger geworden, also wozu der Aufwand, aber 20 Schleifen wären schon 20 Bytes, also weiter. Bei der Codeanalyse fällt auf, dass der Compiler immer einen 16 Bit Wert bildet, da der int hier 16 Bit hat, also ändern wir auf 8 Bit, da nur auf 10 gezählt werden soll: uint8_t uc; for(uc = 10;uc > 0;uc--) {}

MOV uc,#0AH C0014: DJNZ uc,?C0014

die Variablen immer nur so groß wie nötig Schleifen auf Null zählen lassen

Page 4: Mikrocontroller effektiv in C programmieren - ein noch ...eitidaten.fh-pforzheim.de/daten/labore/mclt/pdf/C.pdf · Eine Lösung in C wird es an dieser Stelle nicht geben, denn die

Mikrocontroller in C programmieren – ein noch unbekanntes Land ___________________________________________________________________________________________

__________________________________________________________________________________________ Rev. 1.2 20.04.12 Seite 4 von 7

Es ist auch nicht ungeschickt, die Abfrage auf „größer“ durch „ungleich Null“ zu ersetzen. Für die while-Schleife gilt dies ebenso. Auch spielt die Reihenfolge der Anweisungen eine Rolle: uint8_t uc; while(uc != 0) { uc--; (Anweisung) } Obige Struktur arbeitet einwandfrei, doch sie ergibt 6 Bytes mehr als: uint8_t uc; while(uc != 0) { (Anweisung) uc--; } Reihenfolge beachten 1.2 Abfragen Eine klassische if-else-Bedingung, hier innerhalb einer Impulszählung verwendet: if(ucZaehler <= MAXWERT) { ucZaehler++;

Ausgabe(); } else { ucZaehler = 0;

Ausgabe(); } Schnell geschrieben, gut nachvollziehbar und funktionieren tut es auch, aber es erzeugt mehr Code, einfach selber mal testen. Hier ist es so, dass der aktuelle Zählerstand ausgegeben wird, also wird es mit Zählen auf Null etwas schwierig. Dennoch lässt sich der Code reduzieren: ucZaehler++; if(ucZaehler == (MAXWERT + 1)) {

ucZaehler = 0; } Ausgabe(); Wird die Erhöhung vorgezogen und die Abfrage auf die Resetbedingung umgestellt, so entfällt der else-Zweig komplett. Die Ausgabe nach der Abfrage zu machen, spart Code für einen Funktionsaufruf. Das MAXWERT + 1 ist wegen der Ausgabe nötig. Wenn der Zähler nicht ausgegeben werden würde, könnte die if-Bedingung wie folgt lauten: ucZaehler--; if(ucZaehler != 0) {

ucZaehler = MAXWERT; ...

} Immer prüfen, ob der else-Zweig vermeidbar ist if-elseif-else sollten vermieden werden, indem sie in if und if-else geändert werden (im Assembler

kontrollieren, was besser ist)

Page 5: Mikrocontroller effektiv in C programmieren - ein noch ...eitidaten.fh-pforzheim.de/daten/labore/mclt/pdf/C.pdf · Eine Lösung in C wird es an dieser Stelle nicht geben, denn die

Mikrocontroller in C programmieren – ein noch unbekanntes Land ___________________________________________________________________________________________

__________________________________________________________________________________________ Rev. 1.2 20.04.12 Seite 5 von 7

Mischen von Abfragen Werden Bits (Ports) mit Variablen bitweise verknüpft, so wird der Code länger als bei reinen Bit-Verknüpfungen, da Null oder Eins der Variable erst ermittelt werden muss: if((bT1 == 0) && (ucSemaphor == 1)) { LCD_OUT("Ende"); }

JB bT1,C0004 MOV A,ucSemaphor CJNE A,#01H,?C0004 MOV R6,#HIGH ?SC_34 MOV R7,#LOW ?SC_34 LCALL _LCD_OUT C0004:

damit es kurz und schnell wird, immer nur gleiche Typen verknüpfen Da der Befehl cjne auch noch das Programmstatusregister verändert, muss der Compiler eventuell auch noch zwei Stackzugriffe machen, also noch mal 4 Bytes mehr. 1.3 Variablen Hier kann viel gespart oder auch verschwendet werden. wo es geht, nur positiv ganzzahlige Variablen verwenden es ist durchaus möglich, Werte eines AD-Wandlers ohne die Gleitkomma-Einsatz zu berechnen Variablen größer 8 Bit eventuell als Union anlegen Kleines Beispiel für einen AD-Wandler: uiADC_L = ADCDATAL; // Ergebnis low-Byte holen uiADC_H = ADCDATAH; // Ergebnis high-Byte holen uiADC_H &= 0x0F; // Kanalkennung ausblenden uiADC_ERG = uiADC_H<< 8; // Bits 8..11 uiADC_ERG += uiADC_L; // Bits 0..7 dazu Diese Zeilen übersetzt der Compiler wie folgt:

MOV R7,ADCDATAL MOV uiADC_L,#00H MOV uiADC_L+01H,R7 MOV R7,ADCDATAH MOV uiADC_H,#00H MOV uiADC_H+01H,R7 ANL uiADC_H+01H,#0FH MOV uiADC_H,#00H MOV A,uiADC_H+01H MOV uiADC_ERG+01H,#00H MOV uiADC_ERG,A4 MOV A,uiADC_L+01H ADD A,uiADC_ERG+01H MOV uiADC_ERG+01H,A MOV A,uiADC_L ADDC A,uiADC_ERG MOV uiADC_ERG,A

Er kann auch nicht anders, da der Code aus zwei 8 Bit-Werten eben eine 16 Bit-Wert machen muss. Der AD-Wandler gibt Vielfache seiner Quantisierung aus, also einen Integer Wert.

Page 6: Mikrocontroller effektiv in C programmieren - ein noch ...eitidaten.fh-pforzheim.de/daten/labore/mclt/pdf/C.pdf · Eine Lösung in C wird es an dieser Stelle nicht geben, denn die

Mikrocontroller in C programmieren – ein noch unbekanntes Land ___________________________________________________________________________________________

__________________________________________________________________________________________ Rev. 1.2 20.04.12 Seite 6 von 7

In Assembler ist es einfach, man legt sich zwei 8-Bit Variablen an und behandelt sie wie eine 16-Bit Variable. Das geht in C auch: typedef union { uint16_t word; struct { uint8_t msb; uint8_t lsb; } byte; } VAL16; VAL16 uiADC; Nun werden die Variablen durch die Union ersetzt: uiADC.byte.lsb = ADCDATAL; // Ergebnis low-Byte holen uiADC.byte.msb = ADCDATAH; // Ergebnis high-Byte holen uiADC.byte.msb &= 0x0F; // Kanalkennung ausblenden uiADC_ERG = uiADC.word; // Übergabe Dies führt zu einem überraschenden Ergebnis:

MOV uiADC+01H,ADCDATAL MOV uiADC,ADCDATAH ANL uiADC,#0FH MOV uiADC_ERG,uiADC MOV uiADC_ERG+01H,uiADC+01H

Durch die Union wurden 13 Befehle eingespart, damit eine Menge CPU-Zeit und Programmspeicher. Wird die Union statisch angelegt, so fallen auch noch die beiden letzten Mov-Befehle weg. In einer Applikation für ein Energiemessgerät müssen für Strom, Spannung und Energie drei Werte mit je 24 Bit abgeholt und berechnet bzw. „zusammengebaut“ werden. Ohne die Union wäre das in der be-nötigten Zeit nicht möglich, da der Compiler long int Variablen zum Rechnen benutzt, auch wenn das High-Byte immer Null ist. Selbst wenn die Variable vorzeichenbehaftet ist, funktioniert es, da immer das höchste Byte das Vorzeichen enthält. Äußerst schnell wird die Kombination mit der Union, wenn 16 oder 32 Bit-Werte als HEX ausgegeben werden sollen. Da hier meist einzelne Bytes umgewandelt werden, kann jedes Byte aus der Union ent-nommen und bearbeitet werden, ohne das Byte maskieren zu müssen. Kommazahlen ohne float Nehmen wir den AD-Wert aus dem obigen Beispiel und lassen ihn ausgeben: fADC = (float)( uiADC.word)*0.00122; // Register * Quantisierung in Volt Wer Rechenzeit und Programmplatz hat, kann das mit Hilfe eines printf so machen. Erst denken, dann programmieren. Es soll ein Dezimalzahl berechnet werden, die auf Vielfachen einer Float-Zahl beruht. Der gute alte Dreisatz ergibt folgendes. Ermittlung des Grundteilers:

Mittels Dreisatz wird der Teilerfaktor ermittelt ( 4096 / 5 Volt ) = ( X / 1 Volt). Als Ergebnis kommt man 819.2 und somit einen Teiler von 819. Der sich ergebende Fehler soll hier vernachlässigt werden (ca. 4 mV bei 5 V ohne Korrektur).

Page 7: Mikrocontroller effektiv in C programmieren - ein noch ...eitidaten.fh-pforzheim.de/daten/labore/mclt/pdf/C.pdf · Eine Lösung in C wird es an dieser Stelle nicht geben, denn die

Mikrocontroller in C programmieren – ein noch unbekanntes Land ___________________________________________________________________________________________

__________________________________________________________________________________________ Rev. 1.2 20.04.12 Seite 7 von 7

Wird nun das Wandlerergebnis durch 819 geteilt, erhält man als Ergebnis ganze Volt und den Rest der nicht durch 819 teilbar ist. Wird der Rest nun mit 10 multipliziert und anschließend durch 819 geteilt, so bekommt man ganze 1/10 Volt und wieder den unteilbaren Rest. Dieser Vorgang wird bis zur mV-Stelle (also 1/1000 Volt) durchgeführt. Eine Lösung in C wird es an dieser Stelle nicht geben, denn die Studenten im zweiten Semester sollen ja auch noch was zum Denken haben.