PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : TWI stört meinen Interrupt zur Frequenzmessung



Michael-Hage
12.04.2008, 14:14
Ich messe eine Frequenz, indem ich die Flanke vom INT0 auswerte. Bei der ersten Flanke wird TCNT1 in Start gespeichert und bei der zweiten Flanke wird TCNT1 in Stop gespeichert. In der main-Schleife wird dann über eine Freischaltbedingung die Frequenz in Hz berechnet. Das funktioniert einwandfrei.

Jetzt wird in der main-Schleife das Ergebnis nach der Berechnung per I2C an das Display geschickt. Währenddessen können ja ruhig weitere Flanken gemessen werden, da der Interrupt0 ja Vorrang hat. Leider stimmt das Ergebnis nicht mehr, sobald die I2C Befehle im Programm sind.

Wie kann das sein, dass die Zeit falsch gemessen wird, obwohl der Interrupt doch Vorrang hat und ja auch garantiert einmal ohne Unterbrechung abgearbeitet wird?

Die I2C Bibliotheken basieren auf denen von P.Fleury. Also es wird der Hardware-TWI benutzt. Ich konnte im Code auch nirgendwo einen anderen Interrupt finden. Aber die INT0-Routine sperrt ja sowieso andere Interrupt, während sie aktiv ist?




// Interrupt INT0
ISR(INT0_vect)
{
static unsigned char ErsteFlanke = TRUE;

if( Update ) //Erst erneut Messen, wenn vorher ausgewertet wurde.
return;

if( ErsteFlanke )
{
StartTime0 = TCNT1; //Zeit der ersten Flanke speichern
NumberOverflow = 0;
ErsteFlanke = FALSE;
}
else
{
EndTime = TCNT1; //Zeit der zweiten Flanke speichern
Update = TRUE; //Berechnung freigeben
ErsteFlanke = TRUE;
}
}

int main()
{
while (1)
{

if(Update) // Berechnung der Frequenz
{
Drehzahl = (NumberOverflow * 65536) + EndTime - StartTime;
Drehzahl = F_CPU / Drehzahl;
Update0 = FALSE;
}


// I2C
if (!TWIM_Start (SlaveAddress, TWIM_WRITE))
{
TWIM_Stop ();
}
else
{
TWIM_Write (display_value);
TWIM_Stop ();
Delay_ms (1);
}

if (!TWIM_Start (SlaveAddress, TWIM_READ))
{
TWIM_Stop ();
}
else
{
Data[0] = TWIM_ReadNack ();
TWIM_Stop ();
Delay_ms (1);
}
}
}

izaseba
12.04.2008, 15:40
Hallo,


Drehzahl = (NumberOverflow * 65536) + EndTime - StartTime;

Das kann zu Problemen führen, Grund:
NumberOverflow,Endtime und StartTime können sich während der Berechnung ändern.
Leg Dir mal 3 lokale Variablen an , schalte kurz mit cli() die Interrupts ab, kopiere NumberOverflow,Endtime und StartTime in die lokale Variablen, gib die Interrupts wieder frei und rechne in Ruhe mit den Kopien weiter.

Vielleicht hilft das.

Gruß Sebastian

McJenso
12.04.2008, 20:29
Hallo,

wenn du bei 0 U/min Daten über das TWI schickst, wird dann die Drehzahl richtig mit 0 erkannt oder gibt es trotzdem Interrupts? Falls ja, wie sieht die Beschaltung aus, woher kommt das Signal? Wo ich drauf hinaus will ist, dass es sich auch um ein Hardwareproblem handeln kann.
Der Tipp von Sebastian ist aber in jedem Fall richtig und wichtig.

Gruß

Jens

Michael-Hage
13.04.2008, 09:39
Die Berechnung der Frequenz wird ja mit dem Update-Flag freigeschaltet. Es wird also erst wieder ein neuer Wert eingelesen, sobald die vollständige Berechnung bestätigt ist. Tritt ein Interrupt auf, während Update = FALSE ist, wird die Interrupt-Routine nicht ausgeführt, sondern direkt wieder verlassen. Also die Frequenzmessung funktioniert für meine Zwecke zu 100%.

