Forum: Mikrocontroller und Digitale Elektronik AVR-USART-Interrupt Quizfrage


von Cube S. (cube_s)


Lesenswert?

Beim Versuch einen Raspberry PI mit einem Atmega8 per USART zu verbinden 
bin ich auf folgendes Problem gestoßen: Um die Programmierung des 
Atmega8 zu testen habe ich den Atmega8 auf RXD mit seinem eigenen TXD 
gefüttert. Das funktioniert auch im Prinzip, nur habe ich mit einer 
seltsamen Zeichenverdopplung zu kämpfen. Dazu folgender Code (mit dem 
Versuch nur das Wesentliche darzustellen):

Interruptroutine für USART RX
1
.def data = r20
2
.def flag = r21
3
4
usartrx: in data, UDR ; empfangenes Byte nach data sichern
5
         ser flag     ; flag für Hauptprogramm setzen
6
         reti

Hauptprogramm Variante 1 (mit sporadischer Zeichenverdopplung):
1
         cli          ; Interrupts generell im folgenden zugelassen
2
display: tst flag     ; Warten bis Interruptroutine das flag setzt
3
         breq display
4
         clr flag
5
         mov temp, data
6
         ; ... Die übliche LCD Ansteuerung zur Anzeige von temp
7
         rjmp display

Hauptprogramm Variante 2 (funktioniert):
1
loop:     cli         ; Auskommentieren führt auch hier zur Zeichenverdopplung
2
          tst   flag
3
          brne  display
4
          sei         ; Dem Interrupt auch eine Chance geben
5
          rjmp  loop 
6
7
display:  clr   flag
8
          mov   temp, data
9
          sei
10
          ; ... Die übliche LCD Ansteuerung zur Anzeige von temp
11
          rjmp loop
Warum kommt es in Variante 1 zu diesen seltsamen Zeichenverdopplungen? 
Klar, dass das Interrupt-Flag in Variante 2 ein Mutex für 'flag' und 
'data' ist, nur kann ich mir nicht erklären, dass ich Zeichen doppelt 
sehe (nein es ist nicht das Bier) sondern eher würde ich vermuten dass 
welche verschluckt werden. Das ganze geschieht bei absolut moderaten 
Bitraten (9600 Baud) und alle paar Sekunden ein Zeichen.

von Peter D. (peda)


Lesenswert?

Klassischer Fehler, der Interrupt sichert das SREG nicht, ändert es 
aber.

von Cube S. (cube_s)


Lesenswert?

Peter Dannegger schrieb:
> Klassischer Fehler, der Interrupt sichert das SREG nicht, ändert es
> aber.

Nein, das ist es nicht. Weder 'in' noch 'ser' ändern das SREG. Die 
Variante.
1
usartrx:  push  temp
2
          in  temp, SREG
3
          in  data, UDR
4
          ser  flag
5
          out  SREG, temp
6
          pop  temp
7
          reti
verhält sich ganz genauso.

von Malte S. (maltest)


Lesenswert?

Cube S. schrieb:
> eher würde ich vermuten dass
> welche verschluckt werden

Ja denn da ist eine Race Condition zwischen Abfrage und Löschen des 
Flags bei Variante 1. Wenn ich mal unterstelle, dass das cli eigentlich 
ein sei ist? Oder wo ist das sei?

EDIT: so wie oben dürfte es ja eher zu gar keinem Zeichen kommen...

von Cube S. (cube_s)


Lesenswert?

> EDIT: so wie oben dürfte es ja eher zu gar keinem Zeichen kommen...

Kommt es aber. Die 'sei' sind durchaus vorhanden (in Variante 1 habe ich 
es allerdings fälschlicherweise als 'cli' getarnt (der Kommentar 
widerspricht dem). Variante 2 funktioniert ohne Probleme.

Sprich: In Variante 1 sind Interrupts generell zugelassen, in Variante 2 
ist es so, dass Zugriffe auf 'data' und 'flag' quasi atomar sind.

von Malte S. (maltest)


Lesenswert?

Genau so hatte ich das ja vermutet, umso mehr stimme ich Dir zu, dass da 
eigentlich höchstens was verloren gehen kann. TOCTTOU in Bezug auf das 
Flag selbst als auch die Daten:
1
tst flag
2
; ...
3
clr flag
4
mov temp, data
bei aktiviertem Interrupt sieht flasch aus.

Wie es stattdessen aber zu einer Verdopplung kommen kann, würde mich 
auch interessieren. Blöde Neugier :)

Wird der ISR zu oft angesprungen oder sonstwie der Inhalt von r21 
vermüllt?

von Peter D. (peda)


Lesenswert?

Cube S. schrieb:
> Dazu folgender Code (mit dem
> Versuch nur das Wesentliche darzustellen):

Solange Du den Fehler nicht weißt, weißt Du auch nicht, was wesentlch 
ist!

Zeig ein komplettes Programm (als Anhang) und nichts aus dem Gedächtnis, 
sondern das exakte, getestete Programm mit stimmenden Kommentaren.
Sonst raten wir noch in 100 Jahren rum.

