PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Zeit zweischen zwei Flanken (Drehzahlmesser)



Xaver
15.11.2006, 15:54
Hi

Ich möchte die Drehzahl von zwei Motoren (RB40) mit hilfe eines Mega16 ermitteln.
An jedem Motor ist eine Lichtschranke als Taktgeber befestigt, die mit
jeder Umdrehung des Motor 8 Flanken (vier schwarze vier weize Flachen) erzeugt.

Um daraus die Drehzahl zu ermitteln gibt es ja zwei Methoden.
Zum einen Zählt man die Flagen während einer Festen Zeitspanne und
zum anderen misst man die Zeit, die zwischen zwei Flanken vergeht.

Die erste Variante habe ich bereits erfolgreich ausprobiert. Timer1 Zählte die Flankenwechsel
und timer0 rief diesen Werte alle 500ms ab. Die Werte wurden dann Über UART asugegeben.

Da ich aber timer1 für die Ansteuerung der Motoren benötige(PWM) und timer0
eh für Zeitliche Aufgaben gedacht ist, wollte ich die Variante zwei einmal Programieren.
Dazu habe ich mir Vorgestellt, dass ich mit jeder Flanke auf eine Uhr(timer0) schaue und
mir die Zeit Merke. Bei der nächsten Flanke bilde ich die differenz aus der neuen und
der alten Uhrzeit und erhalte so die Zeit zwischen den Flanken. Wie genau ich das machen
möchte hängt von der Einstellung der Uhr ab. Der timer0 macht 256 Schritte bis er einmal
rum ist, dabei dauer jeder Schritt 5,8us (11,059Mhz, und Prescaler von 64).
Also ist die Uhr nach 1,48ms einmal rum. Natürlich kann es vorkommen das die Uhr bereits
einmal rum ist, eh die zweite Flanke kommt. Dafür habe ich mir einem 16bit Merker erstellt,
der die Umrundungen der Uhr zwischen zwei Flanken zählt. Da ich die Uhr nicht stoppe, kann
ich auch zwei drehzahlen Gleichzeitig erfassen(allerdings zwei Merker).
So weit so gut..

Nun habe ich mir die Werte mal alle 50ms über UART ausgeben lassen und bekomme Regelmäßig
keine richtigen Ergebnisse( siehe Abbildung).

Jetzt die Frage an euch: Woran könnte dies liegen und wie kann ich es besser machen.



Erklärung zur abbildung:
die X-Achse zeigt die Messdauer in Sekunden
die Y-Achse die Zeit zwischen zwei Impulsen in usec

der Motor drehte ohne Lasst mit 12V, also mit etwa 5000U/min
daraus sollte eine Umdreung 12ms dauern und die impulsdauer 1,5ms



/* Interrupt Service Rountine bei Überlauf des Timer0 */
ISR(TIMER0_OVF_vect) /* veraltet: SIGNAL(SIG_OVERFLOW1) */
{
/* Interrupt Code */
timer0_counter++; // Merker für Überläufe des Timers
timer0_counter_D1++; // Merker für Überläufe des Timers, für die Drehzahl1

if(timer0_counter % 3){ // alle 4,4ms
}else{
//tasten(); // Aufruf zur tasten entprellung
}

if(timer0_counter % 30){ // alle 44ms
}else {
auswerten(); // den Wert über UART ausgeben
}

if( (timer0_counter >= 255)){ // alle 380ms
PORTB ^=(1<<PB3); // eine LED getoggelt
}
}

/*Interrupt Service Routine bei Flanke an int0(extern) */
ISR(INT0_vect)
{
PORTB ^=(1<<PB2); // Toggel Led, wildes blinken ist immer gut
counter_neu = (timer0_counter_D1*256) + TCNT0; // Merke mir die Uhrzeit
timer0_counter_D1=0; // sezte den Umrundungsmerker zurück
if(counter_neu > counter_alt){
time_delta = counter_neu - counter_alt;} // ermittel die Zeitdifferenz
else{
time_delta = counter_alt - counter_neu;} // ermittel die Zeitdifferenz
counter_alt = counter_neu; // Merke mir die Uhrzeit für die nächste Messung
}

