Forum: Mikrocontroller und Digitale Elektronik ext. Interrupt, ASM Problem


von Bruno M. (brumay)


Lesenswert?

Hallo,
Ich versuche mit einer Fernbedienung den externen Interrupt zu testen 
und sehe offensichtlich wieder mal den Wald vor lauter Bäume nicht.
Ich bekomme zwar die UART Ausgabe, aber die bits toggeln nicht!!!
1
;********************************************************************************
2
;*
3
;*    Test des externen Interrupt und Ausgabe per UART
4
;*
5
;********************************************************************************
6
.include "m8def.inc"
7
 
8
.def temp    = r16                           ; Hilfsregister
9
.def temp1    = r17
10
.def IR_bit    = r18              ; Toggelbitregister
11
.def IO_    = r19
12
.def IR_tst    = r21
13
14
.equ F_CPU    = 8000000                       ; Systemtakt in Hz
15
.equ BAUD    = 9600              ; Baudrate
16
 
17
.equ UBRR_VAL   = ((F_CPU+BAUD*8)/(BAUD*16)-1)  ; clever runden
18
.equ BAUD_REAL  = (F_CPU/(16*(UBRR_VAL+1)))     ; Reale Baudrate
19
.equ BAUD_ERROR = ((BAUD_REAL*1000)/BAUD-1000)  ; Fehler in Promille
20
 
21
.if ((BAUD_ERROR>10) || (BAUD_ERROR<-10))       ; max. +/-10 Promille Fehler
22
;.error "Systematischer Fehler der Baudrate grösser 1 Prozent und damit zu hoch!"
23
.endif
24
25
.dseg
26
IR_Wert:  .BYTE  50
27
28
.cseg 
29
.org 0x000
30
    rjmp  start                ; Reset Handler
31
.org INT0addr
32
    rjmp  int0_handler            ; IRQ0 Handler
33
34
int0_handler:
35
  rjmp  IRbit
36
    reti
37
 
38
;*******************************************************************************
39
start:
40
  ldi     temp, LOW(RAMEND)
41
  out     SPL, temp
42
  ldi     temp, HIGH(RAMEND)
43
  out     SPH, temp
44
   
45
  ldi    temp, (1<<ISC00)          ; INT0 auf Zustandswechsel konfigurieren
46
  out    MCUCR, temp
47
  ldi    temp, (1<<INT0)            ; INT0 aktivieren
48
  out    GICR, temp
49
  
50
  ldi    temp,LOW(UBRR_VAL)          ;Teilerformel
51
  out    UBRRL, temp
52
  ldi    temp,HIGH(UBRR_VAL)
53
  out    UBRRH, temp
54
55
  sbi    UCSRB, RXEN              ;Empfänger einschalten
56
  sbi    UCSRB, TXEN              ;Sender einschalten  
57
  ldi    temp, (1<<URSEL)|(1<<UCSZ1)|(1<<UCSZ0)
58
  out    UCSRC, temp
59
  in    temp, UDR              ;Empfänger leeren
60
  
61
  clr    IR_bit
62
  ldi    IR_tst, 1
63
64
;****************************************************************************
65
;Hauptschleife
66
IR:
67
  clr    temp
68
  sei                      ;Interrupts allgemein aktivieren
69
70
IR_Loop:                    ;auf nächsten Interrupt warten
71
  tst    IR_tst                ;bei 1 wurde Ausgaberoutine bereits durchlaufen
72
  breq  _Ausgabe
73
  rjmp  IR_Loop
74
75
;**********************************************************************************
76
;Routine für externen Interrupt
77
;toggeln von IR_bit bei jedem Interrupt
78
IRbit:
79
  push  temp             
80
    in    temp, SREG       
81
82
  inc    IR_bit                ;toggeln
83
  andi  IR_bit, 1
84
  
85
   clr    IR_tst                ;neue Ausgabe anstoßen
86
  out    SREG, temp        
87
    pop    temp              
88
    reti
89
90
;******************************************************************************************
91
;UART Ausgabe
92
_Ausgabe:
93
  mov    IO_, IR_bit              ;IR_bit freigeben
94
  sbrc  IO_, 0        
95
  rjmp  Wert1
96
  ldi    temp, '0'
97
  rcall  Ausgabe
98
  rjmp  weiter
99
100
Wert1:
101
  ldi    temp, '1'
102
  rcall  Ausgabe
103
104
weiter:
105
  ldi    temp, ' '
106
  rcall  Ausgabe
107
  inc    IR_tst
108
  rjmp  IR_Loop
109
110
Ausgabe:
111
  sbis  UCSRA, UDRE              ;überspringe wenn Sender frei
112
  rjmp  Ausgabe
113
  out    UDR, temp
114
  ret

kann jemand helfen?

Gruß Bruno

von Bitflüsterer (Gast)


Lesenswert?

Es werden hier sicher einige Leser bereit sein, den Code zu lesen, zu 
analysieren, was er macht; zu überlegen was er machen soll und auf 
welche Weise und Dir zu sagen inwiefern beides voneinander abweicht.
Wenn Du darauf Wert legst, dann ignoriere meinen Beitrag.

Ich bin eher für Hilfe zur Selbsthilfe. Das ist aber meistens nicht 
erwünscht, deswegen biete ich es hier erstmal nur an, ohne weiter Kritik 
zu üben oder Hinweise zu geben, wie Du das Problem selbst lösen kannst. 
Das ist ganz klar der aufwendigere Weg für Dich. Vielleicht auch 
manchmal nicht ganz angenehm für das Selbstbewußtsein.
Falls Du das doch möchtest, teile uns (mir) das hier bitte mit.

von Bruno M. (brumay)


Lesenswert?

Bitflüsterer schrieb:

> Ich bin eher für Hilfe zur Selbsthilfe.

Ich versuche eigentlich immer den Dingen selbst auf den Grund zu gehen, 
aber manchmal ist man einfach blind, wenn man immer nur im eigenen Saft 
schmort.

