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.
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.
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.
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.
Wie muß diese Konstante denn aussehen?
Also 1s
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
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
durch die Textersetzung sowieso wieder das
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.
> 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.
hast du wirklich soviele LED, oder wäre da ein uint8_t (zumindest jetzt)
nicht einfacher?
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.
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 | }
|
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?
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.
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.
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.
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.
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.
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.
Das funkt nicht:
Aber das hier: :)
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.
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.
Kann ich das ganze nicht in den TIMER0_OVF_vect schmeißen?
Es klappt nicht.
Ich hänge mal den Code an.
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.
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)
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
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
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.
>
> 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.
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.
Ist der CTC Modus jetzt der richtige oder nicht?
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.
hm, ok. Dann versteh ich aber den Aufschrei nicht.
oder gings dabei nur darum weil die Routine im falschen Timer lag
(TIMER0_OVF_vect) ?
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);
|
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.
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.
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.
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.
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.
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?
Mega644 und Port C
-> hast du das JTAG mit der Fuse JTAGEN abgeschaltet?
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.
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.
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.
> 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?
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.
|