PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Frequenz-Auswertungssystem



Powell
24.09.2010, 01:16
Hallo zusammen!

Ich habe gerade folgendes Projekt im Bau, und würde mich freuen wenn wir hier mal ein bisschen über Möglichkeiten der Umsetzung sprechen können.

Um folgendes gehts:

Ein Frequenzsignal (kommt ursprünglich von einer Ottomotor-Zündanlage) wird durch ein aktives Filter geschickt, am Ende kommt ein schönes Rechtecksignal mit definierter Signalbreite und Variablem Abstand raus (die Zeit zwischen zwei Signalen ist also umgekehrt proportional zur Frequenz).

Diese Zeit wird nun mittels ATMEGA8 mit Bascom programmiert gemessen, und mit dem PULSEIN-Befehl ausgegeben (der dezimalwert entspricht der Zeit zwischen zwei Flanken in der Einheit [10µs])

Bis hierhin habe ich die Schaltung am Laufen. Ich wertete das ganze über ein LCD Display aus, welches 3x pro sekunde über die Timer ISR aktualisiert wird.

Nun fängt es an mit den Praxisproblemen:

Die Anwendung ist sehr Zeitkritisch. Wenn ich es laufen lasse, tauchen immer mal wieder "Lücken" auf, d.h. mir wird eine Frequenz von 0 angezeigt, obwohl das nicht stimmt. Wenn ich im Hauptprogramm eine Mittelung vornehme (die letzten 5 Pulsabstand-Werte werden addiert und durch 5 geteilt) wird es etwas besser, verschwindet jedoch noch nicht ganz.
Meine Vermutung: Es kommt ein Impuls in diesem Falle genau dann, wenn das Programm in der ISR ist, der Pulsabstand wird nicht aufgenommen. Wenn das öfters passiert (genauergesagt wenn 635,55ms kein Impuls registriert wird) gibt es einen Timeout (laut Bascom hilfe). Das Display zeigt mir eine 0 an.

Nun gilt es wohl das Programm zu optimieren, heißt den controller sich möglichst auf diese Pulsweitenmessung konzentrieren zu lassen.

Gleichzeitig möchte ich aber dass eine Ausgabe des Wertes erfolgt. Wie kann ich das also bestmöglich realisieren?

Eine Idee war, dass ich das Signal an einen zweiten Controller sende, der mir dann in aller ruhe als Anzeigetreiber dient. Nur in welcher form kann dieser wert gesendet werden? Als Analogwert und im anderen uC wieder ADC rückkonvertieren? Bestimmt nicht optimal. Oder als Frequenz und dann wieder rückkonvertierung? Stelle ich mir auch nicht so besonders geeignet vor. Auch dieses Senden muss ja möglichst wenig Takte beanspruchen.
Mit kommunikation zwischen zwei Controllern hab ich mich bisher noch gar nicht beschäftigt....

Sollte erst mal genug Input sein, hoffe ihr habt ein paar gute Vorschläge für mich.

Hier noch der Code:




'---------------------------------------------------------


$regfile = "m8def.dat"
$crystal = 16000000 'Quarz: 16,000 MHz


'---------------------------------------------------------
Config Pinc.0 = Input

Dim Pulsbreite As Long


'Config Adc = Single , Prescaler = Auto , Reference = Internal
'Start Adc

Dim A As Word
Dim B As Word
Dim C As Word
Dim D As Word
Dim E As Word
Dim Schnitt As Word
Dim N As Long

'===========================================

'++++++++FÜR LCD Verwendung:++++++++++++++++

'Config Lcdpin = Pin , E = Portc.3 , E2 = Portd.7 , Rs = Portc.2 , Db4 = Portd.2 , Db5 = Portd.3 , Db6 = Portd.4 , Db7 = Portd.5
'Config Lcd = 20 * 4a , Chipset = Ks077
'Config Lcdbus = 4

'Config Pind.6 = Output 'RW=0 (für LCD erforderlich)
'Portd.6 = 0

'Config Pind.7 = Output 'LCD Licht ein
'Portd.7 = 1



