42
C für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung in C. Die Entwicklungsumgebung steht als sog. Tool-Chain zur Verfügung. Dabei gibt es Firmenprodukte (z.B. von KEIL) oder offene Produkte (z.B. AVR-GCC: GNU C Compiler). In dieser Vorlesung wird WinAVR eingesetzt: eine für Windows adaptierte Tool-Chain für die C-Programmentwicklung der AVR- Mikrocontroller. Im Folgenden geht es um eine Einführung in die grundsätzlichen sprachlichen Merkmale. Die Installation und Handhabung der Tool-Chain wird gesondert für die Übungen beschrieben. Als Beispiel dient ein Programm, das auf dem Butterfly-Modul zyklisch einen Piepton erzeugt (800 msec an, 200 msec aus).

C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Embed Size (px)

Citation preview

Page 1: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

C für MikrocontrollerDie wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung in C.

Die Entwicklungsumgebung steht als sog. Tool-Chain zur Verfügung.

Dabei gibt es Firmenprodukte (z.B. von KEIL) oder offene Produkte (z.B. AVR-GCC: GNU C Compiler).

In dieser Vorlesung wird WinAVR eingesetzt: eine für Windows adaptierte Tool-Chain für die C-Programmentwicklung der AVR-Mikrocontroller.

Im Folgenden geht es um eine Einführung in die grundsätzlichen sprachlichen Merkmale.

Die Installation und Handhabung der Tool-Chain wird gesondert für die Übungen beschrieben.

Als Beispiel dient ein Programm, das auf dem Butterfly-Modul zyklisch einen Piepton erzeugt (800 msec an, 200 msec aus).

Page 2: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Das Anwendungsproblem

Auf dem Butterflymodul steht ein Piezoelement zur Tonerzeugung zur Verfügung.

Durch zyklisches Ein/Ausschalten des Elements kann man einen Ton erzeugen.

Der Ton soll eine Frequenz von 500 Hz, also eine Periodendauer von 2 msec haben.

Außerdem soll der Ton 800 msec zu hören sein, dann 200 msec nicht. Das soll sich zyklisch wiederholen.

Tue endlos

Schalte 500Hz-Ton für 800 msec ein

Schalte 500Hz-Ton für 200 msec aus

Page 3: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Das Hauptprogramm

Im vorangehenden Struktogramm wird die Gesamtfunktion des Anwenderprogramms beschrieben, das in C zu programmieren ist.

Im Kontext von C wird die Gesamtfunktion durch das Hauptprogrammrepräsentiert. Mit anderen Worten: Dem abstrakten Begriff Gesamtfunktionentspricht das Hauptelement eines C-Programm: die main-Funktion.

Ein Struktogramm hat die Aufgabe, den Programmierer mit anschaulichen Mitteln dabei zu unterstützen, pauschal beschriebene Funktionen in genauer spezifizierte Teilfunktionen zu untergliedern.

Dem entsprechen in einem C-Programm die Funktionen, die innerhalb der main-Funktion aufgerufen werden bzw. Funktionen, die innerhalb von anderen Funktionen aufgerufen werden.

Aus der Sicht des Hauptprogramms (main-Funktion) sind die von ihm aufgerufenen Teilprogramme „untergeordnet“, die wiederum andere „diesen untergeordnete“ Teilprogramme aufrufen. In Assemblersprachen ist dafür Begriff Unterprogramme üblich. In C verzichtet man auf diesen hierarchischen Aspekt und spricht nur von Funktionen.

Page 4: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

C-Funktionen, main-Funktion

Die Form der Definition einer C-Funktion berücksichtigt eine allgemeine Eigenschaft: man kann einer Funktion beim Aufruf explizit Werte zur Verarbeitung übergeben und sie kann nach Ablauf einen Wert zurückgeben.

Das führt zu folgender Form der einleitenden Zeile einer Funktionsdefinition, die auch das Format des Funktionsaufrufs festlegt:

