Anfänger mit STK500 und Assembler
Hallo Leute,
beschäftige mich mit der Programmierung des STK500 mit Assembler.
Naja vielleicht sollte ich lieber schreiben, ein Newbi möchte sich mit der Assemblerprogrammierung des STK500 beschäftigen.
Als Software benutze ich die mitgelieferte AVR-Studio.
Habe im Vorfeld schon einiges in Foren und Büchern gelesen.
So als Einstieg und zum besseren Erlernen möchte ich mir kleine Aufgaben stellen und diese dann umsetzen.
Den Code werde ich hier posten und wäre sehr an euren Meinungen, Vorschlägen und Kritiken interessiert.
Zu den Code werde ich immer Kommentare anfügen, damit man versteht was ich damit bezwecke.
Anfangen möchte ich mit einer einfachen Tastenschaltung.
Bei dieser sollen die 8 LEDs des STK500 mit einer Taste entprellt an- bzw. ausgeschaltet werden.
Das STK500 habe ich mit einen ATMega8515 bestückt. PORTB ist mit den Tastern und PORTD mit den LEDs verbunden.
Code:
;***** STK500 Lernprogramm
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 an bzw. ausschalten
;*** zum Entprellen soll ein Interrupt(Int0) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def LED_STAT = r17 ; LED sind mit PortB verbunden
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
ldi temp, 1 << INT0 ;Interrupt INT0 aktiviert
out GICR, temp
ldi temp, 1 << ISC00 ;Interrupt INT0 konfiguriert
ori temp, 1 << ISC01
out MCUCR, temp
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rjmp MAIN ;Die Schleife ruft mit dem Sprungbefehl
;rjmp sich ständig selbst auf.(endlos)
INT0_ISR:
cli ;Interrupts sperren
in LED_STAT, PORTB ;PORTB auslesen in LED_STAT
com LED_STAT ;Invertieren von LED_STAT
out PORTB, LED_STAT ;Ausgabe von temp an PORTB -> LEDs aus
reti
Das Programm habe ich zum STK500 übertragen und tut auch das was es soll.
verstehe ich das so richtig?
Hallo mare_crisium,
habe mein Listing angepasst.
Beim Testen im Simulator habe ich mir den Datenspeicher mit als Fenter anzeigen lassen und den Verlauf verglichen.
Das erste was nach Eintreten der ISR auf den Stack kommt ist der Programmcounter(zaehler). Dann werden die Registerinhalte, wie ich sie sichere, nach unten gestapelt und spaeter wieder ausgelesen.
Die Speicherinhalte vom Datenspeicher bleiben bestehen, bis sie von erneuten Aufrufen der ISR ggf. ueberschrieben werden.
Code:
;***** STK500 Lernprogramm
;*** Aufgabe: alle LEDs mit einem Taster auf dem STK500 an bzw. ausschalten
;*** zum Entprellen soll ein Interrupt(Int0) benutzt werden
;***
.include "m8515def.inc"
.def Temp = r16 ; Temporary register
.def LED_STAT = r17 ; LED sind mit PortB verbunden
;*****
;Reset and Interrupt vector ;VNr. Beschreibung
rjmp RESET ;1 POWER ON RESET
rjmp INT0_ISR ;2 Int0-Interrupt
reti ;3 Int1-Interrupt
reti ;4 TC1 Capture
reti ;5 TC1 Compare Match A TC2 Overflow
reti ;6 TC1 Compare Match B TC1 Capture
reti ;7 TC1 Overflow TC1 Compare Match A
reti ;8 TC0 Overflow TC1 Compare Match B
reti ;9 SPI, STC Serial Transfer Complete TC1 Overflow
reti ;10 UART Rx Complete TC0 Overflow
reti ;11 UART Data Register Empty SPI, STC Serial Transfer Complete
reti ;12 UART Tx Complete UART Rx Complete
reti ;13 Analog Comparator
reti ;14 Int2-Interrupt
reti ;15 Timer 0 Compare Match
reti ;16 EEPROM Ready
reti ;17 Store Program Memory Ready
RESET:
ldi r16, LOW(RAMEND) ;Stack initialisieren
out SPL, r16
ldi r16, HIGH(RAMEND)
out SPH, r16
ldi temp, 1 << INT0 ;Interrupt INT0 aktiviert
out GICR, temp
ldi temp, 1 << ISC00 ;Interrupt INT0 konfiguriert
ori temp, 1 << ISC01
out MCUCR, temp
clr Temp ;Temp mit 0b00000000 bzw. 0x00 laden
out DDRD, Temp ;PORTD als Eingang
ser Temp ;Temp mit 0b11111111 bzw. 0xFF laden
out PORTD, temp ;PullUp an PortD einschalten
out DDRB,Temp ;PORTB als Ausgang
out PORTB, temp ;PORTB (LEDs) aus
sei ;Interrupts zulassen
MAIN:
rjmp MAIN ;Die Schleife ruft mit dem Sprungbefehl
;rjmp sich ständig selbst auf.(endlos)
INT0_ISR:
push R16 ;Inhalt von R16 auf Stack ablegen
in R16, SREG ;Statusregister in R16 lesen
push R16 ;Inhalt von R16(SREG) auf den Stack ablegen
push R17 ;Inhalt von R17 auf den Stack ablegen
cli ;Interrupts sperren
in LED_STAT, PORTB ;PORTB auslesen in LED_STAT
com LED_STAT ;Invertieren von LED_STAT
out PORTB, LED_STAT ;Ausgabe von LED_STAT an PORTB -> LEDs an bzw. aus
pop R17 ;Ruecksichern von R17
pop R16 ;Ruecksichern von R16(SREG)
out SREG, R16 ;Ruecksichern von SREG
pop R16
reti
Liste der Anhänge anzeigen (Anzahl: 1)
robo_wolf,
meine Kommentare sind diesmal ein bisschen länglich ausgefallen ;-), deshalb hab' ich sie als .pdf angehängt.
Ciao,
mare_crisium
Liste der Anhänge anzeigen (Anzahl: 1)
robo_wolf,
als erstes fiel mir am Lernprogramm Nr. 4 auf, dass Du nach dem Label 'WARTE' diese "push"-Anweisungen eingebaut hast. Die brauchst Du gar nicht. Auf die Gefahr hin, Dir nichts Neues zu erzählen, habe ich trotzdem 'mal ein paar Erklärungen zum Stack und zu den "call"-, "ret"-, "push"- und "pop"-Anweisungen aufgeschrieben. Es kann ja nicht schaden ... ;-).
Den Rest Deines Programms kommentiere ich später.
mare_crisium
P.S.: Ich hab's bisher noch nicht hingekriegt, die Zeilennummern anzeigen zu lassen. Ich habe mich bisher immer mit der kleinen Anzeige unten rechts beholfen, die die Zeilennummer beim Cursor angibt.
Liste der Anhänge anzeigen (Anzahl: 1)
Guten Abend, robo_wolf,
genauso geht mir's auch: Ich will eben gern wissen, was der MC so treibt ;-). Dazu kommt, dass das Assemblerprogrammieren umso zügiger voran, je mehr sorgfältig ausgetestete Module man sich im Laufe der Zeit aufbaut. Das grösste Programm, das ich so geschrieben habe ist 13kB gross. Der Quelltext (alle Module zusammengerechnet und inkl. Kommentare) ist 13000 Zeilen lang.
Wegen des Quarzes? Na, wenn schon dann "the full monty"! Nimm ruhig die 16MHz, die Dein 8515 laut Datenblatt maximal vertägt. Meine ATmegas auf dem STK500 laufen auch so.
Ciao,
mare_crisium
P.S.: Zu Erleichterung der Parameterberechnung für den Timer hänge ich mal die Rechentabelle an, die ich mir dafür gebastelt habe. Nur die gelb unterlegten Felder sind für die Eingabe.
Liste der Anhänge anzeigen (Anzahl: 1)
robo_wolf,
hier die Kommentare zu Deinem Lernprogramm Nr. 4, diesmal in graphischer Form. Die graphische Darstellung ist manchmal ein gutes Mittel, um die Struktur des Programms zu durchschauen. Oft sieht man dann wieder den Wald und lässt sich von den vielen Bäumen nicht mehr so durcheinanderbringen ;-).
Es gibt verschiedene Normen bzgl. der Symbole, die für Abfrage, Anweisungen, Sprungmarken usw. verwendet werden sollten. Meine Darstellung ist "Freistil".
Ciao,
mare_crisium
Liste der Anhänge anzeigen (Anzahl: 1)
graphische Darstellung
Hallo mare_crisium,
habe mal eine graphische Darstellung gemacht, so wie ich es mir vorstelle.
Bitte verbessere mich, wenn Du Ungereimtheiten oder Fehler siehst.
Liste der Anhänge anzeigen (Anzahl: 1)
robo_wolf,
an Deiner Grafik kann man gut die Struktur erkennen, die Dir vorschwebt - dafür sind diese grafischen Darstellungen sehr praktisch.
Ich lese Dein Bild so:
Du hast vor, zwei von einander unabhängige Programmteile aufzubauen:
1. einer liest den Tastenzustand aus
2. der andere wertet den Tastenzustand aus und führt abhängig davon eine Aktion aus
(LEDs ein und aus).
Der 1. Teil, der vom Timerinterrupt angestossen wird, ist für die Entprellung zuständig. Dazu gibt's im Anhang noch etwas Ausführlicheres ;-) . Der 2. Teil reagiert auf die Tastenzustände, wie er sie vom 1. Teil "vorgekaut" vorfindet. Diese Einteilung gefällt mir sehr gut, weil sie voneinander unabhängige Aufgaben getrennten Programmteilen zuordnet.
Zum 1. Teil fallen mir noch folgende Gesichtspunkte ein: Jeder Taster hat die beiden Zustände “betätigt“/“nicht betätigt“. Welche Pegel dabei am Portpin anstehen, hängt von der Art des Tasters („Öffner“ oder „Schliesser“) und von der Beschaltung ab. Ein Öffner zwischen Portpin (mit aktiviertem pull-up-Widerstand)und Masse liefert im betätigten Zustand eine logische Eins, ein Schliesser eine logische Null. Um aller möglichen Verwirrung vorzubeugen, ist es gute Praxis, wenn der 1. Programmteil immer denselben logischen Zustand liefert, wenn der Taster betätigt wird, z.B. den Zustand „hoch“ (= logisch Eins), ganz unabhängig davon, ob das jetzt 5V oder 0V am Pin entspricht.
Wenn man's nicht so macht, muss man sich später im 2. Programmteil immer wieder daran erinnern, wie die Hardware aussieht. Als ich noch mit sehr umfangreichen, mit Relais aufgebauten Steuerungen (naja, das war in den 1980ern ;-) ) zu tun hatte, habe ich mich mir mit sowas mehr als einmal fast das Gehirn verstaucht.
Der erste Programmteil ist ein „Treiber“, der die Hardware kapselt. D.h. alle Programmteile, die die Tasterzustände verarbeiten, die der Treiber liefert, brauchen nichts über die Hardware zu wissen. Wird irgendwann mal ein Taster von Öffner auf Schliesser getauscht, dann braucht man nur den 1. Programmteil anzupassen. Alle anderen Teile können so bleiben, wie sie sind.
Im 2. Teil hast Du die möglichen Tastenzustände, die er vorfindet, in den drei hellbraunen Kästchen aufgezählt. Da fehlt aber einer, nämlich der „Taste losgelassen“. Es ist sehr wichtig, sich immer eine vollständige Liste aller möglichen Zustände aufzuschreiben, damit man ja nicht vergisst, für alle Zustände die Reaktion festzulegen. Und wenn man einen Zustand ausser Acht lassen will, dann ist ganz besonders wichtig, genau dafür die Begründung aufzuschreiben. Man glaubt gar nicht, wie schnell man die Gründe für das Weglassen vergessen hat. Liest man dann später das Programm, hält man das Weglassen erstmal für einen Fehler. Es kostet erfahrungsgemäss sehr viel Zeit, bis man schliesslich wieder dahinterkommt, dass man schon damals so ein schlaues Kerlchen gewesen war...
In Deinem Fall hast Du festgelegt, dass nichts passieren soll, wenn der Taster gedrückt gehalten wird oder wenn er dauernd nicht gedrückt ist. Nur, wenn der Taster gerade gedrückt worden ist, soll das LED umgeschaltet werden. Für das Loslassen hast Du keine Reaktion vorgesehen. Das führt dazu, dass die LED bei jedem zweiten Tastendruck wieder ausgeht. Es gibt also keine ein-eindeutige Zuordnung von Taster- und LED-Zustand. Aber - vielleicht wolltest Du's ja so haben :-) .
Noch ein Vorschlag: Lass' uns diese Version erstmal für eine einzige Taste zum Laufen bringen, danach kümmern wir uns um die Behandlung von mehreren Tasten. Da können wir dann man gucken, wie man vorgeht, wenn man mehr Variablen als Register verfügbar hat...
Ciao,
mare_crisium
Edit: Im Anhang den Fehler korrigiert, auf den robo_wolf in seinem Posting vom 01.02.2010 hinweist.
Liste der Anhänge anzeigen (Anzahl: 1)
robo_wolf,
jau, danke für den Hinweis auf den Fehler - ist schon korrigiert!
Es macht richtig Spass, mitzulesen, wie Du Dich in das Thema hineinwühlst :-) ! Am Besten gefällt mir Deine Schlussfolgerung:
Zitat:
Zitat von robo_wolf
Aber dieses Thema einmal komplett durchdacht und abgearbeiten - kann mann diesen Treiber immer wieder verwenden, in andere Projecte includen.
Zu genau demselben Schluss bin ich nach ein paar Monaten auch gekommen und bin damit seither gut gefahren. Ein Programm-Modul deckt bei mir jeweils eine Funktion komplett ab (im Sinne von Kapselung, wie man sie von objektorientiertem Programmieren her kennt). Z.B. steckt die Handhabung des TWI-Busses und meines eigenen TWI-Protokolls in einem Modul. Wenn ich Daten über den Bus versenden will, schreibe ich sie byteweise in die Ausgangs-FIFO, rufe im TWI-Modul die Prozedur TWI_SEND auf und weiter geht's. Über die ganze interne Abwicklung im Modul brauche ich nie mehr ( ;-) ) Gedanken zu machen.
Die Module werden einfach per "include"-Anweisung eingebunden. Z.B. sieht der "include"-Block in meinem 13kB-Programm so aus:
Code:
.include "ASTWI16_V01.asm" ; Modul zur TWI-Abwicklung
.include "ASTWIIF16_V04.asm" ; Interface zwischen Hauptprogramm und TWI-Modul, enthält die Dialogstruktur
.include "FIFOk16_V01.asm" ; Ein- und Ausgangs-Datenpuffer für TWI-Bus
.include "System16_V02.asm" ; div. Utilities, z.B. ASCII_to_hex u.ä.
.include "MathLibSF24_16_V07.asm" ; 32-Bit Fliesskomma Bibliothek
.include "M2KNavigation_16_V08.asm" ; Koppelnavigations-Algorithmus
.include "ADNS2610SPI16_V05.asm" ; SPI-Kommunikation mit dem Maussensor
.include "ASADNS2610IF16_V04.asm" ; Interface zwischen Hauptprogramm und Maussensoren
.include "ZstAutomat16_V04.asm" ; der Zustandsautomat zum Hochfahren und Überwachen der Maussensoren usw.
Dieses Vorgehen führt zu etwas erhöhtem Zeitbedarf, weil man sich bestimmte Vorschriften setzen und sie einhalten muss. Z.B. werden bei mir alle verwendeten Register mit "push"- und "pop"-Anweisungen gesichert, damit ich nicht jedesmal nachgucken muss, welche Register verändert werden. Trotzdem hat das Verfahren sich sehr bewährt, weil es mir erlaubt, mit Assembler fast wie in einer Hochsprache Programme zu schreiben. -
Der Vollständigkeit halber habe ich im Anhang noch die Geschichte mit dem Zustandsautomaten zuendegeführt. Bin gespannt auf Dein nächstes "Lernprogramm". Als Takt für den Timer-Interrupt schlage ich vor, zunächst mit so 2 bis 5 Hz anzufangen; da kann man das Flackern noch sehen. Wenn dann alles fehlerfrei läuft, kannst Du's immer noch schneller machen.
Ciao,
mare_crisium
@oberallgeier,
ja, für diese Anwendung könnte ein endlicher Zustandsautomat ein gangbarer Weg sein. Natürlich nur, wenn er in Assembler programmiert ist, nicht in cäh ;-) !
mare_crisium
Liste der Anhänge anzeigen (Anzahl: 1)
robo_wolf,
wenn sich einer zu schämen hat, dann wohl ich, weil ich's nicht gut genug erklärt habe :oops: . Wenn's Dir nichts ausmacht, dann lass' uns die Sache in kleineren Schritten angehen (siehe Anhang).
Ciao,
mare_crisium
Edit 1:
im .pdf haben sich folgende Fehler eingeschlichen:
1. "...mit der „ldi2-Anweisung setzen kann." Gemeint ist die "ldi"-Anweisung.
2. Im Programm-Ausschnitt "; LEDs von Tasten 1 und 2 steuern" müssen die Skip-Anweisungen nicht "sbrs r19,bFLANKE0" und "sbrs r19,bFLANKE1" sondern "sbrc r19,bFLANKE0" und "sbrc r19,bFLANKE1" heissen.