Forum: Mikrocontroller und Digitale Elektronik LED mit Encoder ansteuern


von Lokus P. (derschatten)


Lesenswert?

Ich möchte gerne mit einem Drehencoder einzelne PIN's auf einem Port 
hochzählen. Wie bei einem LED-Kranz. Wobei immer nur eine LED leuchten 
soll.
Das sieht momentan so aus:
1
#include <avr/io.h>
2
#include <avr/interrupt.h>
3
#include <avr/eeprom.h>
4
#include <util/delay.h>
5
6
// CPU: ATmega644P-20PU 8MHz
7
// =======================================================================
8
9
#define ENCODER_PORT  PORTC
10
#define LEDS_PORT    PORTA
11
#define ENCODER_DDR    DDRC
12
#define LEDS_DDR    DDRA
13
#define ENCODER_PIN    PINC
14
#define LED_PIN      PINA
15
16
#define ENC1_A      (PINC & 1 << PC0)
17
#define ENC1_B      (PINC & 1 << PC1)
18
#define ENC1_TASTER    (1 << PC2)
19
20
volatile int8_t enc_delta1;
21
static int8_t last1;
22
23
void encode_init(void)
24
{
25
  int8_t new1;
26
27
  new1 = 0;
28
  if(ENC1_A)
29
    new1 = 3;
30
  if(ENC1_B)
31
    new1 ^= 1;
32
  last1 = new1;
33
  enc_delta1 = 0;
34
  TCCR0B = 1<<WGM01^1<<CS11^1<<CS10;
35
  OCR0A = (uint8_t)(F_CPU / 64.0 * 1e-3 - 0.5);
36
  TIMSK0 |= 1<<OCIE0A;
37
}
38
39
40
ISR(TIMER0_COMPA_vect)
41
{
42
  int8_t new1, diff1;
43
44
  new1 = 0;
45
  if(ENC1_A)
46
    new1 = 3;
47
  if(ENC1_B)
48
    new1 ^= 1;
49
  diff1 = last1 - new1;
50
  if(diff1 & 1)
51
  {
52
    last1 = new1;
53
    enc_delta1 += (diff1 & 2) - 1;
54
  }
55
}
56
57
int8_t encode_read1( void )
58
{
59
  int8_t val1;
60
61
  cli();
62
  val1 = enc_delta1;
63
  enc_delta1 = val1 & 1;
64
  sei();
65
  return val1 >> 1;
66
}
67
68
int main(void)
69
{
70
  int32_t val1 = 0;
71
72
  LEDS_DDR = 0xFF;
73
  encode_init();
74
  sei();
75
76
  for(;;)
77
  {
78
    val1 += encode_read1();
79
    LEDS_PORT ^= (1 << val1);
80
  }
81
}

Das hochzählen mit der Variable val1 funktioniert jedoch nicht. Kann mir 
jemand einen Tip geben wie es richtig sein muß?

Oder sollte ich mir besser eine Tabelle erstellen die die Portzustände 
definiert? Da ich vorhabe mit mehreren Encoder verschiedenen Pins 
anzusteuern.

So in etwa:
1
#define ENCODER1_1    0x01                // 0000 0001
2
#define ENCODER1_2    0x02                // 0000 0010
3
#define ENCODER1_3    0x04                // 0000 0100
4
#define ENCODER1_4    0x08                // 0000 1000
5
6
uint8_t ENCODER1[4]  = {ENCODER1_1, ENCODER1_2, ENCODER1_3, ENCODER1_4};
7
8
  for(;;)
9
  {
10
    val1 += encode_read1();
11
    LEDS_PORT = ENCODER1[val1];
12
  }

Das funktioniert aber leider auch nicht so ganz. Es leuchtet immer nur 
die erste LED in verschiedenen helligkeitsstufen beim drehen.

Es scheint so als würde der Encoder wirre Werte ausgeben.

Ich bin mir nicht sicher ob ich die richtigen Interrupts verwendet habe. 
Das ganze soll mit einem ATMEGA644 laufen.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Manfred W. schrieb:
> TCCR0B = 1<<WGM01^1<<CS11^1<<CS10;

