Themabewertung:
  • 0 Bewertung(en) - 0 im Durchschnitt
  • 1
  • 2
  • 3
  • 4
  • 5
FAQ zum Einsteigerkurs uC-Programmierung
Ja Frank, wir brauchen nur für das Inkrement einen Interrupt. Die Richtungserkennung geschieht ja direkt nachfolgend. Der Interrupt lauscht auf abfallende oder ansteigende Flanke am Inkrement.

Man kann nicht alle Digital-Ports für alle Funktionen nehmen. Nur wenige können Interrupts auslösen, und nur einige PWM-Ausgaben.
Gruß,
Jupp
-----------------------------

was du baust ist immer mit dir verbunden
(Lego)

Einsamkeit ist nur ein Mangel an Technologie
(@beetlebum)
Zitieren
JA 1 (ein) Interrupt ist ausreichend, denn in #235 bzw. dem Algorithmus im PDF wird ja auch nur der A-Kanal ständig gescannt. Und die nächste wichtige Info hat Jupp mitgeliefert: "Der Interrupt lauscht auf abfallende oder ansteigende Flanke"

Auf beide Flanken reagieren ist das CHANGE-Event.

Schreiben wir also im Code was wir bisher rausgefunden haben:

PHP-Code:
// Es reicht ein Interrupt-Pin für den Encoder zu reservieren, der andere Pin kann "normal" sein
#define PIN_A  2 
#define PIN_B  5 
#define INTERRUPT_PIN PIN_A

void setup() {
   Serial.begin(9600);  // klar hier soll wieder die Zahl ausgegeben werden
   interrupts();

   pinMode(INTERRUPT_PININPUT_PULLUP);
   pinMode(PIN_BINPUT_PULLUP);
   attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), ISR_funktionCHANGE);
}

//nun die ISR
void ISR_funktion() {
  
}


