http://www.mikrocontroller.net/articles/Drehgeber
Hallo,
ich möchte den Code für einen wackeligen Drehencoder verwerden (APLS).
Zur Hardware,
im Vergleich zum Orignalcode würde ich gerne ohne Pullups arbeiten. Habe
das soweit auch angepasst.
Die Timer/ISR musste ich für dem m328p anpassen. ISR wird jede Sekunde
aufgerufen (getestet).
Als Werte bekomme ich nur -1 oder 0 zurückgeliefert. Inkrementieren
macht er nicht.
encoder.h
Herman Uh. schrieb:> Als Werte bekomme ich nur -1 oder 0 zurückgeliefert. Inkrementieren> macht er nicht.
Auch nicht, wenn Du den Drehgeber in Super-Zeitlupe drehst?
Dein Timer-Interrupt scheint bei 16 MHz Takt des Controllers nur 62,5
mal pro Sekunde zu laufen. Und noch langsamer, wenn Du mit weniger als
16 MHz taktest.
Das liegt für eine vernünftige Drehgeberauswertung für meinen Geschmack
um den Faktor 10 zu niedrig.
Ich würde als erstes mal den Timer-Interrupt 500 bis 1000 mal pro
Sekunde laufen lassen.
Pin getoggelt und mit Logik Analyser kommt ich auf 1kHz. Das ist es
nicht. Also wird häufiger abgefragt. Habe es auch direkt mit OCR = 249
versucht. Timer 0 und 2 beide getestet.
Herman Uh. schrieb:> #if ENCODER_USE_PULLUP> ENCODER_PIN |= ((1<<ENCODER_PHASE_A)|(1<<ENCODER_PHASE_B));> #endif
Ich habe keine Ahnung, was du da vorhast, aber wenn man ins PIN Register
was reinschreibt, toggelt man nur das entsprechende Bit im Data
Register:
Zitat:
'However, writing a logic one to a bit in the PINx Register, will result
in a toggle in the corresponding bit in the Data Register.'
Vllt. schreibst du nochmal, wie du den Encoder nun wirklich
angeschlossen hast. Ausserdem sehe ich keine Initialisierung für die
internen Pullups.
Heißer Tipp, Mikrocontroller mit Hardwareunterstützung für
Quadraturencoder nehmen, z.B. xmega. Alternativ gehen auch spezielle
ASICs oder wenn du ambitioniert bist ein CPLD/FPGA. Alles andere
verursacht nur Kopfschmerzen, gerade wenn's genau sein soll und der
Mikrocontroller noch andere Sachen tun soll außer den Encoder
abzufragen.
greg schrieb:> Heißer Tipp, Mikrocontroller mit Hardwareunterstützung für> Quadraturencoder nehmen, z.B. xmega.
Eiskalter Tipp: nimm den ATmega, den Du hast und programmiere ihn
richtig! Ich hänge ein Beispiel(-schnipsel) für einen Quadraturdekoder
per Timer an. Dieses ganze Tabellenzeugs braucht man nicht.
greg schrieb:> Alles andere> verursacht nur Kopfschmerzen,
So ist es ;-)
@greg: Warum nur für den Encoder ein speziellen Controller? Es handelt
sich um folgenden Encoder (siehe Bild). Genutzt wird dieser mit einer
niedrigen Geschwindigkeit. Dieser dient lediglich zur Menüführung.
Externe Pullups möchte ich nicht verwenden, da ich diese nirgends
anlöten kann derzeit. Sollte aber doch auch ohne gehen wenn ich die
internen aktiviere. Tut es leider noch nicht.
Der Encoder ist wie folgt angeschlossen:
1 PD5
2 GND
3 PD6
//h-File
Herman Uh. schrieb:> @greg: Warum nur für den Encoder ein speziellen Controller? Es handelt> sich um folgenden Encoder (siehe Bild). Genutzt wird dieser mit einer> niedrigen Geschwindigkeit. Dieser dient lediglich zur Menüführung.
Naja, dann ist es ziemlich egal, wie man die Signale dekodiert. Ich
dachte ich hätte aus deinem Post rausgelesen, dass du hohe Genauigkeit
benötigst. Hab mich da wohl geirrt.
Herman Uh. schrieb:> Externe Pullups möchte ich nicht verwenden, da ich diese nirgends> anlöten kann derzeit. Sollte aber doch auch ohne gehen wenn ich die> internen aktiviere.
Solange die Leitungen nicht zu lang werden, spricht ja auch gar nichts
gegen die internen PullUps.
> Tut es leider noch nicht.
Heute mit dem geänderten Timer läuft der Interrupt auch tatsächlich 1000
mal pro Sekunde.
Ich habe mir Deinen Code in die Arduino IDE gezogen (mit minimalen
Änderungen, die der Arduino-IDE geschuldet sind), und der Code
funktioniert wunderprächtig auf einem Arduino-Board mit Atmega328 und 16
MHz Taktfrequenz!
Ich sehe nicht, warum das bei Dir nicht funktionieren sollte.
Code aus verschiedenen Quellen per c&p zusammengeklickt u. noch ein
wenig selbst drann rumgepfuscht.
Und du erwartest das dabei was vernünftiges raus kommt?
Warum fragt hier niemand nach dem konkreten Schaltplan ?
Wenn hier interne Pullups verwendet werden (und sonst nichts)
dann hat der Aufbau keinerlei Entprellung (dazu bräuchte man
einen Längswiderstand und einen Kondensator für jedes einzelne
Signal.
isidor schrieb:> Warum fragt hier niemand nach dem konkreten Schaltplan ?>> Wenn hier interne Pullups verwendet werden (und sonst nichts)> dann hat der Aufbau keinerlei Entprellung (dazu bräuchte man> einen Längswiderstand und einen Kondensator für jedes einzelne> Signal.
Das Ding ist aus zwei Gründen entprellt:
1. Die Drehgeberstellung wird nur 1x pro Millisekunde abgefragt und das
Prellen mechanischer Kontakte dauert fast nie länger als 1 Millisekunde.
2. Der Drehgeber zählt mit einwandfreier Auswertelogik vor- und
rückwärts. Solange von der Drehgeschwindigkeit her nicht "überdreht"
wird, führt ein Prellen, das ausnahmsweise über 1ms dauert, allenfalls
dazu, dass im Millisekundentakt vor- und rückwärts gezählt wird.
Der heute gepostete Code, der @16 Mhz Taktfrequenz mit 1000 Interrupts
pro Sekunde läuft, läuft nach meiner Feststellung einwandfrei, ich kann
jedenfalls kein Problem damit feststellen.
Hi,
also bei mir funktioniert das - mit PA2 und PA3 - mit denke ich dem
gleichen oder ähnlichen Encoder wunderbar (Dank P. Danegger).
Die Erklärung, wie das funktioniert habe ich mir aus div. Quellen
zusammenkopiert, damit ich immer weiß, was ich da mache:
/***********************************************************************
*/
/*
*/
/* Drehgeber mit wackeligem Rastpunkt dekodieren
*/
/*
*/
/***********************************************************************
*/
#include <avr/io.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#define XTAL 16e6 // 16MHz
#define PHASE_A (PINA & 1<<PA3) // an Pinbelegung
anpassen
#define PHASE_B (PINA & 1<<PA2) // an Pinbelegung
anpassen
volatile int8_t enc_delta; // Drehgeberbewegung
zwischen
/*
Darauf basiert die Tabelle ...
dgtab: ;Tabelle mit Drehgeber-Werten (alt-alt-neu-neu als Index)
;aa nn, aa nn
.db 0, 0 ;00 00, 00 01
.db 0, 0 ;00 10, 00 11
.db 1, 0 ;01 00,++ 01 01
.db 0, 0 ;01 10, 01 11
.db -1, 0 ;10 00,-- 10 01
.db 0, 0 ;10 10, 10 11
.db 0, 0 ;11 00, 11 01
.db 0, 0 ;11 10, 11 11
Mein Alps Drehgeber liefert nur Impulse - hat also generell den Zustand
11 und liefert je nach Drehrichtung Impulsfolgen 01->00 oder 10->00 !!
Hier die generelle Erklärung:
Bei Drehgebern, die ihre Rastung auf 00 und 11 haben, gibt es pro
Rastung auf jeder Spur eine Flanke. Um diese Drehgeber auszuwerten,
prüft man ja eine Spur auf Flanke und die andere Spur auf Zustand. Bei
symmetrischen Drehgebern ist es egal, welche Spur man auf Flanke prüft.
Bei diesen "selten dämlichen" Drehgebern prüft man Spur A (also die
Spur, die im eingerasteten Zustand stabilen Pegel liefert) auf Flanke
und Spur B auf Zustand. Da der Zustand nur relevant ist, wenn eine
Flanke erkannt wurde, spielt der (eingerastet) undefinierte Zustand der
Spur B keine Rolle.
Zum Abtasten des Drehgebers wird das Bitmuster (der Zustand der beiden
Spuren) eingelesen und auf die beteiligten Bits maskiert. Dies wird dann
zu dem "gemerkten" und um 2 Bits verschobenen Bitmuster der letzten
Abtastung geORT. Es entsteht eine 4-Bit-Zahl, die als Index auf die LUT
genutzt wird. Von diesen 16 möglichen Zuständen sind bei diesen
Drehgebern aber nur 4 Zustände relevant.
In die LUT werden also nur dort Incremente eingetragen, wo Spur A den
Pegel wechselt (also eine Flanke hat).
Eine Drehrichtung:
Rastung alt neu
A B A B
-------
--> 0 0 0 1 1
| > 0 1 1 1 7 Flanke an A
| 1 1 1 0 14
| > 1 0 0 0 8 Flanke an A
| 0 0 0 1 1
| > 0 1 1 1 7 Flanke an A
| 1 1 1 0 14
| > 1 0 0 0 8 Flanke an A
--< 0 0 0 1 1
Andere Drehrichtung:
Rastung alt neu
A B A B
-------
--> 0 1 0 0 4
| > 0 0 1 0 2 Flanke an A
| 1 0 1 1 11
| > 1 1 0 1 13 Flanke an A
| 0 1 0 0 4
| > 0 0 1 0 2 Flanke an A
| 1 0 1 1 11
| > 1 1 0 1 13 Flanke an A
--< 0 1 0 0 4
Die eine Drehrichtung ergibt also bei Flanken an Spur A die Zahlenwerte
(als Index auf die LUT) 7 und 8, die andere Drehrichtung 2 und 13.
Daraus ergibt sich, dass bei Index 7 und 8 der Wert +1 (als Increment)
in die LUT eingetragen wird und bei Index 2 und 13 der Increment-Wert
-1. Alle anderen Elemente des Arrays (der LUT) werden mit dem Wert 0
aufgefüllt.
Wird Spur A und B vertauscht, so ergeben sich andere Index-Werte. Bei
symmetrischen Drehgebern ist das egal, die "selten dämlichen" spinnen
dann aber.
Somit wird der Zählerstand nur verändert, wenn eine Flanke an Spur A
erkannt wurde. Der Zustand von B ist in diesem Zeitpunkt ja stabil.
Dies alles läuft im Timer-Int oder einem per Timer synchronisierten Job
ab. Die Aufruf-Frequenz ist ein Kompromiss zwischen CPU-Last und maximal
möglicher Drehgeschwindigkeit. Bei Verwendung des Drehgebers als
manuelles Eingabegerät hat sich bei mir eine Abtastfrequenz von 1 kHz
bewährt.
Die Mainloop (bzw. ein Job davon) addiert nun diesen Zählerstand auf den
zur Bearbeitung anstehenden Wert und löscht ihn danach. Somit gehen
keine Drehbewegungen verloren, wenn es in Main mal länger dauert.
*/
// zwei Auslesungen im Hauptprogramm
// Dekodertabelle für meinen speziellen
Drehgeber
// volle Auflösung
const int8_t table[16] PROGMEM = {0,0,0,0,1,0,0,0,-1,0,0,0,0,0,0,0};
ISR( TIMER0_COMP_vect ) // 1ms fuer manuelle Eingabe
{
static int8_t last=0; // alten Wert speichern
static int8_t akt =0; // alten Wert speichern
akt = last & 0x0C;
if (PHASE_A) akt |=2;
if (PHASE_B) akt |=1;
if( akt != last)
{
enc_delta += pgm_read_byte(&table[akt]);
last = ((akt << 2) & 0x0C) | (akt & 0x03);
}
last = (last << 2) & 0x0F;
if (PHASE_A) last |=2;
if (PHASE_B) last |=1;
enc_delta += pgm_read_byte(&table[last]);
}
void encode_init( void ) // nur Timer 0 initialisieren
{
DDRA &= ~(1<<PA2); //Pins als Eingang
DDRA &= ~(1<<PA3);
PORTA |= (1<<PA2); //Pullups aktivieren
PORTA |= (1<<PA3);
TCCR0 = (1<<WGM01) | (1<<CS01) | (1<<CS00); // CTC, XTAL / 64
OCR0 = (uint8_t)(XTAL / 64.0 * 1e-2 - 0.5); // 64 uS
TIMSK |= 1<<OCIE0;
sei();
}
int8_t encode_read( void ) // Encoder auslesen
{
int8_t val;
// atomarer Variablenzugriff
cli();
val = enc_delta;
enc_delta = 0;
sei();
return val;
}
Drehgeber.h sieht bei mir so aus:
/*
* Drehgeber.h
*
* Created: 07.12.2013 09:31:02
* Author: Dieter
*/
#ifndef DREHGEBER_H_
#define DREHGEBER_H_
#endif /* DREHGEBER_H_ */
void encode_init( void );
int8_t encode_read( void ); // read single step
encoders
int8_t encode_read1( void );
int8_t encode_read2( void );
int8_t encode_read4( void );
Das sind rein private Kopieen von Peter Daneggers Programmier-Leistung
und nicht zur Weitergabe bestimmt. Wer es verwendet möge bitte
beachten, dass nicht ich der Autor bin !!!!