Dieses Konstrukt sieht eigenartig aus. Schreib das doch mal bitte so:
1
TCCR0B = (1<<WGM01)|(1<<CS11)|(1<<CS10);
Allerdings sind m.E. da auch die Timer 1 und Timer 0 Dekarationen 
verwechselt. Das ist zwar nicht so wild, weil die bei Timer 1 und Timer 
0 zufällig übereinstimmen, aber so wäre es besser:
1
TCCR0B = (1<<CS01)|(1<<CS00);
PeDas Encoderroutine nutzt normalerweise den CTC Mode (Mode 2), also nur 
WGM01 setzen, welches sich in TCCR0A befindet. Dann nicht vergessen, das 
OCR0A Register auf was sinnvolles zu setzen und den Overflow Interrupt 
zu nutzen:
1
TCCR0A = (1<<WGM01);
2
OCR0A = OCR_SET; // Fuer meinen Encoder auf 1 ms 
3
TCCR0B = (1<<CS01)|(1<<CS00);
4
TIMSK0 = (1<<TOIE0);
5
// und dann als Interrupt
6
ISR(TIMER0_OVF_vect) {
Je nach Encoder nimmst du dann eine der drei Abfrage Routinen, single 
step, two step oder four step encoders. Da du evtl. nicht weisst, was 
für einen Encoder du hast, probierst dus mit single step und siehst dann 
ja gleich, ob dein Wert immer um 2 springt oder gar um 4 Werte.

von Lokus P. (derschatten)


Lesenswert?

Alles klar. Danke schon mal. Werde das am Abend testen.

Aber was passt an dem nicht?:
OCR0A = (uint8_t)(F_CPU / 64.0 * 1e-3 - 0.5);

nicht?

Die Encoderroutine selbst sollte mit single step passen für meinem Typ.
Habe ich zumindest immer so verwendet.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Manfred W. schrieb:
> Aber was passt an dem nicht?:
> OCR0A = (uint8_t)(F_CPU / 64.0 * 1e-3 - 0.5);

Oh, das passt schon - aber da kann es dir passieren, das du die 
FloatingPoint Library mit reinziehst wg. des '0.5'. Ich lass das 
normalerweise in einem #define als Konstante ausrechnen. Kannst ja beim 
Kompilieren mal vergleichen, wie gross der Code mit deiner Zeile 
aussieht im Vergleich mit einer vorher errechneten Konstante.
Ehrlich gesagt, kommt es bei einem Encoder auch nicht wirklich auf die 
0.5 an.

von Lokus P. (derschatten)


Lesenswert?

Wie muß diese Konstante denn aussehen?
Also 1s

von Karl H. (kbuchegg)


Lesenswert?

Matthias Sch. schrieb:
> Manfred W. schrieb:
>> Aber was passt an dem nicht?:
>> OCR0A = (uint8_t)(F_CPU / 64.0 * 1e-3 - 0.5);
>
> Oh, das passt schon - aber da kann es dir passieren, das du die
> FloatingPoint Library mit reinziehst wg. des '0.5'.

> Ich lass das
> normalerweise in einem #define als Konstante ausrechnen.


Ähm.
Dir ist aber schon klar, dass ein Makro einfach nur eine Textersetzung 
ist und rein gar nichts damit zu tun hat, ob der Compiler Code 
wegoptimiert oder nicht?

Ob du
1
  i = 5 * 8;

oder
1
#define RECHNUNG  5 * 8
2
3
   i = RECHNUNG;
schreibst, ist für den COmpiler ein und dasselbe, denn der eigentliche 
C-Compiler bekommt in beiden Fällen EXAKT dasselbe zu sehen, weil ihm 
der Präprozessor aus
1
   i = RECHNUNG;
durch die Textersetzung sowieso wieder das
1
  i = 5 * 8;
zum Frass vorwirft.

Den Optimizer auf -Os einstellen und gut ists, das wird schon 
wegoptimiert.


> aussieht im Vergleich mit einer vorher errechneten Konstante.
> Ehrlich gesagt, kommt es bei einem Encoder auch nicht wirklich auf die
> 0.5 an.

Da stimme ich allerdings zu. Dieses Beharren auf exakten Millisekunden 
bei Encodern oder auch Tastenentprellungen ist nicht wirklich notwendig. 
Wenn die Größenordnung so einigermassen stimmt, dann reicht das schon.

von Karl H. (kbuchegg)


Lesenswert?

> Das hochzählen mit der Variable val1 funktioniert jedoch nicht.
> Kann mir jemand einen Tip geben wie es richtig sein muß?

Was genau bedeutet 'funktioniert nicht'. Hast du mal unabhängig von 
deinem eigentlichen Problem überprüft, ob die Variable hoch/runter 
gezählt wird. Zb. in dem du dir den Zählerwert irgendwo ausgegeben hast.

Denn das hier
1
  for(;;)
2
  {
3
    val1 += encode_read1();
4
    LEDS_PORT ^= (1 << val1);
5
  }
ist Unsinn. Das führt zu einem enormen Geblinke an den LEDs, das mglw. 
so schnell ist, dass du es nicht mehr siehst. Aber abhängig davon, ob du 
den Encoder jetzt genau in dem Moment weiterdrehst, an dem die LED an 
oder aus ist, bleibt sie auch an oder aus und die nächste LED fängt wie 
wild zu blinken an.

Also: 2 Dinge voneinander trennen.
Das eine ist der Encoder
und das andere ist die Umsetzung, dass an deinen LED jeweils die 
zugeörige(n) LED(s) leuchten sollen.

Lass uns mit dem ersten anfangen. Funktioniert die Encoderauswertung? 
Lass dir einfach mal den val1 so wie er ist auf die 8 LED ausgeben.
1
  for(;;)
2
  {
3
    val1 += encode_read1();
4
    LEDS_PORT = val1;
5
  }

wenn du am Encoder drehst, müsstest du an den LED das Muster der 
Binärzahlen erkennen können. Je nachdem, wie deine LED angeschlossen 
sind leuchten sie bei 1 oder bei 0), mag es auch sinnvoll sein, das 
Bitmuster zu invertieren. Leuchtende LEDs lassen sich im Kopf meistens 
leichter den Binärzahlen zuordnen als invertierte (also dunkle) LEDs. 
Programmtechnisch ist das aber trivial umzudrehen.
1
  for(;;)
2
  {
3
    val1 += encode_read1();
4
    LEDS_PORT = ~val1;
5
  }

Denn das hier
> Es scheint so als würde der Encoder wirre Werte ausgeben.
musst du erst mal aus dem Status 'scheint so' in den Status 'ich weiß, 
dass ...' überführen, ehe du damit weiter arbeitest. Auf wilden Annahmen 
aufbauend programmiert es sich schlecht.

