Forum: Mikrocontroller und Digitale Elektronik Drehgeber Artikel Frage


von Tester (Gast)


Lesenswert?

Hallo!

Habe eine kurze Frage zum Artikel: Drehgeber

http://www.mikrocontroller.net/articles/Drehgeber

Was wird in dieser folgenden Zeile genau gemacht:
1
  OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);   // 1ms

Ist in dieser init function enthalten
1
void encode_init( void )
2
{
3
  int8_t new;
4
 
5
  new = 0;
6
  if( PHASE_A )
7
    new = 3;
8
  if( PHASE_B )
9
    new ^= 1;                   // convert gray to binary
10
  last = new;                   // power on state
11
  enc_delta = 0;
12
  TCCR0 = 1<<WGM01^1<<CS01^1<<CS00;     // CTC, XTAL / 64
13
  OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);   // 1ms
14
  TIMSK |= 1<<OCIE0;
15
}

Danke euch!

Schöne Grüße

von Karl H. (kbuchegg)


Lesenswert?

Tester schrieb:

> Was wird in dieser folgenden Zeile genau gemacht:

Ein Wert berechnet

>
1
>   OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);   // 1ms
2
>

XTAL  ist die Taktfrequenz, also zb 4000000  für 4Mhz
64    korrespondiert mit dem Timer Prescaler von 64
1e-3  ist die wissenschaftliche Exponentenschreibweise von 0.001
      also einem Tausendstel. Da eine Millisekunde eine tausendstel
      Sekunde ist, könnte da ev. ein Zusammenhang bestehen?

Und die 0.5: die sind einfach nur Rundungskorrektur, wenn das floating 
Point Ergebnis wieder auf einen Integer gebracht wird.

Wenn ein Timer in 1 Sekunde von 0 bis XTAL/64 zählen kann, wie weit (x) 
muss er dann zählen, damit er genau nach 1 tausendstel Sekunde damit 
fertig ist?


     XTAL/64    .....      1
        x       .....      0.001
-------------------------------------

           XTAL/64 * 0.001
     x = ----------------------
                 1

von Tester (Gast)


Lesenswert?

Danke für die ultraschnelle Antwort!

Ich möchte aber einmal mein Problem schildern:

Wenn ich es so hier habe: (wie im Beispielcode)

  TCCR0 = 1<<WGM01^1<<CS01^1<<CS00;     // CTC, XTAL / 64
  OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);   // 1ms


und so abändere dass ich den Vorteiler auf 8 stelle aber auch bei der 
Rechnung die 8 anpasse dann müssten die Interrupts doch wieder in 
gleichen Zeitabständen auslösen:

  TCCR0 = 1<<WGM01^1<<CS01^0<<CS00;     // CTC, XTAL / 64
  OCR0 = (uint8_t)(XTAL / 8.0 * 1e-3 - 0.5);   // 1ms

Tun sie aber nicht!!??

Wieso benötige ich das (uint8_t)?

von Karl H. (kbuchegg)


Lesenswert?

Tester schrieb:

> und so abändere dass ich den Vorteiler auf 8 stelle aber auch bei der
> Rechnung die 8 anpasse dann müssten die Interrupts doch wieder in
> gleichen Zeitabständen auslösen:

Bei welcher Taktfrequenz?

Schon mal nachgerechnet, welcher Zahlenwert dann bei der Rechnung 
rauskommt?

Alles über 255 ist: game over. you loose

Auch sollte der Zahlenwert, der da rauskommt so sein, dass OCR0 * 8 
einen Wert von größer, sagen wir mal 200, ergibt. Ansonsten verbringt 
dein µC seine Zeit fast nur noch im Interrupt.

Und zu guter letzt:
Lass dich nicht von der 1 Millisekunde verrückt machen. Die spielt keine 
große Rolle, sondern ist einfach nur ein Kompromiss zwischen "es wird 
oft genug nachgesehen" und "aber auch nicht so oft, dass nur noch 
nachgesehen wird". Wenn deine ISR alle 2 Millisekunden oder alle 0.5 
Millisekunden aufgerufen wird, dann ist das auch gut.

von Tester (Gast)


Lesenswert?

Ok sorry! Habe hier was verwechselt.

Nur eines noch:

Im Beispielcode ist XTAL mit 8000000 angegeben.

           XTAL/64 * 0.001
     x = ----------------------
                 1

Durch den Teiler zählt er nun in 1s auf 125000.