Also, jede Art von Hilfe ist willkommen!

von Karl H. (kbuchegg)


Lesenswert?

Lad den Code in den Simulator
Setze einen Breakpoint an den Anfang des Interrupt Handlers, also hier
1
int0_handler:
2
  rjmp  IRbit
und steppe den Code erst mal soweit durch, bis du in der Hauptschleife 
angelangt bist. Dann klickst du in der I/O View (keine Ahnung wie das im 
neueren Atmel Studio heißt, ich verwende lieber das alte AVR Studio) den 
Pin an, an dem der Interrupt hängt. Der Breakpoint müsste ansprechen 
(gegebenenfalls 'Single Step' drücken) und von dort kannst du dann mit 
weiteren Single Steps verfolgen was passiert.

: Bearbeitet durch User
von Bruno M. (brumay)


Lesenswert?

Das habe ich schon x mal gemacht. Es passiert genau das was beabsichtigt 
ist, aber leider funktioniert es in der Realität nicht.

von Karl H. (kbuchegg)


Lesenswert?

Bruno M. schrieb:

>   ldi     temp, LOW(RAMEND)
>   out     SPL, temp
>   ldi     temp, HIGH(RAMEND)
>   out     SPH, temp

Beim Lesen von 16 Bit "Registern" (wie zb dem Ergebnis vom ADC): erst 
Low, dann High
Beim Schreiben genau umgekehrt: erst High, dann Low

von Karl H. (kbuchegg)


Lesenswert?

Bruno M. schrieb:
> Das habe ich schon x mal gemacht. Es passiert genau das was beabsichtigt
> ist, aber leider funktioniert es in der Realität nicht.


Wenn deine Ausgabe anspringt, dann wird die ISR auch angesprungen.
Was noch sein könnte: Achte mal darauf, ob dein Prozessor resettet.

von Bitflüsterer (Gast)


Lesenswert?

Bruno M. schrieb:
> Bitflüsterer schrieb:
>
>> Ich bin eher für Hilfe zur Selbsthilfe.
>
> Ich versuche eigentlich immer den Dingen selbst auf den Grund zu gehen,
> aber manchmal ist man einfach blind, wenn man immer nur im eigenen Saft
> schmort.
>
> Also, jede Art von Hilfe ist willkommen!

Gut.
Betriebsblind sind wir alle mal. Nicht weiter schlimm. Aber wie gehen 
wir damit um?

Ich versuche es mal: Dieser Teilsatz "... zu analysieren, was er macht; 
zu überlegen was er machen soll und auf welche Weise ..." sollte Dir 
Anregungen geben.


Aber ich will das noch etwas detaillierter ausführen:

Ein wesentlicher Punkt bei der Softwareentwicklung ist eine genau 
Beschreibung der Umgebung. Was für ein Puls kommt am Interrupt-Eingang 
an? Von welcher Quelle (Schaltkreis, Taster etc.)? In welchem Rythmus? 
Schaltung? Wie testest Du?

Ein weiterer wesentlicher Punkt ist die Beschreibung der Funktion einer 
SW im voraus - ein Entwurf. Als Text, Pseudocode, Diagramm etc.

Wenn man ein Feature (z.B. Interrupts von externen Quellen) erst wenig 
benutzt hat, lohnt sich ein schrittweiser Aufbau, der SW. Wenn man an 
einen toten Punkt kommt, dann ein Rückbau. Z.B. könntest Du ersteinmal 
einfach einen Interrupt schreiben, der eine LED toggelt. Nur um zu 
sehen, ob das überhaupt wie gewünscht geht.

Ein weiterer Punkt sind Variablen- und Funktionsnamen (resp. Labels). 
Idealerweise ersparen gut formulierte Variablen- und Funktionsnamen den 
Kommentar. In Deinem Fall sind z.B. "IR_bit", "IO_" und "IR_tst" 
schlecht gewählt. Ich weiss nicht und kann nur raten, was genau diese 
Bits anzeigen oder bewirken. Ähnlich geht es mir mit dem Funktionsnamen 
"weiter".
Funktionsnamen enthalten am besten ein Verb und mindestens das Subjekt; 
das Objekt würde auch noch helfen. Ein Variablenname sind am besten so 
formuliert, dass sie eine Zustand beschreibe. Z.B. "XXXIntIstBehandelt" 
oder "ADCIstInitialisiert" oder "EingangsDatenVorhanden" oder ähnliches.

Eine ganz wesentliche Tätigkeit bei der Software-Entwicklung ist das 
"debuggen". Im eigentlichen Sinn, der Vergleich, von dem was geschehen 
soll (was man aus dem Entwurf abliest) und dem was tatsächlich 
geschieht. Praktisch stößt wird man dabei mit Beschränkungen 
konfrontiert. Etwa wenn man kein Oszilloskop hat, oder keinen Debugger. 
Aber man kann das umgehen, indem man etwa eine oder mehrere LEDs 
anschliesst oder den UART zu Ausgabe benutzt.
Ein anderes Problem ist, dass man unter Umständen garnicht sicher ist, 
dass die UART-Ausgabe funktioniert oder die LED-Ausgabe. Dann kommt der 
oben erwähnte stufenweise Aufbau (bzw. der Rückbau) zum Tragen. Ich kann 
das noch näher ausführen, falls Du es möchtest.

Ich hoffe das hilft Dir weiter. Mein Ansatz ist sehr analytisch. Auch 
nicht jedermanns Fall. Falls es nicht Deiner ist, ignoriere diesen 
Beitrag.

Gruß
Bitflüsterer


P.S. In der Zwischenzeit hat Karl Heinz geantwortet und Du.

Meinem Ansatz folgende ergeben sich folgende Fragen: Was genau soll 
geschehen? (Siehe Entwurf). Es ist oftmals ein Problem, dass man meint, 
der Code würde genau das realisieren, was man im Kopf entworfen hat, es 
aber garnicht der Fall ist. Wenn man den Entwurf auf Papier hat, dann 
ist es einfacher, die Tücke zu umschiffen, dass man gedanklich 
Voraussetzungen macht, die man nicht mehr hinterfragt. Im Ergebnis hat 
man tausendmal über die fehlerhafte Stelle geguckt ohne zu merken, dass 
sie es ist.