'Initlcd

'Cursor Off

'Cls

'------------------------------------------------------------

'+++++++ISR Config: Springt 3x pro sec in ISR um etwas auszuführen (z.B. LCD Betrieb)++++++++

'Config Timer1 = Timer , Prescale = 64 '16Mhz / 64 = 3 Hz Konfiguriere Timer0
'Enable Timer1 'schalte Den Timer0 Ein
'On Timer1 Isr_von_timer1 'verzweige Bei Timer0 überlauf Zu Isr_von_timer0
'Enable Interrupts

'============================================

Do

Pulsein Pulsbreite , Pinc , 0 , 1 'Pulsein Abfrage an PC0

'------ Umrechnungsfaktor auf U/min: ----------

If Pulsbreite > 400 Then 'Pulsbreite = 6.000.000 / Drehzahl
N = 6000000 / Pulsbreite 'PB(3000/min)=2000, (4000)=1500, (5000)=1200, (6000)=1000, (7000)=857, (8000)=750, (9000)=667, (10000)=600, (11000)=545, (12000)=500, (13000)=462, (14000)=429, (15000)=400, (16000)=375
End If

'-----------------------------------------------


E = D
D = C
C = B
B = A
A = N 'Bildung eines Mittelwerts der letzten 5 Zündungen

Schnitt = A + B
Schnitt = Schnitt + C
Schnitt = Schnitt + D
Schnitt = Schnitt + E

Schnitt = Schnitt / 5


If Schnitt < 6000 Then
Portd.7 = 1
Else
Portd.7 = 0
End If

Loop

Isr_von_timer1:


'ISR von Timer0
Timer1 = 0

Locate 1 , 3
Lcd " "
Locate 1 , 1
Lcd "c=" ; Schnitt

Return





End

wkrug
24.09.2010, 08:57
Wenn das mit Pulsein Probleme macht, schreib doch die Auswerteroutine selber.
Am Schönsten geht das mit dem Input Capture Interrupt des Timers 1.
Wenn da am ICP Pin ein Flankenwechsel ( programmierbar ) stattfindet wird der aktuelle TCNT Stand in das Input Capture Register geschrieben.
Den holst Du Dir dann in der Interruptroutine ab, ziehst den vorher ermittelten Wert ab und legst ihn in einem kleinen ARRAY ab.
Da Du für das Array ohnehin einen Pointer brauchst, kannst Du den gleich als Marker für neue Impulse verwenden.
Einlesezeiger != Auslesezeiger = neuer Impuls eingetroffen.

Aus dem Array kannst Du dann gleich deine Mittelwerte in der Hauptroutine rausholen.

Eine Sache musst Du dabei beachten, wenn sehr wenige Impulse kommen kann der Timer überlaufen - das musst Du programmtechnisch abfangen.

Powell
25.09.2010, 10:25
Danke für deinen Beitrag, das ist eine gute Idee!

Das Prinzip hab ich soweit verstanden, mir ist nur noch nicht klar wie du das mit den Arrays und zeigern meinst, und vor allem nicht, wie ich es in Code umsetzen kann (bin nicht so übermäßig Fit was den Programmierteil angeht). Vielleicht kannst du ja mal ein kleines Code Beispiel Posten.

Für den Timerüberlauf lasse ich mir dann was einfallen, das sollte lösbar sein.

Besserwessi
25.09.2010, 11:14
Wenn überhaupt etwas dabei schierig ist, dann die Behandlung des Timerüberlaufs. Die einfache Lösung ist es den Überlauf zu vermeiden solange der Motor läut. Dazu reicht schon ein passend gewählter Prescaler für den Timer. Den Stillstand zu erkennen ist nicht so schwierig wie eine vollständige Berücksichtign der Überläufe.

Die Benutzung von Array wird man wohl selber lernen müssen.

Hier soll da array wohl als zyklischer Puffer dienen. Die gemessenen Werte aus dem ICP Register werde der reihe nach gespeichert, und wenn man am Ende angekommen ist, wird wieder von vorne angefange und die dann ältesten Werte werden überschrieben. Dazu muß man sich dann den Index merken wo der letzte neue Wert steht und das Hauptprogramm muß sich merken (als Index) welcher Wert als letztes verarbeitet wurden.


