Forum: Mikrocontroller und Digitale Elektronik Drehzahlmessung


von Dieter P. (Gast)


Lesenswert?

Hallo!

Ich habe vor, an einem Uralt-Heimtrainer die Umdrehungszahl der Pedale 
zu ermitteln.

Die gemessene Umdrehungszahl soll über den seriellen Port ausgegeben 
werden.

Ich habe folgenden Code geschrieben:
1
#include <avr/io.h>
2
#define XTAL 1000000
3
4
5
6
7
void main (void)
8
{
9
  DDRA = 0;                   //PortA als Eingang
10
  TCCR0 |=  (1<<CS00) | (1<<CS02);       //Prescaler für Timer: 1024          
11
12
  UCSRB |= (1<<TXEN);                     //Senden aktivieren
13
  UCSRC |= (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1);  
14
15
  UBRRH = 00;                            //Baudrate einstellen 9600 bei 1 MHz
16
  UBRRL = 6;
17
18
19
20
21
22
  
23
  TCNT0 = 0;
24
  while(1)
25
  {
26
    
27
    if (PINA == 0xFE)
28
    {
29
      UDR = TCNT0;
30
      TCNT0 = 0;
31
    }
32
33
  }
34
}

Ich verwende einen Quarz mit 1 MHz-Takt. Nun habe ich noch da Problem, 
dass bei einem Prescaler von 1024 die "Zählgeschwindigkeit" des Timers 
bei etwa 1 kHz liegt. Liege ich da richtig?

Wie kann ich die Zeit im Timer (!) so herabsenken, dass sie noch 
langsamer zählt als 1024tel von 1 MHz?

Danke

von Dieter P. (Gast)


Lesenswert?

Der Grund dahinter ist übrigens der, dass ich zum Beispiel nicht noch 
zusätzlich ein Byte neben dem des Zählregisters (TCNT0) übertragen muss, 
um die Überläufe des Timers zu übertragen.

von Karl H. (kbuchegg)


Lesenswert?

Dieter P. wrote:

> Wie kann ich die Zeit im Timer (!) so herabsenken, dass sie noch
> langsamer zählt als 1024tel von 1 MHz?

Du könntest zb. den Prescaler zurück auf 1 stellen.
Dann installierst du einen Timer Overflow Interrupt
handler. Dort, in diesem Interrupt Handler, kannst du dann
runterteilen wie du lustig bist, indem du einfach die Anzahl
der Interrupts mitzählst und bei einer dir genehmen Anzahl
von Interrupts einen weiteren Zähler jeweils um 1 erhöhst.

von Dieter P. (Gast)


Lesenswert?

Hallo nochmal!

Vielen Dank für deine Antwort. Habe mich nun doch umentschieden, ich 
werde 3 Bytes senden.
 Byte-1: Momentanwert des Timerzählregisters
 Byte-2: Char, Trennzeichen zwischen Byte-1 und Byte2: 'x'
 Byte-3: Anzahl der Überläufe


Hier der bisherige Code.
1
#include <avr/io.h>
2
#define XTAL 8000000
3
4
5
6
7
void main (void)
8
{
9
  int Registerwert=0;  
10
  int Uberlauf=0;
11
12
  DDRA = 0;                     //PortA als Eingang
13
                   
14
15
  UCSRB |= (1<<TXEN);                       //Senden aktivieren
16
  UCSRC |= (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1);  
17
18
  UBRRH = 0;                                  //Baudrate einstellen 9600 bei 8 MHz
19
  UBRRL = 51;
20
21
22
23
24
25
  
26
  TCNT0 = 0;
27
28
    TCCR0 |=  (1<<CS00) | (1<<CS02);
29
  
30
  while(1)
31
  {
32
    if (PINA = 0xFE)
33
    {
34
      Registerwert = TCNT0;
35
    }  
36
    
37
    if (TIFR &(1<<TOV0))             //wenn Uberlauf stattfindet
38
      {
39
      Uberlauf++;
40
    }
41
  
42
    while (!(UCSRA & (1<<UDRE)));        //warten bis Senden moeglich 
43
       UDR = Registerwert;
44
    while (!(UCSRA & (1<<UDRE)));
45
      UDR = 'x';
46
    while (!(UCSRA & (1<<UDRE)));
47
      UDR = Uberlauf;
48
    
49
  }    
50
51
}

