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?
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).
:-)
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.
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.
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_tdata=SPDR;
5
fifoPush(&fifoIn,data);
6
PORTA&=~(1<<5);
7
}
1
typedefstruct_fifo
2
{
3
volatileuint16_tused;
4
volatileuint8_treadPos;
5
volatileuint8_twritePos;
6
volatileuint8_tdata[256];
7
}Fifo;
1
voidmain()
2
{
3
while(1)
4
{
5
charbuffer[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_tfifoPop(Fifo*fifo)
2
{
3
if(!fifo->used)
4
return0;
5
uint8_tdata=fifo->data[fifo->readPos];
6
fifo->readPos++;
7
//uint8_t sreg=SREG;
8
cli();
9
fifo->used--;
10
//SREG=sreg;
11
sei();
12
returndata;
13
}
1
inlinevoidfifoPush(Fifo*fifo,uint8_tdata)
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.
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.
> 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;
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
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.
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.
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.
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.
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
inlinevoidfifoPush(Fifo*fifo,uint8_tdata)
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?
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.
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?