von Cube S. (cube_s)


Lesenswert?

Gerne hier auch das komplette Programm so wie es fehlerlos funktioniert. 
Allein das auskommentieren des 'cli' nach dem 'loop:'-Label zeigt den 
Effekt der sporadischen Zeichenverdopplung.
1
.include  "m8def.inc"
2
3
4
       rjmp main  ; Reset Handler
5
      rjmp noint ; IRQ0 Handler
6
      rjmp noint ; IRQ1 Handler
7
      rjmp noint ; Timer2 Compare Handler
8
      rjmp noint ; Timer2 Overflow Handler
9
      rjmp noint ; Timer1 Capture Handler
10
      rjmp noint ; Timer1 CompareA Handler
11
      rjmp noint ; Timer1 CompareB Handler
12
      rjmp timer1ov ; Timer1 Overflow Handler
13
      rjmp noint ; Timer0 Overflow Handler
14
      rjmp noint ; SPI Transfer Complete Handler
15
      rjmp usartrx ; USART RX Complete Handler
16
      rjmp noint ; UDR Empty Handler
17
      rjmp usarttx ; USART TX Complete Handler
18
      rjmp noint ; ADC Conversion Complete Handler
19
      rjmp noint ; EEPROM Ready Handler
20
      rjmp noint ; Analog Comparator Handler
21
      rjmp noint ; Two-wire Serial Interface Handler
22
      rjmp noint ; Store Program Memory Ready Handler
23
24
25
.def temp = r19
26
.def data = r20
27
.def flag = r21
28
.def count = r22
29
.def ccount = r23
30
31
main:    ldi    temp, low(ramend)
32
      out    spl, temp
33
      ldi    temp, high(ramend)
34
      out    sph, temp
35
36
      rcall  lcd_init
37
38
39
; Timer1
40
; ------
41
      clr    temp
42
      out    TCNT1H, temp
43
      out    TCNT1L, temp
44
      ldi    temp, (2<<CS10)
45
      out    TCCR1B, temp
46
      ldi    temp, (1<<TOIE1)
47
      out    TIMSK, temp
48
      ldi    temp, (1<<TOV1)
49
      out    TIFR, temp
50
51
      ldi    ccount, 'A'
52
53
; USART initialization
54
; --------------------
55
56
      clr    temp
57
      out    UBRRH, temp
58
      ldi    temp, 12
59
      out    UBRRL, temp
60
61
      ldi    temp, (1<<U2X)
62
      out    UCSRA, temp
63
      ldi    temp, (1<<RXEN)|(1<<RXCIE)|(1<<TXEN)|(1<<TXCIE)
64
      out    UCSRB, temp
65
      ldi     temp, (1<<URSEL)|(3<<UCSZ0)|(0<<UPM0)
66
      out     UCSRC, temp
67
      ldi    temp, (1<<RXC)|(1<<TXC)
68
      out    UCSRA, temp
69
70
      clr    flag
71
      clr    count
72
73
loop:    cli
74
      tst    flag
75
      brne  display
76
      sei
77
      rjmp  loop
78
79
display:  mov    lcd_temp, data
80
      clr    flag
81
      sei
82
83
      rcall  lcd_data
84
      inc    count
85
      cpi    count, 8
86
      brlt  loop
87
      clr    count
88
      ldi    lcd_temp, $80
89
      rcall  lcd_cmd
90
      ;rcall  lcd_5ms
91
92
      rjmp  loop
93
94
noint:    reti
95
96
; --------------------------------------
97
98
99
usartrx:  push  temp
100
      in    temp, SREG
101
      in    data, UDR
102
      ser    flag
103
      out    SREG, temp
104
      pop    temp
105
      reti
106
107
usarttx:  
108
      reti
109
110
; --------------------------------------
111
112
timer1ov:  out UDR, ccount
113
      inc ccount
114
      cpi ccount, 'z'+1
115
      brlt timer1l1
116
      ldi ccount, 'A'
117
timer1l1:  reti
118
119
; --------------------------------------
120
121
lcd_hex:  push  lcd_temp
122
      push  zl
123
      push  zh
124
      push  r0
125
126
      push  lcd_temp
127
      swap  lcd_temp
128
      andi  lcd_temp,15
129
      rcall  lcd_nibble
130
      pop    lcd_temp
131
      andi  lcd_temp,15
132
      rcall  lcd_nibble
133
134
      pop    r0
135
      pop    zh
136
      pop    zl
137
      pop    lcd_temp
138
      ret
139
140
lcd_nibble: ldi    zl, low(2*hex)
141
      ldi    zh, high(2*hex)
142
      add    zl, lcd_temp
143
      clr    lcd_temp
144
      adc    zh, lcd_temp
145
      lpm
146
      mov    lcd_temp, r0
147
      rcall  lcd_data
148
      ret