Würde das so funktionieren???

Danke

von Dieter P. (Gast)


Lesenswert?

Habe einen Fehler entdeckt: die     while(!(UCSRA & (1<<UDRE))); 
müssen in die erste IF-anweisung, sonst sendet der Controller ständig 
die Inhalte der Variablen  Uberlauf und Registerwert.
1
  while(1)
2
  {
3
    if (PINA = 0xFE)
4
    {
5
      Registerwert = TCNT0;
6
      //while (!(UCSRA & (1<<UDRE)));        //warten bis Senden moeglich 
7
       UDR = Registerwert;
8
      //while (!(UCSRA & (1<<UDRE)));
9
      UDR = 'x';
10
      //while (!(UCSRA & (1<<UDRE)));
11
      UDR = Uberlauf;
12
    }  
13
    
14
    if (TIFR &(1<<TOV0))             //wenn Uberlauf stattfindet
15
      {
16
      Uberlauf++;
17
    }
18
  
19
    
20
    
21
  }    
22
23
}

Ich habe nun mal diesen Code ins AVR Studio geladen und auf meinen AVR 
geflasht.

Ich habe nun das Problem, dass der Controller non-stop ohne eine Taste 
zu drücken irgendwelche Zeichen an die serielle Schnittstelle aussendet.
Er soll aber doch erst dann senden, wenn der Taster gedrückt wird?!

Woran könnte das liegen?

von STK500-Besitzer (Gast)


Lesenswert?

>Woran könnte das liegen?
>(PINA = 0xFE)

Das ist zumindest eine Zuweisung und kein Vergleich.
Und deren Ergebnis ist i.d.R. wahr...

von Dieter P. (Gast)


Lesenswert?

HAA :)

dann war meine Eingebung heute nacht im Schlaf um etwa 3 Uhr identisch 
mit meinem Fehler ;)


Vielen Dank!

von Dieter P. (Gast)


Lesenswert?

Also er sendet nun lauter "x" wenn ich die Taste länger drücke. Der 
Grund wird der sein, dass der Controller dann schneller schickt als der 
Timer hochzählt, und somit "zwischen" den X's kein Inhalt der 
Zählvariable und der Überläufe steht, da diese auf Null sind.

Jetzt bräuchte ich also noch eine Funktion, die nur ein einmaliges 
Senden der Messdaten zulässt.

Könntet ihr mir vielleicht helfen?
Stimmt denn der Rest von meinem Code?
1
#include <avr/io.h>
2
#define XTAL 8000000
3
4
5
6
7
void main (void)
8
{
9
  int Registerwert=0;  
10
  int Uberlauf=0;
11
12
  DDRA = 0;                     //PortA als Eingang
13
                   
14
15
  UCSRB |= (1<<TXEN);                       //Senden aktivieren
16
  UCSRC |= (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1);  
17
18
  UBRRH = 0;                                  //Baudrate einstellen: 9600 bei 8 MHz
19
  UBRRL = 51;
20
21
22
23
24
25
  
26
  
27
    TCNT0 = 0;
28
    TCCR0 |=  (1<<CS00) | (1<<CS02);        //Timer aktivieren
29
  
30
  while(1)
31
  {
32
    if (PINA == 0xFE)
33
    {
34
      Registerwert = TCNT0;              //wenn Taster gedrückt, schicke momentanen Zählerwert über UART   
35
       UDR = TCNT0;              
36
      
37
      UDR = 'x';                              //Schicke ein "Trennzeichen" (x)
38
      UDR = Uberlauf;              //Schicke dann die Anzahl der Überlaufe
39
      
40
      TCNT0 = 0;                //setze anschließend Zählregister zurück
41
      Registerwert = 0;            //setze Registerwert auf null
42
      Uberlauf = 0;              //setze Uberlauf auf null
43
    }  
44
    
45
    if (TIFR &(1<<TOV0))             //wenn Uberlauf stattfindet, inkrementiere Variable Uberlauf
46
      {
47
      Uberlauf++;
48
    }
49
  
50
    
51
    
52
  }    
53
54
}

Vielen Dank!

von Karl H. (kbuchegg)


Lesenswert?

>      Registerwert = TCNT0;
>       UDR = TCNT0;
>      UDR = 'x';
>      UDR = Uberlauf;

