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).
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);