Die Datenübertragung auf einen 2 ten µC kann man sich vermutlich sparen. Die eigentliche Messung über die ICP Funktion ist nicht wesentlich komplizierter als des Empfanden der Daten über eine Schnittstelle.

wkrug
25.09.2010, 12:18
So wie Besserwessi das schreibt, war das gedacht.

Ein kurzes "Pseudo Beispiel" in "C"


}

//Variablen definieren
volatile unsiged int ui_wertebuffer[5] //Das wird mal der Ringpuffer
volatile unsigned char uc_readin=0 //Einlesezeiger
volatile unsigned char uc_readout=0 //Auslesezeiger wird hier als Marker genutzt

//Einlesen der Werte
void einlesen(unsiged int ui_wert)
{
ui_wertebuffer[uc_readin]=ui_wert; //Aktuellen Wert einlesen
uc_readin++; //Pointer erhöhen
uc_readout++; //Wird hier nur als Marker genutzt
if (uc_readin>4){uc_readin=0;} //Pointer auf 0 zurücksetzen wenn Pufferende erreicht
}
/* Diese Subroutine kann man in die Input Capture Routine einbauen. oder von da aus aufrufen - Ich würd die paar Zeilen direkt in die ICP Interrupt Routine einbauen */

//Auslesen der Werte
unsigned int auslesen(void)
{
unsigned char uc_i=0; //Schleifenvariable
unsigned int ui_result=0; //Ergebnisvariable
unsigned long int ui_math; //Berechnungsvariable
if(uc_readout>0)
{
for(uc_i=0;uc_i<5;uc_i++) //Eine Schleife, die die 5 Werte ausliest
{
ul_math+=ui_wertebuffer[uc_i]; //Die 5 Werte werden addiert
}
ul_math/=5; //Das Ergebnis wird durch 5 geteilt
ui_result=ul_math; //Das Endergebnis wird in die unsigned int Variable ui_result übergeben
}
return(ui_result); //Das Endergebnis wird an die aufrufende Routine zurück gegeben
uc_readout=0;
/* Man könnte es auch so machen, diese Routine nur dann aufzurufen, wenn uc_readout>0 ist */
}

Powell
27.09.2010, 17:35
Hallo zusammen,

also erstmal hat mich das ein gutes Stück vorangebracht, vielen Dank.

Ich habe mal ein neues Programm geschrieben, und wollte das mit dem Bascom simulator mal testen bevor ich es auf den Controller lade, da ich hierfür auch noch die Hardware umbasteln muss (ICP Pin ist PB0 beim Atmega 8)

Im Simulator spring er mir allerdings nie in den Interrupt, jetzt weiß ich nicht obs am Simulator liegt, oder ob ich den Controller noch mal speziell Konfigurieren muss für diesen Betrieb.

Hier mal mein Code so weit, vielleicht fällt ja jemandem auf wenn noch was bestimmtes fehlen sollte:



'---------------------------------------------------------

$sim
$regfile = "m8def.dat"
$crystal = 16000000 'Quarz: 16,000 MHz


'---------------------------------------------------------


Dim Counterstand As Word 'Zählerstand des Counters
Dim Altercounterstand As Word 'Vorheriger Zählerstand
Dim Cntdifferenz As word 'Cnts die zwischen zwei interrupts vergangen sind

Config Pinb.0 = Input
Enable Interrupts

Config Timer1 = Counter , Edge = Falling , Prescale = 1024 , Noise Cancel = 1 , Capture Edge = Falling

'============================================

Do


On Capture1 Puls_kommt


Loop

Puls_kommt:

Altercounterstand = Counterstand
Counterstand = Capture1
Cntdifferenz = Counterstand - Altercounterstand
Print Cntdifferenz


Return

End