SprinterSB
15.11.2006, 16:12
time_delta kann bei dieser Rechnung negativ sein.

Welche Datentypen verwendest du? An so einem Schnippsel kann man nix sagen. Ich vermite mal 16 Bit. Greifst du atomar drauf zu?

chientech
15.11.2006, 16:16
Hi,
Kommt es eventuell zu einem Counteroverflow? Oh hast du schon bedacht.
Vielleicht ist das Signal "Unsauber" -> Oberwellen, Spikes

Xaver
15.11.2006, 16:37
also
8Bit
timer0_counter_D1
TCNT0
timer0_counter_D1 habe ich als volatile definiert, glaube es heißt das der Wert sofort geschrieben werden soll.
TCNT0 ist der Direkte TimerWert.

16Bit
counter_neu
counter_alt
time_delta

das mit dem negative Time_delta habe ich nun auch ausprobiert.
habe eine If-Bedingung eingefügt, die die differenz janachdem ob der alte oder neue Wert größer ist, bildet.


if(counter_neu > counter_alt){
time_delta = counter_neu - counter_alt;} // ermittel die Zeitdifferenz
else{
time_delta = counter_alt - counter_neu;} // ermittel die Zeitdifferenz

Xaver
15.11.2006, 17:36
Ihr hattet recht. Der Fehler lag an der falschen Berechnung der Zeitdifferens.
So ist es richtig.


if(counter_neu > counter_alt){
time_delta = counter_neu - counter_alt;} // ermittel die Zeitdifferenz
else{
time_delta = (65535-counter_alt) + counter_neu;} // ermittel die Zeitdifferenz


Damit andere auch etwas davon haben hier der gesammte Code für einen Mega16. Ich verwende das ATmega16 Testboard v2.0 von kreatives-chaos.com
main.c


/**
* Mit hilfe dieses Programmes soll die Drehzal
* über ein UART interfase am PC ansgegeben werden.
* Dazu wird die Drehzal wie folgt ermittelt:
*
* Zeit(Timer0) zwischen zwei Impulsen
*
*
* Hierzu wird bei einen Externen Interrupt(INT0),
* die bisher vergangene Zeit des Timers0 über Uart ausgegeben
*
**/

#include <avr/interrupt.h>
#include <stdint.h> // integerwerte wie uint8_t, uint16_t
#include <stdlib.h> // itoa
#include "timer0.h" // einbinden der Header datei für Timer0
#include "uart.h" // einbinden der Header datei für UART
//nicht vergessen die Passenden C Dateien in der Makefile zu schreiben

#define STATUS_LED1 PB3 // PIN der on-board Status-Led
#define STATUS_LED2 PB2 // Pin der on-board Status_Led


void init(void); // allgemeine Initialisierung
void auswerten(void); // Status Ausgabe über Uart

volatile uint16_t counter_alt; // alter Timerstand
volatile uint16_t counter_neu; // neuer Timerstand
volatile uint16_t time_delta; // Zeitdifferens

volatile uint16_t m1,m2,m3,m4,m5,m6,m7,m8,m9,m0; //Mittelwertbildung
volatile uint8_t sm;

int main(void){

init(); // Allgemeine Port initialisierung
timer0_init(); // Timer0 initialisieren
uart_init(); // UART initialisieren

sei(); //global Interrupts Aktivieren

uart_putc('*'); //senden eines einzelenen Zeichen
uart_puts(" UART inizialisierung ... OK \n\r\n\r"); //sendet einen string
/*uart_puts("* Drehzahl1\r\n* Mit hilfe dieses Programmes soll die Drehzal \r\n");
uart_puts("* ueber ein UART interfase am PC ansgegeben werden.\r\n* Hierfuer wird die Drehzal wie folgt ermittelt. \r\n");
uart_puts("* Zeit(Timer0) zwischen zwei Impulsen\r\n");
uart_puts("* Hierzu wird alle durch einen Externen Interrupt(INT0) \r\n");
uart_puts("* Die Bisher vergangene Zeit des Timers0 über Uart ausgegeben\r\n");
*/

while(1)
{ }
//never reached i hope ;)
}


