AVR-Programme debuggen, Teil 1: Fehler mit der I/O-RegisterĂĽbersicht finden
Ergänzend zum Artikel in der Make 4/24 zeigen wir Ihnen hier, wie Sie mit dem MPLAB Snap Debugger und den Registern des ATtiny Programmierfehler aufspüren.
(Bild: shutterstock.com/ KatePilko)
- Florian Schäffer
Nachdem Sie mit dem Artikel AVR-Programme debuggen, Teil 1 aus der Make 4/24 erfolgreich Ihren ersten Code auf dem ATtiny debuggt haben, folgt hier ein weiteres Beispiel: In diesem erklären wir, wie Sie mit dem MPLAB Snap Fehler finden, die beim direkten Zugriff auf Register und Bitmanipulationen entstehen können.
Dafür nutzen wir den gleichen Aufbau wie im Hauptartikel und dieses Mal auch die zweite (grüne) LED: Beide sollen einfach zum Leuchten gebracht werden – nicht gerade aufregend, aber es geht weniger um den Effekt, als um das, was unter der Haube passiert (bzw. unter der Vergussmasse auf dem Mikrocontroller).
Register sind besondere Speicherbereiche und bilden das Rückgrat der RISC-Architektur (Reduced Instruction Set Computer), die auch Microchip-AVRs (vormals Atmel AVR) wie der ATtiny oder ATmega nutzen. Daten können nur verarbeitet werden, wenn sie in einem Register gespeichert sind. Im Hintergrund werden deshalb bei allen Berechnungen etc. die jeweiligen Daten in ein Register hinein und wieder raus kopiert.
Bei den AVRs (und anderen Mikrocontroller) gibt es noch Spezialregister, die für das Debugging eine besondere Rolle spielen: In den 8-Bit breiten Speicherstellen bei AVRs befinden sich Statusregister, um das Verhalten der CPU zu steuern. Ob ein I/O-Pin als Ausgang oder Eingang arbeitet, wird im Data Direction Register (DDR) festgelegt. Der Zustand eines I/O-Pins (High oder Low) liegt für jeden Pin einzeln im Data Register und wird bei Pins, die als Ausgang arbeiten, vom Programmcode geändert. Soll ein Pull-up-Widerstand einem Eingang zugeschaltet werden, ändert man das Port Register. Es gibt über 80 weitere Register zur Kontrolle der seriellen Schnittstellen (USART), SPI, Interrupts, Timer usw.
Als Nutzer der Arduino IDE bekommen Sie davon nichts mit, denn anstatt direkt auf die Register zuzugreifen, nutzen Sie Funktionen wie Serial.begin() oder pinMode(). Um erfolgreich ein Programm zu debuggen, müssen Sie sich allerdings in die technischen Hintergründe einarbeiten und wissen, was die Aufgabe der Register ist. Die Datenblätter zu Microchips Mikrocontrollern sind sehr empfehlenswert und stammen wie beim ATmega328 teilweise noch von Atmel, dem ehemaligen Entwickler der Chips. Sie sind zwar auf Englisch aber sehr ausführlich, relativ leicht verständlich und zu sämtlichen Funktionen gibt es kurze Beispiel in C.
Ausgänge konfigurieren
Erstellen Sie ein neues Projekt in Microchip Studio fĂĽr den ATtiny212 und richten Sie es genauso ein wie beim vorherigen Beispiel (Debugger, EEPROM-Speicher, Compiler-Optimierungen) oder laden Sie sich das Projekt attiny2.atsln aus dem GitHub-Repository des Projekts.
#define F_CPU 3333333UL
#include <avr/io.h>
#include <util/delay.h>
int main(void)
{
PORTA.DIR = (1 << 1) & (1 << 2);
PORTA.OUT = (1 << 1);
PORTA.OUT = (1 << 2);
while(1);
return(0);
}
Das kurze Programm ähnelt auf den ersten Blick dem ersten Beispiel. Wenn Sie genauer hinsehen, werden Sie aber feststellen, dass hier nicht DIRSET und OUTSET benutzt wurden – mehr zu dem Unterschied steht im Kasten „Ausgänge konfigurieren und ansteuern“ als Hintergrundwissen. Stattdessen soll die Anweisung
PORTA.DIR = (1 << 1) & (1 << 2);
dafür sorgen, dass die Pins PA1 (Anschluss 4 am ATtiny212) und PA2 (Anschluss 5) als Ausgang arbeiten – deshalb die UND-Verknüpfung. Anschließend wird auf das DIR-Register zugegriffen, um den Ausgangs-Pin PA1 und danach PA2 einzuschalten. So ist zumindest der Plan, der natürlich nicht aufgehen wird.
Der ATtiny nutzt ein paar Register für den Zugriff auf die I/O-Pins, die etwas von den älteren abweichen, die Sie in der Make-Ausgabe 5/24 beim Arduino Uno noch kennenlernen werden. Das Prinzip ist aber das gleiche: Zuerst wird festgelegt, ob ein Pin als Ein- oder Ausgang arbeiten soll und dann kann jeder Pin auf High oder Low geschaltet werden.
Dafür geben Sie zuerst an, auf welchen Port zugegriffen werden soll. Je nach Anzahl der I/O-Pins gibt es mehrere Ports mit je bis zu acht Pins. Beim ATtiny gibt es nur Port A (PORTA). Im Register DIR (Data Direction) wird festgelegt, ob ein Pin als Ein- oder Ausgang geschaltet wird, indem man das jeweilige Bit auf 1 (Pin als Ausgang) setzt oder mit 0 (Pin als Eingang) löscht.
Auch wenn das Register 8 Bit breit ist, hat der ATtiny212 nur sechs I/O-Pins, die zudem etwas verquer nummeriert sind: PA0, PA1, PA2, PA3, PA6 und PA7. Ein Bit lässt sich mit der Anweisung = (1 << 3) in einem Register setzen. Der Wert 1 wird dabei um drei Stellen nach links geshiftet und der sich ergebende Wert (0000 0100 binär) dann zugewiesen. Dadurch wird das dritte Bit (von rechts gelesen) und damit PA3 zu einem Ausgang und alle anderen Pins sind Eingänge. Welches Bit für welchen Pin zuständig ist, finden Sie im Datenblatt in Kapitel 16, aber die Zuordnung ist immer logisch von rechts nach links fortlaufend. Das Löschen eines Bits erfolgt mit einer ähnlichen Shift-Operation, deren Ergebnis aber negiert wird. Die Methode ist zwar gebräuchlich, aber nicht ganz benutzerfreundlich.
Bei den neuen Chips gibt es zusätzlich das Register DIRSET (Data Direction Set), um die einzelnen Pins des Ports als Ausgang zu schalten. Mit DIRSET können Sie keine Pins als Eingang setzen! Wenn Sie = (1 << 3) zusammen mit dem Befehl DIRSET benutzen, werden lediglich die Bits in das DIR-Register übernommen, bei denen eine 1 steht. Die Bits mit 0 haben keinen Effekt und es wird nichts geändert. Ähnlich funktioniert DIRCLR (Data Direction Clear): Nur bei Bits, die den Wert 1 haben, werden ihre korrespondierenden Pins zu Eingängen, indem im DIR-Register die Bits gelöscht (auf 0 gesetzt) werden.
Ob Sie nun direkt mit DIR arbeiten oder den Umweg über DIRSET und DIRCLR gehen, ist nicht wichtig – Hauptsache, die Bits werden korrekt eingestellt. Genauso funktionieren die Register OUT, OUTSET und OUTCLR, um den Zustand eines Ausgangs (High oder Low) zu bestimmen.