Setzt man aber voraus, dass Dein Code perfekt ist dann bleibt noch die 
Frage ob es die Schaltung nicht ist. Das wiederrum führt zu dem Punkt 
des Rückbaus. Geht die serielle Kommunikation? Ändert sich die Ausgabe, 
wenn Du im Code das auslösende Ereignis "hart" herbeiführst? Gehen die 
Interrupts? Löst das physische Ereignis auch eine Änderung des Ablaufs 
der SW aus?

von adenin (Gast)


Lesenswert?

Karl Heinz schrieb:
> Bruno M. schrieb:
>
>>   ldi     temp, LOW(RAMEND)
>>   out     SPL, temp
>>   ldi     temp, HIGH(RAMEND)
>>   out     SPH, temp
>
> Beim Lesen von 16 Bit "Registern" (wie zb dem Ergebnis vom ADC): erst
> Low, dann High
> Beim Schreiben genau umgekehrt: erst High, dann Low

Die Regel muss man nicht für den Stackpionter beachten, sie gilt nur für 
Registerpaare, die mit dem Shadowregister arbeiten.

von Bruno M. (brumay)


Lesenswert?

Danke für den Hinweis. Am Problem hat das aber nichts geändert.

Z. B. eine typische UART Ausgabe:

1 0 1 0 1 1 0 0 0 1 1 0 1 0 1 1 1 0 0 0 1 1 0 0 1 1 1 1 1 1 1 0 1 0

von adenin (Gast)


Lesenswert?

Bruno M. schrieb:
> Danke für den Hinweis. Am Problem hat das aber nichts geändert.
>
> Z. B. eine typische UART Ausgabe:
>
> 1 0 1 0 1 1 0 0 0 1 1 0 1 0 1 1 1 0 0 0 1 1 0 0 1 1 1 1 1 1 1 0 1 0


Bruno M. schrieb:
> Ich bekomme zwar die UART Ausgabe, aber die bits toggeln nicht!!!

Toggled doch, wo ist das Problem?

von uwe (Gast)


Lesenswert?

Mit welcher Frequenz arbeitet den die Fernbedienung!? Was soll die 
Software denn machen wenn die Fernbedienung scheller als der UART ist!?

von Bruno M. (brumay)


Lesenswert?

adenin schrieb:

> Toggled doch, wo ist das Problem?

Da ich bei jedem Interrupt den Wert toggle, stimmen nur die ersten 5, 
aber der 6. nicht mehr.

Zusätzlich ist mir jetzt noch aufgefallen, daß das Muster immer gleich 
ist!

von c-hater (Gast)


Lesenswert?

Bruno M. schrieb:

> Das habe ich schon x mal gemacht. Es passiert genau das was beabsichtigt
> ist

Nicht wirklich.

> aber leider funktioniert es in der Realität nicht.

Es funktioniert auch in der Simulation nicht. Jedenfalls dann nicht, 
wenn man denn INT0-Pin hinreichend häufig toggelt.

Das Ganze ist ein sehr schönes Beispiel dafür, wie man eine 
Synchronisierung zwischen ISR und main KEINESFALLS bauen sollte. So, wie 
der Code dort steht, stellt er eigentlich nur eins sicher:

Daß irgendwann mal entweder '0' oder '1' über die UART ausgegeben wird, 
wenn der externe Interrupt mindestens einmal ausgelöst wurde.

von Bruno M. (brumay)


Lesenswert?

uwe schrieb:
> Mit welcher Frequenz arbeitet den die Fernbedienung!? Was soll die
> Software denn machen wenn die Fernbedienung scheller als der UART ist!?

Ich vermute, das ist die Lösung des Rätsels. Fernbedienung ca 38 kHz, 
UART 9600 Baud.

Danke für den Hinweis!

von Bruno M. (brumay)


Lesenswert?

c-hater schrieb:

> Das Ganze ist ein sehr schönes Beispiel dafür, wie man eine
> Synchronisierung zwischen ISR und main KEINESFALLS bauen sollte.

Etwas mehr Aufklärung wäre sehr hilfreich und willkommen.

von Karl H. (kbuchegg)


Lesenswert?

Was, genau, hängt eigentlich an deinem Interrupt Pin?

Woran machst du fest, dass aus der Fernsteuerung genau 1 Puls beim 
einmaligen Drücken einer Taste rauskommt?

Weiteres Problem in der Software: dir können Zeichen verloren gehen, 
weil du einfach ohne Ansehen des Arbeitsstatuses der UART Zeichen ins 
UDR schreibst.
Kommen die Interrupts schneller als die UART ausliefern kann, dann 
verlierst du Zeichen.

