Serie inDetail - komplexe Themen verständlich dargestellt

Programm zum Blinken einer LED

Im folgenden wird der Arduino Blink Sketch in Maschinencode entwickel und auf den ATtiny übertragen.

Wie im vorhergehenden Kapitel muss man zunächst den Pin 2 im DDRB Register als Ausgang definieren. Über das Register PORTB kann man den Ausgang bzw. die LED dann ein- und wieder ausschalten.

Neu kommt die Umsetzung der delay() Funktion dazu. Nachdem die LED eingeschaltet wurde muss im Programmablauf zu einer Subroutine gesprungen werden - die eine Zeit lang dauert - und anschließend wieder zurück zum Befehl "LED abschalten". Dann wieder Sprung zur Subroutine und am Anschluss erneut zurück zum Befehl "LED anschalten". Und das ganze in einer Dauerloopschleife.

Assemblercode

In Assembler sieht das Blink Programm zum Beispiel folgendermaßen aus:

00: SBI DDRB, 3 ; Set Bit 3 in I/O Register DDRB (0x17) 01: SBI PORTB, 3 ; Set Bit 3 in I/O Register PORTB (0x18) 02: RCALL +3 ; Relative Call to Subroutine Address 06 (next Programmadress + 3) 03: CBI PORTB, 3 ; Clear Bit 3 in I/O Register PORTB (0x18) 04: RCALL +1 ; Relative Call to Subroutine Address 06 (next Programmadress + 1) 05: RJMP -5 ; Relative Jump to Programm Address 01 06: LDI R16, 255 ; Load Immediately 255 in Register 16 (0x00) 07: LDI R17, 255 ; Load Immediately 255 in Register 17 (0x01) 08: DEC R17 ; Decrement R17 09: BRNE -1 ; Branch if not equal (to address 08) 10: DEC R16 ; Decrement R16 11: BRNE -5 ; Branch if not equal (to address 07) 12: RET ; Return from Subroutine to the main Programm

Ganz links steht zu Information die Adresse im Programmspeicher (Flash). Dann folgt der eigentliche Assemblerbefehl und die Kommentare stehen nach dem ';' Zeichen.

In Adresse 00 steht ein Befehl der nur einmalig ausgeführt wird. In Adresse 01-05 ist das sich ständig wiederholende Hauptprogramm gespeichert. Die Subroutine für die Zeitverzögerung ist in Adresse 06-12 gespeichert.

Setup (00)

Im DDRB Register wird das Bit 3 gesetzt. Pin 2 am µC ist damit asl Ausgang eingestellt.

Main-Loop (01-05)

Pin 2 und damit die LED wird eingeschaltet. Danach wird zu der Subroutine (Adresse 06) gesprungen indem dem die nächsten 3 Befehle übersprungen werden. Beim Springen mit RCALL merkt sich der µC die Adresse an dem das Hauptprogramm nach Beendigung der Subroutine fortgesetzt wird.

Nach der Zeitverzögerung-Subroutine wird das Hauptprogramm bei Adresse 03 fortgesetzt und die LED ausgeschaltet. Dann wird wieder zu Subroutine gesprungen und dabei erneut im RAM Speicher die Adresse zur Weiterführung des Hauptprogramms zwischengespeichert.

Nach der Rückkehr von der Subroutinen erfolgt mit dem folgenden Befehl RJMP ein Rücksprung von 5 Programmzeilen. Der Main-Loop startet damit erneut bei Zeile 01.

Subroutine

Die Zeitverzögerung wird einer Zählschleife durchgeführt. Zuerst läd man in zwei leere Speicherbereiche jeweils die Zahl 255. Dann wird mit in Codezeile 08 vom Speicher R17 1 abgezogen. Mit BRNE erfolgt jeweils der Rücksprung zu Zeile 08 solange der Registerwert nicht 0 ist (255x). Ist der Inhalt im R17 =0 wird Codezeile 10 ausgeführt und von R16 1 abgezogen. Solange dann R16 nicht 0 ist wird immer wieder zu Zeile 07 gesprungen. R17 mit 255 gefüllt und danach mit jedem Prozessortakt um 1 reduziert. Immer wenn R17 =0 ist wird R16 um 1 reduziert.

Die Zeitverzögerung ergibt daducrh, dass der Prozessor mit einer Zählung von 255 x 255 = >65000 Takte beschäftigt ist.

Machinecode

Jede Assemblerbefehl entspricht 1zu1 einer Maschinencode Befehl. Mit dem AVR Instruction Set kann man die Befehle übersetzten. Um die Befehle nachzuvollziehen noch ein Hinweis: Um negative Zahlen binär darzustellen wird das Two's Complement Verfahren verwendet.

Das Blink Programm hat in Summe eine Programmgröße von 13 Befehlen x 2 Byte = 26 Byte. Der Arduino Blink Sketch mit Bootloader und Co. benötigt 924 Byte (=462 Codezeilen).

Größe des Blink Sketches im Flash Speicher

Programmieren des ATtiny

Dargestellt sind nur die Codezeilen die im Arduino Uno Programm (Kapitel zum Flash beschrieben) ausgetauscht werden muss.

// Programmcode der übertragen wird (LED blinken) // Setup loadWordToBuffer(0, 0b1001101010111011); // SBI 0x17, 3 // Main_Loop loadWordToBuffer(1, 0b1001101011000011); // SBI 0x18, 3 loadWordToBuffer(2, 0b1101000000000011); // RCALL +3 (+3 = 0b000000000011) loadWordToBuffer(3, 0b1001100011000011); // CBI 0x18, 3 loadWordToBuffer(4, 0b1101000000000001); // RCALL +1 (+1 = 0b000000000001) loadWordToBuffer(5, 0b1100111111111011); // RJMP -5 (-5 = 0b111111111011) // delay_Subroutine loadWordToBuffer(6, 0b1110111100001111); // LDI 0x00, 255 loadWordToBuffer(7, 0b1110111100011111); // LDI 0x01, 255 loadWordToBuffer(8, 0b1001010100011010); // DEC R17 loadWordToBuffer(9, 0b1111011111110001); // BRNE -1 (-1 = 0b111110) loadWordToBuffer(10, 0b1001010100001010); // DEC R16 loadWordToBuffer(11, 0b1111011111011001); // BRNE -5 (-5 = 0b111011) loadWordToBuffer(12, 0b1001010100001000); // RTE to the return address in the stack writeBufferToFlash(0);