PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : 2 Frequenzen Messen Atmega8-16Mhz, bis 267Hz und 53Hz



Michael-Hage
31.08.2007, 08:37
Ich wollte mit einem Atmega8 16Mhz zwei Sensoren einlesen. Und zwar ist das einmal ein Drehzahlsensor und ein Geschwindigkeitssensor. Beide geben 0V/5V raus pro Umdrehung. Die Drehzahl wird bis 267Hz gemessen und die Geschwindigkeit bis 53Hz.

16000 U/min ~ 267 Hz
270 km/h bei ca. 1,44m Radumfang ~ 53 Hz

Wenn beide Frequenzen gemessen worden sind, soll die Übersetzung errechnet werden und mit der bekannten Getriebeübersetzung verglichen werden. Dann sollen 1-6 Leds leuchten für jeden Gang.

Wie kann ich mit dem Atmega8 zwei Frequenzen gleichzeitig messen? Welchen Weg soll ich gehen:

1. Einen Timer laufen lassen und Flanken zählen.
2. Die Zeit zwischen zwei Flanken messen.
3. Zwei LM 2907 an zwei AD-Wandler schalten.

Welche Lösung ist genauer? Denn die Getriebeabstufung ist im 5. und 6. Gang ja nicht mehr so groß.

wkrug
31.08.2007, 13:51
Bei so niedrigen Frequenzen dürfte wohl die Methode 2 die genaueste und vor allem schnellste sein.
Und du erhältst bei jedem neien Impuls ein Ergebnis.
Bei Methode 1 üblicherweise nur jede Sekunde.
Du darfst halt nur die Teilerfaktoren für den verwendeten Timer bei Methode 2 nicht zu hoch einstellen.
Je höher der Teilerfaktor um so höher der Messfehler.
Allerdings produzieren kleinere Teilerfaktoren mehr Timer-Interrupts.

Ich hab das mal mit einem ATMEGA8 bei 8MHz und einem Teilerfaktor von 8 gemacht.

Erst bei ca. 15000 U/min war deine Methode 1 genauer (60 U/min Sprünge).

Michael-Hage
31.08.2007, 15:54
Um den Timer flankengesteuert zu starten und zu stoppen wird ja der ICP-Pin verwendet. Leider hat der Atmega8 nur einen davon. Wie starte ich dann gleichzeitig zwei Timer parallel?

wkrug
31.08.2007, 19:12
Den ICP Pin kannst Du ja schon mal verwenden.
Als zweiten Eingang nimmst Du einen Interrupt (INT 0).
In diesen Interrupts wird nur der eine !!! Timer ausgelesen und in einem Hilfsregister (RAM) abgelegt und ein Flag gesetzt, das es einen neuen Wert gibt.
Das Hauptprogramm fragt dann dieses Flag ab und berechnet die entsprechenden Drehzahlwerte.
Da die Anzahl der Takte bis zur Abfrage des Timerstandes immer gleich ist, spielt es auch keine Rolle ob der Counter bis dahin weitergelaufen ist (beim INT 0).
Nur wenn Du für den Timer Überlaufregister brauchst, weil dir der Zählumfang von 65536 nicht mehr reicht könnte es beim INT 0 zu Problemen kommen.
Bei ICP ist das natürlich kein Problem.

Ich hab meine Interruptroutine so aufgebaut, das in beiden Fällen nur der Zählerstand von TCNT1 abgefragt wird und dieser dann im RAM für die jeweilige Drehzahl abgelegt wird.
Dieser Programmteil ist bei mir in Assembler.
(Na gut meine Drehzahlmessung funktioniert aber auch bis 800.000 U/min fehlerfrei.)
Der vorherige aktuelle Wert wird in eine "_OLD" Variable umgespeichert, weil Du die ja für die Berechnung der jeweiligen Drehzahl brauchst.
Dann wird noch ein BIT Flag gesetzt (z.B. dr1_new=1; ), damit dein Hauptprogramm weiß, das es einen neuen Drehzahlwert für die Drehzahl 1 gibt und die Berechnung vornehmen kann.
Dann musst Du nur noch im Hauptprogramm das Verhältnis der beiden Drehzahlen feststellen und daraus den Gang ermitteln und die LED's zum leuchten bringen.
Probleme seh ich dann nur noch wenn ausgekuppelt ist ;-)