Sobald ich den TWI-Client abziehe, wird die Frequenz wieder richtig angezeigt. Ich verstehe nicht, wie ein paar Funktionen im pollenden Betrieb meine Interrupt-Routine beeinflussen können. Mit cli() habe ich auch schon versucht das zu verriegeln, aber das hatte keine Wirkung, da die Interrupt-Routine cli() zu Beginn ja selbst auch ausführen.

Es geht also darum:


while(1)
{
if (Update) //Update => Freischaltbedingung für vollständige Messung
{
Frequenzberechnung...
Update = FALSE; //Neue Messung erlauben
}

I2C_Sende();
I2C_Stop();
I2C_Empfange();
I2C_Stop();
}

Bei jedem Schleifendurchlauf wird also einmal per I2C gesendet. Wenn ich die I2C-Befehle auskommentiere, stimmt die Berechnung natürlich wieder. Ich kann mir überhaupt nicht erklären, warum die Interrupt-Routine vom pollenden I2C gestört werden könnte.

Die gemessene Frequenz wird von einem Atmega8 generiert.

izaseba
13.04.2008, 10:47
Hallo,
kannst Du mal das ganze Programm posten ?

Gruß Sebastian

Michael-Hage
13.04.2008, 12:54
Hier mal als Anhang der Code. Danke schonmal für die Mühe!

Hubert.G
13.04.2008, 14:38
Du bist sicher das in deiner TWI_Master.c kein Interrupt verwendet wird.

izaseba
13.04.2008, 15:05
Nur mal so eine Frage am Rande,
was ergibt
(NumberOverflow0 * 65536)
wenn
unsigned int NumberOverflow0 = 0;

Gibt es hier keinen Überlauf ?

McJenso
13.04.2008, 15:10
Hallo,

das dachte ich auch erst und wollte schon ein Typecasting vorschlagen. Aber da 65536 nicht mehr in 2Byte geht (max. 65535) sollte er schon richtig rechnen.

Gruß

Jens

Michael-Hage
13.04.2008, 15:17
Wie gesagt funktioniert die Frequenzmessung einzeln korrekt. Die Overflow-Variable wird gelegentlich auf 1 gesetzt, wenn zwischen der ersten Messung und der zweiten Messung die 65535 des Timers auf 0 springen.

Ich hatte auch schon vermutet, dass die I2C-Routine irgendwelche Interrupts nutzt, die meine Frequenz verfälschen. Aber im Quellcode sind nur Registerabfragen und wait-Befehle. Mal abgesehen davon, wäre Interrupts auch garnicht schlimm, da eine Interrupt-Routine nie mittendrin verlassen werden kann, sondern immer erst am Ende.

Wenn ich vor den I2C-Befehlen ein cli() schreibe, dürften ja keine Interrupts ausgeführt werden? Da I2C dann noch funktioniert, gehe ich davon aus, dass die Bibliotheken keine Interrupts enthalten.

Edit:
Könnte folgender Code aus meiner TWI-Bibliothek meinen INT0 beeinflussen?

/*
** Wait until transmission completed
*/
while (!(TWCR & (1<<TWINT)));

