Forum: Mikrocontroller und Digitale Elektronik volatile-Variable wird nicht geschrieben


von Chris R. (hownottobeseen)


Angehängte Dateien:

Lesenswert?

Hallo zusammen,

ich grüble gerade seit mehreren Tagen über ein merkwürdiges Problem - 
das ich zwar isolieren aber bis jetzt nicht nachvollziehen konnte.

Zur Umgebung:
Ein Atmega328P bei 3,3 V und 12 MHz (Quarz) soll als SPI-Slave agieren.

Zur Synchronisation kann leider nicht das CS-Signal verwendet (daher mit 
GND verbunden) werden, stattdessen soll das Timing der Kommunikation 
genutzt werden:
Wir für etwa 250 µs kein SPI-Interrupt ausgelöst, werden die empfangenen 
Daten ausgewertet (momentan aus dem Code entfernt) und SPIE deaktiviert 
und aktiviert, um möglichen Versatz der Daten zum Clock zu umschiffen.

Dazu wird im SPI-Interrupt ticks auf 0 gesetzt und über einen 
Timer-Interrupt der Wert (sofern kleiner 255) inkrementiert. Über einen 
Task, der im main-loop ausgeführt wird, wird das Timing und die Anzahl 
der empfangenen Bytes überprüft. Die Variable ist selbstverständlich 
volatile und im Timer-Interrupt habe ich aus Verzweiflung schon einen 
Atomic-Block gesetzt, welcher aber keine Besserung bringt.

Zusätzlich lasse ich, um Überlappung der Interrupts besser erkennen zu 
können, in den jeweiligen Funktionen Pins wackeln und konnte soweit 
keine Auffälligkeit erkennen.

Das Problem:

Wie im Screenshot vom Logic Analyzer zu sehen ist (Aufzeichnung mit 
Saleae Logic 1.2.12 in tickerror.logicdata.zip), hätte vor dem SPI IRQ 
(kurz nach der +30 µs-Marke, Ch06) tick inkrementiert, kurz danach (kurz 
vor der +40 µs-Marke, Ch05) aber im SPI-Interrupt wieder auf 0 
zurückgesetzt werden sollen.

Im darauf folgenden Task-Aufruf (+40 µs-Marke, Ch07) steht in Ticks 
allerdings 255 (Format auf dem UART: Ticks und Anzahl der der 
empfangenen Bytes roh) - die Nachricht hätte aber nicht auftreten 
dürfen, sondern erst nachdem alle Bytes empfangen wurden (nicht im Bild, 
4,333 ms später).

main.c enthält das isolierte Problem, nicht den gesamten Code. 
Zusätzlich habe ich das Atmel Studio 7-Projekt angehängt.

Die mit dem LA erfassten Daten wurden mit dem angehängten Code erzeugt 
(Stimuli erfolgte mit einem "rückwärts" betriebenen Logic8, Ch00-Ch02), 
wobei der Fehler reproduzierbar aber nicht immer an der selben Stelle 
auftritt.

Vielleicht bin ich mittlerweile blind und habe eine Kleinigkeit 
übersehen oder "fressen" sich die Interrupts gegenseitig?

Viele Grüße & Vielen Dank!

Chris

von Peter II (Gast)


Lesenswert?

hat vermutlich nicht mit deinem Problem zu tun aber

ATOMIC_BLOCK(ATOMIC_FORCEON)

gehört nicht in eine ISR. Bei dem Atmel sind alles ISR gesperrt wenn 
eine ISR aufgerufen wird, es macht damit gar keine sinn.

von hownottobeseen (ohne Login) (Gast)


Lesenswert?

Hallo Peter,

Danke für den Hinweis. Der Atomic-Block war wie schon geschrieben eine 
Verzweiflungstat. Was ich vergessen habe zu schreiben: das Problem 
besteht unabhängig von dem Block. Habe beides getestet und kam auf das 
gleiche Ergebnis.

Viele Grüße

Chris

von Rainer B. (katastrophenheinz)


Lesenswert?

Moin,

du führst das volatile-Konzept selbst ad absurdum, indem du in "task" 
mit einer Kopie von "ticks" arbeitest und nicht mit "ticks" selbst.

Da dein Programm zu 99,9% aus wiederholter Ausführung von "task" 
besteht, schlägt jeder Interrupt auch innerhalb der Ausführung von 
"task" zu und beschert dir inkonsistente Zustände ( dh unterschiedliche 
Werte von dem lokalen "t" in "task" und "ticks" ).