Schau dir bitte im Tutorial
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial#Senden_einzelner_Zeichen
an, wie man ein Zeichen wegschickt.
Die Zuweisung

    UDR = TCNT0;

wartet nicht darauf, dass das Zeichen auch tatsächlich
weggeschicjt wurde! Du musst schon vorher warten, ob das
Senderegister überhaupt aufnahmebereit für das nächste
Zeichen ist.


PS: Die Idee mit einem Trennzeichen finde ich nicht so
geschickt.
Einfacher für den Empfänger ist es, wenn er ein bestimmtes
Zeichen empfängt und dieses Zeichen den Beginn eines
Telegrams darstellt. Von diesem Zeichen ausgehend braucht
der Empfänger dann nur noch die empfangenen Bytes mitzählen
um zu wiessen welches Byte was darstellt.
Mit einem Identifikationszeichen mitten im Telegram geht
das bei weitem nicht so simpel. Von der erstmaligen Synchronisierung
des Empfängers mit dem Sender reden wir mal gar nicht.

von Dieter P. (Gast)


Lesenswert?

Hallo!

Danke für euere Antworten. Ich war dienstlich unterwegs, sorry dass ich 
erst jetzt schreibe.

ICh habe nun meinen Code noch einmal geringfügig überarbeitet.

Könntet ihr mal bitte schauen ob es so funktionieren wird?

Ich habe jetzt erstmal das X dringelassen zur Trennung der Bytes.
Ich werde aber deinen Rat befolgen und es im 2. Step rausnehmen.
1
#include <avr/io.h>
2
#define XTAL 8000000
3
4
5
SendChar(char input)
6
{
7
  while (!(UCSRA & (1<<UDRE)))  
8
    {
9
    }
10
11
  UDR = input;
12
}
13
14
15
SendTCNT0(void)
16
{
17
  while (!(UCSRA & (1<<UDRE)))  
18
    {
19
    }
20
  UDR = TCNT0;
21
}
22
23
24
25
26
void main (void)
27
{
28
  int Registerwert=0;  
29
  int Uberlauf=0;
30
31
  DDRA = 0;                     //PortA als Eingang
32
                   
33
34
  UCSRB |= (1<<TXEN);                       //Senden aktivieren
35
  UCSRC |= (1<<URSEL)|(1<<UCSZ0)|(1<<UCSZ1);  
36
37
  UBRRH = 0;                                  //Baudrate einstellen: 9600 bei 8 MHz
38
  UBRRL = 51;
39
40
41
42
43
44
  
45
  
46
    TCNT0 = 0;
47
    TCCR0 |=  (1<<CS00) | (1<<CS02);        //Timer aktivieren
48
  
49
  while(1)
50
  {
51
    if (PINA == 0xFE)
52
    {
53
      SendTCNT0();                        //wenn Taster gedrückt, schicke momentanen Zählerwert über UART                 
54
      SendChar('x');                              //Schicke ein "Trennzeichen" (x)
55
      SendChar(Uberlauf);              //Schicke dann die Anzahl der Überlaufe
56
      
57
      TCNT0 = 0;                //setze anschließend Zählregister zurück
58
      Registerwert = 0;            //setze Registerwert auf null
59
      Uberlauf = 0;              //setze Uberlauf auf null
60
    }  
61
    
62
    if (TIFR &(1<<TOV0))             //wenn Uberlauf stattfindet, inkrementiere Variable Uberlauf
63
      {
64
      Uberlauf++;
65
    }
66
  
67
    
68
    
69
  }    
70
71
}


Vielen DAnk!

von Karl H. (kbuchegg)


Lesenswert?

Dieter P. wrote:

> Könntet ihr mal bitte schauen ob es so funktionieren wird?

Hänge ein Terminalprogramm drann und sieh nach :-)


> SendChar(char input)
> {
>   while (!(UCSRA & (1<<UDRE)))
>     {
>     }
>
>   UDR = input;
> }
>
>
> SendTCNT0(void)
> {
>   while (!(UCSRA & (1<<UDRE)))
>     {
>     }
>   UDR = TCNT0;
> }

Aus welchem Grund erfindest du hier das Rad neu.
Du hast doch eine Funktion die einen char sendet.
Nutze sie (Dazu später noch mehr)

void SendTCNT0(void)
{
  SendChar( TCNT0 );
}