Mach dir halt mal in die ISR eine Error-LED rein, die dann eingeschaltet 
wird, wenn ein ISR AUfruf erfolgt, noch ehe die Abarbeitung des 
vorhergehenden Interrupts beendet ist (erkennbar am Inhalt von IR_tst. 
Ist IR_tst auf 0, wenn die ISR angesprungen wird, dann ist die UART 
Sache noch nicht durchgelaufen.

: Bearbeitet durch User
von Bitflüsterer (Gast)


Lesenswert?

Karl Heinz schrieb:
> Was, genau, hängt eigentlich an deinem Interrupt Pin?
>
> Woran machst du fest, dass aus der Fernsteuerung genau 1 Puls beim
> einmaligen Drücken einer Taste rauskommt?

@ Bruno
Ein kleiner Bezug auf meinen Beitrag oben als Beleg, dass solche 
Vorgehensweise Vorteile haben kann:

> Ein wesentlicher Punkt bei der Softwareentwicklung ist eine genau
> Beschreibung der Umgebung. Was für ein Puls kommt am Interrupt-Eingang
> an? Von welcher Quelle (Schaltkreis, Taster etc.)? In welchem Rythmus?
> Schaltung? Wie testest Du?

Gruß
Bitflüsterer

von Bruno M. (brumay)


Lesenswert?

An meinem Pin hängt ein IR-Empfänger.

Natürlich kommen bei einmal drücken erheblich mehr Impulse. Jeder Impuls 
löst aber einen Interrupt aus. Lt. UART Ausgabe wären es 34. Ich weiß 
jetzt allerdings nicht mehr, was die Ausgabe Routine überhaupt macht, 
wenn sie von den Interrupts überholt wird.

von Bruno M. (brumay)


Lesenswert?

Ich muß mich korrigieren:

>Fernbedienung ca 38 kHz,
>UART 9600 Baud.

Das stimmt so nicht! 38 kHz ist die Trägerfrequenz der FB. Die Ausgabe 
des Empfängers ist wesentlich langsamer.

Ich sehe schon, ich muß das alles noch mal überdenken.

von c-hater (Gast)


Lesenswert?

Bruno M. schrieb:

> 38 kHz ist die Trägerfrequenz der FB. Die Ausgabe
> des Empfängers ist wesentlich langsamer.

Zumindest zeitweise kommt ganz offensichtlich der 38kHz-Träger durch.

Das war, was ich dir mit dem Hinweis sagen wollte, daß es auch in der 
Simulation nicht klappt, wenn man nur häufig genug am Int-Pin "wackelt".

> Ich sehe schon, ich muß das alles noch mal überdenken.

Besser ist das.

von Bruno M. (brumay)


Lesenswert?

Nachtrag für alle die an diesem Thread Interesse hatten:

Nach Änderung der Baudrate auf 38400 sieht die UART Ausgabe so aus:

1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 
1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0

ein Beweis für die Vermutung, daß die Ausgabe den Interrupts nicht 
folgen konnte.

von c-hater (Gast)


Lesenswert?

Bruno M. schrieb:

> Nachtrag für alle die an diesem Thread Interesse hatten:
>
> Nach Änderung der Baudrate auf 38400 sieht die UART Ausgabe so aus:
>
> 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
> 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0 1 0
>
> ein Beweis für die Vermutung, daß die Ausgabe den Interrupts nicht
> folgen konnte.

Genau. Und wenn du korrekt synchronisiert hättest, wäre das schon bei 
der Synchronisation aufgefallen. Dann hätte nämlich die ISR auf main 
warten müssen, was sie nicht tun kann (weil sie dann bis in alle 
Ewigkeit warten würde).

Was sie aber tun kann, ist, die Fehlersituation (Echtzeitbedingung nicht 
gegeben) zu erkennen und dann eine sinnvolle Fehlerbehandlung zu 
starten. Bei der Testanwendung wäre das natürlich eine entsprechende 
Ausgabe auf das Debug-Terminal gewesen.

Und schon hätte der Controller selber dir gesagt, wo die Säge klemmt...

Dein Programm krankt, je nach Betrachtungsweise, an drei Sachen oder an 
einer:

Variante A)

1) Der eigentliche Meßwert wird an der falsche Stelle gewonnen, nämlich 
in main. Er sollte aber in der ISR gewonenn werden, denn dort ist 
(zeitlich) die erste Chance, den aktuellen Pegelstatus nach einem durch 
Interrupt signalisierten Pegelwechsel zu gewinnen. Ja, das ist auch 
nicht perfekt, weil auch hier immer noch hochfrequentes "Prellen" stören 
könnte, aber allemal besser, als erst viele Takte später in main 
nachzuschauen.

2) Die Synchronisation schützt wegen der falschen Stelle der Gewinnung 
nicht den Meßwert, sondern bestenfalls sich selber, hier also das 
Faktum, daß ein Interrupt aufgetreten ist.

3) Die Synchronisation ist nur eine halbe Synchronisation. Es fehlt 
effektiv die Rückmeldung "Datum verarbeitet", bzw. eigentlich: deren 
Prüfung. Damit ist also nur noch die Information über das erstmalige 
Auftreten eines Interrupts wirklich geschützt.

Variante B)

Die Aufgabenteilung zwischen ISR und main ist hier grundsätzlich 
unsinnig, bringt keinen Vorteil und macht die Sache nur unnötig langsam 
und kompliziert. Die Mitarbeit von main wird hier nämlich überhaupt 
nicht benötigt. Eine Synchronisation allerdings sehr wohl weiterhin 
nötig, auch wenn die Sache an main völlig vorbeigeht. Sie ließe sich nur 
viel effizienter implementieren...

von Bruno M. (brumay)


Lesenswert?

Hallo c-hater,

danke für den umfangreichen Kommentar.
Einiges ist mir dabei allerdings nicht klar!

> Genau. Und wenn du korrekt synchronisiert hättest, wäre das schon bei
> der Synchronisation aufgefallen. Dann hätte nämlich die ISR auf main
> warten müssen, was sie nicht tun kann (weil sie dann bis in alle
> Ewigkeit warten würde).

das wäre auch nicht im Sinne des Erfinders gewesen, da ich ja kein 
Interruptsignal auslassen darf. Dabei ist mir allerdings nicht ganz 
klar, was du mit main bezeichnest, ich nehme aber an die Loop Schleife.

> Was sie aber tun kann, ist, die Fehlersituation (Echtzeitbedingung nicht
> gegeben) zu erkennen und dann eine sinnvolle Fehlerbehandlung zu
> starten. Bei der Testanwendung wäre das natürlich eine entsprechende
> Ausgabe auf das Debug-Terminal gewesen.

Das ist sicherlich richtig! Da ich aber ein anderes Programm mit 
gleichen Einstellungen laufen habe (von microcontroller.net) habe ich 
überhaupt nicht damit gerechnet, daß es an dieser Stelle haken könnte. 
Die Arbeitsweise dieses Programms ist allerdings ganz anders. Im Moment 
weiß ich auch nicht, wie diese Fehlerbehandlung zu realisieren ist.

