Drehencoder ALPS EC11, PCINT, Quasi-PWM und Zahlen vorblinken - Beispiel-Sketch

Hi

Da ich gerade mit einem Drehencoder spiele, wollte ich Euch auch Was zum Spielen anbieten.
Der Sketch läuft bei mir auf einem Nano, sollte somit auch auf einem Uno funktionieren, bei einem Mega müsste man die Register für die ISR-Routinen wohl anpassen.

//Beispiel und Auflistung der verschiedenen Vectoren
//Quelle:https://thewanderingengineer.com/2014/08/11/arduino-pin-change-interrupts/
//
//Zuordnung Arduino-Pins zu PCINT-Nummer nach
// -Link entfernt-, siehe Post #2
//
//Aufblasen des Code durch boolean-Merker und einer Anzeige, welchen Status wir haben
//bei Änderungen durch Drehencoder
//alle 2 Sekunden nach der letzten Ausgabe *111 0 92 0 -> ABC Status Wert Anzahl
//A,B die Phasen des Drehencoder, pro Raste 11 01 00 10
//C Button, gedrückt 0, zählt auch Status um 1 hoch
//Status, das aktuelle 'Programm' - Quasi-PWM 0, oder Zahl Blinken 1
//Wert die vorzublinkende Zahl oder die Offzeit beim Quasi-PWM
//Anzahl Summe der aufgerufenen ISR

//ISR(PCINT0_vect){}    // Port B, PCINT0 - PCINT7 (D8,D9,D10,D11,D12,D13,--,--) (XTAL1 & XTAL2)
//ISR(PCINT1_vect){}    // Port C, PCINT8 - PCINT14 (D14,D15,D16,D17,D18,D19,--,--   (Reset, nicht vorhanden)
//ISR(PCINT2_vect){}    // Port D, PCINT16 - PCINT23 (D0,D1,D2,D3,D4,D5,D6,D7) (D0 & D1 RX/TX SERIAL-USB-Anschluss)

#include <avr/interrupt.h>

volatile uint16_t value = 92;     //wird hoch/runter gezählt durch Encoder
volatile uint16_t value2 = 0;     //wird nur hoch gezählt in jeder ISR
volatile boolean a = 1, b = 1, c = 1, aold = 1, bold = 1, cold = 1, newset = 0, newc = 0; //Merker für die Phasen, den Button und, ob eine ISR ausgelöst hat
uint16_t status = 0;  //wird durch Drücken des Button geändert

//um eine Zahl ausblinken zu lassen
const int ledPin =  LED_BUILTIN;// the number of the LED pin
// Nicht ändern, da die Pin-Nummern mit den Bitmasken überein stimmen müssen
//Phase b und Button sind an PCINT1, Phase a an PCINT0
const byte pinPhaseA=17;
const byte pinPhaseB=8;
const byte pinButton=9;

void setup()
{
  cli();
  PCICR |= 0b00000011; // Enables Ports B and C Pin Change Interrupts
  PCMSK0 |= 0b00000011; // PCINT0 = D8 -> PCINT0, D9 -> PCINT1
  PCMSK1 |= 0b00001000; // PCINT11 = D17/A3 -> PCINT1
  pinMode(pinPhaseA, INPUT_PULLUP);
  pinMode(pinPhaseB, INPUT_PULLUP);
  pinMode(pinButton, INPUT_PULLUP);
  sei();
  Serial.begin(9600);
}

void loop()
{
  static unsigned long oldmillis = millis();
  if (millis() - oldmillis >= 2000) {
    oldmillis += 2000;
    Serial.print("*");
    printer();
    newset = 0;
  }
  if (newset == 1) {
    oldmillis = millis();
    newset = 0;
    printer();
  }
  if (newc == 1) {
    newc = 0;
    status++;
  }
  switch (status) {
    case 0: //Lampe blitzen lassen, ONtime 10ms, OFFtime value
      blitzout(10, value);
      break;
    case 1: //LED die Zahl vorblinken lassen, value in Impulsen ausgeben
      impulseout();
      break;
    case 2: status = 0; //zurück setzen
  }
}