1
  int32_t val1 = 0;
hast du wirklich soviele LED, oder wäre da ein uint8_t (zumindest jetzt) 
nicht einfacher?

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:
> Wie muß diese Konstante denn aussehen?
> Also 1s

Wieso 1 Sekunde?
Du willst doch gar nicht, dass die ISR alle 1 Sekunde aufgerufen wird, 
sondern irgendwas in der Größenordnung von 1 Millisekunde. Daher ja auch 
das 1e-3 (also 0.001) in PeDas Berechnung.

Aber wie schon gesagt: Ob das jetzt 1ms sind, oder 0.8ms oder 2ms oder 
irgendwas dazwischen, oder meinetwegen auch 5ms spielt praktisch gesehen 
überhaupt keine Rolle, weil du den Encoder händisch sowieso nicht so 
schnell drehen kannst, dass er da was übersehen würde.

Und zur eigentlichen Berechnung
FAQ: Timer
man muss Formeln nicht auswendig können. Man kann sie sich mit ein wenig 
Nachdenken auch herleiten.

von Karl H. (kbuchegg)


Lesenswert?

Wie ist denn eigentlich dein Encoder angeschlossen?

So ein Encoder sind ja im Prinzip nichts anderes als 2 Tasten, die 
mechanisch miteinander gekoppelt sind. Aber aus Programmsicht sind das 
im Grunde nur 2 Tasten.
Ich seh aber in deinem Code weder das Schalten der Pins auf Eingang (ok, 
das ist Default und von daher nicht so schlimm), noch sehe ich irgendwo, 
dass du Pullup Widerstände eingeschaltet hättest. Von daher: wie ist der 
Encoder angeschlossen? Wenn du wegen fehlender Pullups offene Eingänge 
hast, dann würde das zb teilweise erklären, warum deine Tabellenlösung 
nicht funktioniert. Denn die, mit einer kleinen Modifikation, damit du 
nicht aus dem Array rausrtauscht, sollte eigentlich schon funktionieren
1
  for(;;)
2
  {
3
    val1 += encode_read1();
4
    LEDS_PORT = ENCODER1[val1 % 4];
5
  }

von Lokus P. (derschatten)


Lesenswert?

danke euch schon mal.
ich habe das Programm jetzt nicht vor mir, aber du hast recht. Das 
setzen der Ein/Ausgänge hatte ich tatsächlich vergessen. Das könnte auf 
jedenfall ein Grund sein.

Ich verwende das Encoder-Beispiel von Peter Dannegger in mehreren 
bereits funktionierenden Programmen, und daher nehme ich es immer wieder 
gerne als Grundausstattung her wenn ich mit Encoder arbeite. Ich hatte 
das ganze jedoch bis jetzt noch nicht auf einem ATMEGA644 angewendet.

Das mit der Formel hatte ich auch aus dem Beispiel und immer so 
belassen.
1s ist natürlich Blödsinn. Ich meinte 1ms :)

Das Beispiel die Werte auf den LED's auszugeben hatte ich gleich zu 
Beginn, bevor ich das Programm weiterentwickelt hatte ausprobiert. Nur 
mal um zu sehen ob sich da etwas tut. Und es ist tatsächlich so das die 
LED's wirkürlich leuchten. Aber manche auch hin und wieder etwas dunkler 
und heller.

Ich würde ja gerne den Wert der Variable sichbar machen. Nur wie? Ich 
könnte ein LCD anschließen und dort ausgeben, aber das finde ich etwas 
umständlich.

Gibt es eine Möglichkeit das ganze im AVRStudio zu simulieren?

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:
> danke
> Das Beispiel die Werte auf den LED's auszugeben hatte ich gleich zu
> Beginn, bevor ich das Programm weiterentwickelt hatte ausprobiert. Nur
> mal um zu sehen ob sich da etwas tut. Und es ist tatsächlich so das die
> LED's wirkürlich leuchten. Aber manche auch hin und wieder etwas dunkler
> und heller.


Ja. Bei offenen Eingängen kann da alles mögliche passieren. Zb auch, 
dass dein Programm die Einstreungen des nahen Staubsaugermotor mit 
Encoderdrehungen verwechselt und mit ein wenig Pech draufkommt, dass du 
den Encoder mit 348U/min (Wert geschätzt) drehst. UNd eine LED die 
derart schnell flackert, wirkt nun mal gedimmt.

> Ich würde ja gerne den Wert der Variable sichbar machen. Nur wie?

Du hast 8 LED an einem Port?
Wunderbar. Reicht doch für Werte von 0 bis 256. Kein Mensch sagt, dass 
du eine dezimale Anzeige brauchst. Binär tuts auch. Und mit 8 LED an 
einem Port ist das keine Hexerei. Code dafür hab ich dir ja schon 
gegeben.

> Ich
> könnte ein LCD anschließen und dort ausgeben, aber das finde ich etwas
> umständlich.

Tja.
Was ist dir lieber?
2 Tage Fehlersuche oder 1/2 Stunde LCD anschliessen?

>
> Gibt es eine Möglichkeit das ganze im AVRStudio zu simulieren?

Schliess jetzt erst mal deinen Encoder korrekt an - Pullups oder 
Pulldowns, je nachdem.
Mit offenen Eingängen kann man prinzipiell nicht vernünftig arbeiten. 
Solange der Teil nicht erfüllt ist, ist alles andere Kaffeesatzleserei.