149
150
hex:    .db    "0123456789ABCDEF"
151
152
153
; --------------------------------------
154
155
.equ    lcd_port = PORTC
156
.equ    lcd_ddr = DDRC
157
.equ    lcd_en = 5
158
.equ    lcd_rs = 4
159
160
.def    lcd_temp = r16
161
.def    lcd_c1 = r17
162
.def    lcd_c2 = r18
163
164
lcd_init:  push  lcd_temp
165
      ldi    lcd_temp, 0x7f
166
      out    lcd_ddr, lcd_temp
167
      ldi    lcd_temp, 50
168
lcd_init1:  rcall  lcd_5ms
169
      dec    lcd_temp
170
      brne  lcd_init1
171
      ldi    lcd_temp, 3
172
      out    lcd_port, lcd_temp
173
      rcall  lcd_enable
174
      rcall  lcd_5ms
175
      rcall  lcd_5ms
176
      rcall  lcd_5ms
177
      rcall  lcd_5ms
178
      rcall  lcd_enable
179
      rcall  lcd_5ms
180
      rcall  lcd_enable
181
      rcall  lcd_5ms
182
      ldi    lcd_temp, 2
183
      out    lcd_port, lcd_temp
184
      rcall  lcd_enable
185
      rcall  lcd_5ms
186
187
      ldi    lcd_temp, 0x28  ; Function Set
188
      rcall  lcd_cmd
189
      ldi    lcd_temp, 0x08  ; Display Off
190
      rcall  lcd_cmd
191
      ldi    lcd_temp, 0x01  ; Clear Display
192
      rcall  lcd_cmd
193
      rcall  lcd_5ms
194
      ldi    lcd_temp, 0x06  ; Entry Mode Set
195
      rcall  lcd_cmd
196
      rcall  lcd_5ms
197
      ldi    lcd_temp, 0x0C  ; Display On
198
      rcall  lcd_cmd
199
200
      pop    lcd_temp
201
      ret
202
203
lcd_cmd:  push  lcd_temp
204
      push  lcd_temp
205
      swap  lcd_temp
206
      andi  lcd_temp, 0b1111
207
      out    lcd_port, lcd_temp
208
      rcall  lcd_enable
209
      pop    lcd_temp
210
      andi  lcd_temp, 0b1111
211
      out    lcd_port, lcd_temp
212
      rcall  lcd_enable
213
      rcall  lcd_50us
214
      pop    lcd_temp
215
      ret
216
217
lcd_data:  push  lcd_temp
218
      push  lcd_temp
219
      swap  lcd_temp
220
      andi  lcd_temp, 0b1111
221
      sbr    lcd_temp, 1<<lcd_rs
222
      out    lcd_port, lcd_temp
223
      rcall  lcd_enable
224
      pop    lcd_temp
225
      andi  lcd_temp, 0b1111
226
      sbr    lcd_temp, 1<<lcd_rs
227
      out    lcd_port, lcd_temp
228
      rcall  lcd_enable
229
      rcall  lcd_50us
230
      pop    lcd_temp
231
      ret
232
233
lcd_enable:  sbi    lcd_port, lcd_en
234
      nop
235
      nop
236
      nop
237
      cbi    lcd_port, lcd_en
238
      ret
239
240
lcd_50us:   push  lcd_temp
241
      ldi    lcd_temp, 66
242
lcd_50us1:  dec    lcd_temp
243
      brne  lcd_50us1
244
      pop    lcd_temp
245
      ret
246
247
lcd_5ms:  push  lcd_c1
248
      push  lcd_c2
249
      ldi    lcd_c1, $21
250
lcd_5ms1:   ldi    lcd_c2, $C9
251
lcd_5ms2:   dec    lcd_c2
252
             brne  lcd_5ms2
253
             dec    lcd_c1
254
             brne  lcd_5ms1
255
      pop    lcd_c2
256
      pop    lcd_c1
257
             ret

von Malte S. (maltest)


Lesenswert?

>      tst    flag

> timer1ov:  out UDR, ccount
>       inc ccount
>       cpi ccount, 'z'+1
>       brlt timer1l1
>       ldi ccount, 'A'

>      brne  display

von Cube S. (cube_s)


Lesenswert?

Malte S. schrieb:
> bei aktiviertem Interrupt sieht flasch aus.

Da stimme ich absolut zu, deswegen habe ich ja auch die Variante mit den 
gesperrten Interrupts gegenübergestellt. Nur bei der Baudrate und der 
Häufigkeit von selbst gesendeten Zeichen hätte ich das "durchgehen 
lassen". Und ich finde einfach die Erklärung nicht warum es so ist wie 
es ist.

von Malte S. (maltest)


Lesenswert?

wenn's das war, wovon ich erstmal ausgehe, dann war

Peter Dannegger schrieb:
> Klassischer Fehler, der Interrupt sichert das SREG nicht, ändert es
> aber.

ins schwarze. Nur ohne den schuldigen ISR auf dem Silbertablett :)

von Cube S. (cube_s)


Lesenswert?

Malte S. schrieb:
> ins schwarze

Treffer, versenkt. Das war's. Besten Dank auch. Gute Güte, das sollte ja 
nur zum Testen sein. Naja gescheiter wird man durch scheitern.

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.