void printer(void) {
  Serial.print(a);
  Serial.print(b);
  Serial.print(c);
  Serial.print(" ");
  Serial.print(status);
  Serial.print(" ");
  Serial.print(value);
  Serial.print(" ");
  Serial.println(value2);
}

ISR(PCINT0_vect)
{
  b = digitalRead(pinPhaseB);
  c = digitalRead(pinButton);
  if (b != bold) {
    if (a == b) {
      value++;
    } else {
      value--;
    }
    bold = b;
  }
  if (c != cold) {
    if (c == 0) {
      newc = 1;
    }
    cold = c; //hier wird nur C angepasst, kann im Programm dann normal ausgelesen werden
  }
  value2++;
  newset = 1;
}

ISR(PCINT1_vect)
{
  a = digitalRead(pinPhaseA);
  if (aold != a) {
    if (a == b) {
      value--;
    } else {
      value++;
    }
    aold = a;
  }
  value2++;
  newset = 1;
}

/*
  Funktion gibt den Wert der Variable 'value' 10-stellig in Blinkimpulsen aus (32bit = 10stellig)
  Vor dem Ziffernblinken wird der Ausgang 'ledPin' auf LOW gesetzt
  Vor und Nach dem Ziffernblinken wird eine 'wartezeit' ... gewartet :) - in ms
  Führende Nullen werden ignoriert
  1-9 Blinkimpulse mit gleich langer Pause, Länge 'waitkurz' ms
  0 Blinkimpuls mit 3-facher Länge
  Zwischen den Ziffern Pause in 3-facher Länge
*/
void impulseout(void) {
  const uint16_t waitkurz = 400;   //ms, Die ein Impuls hell bleibt (x3 für Null, x3 für Pause zwischen Ziffern)
  const uint16_t wartezeit = 1000;  //ms vor und nach dem Ziffernblinken
  static byte ziffer[] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0};    //Merker für die Ziffernzeichen
  static byte blinkstatus = 0;                  //State der Blink-Maschine
  static unsigned long blinkvalue2, millisstart;  //Berechnung, Merker für Startzeit
  uint32_t zehnerpotenz = 1000000000;             //Berechnung, Start-Zehnerpotenz
  static byte i, i2;                              //Merker für die aktuelle Ziffer bzw. den darzustellenden Wert
  switch (blinkstatus) {
    case 0:
      blinkvalue2 = value;                        //Den aktuellen Zahlenwert übernehmen
      i = 10;                                      //und in das Ziffern-Array packen
      while (i > 0) {       //Die Zahl in Einzelziffern aufteilen, pow versagte mit 999999 für 10^5
        i--;
        ziffer[i] = blinkvalue2 / zehnerpotenz;
        Serial.print(ziffer[i]);
        Serial.print(" ");
        blinkvalue2 -= ziffer[i] * zehnerpotenz;
        zehnerpotenz /= 10;
      }
      Serial.println();
      blinkstatus++;      //Weiter mit Status 1
      millisstart = millis(); //merken, wann wir aufgerufen wurden wegen der Start-Pause
      digitalWrite(ledPin, LOW);      //Pin für Anzeige AUS schalten
      break;
    case 1:
      if (millis() - millisstart >= wartezeit) {      //Anfangs-Wartezeit, damit die LED vor dem Blinken AUS war
        //ziffer[7]...ziffer[0] = Anzahl der Blinkimpulse
        //die erste Ziffer (oder die Null beim Einer) erfassen
        i = 10; //mit 100000er anfangen      //erste Ziffer auslesen, führende Nullen ignorieren, Einer-Null ausgeben
        do {
          i--;
          i2 = ziffer[i];
        } while (i2 == 0 && i != 0);
        blinkstatus++;                //Weiter mit der HIGH-Phase für diese Ziffer
      }
      break;
    case 2:
      digitalWrite(ledPin, HIGH);     //LES für Ziffer AN
      millisstart = millis();         //merken, wann die HIGH-Phase gestartet hat
      blinkstatus++;                  //und zur Abschaltung
      break;
    case 3:
      if (millis() - millisstart >= waitkurz * (i2 == 0 ? 3 : 1)) {   //bei einer Null länger bis zum LOW warten
        digitalWrite(ledPin, LOW);
        millisstart = millis();       //merken, wann wir die LED abgeschaltet haben
        blinkstatus++;                //weiter um neue Ziffer auszulesen oder die Aktuelle fertig zu blinken
      }
      break;
    case 4:
      if (millis() - millisstart >= waitkurz * (i2 <= 1 ? 3 : 1)) {   //nach dem 1er oder 0er Blink-Impuls Pause zwischen den Ziffern
        if (i2 > 1) {
          i2--;             //Ziffer noch nicht fertig, weiteren Blinkzyklus einleiten
          blinkstatus -= 2; //2 hoch zu 'LED AN'
        } else {
          if (i > 0) {      //wenn noch Ziffern auszugeben sind, dann die Nächste auslesen
            i--;
            i2 = ziffer[i];     //die neue Ziffer auslesen
            blinkstatus -= 2;   //weiter mit 'LED AN'
          } else {
            blinkstatus++;      //sonst weiter mit End-Pause
          }
        }
      }
      break;
    case 5:
      if (millis() - millisstart >= wartezeit) {      //Wartezeit nach dem Ziffernblinken
        blinkstatus++;                                //verlassen wir die Blinkerei
      }
      break;
    default:
      status++; //diesen Modus wieder verlassen, zur 'normalen' Blinkerei wechseln
      blinkstatus = 0;      //für den nächsten Start vorbereiten
  }
}
// Siehe #4
//Wenn keine 2er Potenzen als Summe von ontime und offtime benutzt werden, spielt
//die Funktion nach 49 Tagen verrückt - Überlauf von millis() und somit ein Sprung in der 'Zeitlinie'
//4294967295ms = 49 Tage, 17 Stunden, 2 Minuten, 47 Sekunden und 275 Millisekunden.
void blitzout(uint16_t ontime, uint16_t offtime) {
  //Wielle Quasi-PWM: http://forum.arduino.cc/index.php?topic=530097.msg3621399#msg3621399
  digitalWrite(ledPin, (millis() % ((uint32_t)ontime + offtime) < ontime)); //(millis()%ontime+offtime)<ontime}
}