Michael-Hage
09.09.2007, 16:29
Ich habe das mal soweit versucht zu realisieren, wie ich konnte. Den Code habe ich angehängt. Leider habe ich noch einen schweren Fehler. Die Frequenz am INT0 stimmt (noch) nicht. Der Fehler liegt wohl in der Umrechnung. Am ICP1 stimmt die Messung. Dann habe ich noch das Problem, dass zwischendurch die Messung am INT0 einen Ausreißer hat.



//################################################## ################
//# MC: Atmega8 16Mhz #
//# Compiler: AVR-GCC 4.1.2 20061115 #
//# Version: 1.01 #
//# Ports: (TXD) PD1 (3); (ICP1) PB0 (14), (INT0) PD2 (4) #
//# Funktion: Frequenzzahler, Timer0 an INT0, Timer1 an ICP #
//################################################## ################

#define F_CPU 16000000

#include <avr/io.h>
#include <avr/interrupt.h>
#include <stdint.h>
#include <util/delay.h>
#include "rs232.c"


#ifndef TRUE
#define TRUE 1
#define FALSE 0
#endif

volatile unsigned char NumberOverflow0 = 0; // Anzahl Timer0 Overflows
volatile unsigned char NumberOverflow1 = 0; // Anzahl Timer1 Overflows
volatile unsigned int StartTime0 = 0; // TCNT0-Wert bei 1.High-Flanke speichern
volatile unsigned int StartTime1 = 0; // ICR1-Wert bei 1.High-Flanke speichern
volatile unsigned int EndTime0 = 0; // TCNT0-Wert bei 2.High-Flanke speichern
volatile unsigned int EndTime1 = 0; // ICR1-Wert bei 2.High-Flanke speichern
volatile unsigned char Update0; // Flag
volatile unsigned char Update1; // Flag

//----------------------------------------------------------------------------------
// Flankenauswertung Timer0 INT0
ISR(INT0_vect)
{
static unsigned char ErsteFlanke0 = TRUE;

if( Update0 && Update1)
return;

if( ErsteFlanke0 )
{
StartTime0 = TCNT0;
NumberOverflow0 = 0;
ErsteFlanke0 = FALSE; // Die naechste Flanke ist das Ende der Messung
}

else
{
EndTime0 = TCNT0;
Update0 = TRUE; // Eine vollstaendige Messung. Sie kann ausgewertet werden
ErsteFlanke0 = TRUE; // Bei der naechsten Flanke beginnt der naechste Messzyklus
}
}
//---------------------------------------------------------------------------------
// Flankenauswertung Timer1 ICP1
ISR( TIMER1_CAPT_vect )
{
static unsigned char ErsteFlanke1 = TRUE;

if( Update1 && Update0 )
return;

if( ErsteFlanke1 )
{
StartTime1 = ICR1;
NumberOverflow1 = 0;
ErsteFlanke1 = FALSE; // Die naechste Flanke ist das Ende der Messung
}

else
{
EndTime1 = ICR1;
Update1 = TRUE; // Eine vollstaendige Messung. Sie kann ausgewertet werden
ErsteFlanke1 = TRUE; // Bei der naechsten Flanke beginnt der naechste Messzyklus
}
}
//----------------------------------------------------------------------------------
// Timer0 Overflows zählen
ISR( TIMER0_OVF_vect )
{
NumberOverflow0++;
}
//----------------------------------------------------------------------------------
// Timer1 Overflows zählen
ISR( TIMER1_OVF_vect )
{
NumberOverflow1++;
}
//---------------------------------------------------------------------------
int main(void)
{
double Erg0 = 0.0, Erg1 = 0.0;
char Wert0[8], Wert1[8];

initrs232();


DDRB &= ~(1<<PB0); //PB0 als Eingang
PORTB |= (1<<PB0); //PB0 Pullup nach Vcc

DDRD &= ~(1<<PD2); //PD2 als Eingang
PORTD |= (1<<PD2); //PD2 Pullup nach Vcc

GICR |= (1<<INT0); //INT0 aktivieren
MCUCR |= (1<<ISC00) | (1<<ISC01); //pos.Flanke an INT0

//Timer0 8bit
TCCR0 |= (1<<CS00); // Kein Prescaling,
TIMSK |= (1<<TOIE0); //Overflow Interrupt aktiviert

//Timer1 16bit,
TCCR1B |= (1<<CS10) | (1<<ICES1); //Kein Prescaling
TIMSK |= (1<<TICIE1) | (1<<TOIE1); //pos. Flanke, Overflowinterrupt aktivieren

sei(); //globale Interrupts aktivieren

while(1)
{


if( Update0 && Update1) //Auf vollständige Messung prüfen
{

Erg0 = (NumberOverflow0 * 256) + EndTime0 - StartTime0;
Erg1 = (NumberOverflow1 * 65536) + EndTime1 - StartTime1;
Erg0 = F_CPU / Erg0;
Erg1 = F_CPU / Erg1;

dtostrf( Erg0, 5, 3, Wert0 );
dtostrf( Erg1, 5, 3, Wert1 );

uart_puts("Frequenz an INT0: ");
uart_puts( Wert0 );
uart_puts(" Hz ");

uart_puts("Frequenz an ICP: ");
uart_puts( Wert1 );
uart_puts(" Hz\n");

Update0 = FALSE;
Update1 = FALSE;

}
}
}

