Forum: Mikrocontroller und Digitale Elektronik Interrupt unterdrückung mit cli/sei?


von Fabian S. (jacky2k)


Lesenswert?

Hallo,
ich stehe hier vor einem sehr seltsamen Problem. Ich habe im Atmega 
einen SPI Slave implementiert, der ca. alle 10µsec nen Byte bekommt. Bei 
Empfang wird der Interrupt ausgelöst und das Byte in einen fifo 
geschrieben.
Es geht mir nun um das auslesen aus dem Fifo von der main aus, speziell 
um die Variable, die den aktuell belegten Speicher im Fifo beinhaltet.
Wenn ich nun in der main() ein fifo.used--; mache ist das ja kein 
atomarer Befehl, sprich er kann unter brochen werden von dem Interrupt, 
welcher dann ein fifo.used++; macht und schon habe ich inkonsistente 
Daten.
Dafür hatte ich nun um das fifo.used--; ein cli()/sei() gemacht, jedoch 
führt das aus mir nicht ganz erklärlichen Gründen dazu, dass nicht mehr 
alle Bytes angekommen.
Ich dachte, wenn ich cli() mache werden keine Interrupts ausgeführt, 
sollte jedoch ein Flag gesetzt werden, wird der Interrupt ausgeführt, 
sobald ich wieder sei() mache. Ist die Annahme falsch?

von Floh (Gast)


Lesenswert?

Fabian S. schrieb:
> Dafür hatte ich nun um das fifo.used--; ein cli()/sei() gemacht, jedoch
> führt das aus mir nicht ganz erklärlichen Gründen dazu, dass nicht mehr
> alle Bytes angekommen.

Dann brauchst du zum Auslesen länger als  (10us - Zeitbedarf für einen 
Interrupt).
:-)

von Sam .. (sam1994)


Lesenswert?

Wenn aber 2 Bytes in dieser Zeit ankommen, nützt das nichts. Das Flag 
sagt nicht wie viele Byte und welche angekommen sind.

von Fabian S. (jacky2k)


Lesenswert?

Es sollten in der Zeit laut meiner Berechnung keine zwei Bytes ankommen.
1
cli();
2
fifo->used--;
3
sei();
Ich würde mal schätzen, das sind 5 Takte. Alle 10µsec kommt im 
schlimmsten Fall ein Byte, bei 16Mhz macht das 160 Takte, die ich Zeit 
habe.
Ich wage mal zu bezweifeln, dass das zwei ankommen während dieser kurzen 
Zeit.

von Fabian S. (jacky2k)


Lesenswert?

Habe das mal eben simuliert, die drei Befehle brauchen 7 Takte, also bei 
weitem weniger als ich Zeit habe. Wo liegt dann das Problem?

von Floh (Gast)


Lesenswert?

Fabian S. schrieb:
> Wo liegt dann das Problem?

Zeig doch mal deinen Code. Vielleicht ist ja irgendwo sonst der Hund 
drin.
:-)

von Sam .. (sam1994)


Lesenswert?

Fabian S. schrieb:
> Habe das mal eben simuliert, die drei Befehle brauchen 7 Takte, also bei
> weitem weniger als ich Zeit habe. Wo liegt dann das Problem?

sry dann hab ich zuerst deine Beschreibung falsch verstanden.

von Fabian S. (jacky2k)


Lesenswert?

