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 Nachteile

Licht an, Licht aus

Im folgenden das klassische Hello-World bzw. Blink-Programm in C, ganz ohne Arduino-Libraries:
  1. #include <avr/io.h>
  2. #include <util/delay.h>
  3. int main (void) {
  4.   DDRB |= 0b00100000;
  5.   while (1) {
  6.     PORTB |= 0b00100000;
  7.     _delay_ms(500);
  8.     PORTB &= 0b11011111;
  9.     _delay_ms(500);
  10.   }
  11.   return 0;
  12. }

Die Funktion der einzelnen Zeilen:
  1. Inklusion der avr/io.h welche zuständig ist für die Zuweisung der Ports und Register (z.B. PORTB)
  2. Inklusion der util/delay.h welche die Funktion _delay_ms() beinhaltet
  3. Definition der main()-Routine mit dem Rückgabetyp int
  4. Pin 13 als Ausgang setzen (siehe dazu das Datenblatt des Atmega328)
  5. Definition einer while-Schleife welche für immer läuft, dies entspricht der loop()-Funktion in der Arduino-IDE
  6. Pin 13 auf HIGH setzen mittels bitweisem ODER
  7. Dann wartet das Programm 500ms
  8. Pin 13 auf LOW setzen mittels bitweisem UND
  9. Dann wartet das Programm erneut 500ms
  10. Das Ende der while-Schleife
  11. Der Return-Wert muss für die Main-Funktion definiert werden, auch wenn das Programm diesen Punkt niemals erreichen kann
  12. 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: Mit der Option -U kann man ausserdem auch den Flash des Atmega auslesen und in eine Datei übertragen („-U flash:r:blink.hex:i“ , „:i“ ist nötig um das Intel-HEX-Format zu spezifizieren)

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)

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
Wer bin ich? | Datenschutz | Impressum
Copyright © r3v0luti0n