von Lokus P. (derschatten)


Angehängte Dateien:

Lesenswert?

Ich habe jetzt das ganze Programm nach den Vorgaben hier umgemodelt.
Das Ergebnis ist jedoch, dass sich jetzt gar nichts mehr tut beim drehen 
des Encoders.

Ok, das gar nichts angezeigt wurde, lag jetzt mal daran:

int8_t val1 = 0;

mit

int16_t val1 = 0;

funktioniert es wieder.

Ich habe vor 13 LED's und 5 Encoder zu verwenden.
Also pro EEncoder ca. 3 LED's ansteuern.

Zumindest bekomme ich mit einer fixen Zuweisung:

zb. LEDS_PORT = ENCODER1_3;
die richtige LED angezeigt.

Allerdings bewirkt der Encoder nichts.

Ich vermute ja schwer das die Interrupts falsch angesteuert werden.

von Lokus P. (derschatten)


Lesenswert?

noch eine Ergänzung:
1
  for(;;)
2
  {
3
    val1 += encode_read1();
4
    LEDS_PORT = ENCODER1[val1 % 4];
5
  }

zeigt mir die erste LED.
Der Encoder tut nicht encoden.

von Lokus P. (derschatten)


Lesenswert?

Also, das hat mir jetzt keine Ruhe gelassen :)
Aber es funktioniert jetzt!

Ausschlaggebend war wie ich vermutet habe die Interrupt-Routine.

Und zwar muss das ganze so aussehen:
1
void encode_init(void)
2
{
3
  int8_t new1;
4
5
  new1 = 0;
6
  if(ENC1_A)
7
    new1 = 3;
8
  if(ENC1_B)
9
    new1 ^= 1;
10
  last1 = new1;
11
  enc_delta1 = 0;
12
13
  TCCR0A = 1<<WGM01;
14
  TCCR0B = 1<<CS01^1<<CS00;
15
  OCR0A = (uint8_t)(F_CPU / 64.0 * 1e-3 - 0.5);
16
  TIMSK0 |= 1<<OCIE0A;
17
}

und
1
ISR(TIMER0_COMPA_vect)
2
{
3
  int8_t new1, diff1;
4
5
  new1 = 0;
6
  if(ENC1_A)
7
    new1 = 3;
8
  if(ENC1_B)
9
    new1 ^= 1;
10
  diff1 = last1 - new1;
11
  if(diff1 & 1)
12
  {
13
    last1 = new1;
14
    enc_delta1 += (diff1 & 2) - 1;
15
  }
16
}

Was mir jetzt noch fehlt ist das der Zähler nur von 1 bis 4 zb. zählt.

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:
> Also, das hat mir jetzt keine Ruhe gelassen :)
> Aber es funktioniert jetzt!
>
> Ausschlaggebend war wie ich vermutet habe die Interrupt-Routine.

Ah logisch
(ISt es nicht schön, das man hinterher immer alle Probleme sofort sieht)
1
  TCCR0A = (1<<WGM01);
2
  OCR0A = (uint8_t)(F_CPU / 64.0 * 1e-3 - 0.5);
3
  TCCR0B = (1<<CS01)|(1<<CS00);
4
  TIMSK0 = (1<<TOIE0);

Wenn du CTC Modus hast und den Timer nicht bis zum Endwert durchlaufen 
lässt, dann wirds auch nichts mit einem Overflow-Interrupt. Denn der 
Overflow wird logischerweise ja nie erreicht. Denn ... der Timer 
'overlowed' ja nicht, weil ihn der CTC vorher auf 0 zurücksetzt.

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:

> Was mir jetzt noch fehlt ist das der Zähler nur von 1 bis 4 zb. zählt.

Ist ja kein Problem
1
    val1 += encode_read1();
2
3
    while( val1 > 3 )
4
      val1 -= 4;
5
6
    while( val1 < 0 )
7
      val1 += 4;

val1 muss natürlich ein signed int Typ sein. Einer mit 8 Bit, also ein 
int8_t tuts. Du wirst es kaum schaffen in der kurzen Zeit mehr als 128 
Encoder-Ticks auflaufen zu lassen.

von Lokus P. (derschatten)


Lesenswert?

Das funkt nicht:
1
TIMSK0 = (1<<TOIE0);

Aber das hier: :)
1
TIMSK0 = (1<<OCIE0A);

Das ganze geht jetzt noch etwas weiter (wäre ja zu einfach wenn nicht :)
Der Encoder hat natürlich auch noch einen Taster der genutzt werden 
soll.

Bisher bin ich mit dem Codebeispiel ebenfalls von hier -> 
http://www.mikrocontroller.net/articles/Entprellung
Tastendruck kurz/lang gut gefahren.

Ich habe nun versucht das auch hier zu implementieren.
Anscheinend scheitert es aber auch hier wieder an den Interrupts:

Das Beispiel nutzt ebenfalls TCCR0 (in meinem Falle TCCR0B) den ich 
allerdings schon für den Encoder verwende. Ebenso wird TIMSK0 genutzt.
Jetzt stellt sich für mich die Frage, wie ich das umsetze.