--> In 1ms auf 125

Deshalb OCR0=125;

Müsste so sein oder?


Und die -0.5 wahrscheinlich damit wenn das Ergebnis nicht rund ist der 
Interrupt sicherheitshalber minimal früher kommt oder?
Richtig?


Was hat es noch genau mit dem (uint8_t) auf sich.
Wenn ich das (uint8_t) weglasse müsste es doch auch funktionieren??

Danke dir nochmals!

von Karl H. (kbuchegg)


Lesenswert?

Tester schrieb:

> Im Beispielcode ist XTAL mit 8000000 angegeben.
>
>            XTAL/64 * 0.001
>      x = ----------------------
>                  1
>
> Durch den Teiler zählt er nun in 1s auf 125000.
>
> --> In 1ms auf 125
>
> Deshalb OCR0=125;
>
> Müsste so sein oder?

Ja.

> Und die -0.5 wahrscheinlich damit wenn das Ergebnis nicht rund ist der
> Interrupt sicherheitshalber minimal früher kommt oder?
> Richtig?

Eher nicht.
Wenn bei der Division 256.3 rauskommt, dann retten die 0.5 die Sache 
noch, indem sie das Ergebnis in den erlaubten Bereich bis 255 drücken. 
Ich denke, das wird die Absicht dahinter sein.

Wie gesagt: Häng dich nicht an der 1 Millisekunde auf. Ob die exakt 
stimmt oder nicht, oder ob du dich eher im Bereich von 2 Millisekunden 
oder 0.5 Millisekunden bewegst, spielt überhaupt keine Rolle.

> Was hat es noch genau mit dem (uint8_t) auf sich.
> Wenn ich das (uint8_t) weglasse müsste es doch auch funktionieren??

probiers aus.
Manche Compiler warnen dann:
"Zuweisung einer Floating Point Zahl an einen Integer. Die Kommastellen 
gehen verloren, weil sie einfac abgeschnitten werden. Willst du das 
wirklich?"

Und die C Art zu sagen "Ja das will ich" besteht darin, den Cast selbst 
zu machen.

von Kluchscheißernder N. (kluchscheisser)


Lesenswert?

Wie isst man einen Elefanten? - In kleinen Stücken...

Wenn Dir die Formel zu abstrakt und unverständlich ist, dann zerlege die 
Aufgabe in so kleine Stücke, dass Du jeden Teil davon nachvollziehen 
kannst.

Du hast:
- eine Taktfrequenz,
- einen maximalen Timer-Zählumfang,
- verschiedene Timer-Vorteiler-Stufen.
- eine Wunschvorstellung für das Intervall zwischen den
  Interrupt-Aufrufen

Du suchst:
- den optimalen Timer-Vorteiler,
- den Compare-Wert, der bei dem gewählten Vorteiler das gewünschte
  Intervall ergibt.

Rein logisch, ohne jede unverstandene Formel, kann man erstmal schaun, 
wie groß das gewünschte Intervall ist, wenn man es micht in 
Millisekunden, sondern in CPU-Takten betrachtet. Bei 8 MHz CPU-Takt 
entspricht eine Millisekunde (bzw. 1 kHz) 8000 Takte.
Du musst also _alle 8000 CPU-Takte einen Interrupt auslösen._

Nun entscheide Dich für einen der verfügbaren Timer und schau Dir dessen 
maximalen Zählumfang an.

Ein 16-Bit-Timer kann bis 65535 zählen, 65536 entspricht 0. Damit 
könntest Du die 8000 Takte ohne Probleme abzählen, der Timer kann ohne 
Vorteiler arbeiten, also direkt die CPU-Takte zählen. Du setzt dazu den 
Compare-Wert auf 8000 und wählst (falls vorhanden) den CTC-Mode (Clear 
To Compare-match). Das macht natürlich nur Sinn, wenn der Timer keine 
anderen Aufgaben zu erledigen hat.