> Variante A)
>
> 1) Der eigentliche Meßwert wird an der falsche Stelle gewonnen, nämlich
> in main. Er sollte aber in der ISR gewonenn werden, denn dort ist
> (zeitlich) die erste Chance, den aktuellen Pegelstatus nach einem durch
> Interrupt signalisierten Pegelwechsel zu gewinnen. Ja, das ist auch
> nicht perfekt, weil auch hier immer noch hochfrequentes "Prellen" stören
> könnte, aber allemal besser, als erst viele Takte später in main
> nachzuschauen.

Da ich wie gesagt nicht sicher bin was du mit main bezeichnest, kann ich 
nur vermuten.
Sicherlich hätte ich den Inhalt von IRbit: sofort in der ISR erledigen 
können, die Aufteilung ist hier sicherlich Unsinn.

> 2) Die Synchronisation schützt wegen der falschen Stelle der Gewinnung
> nicht den Meßwert, sondern bestenfalls sich selber, hier also das
> Faktum, daß ein Interrupt aufgetreten ist.

Verstehe ich nicht.

> 3) Die Synchronisation ist nur eine halbe Synchronisation. Es fehlt
> effektiv die Rückmeldung "Datum verarbeitet", bzw. eigentlich: deren
> Prüfung. Damit ist also nur noch die Information über das erstmalige
> Auftreten eines Interrupts wirklich geschützt.

Auch das verstehe ich nicht. Für mich hat, wie gesagt die Erfassung 
aller Interrupts erste Priorität.

> Variante B)
>
> Die Aufgabenteilung zwischen ISR und main ist hier *grundsätzlich*
> unsinnig, bringt keinen Vorteil und macht die Sache nur unnötig langsam
> und kompliziert. Die Mitarbeit von main wird hier nämlich überhaupt
> nicht benötigt. Eine Synchronisation allerdings sehr wohl weiterhin
> nötig, auch wenn die Sache an main völlig vorbeigeht. Sie ließe sich nur
> viel effizienter implementieren...

????

Gruß Bruno

von c-hater (Gast)


Lesenswert?

Bruno M. schrieb:

> ????

OK, heute und morgen habe ich keine Zeit dafür, das ausführlich zu 
beantworten, aber am WE werde ich was dazu schreiben, was allgemein die 
Probleme der Synchronisation zwischen ISRs und zwischen ISRs und main 
erklärt. Guck' einfach Sonntagabend nochmal in diesen Thread rein.

Aber soviel vorab: Mit "main" meine ich das, was auch ohne jeden 
Interrupt geht, also die Code-Ebene, die nach dem Reset eines µC 
standardmäßig abläuft.

Die (oft genug völlig unpassende) Bezeichnung "main" stammt daher, daß 
ich mich leider viel zu oft auch mit Scheißdreck wie C abgeben muß und 
dort wird dieser Codebereich halt üblicherweise so genannt, weil's der 
verfickte Macroassembler, den die C-Typen für eine Programmiersprache 
halten, halt vor einem halben Menschenleben irgendwann mal so vorgekaut 
hat und alle wahren Gläubigen das bis heute exakt so nachbeten.

Sogar ich... Aber es ist wirklich echt verrückt, da gebe ich dir absolut 
Recht.

von Bastler (Gast)


Lesenswert?

Hallo "einzig wahrer Gläubiger" aka c-hater. Bei dem Sch..ssdreck würde 
halt der GCC die Drecksarbeit machen und man könnte sich auf's Problem 
konzentrieren. Ich kann übrigens diverse Kisten in ASM dirigieren und 
weiß deshalb das die "Drecksarbeiter" ihre Arbeit fast immer gut genug 
machen. Und wenn nicht, kann man ja nachhelfen, muß sich aber nie fragen 
ob SP -= 2; erst low oder erst High Byte braucht.
Aber schon lustig zuzusehen/mitzulesen ;-)

von Bruno M. (brumay)


Lesenswert?

Inzwischen habe ich ich eine Fehlerroutine eingebaut.

Da ich aber nicht nur die Interrupts erfassen will, sondern auch die 
dazwischliegenden Zeiten, funktioniert das so nicht mehr. Ich werde die 
Werte daher im Flash zwischenspeichern und dann erst ausgeben.

von c-hater (Gast)


Lesenswert?

c-hater schrieb:

> OK, heute und morgen habe ich keine Zeit dafür, das ausführlich zu
> beantworten, aber am WE werde ich was dazu schreiben, was allgemein die
> Probleme der Synchronisation zwischen ISRs und zwischen ISRs und main
> erklärt. Guck' einfach Sonntagabend nochmal in diesen Thread rein.

Also hier die Lieferung:

Bruno M. schrieb:

> das wäre auch nicht im Sinne des Erfinders gewesen, da ich ja kein
> Interruptsignal auslassen darf.

Das geht nur, wenn die Datensenke mindestens so schnell ist wie die 
Datenquelle. Man kann allerdings u.U. ein wenig tricksen, um kurzzeitige 
Bursts verarbeiten zu können, bei denen diese Bedingung nicht über die 
gesamte Laufzeit gegeben ist, sondern nur im Mittel über einen 
bestimmten Zeitabschnitt.

> Auch das verstehe ich nicht. Für mich hat, wie gesagt die Erfassung
> aller Interrupts erste Priorität.

Das ist ein grober Denkfehler. Was nützt ein erfaßter Interrupt, der 
nicht verarbeitet werden konnte? Genauso viel wie ein nicht erfaßter 
Interrupt, nämlich garnix. Der einzige Unterschied ist, daß die 
Information an einer anderen Stelle der Verarbeitungskette verloren 
geht, weg ist sie aber so und so gleichermaßen. Genau das hast du ja 
ganz praktisch und hautnah selber erlebt!