Wobei ich überlege gerade ob ich eine doppelte Tastenbelegung überhaupt 
benötige. Eventuell reicht eine einfache Entprellung.

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:
> Das funkt nicht:
>
1
> TIMSK0 = (1<<TOIE0);
2
>
>
> Aber das hier: :)
>
1
> TIMSK0 = (1<<OCIE0A);
2
>

Jaaa. Sag ich doch!
Im CTC Modus kriegst du keinen Overflow Interrupt mehr, weil es zu 
keinem Overflow mehr kommt.
Also ist es reichlich sinnlos, den Overflow Interrupt freizuschalten.

Entweder CTC Modus (also WGM01 gesetzt) + Compare Match Interrupt
Oder Normaler Modus + Overflow Interrupt

Aber beides mischen geht naturgemäss schief.



> Ich habe nun versucht das auch hier zu implementieren.
> Anscheinend scheitert es aber auch hier wieder an den Interrupts:
>
> Das Beispiel nutzt ebenfalls TCCR0 (in meinem Falle TCCR0B) den ich
> allerdings schon für den Encoder verwende. Ebenso wird TIMSK0 genutzt.
> Jetzt stellt sich für mich die Frage, wie ich das umsetze.

Du kopierst den Inhalt der Entprell-ISR in deine bestehende ISR mit 
hinein (natürlich ohne die OCR0A berechnung, die Timer Manipulation vom 
Entprellcode lässt du einfach weg)

Der Rest des Entprell-Systems bleibt so, wie er ist.

Sagt ja keiner, dass man in 1 ISR auch nur 1 Aufgabe erledigen darf. Man 
darf durchaus in ein und derselben ISR sowohl Encoder auswerten als auch 
Tasten entprellen.

Nochmal: Das ist alles nicht wirklich zeitkritisch. Die Interrupts 
müssen natürlich kommen, das ist schon klar. Aber das hast du ja jetzt 
sicher gestellt.

von Lokus P. (derschatten)


Angehängte Dateien:

Lesenswert?

Kann ich das ganze nicht in den TIMER0_OVF_vect schmeißen?

Es klappt nicht.
Ich hänge mal den Code an.

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:
> Kann ich das ganze nicht in den TIMER0_OVF_vect schmeißen?

Du kannst alles in EINEN Interrupt werfen. Wie schon gesagt
entweder CTC Modus (WGM01 gesetzt) und Compare Match
Oder normaler Modus und Overflow


Wo liegt denn bitte das Problem, den Code von 2 ISR-Funktionen in eine 
einzige zusammenzukopieren? Copy&Paste Programmierung hat ja doch noch 
so ziemlich jeder irgendwie hingekriegt.

von Karl H. (kbuchegg)


Lesenswert?

1
ISR (TIMER0_COMPA_vect)
2
{
3
  int8_t new1, diff1;
4
5
  new1 = 0;
6
  if(ENCODER1_A)
7
    new1 = 3;
8
  if(ENCODER1_B)
9
    new1 ^= 1;
10
  diff1 = last1 - new1;
11
  if(diff1 & 1)
12
  {
13
    last1 = new1;
14
    enc_delta1 += (diff1 & 2) - 1;
15
  }
16
17
  static uint8_t ct0, ct1, rpt;
18
  uint8_t i;
19
20
  TCNT0 = (uint8_t)(int16_t)-(F_CPU / 1024 * 10e-3 + 0.5);

und ich sach auch noch:
kopier dir alles aus dem ISR Code für die Entprellung aber lass den 
Timer dabei in Ruhe.
Weg mit der Manipulation des Timer-Zählregisters (TCNT0)

von Karl H. (kbuchegg)


Lesenswert?

1
ISR (TIMER0_OVF_vect)
2
{
3
  ....
4
}

Manfred.
Wie oft denn noch.

Wenn du den Timer im CTC Modus betreibst und das tust Du wegen dem hier
1
  TCCR0A = (1<<WGM01);

dann kommt da kein Overflow. Nicht mal wenn du auf dem Boden aufstampfst 
und 'Ich will aber' rufst.

Der Timer zählt bis zum Wert im OCR0A Register, Hausnummer: 42, also 38, 
39, 40, 41 und dann kommt der Compare Match / CTC zum Zug, setzt den 
Timer wieder auf 0 und führt die Compare Match ISR aus. Das ist aber 
KEIN Overflow. Ein Overflow wäre es, wenn der Timer an seine Grenze 
stösst: 253, 254, 255, 0, 1, .... das wäre ein Overflow. Der findet aber 
nie statt, weil durch den CTC Modus der Timer schon viel früher wieder 
auf 0 gesetzt wird.

FAQ: Timer

von Lokus P. (derschatten)


Lesenswert?

Ich glaub heut wird das nix mehr :)
Ich schau mir das mit den Timern Morgen nochmal an.

Den ISR (TIMER0_OVF_vect) hab ich jetzt gestanzt und die Entprellung 
(ohne TCNT0) in den (TIMER0_COMPA_vect) geschmissen.
1
ISR (TIMER0_COMPA_vect)
2
{
3
  int8_t new1, diff1;
4
5
  new1 = 0;
6
  if(ENCODER1_A)
7
    new1 = 3;
8
  if(ENCODER1_B)
9
    new1 ^= 1;
10
  diff1 = last1 - new1;
11
  if(diff1 & 1)
12
  {
13
    last1 = new1;
14
    enc_delta1 += (diff1 & 2) - 1;
15
  }
16
17
  static uint8_t ct0, ct1, rpt;
18
  uint8_t i;
19
20
  i = key_state ^ ~TASTER_PIN;
21
  ct0 = ~(ct0 & i);
22
  ct1 = ct0 ^ (ct1 & i);
23
  i &= ct0 & ct1;
24
  key_state ^= i;
25
  key_press |= key_state & i;
26
27
  if((key_state & REPEAT_MASK) == 0)
28
    rpt = REPEAT_START;
29
  if(--rpt == 0)
30
  {
31
    rpt = REPEAT_NEXT;
32
    key_rpt |= key_state & REPEAT_MASK;
33
  }
34
}