Richard
27.09.2010, 18:19
Im Simulator kann man oben in der Leiste die IRQ ein/aus schalten (glaube ich) ? Oder jedenfalls irgendwie festlegen...In einer IRQ nichts Zeitaufwändiges erledigen, nur Wert in eine Variable speichern. den dann als erstes in ein Arry schieben.
Danach auswerten.

Gruß Richard

Besserwessi
27.09.2010, 18:30
Den Print Befehl sollte man nicht in der ISR nutzen, außer vielleicht für einen erste Test. Der Print-befehlt braucht ausgesprochen viel Zeit, in der Regel zu viel für eine ISR.

Soweit ich weiss muß man den Interrupt noch extra einschalten, per Enable capture1 oder so ähnlich.

Powell
27.09.2010, 18:51
Ja das problem scheint irgendwo bei einem Start oder Aktivierbefehl eines timers zu liegen. Er zählt schlichtweg nicht und der Interrupt wird auch nicht ausgeführt. Habe sogar jetzt extra die Hardware angeschlossen aber identisch mit der Software. Irgendwo hängts.

Wie aktiviert man einen Timer und lässt ihn loslaufen? Mit Enable capture1 ebenfalls kein unterschied.

Powell
27.09.2010, 19:03
Habe den Fehler gefunden ^^:


Config Timer1 = Counter , Edge = Falling , Prescale = 1024 , Noise Cancel = 1 , Capture Edge = Falling

Niemals Edge = Falling und Capture Edge = Falling kombinieren (so wie komischerweise in der Hilfe), der counter wird genullt und abgefragt gleichzeitig, logisch warum ich eine 0 angezeigt bekomme.

mirco99
28.09.2010, 10:28
Benutze in der isr keinen Print. Immer so klein wie möglich halten, so wie es besserwessi auch schon geschrieben hat. Da habe ich mir schon viele Bugs mit gebaut.

chris155
28.09.2010, 16:15
Ich sehe in Deinem ersten Prog zwei Probleme:

- Du dimensionierst die Variablen als Word und Long. Der µC braucht sehr lange um die zu berechnen. Versuche mit Bytes auszukommen.

- Du legst die LCD Anzeige in die Timer ISR. Die braucht zu lange für eine ISR. Das ist auch gar nicht nötig. Lege die LCD in die Hauptschleife und die Pulsabfrage in die ISR.

Grüße

BoGe-Ro
01.10.2010, 13:33
Hallo,

ich hatte in einem Projekt auch mal ab und an das Phänomen, dass in einem Array, welches vom ADC gefüllt wird, Werte > 1023 waren.

Später hat sich herausgestellt, das dieser Fehler (und ein paar sporadische weitere) durch schlechtes Timing auftraten und dieses Timing wurde durch die LCD-Ausgabe "versaut"

hierbei stellte ich fest, dass der Bascom-Befehl für eine Ausgabe von 20 Zeichen ca. 23ms brauchte.
Da gerade beim programmieren und testen, mehr Debug-Ausgaben auch auf das Display gebracht wurden, produzierte ich die Fehler umso öfter.

Die Lösung für mich war dann:
- das Display im 8-Bit-Modus zu betreiben
- anstelle des lcd-Befehls eigene Routinen zu schreiben, welche die Ausgaben in einen 80 Zeichen-String legten (4*20Zeichen Display)
- Zeitlich gesteuert diesen 80-Zeichen-String an das Display zu senden.

im Gegensatz zu
20 Zeichen / 23ms erreichte ich
80 Zeichen / 3,3ms

damit waren meine Timingprobleme weg ;-)

Gruß BoGe-Ro

mirco99
01.10.2010, 13:47
Displayprobleme konnten es bei mir nicht sein, da

Ein Datensatz angefortert wird,
Ein Datensatz kommt,
Nachgeschaut, ob er vollständig ist
und dann per LCD ausgegeben.
150 us gewartet
erst dann wird ein neuer Datensatz angefordert.

Nach dem der 14 Byte Datensatz angekommen ist, passiert über die UART nichts mehr.

Das Display kommt ja später wieder weg, ist nur zu Testzwecken angeschlossen.

Trotzdem ein berechtigter Hinweis.