(PS: Es ist nicht egal was du als Returnwert einer Funktion
angibst. Wenn du nichts angibst, dann ist der Default 'Funktion
returniert einen int

   foo( void )
   {
   }

ist also gleichwertig zu

   int foo( void )
   {
   }

und das ist ein grundsätzlicher Fehler. Wenn eine Funktion
in ihrer Signatur verspricht, dass sie einen int returnieren wird,
dann sollte sie das auch wirklich tun!
)

>     if (PINA == 0xFE)
>     {
>       SendTCNT0();                        //wenn Taster gedrückt,
> schicke momentanen Zählerwert über UART
>       SendChar('x');                              //Schicke ein
> "Trennzeichen" (x)
>       SendChar(Uberlauf);              //Schicke dann die Anzahl der
> Überlaufe

Dir ist schon klar, dass du hier eine binäre Übertragung machst?
D.h. da werden keine lesbaren Zeichen übertragen, sondern die
Zahlenwerte so wie sie sind. Ein Zahlenwert von 65 würde dir
ein Terminal als 'A' anzeigen, weil der ASCII Code von 'A' nun
mal 65 ist.
Damit disqualifiziert sich dein Trennzeichen 'x' quasi von alleine,
weil du einen TCNT0 Wert von 120 nicht vom Zeichen 'x' unter-
scheiden kannst.

Wie gesagt, wenn dir das klar ist, dann ist das ok. Du musst dir
dann nur noch überlegen mit welcher Strategie der Empfänger die
einzelnen Bytes wieder auf die Reihe kriegt. Dein 'x' mittendrin
(oder sonst an einer anderen Stelle) könnte da unter Umständen nicht
wirklich hilfreich sein.

von Dieter P. (Gast)


Lesenswert?

Aber das 'x' sollte er (und das tut er auch) als das Zeichen-X senden. 
Bei den Werten ist klar, dass er anstelle des Werts zwischen 0 und 255 
das dafür entsprechende ASCII-Zeichen sendet, was aber kein Problem sein 
wird, da man in Visual Basic dann eine Funktion wie ASCIItoHex hat.

Ich habe noch das Problem, dass er solange ich diese Taste drücke, das 
Zeichen raussendet. Er soll aber egal wie lange ich auf der Taste bleibe 
(sprich auch wenn der Reed-Kontakt mal prellt) nur einmal das Zeichen 
rausschicken. Habe schon mit Schleifen, If-Anweisungen und Co. versucht, 
das Problem zu lösen, komme aber damit auf keinen grünen Zweig.

Wie könnte ich es machen?

Danke!

von Karl H. (kbuchegg)


Lesenswert?

Dieter P. wrote:
> Aber das 'x' sollte er (und das tut er auch) als das Zeichen-X senden.
> Bei den Werten ist klar, dass er anstelle des Werts zwischen 0 und 255
> das dafür entsprechende ASCII-Zeichen sendet, was aber kein Problem sein
> wird, da man in Visual Basic dann eine Funktion wie ASCIItoHex hat.

Dein Empfang sieht so aus

  89 120 120 01 119 120

Welches Byte ist nun was?
Bedenke: Du steigst mitten in die Übertragung ein. Das erste
Byte welches du empfängst ist nicht notwendigerweise das vom
TCNT0. Es könnte auch das 'x' oder die Anzahl der Überläufe sein.

Das ist so, wie wenn du auf einer Party zu einer Gruppe
hinzustösst. Die ersten Wortfetzen die du aufschnappst
können irgendwo mitten aus einem Satz sein.

> Ich habe noch das Problem, dass er solange ich diese Taste drücke, das
> Zeichen raussendet. Er soll aber egal wie lange ich auf der Taste bleibe
> (sprich auch wenn der Reed-Kontakt mal prellt) nur einmal das Zeichen
> rausschicken. Habe schon mit Schleifen, If-Anweisungen und Co. versucht,
> das Problem zu lösen, komme aber damit auf keinen grünen Zweig.
>
> Wie könnte ich es machen?

Indem du dir beim Erkennen eines Tastendrucks merkst, dass
die Sequenz bereits verschickt wurde.

Taste gedrückt?
   Ja:
      Daten bereits verschickt ?
         Ja:
            keine weitere Aktion
         Nein:
            verschicke Daten
            in einer Variablen merken, dass Daten bereits verschickt

   Nein:
      'Daten bereits verschickt'-Merker wieder zurücksetzen

von Dieter P. (Gast)


Lesenswert?

aber dann muss ich ja irgendwie vergleichen, ob die "neuen" Daten schon 
verschickt wurden oder?

von Karl H. (kbuchegg)


Lesenswert?

Dieter P. wrote:
> aber dann muss ich ja irgendwie vergleichen, ob die "neuen" Daten schon
> verschickt wurden oder?

Moment.
Was soll jetzt wann verschickt werden?
Ich bin davon ausgegangen, dass du beim Drücken
einer Taste die Daten verschickt haben möchtest. Egal
ob das jetzt neue, alte oder sonstige Daten sind.
Taste gedrückt -> 1 mal verschicken


Ja klar, musst du da vergleichen. Ich sagte doch, dass du
einen Merker (eine Variable) dafür abstellen musst.
Ach noch was: Das Prellen kriegst du so nicht unter Kontrolle,
da musst du schon noch eine Tastenentprellung vornehmen.

Hmm. Wenn du die Tastenentprellung ala PeDa nimmst, kannst
du dir den ganzen Aufwand mit dem 'schon verschickt' Merker
sparen. Da sorgen die Tastenfunktionen schon dafür, daß jeder
Tastendruck nur einmal ausgewertet wird.
http://www.mikrocontroller.net/articles/Entprellung#Komfortroutine_.28C_f.C3.BCr_AVR.29

von Dieter P. (Gast)


Lesenswert?

Ja genau, es soll der momentane Wert einfach verschickt werden, sobald 
die Taste gedrückt wird.

von Dieter P. (Gast)


Lesenswert?

Irgendwie kann ich meine Vorstellung nicht in Programmcode 
umsetzen...Bin wohl zu dumm dafür.

von Karl H. (kbuchegg)


Lesenswert?

Dieter P. wrote:
> Irgendwie kann ich meine Vorstellung nicht in Programmcode
> umsetzen...Bin wohl zu dumm dafür.

Das kommentiere ich mal nicht :-)