Aber irgendwie will das trotzdem noch nicht.

Das mit den
TCCR0A, OCR0A, TCCR0B, etc. überfordert mich etwas. Das muß ich mir noch 
genauer ansehen. Ist ja doch tiefe Materie.

von Karl H. (kbuchegg)


Lesenswert?

>
>
1
>         if(get_key_short(TASTER1))
2
>

mach da mal einen get_key_press draus.

Die Zeitparameter der Entprellroutinen sind noch nicht an die geänderten 
ISR-Aufruffrequenzen angepasst, weswegend die Short/Long/Repeat Zeiten 
noch viel zu kurz sein werden.

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Wenn du den Timer im CTC Modus betreibst und das tust Du wegen dem hier
> TCCR0A = (1<<WGM01);
>
> dann kommt da kein Overflow. Nicht mal wenn du auf dem Boden aufstampfst
> und 'Ich will aber' rufst.

Ja, sorry, mein Fehler. Ich habe mit dem Mega644 noch nichts gemacht und 
meine PeDa-Encoder Testsoftware läuft auf dem Tiny2313. Dieser löst beim 
CTC Event den Overflow Interrupt aus und nicht den COMPA, es scheint, 
das die MC sich da ein wenig unterscheiden.
Wie immer ist das Datenblatt da die richtige Anlaufstelle.

von Lokus P. (derschatten)


Lesenswert?

Ist der CTC Modus jetzt der richtige oder nicht?

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

CTC ist richtig (zumindest wird er in PeDas Routinen benutzt), weil du 
da schön die 1ms einstellen kannst. Im Prinzip geht jeder Modus, der dir 
nach der Millisekunde einen Interrupt feuert. Karl-Heinz sagte ja auch 
schon, das es nicht so auf die Genauigkeit der Zeit ankommt, bei 
schlechten Encodern (stark prellenden) ist eine Einstellmöglichkeit aber 
schon praktisch, so hier erlebt mit z.B. alten Encodern aus Autoradios.

von Lokus P. (derschatten)


Lesenswert?

hm, ok. Dann versteh ich aber den Aufschrei nicht.

oder gings dabei nur darum weil die Routine im falschen Timer lag 
(TIMER0_OVF_vect) ?

von Lokus P. (derschatten)


Lesenswert?

Ist das jetzt hier richtig oder nicht:
1
  TCCR0A = (1<<WGM01);
2
  OCR0A = (uint8_t)(F_CPU / 64.0 * 1e-3 - 0.5);
3
  TCCR0B = (1<<CS01 | 1<<CS00);
4
  TIMSK0 = (1<<OCIE0A);

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:

> hm, ok. Dann versteh ich aber den Aufschrei nicht.
> Ist das jetzt hier richtig oder nicht:


Genau deswegen gibt es den Aufschrei. WEil du dich anstellst wie ein 
Kindergartenkind, das die Tante braucht wenn es Lulu gehen muss.

>
>
1
>   TCCR0A = (1<<WGM01);
2
>   OCR0A = (uint8_t)(F_CPU / 64.0 * 1e-3 - 0.5);
3
>   TCCR0B = (1<<CS01 | 1<<CS00);
4
>   TIMSK0 = (1<<OCIE0A);
5
>

Du brauchst einen INterrupt, der so ungefähr alle 1ms feuert und eine 
ISR aufruft. Macht diese Einstellung vom Timer das oder macht sie das 
nicht? Tschuldige, aber sowas wirst du doch wohl alleine entscheiden 
bzw. feststellen können. Oder nicht? Wenn nicht, was machst du dann in 
der Programmierung auf diesem Level und warum lässt du nicht erst mal 
ein paar LED timergesteuert blinken?

Ich hab mitlerweile eine extrem starke Abneigung gegen Fragen, die 5 
Zeilen Code x-mal posten und jedesmal eine Bestätigung brauchen, ob das 
auch genau das tut, was sie wollen. Dann willst du sie als Antworter zum 
Nachdenkenb bringen mit dem Effekt, dass dfieselben 5 Zeilen erneut 
gepostet werden. Sorry, aber so funktioniert das meiner Meinung nach 
nicht. Man muss die Dinge verstehen und nicht einfach nur kopiern. 
KOpieren an sich ist schon ok, aber dann muss man auch ergründen was da 
eigentlich warum passiert.

von Lokus P. (derschatten)


Lesenswert?

Nun, es verwirrt und macht unsicher. Was dazu führt weiter zu 
hinterfragen.
Bisher wurde der Code von Leuten hier mehrmals umgemodelt, weil doch 
festgestellt wurde das etwas nicht passt.

von Karl H. (kbuchegg)


Lesenswert?