wkrug
09.09.2007, 19:01
Wo deine Aussreisser herkommen kann ich jetzt genau auch nicht sagen.
Ich vermute das es sich dabei um einen Überholeffekt handelt.
Der Timer0 Overflow Interrupt wurde ausgelöst kann aber den Überlaufzähler nicht inkrementieren, weil ein anderer Interrupt gerade läuft und das SEI Flag somit gelöscht ist.
Der TCNT0 ist dann auf 0 ohne den Überlaufzähler NumberOverflow0 zu inkrementieren.
Das Problem würde sich verbessern, wenn du für beide Routinen den Timer 1 verwenden würdest, da dieser 16 Bits hat und der Fehler dadurch wesentlich seltener auftritt.
Ausserdem brauchst Du bei deiner Methode immer 2 interrupt Zyklen um die Drehzahl zu bestimmen.
Wenn du, während der INT0 / ICP Interruptroutine, den Zählerstand der vorherigen Messung in den OLD Vektor schiebst und den neuen Zählerstand in den NEW Vektor einließt kannst Du bei jeder Flanke eine Drehzahlmessung vornehmen.
Das heisst es geht doppelt so schnell.

Ausserdem würde ich noch ein Timeout einbauen um den Leerlauf, sowie einen stehenden Motor zu detektieren.

z.B. 2 sek kein Geschwindigkeitssignal aber Drehzahlsignal vorhanden = Leerlauf.
2 sek gar kein Signal = Motor aus.

Ausserdem würd ich das Programm mal mit dem Simulator vom AVR Studio austesten - sollte mit AVR GCC gehen.

Ich benutz leider Codevision AVR und kann deshalb dein Prog nicht austesten.

Michael-Hage
09.09.2007, 21:13
Der ICP-Pin startet und stoppt ja den Timer1. Würde das dann nicht die zweite Messung stören, wenn plötzlich der Timer1 zurückgesetzt wird? Der erste Wert wird gespeichert und der zweite Wert ist dann vielleicht null. Ich habe es leider noch nicht ganz verstanden.

Die Sache mit der Leerlauferkennung etc. kommt später dazu. Erstmal bin ich schon froh, wenn beide Frequenzen richtig gemessen werden.