>> Die Aufgabenteilung zwischen ISR und main ist hier *grundsätzlich*
>> unsinnig, bringt keinen Vorteil und macht die Sache nur unnötig langsam
>> und kompliziert. Die Mitarbeit von main wird hier nämlich überhaupt
>> nicht benötigt. Eine Synchronisation allerdings sehr wohl weiterhin
>> nötig, auch wenn die Sache an main völlig vorbeigeht. Sie ließe sich nur
>> viel effizienter implementieren...
>
> ????

Na, das sollte doch klar sein. Stelle dir einfach die Frage, was main 
hier eigentlich tut (ich bleibe einfach mal bei diesem oft unpassenden 
Ausdruck). Es holt ein (möglicherweise bereits veraltetes) Datum, 
checkt, ob eine Ausgabe aktuell möglich ist, wartet ggf. bis das der 
Fall ist und macht dann die Ausgabe. Bis auf das Warten kann das alles 
auch die ISR selber erledigen.




Anmerkung: Bei allen folgenden Beispielcodes vereinfache ich im Detail 
etwas, um das Wesentliche darin deutlicher hervortreten zu lassen. Weder 
Retten/Wiederherstellen von Registern und Flags ist dort enthalten noch 
wird darauf Rücksicht genommen, ob eine IO-Adresse eventuell memory 
mapped ist und damit nicht per in/out ansprechbar oder ob sie eventuell 
nicht für sbic/sbis/sbi/cbi erreichbar ist. Auch werden alle Daten in 
Registern gehalten, nicht wie normalerweise meist nötig/sinnvoll im RAM. 
Realer Code müsste das natürlich alles korrekt bzw. sinnvoll 
implementieren.

isr_ext:
  ldi tmp,1              ;status togglen
  eor status,tmp

  sbis UCSRA,UDRE        ;ausgabekanal prüfen
  rjmp fail_realtime     ;echtzeitfehler, ausgabekanal noch belegt

  mov tmp,status         ;ausgabezeichen berechnen
  subi tmp,-'0'
  out UDR,tmp            ;und ausgeben
  reti

ext_fail_realtime:
  sbis UCSRA,UDRE        ;ausgabekanal prüfen
  rjmp ext_fail_realtime ;warten, bis sich daran etwas ändert

  ldi tmp,'f'            ;und ausgeben
  out UDR,tmp
  reti

Damit hast du schonmal die Synchronisation mit main komplett eingespart, 
kriegst mit Sicherheit entweder korrekte Daten oder aber eine 
Fehlermeldung, wenn der Interrupt häufiger auftritt, als du die Daten 
wegschreiben kannst.
Diese Lösung hat aber noch weiteres Verbesserungspotential. Zum einen 
blockiert die ISR hier nämlich im Fehlerfall bis zu dessen Bereinigung 
das gesamte System und zum anderen sorgt sie auch nicht dafür, daß der 
Schaden, der aus der Fehlersituation resultiert, möglichst gering 
bleibt.

An diesen beiden Sachen kann man noch arbeiten:

ext_fail_realtime:
  cbi GICR,INT0          ;den eigenen interupt verbieten
  sei                    ;Interrupts global erlauben

ext_fail_poll:
  sbic GIFR,INTF0        ;interruptflag nunmehr pollen
  rjmp ext_fail_noint    ;noch keiner wieder aufgetreten

  sbi GIFR,INTF0         ;interruptflag zurücksetzen
  eor status,tmp         ;status korrekt nachführen

ext_fail_noint:
  sbis UCSRA,UDRE        ;ausgabekanal prüfen
  rjmp fail_poll         ;warten, bis sich daran etwas ändert

  ldi tmp,'f'            ;fehlermeldung ausgeben
  out UDR,tmp

  cli                    ;alle interrupts kurzzeitig sperren
  sbi GICR,INT0          ;eigenen interrupt wieder freigeben
  reti                   ;rückkehr zur normalsituation