Also den ganzen Code kann ich hier nicht posten ;) Ich versuche es mal 
auf das wesentliche zu beschränken:
1
ISR(SIG_SPI)
2
{
3
  PORTA |= (1<<5);
4
  uint8_t data=SPDR;
5
  fifoPush(&fifoIn, data);
6
  PORTA &= ~(1<<5);
7
}
1
typedef struct _fifo
2
{
3
  volatile uint16_t used;
4
  volatile uint8_t readPos;
5
  volatile uint8_t writePos;
6
  volatile uint8_t data[256];
7
} Fifo;
1
void main()
2
{
3
  while(1)
4
  {
5
    char buffer[200];
6
    for(i=0; i<200; i++)
7
    {
8
      while(!fifoIn.used);
9
      last++;
10
      current=fifoPop(&fifoIn);
11
      if(current!=last)
12
      {
13
        //uart_putcc("error: \n");
14
        PORTD |= (1<<7);
15
        last=current;
16
      }
17
      else
18
        PORTD &= ~(1<<7);
19
      buffer[i]=current;
20
    }
21
}
1
uint8_t fifoPop(Fifo* fifo)
2
{
3
  if(!fifo->used)
4
    return 0;
5
  uint8_t data=fifo->data[fifo->readPos];
6
  fifo->readPos++;
7
  //uint8_t sreg=SREG;
8
  cli();
9
  fifo->used--;
10
  //SREG=sreg;
11
  sei();
12
  return data;
13
}
1
inline void fifoPush(Fifo* fifo, uint8_t data)
2
{
3
  if(fifo->used==256)
4
  {
5
    //PORTD ^= (1<<7);
6
    return;
7
  }
8
  fifo->data[fifo->writePos]=data;
9
  fifo->used++;
10
  fifo->writePos++;
11
}

In dem Struct fifoIn wird beim starten alles auf 0 initialisiert (außer 
den Daten). Senden tuh ich vom Master immer 0,1,2,3,...,254,255,0,1,... 
und das kommt im Interrupt auch genau so an. Jedoch sehe ich am Scope, 
dass PD7 immer fleißig am flimmern ist, und das auch sehr unregelmäßig.

Interessant ist auch, wenn ich das cli/sei komplett raus nehme scheint 
alles zu funktionieren, jedoch habe ich Angst, dass dann mal ein Byte 
flöten geht, was fatal wäre.

von Thomas E. (thomase)


Lesenswert?

Warum nimmst du nicht 2 Indexer? FifoRd und FifoWr.

Wenn etwas im Buffer ist, dann ist FifoRd != FifoWr.

Das: if(fifo->used==256) funktioniert nicht. unint8 ist maximal 255.

mfg.

von Fabian S. (jacky2k)


Lesenswert?

> Warum nimmst du nicht 2 Indexer? FifoRd und FifoWr.
> Wenn etwas im Buffer ist, dann ist FifoRd != FifoWr.
>
Das ist ne Idee, aber kann ich dann auch die aktuelle Größe bestimmen? 
Das müssten dann ja zwei 16 bit sein, aber auch die laufen irgendwann 
einmal über...

> Das: if(fifo->used==256) funktioniert nicht. unint8 ist maximal 255.

volatile uint16_t used;

von Peter D. (peda)


Lesenswert?

160 Takte ist nicht viel, die können schnell durch Interrupthandler 
draufgehen.
Insbesondere Unterfunktionsaufrufe sind sehr teuer (viel Push/Pop).
Ein einziger Befehl zuviel und es kann krachen.

Laß den Master mal langsamer senden, obs dann geht.


Peter

von Thomas E. (thomase)


Lesenswert?

Fabian S. schrieb:
> volatile uint16_t used;

Hab' jetzt auch gesehen.

Fabian S. schrieb:
> Das müssten dann ja zwei 16 bit sein, aber auch die laufen irgendwann
> einmal über...

Mußt du alles gar nicht machen.

Dein Eingangsbuffer ist 256 Byte groß. Dann nimmst du 2 Indexer als 
uchar
und wenn die bei 256 angekommen sind, gehen sie wieder auf 0. Weil 256 
können sie ja nicht.

mfg.

von Fabian S. (jacky2k)


Lesenswert?

Je langsamer ich den Master senden lasse, desto seltener treten die 
Fehler auf.
Damit ich das mit dem Interrupt-Zeit im Auge habe setze ich beim Start 
ein Pin auf high und am ende Auf low, das Scope sagt mir, dass der nicht 
mal 20% der Zeit benötigt, zugegeben, es kommen noch ein paar Zyklen an 
Aufräumarbeiten dazu, das sollten jedoch nicht mehr als 20 sein. In 
Assembler braucht er 3 Takte für den Sprung in den Interrupt und 5 
wieder raus. Dazu kommen noch ein paar zum sichern der Register, weiß 
nicht wie effizient gcc da ist.
Was die Funktionsaufrufe angeht: Welche Funktionsaufrufe meinst du? Da 
ist nur fifoPush und das ist inline, also kein push/pop.

von Thomas E. (thomase)


Lesenswert?

Peter Dannegger schrieb:
> Insbesondere Unterfunktionsaufrufe sind sehr teuer (viel Push/Pop).
>
> Ein einziger Befehl zuviel und es kann krachen.

Du brauchst in der ISR nicht mehr als das:

nInBuf[nIndW++] = SPDR;

Und in der main:

if (nIndW != nIndR)
{
  Lesen und verarbeiten.
}

Fabian S. schrieb:
> aber kann ich dann auch die aktuelle Größe bestimmen?

Ist das wichtig? Du liest Daten solange welche da sind.

mfg.

von Fabian S. (jacky2k)


Lesenswert?

Deine Lösung hat einen Haken: Was passiert, wenn ich 256 Bytes schreibe 
ohne eins zu lesen? Dann stehen beide Zeiger auf 0 und es sind keine 
Daten im Puffer? Dafür ist die used Variable.

Und ja, es ist wichtig, dass ich weiß wie viele Daten im Puffer sind, da 
ich Befehlsblöcke verarbeiten muss und ich kann die nur verarbeiten, 
wenn alle Daten des Befehls da sind.

von Fabian S. (jacky2k)


Lesenswert?

Alles klar, ich denke ich habs gelöst. Der Ansatz vom Thomas war doch 
nicht so falsch wie ich dachte. Mit einer kleinen Sonderbedingung beim 
Schreiben habe ich das Problem umgangen:
1
inline void fifoPush(Fifo* fifo, uint8_t data)
2
{
3
  if(fifo->writePos+1==fifo->readPos)
4
    return;
5
  fifo->data[fifo->writePos]=data;
6
  fifo->writePos++;
7
}

Dann habe ich zwar nur 255 statte 256 Bytes zur Verfügung aber das macht 
den Kohl nun auch nicht fett.

Die aktuelle Größe des Puffers müsste sich dann so bestimmen lassen:
1
(fifo->writePos-fifo->readPos)

Ich bedanke mich recht herzlich für die Hilfe.
Hat noch jemand etwas auszusetzen? Habe ich etwas übersehen?

von Thomas E. (thomase)


Lesenswert?

Fabian S. schrieb:
> Deine Lösung hat einen Haken: Was passiert, wenn ich 256 Bytes schreibe
> ohne eins zu lesen?

Dann habe ich festgestellt, daß der Puffer zu klein ist.

Die nächste Frage wäre dann logischerweise: Was passiert, wenn 257 Bytes 
angekommen sind, ohne eins gelesen zu haben?

Dafür muß man natürlich Sorge tragen, daß beides nicht passiert.

Fabian S. schrieb:
> Hat noch jemand etwas auszusetzen? Habe ich etwas übersehen?

> if(fifo->writePos+1==fifo->readPos)
>return;
Damit gehen dir u.U. Daten verloren.

> Die aktuelle Größe des Puffers müsste sich dann so bestimmen lassen:
>(fifo->writePos-fifo->readPos)

Gilt nicht nach einem Wrap-Around des WritePtr.

mfg.

von Fabian S. (jacky2k)


Lesenswert?

Also der Puffer ist 256 Bytes groß, damit ich eben den Überlauf von den 
Zeigern missbrauchen kann.

Und warum geht das deiner Meinung nach nicht, wenn der writePos 
übergelaufen ist? Wenn ich nun 256 geschrieben habe (writePos wieder auf 
0) und 10 gelesen sind folglich noch 246 im puffer. Rechne ich dann 0-10 
kommt da 246 raus. Wo ist das Problem?

von Thomas E. (thomase)


Lesenswert?

Fabian S. schrieb:
> Rechne ich dann 0-10
> kommt da 246 raus. Wo ist das Problem?

Hast recht. Nirgends.

mfg.

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.