izaseba
13.04.2008, 16:37
Ähm, sorry Doppelpost :-(

izaseba
13.04.2008, 16:38
Könnte folgender Code aus meiner TWI-Bibliothek meinen INT0 beeinflussen?

/*
** Wait until transmission completed
*/
while (!(TWCR & (1<<TWINT)));

Nein, der µC steckt solange in der Schleife, bis die Übertragung beendet ist.
Interrupt wird nur dann ausgeführt, wenn TWIE in TWCR gesetzt ist.

Ich kann ehrlich gesagt das Problem nicht orten :-(

Fassen wir mal zusammen.

Wie wird die Messung ohne TWI ausgegeben ? Uart ?
Und wenn TWI dazukommt, wo gibt es den Fehler bei der TWI Übertragung oder bei UART , oder beides ?

Ich nutze selber die Fleury Lib in Verbundung mit Interrupts, es Funktioniert einwandfrei :-k

Gruß Sebastian

McJenso
13.04.2008, 17:50
Hallo,

kannst du diese Funktion mal posten. Es macht mich stutzig, dass der Rückgabewert in das erste Feld eines 8 Byte Array geschrieben wird.


Data[0] = TWIM_ReadNack ();





Die gemessene Frequenz wird von einem Atmega8 generiert.

Das ist gut, damit sollte das Signal immer ein definiertes Potential haben, nicht floaten und nicht prellen. Dennoch, hast du mal bei 'Drehzahl' 0 geprüft, ob die TWI Signale in die Leitung zum Interrupt strahlen und diesen auslösen?

Hast du NumberOverflowX nur aus Gewohnheit als 16Bit deklariert oder kann der Wert größer als 255 werden? Falls der Wert größer werden kann. möchte ich dir Sebastians erste Antwort noch einmal ans Herzen legen. Der 8Bit Controller kann die Variable nicht in einem Takt abarbeiten. Der Sprung in den Interrupt TIMER1_OVF_vect kann aber jederzeit eintreten, auch während des Übergangs von 00000000 11111111
nach 00000001 00000000 ;-)

Gruß

Jens

Michael-Hage
13.04.2008, 18:43
Ich konnte den Fehler jetzt etwas eingrenzen. Ich habe die I2C-Funktionen direkt hinter die Drehzahlberechnung gesetzt. Also direkt vor dem Update = False Flag. Jetzt tritt der Meßfehler nicht mehr auf!

Leider weiß ich jetzt genauso wenig wie vorher, warum das so ist. Der I2C-Teil kann da auch nicht bleiben, sondern muss ja wieder in die main-Schleife.

Derzeit wird nur ein byte in Data[0] geschrieben, geplant sind aber acht. Sämtliche andere Ursachen mit der Frequenzmessung kann ich eigentlich ausschließen, da es ja so Funktioniert. Irgendwas funkt dazwischen, sobald der I2C-Teil im main-Loop steht.

SprinterSB
19.04.2008, 08:49
Prinzipiell finde ich bei Deinem Aufbau die Warteschleifen problematisch -- sowohl die blockierenden TWI-Schleifen als auch die wait() in der Hauptschleife.

Also: entweder so umcoden, daß Dein Prog *ohne* Warteschleifen auskommt. Oder: die Berechnung der Drehzahl in die ISR verlagern. Falls nämlich wegen des Rumgammelns in den Wartern mehr als 1 Periode der Drehzahlmessung vergeht, hast du ein Problem.

Ein weiteres Problem bereitet der Überlauf von TCNT1, ich sehe nicht, wo der Fall EndTime-StartTime < 0 behandelt wird.

Hast du mal überlegt, den InputCapture von Timer1 zu verwenden? Für Zeitmessung passt das vielleicht besser.

Michael-Hage
20.04.2008, 21:43
Die 1ms-Delays musste ich einbauen, da sonst der I2C nicht richtig funktioniert hat. Wie macht man das ohne Wartezeit? Hat ein Interrupt nicht trotzdem Vorang?

Und wie führe ich die TWI-Funktionen nicht blockierend aus?

Den InputCapture wollte ich mal verwenden. Aber ich messe ja zwei Frequenzen. Das nützt ja nichts, wenn nur eine richtig gemessen wird.

SprinterSB
23.04.2008, 14:20
Vorrang hat er schon, aber dann müssen die Werte, die er verändert hat, auch ausgewertet werden, bevor er ein zweites Mal auftritt, sonst werden die Werte wieder überschrieben.

Fleurys Funktionen (zB i2c_readXXX) enthalten Warteschleifen wie


while(!(TWCR & (1<<TWINT)));

die eine Verzögerung verursachen, für die du keine Obergrenze angeben kannst (ein I2C-Sklave kann die Übertragung beliebig verzögern).

Eine nicht-blockierende I2C-Implementierung wäre daher IRQ-getrieben oder man müsste sich eine State-Machine bauen (wäre ne IRQ-getriebene im Endeffekt auch).

Ein sehr einfaches Beispiel einer nicht-blockierenden Hauptschleife ist auf meiner HP zkizziert. Dort werden 2 LED geblinkt. Eine im Takt von 1s und eine im Takt von 1.1s, was mit Warteschleifen eh net zu machen wäre.

http://www.gjlay.de/pub/c-code/countdown.html

In deinem Fall käme man ohne extra Timer aus. Die Hauptschleife läuft nichtblockierend über alle Aufgaben und falls an der Aufgabe erledigt werden muss, wird es erledigt. Beispiel: Daten über TWI senden.

Irgendwo in Programm (also innderhalb der Hauptschleife) schreibt was Daten in den Ausgabepuffer. Im TWI-Teil wird geschaut, ob Daten da sind. Wenn ja, wird das Datum gesendet, falls nicht schon eine Übertragung im Gange ist. Dazu wird ein Start geschickt, falls es nicht schon geschickt wurde, etc.

Man muss sich also Zusatände merken (daher der Name /State-Machine/). Wenn man nicht weiterkommt, weil eine Bedingung nicht erfüllt ist (zB weil Daten auf die man wartet noch nicht eingetroffen sind), macht man mit der nächsten Aufgabe (z.B Frequezauswertung) weiter und schaut beim nächsten Durchlauf der Hauptschleife, was geht.

Das ist natürlich aufwändiger zu programmieren als ein blockierender Ansatz, aber eben auch leistungsfähiger und Module bremsen sich net gegenseitig aus.

Michael-Hage
24.04.2008, 19:20
Vorrang hat er schon, aber dann müssen die Werte, die er verändert hat, auch ausgewertet werden, bevor er ein zweites Mal auftritt, sonst werden die Werte wieder überschrieben.

Was ich daran noch nicht verstehe: Die Messung, die nicht ausgewertet werden kann, wird überschrieben. Nagut, dann fehlt eine Messung. Aber die nächste Messung überschreibt die Werte und wird ausgewertet. Und dieses Ergebnis muss doch dann stimmen. Es können Messungen aus Zeitgründen verloren gehen. Aber wenn ausgewertet wird, dann stimmt das Ergebnis.

Ich verstehe deine Programmierung ohne Warteschleifen und habe das auch schonmal angewendet. Damit habe ich eine Animation auf einer 7-Segment Anzeige laufen lassen, während die Messungen liefen. Das funktionierte auch wie gewünscht. Nur weiß ich jetzt nicht so richtig, wie ich das auf die I2C Bibliothek anwende.

Als Beispiel nehme ich mal die TWIM_Stop Funktion:



void TWIM_Stop (void)
{

// Send stop condition

TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);

//Wait until stop condition is executed and bus released

while (TWCR & (1<<TWINT));
}