eBay, Drehencoder ALPS, Beispiel-Link

MfG

Edit
Link im Quellcode entfernt
Anmerkung zu millis() in #4 im Code eingebracht

Mit dem Drehencoder (A&B an 8 und 17, Button an 9, schaltend auf GND) kann der 3.te Wert eingestellt werden.
Dieser bestimmt die OFF-Zeit der internen LED, ON-Zeit sind 10ms fest.
Durch Druck auf die Achse des Drehencoder (= der Button) wird der Wert 'status' um 1 erhöht.
Status 0: Quasi-PWM
Status 1: Zahl vorblinken
Status 2: status=0;
Das Zahl Vorblinken kann man unterbrechen, aber nicht abbrechen (dafür müsste ich blinkstatus, i, i2 ändern, Die aber nur in der Funktion vorhanden sind - also ggf. global machen.
So wird beim nächsten Status==1 einfach dort weiter geblinkt, wo wir zuvor aufgehört haben.

Den Quasi-PWM habe ich heute erst hier im Forum gefunden ... meine Funktion war 'etwas' aufgeblasener so 20 Zeilen oder mehr ...

Habe einige 'magic numbers' durch Konstanten nebst Kommentaren ersetzt, hoffentlich habe ich mich dabei nicht zu sehr verhaspelt.

Hatte mir von den ISR etwas mehr erhofft, da die Kontakte des Encoder aber laut Doku 2ms Prellzeit aufweisen können und mit maximal einer Umdrehung pro Sekunde angegeben sind, will ich mich Mal nicht so weit aus dem Fenster lehnen.
Wobei 15x4=60, 60x2=120ms im ungünstigsten Fall - bei schnellen Drehungen habe ich aber 'Schrittverluste'.
Soll für Heute aber so gut sein!

MfG²

Hallo,

ich weiß zwar noch nicht so recht was das am Ende werden wird, aber deine Links solltest du ändern. Erstens führt es zum falschen µC und zweitens warnt noscript davor. Lieber diese Links als Bsp. verwenden. Vom ersten Link aus kann jeder leichter zu seinem Board weiter klicken

Pinouts  >>> http://www.pighixxx.com/test/pinoutspg/boards/
Uno      >>> http://www.pighixxx.com/test/portfolio-items/uno/?portfolioID=314
Mega2560 >>> http://www.pighixxx.com/test/portfolio-items/mega/?portfolioID=314

Pinouts : http://www.pighixxx.com/test/pinoutspg/boards/
Uno: pighixxx.com
Mega2560: pighixxx.com

Hi

Werden soll Das erst Mal gar Nichts, nur Funktion zeigen.
Hiermit kann ich, wenn ich halt langsam genug drehe, diese Drehbewegung erfassen und in einen sich wie gewünscht ändernden Wert umsetzen.
Weiter kann ich durch Tastendruck das ablaufende Programm beeinflussen.
Ausgelöst durch PC-Interrupts, Die man ggf. auch Mal wo anders verwenden kann.
Vll. kann ja auch Mal Wer die Zahlenblinkerei gebrauchen, Die aber (leider) nicht auf meinem Mist gewachsen ist, Die habe ich von dort abgekupfert:
Petling-Thermometer

Noch gefällt mir in dem Code nicht, daß man wirklich jede Zahl 'durchdrehen' muß - da ich kaum schneller drehen kann, um die Drehgeschwindigkeit künstlich hoch zu rechnen (ähnlich der Mausbeschleunigung der früheren Maustreiber zur DOS-Zeit), schwebt mir eine Verstellung des Wertes ziffernweise vor - Mal schauen.

Akut habe ich in dem Sketch noch einen 5er NeoPixel verbaut, auf Dem eine LED 'durchflitzt' - mit identischer Wartezeit wie beim Quasi-PWM. Als Ring gehalten gibt Das eine billige Art einer Rundum-Flitze.
Hätte auch noch zwei 5m Stripes liegen, Da werden aber die Katzen komisch, wenn ich dort einen Punkt drüber flitzen lasse. :o (bereits getestet - dafür ist das Zeug dann aber auch vom China-Mann zu teuer)

MfG

Link zur Pinbelegung wurde entfernt
Da ich selber NoScript nutze, habe ich wohl die entsprechenden Seiten/Inhalte bereits geblacklistet.

Hi..
Ich hoffe, dass du mir nicht böse wirst.... :o

Habe mir den Code mal an gesehen.
2 Probleme entdeckt, zumindest das was ich für Probleme halte.

A: Deine SoftPWM wird, wenn die Zeiten nicht exakt auf 2er Potenzen liegen, alle 49 Tage einen Schluckauf haben.

B: Ich selber habe mir an Drehencoder ISRs das Versagen eingefangen.
Die Lösung für meine Sorgen war: Dekoder für Drehgeber mit wackeligen Rastpunkten
Wobei ich dann die Timer ISR durch unser BlinkWithoutDelay Prinzip ersetzt habe.

Hi

Zu A - verdammt, hast Recht - wäre ja auch zu schön gewesen, wenn ein schlanker Aufruf auch 'tun tät'.
Werde dazu einen Hinweis im Quelltext einpflegen, damit der freudige Finder das Problem direkt sieht.
Bei Laufzeiten<49 Tagen wäre Das aber verschmerzbar, immerhin.

Zu B - Du pollst somit wieder? Gerade damit war ich zu langsam, weshalb ich auf die ISR gegangen bin.
Hatte oben ja schon gerechnet, wenn ich von 2ms Prellzeit ausgehe, wäre eine Umdrehung (15 Rasten a 4 Flanken) mit 120ms immer noch sau schnell (laut Datenblatt ist eine Umdrehung/Sekunde das anzustrebende Maximum) - hier käme ich auf über 8.
So flott bin ich mit den Fingern bestimmt nicht, konnte aber keine saubere Erkennung erstellen.

Habe den Link überflogen (und hatte Den auch schon Mal 'unter den Fingern'), Mal schauen, was sich draus ableiten lässt.

Zu Deiner Einleitung: Böse? Ne, noch lange nicht.
Ist ja nicht ganz uneigennützig, wenn der Drehencoder so flott verarbeitet wird, daß man mit Drehen nicht mehr hinterher kommt.
Besser, als nach vielen schnellen Drehern zu erkennen, daß sich der Wert erst um wenige Ticks geändert hat.

MfG

Hallo,

den Code von Peter Dannegger verwende ich auch schon länger.
https://www.mikrocontroller.net/topic/drehgeber-auslesen

Für manuelle Drehencoder brauchst du keine Interrupteingänge. Dafür dreht man viel zu langsam. Polling ist dafür vollkommen ausreichend. Das gleiche für Tastereingänge. Interrupteingänge machen erst Sinn für Encoder die nicht manuell sondern durch Motoren mitgedreht werden.

Dein Code ist zudem leider unübersichtlich, weil du hunderte Schreibweisen vermischt.

Du pollst somit wieder?

Ja.

Hi

Doc_Arduino:
Dein Code ist zudem leider unübersichtlich, weil du hunderte Schreibweisen vermischt.

Das nennt man Multi-Kulti und ist heute, gendertechnisch, vorgegeben :confused:
Ne, natürlich nicht - worauf beziehst Du Dich?
Besser werden ist einfacher, wenn man nicht nur weiß, daß man was falsch macht, sondern auch das 'Was' kennt.

Den Link werde ich mir gleich zu Gemüte führen, den Namen Peter Dannegger hat man auch schon öfter gelesen!

MfG

Hallo,

das hat nichts mit Multi Kulti zu tun. Fang nicht solchen Mist an.

Man sollte entweder byte, unsigned int, unsigned long schreiben (Arduino Schreibweisen) oder uin8t_t, uint16_t, uint32_t (klassische Schreibweise) womit aber der Arduino Neuling nichts anfangen kann.

oder Vergleiche, zwei Syntaxe ineinander gewurfen, auch hier kann der Arduino Neuling mit letzteren nichts anfangen.

if (millis() - millisstart >= waitkurz * (i2 <= 1 ? 3 : 1)) {

Hi

Ich hätte das 'Ne, natürlich nicht' wohl doch fett schreiben sollen - passiert.
(Und Doch: Heute ich ALLES gendertechnisch versaut ... Bäckerer vs Bäckerin, da Bäcker das Geschlecht nicht eindeutig zuordnet - und Da steht es ja auch schon drin: das ist (Ge)schlecht)

Die klassische Schreibweise finde ich ganz praktisch, da man so 'sieht', wie viele Bits dort wirklich verwendet werden.
Was ich, so muß ich gestehen, beim Arduino teilweise nachlesen muß.
Klar macht das Vermischen von unsigned long und uint32_t die Sache nicht einfacher, hier kann man bei Klärungsbedarf fragen.

Bei Deiner Nummer 2 kann der geneigte Arduino-Neuling ebenfalls gerne Nachfragen,ich bin der Letzte, Der darauf nicht antwortet - gerade, da ich diese Zeile so geschrieben habe.
Auch sind Kommentare vorhanden, Die auf die Funktionalität eingehen, man sich also ggf. einen Reim auf diese Zeile machen kann.

Soll ich nun meinen Code künstlich aufblasen, damit ich eine gültige Schreibweise vermeide, damit der Arduino-Neuling, Der Seinen Mars-Rover gerne Blinker anbauen möchte, meinen Code sonst nicht versteht?
Ich finde die eingebundene IF-Abfrage sogar recht elegant.

MfG

Ich könnte dir meine Variante zu kommen lassen.....
Aber ob dich das glücklich macht?

Hi

Das kann man so noch nicht sagen - meine Pollerei ist auch nicht schneller, als die ISR-Variante - immerhin wird Dort mit einer Klasse gearbeitet, Die ich nach herzenslust umstricke.
Quelle des Klassen-Beispiel
Nach diesen Überlegungen umgeschrieben:
Peter Dannegger @ mc.net
Wenn das Serial.print auf ein Minimum begrenzt ist (alle Sekunde als Status), kann ich etwas schneller drehen (logisch: der µC hat dann wesentlich mehr Freizeit):

//----------------------------------------------------------------------------------------
/*
  Funktionen zur Bitmanipulationen
  Quelle: https://github.com/cubetech/arduino.coffeehack aus der enthaltenen .ino
*/
// define some basics
#ifndef bitRead
#define bitRead(value, bit) (((value) >> (bit)) & 0x01)
#define bitSet(value, bit) ((value) |= (1UL << (bit)))
#define bitClear(value, bit) ((value) &= ~(1UL << (bit)))
#define bitWrite(value, bit, bitvalue) (bitvalue ? bitSet(value, bit) : bitClear(value, bit))
#endif
//========================================================================================

//-----------------------------------------------------
// Begin class declaration encoder
//-----------------------------------------------------

class encoder {
  public:
    encoder(byte Pin1, byte Pin2);  //Constructor
    int read();
  private:
    byte _Pin1, _Pin2;
    byte _status, _statusold;
};

//-----------------------------------------------------

encoder::encoder(byte Pin1, byte Pin2) {
  _Pin1 = Pin1;
  _Pin2 = Pin2;
  pinMode(_Pin1, INPUT_PULLUP);
  pinMode(_Pin2, INPUT_PULLUP);
  _statusold = digitalRead(_Pin2) << 1;
  if (bitRead(_statusold, 1)) {
    bitWrite(_statusold, 0, !digitalRead(_Pin1));
  } else {
    bitWrite(_statusold, 0, digitalRead(_Pin1));
  }
  
}

int encoder::read() {
  _status = digitalRead(_Pin2) << 1;
  if (bitRead(_status, 1)) {
    bitWrite(_status, 0, !digitalRead(_Pin1));
  } else {
    bitWrite(_status, 0, digitalRead(_Pin1));
  }
  int rc = _status - _statusold;
  if (abs(rc) > 2) rc = rc + (rc < 0 ? 4 : -4);  //jahaa ... ;)
  _statusold = _status;
  return rc;
}

//-----------------------------------------------------
// End class declaration encoder
//-----------------------------------------------------

encoder my_encoder(8, 17);

int encoder_val;
int my_Value = 100;
unsigned long lastmillis;

void setup() {
  Serial.begin(9600);
}

void loop() {
  encoder_val = my_encoder.read();
  if (encoder_val != 0) {
    my_Value += encoder_val;
  }
  if (millis() - lastmillis >= 1000) {   //Statusausgabe
    Serial.println(my_Value);
    lastmillis = millis();
  }
}

Trotzdem habe ich Versprünge, also fehlende Zwischenschritte, Die Es, meinem Verständnis nach, nicht geben dürfte.
Die Rechnung mit der Prellzeit von oben ließe mich 8 Umdrehungen pro Sekunde machen, ohne, daß ein Zwischenschritt verloren gehen dürfte - vll. bekomme ich zwei Umdrehungen pro Sekunde hin (per Schraubendreher gedreht), habe dort aber nur selten keine Differenz, wenn ich in Ausgangsstellung zurück drehe.

Klar ist Das unerheblich, wenn ich damit Werte per LCD einstellen will, da ich ja das visuelle Feedback für die Dauer der Eingabe benutze, aber unschön ist's schon.

MfG

Hallo,

ich weiß nicht was du mit deinem Gender Mist bezwecken willst. Das hat damit gar nichts zu tun. Programmierung folgt anderen Regeln bzw. Empfehlungen.

Nach diesen Überlegungen umgeschrieben:
Peter Dannegger @ mc.net

Die erste Variante, aus dem Thread nutze ich schon seit geraumer Zeit.
Hat sich, im Umgang mit verschiedenen Encodern, bewährt.

Die Zweite habe ich gerade mal getestet.
Tuts.

Trotzdem habe ich Versprünge, also fehlende Zwischenschritte,

So schnell meine Fingerchen den Knopf auch drehen ... keine Versprünge.