Nabend.
Ich hab vor zwei Tagen ein STK500 mit einem Atmega8515L-Muster bekommen.
Und genau so alt sind auch meine Programmier-Kentnisse. Habe mir als
erstes Projekt vorgenommen das Signal eines RC-Empfängers auswerten zu
lassen. Ich weiß dass es hier schon Code für sowas gibt. Allerdings
möchte ich das Programm in Assembler schreiben und auch selbst
erarbeiten. Ich habe jetzt ein erstes Programm zusammen geschrieben:
1
.include "m8515def.inc"
2
3
.def impulslaenge_ch1 = r16
4
.def temp = r17
5
6
.org 0x000
7
8
rjmp main ;Reset Handler
9
10
.org INT0addr
11
12
rjmp impuls_ch1 ;IRQ0 Handler
13
14
15
main:
16
17
ldi temp, 0xFF
18
out DDRB, temp ;PortB auf Ausgang
19
20
ldi temp, 0x00
21
out DDRD, temp ;PortD auf Eingang
22
23
ldi temp, (1<<ISC00) | (1<<ISC01)
24
out MCUCR, temp ;INT0 auf steigende Flanke
25
26
ldi temp, (1<<INT0)
27
out GICR, temp ;INT0 aktivieren
28
29
sei
30
31
cpi impulslaenge_ch1, 50 ;Timerwert vergleichen
32
brne unter200
33
34
35
36
37
unter200:
38
39
ldi temp, 0x00
40
out PORTB, temp ;alle LEDs aus
41
42
rjmp main
43
44
ueber200:
45
46
ldi temp, 0xFF
47
out PORTB, temp ;alle LEDs an
48
49
rjmp main
50
51
loop:
52
53
rjmp loop ;leere Warteschleife
54
55
56
impuls_ch1:
57
58
ldi temp, 0
59
out TCNT0, temp ;Timer Reset
60
61
ldi temp, (1<<CS00) | (0<<CS01) |(0<<CS02) ;Vorteiler auf 8
62
63
out TCCR0, temp ;Timer starten
64
65
ldi temp, (0<<ISC00) | (1<<ISC01) ;INT0 auf fallende Flanke
66
67
ldi temp, (0<<CS00)
68
out TCCR0, temp ;Timer stoppen
69
70
ldi impulslaenge_ch1, TCCR0 ;Wert von Timer0 speichern
71
72
reti
Dieses Programm soll folgendermaßen arbeiten:
Bei steigender Flanke des RC-Signals soll der Timer0 gestartet werden.
Sobald die Flanke abfällt wird dieser gestoppt. Anschließend wird der
Wert des Timers in impulslaenge_ch1 gespeichert welcher als
Vergleichswert dienen soll.
Ich kann mir gut vorstellen das es nicht schön geschrieben ist und
wahrscheinlich auch ziemlich durcheinander.
Leider funktioniert das Programm nicht. Die LEDs sind dauerhaft
eingeschaltet und ändern diesen Zustand auch nicht. Bevor ich jetzt
stundenlang nach einem vll simplen Fehler suche welchen ich als Anfänger
nicht finde dachte ich: Fragste hier mal um Hilfe.
Danke schonmal für eure Mühen
Gruß
Christopher
Naja, Servoimpulse auswerten ist für den Anfänger schon ein hartes Brot.
Schau doch einfach mal, wie es Andere gelöst haben. Einfach mal
versuchen, Codebeispiele Anderer zu analysieren und zu verstehen. Ein
paar uralte Beispiele von mir findest Du hier:
http://www.hanneslux.de/avr/mobau/index.html
...
Das es kein leichtes Projekt ist habe ich schon bemerkt. Aber an
einfachen Projekten lernt man weniger. Ist meine Meinung.
Ich hab mir die Programme auf deiner Seite schon zuvor ein mal
angesehen. Wirklich viel mit anfangen konnte ich damit nicht:
- Das Sendepult arbeitet anders herum. Außerdem will ich im Moment nur
ein einzelnes Signal auslesen und (noch) nicht sieben
- der Impulsdekoder generiert aus dem Summensignal die einzelnen Signale
der Kanäle. Damit kann ich auch (noch) nicht viel anfangen. Zumindest
finde ich keinen passenden Ansatz
- Die Regler-Programme sind ewig lang. Bis ich da nen Durchblick habe
kann ich mein Programm noch 3mal schreiben ;) Ob ich den Fehler dann
aber gefunden hab steht auf einem anderen Blatt.
Im Moment möchte ich nur einmal das Signal auslesen. Was damit im
Anschluss gemacht wird ist noch offen und wird den Schwierigkeitsgrad
erneut nach oben treiben. Das An- und Abschalten der LEDs sollte nur
eine erste Erfolgskontrolle sein und zum ermitteln der erreichten
Timer-Werte dienen. Für die Projekte in meinem Kopf ist das bei weitem
noch nicht ausreichend ;)
EDIT: Ich habe grad gesehen das ich "ueber200" gar nicht aufrufe. Der
Fehler könnte also dort liegen. Werde ein wenig experimentieren. Freue
mich aber trotzdem über helfende Vorschläge
Gibst du denn auch nur "ein" Signal raus? Bei einer normalen
Fernsteuerung werden doch alle Kanäle nacheinander ausgegeben; d.h. wenn
nur ein Signal länger ist, sieht es so aus als würden die LEDs dauerhaft
leuchten (was sie aber nicht tun)
Ich hab nochmal ein wenig am Queltext gedreht. Aber auch die aktuelle
Version funktioniert nicht:
1
.include "m8515def.inc"
2
3
.def impulslaenge_ch1 = r16
4
.def temp = r17
5
6
.org 0x000
7
8
rjmp main ;Reset Handler
9
10
.org INT0addr
11
12
rjmp impuls_ch1 ;IRQ0 Handler
13
14
main:
15
16
ldi temp, 0xFF
17
out DDRB, temp ;PortB auf Ausgang
18
19
ldi temp, 0x00
20
out DDRD, temp ;PortD auf Eingang
21
22
ldi temp, (1<<ISC00) | (1<<ISC01)
23
out MCUCR, temp ;INT0 auf steigende Flanke
24
25
ldi temp, (1<<INT0)
26
out GICR, temp ;INT0 aktivieren
27
28
sei ;Interrupts aktivieren
29
30
31
cpi impulslaenge_ch1, 125 ;Timerwert vergleichen
32
brne unter125
33
rjmp ueber125
34
35
36
loop:
37
38
rjmp loop ;leere Warteschleife
39
40
41
impuls_ch1:
42
43
ldi temp, 0
44
out TCNT0, temp ;Timer Reset
45
46
ldi temp, (1<<CS00) | (0<<CS01) |(0<<CS02) ;Vorteiler auf 8
47
48
out TCCR0, temp ;Timer starten
49
50
ldi temp, (0<<ISC00) | (1<<ISC01) ;INT0 auf fallende Flanke
51
52
ldi temp, (0<<CS00)
53
out TCCR0, temp ;Timer stoppen
54
55
cli ;Interrupts deaktivieren
56
57
ldi impulslaenge_ch1, TCCR0 ;Wert von Timer0 speichern
58
59
60
61
cpi impulslaenge_ch1, 125 ;Timerwert vergleichen
62
brne unter125 ;springe zu unter125
63
rjmp ueber125 ;springe zu ueber125
64
65
reti
66
67
unter125:
68
69
ldi temp, 0xFF
70
out PORTB, temp ;alle LEDs aus
71
72
rjmp main
73
74
ueber125:
75
76
ldi temp, 0x00
77
out PORTB, temp ;alle LEDs an
78
79
rjmp main
Hier noch eine Erläuterung wie ich Empfänger und SKT500 verbunden habe.
Nicht dass der Fehler hier liegt und ich mich am Programm die Finger
wund tipe:
Der Empfänger wird über ein externes Akkupack mit 6V versorgt. Das
SKT500 bekommt seinen Saft über ein externes Netzteil.
Der Masse-Pin des Servo-Anschlusses ist mit dem Masse-Pin (8) der
Steckleiste PORTD des SKT500 verbunden.
Der Signal-Pin des Servo-Anschlusses ist mit dem PD2-Pind der
Steckleiste PORTD des SKT500 verbunden.
Gruß
Christopher
< cpi impulslaenge_ch1, 125 ;Timerwert vergleichen
< brne unter125 ;springe zu unter125
< rjmp ueber125 ;springe zu ueber125
nur wenn der Timmerwert genau 125 ist wird die über125 angesprungen
sonst wird immer zu unter125 gesprungen (zb auch bei 130). Ist das so
gewollt?
Hmm, ausserdem wird die ISR nie mit reti verlassen. Du springst direkt
aus der ISR mit rjmp wieder zurück in die main
Gruss Andi
Das "brne" war tatsächlich falsch und wurde durch ein "brlt" ersetzt.
Auch hatte ich einen Denkfehler drin. Die Zeilen lauten jeztzt
1
cpi impulslaenge_ch1, 125 ;Timerwert vergleichen
2
brlt unter125 ;springe zu unter125
3
rjmp ueber125 ;springe zu ueber125
Dass ich die ISR nicht reti verlasse stimmt. Muss mir da noch was
einfallen lassen wie ich das ander lösen kann. Aber nicht mehr heute
Nacht.
Danke schonmal für die Hinweise. Morgen gehts weiter ;)
.... da es schon etwas spät ist, sehe ich gerade nicht alle Fehler :-)
aber hier ist noch einer:
< ldi impulslaenge_ch1, TCCR0 ;Wert von Timer0 speichern
hier lädst du immer einen Konstantenwert in deine Variable (und zwar die
Adresse vom TCCR0)
Habe noch ein bischen am Programm geschraubt aber es funktioniert noch
immer nicht. Habe grad erst den Post von Andreas W. gelesen
< ldi impulslaenge_ch1, TCCR0 ;Wert von Timer0 speichern
hier lädst du immer einen Konstantenwert in deine Variable (und zwar die
Adresse vom TCCR0)
Mit welchem Code kann ich denn den Wert eines Timers in eine Variable
speichern. Das dürfte ja ein grundlegender Fehler sein ;)
Gruß
Christopher
denn TCNT ist doch das Register in dem der Timer hochzählt und nicht
TCCR oder?
Und trotzdem funktioniert das Programm nicht.
Ich habe "unter125" und "ueber125" mal so geändert das in beiden Fällen
die LEDs anspringen müssten. Doch das passiert nicht. Also scheint das
Programm erst gar nicht in diese Bereiche des Programms zu springen.
Hier die aktuellste Version:
1
.include "m8515def.inc"
2
3
4
.def temp = r17
5
.def impulslaenge_ch1 = r18
6
7
.org 0x000
8
9
rjmp main ;Reset Handler
10
11
.org INT0addr
12
13
rjmp impuls_ch1 ;IRQ0 Handler
14
15
main:
16
cli
17
18
ldi temp, LOW(RAMEND)
19
out SPL, temp
20
ldi temp, HIGH(RAMEND)
21
out SPH, temp ; Stackpointer init:
22
23
ldi temp, 0xFF
24
out DDRB, temp ;PortB auf Ausgang
25
26
ldi temp, 0x00
27
out DDRD, temp ;PortD auf Eingang
28
29
30
ldi temp, (1<<INT0)
31
out GICR, temp ;INT0 aktivieren
32
33
34
ldi temp, (1<<ISC00) | (1<<ISC01)
35
out MCUCR, temp ;INT0 auf steigende Flanke
36
37
cpi impulslaenge_ch1, 125 ;Timerwert vergleichen
38
brlt ueber125 ;springe zu ueber125
39
rjmp unter125 ;springe zu unter125
40
41
42
sei ;Interrupts aktivieren
43
44
45
loop:
46
47
rjmp loop ;leere Warteschleife
48
49
50
impuls_ch1:
51
52
ldi temp, 0
53
out TCNT0, temp ;Timer Reset
54
55
ldi temp, (1<<CS00) | (0<<CS01) |(0<<CS02) ;Vorteiler auf 8
56
57
out TCCR0, temp ;Timer starten
58
59
ldi temp, (0<<ISC00) | (1<<ISC01) ;INT0 auf fallende Flanke
60
61
ldi temp, (0<<CS00)
62
out TCCR0, temp ;Timer stoppen
63
64
cli ;Interrupts deaktivieren
65
66
in impulslaenge_ch1, TCNT0 ;Wert von Timer0 speichern
(ASM Kenntnisse hervorkram...)
>denn TCNT ist doch das Register in dem der Timer hochzählt und nicht>TCCR oder?
Ohne in die .pdf zu gucken (mit / ohne 0,1,2): Ja.
>Und trotzdem funktioniert das Programm nicht.
Glaube ich.
Also wenn ich den Programmablauf richtig verstehe macht das Ding
folgendes:
1. Stack / Ports Init
2. INT0 auf steigende Flanke einstellen
3. noch nie gesetztes Register impulslaenge_ch1 mit 125 vergleichen
4a. nach unter125 springen, Leds aus und zurück zu Punkt 1
4b. nach ueber125 springen, Leds an und zurück zu Punkt 1
Also je nach (zufälligem) wert im r18 gehen die leds nach dem reset
an oder aus. Mehr passiert nicht.
Für deinen Vergleich:
1
cpi impulslaenge_ch1, 125 ;Timerwert vergleichen
2
brlo is_lower ; Springe wenn < 125
3
rcall ueber125 ; Nicht kleiner, also grösser
4
rjmp is_done ; Vergleich fertig
5
is_lower:
6
rcall unter125 ; Kleiner...
7
is_done:
8
; Vergleich fertig.
So kannst du (r)call verwenden und in ueber125 || unter125 per
ret zurückspringen.
Der AVR sichert bei einem Interrupt bzw (r)call NUR den PC.
Das Status Register sreg musst du selbst sichern und wiederherstellen:
1
impuls_ch1:
2
push temp ; Ein Register Freimachen
3
in temp, sreg ; Status Register hohlen
4
push temp ; Status Register sichern
5
6
; bla bal
7
; hier kannst du mit temp machen was du willst
8
9
pop temp ; Status Register zurückhohlen
10
out sreg, temp ; Status Register setzen
11
pop temp ; temp widerherstellen
12
reti ; ISR ende
Jetzt darf dein Interrupt auch zwischen einem Vergleich (cp/cpi/...)
und dessen Auswertung (br??/...) kommen. Ohne sichern vom sreg
würde br?? auf grund der änderungen am sreg durch die ISR springen....
Den cli innerhalb von einer ISR ist überflüssig. Die Interrupts
werden beim anspringen automatisch abgeschaltet (deswegen ret*I*).
Da dein Programm etwas wirr ist folgender Vorschlag zum Ablauf:
1. Stack / Ports init, Timer starten, INT0 auf steigende Flanke, sei
2. loop: rjmp loop
In impuls_ch1:
0. Sreg & benötigte Register sichern
1. TCNT0 hohlen und merken, TCNT0 zurücksetzen
2. Festellen ob IRQ wegen Steigender (A) oder Fallende Flanke (B)
(MCUCR)
3A. (0->1) auf fallende Flanke einstellen und rjmp 5.
3B. (1->0) auf steigende Flanke einstellen
4. gemerkten wert von TCNT0 auswerten, leds an/aus
5. schritt 0 rückgängig, reti
(Nur so als Vorschlag....)
puh. Ne Menge Input aber ich denke das krieg ich durchgearbeitet.
Wenn ich in C programmiere war es doch so das der uC nach dem includen
und definieren mit void (main) beginnt. Auch wenn vorher noch ein paar
funktionen definiert werden. Ist es in assembler ähnlich oder arbeitet
der uC hier strikt von oben nach unten sofern keine Intterrupts und
sprünge dazwischen funken?
Chrisopher Rohe schrieb:
> Wenn ich in C programmiere war es doch so das der uC nach dem includen> und definieren mit void (main) beginnt. Auch wenn vorher noch ein paar> funktionen definiert werden.
So sieht es zumindest aus... ;-)
Der Einstiegspunkt wird durch den Reset-Vektor definiert. Irgendwo in
der Compiler-Linker Kette wird dann der Vektor so eingerichtet, dass er
auf "main" zeigt. Wird wohl auch beim ASM-Programmieren nicht anders
sein, du definierst auch eine Marke "main", die dann nach irgendeiner
Konvention als Einstiegspunkt gilt.
Wie das Einrichten der Reset-Vektors genau funktioniert, hab ich mir
(noch) nicht angesehen.
Chrisopher Rohe schrieb:
> .org 0x000> rjmp main ;Reset Handler
...
> main:> cli
Ich seh' gerade, da steckt keine Magie drin. Du hast den Reset-Vektor ja
explizit definiert und beginnst dann gleich mit "cli" und der
Stack-Initialisierung. Deine Frage hättest du also auch selbst
beantworten können. 8)
Und schon wieder eine neue Variante meines Programms. Doch noch immer
funktioniert es nicht. Habe versucht alle Vorschläge von Tim einzubauen.
Lediglich "5. schritt 0 rückgängig, reti" ist mir nicht ganz klar. Hier
der aktuellste Code:
1
.include "m8515def.inc"
2
3
.def impulslaenge_ch1 = r16
4
.def temp = r17
5
.def leds = r18
6
7
.org 0x00
8
9
rjmp main ;Reset Handler
10
11
.org INT0addr
12
13
rjmp impuls_ch1 ;IRQ0 Handler
14
15
main:
16
17
ldi temp, LOW(RAMEND)
18
out SPL, temp
19
ldi temp, HIGH(RAMEND)
20
out SPH, temp ;Stackpointer init:
21
22
ldi temp, 0xFF
23
out DDRB, temp ;PortB auf Ausgang
24
25
ldi temp, 0x00
26
out DDRD, temp ;PortD auf Eingang
27
28
ldi temp, (1<<ISC00) | (1<<ISC01)
29
out MCUCR, temp ;INT0 auf seigende Flange einstellen
30
31
sei ;Interrupts aktivieren
32
33
loop:
34
35
rjmp loop ;leere Warteschleife
36
37
impuls_ch1:
38
39
in temp, MCUCR ;MCUCR auslesen um Triggerflanke zu bestimmen
40
cpi temp, 0x03 ;Teste MCUCR auf steigende Flange
41
breq sflanke ;wenn steigende FLanke springe zu sflanke
42
rcall fflanke ;sonst springe zu fflanke
43
44
pop impulslaenge_ch1 ;impulslaenge_ch1 zurückholen
45
46
cpi impulslaenge_ch1, 125 ;Timerwert vergleichen
47
brlo is_lower ;Springe wenn < 125
48
rcall ueber125 ;Nicht kleiner, also grösser
49
rjmp is_done ;Vergleich fertig
50
51
is_lower:
52
rcall unter125 ;Kleiner...
53
is_done:
54
;Vergleich fertig.
55
reti
56
57
58
sflanke:
59
60
ldi temp, (0<<CS00) | (1<<CS01) | (0<<CS02) ;Prescaler auf 8
61
out TCCR0, temp ;Timer starten
62
63
ldi temp, (0<<CS00) | (1<<CS01)
64
out MCUCR, temp ;INT0 auf fallende Flanke einstellen
65
66
ret
67
68
fflanke:
69
70
ldi temp, (0<<CS00) | (0<<CS01) | (0<<CS02)
71
out TCCR0, temp ;Timer stoppen
72
73
in impulslaenge_ch1, TCNT0 ;Timer auslesen und Wert in impulslaenge_ch1 schreiben
Erkläre mir (und Dir selbst) bitte mal, wie das Programm diese Sequenz
2
erreicht:
3
[code]
4
pop impulslaenge_ch1 ;impulslaenge_ch1 zurückholen
5
6
cpi impulslaenge_ch1, 125 ;Timerwert vergleichen
7
brlo is_lower ;Springe wenn < 125
8
rcall ueber125 ;Nicht kleiner, also grösser
9
rjmp is_done ;Vergleich fertig
10
11
is_lower:
12
rcall unter125 ;Kleiner...
13
is_done:
14
;Vergleich fertig.
15
reti
Ich hatte es im Tutorial so verstanden das ein "ret" am Ende einer
Sequenz zu der gesprungen wurde dafür sorgt, dass der uC wieder zur
Sprungstelle zurück kehrt und dort mit dem Code weiter macht.
der bestimmte Sprung ist doch nötig damit der Vergleich ausgwertet wird.
In diesem Falle wenn das Z-Flag gesetzt ist also der aus MCUCR Wert
gleich 0x03 (INT0 auf steigende Flanke eingestellt)
der unbestimmte Sprung ist quasi der "else"-sprung. Das hab ich so aus
dem Tutorial abgeleitet (Siehe Vergleiche)
btw: Kann mir jemand den Unterschied zwischen rcall und rjmp erklären?
Sicher ? :D
Ret ohne Call ? Ein Breq ist kein Call, beim Ret werden 2 Bytes vom
Stack in den PC gepoppt (mit ungeklärtem Ziel) und tschüss.
Relative Call und Relative Jump, beim Call wird der PC auf den Stack
gepusht, beim Jump nicht. Bei Atmel gibt's das komplette Instruction Set
als Pdf.
Sicher? Auf keinen Fall. Ich schreibe erst seid 4 Tagen Programme für
meinen 8515.
Und was ist der PC? Wohl nicht mein Personal Computer :D
Das mit dem Stack muss ich mir noch ein paar mal durchlesen. Davon hab
ich bisher am wenigsten verstanden. Und es scheint ja durchaus ein
wichtiges Kapitel zu sein XD
> Bei Atmel gibt's das komplette Instruction Set als Pdf.
Und auch in der mit F1 erreichbaren Online-Hilfe des AVR-Studios...
Ein Branch (BRxx) ist ein bedingter Sprung über kurzr Distanz mit
relativer Angabe der Distanz.
Ein RJMP ist ein unbedingter Sprung über mittlere Distanz mit relativer
Angabe der Distanz.
Ein JMP ist ein unbedingter Sprung großer Distanz mit absoluter Angabe
der Zieladresse. Den gibt es beim AVR erst ab 16KB Flash.
Ein IJMP ist ein indizierter Sprung, dessen Zieladresse um Z-Pointer
steht. Man kann also die Zieladresse vorher anhand einiger Bedingungen
(z.B. Menüpunktnummer) berechnen.
Ein Skip (SBRS, SBRC, SBIS, SBIC) ist ein Sprung über den nächsten
Befehl drüberweg, der von einer Bitprüfung in einem Register oder
I/O-Register abhängig ist und keine Flags im SREG beeinflusst.
Alle diese Sprungbefehle (und auch CPSE) sichern sich nicht die
Rücksprungadresse auf dem Stack und ermöglichen somit keinen Rücksprung
per RET / RETI.
Und dann gibt es noch die Sprungbefehle, die den aktuellen
Programmcounter auf Stack legen, damit per RET (RETI bei INT-Aufrufen)
an die alte Stelle (also hinter den Sprungbefehl) zurückgesprungen
werden kann. Dies wären:
RCALL
CALL
ICALL
Interrupt-Aufruf per Hardware
Einzelheiten gibt's in der Onlinehilfe zum AVR-Studio sowie im
AVR-Instruction-set, eine gut formatierte Zusammenfassung gibt es am
Ende des Datenblatts des jeweiligen AVRs.
...
Ein Breq ist ein bedingter Rjmp. Beim Call dagegen sichert der µC die
Rücksprungadresse auf dem Stack, er schiebt dort den PC (program
counter) als 2 Bytes rauf, beim Ret holt er sich die 2 Bytes wieder und
lädt sie in den PC zurück. Der Stack arbeitet nach dem LIFO Prinzip,
last in first out.
Da aber ein Breq nix auf den Stack sichert, ist auch keine gültige
Rücksprungadresse auf dem Stack, sondern Irgendetwas.
Speziell in Deinem Code, da diese Routine in einer ISR steht, besteht
dieses Irgendetwas zufälligerweise aus der Rücksprungadresse der ISR.
Die würdest Du eigentlich erst beim Reti wieder anspringen wollen.
Nun hat das Ret gegenüber dem Reti einen entscheidenden Unterschied: Das
Reti setzt wieder das global interrupt flag im Statusregister des µC,
welches beim Start der ISR automatisch gelöscht wurde. Das Ret dagegen
setzt es nicht mehr.
Mal abgesehen davon, daß Du an der falschen Stelle die ISR verlässt,
sind zusätzlich ab diesem Zeitpunkt keine weiteren Interrupts mehr
möglich, eben wegen gesperrten Interrupt Flag.
Moin,
und bedenke, dass Du mit push und pop den gleichen Stack benutzt, in den
auch die Rücksprungadressen für Unterprogramme und Interrupts
geschrieben werden.
Wenn Du µC schonmal in C programmiert hast, wieso machst Du den Schritt
zu Assembler zurück?
Tipp: Back erstmal kleinere Brötchen und lass mal eine LED blinken (ohne
Interrupts), dann langsam steigern. Zum Ausprobieren würde ich alles
komplett erstmal ohne Interrupts machen, bis das Gefühl für Stack, calls
und Nutzung von Speicher/Registern da ist.
Wenns ohne Interrupt dann funktioniert, kann man es immer noch
verschlimmbessern ;)
Chrisopher Rohe schrieb u.A.:
> Sicher? Auf keinen Fall. Ich schreibe erst seid 4 Tagen Programme für> meinen 8515.
Dann wäre es vermutlich doch hilfriech, Dir die Befehlsliste (Datenblatt
des AVRs) auszudrucken und damit ASM-Quelltexte anderer Leute zu
analysieren.
...
Marvin M. schrieb:
> Wenn Du µC schonmal in C programmiert hast, wieso machst Du den Schritt> zu Assembler zurück?
Das letzte Mal das ich einen µC programmiert habe war im Rahmen eines
Roboterwettbewerbes. Damals hatte ich lediglich 3 Wochen Zeit und der µC
war das Hirn eines Asuros. Also schon gut vorbereitet. Und die
programmierten Funktionen waren wesentlich einfacher.
Jetzt möchte ich aber richtig in das Thema einsteigen. Daher wollte ich
bei Null anfangen. Und in dem Tutorial hier steht das man mit Assembler
anfangen sollte um die Arbeitsweise besser verstehen zu können.
Hannes Lux schrieb:
> Dann wäre es vermutlich doch hilfriech, Dir die Befehlsliste (Datenblatt> des AVRs) auszudrucken und damit ASM-Quelltexte anderer Leute zu> analysieren.
Das Problem, das ich dabei sehe, ist, dass jeder seinen eigenen "Stil"
hat. Und es ist auch was anderes ein funktionierendes Programm zu
"lesen". Dass man es dann auch "verstanden" hat sehe ich nicht immer als
gegeben. Zumindest bei mir ist es so. Ich bin eher der Praktiker der was
macht und dann fragt warum es nicht funktioniert. Wie man m Verlaufe
dieses Threads vielleicht schon erkennen kann
Marvin M. schrieb:
> Tipp: Back erstmal kleinere Brötchen und lass mal eine LED blinken (ohne> Interrupts), dann langsam steigern.
Das wird wohl der Tipp dieses Threads. Ich hatte es mir ehrlich
einfacher vorgestellt. Die Geschichte mit dem Stack ist echt ne Nummer
für sich. Auch wenn meine Kenntnisse im C-Programmieren auch eher gegen
Null laufen kommt es mir so vor als wäre Assembler schwieriger.
Was würdet ihr einem Anfänger raten? C oder Assembler?
Hi
>> Dann wäre es vermutlich doch hilfriech, Dir die Befehlsliste (Datenblatt>> des AVRs) auszudrucken und damit ASM-Quelltexte anderer Leute zu>> analysieren.>Das Problem, das ich dabei sehe, ist, dass jeder seinen eigenen "Stil">hat. Und es ist auch was anderes ein funktionierendes Programm zu>"lesen".
Das ist wirklich manchmal ein Problem. Fehler in eigenen finde ich meist
auch wesentlich schneller, als in fremden Programmen.
>Was würdet ihr einem Anfänger raten? C oder Assembler?
Kommt darauf, wer antwortet. Aber auch für C-Programmierer ist es
manchmal hilfreich den vom Compiler erzeugten Assemblercode zu
verstehen.
MfG Spess (notorischer Assemblerprgrammierer)
> Die Geschichte mit dem Stack ist echt ne Nummer für sich.
Überhaupt nicht...
Der Stack ist ein Stück RAM (SRAM), das einen eigenen Zeiger (den
Stackpointer) hat, der beim AVR nach unten (zur kleineren Adresse hin)
wächst, wenn man etwas auf den Stack legt. Daher initialisiert man den
Stackpointer (SPL, SPH) zweckmäßigerweise auf die letzte verfügbare
SRAM-Adresse.
Es gibt nun Befehle, die etwas auf den Stack legen ("parken") und
welche, die etwas vom Stack herunternehmen. Sie aktualisieren dabei den
Stackpointer per Hardware, da musst Du Dich also nur bei der
Initialisierung drum kümmern. Wichtig ist allerdings, dass sich das
Drauflegen und Herunternehmen die Waage halten, da es sonst zu einem
Stacküberlauf kommt, der zum Absturz des AVRs führt.
Folgende Befehle nutzen den Stack:
PUSH (legt ein Byte auf den Stack)
POP (holt ein Byte vom Stack)
CALL, RCALL, ICALL (legt die Rücksprung-Adresse auf den Stack und
springt)
RET (nimmt die Rücksprungadresse vom Stack in den ProgrammCounter)
Int-Aufruf (legt die Rücksprungadresse auf den Stack und springt zur
entsprechenden Adresse in der Interrupt-Sprungtabelle, in der dann Dein
(R)JMP zur ISR (Interrupt-Service-Routine) steht, löscht dabei das
I-Flag im SREG)
RETI (holt die Rücksprungadresse vom Stack in den ProgrammCounter und
setzt das I-Flag im SREG)
Damit der als Stack benutzte Teil des SRAMs nicht mit den anderen im
SRAM gehaltenen Variablen kollidiert, legt man die normalen Variablen an
den Anfang des SRAMs, während der Stack am Ende beginnt.
Hochsprachen brauchen noch einen separaten Stack zur Übergabe und
Rückgabe von Parametern zu Funktionen. Dieser wird dann meist mit dem
X-Pointer realisiert.
Charakteristisch für einen Stack (Stapelspeicher, Kellerspeicher, Lifo)
ist die Tatsache, dass man immer nur das zuletzt hineingelegte Byte
herausholen kann. Bei einem Fifo (Ringpuffer) ist das umgedreht.
...