Wir alle machen Fehler. Überhaupt hier, wo von den Antowrtern Code 
direkt eingetippt wird ohne ihn vorher zu testen.
Wenn es mir passiert, dann habe ich es mir zur Gewohnheit gemacht, bei 
nicht unbedingt offensichtlichen Sachen auch immer dazuzuschreiben, was 
denn der Fehler ist, was er auslöst und wie er behoben wird.
Allerdings: Ich mach das gerne eben nicht in Form von wieder vorgekautem 
Code sondern in Form von Beschreibungen, auch wenn das für mich 10 mal 
so viel Arbeit bedeutet. Denn: DU musst das lernen, nicht ich. Und du 
lernst das nur, wenn du mitdenkst und nicht einfach nur Code hirnlos von 
A nach B kopierst.

> Was dazu führt weiter zu hinterfragen.
Dann hinterfrage.
Aber 4 Zeilen Code mit der Frage zu posten "klappt das ja/nein", ist 
nicht hinterfragen.


Sorry für die recht direkten Worte. Aber so seh ich das mittlerweile. 
Vor einigen Jahren hat das noch anders ausgesehen - hat sich nicht 
bewährt.

von Lokus P. (derschatten)


Lesenswert?

Nein, ich finde das schon ok, wie du vorgehst.
Ich befürworte das voll und ganz.
Ich muß es ja auch verstehen, sonst komm ich irgendwann wieder mit der 
gleichen Frage und das sollte nicht sein.

Ich habe mir jetzt nochmal deine Erklärung zur Gemüte genommen.
Du schreibst:

> Jaaa. Sag ich doch!
> Im CTC Modus kriegst du keinen Overflow Interrupt mehr, weil es zu
> keinem Overflow mehr kommt.
> Also ist es reichlich sinnlos, den Overflow Interrupt freizuschalten.

> Entweder CTC Modus (also WGM01 gesetzt) + Compare Match Interrupt
> Oder Normaler Modus + Overflow Interrupt

> Aber beides mischen geht naturgemäss schief.

ich denke das ich jetzt verstanden habe worauf du angesprochen hast.

Ich geh das jetzt nochmal durch.

von Lokus P. (derschatten)


Lesenswert?

Entweder:
1
// Overflow Interrupt erlauben
2
TIMSK0 = (1<<TOIE0);
3
ISR (TIMER0_OVF_vect)

Oder:
1
TCCR0A = (1<<WGM01); // CTC Modus
2
// Compare Interrupt erlauben
3
TIMSK0 = (1<<OCIE0A);
4
ISR (TIMER0_COMPA_vect)


In meinem Falle ist das also korrekt:
1
  TCCR0A = (1<<WGM01); CTC Modus
2
  OCR0A = (uint8_t)(F_CPU / 64.0 * 1e-3 - 0.5);
3
  TCCR0B = (1<<CS01 | 1<<CS00);
4
  TIMSK0 = (1<<OCIE0A);

Hier wird das sehr gut erklärt:
http://www.mikrocontroller.net/articles/AVR-GCC-Tutorial/Die_Timer_und_Z%C3%A4hler_des_AVR

Dann geh ich am Abend noch das Problem mit den Taster an.

von Lokus P. (derschatten)


Lesenswert?

Der Ausgang PD0 geht bei mir gleich nach dem Einschalten auf HI, so als 
wenn der Taster gleich gedrückt ist.
Auch mit get_key_long(TASTER1).
Das liegt aber nicht am Taster selbst, denn auch wenn dieser nicht 
angeschlossen ist reagiert der PIN.

Ich habe den DDRD korrekt auf Ausgang gesetzt.
Kann das jetzt doch nicht mit den Zeiten zu tun haben?

von Karl H. (kbuchegg)


Lesenswert?

Mega644 und Port C

-> hast du das JTAG mit der Fuse JTAGEN abgeschaltet?

von Lokus P. (derschatten)


Lesenswert?

Daran lag es tatsächlich.
Ich hab den noch nie abgeschalten.

Hat der Einfluss auf den Timer?
Der JTAG ist doch nur eine Programmierschnittstelle.

Es funktioniert jetzt schon "etwas besser".
Es tritt jedoch noch folgendes Phänomen auf:

Wenn der Encoder nicht den Wert 0 (alos 1, 2, 3) hat und ich betätige 
die Taste, springt der Ausgang sofort auf HI wenn ich den Encoder auf 0 
drehe. Der Tastendruck bleibt somit fixiert.
1
    switch(val1)
2
    {
3
      case 0:
4
        LEDS_PORT = ENCODER1_1;
5
        if(get_key_short(TASTER1))
6
        {
7
          FUNKTION_PORT = FUNKTION1_1;
8
        }
9
        break;
10
      case 1:
11
        LEDS_PORT = ENCODER1_2;
12
        if(get_key_long(TASTER1))
13
        {
14
          FUNKTION_PORT = FUNKTION1_2;
15
        }
16
        break;
17
      case 2:
18
        LEDS_PORT = ENCODER1_3;
19
        break;
20
      case 3:
21
        LEDS_PORT = ENCODER1_4;
22
        break;

Und der "get_key_long" tut auch nicht so wie er soll. Er reagiert auch 
auf einen kurzen Tastendruck.

von Karl H. (kbuchegg)


Lesenswert?

Manfred W. schrieb:
> Daran lag es tatsächlich.
> Ich hab den noch nie abgeschalten.
>
> Hat der Einfluss auf den Timer?

Nein

> Der JTAG ist doch nur eine Programmierschnittstelle.

Ja.
Aber sie ist per Default aktiv und übernimmt die Kontrolle über ein paar 
Pins am bewussten Port. D.h. solange JTAG nicht abgeschaltet ist, können 
diese Pins nicht normal benutzt werden.
Und jetzt rate mal, welche Pins das an deinem µC-Typ sind.
Du könntest auch einfach ins Datenblatt sehen, aber das wär zu einfach 
:-)