Willst Du den 1ms-Takt aber mit einem 8-Bit-Timer erzeugen, dann hast Du 
nur einen Zählumfang von 256 (0 bis 255, 256=0). Damit kannst Du erstmal 
keine 8000 Takte abzählen... Da war doch aber noch der Vorteiler, mit 
dem man den Timer langsamer laufen lassen kann. Um 8000 CPU-Takte zählen 
zu können, bräuchte man einen Vorteiler von 31,25 (8000 durch 256 = 
31,25). Den gibt es aber nicht, der nächste verfügbare Vorteiler ist 64. 
Teilt man die 8000 Takte durch den Vorteilerwert von 64, dann erhält man 
125, das bedeutet, dass der Timer, der ja nur bei jedem 64. CPU-Takt 
einen Schritt weiter zählt, _nach jeweils 125 Takten einen Interrupt 
auslösen soll_. Hierzu bietet sich (bei neueren AVRs) einer der 
Compare-Interrupts an, der oftmals auch mit CTC-Mode ausgestattet ist.

Die Formel macht übrigens nichts weiter, als all diese Zusammenhänge 
zusammenzufassen und den Compare-Wert vom Preprozessor ausrechnen zu 
lassen. Dies ist eine immense Arbeitserleichterung für den, der die 
Zusammenhänge mit links und 40 Fieber beherrscht.

Für den Neuling besteht allerdings bei unbekümmerter Benutzung der 
Formel die Gefahr, dass der (im Hintergrund) ermittelte Wert nicht mehr 
in den Wertebereich der Ergebnisvariable (hier Byte) passt und dadurch 
(oft unbemerkt) unsinnige Werte entstehen. Das könnte einerseits sein, 
dass der Wert zu groß wird und die oberen Bits abgeschnitten werden, 
andererseits kann der Wert zu klein werden, wodurch die Nachkommastellen 
abgeschnitten werden und der Wert ganz schön daneben liegt. Dazu kommt 
noch die Tatsache, dass nicht jeder Preprozessor mit Fließkommazahlen 
rechnet. Der Preprozessor des AVR-Studios (bei ASM-Programmierung) 
rechnet meines Wissens nach z.B. nur mit 32-Bit-Ganzzahlen (long 
integer), da würde die Formel wohl nicht das gewünschte Ergebnis 
liefern.

von ??? (Gast)


Lesenswert?

Hallo nochmals!

Leider schnall ich es noch nicht ganz bzw. mein Drehgebertest 
funktioniert nur so halb: Wenn ich meinen Motor drehe (der mit zwei 
Hallsensoren und Magnetscheibe ausgestattet ist) werden die Schritte in 
eine Richtung inkrementiert(wobei noch Schritte verloren gehen), in die 
andere Richtung werden die Schritte zwar dekrementiert allerdings um 
einen viel viel größeren Faktor. (unbrauchbar)

Die Umwandlung von gray nach binär ist mir klar.

   Gray-Code   Binär-Code
   00          00 (0)     o.k.
   01          01 (1)     o.k.
   11          10 (2)     ändern über XOR
   10          11 (3)     ändern über XOR

Dann wird nachgesehen ob es eine Änderung der Pegel an den Hallsensoren 
gegeben hat.
Die if-funktion wird nur erreicht wenn die Differenz 1 ist sprich kein 
Schritt verloren gegangen ist.


1
ISR( TIMER0_COMP_vect )             // 1ms for manual movement
2
{
3
  int8_t new, diff;
4
 
5
  new = 0;
6
  if( PHASE_A )
7
    new = 3;
8
  if( PHASE_B )
9
    new ^= 1;                   // convert gray to binary
10
  diff = last - new;                // difference last - new
11
  if( diff & 1 ){               // bit 0 = value (1)
12
    last = new;                 // store new as next last
13
    enc_delta += (diff & 2) - 1;        // bit 1 = direction (+/-)
14
  }
15
}

Könnt ihr versuchen mir diesen Teil:
1
  diff = last - new;                // difference last - new
2
  if( diff & 1 ){               // bit 0 = value (1)
3
    last = new;                 // store new as next last
4
    enc_delta += (diff & 2) - 1;        // bit 1 = direction (+/-)

auf eure Art zu erklären?


Bedeutet "read two step encoders" eigentlich schon, dass man diese 
Funktion nimmt wenn man zwei Hallsensoren an einer Magnetscheibe zur 
Auswertung hat, oder?

Was wird hier noch aus eurer Sicht gemacht?
1
int8_t encode_read2( void )         // read two step encoders
2
{
3
  int8_t val;
4
 
5
  cli();
6
  val = enc_delta;
7
  enc_delta = val & 1;
8
  sei();
9
  return val >> 1;
10
}

Danke euch schonmal!

von abcd (Gast)


Lesenswert?

Wie kann man es realisieren, dass in die eine Richtung val inkrementiert 
wird und in die andere val dekrementiert wird.

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.