header
Jeder, der schon einmal ein Programm mit 100 Zeilen oder mehr programmiert hat, ist dabei auf folgendes Problem gestoßen: Im Code ist ein Fehler, der sich nicht so leicht finden lässt. Meist kann man die fehlerhafte Stelle mit einem Debugger finden. Auch sehr hilfreich ist es, in ausgewählten Zeilen des Codes ein "printf(...)", "MessageBox(...)" oder Ähnliches zu platzieren, um den Programmverlauf so zu verfolgen und sich den Inhalt wichtiger Variablen ausgeben zu lassen.

Nur wie funktioniert das mit einem Mikrocontroller, der leider kein eigenes Ausgabegerät hat und auch in vielen Schaltungen nicht an ein Display angeschlossen ist?

Hier liefert der Raspberry Pi mit seinen GPIO-Pins eine großartige Lösung! Mit der GPIO-Schnittstelle kann man nämlich nicht nur den AVR programmieren, sondern auch mit dem Chip kommunizieren bzw. im einfachsten Fall Daten vom Chip empfangen.

Standard-Protokolle
Üblich ist es, Daten per UART, SPI oder I2C zu übertragen. Abhängig vom Einsatzgebiet des μC liefert jede dieser Schnittstellen Vor- und Nachteile. Hier (*klick*) gibt es dafür ein paar Anwendungsbeispiele!

Nicht-Standard-Kommunikation mit dem Chip
Alle die oben genannten Kommunikationswege haben aber gemeinsam, dass sie auf dem Raspberry Pi schwer in Anwendersoftware implementierbar und komplizierter als erforderlich sind (I2C ist z.B. fähig, über einen BUS mit mehr als 100 Geräten zu kommunizieren.Dies lässt sich nur über ein nicht-triviales Protokoll erzielen.)
Hier stelle ich nun ein leistungsfähiges Verfahren vor, dass ich speziell für die Datenübertragung von AVR-Controller zum Raspberry Pi entwickelt habe. Das Verfahren ist leicht implementierbar, verwendet wenig Speicher im μC, belegt nur 3 Datenpins und kann leicht an die unterschiedlichsten Anwendungen innerhalb des Raspberry Pi angepasst werden: so können die Daten nicht nur über ein spezielles Programm im Raspberry Pi empfangen werden, sondern auch über Python, Perl und sogar PHP (Letzteres ist theoretisch möglich, allerdings praktisch ohne Nutzen. Später dazu mehr). Also können die vom Mikrocontroller gesendeten Daten auch über den Browser eines beliebigen Rechners, Tablets oder Handys abgerufen werden :-)

Die Schaltung
Betrachten wir einmal folgende Beschaltung eines Attiny45:
Schaltbild

Die Schaltung funktioniert selbstverständlich auch mit dem Atmega. Den Attiny habe ich für das Schaltbild gewählt, um die Grafik einfach zu halten.

Der Attiny ist durch 3 digitale Leitungen mit dem Raspberry Pi verbunden: Leitung A, C und D. Über D und C sendet der μC Daten an den Raspberry und über A empfängt er etwas. Der Widerstand R1 ist ein Pulldown-Widerstand, der die Leitung auf Masse zieht.


C steht für Clock. Bei jeder steigenden oder fallenden Taktflanke an C kann der Empfänger ein neues Bit an der Leitung D (=Data) auslesen. Das MSB eines jeden Bytes wird zuerst gesendet, absteigend zum LSB. Das Empfangsgerät muss jedes Bit über die Leitung A (=acknowledge) "bestätigen", indem es den aktuellen Zustand von C an der Leitung A wiederholt. Wenn der μC die Bedingung "A=C" festgestellt hat, wird das nächste Bit an die Leitung D angelegt und der Zustand von Leitung C ändert sich erneut.
Der initiale Wert an Leitung C ist immer 1 (=Vcc).

Der Code für den AVR
Den Code gibt es hier

Dieser Code wurde auf dem Atmega88 und dem Attiny13 getestet. Die Erweiterung auf den Betrieb mit printf(...) benötigt leider ein paar KB Speicher und funktioniert nur auf dem Atmega.


Um die besonderen Features von printf, sprintf usw. nutzen zu können, müssen wir stdout verändern: Unter avr-gcc gibt es die Möglichkeit, die Ausgabe auf eine Datei umzuleiten: wir brauchen eine Funktion, die ein einzelnes Byte als Eingabe erhält und dieses dann an unsere sendpi-Funktion weitergibt:

FILE new_stdout = FDEV_SETUP_STREAM(sendpi_byte,
	NULL, _FDEV_SETUP_RW);

int sendpi_byte(char b, FILE *s) {
        char x[2];
        x[0]=b;
        x[1]='\0';
        sendpi(x);
        return 0;
}
Mit FDEV_SETUP_STREAM wird die Funktion sendpi_byte sozusagen auf den Datenstrom geschaltet. Für jedes Byte, das in new_stdout (und dann also später auch in stdout) geschrieben wird, wird die Funktion sendpi_byte aufgerufen.
In die main()-Funktion kommt dann noch die folgende Zeile:
stdout=&new_stdout;


Der Code für den Raspberry Pi
Eine besonere Stärke der oben vorgestellen Datenübertragungsmethode ist, dass wir mit jeder erdenklichen Programmiersprache oder Skriptsprache, die wir auf dem Raspberry Pi installiert haben, Daten aus dem AVR empfangen können. In der verlinkten zip-Datei (*klick*) sind Codes in Python und Perl.

Mit einem kleinen Trick lässt sich der Perl-Code auch als CGI-Skript durch den Webserver ausführen. (Hierzu muss lediglich die erste Ausgabe lauten: print "Content-Type: text/plain\n\n";). Sogar in PHP kann man auf die GPIO-Schnittstelle zugreifen, allerdings erfolgt das durch trim(shell_exec("cat [...]")) und trim(shell_exec("echo [...]")). Da sich der Code sonst von der Perl- und der Python-Version nicht unterscheidet, ist die Lösung in PHP, da sie extrem langsam ist, weder praktisch noch theoretisch interessant. Natürlich geht das auch nur, wenn der Webserver unter einem User gestartet wird, der die nötigen Zugriffsrechte auf das /sys/class/gpio-Dateisystem hat. ;-)