void init(void)
{
/* STATUS_LED1 und 2 als Ausgang */
DDRB |= (1 << STATUS_LED1) | (1 << STATUS_LED2);
PORTB &= ~((1 << STATUS_LED1) | (1 << STATUS_LED2));

/* INT0 init */
DDRD &= ~(1<<PD2); //PORTD.2 als Eingang
PORTD |= (1<<PD2); //PORTD.2 mit Pullup
GICR |= (1<<INT0); // External Interrupt Request 0 Enable
MCUCR |= (1<<ISC01)|(1<<ISC01); // Die steigende Flanke an INT0 erzeugt einen Interrupt.

/* globale Variablen vorbelegen */
counter_alt = 0;
counter_neu = 0;
m1=m2=m3=m4=m5=m6=m7=m8=m9=m0=0;
}

void auswerten(void)
{
/* gibt Zählerstand über UART aus */
unsigned char s[7]; // max -65536 entspricht 7 Zeichen

uart_puts("Drehzahl: "); // Textausgabe
utoa(time_delta, s, 10); // Uint_16 in Asci-String wandeln
uart_puts(s); // Textausgabe
uart_puts(" \r"); // Textausgabe
}


// Interrupt Service Routine bei Flanke an int0(extern) */
ISR(INT0_vect)
{
PORTB ^=(1<<PB2); // Toggel Led, wildes blinken ist immer gut
counter_neu = (timer0_counter_D1*256) + TCNT0; // Merke mir die Uhrzeit
//timer0_counter_D1=0; // sezte den Umrundungsmerker zurück
if(counter_neu > counter_alt){
time_delta = counter_neu - counter_alt;} // ermittel die Zeitdifferenz
else{
time_delta = (65535-counter_alt) + counter_neu;} // ermittel die Zeitdifferenz

/* gleitender Mittelwert über 10 Messungen */
//m9=m8;m8=m7;m7=m6;m6=m5;m5=m4;m4=m3;m3=m2;m2=m1;m1 =m0;m0=time_delta;
//time_delta=(m1+m2+m3+m4+m5+m6+m7+m8+m9+m0)/10;

counter_alt = counter_neu; // Merke mir die Uhrzeit für die nächste Messung
}

timer0.c


#include <avr/io.h>
#include <avr/interrupt.h>
#include "timer0.h"

/* Timer0 initialisieren */
void timer0_init(void)
{
/* alle 1,48ms Überlauf.Einstellen der Frequenz auf 675Hz ( Prescaler = 64 * 256 bis Überlauf ) */
TCCR0 |= ((1<<CS01) | (1<<CS00));// Prescaler 64, entspricht 5,8usec und 1,48ms bis überlauf

/* Interrupts für Timer0 aktivieren */
TIMSK |= (1<<TOIE0);

DDRB |= (1<<PB3); // toggel led

timer0_counter_D1=0; // sezte den Anfangswert
timer0_counter=0; // sezte den Anfangswert

}

/* Interrupt Service Rountine bei Überlauf des Timer0 */
ISR(TIMER0_OVF_vect) /* veraltet: SIGNAL(SIG_OVERFLOW1) */
{
/* Interrupt Code */
timer0_counter++; // Merker für Überläufe des Timers
timer0_counter_D1++; // Merker für Überläufe des Timers, für die Drehzahl1

if(timer0_counter % 30){ // alle 44ms
}else {
auswerten(); // den Wert über UART ausgeben
}

if( (timer0_counter >= 255)){ // alle 380ms
PORTB ^=(1<<PB3); // eine LED getoggelt
}

}

SprinterSB
15.11.2006, 17:40
Wenn du in deiner Anwendung auf die 16-Bit Variablen zugreifst, dann ist das "volatile" nur die halbe Miete.

Lies mal
https://www.roboternetz.de/wissen/index.php/Fallstricke_bei_der_C-Programmierung#Nicht-atomarer_Code