Forum: Mikrocontroller und Digitale Elektronik RC-Empfänger an Mikrocontroler v2


von Chrisopher R. (newguy)


Lesenswert?

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

von Hannes L. (hannes)


Lesenswert?

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

...

von Chrisopher R. (newguy)


Lesenswert?

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

von Michael (Gast)


Lesenswert?

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)

von Chrisopher R. (newguy)


Lesenswert?

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

von Andreas W. (geier99)


Lesenswert?

< 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

von Chrisopher R. (newguy)


Lesenswert?

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

von Andreas W. (geier99)


Lesenswert?

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

von Harry S. (littlegonzo)


Lesenswert?

Hallo,
ich würde jetzt erstmal aus einen nicht initialisierten Stack tippen...
Oder sehe ich es nur nicht?

Gruß
Littlegonzo

von Tim (Gast)


Lesenswert?

@Harry S.:
Der Punkt geht an dich. Stackpointer init fehlt.

@Chrisopher Rohe:
Bau nach "main:" mal folgendes ein:
1
         ; Stackpointer init:
2
         ldi temp, LOW(RAMEND)
3
         out SPL, temp
4
         ldi temp, HIGH(RAMEND)
5
         out SPH, temp

von Harry S. (littlegonzo)


Lesenswert?

Hey Supi,
zum Jahresende einen Punkt bekommen^^

von Chrisopher R. (newguy)


Lesenswert?

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

von MWS (Gast)


Lesenswert?

Timer0 per IN in ein Register laden und von da per STS in die Variable 
speichern.

von Andreas W. (geier99)


Lesenswert?

... wobei das "in" schon ausreichend ist, da ja "impulslaenge_ch1" schon 
ein Register ist.

von MWS (Gast)


Lesenswert?

> da ja "impulslaenge_ch1" schon ein Register ist

Yep, übersehen.

von Chrisopher R. (newguy)


Lesenswert?

aus
1
ldi impulslaenge_ch1, TCCR0

wurde jetzt
1
in impulslaenger_ch1, TCNT0

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
67
68
  reti
69
70
unter125:
71
72
  ldi temp, 0x00
73
  out PORTB, temp                  ;alle LEDs aus
74
75
  rjmp main
76
77
ueber125:
78
79
  ldi temp, 0xFF
80
  out PORTB, temp                  ;alle LEDs an
81
82
  rjmp main

von Tim (Gast)


Lesenswert?

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

von Chrisopher R. (newguy)


Lesenswert?

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?

von Tom M. (tomm) Benutzerseite


Lesenswert?

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.

von Tom M. (tomm) Benutzerseite


Lesenswert?

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)

von Chrisopher R. (newguy)


Lesenswert?

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
74
  push impulslaenge_ch1                ;impulslaenge_ch1 sichern
75
  
76
  ldi temp, 0
77
  out TCNT0, temp                    ;Timer zurücksetzen
78
79
  ret
80
81
unter125:
82
83
  ldi leds, 0xFF
84
  out PORTB, leds                    ;Alle LEDs aus
85
  ret
86
87
ueber125:
88
89
  ldi leds, 0x00
90
  out PORTB, leds                    ;Alles LEDs an
91
  ret

Danke mal wieder für alle Tips und Vorschläge

von Hannes L. (hannes)


Lesenswert?

Erkläre mir (und Dir selbst) bitte mal, wie das Programm diese Sequenz 
erreicht:
1
  pop impulslaenge_ch1                ;impulslaenge_ch1 zurückholen
2
3
  cpi impulslaenge_ch1, 125                    ;Timerwert vergleichen
4
     brlo is_lower                              ;Springe wenn < 125
5
     rcall ueber125                             ;Nicht kleiner, also grösser
6
     rjmp is_done                               ;Vergleich fertig
7
8
is_lower:
9
   rcall unter125                             ;Kleiner...
10
is_done:
11
                               ;Vergleich fertig.
12
  reti

Denn darüber steht ein bedingter und unbedingter Sprung:
1
  cpi temp, 0x03                    ;Teste MCUCR auf steigende Flange
2
  breq sflanke                    ;wenn steigende FLanke springe zu sflanke
3
  rcall fflanke                    ;sonst springe zu fflanke

und die folgende Sequenz hat kein Label als Sprungziel, ist also toter 
Code.

...

von Chrisopher R. (newguy)


Lesenswert?

1
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?

von MWS (Gast)


Lesenswert?

1
breq sflanke
2
...
3
sflanke:
4
...
5
ret

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.

von Chrisopher R. (newguy)


Lesenswert?

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

von Simon K. (simon) Benutzerseite


Lesenswert?

PC = Program Counter

von Hannes L. (hannes)


Lesenswert?

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

...

von MWS (Gast)


Lesenswert?

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.

von Marvin M. (Gast)


Lesenswert?

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

von Hannes L. (hannes)


Lesenswert?

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.

...

von Chrisopher R. (newguy)


Lesenswert?

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?

von spess53 (Gast)


Lesenswert?

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)

von Hannes L. (hannes)


Lesenswert?

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

...

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.