2.Argument …)(1. Argument,FunktionsnameTypder zurückgegebenen Größe

In diesem Sinne hat die main-Funktion eine besondere Eigenschaft: es gibt keine Größen, die ihr explizit zur Verarbeitung übergeben werden, und sie gibt keine Größe zurück.Das drückt kann man in der Definition der main-Funktion so aus:void main (void)oder vereinfacht

main()

Nameder i-ten Größe

Typ der i-ten übergebenen Größe

Page 5: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

main()

Der Rumpf der main-Funktion folgt der einleitenden Zeile. Er enthält die Anweisungen, die auszuführen sind. Diese Anweisungen werden zusammengenommen als ein Anweisungsblock oder kurz Block bezeichnet.

Der Anweisungsblock der main-Funktion wird mit dem Beginn-Symbol { vor der ersten Anweisung des Blocks und dem expliziten Ende-Symbol } nach der letzten Anweisung des Blocks markiert. Das gilt sinngemäß für jede Funktionsdefinition.

Also:main()

{

Anweisungen …

}bzw.Typ der zurückgegebenen Größe Funktionsname (1. Argument, 2.Argument …)

{Anweisungen …}

Page 6: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Daten

Die Anweisungen verarbeiten Daten, die grundsätzlich nach ihrer Veränderbarkeit unterschieden werden:

Konstante gehen als Festwerte in die Verarbeitung ein. Konstante werden beim Übersetzen Bestandteile des unveränderlichen Programmkodes.

Den Veränderlichen (Variablen) werden bei der Verarbeitung neue Werte zugewiesen. Solche Zustandsänderungen kann man nur auf der Basis von les- und beschreibbaren Speicherzellen realisieren, die durch explizite Datendefintionsanweisungen erfolgen..

In C ist das Format einer expliziten Datendefinitionsanweisung

für eine einzelne Variable:Datentyp Variablenname;

oder für mehrere Variable vom gleichen Datentyp:Datentyp 1.Variablenname, 2.Variablenname,…;

Anweisungen werden immer mit ; abgeschlossen.

Page 7: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Datentypen

In C standardisierte Datentypen für Zeichen und ganze Zahlen sind:

Wort mit der Wortlänge Byte (8Bit)charunsigned

Entspricht der doppelten Wortlänge, die für int giltsigned integer

longsigned

Ganze Zahl ohne Vorzeichenunsignedinteger

intunsigned

Entspricht der Wortlänge, die für int giltsignedinteger

shortsigned

Ganze Zahl mit Vorzeichen mit einer Wortlänge, die vom Zielsystem abhängt, was in der Regel eine Wortlänge von 16Bit bedeutet.

signed integer

intsigned

Ganze Zahl mit der Wortlänge Byte (8Bit)signed integer

charsigned

Page 8: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Selbstdefinierte (nicht standardisierte) Datentypen

In C kann man sich eigene Datentypen definieren:

typedef signed char int8_t;typedef unsigned char uint8_t;/*Der Vorteil der neuen Bezeichnung: man erkennt aus dem Namen unmissverständlich alle Merkmale des Datentyp. uint8_t ist als Datentyp für 8Bit-Register geeignet.*/

typedef short int16_t;typedef unsigned short uint16_t;// uint16_t ist als Datentyp für 16Bit-Register geeignet.

typedef unsigned long uint32_t;typedef unsigned char uint8_t;

Anweisungen werden immer mit ; abgeschlossen.Kommentare werden in /*….*/ eingeschlossen. Ein Kommentar, der nur eine Zeile umfasst, kann mit // begonnen werden.

Page 9: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Variable

Beispiel: eine Variable, die für eine Zeitangabe den Anteil der Millisekunden angibt:

Man braucht die Zahlenwerte 0 bis 1000. Das bedeutet, dass man eine Variable vom Datentyp int16_t oder uint16_t definieren kann, z.B.

uint16_t cnt; /* Variable mit dem Namen cnt für ganze positive Zahlen im Bereich 0 bis 1000*/

Der Compiler ist frei, diese Variable in einem Register im Registersatz der MCU oder im SRAM zu realisieren.

Folgendes einfache Beispiel zeigt eine Anweisungsform, die für eine Wertzuweisung an eine Variable bei Programmablauf sorgt:

cnt = 0;Der Wert, der auf der rechten Seite angegeben wird oder mit einem Ausdruck errechnet wird, wird der neue Wert der Variablen links.

Page 10: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Einfache Ausdrücke

Eine Inkremetierung/Dekrementierung ist ein Beispiel für einen einfachen Ausdruck

cnt = cnt +1;Zuerst wird der rechte Ausdruck ausgewertet: zum augenblicklichen Wert von ms wird 1 addiert und dieser Wert wird als neuer Wert in ms gespeichert.

In C kann man das auch so ausdrücken:cnt++;

(oder ++cnt: Erklärung des Unterschieds später).

cnt = cnt -1;Zuerst wird der rechte Ausdruck ausgewertet: zum augenblicklichen Wert von cnt wird 1 addiert und dieser Wert wird als neuer Wert in cnt gespeichert.

In C kann man das auch so ausdrücken:cnt--;

(oder --cnt: s.o.)

Page 11: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Register als Variable (1)

Wenn man C bei der Anwendung von Mikrocontrollern einsetzen will, dann muss man Register wie Variable behandeln können.

Beispiel: Ein bestimmtes Bit in einem Port (z.B. PortB, Bit 5) soll durch Anweisung 1 oder 0 werden, die anderen Bits in dem Port sollen dabei unverändert bleiben.

Der C-Sprachumfang muss so erweitert werden, dass das PortB wie eine Variable vom Datentyp uint8_t behandelt werden kann.

Dann sind folgende Anweisungen möglich:

PORTB = PORTB | 0x20; /* Oder-Verknüpfung des augenblicklichen Werts von PortB mit der binären Konstanten 0010 0000 und Schreiben des Ergebnisses in PortB; oder kompaktere Form der Anweisung*/PORTB |= 0x20;

PORTB = PORTB & ~0x20; /Und-Verknüpfung des augenblicklichen Werts von PortB mit der binären Konstanten 1101 1111 und Schreiben des Ergebnisses in PortB; oder kompaktere Form der Anweisung*/PORTB &= ~0x20;/* Der Operator ~ invertiert jedes Bit der gegebenen Konstanten.*/

Page 12: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Register als Variable (2)C bietet den links-Shift-Operator << an sowie den rechts-Shift-Operator >>.

Beispiel:

PORTB = PORTB | (1<< 5); /* Oder-Verknüpfung des augenblicklichen Werts von PortB mit der binären Konstanten 0010 0000 und Schreiben des Ergebnisses in PortB; oder kompaktere Form der Anweisung*/PORTB |= (1<< 5);

PORTB = PORTB & ~(1<<5); /Und-Verknüpfung des augenblicklichen Werts von PortB mit der binären Konstanten 1101 1111 und Schreiben des Ergebnisses in PortB; oder kompaktere Form der Anweisung*/PORTB &= ~(1<< 5 );

Eine elegantere Methode, die Konstante anzugeben, ergibt sich, wenn man das Bitmuster aus einem sinnfällig angegebenen Namen vom Compiler generieren lässt. Dazu verwendet man einen Namen, der mit einem Hinweis auf das Bit parametriert ist, z.B. _BV(PB5).

Beispiel: PORTB |= _BV(PB5);

Der Präprozessor ersetzt diesen Namen durch (1<<5), so dass der Compiler die Anweisung in der Form bekommt, die er umsetzen kann.

Page 13: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Einfache Kontrollstrukturen (1)

Kontrollstrukturen definieren die Reihenfolge, in der Aktionen durchgeführt werden.

Eine eine wichtige Kontrollstruktur ist die if-Anweisung:

if (Ausdruck) Anweisung_1else Anweisung_2

Die Entscheidung, ob die Bedingung zutrifft oder nicht, fällt nach der Berechnung des Ausdrucks, der in den Klammern steht. Hat der Wert, den der Ausdruck liefert, einen von 0 verschiedenen Wert, dann trifft die Bedingung zu und Anweisung_1 wird ausgeführt.

Beim Wert 0 wird Anweisung_2 ausgeführt. Fehlt else mit Anweisung_2, dann wird mit der Anweisung fortgesetzt, die der if-Anweisung folgt.

Anweisung_1 bzw. _2 können in {..} geklammerte Anweisungsblöcke sein.

Page 14: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Einfache Kontrollstrukturen (2)Beispiel:

cnt++;if (cnt >= 1000) cnt = 0;

Der Ausdruck in den Klammern vergleicht zwei Operanden mit Hilfe eines Vergleichsoperators. Er liefert einen int-Wert 0, wenn die angegebene Relation falsch ist.Die anderen in C möglichen Vergleichsoperatoren sind: >, < und <= sowie == (gleich) und != (nicht gleich).

Erst, wenn mehr als eine Anweisung auszuführen ist, liegt ein Anweisungs-block vor, der in { } gesetzt werden muss. if (cnt >= 1000) {cnt = 0;} ist nicht falsch, die Klammern sind aber überflüssig;

Man kann die obige Anweisungsfolge auch kompakter schreiben:

if (cnt++ >= 1000) cnt = 0;/*Zuerst wird der Wert mit dem augenblicklichen Wert von cnt ausgewertet, dann erst wird cnt inkrementiert.*/if (++cnt >= 1000) cnt = 0;/*Zuerst wird cnt inkrementiert, dann wird der Ausdruck mit dem inkrementierten Wert ausgewertet.*/

Page 15: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Einfache Kontrollstrukturen (3)

Eine andere wichtige Kontrollstruktur ist die while-Schleife :

while (Ausdruck) Anweisung

Auch hier ist zuerst der Wert des Ausdrucks in den Klammern zu bestimmen. Hat der Ausdruck einen von 0 verschiedenen Wert, dann wird die Anweisung ausgeführt und anschließend der Ausdruck wieder bewertet (Schleife).Hat der Ausdruck den Wert 0, dann wird die Anweisung ausgeführt, die der while-Anweisung folgt.

Die Anweisung kann auch ein { } stehender Anweisungsblock sein.

Beispiel:counter1 = 16;while (counter1) {

counter1--;counter2 = 86;while (counter2) {

counter2--;}

}

Page 16: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Einfache Kontrollstrukturen (4)Eine andere Kontrollstruktur ist die for-Schleife :

for (Ausdruck_1;Ausdruck_2;Ausdruck_3) Anweisung

Beispiel: for (counter1 = 16; counter1 > 0; counter1--) {counter2 = 86;

while (counter2) {counter2--;}

}

Das ist gleichwertig: allgemein:

counter1 = 16; Ausdruck1;while (counter1) { while (Ausdruck_2) {

counter1--; Ausdruck_3;counter2 = 86; Anweisung bzw. Blockwhile (counter2) {

counter2--;}

} }Eine while-Schleife lässt sich als gleichwertige for-Schleife formulieren und umgekehrt.

Page 17: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Einfache Kontrollstrukturen (5)

Eine andere wichtige Kontrollstruktur ist die do-while-Schleife :

do Anweisung while (Ausdruck)

While- und For-Schleife haben die wünschenswerte Eigenschaft, dass die Abbruchbedingung am Anfang und nicht am Ende des Schleifendurchlaufs geprüft wird.

Die do-while-Schleife prüft am Ende der Schleife und die Anweisung wird daher wenigstens einmal durchgeführt.

Die Anweisung kann auch ein in { } gesetzter Anweisungsblock sein.

Beispiel:

do {counter2--;} while (counter2 > 0);

Page 18: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Anwendungsprogramm: main()#include <avr/io.h>#include "utils.h"

void main(void){

uint16_t cnt;

/* enable PB5 as output */DDRB |= _BV(PB5);

/* Beep, Beep */cnt = 0;while (1) {

if ( cnt++ >= 1000 ) cnt = 0;

if ( cnt < 800 ) PORTB ^= _BV(PB5);// ^ ist der Operator für die bitweise EXOR-Verknüpfung..

warten_ms(1);}

}

Page 19: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Anwendungsprogramm: unterstützende FunktionenBeispiel für eine library-Funktion

#include <avr/io.h>void warten_ms(unsigned short ms){

uint8_t counter1, counter2;

while (ms) {counter1 = 16;while (counter1) {

counter1--;counter2 = 87;while (counter2) {

counter2--;}

}ms--;

}

Page 20: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

<io.h> includes <avr/sfr_defs.h> and <iom169.h>/* $Id: io.h,v 1.14 2004/11/24 18:57:36 troth Exp $ */

/** \defgroup avr_io AVR device-specific IO definitions \code #include <avr/io.h> \endcode This header file includes the apropriate IO definitions for the device that has been specified by the <tt>-mmcu=</tt> compiler command-line switch. ..*/

#include <avr/sfr_defs.h>/* Stack Pointer */#define SP _SFR_IO16(0x3D)#define SPL _SFR_IO8(0x3D)#define SPH _SFR_IO8(0x3E) /*…*/

/*Status Register */#define SREG _SFR_IO8(0x3F)/* Status Register - SREG */#define SREG_I 7#define SREG_T 6#define SREG_H 5#define SREG_S 4#define SREG_V 3#define SREG_N 2#define SREG_Z 1#define SREG_C 0/*…*/

#if defined (/*...*/) /*...*/#elif defined (__AVR_ATmega169__) # include <avr/iom169.h>

Page 21: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

avr/sfr_defs.h/* avr/sfr_defs.h - macros for accessing AVR special function registers */

/* $Id: sfr_defs.h,v 1.14.2.1 2005/01/07 19:25:25 arcanum Exp $ */

/** \defgroup avr_sfr_notes Additional notes from <avr/sfr_defs.h> \ingroup avr_sfr The \c <avr/sfr_defs.h> file is included by all of the \c <avr/ioXXXX.h> files, which use macros defined here to make the special function register definitions look like C variables or simple constants, depending on the <tt>_SFR_ASM_COMPAT</tt> define.…*/

#define __SFR_OFFSET 0x20 /*…*/

#define _SFR_MEM8(mem_addr) (mem_addr)#define _SFR_MEM16(mem_addr) (mem_addr)

#define _SFR_IO8(io_addr) ((io_addr) + __SFR_OFFSET)#define _SFR_IO16(io_addr) ((io_addr) + __SFR_OFFSET)#define _SFR_IO_ADDR(sfr) ((sfr) - __SFR_OFFSET)#define _SFR_MEM_ADDR(sfr) (sfr)#define _SFR_IO_REG_P(sfr) ((sfr) < 0x40 + __SFR_OFFSET)

/*…*/

Page 22: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

iom169.h

/* Port D */#define PIND _SFR_IO8(0x09)#define DDRD _SFR_IO8(0x0A)#define PORTD _SFR_IO8(0x0B)/* Port E */#define PINE _SFR_IO8(0x0C)#define DDRE _SFR_IO8(0x0D)#define PORTE _SFR_IO8(0x0E)/* Port F */#define PINF _SFR_IO8(0x0F)#define DDRF _SFR_IO8(0x10)#define PORTF _SFR_IO8(0x11)/* Port G */#define PING _SFR_IO8(0x12)#define DDRG _SFR_IO8(0x13)#define PORTG _SFR_IO8(0x14)

iom169.h wird hier auszugsweise angegeben, um im Kontext der Beispiele Plausibilität herzustellen.

//Ports/* Port A */#define PINA _SFR_IO8(0x00)#define DDRA _SFR_IO8(0x01)#define PORTA _SFR_IO8(0x02)/* Port B */#define PINB _SFR_IO8(0x03)#define DDRB _SFR_IO8(0x04)#define PORTB _SFR_IO8(0x05)/* Port C */#define PINC _SFR_IO8(0x06)#define DDRC _SFR_IO8(0x07)#define PORTC _SFR_IO8(0x08)

Page 23: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

iom169.h

//Timer/Counter0/* Timer/Counter Control Register A */#define TCCR0A _SFR_IO8(0x24)/* Timer/Counter Register */#define TCNT0 _SFR_IO8(0x26)/* Output Compare Register A */#define OCR0A _SFR_IO8(0x27)

/* Clock Prescale Register */#define CLKPR _SFR_MEM8(0x61)#define PRR _SFR_MEM8(0x64)#define PRADC 0#define PRUSART0 1#define PRSPI 2#define PRTIM1 3#define PRLCD 4

//EEPROM#undef EECR/* EEPROM Control Register */#define EECR _SFR_IO8(0x1F)

#undef EEDR/* EEPROM Data Register */#define EEDR _SFR_IO8(0x20)

#undef EEAR#undef EEARL#undef EEARH/* EEPROM Address Register */#define EEAR _SFR_IO16(0x21)#define EEARL _SFR_IO8(0x21)#define EEARH _SFR_IO8(0x22)

Page 24: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

iom169.h

/* Timer/Counter1 Output Compare Register A */#define OCR1A _SFR_MEM16(0x88) #define OCR1AL _SFR_MEM8(0x88)#define OCR1AH _SFR_MEM8(0x89)

/* Timer/Counter1 Output Compare Register B */#define OCR1B _SFR_MEM16(0x8A)#define OCR1BL _SFR_MEM8(0x8A)#define OCR1BH _SFR_MEM8(0x8B)

//Timer/Counter1/* Timer/Counter1 Control Register A */#define TCCR1A _SFR_MEM8(0x80)/* Timer/Counter1 Control Register B */#define TCCR1B _SFR_MEM8(0x81)/* Timer/Counter1 Control Register C */#define TCCR1C _SFR_MEM8(0x82)

/* Timer/Counter1 Register */#define TCNT1 _SFR_MEM16(0x84)#define TCNT1L _SFR_MEM8(0x84)#define TCNT1H _SFR_MEM8(0x85)

/* Timer/Counter1 Input Capture Register */#define ICR1 _SFR_MEM16(0x86)#define ICR1L _SFR_MEM8(0x86)#define ICR1H _SFR_MEM8(0x87)

Page 25: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

iom169.h

//USART0/* USART0 Control and Status Register A */#define UCSRA _SFR_MEM8(0xC0)/* USART0 Control and Status Register B */#define UCSRB _SFR_MEM8(0xC1)/* USART0 Control and Status Register C */#define UCSRC _SFR_MEM8(0xC2)

/* USART0 Baud Rate Register */#define UBRR _SFR_MEM16(0xC4)#define UBRRL _SFR_MEM8(0xC4)#define UBRRH _SFR_MEM8(0xC5)

/* USART0 I/O Data Register */#define UDR _SFR_MEM8(0xC6)

Page 26: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

iom169.h: Interrupt Vectors#define SIG_INTERRUPT0 #define SIG_PIN_CHANGE0#define SIG_PIN_CHANGE1#define SIG_OUTPUT_COMPARE2 #define SIG_OVERFLOW2 #define SIG_INPUT_CAPTURE1 #define SIG_OUTPUT_COMPARE1A #define SIG_OUTPUT_COMPARE1B #define SIG_OVERFLOW1#define SIG_OUTPUT_COMPARE0#define SIG_OVERFLOW0#define SIG_SPI #define SIG_USART_RECV#define SIG_USART_DATA#define SIG_USART_TRANS#define SIG_USI_START#define SIG_USI_OVERFLOW#define SIG_COMPARATOR#define SIG_ADC#define SIG_EEPROM_READY#define SIG_SPM_READY#define SIG_LCD

_VECTOR(1)_VECTOR(2) _VECTOR(3) _VECTOR(4)_VECTOR(5)_VECTOR(6)_VECTOR(7)_VECTOR(8)_VECTOR(9)_VECTOR(10)_VECTOR(11)_VECTOR(12)_VECTOR(13)_VECTOR(14)_VECTOR(15)_VECTOR(16)_VECTOR(17)_VECTOR(18)_VECTOR(19)_VECTOR(20)_VECTOR(21)_VECTOR(22)

Page 27: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

iom169.h /* PINB */#define PINB7 7#define PINB6 6#define PINB5 5#define PINB4 4#define PINB3 3#define PINB2 2#define PINB1 1#define PINB0 0

/* PORTB */#define PB7 7#define PB6 6#define PB5 5#define PB4 4#define PB3 3#define PB2 2#define PB1 1#define PB0 0/* DDRB */#define DDB7 7#define DDB6 6#define DDB5 5#define DDB4 4#define DDB3 3#define DDB2 2#define DDB1 1#define DDB0 0

Page 28: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Textaufbereitung des Präprozessors für das Compilieren (1)

Auszug aus sfr_defs.h #define __SFR_OFFSET 0x20#define _SFR_MEM8(mem_addr) (mem_addr)#define _SFR_MEM16(mem_addr) (mem_addr)#define _SFR_IO8(io_addr) ((io_addr) + __SFR_OFFSET)#define _SFR_IO16(io_addr) ((io_addr) + __SFR_OFFSET)

und aus iom169.h/* PortB */

#define PINB _SFR_IO8(0x03)#define DDRB _SFR_IO8(0x04)#define PORTB _SFR_IO8(0x05)

AusPORTB

wird_SFR_IO8(0x05)

wird ((0x20) + 0x05)

Page 29: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Textaufbereitung des Präprozessors für das Compilieren (2)

Beispiel mit Auszug aus sfr_defs.h#define _BV(bit) (1 << (bit))

und aus iom169.h#define PB5 5

Aus _BV(PB5)

wird(1 << (5))

Beide Beispiele werden auf folgende Anweisung angewendet:PORTB |= _BV(PB5)

Der Präprozessor macht also daraus den übersetzungsfähigen Text:((0x20) + 0x05) |= (1 << (5)).

Das führt aber zu keinem sinnvollen Code; denn der linke Ausdruck ist eine Konstante. Es fehlt links das Merkmal, dass der Ausdruck als Adresswert, also als Zeiger (Pointer) zu verwenden ist. Nur dann kann der Übersetzer auch Befehle mit einer zweckmäßigen Adressierung zuordnen.

Page 30: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Textaufbereitung des Präprozessors für das Compilieren (3)

Die Macros, die dafür in sfr_defs.h vorgesehen sind:

#define _SFR_MEM8(mem_addr) _MMIO_BYTE(mem_addr)#define _SFR_MEM16(mem_addr) _MMIO_WORD(mem_addr)#define _SFR_IO8(io_addr) _MMIO_BYTE((io_addr) + 0x20)#define _SFR_IO16(io_addr) _MMIO_WORD((io_addr) + 0x20)…

#define _MMIO_BYTE(mem_addr) (*(volatile uint8_t *)(mem_addr))#define _MMIO_WORD(mem_addr) (*(volatile uint16_t *)(mem_addr))…

Unter diesen Voraussetzungen wird aus

PORTB |= _BV(PB5);

die C-Anweisung

(*(volatile uint8_t *) ((0x20) + 0x05)) |= (1 << (5));

Page 31: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Textaufbereitung des Präprozessors für das Compilieren (4)Durch das Konstrukt

* ((0x20) + 0x05)

wird der Ausdruck ((0x20) + 0x05) dem Compiler als Zeiger erklärt. Innerhalb einer Anweisung steht das Konstrukt für das Zielobjekt, das dadurch adressiert wird.

In C erklärt der * Operator seinen Operanden als die Adresse eines Zielobjektes. Diese Erklärung als Zeiger muss mit einer Typangabe ergänzt werden, hier also:

* (uint8_t *) ((0x20) + 0x05)Das erklärt, dass der Zeiger ein Zielobjekt vom Typ unsigned char adressiert.

Port-Register sind Variable, deren Wert sich ändern kann, ohne dass eine programmierte Anweisung das tut. Diese Eigenschaft einer Variablen bzw. eines Zeiger auf eine solche Variable muss dem Compiler erklärt werden, indem man den Modifikator (modifier) volatile vor die Typangabe stellt.

*(volatile uint8_t *) ((0x20) + 0x05) |= (1 << (5));

Dadurch kann man verhindern, dass der Compiler die Variable wegoptimiert, wenn sie nicht explizit verwendet wird. Das Zeiger-Konzept von C umfasst wesentlich mehr Eigenschaften als hier beschrieben. Davon später.

Page 32: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

# WinAVR makefile # Released to the Public Domain#....# make all = Make software.## make clean = Clean out built project files.#....# MCU nameMCU = atmega169

# Output format. (can be srec, ihex, binary)FORMAT = ihex

# Target file name (without extension).TARGET = main

# Optimization level, can be [0, 1, 2, 3, s]. 0 turns off optimization.# (Note: 3 is not always the best optimization level. See avr-libc FAQ.)OPT = 1

# List C source files here. (C dependencies are automatically generated.)SRC = $(TARGET).c

# ....

Page 33: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

Listing: unterstützende Funktionen20 warten_ms:

....26 0000 0097 sbiw r24,0 227 0002 49F0 breq .L12 1(-)28 .L10:29 .LM3:30 0004 30E1 ldi r19,lo8(16) 131 .L9:32 .LM4:33 0006 3150 subi r19,lo8(-(-1)) 134 .LM5:35 0008 27E5 ldi r18,lo8(87) 136 .L8:37 .LM6:38 000a 2150 subi r18,lo8(-(-1)) 139 000c F1F7 brne .L8 2(+)40 000e 3323 tst r19 141 0010 D1F7 brne .L9 2(+)42 .LM7:43 0012 0197 sbiw r24,1 244 0014 B9F7 brne .L10 2(+)45 .L12:46 0016 0895 ret

Page 34: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

# WinAVR makefile # Released to the Public Domain#....# make all = Make software.## make clean = Clean out built project files.#....# MCU nameMCU = atmega169

# Output format. (can be srec, ihex, binary)FORMAT = ihex

# Target file name (without extension).TARGET = main

# Optimization level, can be [0, 1, 2, 3, s]. 0 turns off optimization.# (Note: 3 is not always the best optimization level. See avr-libc FAQ.)OPT = s

# List C source files here. (C dependencies are automatically generated.)SRC = $(TARGET).c

# ....

Page 35: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

20 warten_ms:..

26 .LM2:27 0000 0097 sbiw r24,028 0002 49F0 breq .L1229 .LM3:30 0004 34E6 ldi r19,lo8(100)31 .L9:32 .LM4:33 0006 3150 subi r19,lo8(-(-1))34 .LM5:35 0008 24E6 ldi r18,lo8(100)36 .L8:37 .LM6:38 000a 2253 subi r18,lo8(-(-50))39 000c F1F7 brne .L840 000e 3323 tst r1941 0010 D1F7 brne .L942 .LM7:43 0012 0197 sbiw r24,144 0014 F5CF rjmp .L11745 .L12:46 0016 0895 ret

Optimierungsbeispiel

Page 36: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

main() (1)

Hier ist noch nicht erkennbar, dass der Stackpointer mit der höchsten Adresse im SRAM geladen wird. Das wird endgültig im gelinkten Programm bewirkt.

main.lss.. 60: cf ef ldi r28, 0xFF62: d4 e0 ldi r29, 0x0464: de bf out 0x3e, r2966: cd bf out 0x3d, r28...

20 main:21 .LFB2:22 .LM1:23 /* prologue: frame size=0 */

24 0000 C0E0 ldi r28,lo8(__stack - 0)25 0002 D0E0 ldi r29,hi8(__stack - 0)26 0004 DEBF out __SP_H__,r2927 0006 CDBF out __SP_L__,r28

28 /* prologue end (size=4) */29 .LM2:30 0008 259A sbi 36-0x20,5

Page 37: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

main() (2)

void main(void){

uint16_t cnt;

/* enable PB5 as output */DDRB |= _BV(PB5);

/* Beep, Beep */cnt = 0;while (1) {

if ( cnt++ >= 1000 ) cnt = 0;

if ( cnt < 800 ) PORTB ^= _BV(PB5);/* ^ ist der Operator für die bitweise EXOR

Verknüpfung.*/

warten_ms(1);}

}

sbi set bit in i/o register

Man kann i/o-Register im memory-Adressraum adressieren oder im i/o-Adressraum. Im memory-Adressraum hat das Register DDRB die dezimale Adresse 36= 0x24. Subrahiert man von der Adresse im memory-Adressraum eine 0x20, erhält man 0x04, d.h. die äquivalente Adresse im i/o-Adressraum.

20 main:21 .LFB2:22 .LM1:23 /* prologue: frame size=0 */24 0000 C0E0 ldi r28,lo8(__stack - 0)25 0002 D0E0 ldi r29,hi8(__stack - 0)26 0004 DEBF out __SP_H__,r2927 0006 CDBF out __SP_L__,r2828 /* prologue end (size=4) */29 .LM2:30 0008 259A sbi 36-0x20,5

Page 38: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

main() (3)

void main(void){uint16_t cnt;

/* enable PB5 as output */DDRB |= _BV(PB5);

/* Beep, Beep */cnt = 0;while (1) {

if ( cnt++ >= 1000 ) cnt = 0;

if ( cnt < 800 ) PORTB ^= _BV(PB5);/* ^ ist der Operator für die bitweise EXOR

Verknüpfung.*/

warten_ms(1);}

}

31 .LM3:32 000a C0E0 ldi r28,lo8(0)33 000c D0E0 ldi r29,hi8(0)

34 000e 10E2 ldi r17,lo8(32)35 .L2:36 .LM4:37 0010 CE01 movw r24,r2838 0012 2196 adiw r28,139 0014 885E subi r24,lo8(1000)40 0016 9340 sbci r25,hi8(1000)41 0018 10F0 brlo .L442 .LM5:43 001a C0E0 ldi r28,lo8(0)44 001c D0E0 ldi r29,hi8(0)45 .L4:46 .LM6:47 001e 83E0 ldi r24,hi8(800)48 0020 C032 cpi r28,lo8(800)49 0022 D807 cpc r29,r2450 0024 18F4 brsh .L551 .LM7:52 0026 85B1 in r24,37-0x2053 0028 8127 eor r24,r1754 002a 85B9 out 37-0x20,r2455 .L5:56 .LM8:57 002c 81E0 ldi r24,lo8(1)58 002e 90E0 ldi r25,hi8(1)59 0030 0E94 0000 call warten_ms60 0034 EDCF rjmp .L2

Page 39: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

main() (4)

void main(void){

uint16_t cnt;

/* enable PB5 as output */DDRB |= _BV(PB5);

/* Beep, Beep */cnt = 0;while (1) {if ( cnt++ >= 1000 ) cnt = 0;

if ( cnt < 800 ) PORTB ^= _BV(PB5);/* ^ ist der Operator für die bitweise EXOR

Verknüpfung.*/

warten_ms(1);}

}

31 .LM3:32 000a C0E0 ldi r28,lo8(0)33 000c D0E0 ldi r29,hi8(0)34 000e 10E2 ldi r17,lo8(32)35 .L2:36 .LM4:

37 0010 CE01 movw r24,r2838 0012 2196 adiw r28,139 0014 885E subi r24,lo8(1000)40 0016 9340 sbci r25,hi8(1000)41 0018 10F0 brlo .L4

42 .LM5:43 001a C0E0 ldi r28,lo8(0)44 001c D0E0 ldi r29,hi8(0)

45 .L4:46 .LM6:47 001e 83E0 ldi r24,hi8(800)48 0020 C032 cpi r28,lo8(800)49 0022 D807 cpc r29,r2450 0024 18F4 brsh .L551 .LM7:52 0026 85B1 in r24,37-0x2053 0028 8127 eor r24,r1754 002a 85B9 out 37-0x20,r2455 .L5:56 .LM8:57 002c 81E0 ldi r24,lo8(1)58 002e 90E0 ldi r25,hi8(1)59 0030 0E94 0000 call warten_ms60 0034 EDCF rjmp .L2

Page 40: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

main() (5)

void main(void){

uint16_t cnt;

/* enable PB5 as output */DDRB |= _BV(PB5);

/* Beep, Beep */cnt = 0;while (1) {

if ( cnt++ >= 1000 ) cnt = 0;

if ( cnt < 800 ) PORTB ^= _BV(PB5);/* ^ ist der Operator für die bitweise EXOR

Verknüpfung.*/

warten_ms(1);}

}

31 .LM3:32 000a C0E0 ldi r28,lo8(0)33 000c D0E0 ldi r29,hi8(0)

34 000e 10E2 ldi r17,lo8(32)35 .L2:36 .LM4:37 0010 CE01 movw r24,r2838 0012 2196 adiw r28,139 0014 885E subi r24,lo8(1000)40 0016 9340 sbci r25,hi8(1000)41 0018 10F0 brlo .L442 .LM5:43 001a C0E0 ldi r28,lo8(0)44 001c D0E0 ldi r29,hi8(0)45 .L4:46 .LM6:

47 001e 83E0 ldi r24,hi8(800)48 0020 C032 cpi r28,lo8(800)49 0022 D807 cpc r29,r2450 0024 18F4 brsh .L5

51 .LM7:52 0026 85B1 in r24,37-0x2053 0028 8127 eor r24,r1754 002a 85B9 out 37-0x20,r24

55 .L5:56 .LM8:57 002c 81E0 ldi r24,lo8(1)58 002e 90E0 ldi r25,hi8(1)59 0030 0E94 0000 call warten_ms60 0034 EDCF rjmp .L2

Page 41: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

main() (5)

void main(void){

uint16_t cnt;

/* enable PB5 as output */DDRB |= _BV(PB5);

/* Beep, Beep */cnt = 0;while (1) {

if ( cnt++ >= 1000 ) cnt = 0;

if ( cnt < 800 ) PORTB ^= _BV(PB5);/* ^ ist der Operator für die bitweise EXOR

Verknüpfung.*/

warten_ms(1);}

}

Der Parameterwert 1 wird der Funktion im Registerpaar r24,r25 übergeben.

31 .LM3:32 000a C0E0 ldi r28,lo8(0)33 000c D0E0 ldi r29,hi8(0)34 000e 10E2 ldi r17,lo8(32)35 .L2:36 .LM4:37 0010 CE01 movw r24,r2838 0012 2196 adiw r28,139 0014 885E subi r24,lo8(1000)40 0016 9340 sbci r25,hi8(1000)41 0018 10F0 brlo .L442 .LM5:43 001a C0E0 ldi r28,lo8(0)44 001c D0E0 ldi r29,hi8(0)45 .L4:46 .LM6:47 001e 83E0 ldi r24,hi8(800)48 0020 C032 cpi r28,lo8(800)49 0022 D807 cpc r29,r2450 0024 18F4 brsh .L551 .LM7:52 0026 85B1 in r24,37-0x2053 0028 8127 eor r24,r1754 002a 85B9 out 37-0x20,r2455 .L5:56 .LM8:

57 002c 81E0 ldi r24,lo8(1)58 002e 90E0 ldi r25,hi8(1)59 0030 0E94 0000 call warten_ms

60 0034 EDCF rjmp .L2

Page 42: C für Mikrocontroller - Ruhr-Universität · PDF fileC für Mikrocontroller Die wichtigste Alternative zum Assembler-Programmieren von Anwendungen für Mikrocontroller ist die Programmierung

main() (6)

void main(void){

uint16_t cnt;

/* enable PB5 as output */DDRB |= _BV(PB5);

/* Beep, Beep */cnt = 0;while (1) {

if ( cnt++ >= 1000 ) cnt = 0;

if ( cnt < 800 ) PORTB ^= _BV(PB5);/* ^ ist der Operator für die bitweise EXOR

Verknüpfung.*/

warten_ms(1);}

}

31 .LM3:32 000a C0E0 ldi r28,lo8(0)33 000c D0E0 ldi r29,hi8(0)

34 000e 10E2 ldi r17,lo8(32)35 .L2:36 .LM4:37 0010 CE01 movw r24,r2838 0012 2196 adiw r28,139 0014 885E subi r24,lo8(1000)40 0016 9340 sbci r25,hi8(1000)41 0018 10F0 brlo .L442 .LM5:43 001a C0E0 ldi r28,lo8(0)44 001c D0E0 ldi r29,hi8(0)45 .L4:46 .LM6:47 001e 83E0 ldi r24,hi8(800)48 0020 C032 cpi r28,lo8(800)49 0022 D807 cpc r29,r2450 0024 18F4 brsh .L551 .LM7:52 0026 85B1 in r24,37-0x2053 0028 8127 eor r24,r1754 002a 85B9 out 37-0x20,r2455 .L5:56 .LM8:57 002c 81E0 ldi r24,lo8(1)58 002e 90E0 ldi r25,hi8(1)59 0030 0E94 0000 call warten_ms60 0034 EDCF rjmp .L2

Es kostet einige Mühe, durch Abzählen der verbrauchten Zyklen eine möglichst exakte Periode für die Tonausgabe zu erreichen, weil man auch die Übersetzungsstrategie des Compilers einbeziehen muss. Solche Aufgabenstellungen löst man besser unter Benutzung eines Timers.