Ich müsste also natürlich das while entfernen und mit " if ( Zähler == 10 ) " dafür sorgen, dass z.B. alle 10ms diese TWINT-Bedingung überprüft wird. Da die Bedingung aber nicht zu jeder Zeit überprüft werden soll, sondern nur in bestimmten Situationen, muss ich noch eine Variable setzten, die den Zustand speichert und alle anderen Funktionen müssen durch die Zustandsvariable verriegelt sein?

Das würde dann so aussehen:



void TWIM_Stop (int Zähler)
{

// Send stop condition

if ( Zähler == 5 && Zustand == 6 )
{
TWCR = (1<<TWINT)|(1<<TWEN)|(1<<TWSTO);
Zustand++;
}

//Wait until stop condition is executed and bus released

if (Zähler == 10 && Zustand == 7 && (TWCR & (1<<TWINT) )
Zustand++;
}

SprinterSB
25.04.2008, 22:17
Ja, mit den Zuständen kommt das prinzipiell hin. Dar Code wird dadurch natürlich länglicher, und die Bib-Funktionen kann man kaum verwenden, sondern man muss sebst codieren.

Übrigens ist es besser sprechende Bezeichner zu verwenden als magische Zahlen wie 7, also zB


enum
{
ZUSTAND_1,
ZUSTAND_2,
...
ZUSTAND_N
}

// oder

#define STATE_A 0
#define STATE_B 1
#define STATE_C 2
...

if (STATE_A)
...
else if (STATE_B)
...

// oder
switch (state)
{
case ZUSTAND_1:
...
break;
case ZUSTAND_2:
...
}


Zu Deiner Messung: Du brauchst doch immer 2 Werte, die in eine Differenzbildung eingehen. Wenn einer davon nicht gemessen wird, dann wird die Differenz falsch. Oder ich steh aufm Schlauch...

Wenn die Berechnung wie gesagt in der ISR gemacht, wird dann kannst Du nix verpassen (ausser die zeitgerechte Verwendung der Drehzahl/Speed/was-auch-immer falls Du in ner TWI-Schleife gondelst, aber immerhin ist die Berechnung dann OK). Das würde Dir die Implementierung eigener TWI-Routinen sparen.

Michael-Hage
02.05.2008, 12:31
Warum sollte denn einer der Werte nicht gemessen werden? Die Interrupts haben ja Vorang. Also gehe ich sicher, dass beide Werte gemessen werden. Und erst dann wird die Berechnung freigeschaltet, während gleichzeitig die Interrupt-Routine keine neuen Werte mehr einlesen kann.

Ich bin weiter der Meinung, dass die Messung nicht gestört werden dürfte. Selbst wenn tausend waits() im Quelletext sind.

Ich versuche mich seit einiger Zeit an der nicht blockierenden I2C-Übertragung, aber das ist nicht so leicht wie gedacht.

SprinterSB
03.05.2008, 00:11
War unpräzise ausgedrückt von mir. Gemessen wird schon, aber eben die Werte u.u nicht abgeholt. ZB 1=1.Flanke, 2=2.Flanke, X=Wert nicht angeholt wegen Warteschleife, dann kann in der Hauptschleife doch folgendes passieren:

1XX2

Dann ist Deine Berechnung doch falsch oder ich blick absolut net as Du da tust ;-)