Ändere mal "task" so ab, dass du direkt mit "ticks" arbeitest. Weiterhin 
würde ich "task" dann auch nicht permanent aufrufen, sondern nur dann, 
wenn du tatsächlich rücksetzen willst. Diese Rücksetzentscheidung kannst 
du per volatile flag-Variable direkt in der Timer-ISR setzen und diese
Flag-Variable in der main-Loop abfragen:
1
volatile uint8_t reset_flag = 0;
2
#define TICK_LIMIT 200
3
ISR(TIMER0_COMPA_vect)
4
{
5
  PORTD |= (1<<PORTD7);
6
  if(ticks < 255) ticks++;
7
        if ( bytepos > 0 && ticks > TICK_LIMIT ) reset_flag = 1;
8
  PORTD &= ~(1<<PORTD7);
9
}
10
11
void do_reset(void)
12
{
13
        PORTD |= (1<<PORTD6);
14
  spi_disable();
15
  spi_enable();
16
    
17
  uart_debug_putc(ticks);
18
  uart_debug_putc(bytepos);
19
  bytepos = 0;
20
  PORTD &= ~(1<<PORTD6);
21
}
22
23
24
...
25
...
26
...
27
   while ( 1 ) {
28
     if ( reset_flag ) {
29
        ATOMIC_BLOCK(ATOMIC_RESTORESTATE) {
30
            reset_flag=0;
31
            do_reset();
32
        }
33
     }
34
   }

von Nebeltroll (Gast)


Lesenswert?

Wie soll denn ueberhaupt eine Synchronisation passieren ? Einfach etwas 
reinclocken ?  Nee. Ohne CS laeuft nichts. Du kannst es natuerlich ohne 
CS probieren. .. bis es irgendwann nicht mehr geht.

von Chris R. (hownottobeseen)


Angehängte Dateien:

Lesenswert?

Rainer B. schrieb:

> du führst das volatile-Konzept selbst ad absurdum, indem du in "task"
> mit einer Kopie von "ticks" arbeitest und nicht mit "ticks" selbst.

damit wollte ich bezwecken, dass der Wert in ticks in der Ausgabe 
derselbe wie bei der Abfrage ist.

Ich habe deine Codeänderungen übernommen und habe den gleichen Effekt 
wie zuvor. :(

Ich habe lediglich TICK_LIMIT angepasst, da ich kürzere Zeiten für den 
Sync brauche...

Glitches würde ich ausschließen, die Versorgung vom Chip sieht sauber 
aus.

Ich weiß nicht, ob mein Verdacht berechtigt ist, aber der AVR stammt von 
eBay und mir kam die Laserung etwas ungewöhnlich vor (der linke Teil vom 
Atmel-A ist nur ein Stroke, der Rest ist doppelt) hatte bis jetzt aber 
noch keine anderen neuen Atmels auf dem Tisch um vergleichen zu 
können...

Viele Grüße und abermals vielen dank für die Hilfe!

Chris

von Rainer B. (katastrophenheinz)


Lesenswert?

Hi,

für mich sieht es so aus, dass das Programm genau das macht, was es 
soll:
Anhand des Abschnittes, den du getraced hast kann man folgendes sehen:
- Ticks wird mit jedem Aufruf der OCR0A-Match ISR um 1 hochgezählt ( am 
Anfang 4, am Ende 8, das passt zu den Peaks auf PD7
- bytepos wird in do_reset auf 0 gesetzt und mit jedem empfangenen 
Zeichen um 1 hochgezäht
- "ticks" wird im SPI-Interrupt auf 0 gesetzt
- Leider endet der Trace, bevor ticks > TICK_LIMIT, daher lässt sich 
hierzu nichts aussagen.

Frage: Was hättest du denn erwartet?

: Bearbeitet durch User
von Chris R. (hownottobeseen)


Angehängte Dateien:

Lesenswert?

Hi,

wenn der Code so funktionieren würde, wie ich gewollt hatte, müsste 
ticks im SPI-Interrupt auf 0 gesetzt werden, was aber nicht der Fall 
ist. Stattdessen bleibt die 4 in ticks.

Der Code produziert aber auch Verhalten wie es gewollt ist, wie in 
tickerror2_ok.png zu sehen ist.

Das ist eben, was mich komplett stutzig macht...

Viele Grüße

Chris

von Rainer B. (katastrophenheinz)


Lesenswert?

Ah, ok. Was mich stutzig macht: Die Ausführung von do_reset, angezeigt 
durch PD6, wird in jedem Fall durch eine Ausführung der Timer-ISR, 
angezeigt durch PD7, angestossen. Am Beginn des Diagramms passt es: Hier 
folgt der Puls von PD6 direkt auf den Puls von PD7. Am Ende des 
Diagramms ist aber eine ca 20 µs lange Lücke zwischen der fallenden 
Flanke von PD6 und der steigenden Flanke von PD7. Das kann ich mir nicht 
erklären: Wodurch wird an dieser Stelle do_reset aufgerufen? Hast du 
irgendwo einen weiteren Aufruf von do_reset drin? Oder setzt du ausser 
in der Timer-ISR noch irgendwo noch "reset_flag"?

von Chris R. (hownottobeseen)


Lesenswert?

Ich kann es mir zwar nicht zu 100 % erklären, aber nach einem Reboot vom 
PC und einem Clean Build funktionierte der Code. So etwas ist mir bis 
jetzt noch nie untergekommen.
Um sicher zu gehen, gibt der AVR jetzt beim Start _DATE__ und __TIME_ 
aus.

Vielen Dank für deine Unterstützung, Rainer!

Viele Grüße und noch ein schönes Restwochenende

Chris

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.