Zur Drehgeber-Auswertung: Es gibt zwei digitale Eingangs-Ports; diese können zusammen 4 Zustände annehmen. Zusammen mit dem Zustand beim letzten Aufruf – ebenfalls 4 Möglichkeiten – ergeben sich insgesamt 16 mögliche Zustandswechsel. Der zum betreffenden Zustandswechsel gehörende Strittwert (0, +1, -1 oder "ungültig") wird aus einer Tabelle gelesen und zurückgeliefert.
Folgende Aktionen müssen noch ausgefüllt werden:
- MAKE_IN
- Schaltet einen Port als Eingang
- SET
- Setzt einen Port auf HIGH bzw. aktiviert seinen Pullup-Woderstand
- IS_SET
- Ungleich 0, wenn der Port (als Eingang) HIGH ist, 0 sonst
drehgeber.h
#ifndef _DREHGEBER_H_
#define _DREHGEBER_H_
#define DREH_INVALID -128
static inline char drehgeber_step();
static inline char drehgeber_job();
#if defined (__GNUC__) && defined (__AVR__)
# include <avr/pgmspace.h>
#else
# define PROGMEM
# define pgm_read_byte(x) (*(x))
#endif // avr-gcc
// Aufrufen, wenn eine Flanke an einem der Ports festgestellt wurde,
// zB aus einer ISR heraus. Evtl funktioniert auch Polling.
char drehgeber_step ()
{
static const char drehgeber_trainsitions[] PROGMEM =
{
0, 1, -1, DREH_INVALID,
-1, 0, DREH_INVALID, 1,
1, DREH_INVALID, 0, -1,
DREH_INVALID, -1, 1, 0
};
// Speichert den vorherigen 4-er Zustand in Bits 0 und 1
static uint8_t a_alt;
uint8_t a = a_alt;
// Den aktuellen 4-er Zustand in Bits 2 und 3 einfügen
if (IS_SET (PORT_DREH_A)) a |= (1 << 2);
if (IS_SET (PORT_DREH_B)) a |= (1 << 3);
// Den aktuellen Zustand für den nächsten Aufruf merken
a_alt = a >> 2;
// Zustandswechsel auf Schrittwert abbilden
return (char) pgm_read_byte (& drehgeber_trainsitions[a]);
}
// Mein Drehgeber hat eine Rasterung und bei jedem Raster erzeugt
// er *zwei* Flanken. Man muss also die Anzahl der Schritte etwas
// mühsam durch 2 teilen. Falls der Drehgeber nur einen Schritt
// pro Raster liefert oder keine Rasterung hat, brauch man diese
// Funktion nicht und kann stattdessen direkt `drehgeber_step' nehmen.
char drehgeber_job()
{
char step = drehgeber_step();
char s = 0;
if (DREH_INVALID != step)
{
static char tick;
char t = step + tick;
// Wenn |t| >= 2 ist gibt es einen Schritt
if (t >= 2) s = 1;
if (t <= -2) s = -1;
// Schritt aus t entfernen: t -= 2*s
t -= s;
t -= s;
tick = t;
}
return s;
}
#endif // _DREHGEBER_H_
main.c
Anwendungsbeispiel für einen AVR ATmega88. PORT_DREH_A und PORT_DREH_B müssen noch definiert werden und MAKE_IN, SET und IS_SET durch den konkreten C-Code ersetzt werden. Falls die Drehrichtung nicht stimmt, einfach die Portdefinitionen austauschen.
#include <avr/io.h>
#include <avr/interrupt.h>
// Für alte avr-gcc Versionen
#ifndef SIGNAL
#include <avr/signal.h>
#endif // SIGNAL
#include "drehgeber.h"
// Kontroller-Frequenz dem Programm mitteilen
// Besser per Kommando-Zeile via -DF_CPU=1000000
#define F_CPU 1000000
// IRQs pro Sekunde
// Besser per Kommando-Zeile via -DIRQS_PER_SECOND=5000
#define IRQS_PER_SECOND 5000
static void ioinit(void);
static void timer1_init (void);
// Zählt die Schritte: von -128...127
char volatile steps;
// Die Interrupt Service Routine (ISR) wird IRQS_PER_SECOND
// mal pro Sekunde aufgerufen.
// Bei jedem Aufruf wird auf die Drehgeber-Ports geschaut.
// So ergibt sich eine Entprellung und es werden keine extra
// IRQs belegt nur für den Drehgeber.
SIGNAL (SIG_OUTPUT_COMPARE1A)
{
steps += drehgeber_job();
// In dieser IRQ können auch andere periodisch zu erledingende
// Aufgaben ausgefürt werden wie der Countdown-Zäler.
}
void ioinit (void)
{
// Das ist noch anzupassen auf die Ports, die Du verwendest...
MAKE_IN (PORT_DREH_A); SET (PORT_DREH_A);
MAKE_IN (PORT_DREH_B); SET (PORT_DREH_B);
}
void timer1_init (void)
{
// PoutputCompare für gewünschte Timer1 IRQ-Rate
OCR1A = (unsigned short) ((unsigned long) F_CPU / IRQS_PER_SECOND-1);
// Timer1 ist Zähler: Clear Timer on Compare Match (CTC, Mode #4)
// Timer1 läuft @ F_CPU: prescale = 1
TCCR1A = 0;
TCCR1B = (1 << WGM12) | (1 << CS10);
// OutputCompare-Interrupt A für Timer1
TIMSK1 = (1 << OCIE1A);
}
int main (void)
{
// I/O initialisieren
ioinit ();
// Drehgeber initialisieren (man weiß ja nicht, wie er steht).
// Rückgabewert wird nicht gebraucht.
drehgeber_step();
// Timer1 initialisieren
timer1_init();
// IRQs erlauben
sei ();
while (1)
{
// `steps' auswerten
} // main Loop
}