void loop() {



Wir haben den Encoder richtig am Controller angeschlossen (ein Interrupt-Pin), der andere kann bleiben. Unsere Interrupt-Service-Routine wird ohne Zutun von loop() angesprungen, wenn wir auf dem A-Kanal eine steigende oder fallende Flanke haben. 

Wir sind im alten Programm also an dieser Stelle:

void AbfrageDrehencoder(void) {
 
  Last_RoB_Status = digitalRead(PIN_B);  
  while(!digitalRead(PIN_A)) {     
      Current_RoB_Status = digitalRead(PIN_B);     
      flag = 1;    
   }
…….

Was muss in die ISR? Brauchen wir auch Variablen die von der ISR und(!) dem normalen Code benutzt werden? Eine Wortbeschreibung ist ausreichend!
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
mit meinen eigenen Worten mal erklärt: In der ISR setzen wir einfach eine Variable, welche besagt: "Interrupt wurde ausgelöst". Die ISR soll ja schnell abgearbeitet sein und die loop ihre Arbeit wieder fortsetzen. Die Variable ist eine besondere Variable vom Typ volatile (also volatile int), damit sie in allen subroutinen gültig ist. In der loop wird sie dann ausgewertet. Wie gesagt .. eigene Worten für meine eigenen Eselsbrücken.
Aber jetzt lasse ich die anderen wieder knobeln.
Gruß,
Jupp
-----------------------------

was du baust ist immer mit dir verbunden
(Lego)

Einsamkeit ist nur ein Mangel an Technologie
(@beetlebum)
Zitieren
Ja das wäre durchaus eine Möglichkeit wie man die ISR sehr schlank bekommt. Eine ISR soll natürlich schnell abgearbeitet sein, d.h. man sollte darin auf Schleifen ö.ä. verzichten. Für unseren Encoder, der nicht viel Code benötigt, können wir auch den gesamten Code für die Auswertung in die ISR bringen! Ich will mal den von Jupp vorgeschlagenen Weg gehen und den nachfolgenden Code aus der PDF in die ISR integrieren:

void AbfrageDrehencoder(void) {
 
  Last_RoB_Status = digitalRead(PIN_B);  
  while(!digitalRead(PIN_A)) {     
      Current_RoB_Status = digitalRead(PIN_B);     
      flag = 1;    
   }


Hier werden ja die Zustände von Pin_B  bei steigender und fallender Flanke von PIN_A ermittelt und diese in den Variablen Last_RoB_Status und Current_RoB_Status gespeichert. Machen wir doch das 1:1 in unserer ISR.


PHP-Code:
void ISR_funktion() {
 
 if (digitalRead(PIN_A)) {
 
    Current_RoB_Status digitalRead(PIN_B);     
     flag 
true;
 
 } else {
 
    Last_RoB_Status digitalRead(PIN_B);
 
 }



In der ISR, die beim Flankenwechsel von PIN_A automatisch angesprungen wird, lesen wir sofort den Pegel von PIN_A ein, dann wissen wir ob der Interrupt wegen einer steigenden oder fallenden Flanke angesprungen wurde. Vorher hatte das die while-Schleife gemacht. Die entsprechenden Zustände des PIN_B legen wir in den Variablen Current_RoB_Status und Last_RoB_Status ab. Ebenfalls setzen wir das flag auf wahr, da wir nun in der Lage sind zu entscheiden, in welche Richtung der Encoder gedreht wurde. Alle diese drei Variablen müssen global als volatile definiert sein, da nicht nur der ISR, sondern auch wir gleich außerhalb der ISR drauf zugreifen.


PHP-Code:
volatile boolean flag false;
volatile unsigned char Last_RoB_Status
volatile unsigned char Current_RoB_Status


Den restliche Code zur Bestimmung der Drehrichtung, hier rot markiert:


.jpg   restcode.jpg (Größe: 54,29 KB / Downloads: 255)

können wir in die loop setzten, da er nur dann abgearbeitet wird, wenn er vorher von der ISR freigegeben wurde. Die fertige Lösung der Hausaufgabe könnte komplett dann so aussehen:

PHP-Code:
#define PIN_A  2 
#define PIN_B  5 
#define INTERRUPT_PIN PIN_A

volatile boolean flag false;
volatile unsigned char Last_RoB_Status;  // Hinweis: auch hier ist ein boolean eigentlich ausreichend, beides ist ein Byte groß!
volatile unsigned char Current_RoB_Status// Hinweis: auch hier ist ein boolean eigentlich ausreichend, beides ist ein Byte groß!

int iZahl=0;
unsigned long time;

void setup() {
 
  Serial.begin(9600);  
   interrupts
();
 
  pinMode(INTERRUPT_PININPUT_PULLUP);
 
  pinMode(PIN_BINPUT_PULLUP);
 
  attachInterrupt(digitalPinToInterrupt(INTERRUPT_PIN), ISR_funktionCHANGE);
}

void ISR_funktion() {
 
 if (digitalRead(PIN_A)) {
 
    Current_RoB_Status digitalRead(PIN_B);     
     flag 
true;
 
 } else {
 
    Last_RoB_Status digitalRead(PIN_B);
 
 }
}

void loop() {
 
 
   
// Thread 1
 
  if (time+1000 millis()) 
 
  {
 
     time millis();
 
     Serial.println("PING");
 
  }

 
  // Thread 2 : Encoder Pin2/5
 
  if (flag) {
 
    // wird nur bearbeitet nach Freigabe durch Interrupt
 
    flag false;
 
     if((Last_RoB_Status == 0)&&(Current_RoB_Status == 1))
 
     
 
       //system("echo \"next\" | nc 127.0.0.1 9294 -N");
 
       iZahl++;      
        Serial
.println(iZahl);     
      

 
     
     
if((Last_RoB_Status == 1)&&(Current_RoB_Status == 0))
 
          
        
//system("echo \"prev\" | nc 127.0.0.1 9294 -N");       
 
       iZahl--;       
        Serial
.println(iZahl);     
     
  
   
// if (flag)
// loop 

Ich habe mir den Spaß gemacht und gleich noch einen zweiten "Thread" mit ins Hauptprogramm genommen. Dieser Thread erzeugt jede Sekunde eine Ausgabe auf der seriellen Konsole. Weitere Threads könnten ähnlich angelegt sein ohne das die Rechenlast der Encodererkennung merklich Probleme bereitet.


.jpg   result.jpg (Größe: 35,64 KB / Downloads: 254)

Der Encoder wird problemlos eingelesen und die Richtung erkannt. Da man natürlich nicht immer mit gleicher Geschwindigkeit dreht, ist der Raum zwischen den "PINGs", die im Sekundentakt ausgegeben werden, selbstredend unterschiedlich groß.
 
Außerdem haben wir ja noch einen zweiten Pin für externe Interrupts, hier könnte dann ein weiterer Encoder per Interrupt seine Werte liefern. Dadurch das der Prozessor nicht mehr in Vollzeit auf den Encoder aufpassen muss, wie beim Pollingcode im PDF üblich, haben wir uns durch den Interruptbetrieb also genügend Freiraum für andere Spielereien geschaffen.  

Hat doch Spaß gemacht mal so "live" zu programmieren? Ich hoffe Ihr konntet folgen und werdet die anderen Aufgaben (2. Hausaufgabe und den Zusatz) mit Freude und Spaß lösen können.

Keep coding
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
Ich habe den Code mit Interrupt ausprobiert. Insgesamt funktioniert bei mir die Pollingvariante fast perfekt. Mit Interrupt wird öfters eine Bewegung verschluckt.
Wie kommt das, Schalterprellen?
Wofür genau benötigt man hier eine Variable vom Typ volatile? Der Code funktioniert ohne genau so gut.

Gruß Frank
sicheres Auftreten bei völliger Ahnungslosigkeit
Zitieren
(12.02.2020, 18:27)Tubefan schrieb: Ich habe den Code mit Interrupt ausprobiert. Insgesamt funktioniert bei mir die Pollingvariante fast perfekt. Mit Interrupt wird öfters eine Bewegung verschluckt.
Wie kommt das, Schalterprellen?

Siehe #235 oder du kommst vielleicht schon an die Grenzfrequenz deines Versuchsaufbaus. Allerdings sollte es keine Lücken in der Zählung geben! Fängt sich denn die Erkennung bei gleichbleibender Geschwindigkeit des Encoders wieder? Wenn ja, dann wirst Du bei 99,9% der Applikationen praktisch keine Probleme haben.

Über RC angeschlossen bekomme ich hier "handgedreht" mit einem qualitativ guten Encoder (aus einem TRX zur Freq.-Einstellung) noch 75-100 Hz (also Zählungen bis zum nächsten Sekunden PING) hin. Aber auch die Drehzahl eines keinen Motors zu bestimmen, also mit einigen hundert Hz, klappt damit noch ohne Verschlucker! Tipp: Denkt an die Folgeaufgaben! ;-)

(12.02.2020, 18:27)Tubefan schrieb: Wofür genau benötigt man hier eine Variable vom Typ volatile? Der Code funktioniert ohne genau so gut.

Siehe Lektion 4, Seite 3

Damit sich dort keine Fehler durch Compileroptimierung einschleichen, wird empfohlen diese Variablen als volatile zu kennzeichnen. Auf die Variable wird ja von verschiedenen Seiten zu den unterschiedlichsten Zeiten zugegriffen!

https://www.arduino.cc/reference/de/lang.../volatile/

...Im Detail zeigt volatile dem Compiler, dass er die Variable aus dem RAM und nicht aus dem Speicherregister laden soll. Unter bestimmten Bedingungen können die Variablen in den Registern ungenau sein.

Eine Variable sollte als  volatile deklariert werden, wenn sie von irgendwo anders geändert werden kann außer in dem Codeteil, in dem sie auftaucht. Beispiele dafür sind Threads, die parallel ausgeführt werden. Im Arduino kann das nur bei Interrupts vorkommen, in sogennanten Interrupt Service Routinen. ...


Beispiele wo automatische Optimierungen funktionell versagen:
https://de.wikipedia.org/wiki/Volatile_(Informatik)
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
Hallo,

es gibt einige Kursteilnehmer die sich offensichtlich schon mit den Zusatzaufgaben beschäftigen. Eine Frage war dort die Empfindlichkeit der Controllereingänge. Nun natürlich kann der Controller die kleinen Amplituden eines Oszillators nicht direkt verarbeiten, er lebt und kennt abseits der ADCs ja nur die 5V (3.3V) / 0V Logik!

Gruß Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
Hallo,
einige Encoder Typen mit "Rastungen" rasten auch zwischen gleichartigen Flankenwechseln.


.png   encoder.png (Größe: 10,1 KB / Downloads: 156)

Bei der hier vorgeschlagenen Lösung, wird bei solchen Encodern nur bei jeder zweiten Rastung
eine Änderung erkannt. Damit bei jeder Rastung etwas "passiert", kann man nach jedem
Interrupt auf die jeweils andere Flanke umschalten. Hier mein Code dazu.

PHP-Code:
#define PIN_A 3
#define PIN_B 4

volatile char encoderInt;
int iZahl=0iZahl1;

void ISR_funktion() 
{
 
   encoderInt =1;
}

void setup() 
{
 
   Serial.begin(9600);
 
   interrupts();
 
   pinMode(PIN_AINPUT_PULLUP);
 
   pinMode(PIN_BINPUT_PULLUP);
 
   attachInterrupt(digitalPinToInterrupt(PIN_A),ISR_funktionRISING);
 
   
}

void loop() 
{
 
   if (encoderInt)
 
   {
 
       if (EICRA &(1<<ISC10))        // rising edge interrupt
 
       {
 
           if (digitalRead(PIN_B))(iZahl)--; 
 
           else (iZahl)++;
 
           EICRA &= ~(1<<ISC10);      // switch int1 to falling edge
 
       }
 
       else                           // falling edge interrupt
 
       {
 
           if (digitalRead(PIN_B)) (iZahl)++; 
 
           else (iZahl)--;
 
           EICRA |= (1<<ISC10);       // switch int1 to rising edge
 
       }
 
       encoderInt =0;
 
   }
 
 
    if 
(iZahl !=iZahl1)
 
   {
 
       iZahl1 =iZahl;
 
       Serial.println(iZahl);
 
   }


Holger
Zitieren
(13.02.2020, 22:38)HolgerK schrieb: Bei der hier vorgeschlagenen Lösung, wird bei solchen Encodern nur bei jeder zweiten Rastung
eine Änderung erkannt. Damit bei jeder Rastung etwas "passiert", kann man nach jedem
Interrupt auf die jeweils andere Flanke umschalten.

Hallo Holger,

eine Umschaltung des Interrupts von rising auf falling auf rising kann man machen. Bei der Codeübersetzung oben
reagiert der Interrupt ja auch auf beide. Durch den Einsatz von CHANGE ist somit die fallende Flanke, wie die steigende Flanke abgedeckt. Die Unterscheidung welche Flanke den Interrupt nun ausgelöst hat, erfolgt durch den Code in der ISR. Dadurch muss man nicht ständig die Auslöserquelle steigend / fallend umschalten.

Problematisch bei beiden Lösungen (letztendlich sind es alles Sparlösungen, die aber unter vielen Bedingungen gut funktionieren und deshalb auch so angewandt werden) sind solche Encoder, die so "besch***" rasten, das die Ruheposition genau auf dem Flankenwechsel liegt und die Signale praktisch ständig hin und her-wechseln. Hier werden dann ohne weitere Maßnahmen soviele Interrupts ausgelöst, das die CPU nichts anderes mehr macht als ISR-Anforderungen zu bearbeiten. Dort muss man den Code und die Interrupts umorganisieren, also nicht mehr direkt die Spur A/B auswerten und wie immer führen viele verschiedene Wege ans Ziel!

Ziel in der 1. Hausaufgabe der Lektion 4 war es aber nicht den perfekten Code für eine Encoderauswertung zu schreiben, den gibt es meiner Meinung nach sowieso nicht, da auch die oftmals als perfekt propagierten Lösungen dann versagen, wo andere Sparlösungen keine Probleme haben. Wir hier im Kurs wollen uns da gar nicht die Köpfe einschlagen, das passiert in einschlägigen Mikrocontrollerforen so schon zur Genüge. Ziel der 1. Hausaufgabe in Lektion 4 war es einen vorgegeben Code möglichst 1:1 von Polling auf Interrupt umzustellen. Also das Verständnis für Interrupts zu bekommen und welche Möglichkeiten Interrupts für spätere Aufgaben bieten. Das war für einige Teilnehmer die mir verzweifelt Ihre Codeentwürfe zugeschickt haben, schon keine leichte Aufgabe, weshalb wir hier die Sache mal Stück für Stück zusammen gemacht haben.

Da wir ja auch die PIC-Seite bedienen, sind Registerprogrammierungen kein Geheimnis mehr. Zumindest was das direkte schreiben und lesen von Registern angeht.

Bei deinem Code solltest Du vielleicht solche Operationen: EICRA |= (1<<ISC10); mal auseinander-pfriemeln und erklären.
Ich denke die Teilnehmer werden mit dem Datenblatt des Controllers die Register identifizieren können, solche Operatoren |= oder (1<<ISC10) sind vielleicht nicht allen Teilnehmern klar?
Vorteilhaft bei so einer Programmierung ist natürlich die Umgehung des doch nicht ganz kleinen Overhead, den die Arduino-Bibliotheken mitbringen! Dadurch werden Programme mit "direkten" Registermanipulationen immer einen Geschwindigkeitsvorteil gegenüber den Arduino-Highlevel-Bibliotheken haben! Letztere sind dann aber Anfängerfreundlicher, weil besser lesbarer, da solche Operationen wie deine hier praktisch verkleidet oder vor dem Anwender versteckt werden. Dieses deutlich mehr an internen Code in der Bibliothek macht auch keine Compileroptimierung wett! Deutlich wird das in den weiteren Aufgaben der Lektion 4 werden. Ich hoffe ja das der ein oder andere Kursteilnehmer dabei die Arduino-Libs liegen lässt und (zumindest in einem Teil des Codes) mal eine direkte Programmierung der Register wagt! Ermittelt dann die Grenzfrequenzen Eurer Programme!

Gruß und Danke für deine Mitarbeit
Bernhard
Ansprechpartner für Umbau oder Modernisierung von Röhrenradios mittels SDR,DAB+,Internetradio,Firmwareentwicklung. 
Unser Open-Source Softwarebaukasten für Internetradios gibt es auf der Github-Seite! Projekt: BM45/iRadio (Google "github BM45/iRadio")
Zitieren
Hallo Bernhard,
der eingefügte Code sollte eigentlich nur der Verdeutlichung dienen.

Die Intention hinter meinem Post war eher die Erfahrung, dass bestimme Encoder eben
so rasten, dass sie auch mit Level-Triggerung (CHANGE) nur bei jedem zweiten Schritt
eine "Aktion" erzeugen. Mich hat das erst mal gewundert und ich dachte, anderen
könnte das vielleicht ähnlich gehen.  

Die im Code verwendeten Bit-Manipulationen in erkläre ich gerne in einem separaten
Post.

Holger
Zitieren
zu Lektion 5 - Widerstand messen

mir ist die Funktion trunc nicht klar. Wenn trunc weggelassen wird ist das Ergebnis das gleiche. 10x den Messwert addieren und die Summe durch 10 teilen.

//10 Messungen machen und Mittelwert bilden
Messwert=0;
for(int i=0;i<10;i++){
Messwert+=analogRead(ANALOG_PIN);
}
Messwert=trunc(Messwert/10);

Vielleicht kann mir jemand etwas Licht ins dunkle bringen.

Gruß Frank
sicheres Auftreten bei völliger Ahnungslosigkeit
Zitieren
Trunc schneidet nicht benötigte Nachkommastellen durch Nullung ab und benötigt im Gegensatz zur Rundung praktisch keine nennenswerte Rechenzeit. Die nachfolgenden Rechenoperationen werden dann auch schneller erledigt als wenn bis zum x-ten Nachkommabit gerechnet werden muss.
Ob das bei /10 /100 mehr ausmacht als bei /3 /5 /7 kann ich nicht sagen wieviel Zyklen da eingespart werden. Kommt mit Sicherheit auf die komplexität der nachfolgenden Operationen an. Allerdings hab ich in einem AVR Tutorial gelesen das man auf 8 Bittern wohl lieber trunc nimmt als round und nur mit soviel Genauigkeit rechnet wie man braucht. Hier kommt ja auch nochmal das Typecasting in Betracht, da dein /10 immer auf eine Kommazahl hinauslauft während der Ergebnistyp Ganzzahlig ist. Was ist schneller? Casting wenn die Zahl schon ganzzahlig ist oder erstmal ganzzahlig durch das Casting gemacht werden muss? Auf breiten Registern und dreistelligen MHz Zahlen fällt das wohl wenig oder nur bei zeitkritischen Berechnungen ins Gewicht.

Otto
Zitieren
Hallo Otto,

ok, danke dir für die umfassende Erklärung der eigentlich einfachen Funktion. Stumpfes Abschneiden der Nachkommastelle ergab für mich keinen Sinn.
Ein schnellerer Programmlauf klingt für mich logisch, wird bei dem einfachen Programm vermutlich kaum Messbar sein.

Gruß Frank
sicheres Auftreten bei völliger Ahnungslosigkeit
Zitieren
Wenn man trunc() als "Ersatz" fürs Runden nutzt, kann zuvor 0,5 addieren, dann wird wirklich gerundet.

Gruß Ingo.
Die Konvergenz der Apokalypse führt unweigerlich zur Hybris.
Zitieren
@Ingo
stimmt, das ist ein einfacher Trick, merke ich mir.
sicheres Auftreten bei völliger Ahnungslosigkeit
Zitieren


Möglicherweise verwandte Themen…
Thema Verfasser Antworten Ansichten Letzter Beitrag
  Einsteigerkurs Microcontrollerprogrammierung Bernhard45 13 9.926 23.05.2020, 23:55
Letzter Beitrag: Bernhard45

Gehe zu: