Arduino in C
Der Arduino kann auch in reinem C programmiert werden, ganz ohne die Arduino spezifischen Libraries
Will man mit der Arduino zeitkritische Anwendungen realisieren wird man schnell merken, dass es irgendwie hakt. Der Grund dafür liegt in der Implementierung der Arduino-Libraries begründet. Um diesem Problem aus dem Weg zu gehen kann man jeden Mikrocontroller des Arduino-Projekts auch ohne die Arduino-Software betreiben.Vorteile
- Direkte Port-Manipulation über das Register ist schneller als die Arduino eigene Implementierung
- Mehrere Ports gleichzeitig änderbar
- Allgemein höhere Ausführgeschwindigkeit da keine unnötige Kapselung vorgenommen wird
- Unabhängig von den Arduino-Libraries
- Geringere Größe der Binary
- Größere Flexibilität
- Vor allem für Anfänger schlechter lesbar
- Einarbeitungszeit ist höher
- Verlust diverser Komforfunktionen (die allerdings manuell eingebunden werden können)
Licht an, Licht aus
Im folgenden das klassische Hello-World bzw. Blink-Programm in C, ganz ohne Arduino-Libraries:- #include <avr/io.h>
- #include <util/delay.h>
- int main (void) {
- DDRB |= 0b00100000;
- while (1) {
- PORTB |= 0b00100000;
- _delay_ms(500);
- PORTB &= 0b11011111;
- _delay_ms(500);
- }
- return 0;
- }
Die Funktion der einzelnen Zeilen:
- Inklusion der avr/io.h welche zuständig ist für die Zuweisung der Ports und Register (z.B. PORTB)
- Inklusion der util/delay.h welche die Funktion _delay_ms() beinhaltet
- Definition der main()-Routine mit dem Rückgabetyp int
- Pin 13 als Ausgang setzen (siehe dazu das Datenblatt des Atmega328)
- Definition einer while-Schleife welche für immer läuft, dies entspricht der loop()-Funktion in der Arduino-IDE
- Pin 13 auf HIGH setzen mittels bitweisem ODER
- Dann wartet das Programm 500ms
- Pin 13 auf LOW setzen mittels bitweisem UND
- Dann wartet das Programm erneut 500ms
- Das Ende der while-Schleife
- Der Return-Wert muss für die Main-Funktion definiert werden, auch wenn das Programm diesen Punkt niemals erreichen kann
- Das Ende der Main-Funktion
Übertragen des Programms
Bevor man das Programm übertragen kann muss man eventuell noch ein paar Programme installieren, unter anderem avr-gcc, avr-objcopy sowie avrdude. Wer die Arduino-IDE installiert hat kann diesen Schritt überspringen, da diese ebenfalls auf diese Programme zurückgreift und diese somit bereits installiert sind.Zuerst muss man das Programm kompilieren:
$ avr-gcc -Os -DF_CPU=16000000UL -mmcu=atmega328 -o blink.out blink.c
Die Angabe „-Os“ schaltet dabei die automatische Optimierung ein und wird zwingend gebraucht damit die Zeitverzögerung mittels _delay_ms() richtig funktionieren kann. Die Angabe DF_CPU=16000000UL gibt die Taktfrequenz des Mikrocontrollers an, bestimmt diese aber nicht. Sie wird lediglich zum richtigen funktionieren von _delay_ms() benötigt. Ausserdem wird noch der Typ des Mikrocontrollers angegeben (atmega328) sowie die Ausgabedatei bestimmt (blink.out).
Anschließend wandelt man das ganze in das Intel-HEX-Format um:
$ avr-objcopy -O ihex -R .eeprom blink.out blink.hex
Und überträgt die daraus resultierende Datei zum Mikrocontroller:
$ avrdude -F -c stk500v1 -p m328p -b 115200 -P /dev/ttyACM0 -U flash:w:blink.hex
Dafür wird der Zugriff auf die serielle Schnittstelle benötigt. Hat der eigene Benutzer diese nicht, muss das Programm als Root übertragen werden (alternativ kann man sich auch einfach zur entsprechenden Gruppe hinzufügen). Die Parameter im Detail erklärt:
- -F → dient zum Überspringen des Device-Signature-Check welcher bei einer Übertragung über USB nicht verfübgar ist
- -c stk500v1 → gibt das Protokill des Programmers an
- -p m328p → gibt den Typ des Mikroprozessors an
- -b 115200 → gibt die Baudrate an
- -P /dev/ttyACM0 → gibt den Port an. Für gewöhnlich /dev/ttyACMx wobei x bei 0 beginnt
- -U flash:w:blink.hex → Diese Zeile muss von rechts nach links gelesen werden. Sie bedeutet, dass von blink.hex gelesen wird und auf den ROM (flash) geschrieben wird (w wie write)
Der Bootloader wird übrigens nach dieser Methode beim Flashen nicht überschrieben.
Fertig
Damit hat man das Programm übertragen und der Mikrocontroller ist einsatzbereit. Nun unterbricht man kurz die Spannungversorgung des Mikrocontrollers und kann der LED in ihrer vollen Pracht beim Blinken zusehen.Cheat Sheet
Für die grundlegenden Funktionen im folgenden noch ein Cheat Sheet für den schnellen Überblick:Funktion | Schema | Beispiel |
---|---|---|
Port als Eingang definieren | REGISTER &= ~(1<<PORT); | DDRB &= ~(1<<PB5); |
Port als Ausgang definieren | REGISTER |= (1<<PORT); | DDRD |= (1<<PD2); |
Ausgang auf LOW setzen | REGISTER &= ~(1<<PORT); | PORTB &= ~(1<<PB5); |
Ausgang auf HIGH setzen | REGISTER |= (1<<PORT); | PORTB |= (1<<PB5); |
Ausgang toggeln | REGISTER ^= (1<<PORT); | PORTB ^= (1<<PB5); |
Eingang abfragen | if (REGISTER & (1<<PORT)) { ANWEISUNG } |
if (PIND & (1<<PIND2)) { PORTB |= (1<<PB5) } |
Will man mehrere Ports zusammenfassen so kann man dies über den UND bzw. ODER Operator machen:
DDRB &= ~( (1<<PB3) | (1<<PB5) )
DDRB |= (1<<PB5) | (1<<PB6)
DDRB |= (1<<PB5) | (1<<PB6)
Setzt man einen Eingang auf HIGH bzw. LOW wird dadurch der interne PullUp bzw. PullDown-Widerstand gesetzt.
Revision 1 | 2014-03-20 19:36:09