wkrug
10.09.2007, 20:20
Der ICP-Pin startet und stoppt ja den Timer1. Würde das dann nicht die zweite Messung stören, wenn plötzlich der Timer1 zurückgesetzt wird? Der erste Wert wird gespeichert und der zweite Wert ist dann vielleicht null. Ich habe es leider noch nicht ganz verstanden.
Ich kann mich natürlich auch irren, aber soweit ich das weiß ist das nicht so.
Der Zählerstand des TCNT1 wird lediglich in das ICP Register übertragen, wenn ein ICP Interrupt ausgelöst wird.
Dieses Register sollte aber von Dir nur während der ICP Interrupt Routine ausgelesen werden.
In deiner INT0 Routine solltest Du das TCNT1 Register auslesen und zur Berechnung heranziehen.
Was Du meinst ist vermutlich das CTC Bit.
Aber das brauchst Du ja bei der von mir vorgeschlagenen Meßmethode nicht zu verwenden.

Kannst das ja mal mit dem Simulator vom AVR Studio austesten.

Michael-Hage
13.09.2007, 16:22
So, jetzt habe ich mal AVR Studio installiert und das Programm untersucht. Es klappt mit dem Timer1! Damit es einheitlicher ist verwende ich jetzt den INT0 und INT1. Auf jeden Fall werden jetzt beide Frequenzen gleich gemessen und das mit einen Timer.

Endlich funktioniert es. ;-)

Michael-Hage
16.11.2007, 12:26
Das war leider ein Fall von "zu früh gefreut". Man kann jeweils eine Frequenz ausreichend genau messen, aber sobald beide anliegen, wird nur undefinierte Werte ausgegeben. Hat jemand eine Idee? Ich komme nicht mehr weiter...

Hubert.G
16.11.2007, 13:07
Wie wäre es mit abwechselnd messen.

wkrug
16.11.2007, 16:19
"zu früh gefreut". Man kann jeweils eine Frequenz ausreichend genau messen
Das kann ich fast nicht glauben.
Lässt Du den Timer frei laufen, oder setzt Du ihn (TCNT1) bei jedem INT Interrupt auf 0 ?
Wenn Du ihn auf 0 setzt kann es nie funktionieren, da der Timer beim esten der beiden auftretenden Interrupts auf 0 gesetzt wird. Der zweite Interrupt liest dann falsche Werte aus.

Lass den Timer frei laufen lies während der einzelnen INT Interrupts die aktuellen Zählerstände aus und zieh die jeweiligen vorhergehenden Zählerstände ab.

Das sollte funktionieren.

Wenn bei niedrigen Frequenzen die 65536 Stufen der Timers 1 nicht reichen kannst Du im Overflow Interrupt des Timers ein weiteres Register mit hochzählen lassen, das in die Berechnung mit eingeht.

Was dir jetzt immer noch passieren kann ist das was ich als Overtake Effekt bezeichne.
Der Timer steht kurz vor dem Überlauf, es wird ein INT 0 angelegt.
Während der Abarbeitung des INT 0 läuft der Zähler nach 0 über und würde einen Timer1 Overflow Interrupt auslösen. Da aber gerade ein Interrupt ausgeführt wird kann dieser Interrupt nicht ausgeführt werden.
Somit wird der Überlaufzähler nicht Upgedatet und es fehlen Dir bis zu 65536 Counts.
Bei Verwendung des ICP kann Dir das nicht passieren, weil der Zählerstand im ICP Register des Timers zwischengespeichert wird, meines Wissens ohne den Timer selber zu stoppen.
Da die Zeit vom Auftreten des ICP Interrupts bis zu Auslesen der Datei immer gleich lang dauert, dürften sich hier auch keine allzu großen Fehler einschleichen.

Die eigentliche Berechnung der Drehzahl sollte im Hauptprogramm stattfinden um die Interrupts nicht zu lange zu blockieren.

Feil noch mal ein wenig an deiner Software.