Hiermit ist nun (soweit das überhaupt möglich ist) sichergestellt, daß 
während des Bestehens der Fehlersituation der Rest des Systems 
weiterlaufen kann (soweit er in ISRs implementiert ist und daß die 
Variable mit der Nutzinformation auch während der Fehlersituation 
korrekt weiter behandelt wird, so daß ab Ende der Fehlersituation wieder 
korrekte Daten kommen, ansonsten würden hier nämlich mit 50%iger 
Wahrscheinlichkeit "invertierte" Daten geliefert. Zusätzlich ist 
sichergestellt, daß ein zu häufiger ISR-Aufruf nicht in der Katastrophe 
des Stacküberlaufs endet, deswegen sperrt die ISR den eigenen Interrupt 
für die nichtexklusive Phase der Fehlerbehandlung.

Übrigens: Es wäre im konkreten Fall genausogut möglich, auf den 
Interrupt ganz zu verzichten, und alles in main abzuhandeln, genauso, 
wie es hier nur im Fehlerfall passiert, per Polling der beiden 
Interruptflags. Das wäre im konkreten Fall sogar die deutlich 
effizientere Lösung. Aber es soll hier ja um Interrupts und deren 
Synchronisation gehen.



Soweit erstmal zu deinem konkreten Fall, jetzt die Sache etwas 
allgemeiner. Als erstes: Synchronisation funktioniert in einem 
Interruptsystem völlig anders als in einem üblichen Multitasking-System, 
die dort gebrauchten Designpattern sind für ein Interruptsystem fast 
vollständig unbrauchbar, denn sie basieren größtenteils auf Warten. 
Genau das ist aber für eine normale ISR gerade nicht möglich, denn 
während sie exklusiv läuft, läuft keinerlei andere Software, die 
irgendwas an dem Systemstatus ändern könnte.
Was in Multitasking-Systemen nur gelegentlich beim Zugriff auf multiple 
geteilte Resourcen bei gleichzeitig inkorrekter Programmierung passiert, 
der gefürchtete Deadlock, wäre in Interruptsystemen also der absolute 
Normalfall. Nur wenn ein Ereignis ohne Mitarbeit eines anderen 
Codepfades eintreten kann (also ein reines Hardwareereignis ist), oder 
in der ISR die Interrupts wieder freigegeben werden, führt Warten in 
einer ISR nicht zwingend zum Deadlock. In der verbesserten ISR oben 
werden gleich beide Sachverhalte benutzt. Das kann sinnvoll sein, muß es 
aber nicht unbedingt.

So weit, so schlecht. Wie synchronisiert man nun, wenn konkurrierender 
Code unmöglich warten kann? Die einzige Möglichkeit dafür ist, zu 
verhindern, daß konkurrierender Code überhaupt laufen kann, während man 
auf eine geteilte Resource zugreift. Das ist einfach im exklusiven Teil 
einer ISR, denn hier ist das von vornherein sichergestellt. An allen 
anderen Stellen hilft nur, die Zugriffsroutine auf die geteilte Resource 
in einen cli/sei Block zu verpacken. Oder in Kurzfassung:

Der einzige verfügbare Synchronisierungsmechanismus in einem 
Interruptsystem ist das Verbieten der Interrupts.

Das hört sich irgendwie witzig an, ist aber die pure Wahrheit.

Neben diesem wesentlichen Unterschied zwischen "normalem" Multitasking 
und Interruptprogrammierung gibt es aber auch viele Gemeinsamkeiten. 
Z.B. ist es auch bei dem Designpattern normalen Multitaskings, welches 
der Interruptsperre bei Interruptprogrammierung am nächsten kommt, den 
sog. "critical sections" wichtig, diese immer möglichst kurz zu halten. 
Auch steckt in beiden Fällen der Schlüssel für ein effizentes Programm 
darin, die Abhängigkeiten zwischen den Tasks möglichst zu minimieren. 
Genau das war oben auch die allererste Optimierung: Bei genauerer 
Betrachtung des Problems war festzustellen, daß es mit nur einem Task 
lösbar ist. Besser kann man die Abhängigkeiten garnicht verringern als 
dadurch, sie gleich ganz eleminieren.

Um nun zu zeigen, wie man synchronisiert, führen ich mal eine weitere 
Forderung an dein Programm ein: Es soll zusätzlich zu der in der isr_ext 
realisierten Funktionalität auch von anderer Stelle irgendwelche 
Ausgaben auf die UART gemacht werden können, konkret: die Ausgabe eines 
Timerticks in Form eines 'T'. Aus der Anzahl der Ts zwischen den Nullen 
und Einsen der isr_ext könnte man dann also die Dauer des jeweiligen 
status der ISR ermitteln, das wäre doch ein nettes Feature, nicht wahr?

Also die ISR bleibt wie oben gezeigt, und Timer0 läuft in einer 
sinnvollen Betriebsart mit einer bezüglich der Anwendung sinnvollen 
Überlaufrate. Dann könnte die Timer-ISR so aussehen:

isr_tov0:
  sbis UCSRA,UDRE         ;ausgabekanal prüfen
  rjmp tov0_fail_realtime ;echtzeitfehler, ausgabekanal noch belegt

  ldi tmp,'T'             ;ansonsten 'T'
  out UDR,tmp             ;ausgeben
  reti

tov_fail_realtime:
  cbi TIMSK,TOIE0         ;den eigenen interupt verbieten
  sei                     ;Interrupts global erlauben

tov_fail_poll:
  sbis UCSRA,UDRE         ;ausgabekanal prüfen
  rjmp fail_poll          ;warten, bis sich daran etwas ändert

  ldi tmp,'f'             ;fehlermeldung ausgeben
  out UDR,tmp

  cli                     ;alle interrupts kurzzeitig sperren
  sbi TIMSK,TOIE0         ;eigenen interrupt wieder freigeben
  reti                    ;rückkehr zur normalsituation

Auch hier wird die Fehlersituation geprüft, der Ausgabekanal ist ja der 
gleiche, und in eine Fehlerbehandlungsroutine gesprungen, aber bevor wir 
uns mit der näher befassen, noch ein Hinweis: auch in dieser ISR wird 
des mit "tmp" benannte Register verwendet. Geht das einfach so? Was den 
exklusiven Teil der beiden ISRs betrifft: Ja, das geht so, denn sie 
sperren sich gegenseitig und die Kohärenz des Inhalts von "tmp" muß nur 
innerhalb des jeweiligen exklusiven Blocks gewährleistet sein. Es geht 
aber nicht, soweit es die Fehlerbehandlung betrifft, denn die läuft 
nicht exklusiv. Zumindest insofern wären also die ext_fail_realtime 
schonmal umzubauen.

Umgebaut werden müssen beide *_fail_realtime aber auch noch aus einem 
anderen Grund. Beide greifen nämlich auf nichtatomare Art auf die UART 
zu. Zuerst wird gefragt, ob sie frei ist und dann wird bei positivem 
Ergebnis etwas darauf ausgegeben. Nun kann aber jede der beiden ISRs die 
andere zu jeder Zeit unterbrechen, wenn diese nicht mehr exklusiv läuft, 
was sie ja in der Fehlerbehandlung nicht mehr tut. Erfolgt diese 
Unterbrechung nun genau zwischen der Frage und der Ausgabe, passiert 
Scheiße. (Könnte passieren, wenn's die UART-Hardware nicht abfangen 
würde, aber wir tun wir hier mal so, als täte sie es nicht). Dann ergibt 
sich die Notwendigkeit der Synchronisation dieser ZUgriffe.

Da bei beiden ISRs im Fehlerfall bezüglich der Ausgabe exakt das gleiche 
passiert, würde man diesen Teil vielleicht sinnvollerweise in eine 
gemeinsam genutzte Subroutine auslagern (oder besser ein entsprechendes 
Macro), die dann etwa so aussähen könnte:

uartout_sync:
  cli
  sbis UCSRA,UDRE
  rjmp uart_free
  sei
  ret
uart_free:
  ldi tmp,'f'
  out UDR,tmp
  ret

Auch hier wird wieder das tmp-Register verwendet, aber wieder nur in 
einem exklusiven Codeblock. Geht also. Genutzt würde die Routine dann so 
aus den beiden Fehlerbehandlungen heraus:

tov_fail_realtime:
  sei                     ;Interrupts global erlauben
  cbi TIMSK,TOIE0         ;den eigenen interupt verbieten

tov_fail_poll:
  rcall uartout_sync
  brie tov_fail_poll

  sbi TIMSK,TOIE0         ;eigenen interrupt wieder freigeben
  reti                    ;rückkehr zur normalsituation



ext_fail_realtime:
  cbi GICR,INT0           ;den eigenen interupt verbieten
  sei                     ;Interrupts global erlauben
  push tmp2               ;zusätzliches Hilfsregister
  ldi tmp2,1              ;retten und initialisieren

ext_fail_poll:
  sbic GIFR,INTF0         ;interruptflag nunmehr pollen
  rjmp ext_fail_noint     ;noch keiner wieder aufgetreten

  sbi GIFR,INTF0          ;interruptflag zurücksetzen
  eor status,tmp2         ;status korrekt nachführen

ext_fail_noint:
  rcall uartout_sync
  brie ext_fail_poll

  pop tmp2                ;hilfsregister wiederherstellen
  sbi GICR,INT0           ;eigenen interrupt wieder freigeben
  reti                    ;rückkehr zur normalsituation


Damit ist auch diese Aufgabe gelöst, ohne das main irgendwas dazu tun 
muß.

von Bruno M. (brumay)


Lesenswert?

Herzlichen Dank für den umfangreichen Beitrag. Du kannst dir sicherlich 
vorstellen, daß ich das erst noch verarbeiten muß. Sollten dabei Fragen 
auftauchen, melde ich mich noch mal.

> Das geht nur, wenn die Datensenke mindestens so schnell ist wie die
> Datenquelle.

Da ich meine oberste Priorität "keinen Interrupt auslassen" so nicht 
erfüllen kann, habe ich das Prinzip geändert und speichere ein 
komplettes Infrarot Signal im SRAM und gebe es erst dann komplett aus.

von Bruno M. (brumay)


Lesenswert?

So, hier bin ich wieder.

>> Auch das verstehe ich nicht. Für mich hat, wie gesagt die Erfassung
>> aller Interrupts erste Priorität.

> Das ist ein grober Denkfehler.

Das ist kein Denkfehler, da beim Lesen eines IR-Codes das Ergebnis 
sinnlos wird, wenn zwischendrin was fehlt.

>>> Die Aufgabenteilung zwischen ISR und main ist hier *grundsätzlich*
>>> unsinnig, bringt keinen Vorteil und macht die Sache nur unnötig langsam
>>> und kompliziert.

Ich hatte diese Lösung aus zwei Gründen gewählt. Erstens habe ich mal 
gelernt, daß man die ISR möglichst schnell wieder verlassen sollte um 
den Interrupt nicht zu blockieren und zweitens weil der von mir hier 
eingestellte Code nur ein Ausschnitt ist. Das Original ist 
umfangreicher, da auch noch die Zeitabstände zwischen den Interrupts 
gemessen und ausgegeben werden.

>   sbis UCSRA,UDRE        ;ausgabekanal prüfen
>   rjmp fail_realtime     ;echtzeitfehler, ausgabekanal noch belegt

das sollte sicherlich rjmp ext_fail_realtime heißen.

>   mov tmp,status         ;ausgabezeichen berechnen
>   subi tmp,-'0'
>   out UDR,tmp            ;und ausgeben

einfach und elegant gelöst, da fragt man sich warum man selbst nicht 
darauf kommt.
>
> ext_fail_realtime:
>   sbis UCSRA,UDRE        ;ausgabekanal prüfen
>   rjmp ext_fail_realtime ;warten, bis sich daran etwas ändert
>
>   ldi tmp,'f'            ;und ausgeben
>   out UDR,tmp
>   reti

auch das werde ich mir sicherlich merken.

Zum Rest komme ich erst morgen.

von Marc V. (Firma: Vescomp) (logarithmus)


Lesenswert?

Bruno M. schrieb:
> einfach und elegant gelöst, da fragt man sich warum man selbst nicht
> darauf kommt.

 Solange es sich nur um Zahlen 0-9 handelt...

Bruno M. schrieb:
>> ext_fail_realtime:
>>   sbis UCSRA,UDRE        ;ausgabekanal prüfen
>>   rjmp ext_fail_realtime ;warten, bis sich daran etwas ändert
>>
>>   ldi tmp,'f'            ;und ausgeben
>>   out UDR,tmp
>>   reti
>
> auch das werde ich mir sicherlich merken.

 Als sichere Möglichkeit, ewig in ISR zu bleiben ?

: Bearbeitet durch User
von Bruno M. (brumay)


Lesenswert?

Der nächste Teil hat mir schon erheblich größere Kopfschmerzen bereitet. 
Einige Dinge verstehe ich auch nach wie vor nicht.

> ext_fail_poll:
>   sbic GIFR,INTF0        ;interruptflag nunmehr pollen
>   rjmp ext_fail_noint    ;noch keiner wieder aufgetreten

Warum sbic und nicht sbis? Ist die Flag nicht clear nach Start der ISR? 
Erst ein neuer Interrupt setzt es doch auf 1, wobei mir der Begriff 
pollen nicht geläufig ist.

>   sbi GIFR,INTF0         ;interruptflag zurücksetzen
>   eor status,tmp         ;status korrekt nachführen

hier fehlt vor eor wohl das ldi tmp, 1

> ext_fail_noint:
>   sbis UCSRA,UDRE        ;ausgabekanal prüfen
>   rjmp fail_poll         ;warten, bis sich daran etwas ändert
>
>   ldi tmp,'f'            ;fehlermeldung ausgeben
>   out UDR,tmp

>   cli                    ;alle interrupts kurzzeitig sperren

warum muß ich hier sperren?

>   sbi GICR,INT0          ;eigenen interrupt wieder freigeben

muß ich nicht durch sei erneut setzen?

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.