Jepp. Ein nicht-blockierender I²C ist einige Arbeit und auch net so schlank.

Michael-Hage
11.05.2008, 11:15
Wieso sollte die 2 nicht abgeholt werden? Sobald die Flanke kommt, wird die Interrupt-Routine garantiert ausgeführt. Egal, ob gerade ein wait etc. läuft. Der Interrupt hat ja Vorang! Die Zeit wird in der Interrupt-Routine in eine Variable gespeichert.

Trotzdem arbeite ich weiter am nicht-blockieredem I2C, der leider schon am Anfang nicht läuft. Hier mal der erste Block des Zustandsautomaten:

Funktionierende Version noch mit "while":


switch (I2C_Zustand)
{
case 0: //TWIM_START 1

TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);

while (!(TWCR & (1<<TWINT)));

I2C_Zustand = 1;

break;

case 1:

//der zweite I2C Zustand etc.

break:


Jetzt will ich ja das while vermeiden und habe mir folgendes überlegt, dass leider nicht funktioniert:



switch (I2C_Zustand)
{
case 0: //TWIM_START 1

TWCR = (1<<TWINT)|(1<<TWSTA)|(1<<TWEN);

if (!(TWCR & (1<<TWINT)));
I2C_Zustand = 1;

break;

case 1:

//der zweite I2C Zustand etc.

break:



Hier habe ich schon den Fehler, dass anscheinend die Bedingung immer wahr ist. Obwohl die Bedingung erst wahr sein dürfte, wenn der Empfang abgeschlossen ist.

SprinterSB
11.05.2008, 18:42
Und wo wirds vom Interrupt abgeholt wenn dein Hauptprog schleift?

TWI
-- da isn ; zu viel nachm if
-- START is nur ne Flanke, denkbar dass das ohne Verzögerung geht

Michael-Hage
23.05.2008, 08:55
Und wo wirds vom Interrupt abgeholt wenn dein Hauptprog schleift?

Wenn mein Hauptprogramm in einem wait hängt, hat der Interrupt doch Vorang? Also wird direkt in die Int0-Routine gesprungen, inder der Wert abgeholt wird und in einer Variablen gespeichert wird. Bei Ende der Int0-Routine geht es wieder zurück ins Hauptprogramm und das wait wird weiter ausgeführt.

Ich bin jetzt schon einige Zeit am Interrupt-TWI dran. Ich kann schon Zeichen übertragen, aber irgendwie ist das mehr probieren als verstehen. Irgendwie fehlen mir einige Informationen, die im Datenblatt nicht ausführlich genug sind. Hast du vielleicht ein Beispiel oder andere Informationen?