> Wenn der Encoder nicht den Wert 0 (alos 1, 2, 3) hat und ich betätige
> die Taste, springt der Ausgang sofort auf HI wenn ich den Encoder auf 0
> drehe. Der Tastendruck bleibt somit fixiert.
>
>
1
>     switch(val1)
2
>     {
3
>       case 0:
4
>         LEDS_PORT = ENCODER1_1;
5
>         if(get_key_short(TASTER1))
6
>         {
7
>           FUNKTION_PORT = FUNKTION1_1;
8
>         }
9
>         break;
10
>       case 1:
11
>         LEDS_PORT = ENCODER1_2;
12
>         if(get_key_long(TASTER1))
13
>         {
14
>           FUNKTION_PORT = FUNKTION1_2;
15
>         }
16
>         break;
17
>       case 2:
18
>         LEDS_PORT = ENCODER1_3;
19
>         break;
20
>       case 3:
21
>         LEDS_PORT = ENCODER1_4;
22
>         break;
23
>
>
> Und der "get_key_long" tut auch nicht so wie er soll.

Vergiss bitte fürs erste get_key_long, get_key_short und get_key_repeat.

Wie schon geasgt. Die originale Entprellung ist auf längere 
ISR-Aufrufzeiten eingestellt. Die #define Konstanten am Anfang des Codes 
sind auf 10ms AUfruf-Differenz eingerichtet.
Die hast du aber nicht mehr! Du hast jetzt 1ms! d.h. alle Zeitabläufe 
der Entprellung sind jetzt 10 mal so schnell. Lag im Original die 
Schwelle von short zu long bei einer vernünftigen Zeitdauer, so ist die 
jetzt 10 mal so kurz. Du kannst aber einen Taster nicht händisch 10mal 
so kurz drücken! Daher klappt das noch nicht.

von Karl H. (kbuchegg)


Lesenswert?

Karl Heinz Buchegger schrieb:

> Die hast du aber nicht mehr! Du hast jetzt 1ms! d.h. alle Zeitabläufe
> der Entprellung sind jetzt 10 mal so schnell. Lag im Original die
> Schwelle von short zu long bei einer vernünftigen Zeitdauer, so ist die
> jetzt 10 mal so kurz. Du kannst aber einen Taster nicht händisch 10mal
> so kurz drücken! Daher klappt das noch nicht.


Müsste man mal durchrechnen, ob man den Timer noch auf 2ms einstellen 
kann und dafür dann hier
1
#define REPEAT_START  50
2
#define REPEAT_NEXT    20
die Werte mal 5 nimmt. Dann gleicht sich wieder alles aus.
Mal 5 deswegen, weil die Werte nicht größer als 256 werden können und 
mit
1
#define REPEAT_START  250
2
#define REPEAT_NEXT   100
und 2ms ISR-Aufrufabstand wäre man dann wieder bei den gleichen 
Verhältnissen.

von Karl H. (kbuchegg)


Lesenswert?

> Der Tastendruck bleibt somit fixiert.

Ein Tastendruck wird solange registriert und gemerkt, bis du ihn mit 
einer der get... Funktionen abholst. Erst dann wird er gelöscht.
Holst du im Modus 2, 3 einen eventuellen Tastendruck nicht ab, dann ... 
kriegst du ihn logischerweise im Modus 0 das erste mal serviert.

Der ganze Ansatz mit dem Switch-Case ist Momentan Käse. Du solltest viel 
mehr mit Arrays zu deinen Gunsten arbeiten. Man kann die auszugebenden 
Muster auch in einem Array abspeichern und abhängig vom Modus dann aus 
dem Array rausholen was zu tun ist.
1
uint8_t LedMuster[] = {  ENCODER1_1, ENCODER1_2, ENCODER1_3, ENCODER1_4 };
2
uint8_t FunktionMuster[] = { FUNKTION1_1, FUNKTION1_2, FUNKTION1_3, FUNKTION1_4 };
3
4
int main(void)
5
{
6
  int8_t val1 = 0;
7
8
  ioinit();
9
  encode_init();
10
  sei();
11
12
  for(;;)
13
  {
14
    val1 += encode_read1();
15
16
    if (val1 > 3) val1 = 3;
17
    if (val1 < 0) val1 = 0;
18
19
    LEDS_PORT = LedMuster[val1];
20
21
    if (get_key_press( TASTER1 ))
22
      FUNKTION_PORT = FunktionMuster[val1];
23
  }
24
}

zu banal?

von Lokus P. (derschatten)


Lesenswert?

Das würde dann aber bedeuten das ich bei jeder Auswahl mindestens einen 
"get_key_short" definieren muß?

ist das nicht Pfusch?

Array hatte ich zu beginn.
Aufgrund der geringen Verwendung aber wieder verworfen.

In dem Fall würde es sich wieder rentieren.

Die Herausforderung dabei jedoch kommt erst. Ich will mehrere Encoder 
gleichzeitig ansteuern.

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.