Das war der Pseudocode

> Taste gedrückt?
>   Ja:
>      Daten bereits verschickt ?
>         Ja:
>            keine weitere Aktion
>         Nein:
>            verschicke Daten
>            in einer Variablen merken, dass Daten bereits verschickt
>
>   Nein:
>      'Daten bereits verschickt'-Merker wieder zurücksetzen

Und so sieht das in C aus
1
uint8_t Geschickt;
2
3
  ....
4
5
  Geschickt = 0;
6
7
  while(1)
8
  {
9
    // Taste gedrückt?
10
    if( ( PINA & ( 1 << PA0 ) ) == 0 )
11
    {
12
       // Ja:
13
       // Daten bereits verschickt ?
14
       if( Geschickt ==  1 )
15
       {
16
         // Ja:
17
         // keine weitere Aktion
18
       }
19
20
       else
21
       {
22
         // Nein:
23
         // verschicke Daten
24
         SendTCNT0();                        //wenn Taster gedrückt, schicke momentanen Zählerwert über UART                 
25
         SendChar('x');                              //Schicke ein "Trennzeichen" (x)
26
         SendChar(Uberlauf);              //Schicke dann die Anzahl der Überlaufe
27
      
28
         TCNT0 = 0;                //setze anschließend Zählregister zurück
29
         Registerwert = 0;            //setze Registerwert auf null
30
         Uberlauf = 0;              //setze Uberlauf auf null
31
32
         // in einer Variablen merken, dass Daten bereits verschickt
33
         Geschickt = 1;
34
       }
35
    }
36
37
    else
38
    {
39
      // Nein: (keine Taste gedrückt)
40
      // 'Daten bereits verschickt'-Merker wieder zurücksetzen
41
      Geschickt = 0;
42
    }
43
  }

aber wie gesagt: Das hier geht von idealen Tasten aus, die nicht
prellen. In der Praxis wird das daher nicht so gut funktionieren.
Das Problem wird weniger das Drücken einer Taste sein, sondern
das loslassen. Mit etwas Pech erkennt der Code durch das Tasten-
prellen beim Loslassen der Taste einen erneuten Tastendruck,
der in Wirklichkeit nicht stattgefunden hat.

Entprellroutine benutzen und dann brauchst du auch diese Steuerung
mit der 'Geschickt' Variablen nicht mehr.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.