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 }