Arduino: digitalRead in Interrupt ist unzuverlässig

OP #7841761
Lesenswert?

Hallo Allerseits,

ich habe folgendes Problem. Ich habe ein System mit 6 DC12V Motoren, jeweils mit einer HAL Sensor. Da Stepper Motoren von der Leistung her nicht gereicht haben, baue ich ein Portal (wie bei einer 3D drucker) statt mit stepper auf DC Motorenbasis.

Da ich 5 Motoren habe und eigentlich 10 Interrupts benötige aber Arduino nur 2 und Mega 6, benutze ich eine Mega. Eine ESP32 kommt aufgrund von wenige GPIO Anzahl nicht in Frage: Ich benutze derzeit alle PWM Pins, 5 Interrupt Pins, alle digitalen PINS bis auf 6 Stück und weil auf der PCB es günstig gelegen ist, verwende ich 5 der AnalogInputs als Digital Input für 4 HX711.

Da ich nur 6 External Interrupts habe und eigentlich 12 bräuchte, verwende ich die günstige Lösung, wo ich die steigende Flanke Interrupts und den Zustand der anderen HAL Ausgang des Motors abfrage. Siehe Code:

Pin Definition:

1
int hallInterruptPins[MOTOR_AMOUNT] = {2, 3};
2
int hallPins[MOTOR_AMOUNT] = {22, 24};
1
void setup()
2
{
3
  Serial.begin(115200);
4
  pinMode(hallInterruptPins[0], INPUT);
5
  pinMode(hallPins[0], INPUT);
6
  pinMode(hallInterruptPins[1], INPUT);
7
  pinMode(hallPins[1], INPUT);
8
  attachInterrupt(digitalPinToInterrupt(hallInterruptPins[0]), handleEncoderMotor<0>, RISING);
9
  attachInterrupt(digitalPinToInterrupt(hallInterruptPins[1]), handleEncoderMotor<1>, RISING);

und die altbekannte auf die Steigende Flanke hören Lösung:

1
template <int MotorIndex>
2
void handleEncoderMotor()
3
{
4
    if (digitalRead(hallPins[MotorIndex]) == LOW)
5
    {
6
        hallCounters[MotorIndex]++;
7
    }
8
    else
9
    {
10
        hallCounters[MotorIndex]--;
11
    }
12
}

Das ist, wer hätte gedacht, deutlich unzuverlässiger als ich antizipiert habe. Es funktioniert gerade mal 20% der Zeit. Die Unzuverlässigkeit zeigt sich, dass die hallCounter immer ++ gemacht wird, da hallPins[0/1] immer LOW sind (nicht bestätigt weil Osci nicht vorhanden).

Hier (https://www.mikrocontroller.net/articles/Drehgeber) steht, dass man es nicht so macht, geht aber keine Lösung für mein Problem hervor.

Hier (Beitrag "Re: Drehknopf mit Arduino auslesen") hat jemand vollständig auf Interrupts verzichtet. Bei einem Umfeld, wo während der Motor läuft, auch andere Sachen passieren werden (Lichtschranke und Endschalter auslesen, mit HX711 wiegen etc.) ist es so sicher wie Amen in der Kirche, dass ich Steps verlassen werde...

Mir ist gerade die Kreativität ausgegangen, muss ich gestehen. Mehrere Arduinos für die 12 Interrupt pins? Jedes Motor hat ein eigener Arduino Nano? Würde theoretisch funktionieren, wenn da nicht die Synchronisation von zweier Paare wären (Vor Zurück so wie Hoch runter Achsen werden von zwei Motoren betrieben). Da müsste ich ja die Sychronisationinformation über i2c oder serial senden und das wird vermutlich relativ schnell unsychnron (oder mache ich mir vielleicht zu viele Gedanken?)

Vielleicht ist auch Arduino die falsche Lösung? Gibt es eine external Interrupt extender chip, wie es für die GPIO gibt? Ich habe nicht nur nichts gefunden, viel mehr kann ich mir technisch nicht vorstellen, wie so was gehen sollte: Ein Interrupt Pin wird belegt und der liest den Zustand über die SPA / I2C, was schnarch langsam ist? Kann ich mir nicht vorstellen. Wie geht ihr in euren Projekten vor, die mehr als 2 / 6 Interrupts benötigen und viele GPIO Pins notwendig haben.

Vielleicht wichtige Nebeninfo: Das sind Motoren mit einer Getriebe davor und haben 8000rpm = 133 triggers pro Sekunde. Sollte jetzt nicht unbedingt die Arduino in die Knie zwingen...

Viele Grüße Stefan

OP #7841769
Lesenswert?

Harald K. schrieb:

Und, nebenbei, hast Du verifiziert, daß Deine Template-ISR genauso schnell ist wie zwei separate ISRs?

Lohnt sich auf jeden Fall mal zu probieren. Danke für die Gedankenstoß.

Harald K. schrieb:

Hast Du Dir mal Gedanken über die Frequenz gemacht, mit der Deine Interrupts da einlaufen?

Klar: Ich würde jetzt mal behaupten, dass 133 Hz * 2 (weil zwei Motoren) bei einer 20MHz Prozessor erstmal irrelevant sei. Denn aber lasse ich mich gerne von Gegenteil überzeugen.

#7841779
Lesenswert?

Stefan schrieb:

Da ich 5 Motoren habe und eigentlich 10 Interrupts benötige aber Arduino nur 2 und Mega 6,

Unfug. Beide haben DEUTLICH mehr interruptfähige Pins, praktisch ALLE Pins können den Pin Change Intzerrupt. Nur daß das eben von der Aduino-IDE nicht untestützt wird. Muss man selber "per Hand" machen.

Da ich nur 6 External Interrupts habe und eigentlich 12 bräuchte, verwende ich die günstige Lösung, wo ich die steigende Flanke Interrupts und den Zustand der anderen HAL Ausgang des Motors abfrage.

Was sollen denn deine Interrupts überhaupt machen? Laß mich raten? Drehgeber dekodieren? Wenn ja, macht man das so oder so anders. Nämlich mit einem Timer-Interrupt und periodischer Abfrage. Siehe Artikel Drehgeber.

Das ist, wer hätte gedacht, deutlich unzuverlässiger als ich antizipiert habe.

Soso, du antipizierst also? Die Germanen erwarten da eher was.

Es funktioniert gerade mal 20% der Zeit. Die Unzuverlässigkeit zeigt sich, dass die hallCounter immer ++ gemacht wird, da hallPins[0/1] immer LOW sind (nicht bestätigt weil Osci nicht vorhanden).

Also reine Spekulation. So sieht eine saubere Problemanalyse aus. Hab ich voll antizipiert . . .

Hier (https://www.mikrocontroller.net/articles/Drehgeber) steht, dass man es nicht so macht, geht aber keine Lösung für mein Problem hervor.

Bist du blind?

Hier (Beitrag "Re: Drehknopf mit Arduino auslesen") hat jemand vollständig auf Interrupts verzichtet. Bei einem Umfeld, wo während der Motor läuft, auch andere Sachen passieren werden (Lichtschranke und Endschalter auslesen, mit HX711 wiegen etc.) ist es so sicher wie Amen in der Kirche, dass ich Steps verlassen werde...

Was? Wu meinst wohl eher, daß du Schritte auslassen könntest bzw. nicht erfassen könntest?

Mir ist gerade die Kreativität ausgegangen, muss ich gestehen. Mehrere Arduinos für die 12 Interrupt pins?

Blödsinn^3.

unsychnron (oder mache ich mir vielleicht zu viele Gedanken?)

Mach es einfach wie der Rest der Welt. Normal mit bewährten Konzepten!

Vielleicht ist auch Arduino die falsche Lösung?

Nö. Der kann das locker, wenn man nur weiß wie.

Gibt es eine external Interrupt extender chip, wie es für die GPIO gibt?

Unsinn.

Vielleicht wichtige Nebeninfo: Das sind Motoren mit einer Getriebe davor und haben 8000rpm = 133 triggers pro Sekunde. Sollte jetzt nicht unbedingt die Arduino in die Knie zwingen...

Schnarchlangsam. Ein 1kHz Timer-Interrupt reicht da locker. Und wenn es 2-5kHz sein müssen geht das auch.

#7841792
Lesenswert?

Stefan schrieb:

Klar: Ich würde jetzt mal behaupten, dass 133 Hz * 2 (weil zwei Motoren) bei einer 20MHz Prozessor erstmal irrelevant sei.

Ja, nur ein paar hundert Hertz sollte Dein 8-Bit-AVR problemlos schaffen. Tut er das nicht, geht irgendwo in Deinem Code etwas schief.

Aber um in diesem Frequenzbereich zyklische Signale zu erfassen, braucht man wirklich keine Interrupts (außer einen schnelleren Timer-Interrupt). Das kann man /problemlos/ mit Polling abwickeln, denn ein paar hundert Hertz sind, wie hier schon andere schrieben, schnarchlahm.

(Firma: Gast) #7841815
Lesenswert?

Harald K. schrieb:

Und, nebenbei, hast Du verifiziert, daß Deine Template-ISR genauso schnell ist wie zwei separate ISRs?

Das hat mit Templates gar nichts zu tun. Natürlich sind 2 Funktionen genauso schnell, wie eine Template Funktion. Schließlich werden auch so 2 Funktionen generiert.

Was aber negativ zu buche schlägt, sind die CallBack des attachInterrupt(), denn die führen zu Ellen langen Push Pop Kaskaden.

#7841819
Lesenswert?

Hallo Stefan,

Stefan schrieb:

Da ich 5 Motoren habe und eigentlich 10 Interrupts benötige aber Arduino nur 2 und Mega 6, benutze ich eine Mega.

Das ist ein Mißverständnis. Die ATmega-Mikrocontroller haben zwei Arten von Pin-Interrupts. Zum einen sind da die dedizierten Interrupt-Pins (INT0 und INT1 beim ATmega328P, INT0 bis INT7 beim ATmega2560). Zum anderen gibt es die Pin-Change-Interrupt-Pins PCINT0 bis PCINT23 (sowohl beim ATmega328P als auch beim ATmega2560).

Der Unterschied ist, dass bei den Pin-Change-Interrupts nicht jeder Pin eine eigene Interrupt-Routine hat, sondern jeweils 8 zusammengefasst werden. Und außerdem müssen die Pin-Change-Interrupts "händisch" programmiert werden, weil attachInterrupt() nur die dedizierten Interrupts unterstützt.

Das ist auf dieser Seite ganz gut erläutert: http://gammon.com.au/interrupts.

INT6 und INT7 sind auf dem Arduino Mega übrigens nicht nutzbar, weil die Pins PE6 und PE7 des Mikrocontrollers nicht verbunden sind, siehe hier: https://content.arduino.cc/assets/MEGA2560_Rev3e_sch.pdf.

Ich dachte, das sei für dich vielleicht interessant, ganz unabhängig von der Diskussion über den besseren Ansatz mit einem Timer-Interrupt.

LG, Sebastian

Gast #7841821
Lesenswert?

Roland E. schrieb:

Klar, für einen bare metal MuC ist das Pillepalle. Beim Arduino ist doch aber noch ein OS dazwischen. Da wird alles über 1kHz zur Glückssache...

Falk B. schrieb:

Beitrag "Re: Arduino Nano, SD Card, PCM" 22kHz PWM Audioausgabe direkt von SD-Karte auf einen normalen Ardino Uno/Mega.

Da hast du alles Zeitkritische aber am Framework vorbei programmiert (hätte ich auch gemacht).

Damit hast du den Roland bestätigt:

für ... bare metal ... ist das Pillepalle

Gast #7841827
Lesenswert?

Jedenfalls lassen sich die I/O Pins alle absolut zuverlässig lesen, auch mit digitalRead(). Du hast da ein ganz andere Problem. Ich würde die Signale mit einem Oszilloskop überprüfen. In der Nähe von Motoren sind Überraschungen nicht selten, vor allem wenn man das noch nicht kennt und daher das Thema "Leitungsführung" vernachlässigt hat.

OP #7842073
Lesenswert?

Zunächst ein mal vielen Dank für eure Antworten! Jeder einzelne von euch.

Falk B. schrieb:

praktisch ALLE Pins können den Pin Change Intzerrupt. Nur daß das eben von der Aduino-IDE nicht untestützt wird. Muss man selber "per Hand" machen.

Soweit ich es weiß, kann ich immer Input Blockweise die Interrupts einfügen. Also du meinst:

  1. Aktiviere Interrupt für den gesamten PCINT Port
  2. Anschließend frage mit if() ab, welche der Pins in dem Block sich von LOW auf HIGH geändert hat, indem ich die alte Zustände in einer Variable(eher Array) speichere und abfrage
  3. Den neuen Zustand in die Array speichere? So kann ich die benötigte 12 Interrupts erreichen und müsste nicht die günstige Variante machen.

Falk B. schrieb:

Was sollen denn deine Interrupts überhaupt machen? Laß mich raten? Drehgeber dekodieren? Wenn ja, macht man das so oder so anders. Nämlich mit einem Timer-Interrupt und periodischer Abfrage. Siehe Artikel Drehgeber.

Ich muss ehrlicherweise gestehen, dass ich den Code da nicht verstehe. Das sieht mir nach sehr low level Programmierung aus. Sei es drum, bitte korrigiere mich, was ich hier falsch sage: 0) Auf external interrupts durch Pin CHANGE/RISING etc. vollständig verzichten.

  1. Motordrehzahl ist 133 Umdrehungen * 127 = Einen Timer mit 16Khz erstellen (Du hast 1KHz geschrieben, in dem Artikel steht aber Frequenz * 127. Warum ist *8 ausreichend statt wie in Doc * 127?)
  2. In dieser Timer die Zustände aller HAL-Pins von allen Motoren auslesen
  3. Je nach LOW / HIGH Zustand der HAL-Pin-Paare den Zähler hoch oder runter Zählen.

Stimmt das so?

Vielen Dank für deine Unterstützung, Falk. Bitte bedenke, dass ich in Mikrokontroller-Programmierung dir sicherlich nicht gewachsen bin, das aber kein Grund darstellt so einen harschen Ton zu haben :)

Falk B. schrieb:

Mach es einfach wie der Rest der Welt. Normal mit bewährten Konzepten!

Wenn man "Arduino encoder tutorial" googled, findet man eigentlich nur Lösungen mit external interrupt pin Lösungen. :) Das mit dem timer und abfragen ist eine völlig neue Lösung für mich. Danke für den Gedankenstoß, werde heute Abend probieren, wenn du die obere drei Punkte bestätigst / ggf. Gedankenfehler korrigierst.

Falk B. schrieb:

Der Mega hat mehr als genug PWM-Ausgänge, auch wenn man die "manuell" konfigurieren muss.

Ebenfalls mit Timer? Ich glaube, das Thema Timer muss ich mir genauer mal ansehen. Das scheint wirklich viele neue Türen zu öffnen, was man als basic arduino Hobby-Entwickler nie verwendet / kennt.

Harald K. schrieb:

Das kann man problemlos mit Polling abwickeln, denn ein paar hundert Hertz sind, wie hier schon andere schrieben, schnarchlahm.

Reden wir von polling innerhalb von loop() oder innerhalb eines Timers? Danke für dein Beitrag!

Arduino F. schrieb:

Was aber negativ zu buche schlägt, sind die CallBack des attachInterrupt(), denn die führen zu Ellen langen Push Pop Kaskaden.

Arbeitest du dann eher mit der manuellen config weg von
PCICR |= (1 << PCIE0) ? Oder was wäre der gängige Weg, um Interrupts anzubinden, ohne die Push Pop Kaskaden?

Sebastian W. schrieb:

Ich dachte, das sei für dich vielleicht interessant, ganz unabhängig von der Diskussion über den besseren Ansatz mit einem Timer-Interrupt.

Ich bin dir wirklich wirklich(!) sehr dankbar, dass du nicht nur "das ist falsch" schreibst, sondern auch den Anspruch hattest, dass ich was lerne. Danke Sebastian. Ich habe es in meiner Rechnerchen irgendwann was ähnliches gefunden. Da habe ich gelernt, man kann den ganzen Block interrupten. Dass es eine maskierfunktion gibt, hat man nicht wirklich erzählt. Das öffnet mir neue Wege. Könnte mir vorstellen, später die sicherheitskritische Stopper / Lichtschrankensensoren damit zu betreiben.

Sherlock 🕵🏽‍♂️ schrieb:

Ich würde die Signale mit einem Oszilloskop überprüfen

Würde ich, wenn ich eine hätte. Ich sollte mir vielleicht langfristig auch eine Anlegen. Ich schaue erstmal, ob das Problem mit Timers gelöst wird.

Peter D. schrieb:

Wozu sollen diese 24Bit ADC dienen?

Nicht für die Motorsteuerung, falls die Frage dazu geht. Da hängt jeweils einen 10kg Load Cell. Ich will das Objekt in der Mitte des Portals aus diversen Gründen wiegen.

Nochmals danke an euch allen für euren Input!

Gast #7842087
Lesenswert?

Stefan schrieb:

Wenn man "Arduino encoder tutorial" googled, findet man eigentlich nur Lösungen mit external interrupt pin Lösungen. :)

Weil sie alle nur voneinander kopieren, ohne wirklich zu verstehen, was sie da tun.

Falk ist von einer anderen Generation. Er liest noch Datenblätter und Fachbücher über grundsätzliche Vorgehensweisen, die es schon lange vor Arduino gab und gut waren.

Die Menschen müssen dringend wieder dahin zurück kommen, sich über eine blinkende LED freuen zu können, und das in allen erdenklichen Varianten durchzuspielen, bevor sie sich an eine Gedanken-gesteuerte KI für ihren Porsche heran wagen. Jeder (auch die größten Experten) muss ganz unten Anfangen, um nicht als Luftpumpe zu enden.

#7842094
Lesenswert?

Stefan schrieb:

Pins können den Pin Change Intzerrupt. Nur daß das eben von der Aduino-IDE nicht untestützt wird. Muss man selber "per Hand" machen.

Soweit ich es weiß, kann ich immer Input Blockweise die Interrupts einfügen. Also du meinst:

  1. Aktiviere Interrupt für den gesamten PCINT Port
  2. Anschließend frage mit if() ab, welche der Pins in dem Block sich von LOW auf HIGH geändert hat, indem ich die alte Zustände in einer Variable(eher Array) speichere und abfrage
  3. Den neuen Zustand in die Array speichere?

Ja.

So kann ich die benötigte 12 Interrupts erreichen

Die brauchst du für diese Anwendung nicht.

und müsste nicht die günstige Variante machen.

Hä?

Falk B. schrieb:

Was sollen denn deine Interrupts überhaupt machen? Laß mich raten? Drehgeber dekodieren? Wenn ja, macht man das so oder so anders. Nämlich mit einem Timer-Interrupt und periodischer Abfrage. Siehe Artikel Drehgeber.

Ich muss ehrlicherweise gestehen, dass ich den Code da nicht verstehe.

Dann musst du halt noch was lernen. Und selbst wenn man die Details nicht versteht, kann man ihn anwenden. Man muss nur periodisch das Stückchen Software ausführen, für jeden einzelnen Drehgeber einmal. Das gibt es auch als Beispiel für mehrere Kanäle.

Beitrag "Re: Mehrere Drehencoder gleichzeitig abfragen"

Das sieht mir nach sehr low level Programmierung aus.

Das ist normale Programmierung. Ist halt kein Clicki Bunti.

korrigiere mich, was ich hier falsch sage: 0) Auf external interrupts durch Pin CHANGE/RISING etc. vollständig verzichten.

Auf jeden Fall!

  1. Motordrehzahl ist 133 Umdrehungen * 127 = Einen Timer mit 16Khz erstellen

Wieso das denn?

(Du hast 1KHz geschrieben, in dem Artikel steht aber Frequenz

    1. Warum ist 8 ausreichend statt wie in Doc 127?)

Weil du was falsch verstanden hast.

  1. Der Timer muss mindestens mit der gleichen Frequenz aufgerufen werden, wie Codewechsel vom Drehgeber kommen. Dein Motor macht max. 133 U/s bzw. ~8000U/min. Wieviel Codes liefert er pro Umdrehung? Sicher nicht nur einen. Mindestens vier, eher deutlich mehr. Und auch dort braucht man einen Sicherheitsfaktor von 2-4. Sprich, wenn dein Drehgeber wirklich nur 1 Code / Umdrehung liefert, dann braucht man ~300-600 Hz Abtastfrequenz. 1kHz ist eine runde Zahl und technisch kein Problem.

2.) Im Interrut wird der Drehgeber dekodiert und in der kleinen Variable delta gespeichert. Die ist im Beispiel aber nur 8 Bit incl. Vorzeichen, kann also nur -128 bis +127 enthalten. D.h. im Extremfall bei voller Drehzahl gibt es nach 128 ISR-Aufrufen einen Überlauf. Den muss man durch ausreichend häufiges Auslesen der Variable mittels der Funktion encode_read() verhindern. Aber diese Mindestfrequenz ist um Faktor 128 KLEINER! Sprich, hier 1kHz/128 ~ 8Hz. D.h. dein Hauptprogramm muss mindestens mit dieser Frequenz die Zähler auslesen und weiterverarbeiten.

  1. In dieser Timer die Zustände aller HAL-Pins von allen Motoren auslesen
  2. Je nach LOW / HIGH Zustand der HAL-Pin-Paare den Zähler hoch oder runter Zählen.

Ja.

Vielen Dank für deine Unterstützung, Falk. Bitte bedenke, dass ich in Mikrokontroller-Programmierung dir sicherlich nicht gewachsen bin, das aber kein Grund darstellt so einen harschen Ton zu haben :)

Dann solltest du aber auch nicht so naseweis hier reinschneien und alle Hinweise aus dem Netz oder den Artikel hier einfach beiseite schieben. Du solltest eher direkt fragen, wie man es richtig macht.

Mach es einfach wie der Rest der Welt. Normal mit bewährten Konzepten!

Wenn man "Arduino encoder tutorial" googled, findet man eigentlich nur Lösungen mit external interrupt pin Lösungen. :)

Es gibt viel Müll im Internet.

Das mit dem timer und abfragen ist eine völlig neue Lösung für mich.

Aber nicht den Rest der welt. Dieses Thema wirde hier schon jahrelang bis zum Wahnsinn diskutiert.

Der Mega hat mehr als genug PWM-Ausgänge, auch wenn man die "manuell" konfigurieren muss.

Ebenfalls mit Timer?

Ja sicher. Ohne Timer keine PWM.

Reden wir von polling innerhalb von loop() oder innerhalb eines Timers?

Beides. Eher schnell im Timer (einige kHz), eher langsam in der loop() Funktion (einige Dutzend Hz).

Ich bin dir wirklich wirklich(!) sehr dankbar, dass du nicht nur "das ist falsch" schreibst, sondern auch den Anspruch hattest, dass ich was lerne. Danke Sebastian. Ich habe es in meiner Rechnerchen irgendwann was ähnliches gefunden. Da habe ich gelernt, man kann den ganzen Block interrupten.

"interrupten", soso. Früher wurde er unterbrochen. Dieses Denglisch ist grausam!

http://kamelopedia.net/wiki/Denglisch

Dass es eine maskierfunktion gibt, hat man nicht wirklich erzählt. Das öffnet mir neue Wege. Könnte mir vorstellen, später die sicherheitskritische Stopper / Lichtschrankensensoren damit zu betreiben.

Sicherheitskristische Funktionen, u.a. Endschalter, sollte man eher als reine Hardware aufbauen, Software ist da deutlich unsicherer.

Wozu sollen diese 24Bit ADC dienen?

Nicht für die Motorsteuerung, falls die Frage dazu geht. Da hängt jeweils einen 10kg Load Cell. Ich will das Objekt in der Mitte des Portals aus diversen Gründen wiegen.

Dann sollte man das aber nicht nebenläufig so in einem Beitrag nennen, denn das verwirrt. Klare Sprache verwenden, kein Denglisch! Siehe Netiquette.

Nochmals danke an euch allen für euren Input!

Früher waren das Hinweise. Oder bist du eine Maschine? Oder ein Chat GPT-Bot?

#7842107
Lesenswert?

digitalRead() ist schon eher gemächlich. Ich habe ein ähnliches Problem (ps2 Mausdaten lesen mittels Interrupt statt Polling) damals durch Benutzung von https://github.com/mikaelpatel/Arduino-GPIO gelöst. Zuvor war oft die Zeit vom Interrupt zum digitalRead()-Ergebnis anscheinend zu lang, die Empfangsroutine unzuverlässig und regelmässige resyncs waren normal. Seit dem Umstieg auf Arduino-GPIO läuft das zuverlässig.

Gast #7842176
Lesenswert?

Stefan S. schrieb:

digitalRead() ist schon eher gemächlich. Seit dem Umstieg auf Arduino-GPIO läuft das zuverlässig.

Wieso zum Teufel "braucht" man eine alternative Bibliothek, um Port- Register zu lesen?

Mir ist klar, was digitalRead() noch alles so macht, um für dummies den schnellen Erfolg zu bieten. Aber wenn man schon so weit ist, dass einem digitalRead() nicht mehr gut genug ist, sollte man sich besser mit dem Datenblatt befassen, anstatt mit anderen Bibliotheken.

Stefan schrieb:

Ich muss ehrlicherweise gestehen, dass ich den Code da nicht verstehe. Das sieht mir nach sehr low level Programmierung aus.

Arduino gibt dir mit seiner arg knappen Dokumentation nur das Gefühl, mehr zu verstehen. Aber in Wirklichkeit verstehst du dein eigenes damit geschriebenes Werk weniger, als Falks Beispiel. Schau dir mal den Quellcode von digitalRead() an, dann siehst du was ich meine. Und das ist erst der Anfang. Das ganze Framework ist alles andere als einfach, es sieht nur auf den ersten Blick so aus. Und genau deswegen wird es so oft missverstanden.

Einfach ist das Datenblatt. Damit sollte man anfangen, wenn man ernsthaft lernen will.

#7842320
Lesenswert?

Sherlock 🕵🏽‍♂️ schrieb:

Wieso zum Teufel "braucht" man eine alternative Bibliothek, um Port- Register zu lesen?

"brauchen" tut man das natürlich nicht, aber ich mache das deswegen mit dem Arduino-Framework, damit ich eben mal schnell einen anderen microcontroller nehmen kann ohne groß an meinem Code zu schrauben. Daß das hier für die Hardcore-Assembler-only-Taliban natürlich eine Gotteslästerliche Sünde ist, dessen bin ich mir bewußt.

Und für diese Portabilität ist die GPIO lib super und auch dafür mag ich das Arduino Framework, selbst wenn mir nicht alles daran gefällt.

Für mich ist das mit der GPIO Bibliothek "gut genug". Wenn ich Fälle habe wo das nicht reicht, dann bin ich in der Lage das zu erkennen und dann habe ich ein paar Bekannte von denen ich weiß daß sie sich mit den Innereien der ganzen Prozessoren wesentlich besser auskennen als ich und gebe den "Auftrag" (meistens ist das irgendwas aus dem eh gemeinsamen Freundeskreis) halt ab.

Ich bin zu alt um noch das Bedürfnis zu verspüren, allem selbst ganz auf den Grund zu gehen. Mir reicht es im zweifelsfall jemand zu kennen der das kann.g

Joachim B. schrieb:

dafür wurde digital fast read erfunden https://www.instructables.com/Fast-digitalRead-digitalWrite-for-Arduino/

Der war flach, der Witz.

#7842491
Lesenswert?

Na gut, da fehlt mir jetzt tatsächlich das tiefere Verständnis wie das ganz genau intern als Maschinencode aus dem Compiler rauskommt, aber nachdem ich kurz in die Quellen von Arduino-GPIO und dann das DIO2 geschaut hab, kann ich jetzt nicht feststellen, wo das offensichtlich soviel besser sein soll.

Am Ende ist es nicht anderes als "digitalRead() ist eher gemächlich, deswegen nimmt man was anderes".

Ich hab mich an das interface von Arduino-GPIO gewöhnt und mir ist es schnell genug, ob DIO2 noch ein paar nanosekunden rauskitzelt kann ich nicht beurteilen. Wenn man das aus dem Arduino library manager (oder von github) holt, dann kann es auch ein paar boards mehr aber immer noch weniger als Arduino-GPIO (was in meinem Fall allerdings nicht so wichtig wäre)

Das mit dem "selbst machen ohne Framework" war ja in dem Flachwitz beschrieben ;-)

#7842495
Lesenswert?

Stefan S. (seife) 10.03.2025 08:04

Na gut, da fehlt mir jetzt tatsächlich das tiefere Verständnis wie das ganz genau intern als Maschinencode aus dem Compiler rauskommt, aber nachdem ich kurz in die Quellen von Arduino-GPIO und dann das DIO2 geschaut hab, kann ich jetzt nicht feststellen, wo das offensichtlich soviel besser sein soll.

Du könntest es praktisch testen: Einfach die verschiedenen Versionen von digitalRead in eine Schleifen mit 10.000 Mal lesen stecken und mittels start=micros() die Zeit messen.

#7842638
Lesenswert?

Ich habe jetzt das Datenblatt zu Deinen Drehgebern leider nicht gefunden, aber anhand des Templates rate ich jetzt mal, dass jeder einen Geschwindigkeits- und einen Richtungsausgang hat. Wenn die, wie üblich, Open-Drain sind, gibt es auch die Möglichkeit zu schnell statt zu langsam zu lesen.

Speziell wenn man die Pull-Up-Widerstände arg hochohmig macht. Dann kann der Drehgeber seinen Ausgang relativ niederohmig und damit zügig nach low ziehen. Lässt der Drehgeber das Signal dann wieder los, wird es vom Pull-Up wesentlich langsamer, weil hochohmiger, auf high gezogen.

Kommt also jetzt nach einem Wechsel der Drehrichtung eine Flanke von high nach low aus dem Geschwindigkeitsausgang, während der Richtungsausgang jetzt high sein müsste, kann es sein, dass der Puls in die falsche Richtung gezählt wird, weil das Richtungssignal noch dabei ist gen high zu kriechen.

Dagegen spricht nur, dass Du in 80% der Fälle Mist misst. Das wäre bei nur diesem Fehler wesentlich weniger.

(Firma: Jasinski) #7842986
Lesenswert?

Stefan S. schrieb:

(..) Daß das hier für die Hardcore-Assembler-only-Taliban natürlich eine Gotteslästerliche Sünde ist, dessen bin ich mir bewußt. Und für diese Portabilität ist die GPIO lib super und auch dafür mag ich das Arduino Framework, selbst wenn mir nicht alles daran gefällt.

Das hat primär nichts mit Assembler zu tun, es sind einfach nur Grundlagen in C-Sprache und Digitaltechnik, bei denen man sich weigert, sie einfach einmal zu erlernen. Hinzu kommt noch oft die beliebte Aversion, Trägheit oder geradezu phobieartige Panik, Datenblätter zu öffnen und an der entsprechenden Stelle zu lesen, um die Mechanik der I/O-Ports des gewählten µControllers zu begreifen; viele Arduinojünger wissen gar nicht, welcher µC auf dem gekauften Board sitzt und wo er überhaupt sitzt, insofern erübrigt sich dann auch die Frage nach irgendeiner entsprechenden Stelle im Datenblatt. In der Regel ist es so, dass, wenn man es einmal richtig begriffen hat, man es für immer begriffen hat und dieses Wissen bzw. Vorgehensweise dann immer auf andere µC übertragen kann. Mit Grundlagen in C ist in diesem Fall das Anden, Oren und Xoren gemeint – wer sich all dem aber bewusst entziehen will oder alles verweigert, der sollte vielleicht lieber Tischtennis spielen oder darf sich dann halt weiter mit Arduino-Pseudo-Befehlen, die übrigens ganze Bildschirme mit Asseblerbefehlen füllen (können), herumärgern und wundern, das zeitkritische Codeteile nicht wie gewünscht funktionieren werden – richtig in C geschrieben generiert der Compiler an so einer Stelle in der Regel nur eine Zeile Assemblercode. So eine Verandung oder Verorung schreibt man (in C) dann nur einmal als #define-Zeile und verwendet im Code immer wieder nur die selbstausgedachten, lesbaren Synonyme wie LED1_On oder Buzzer_AUS, was natürlich viele auch nicht tun, weil sie diese Art des Codeschreibens gar nicht kennen oder kennengelernt haben, da viele Beispiele im Netz ziemlich dumm geschrieben sind und jeder von jedem den generierten Blödsinn kopiert und weiterverbreitet. Es gibt natürlich noch andere Arten und Methoden, die Portausgänge eines µControllers atomar zu beeinflussen – das beste Beispiel dafür sind die STM32 oder die neuen AVR-Familien, wo man die Innereien grundlegend für diese Zwecke komplett überarbeitet und dafür ausgelegt hat. Wer aber weiter Arduino spielen will, der darf das selbstverständlich weiter tun und man sollte ihn am besten auch gar nicht daran hindern. Wem das richtige Ansprechen der Ports egal ist, dem sind in der Regel auch andere Dinge (im Code, Design und Aufbau) so ziemlich egal bzw. in seinem Ermessen nicht so relevant, was hier vermutlich als Kombination dieser Dinge schön zusammengelaufen ist – diese Analogie trifft leider immer wieder zu. Wie auch immer – ich wünsche jedem arduinoverliebten viel Spaß mit digitalWrite, digitalRead & Co, denn den wird er früher oder später bestimmt in irgendeiner Form schon bekommen.

#7842996
Lesenswert?

Das ist wohl auch eine Frage auf welchem Abstraktions-Level man sich wohl fühlt. Ich fahre auch Auto ohne mich mit Verbrennertechnik auszukennen. Bei uC komme ich von der Hardwareseite und programmiere meine AVRs folgerichtig in C mit dem Datenblatt daneben. Und, wie zuvor beschrieben kann ich dann meine Variablen und Co-Prozeduren genauso benennen wie es mir am besten gefällt. Das ist ein nicht unwesentlicher Anteil an der Programmierarbeit. Das ist natürlich nicht jedermanns Sache, abstraktere Hochsprachen mit den entsprechenden Bibliotheken haben unbestreitbar ihren Platz.

(Firma: Gast) #7843008
Lesenswert?

Gregor J. schrieb:

das richtige Ansprechen der Ports

Und du bist der Dominator, welcher bestimmen darf, was "richtig" ist.

Natürlich darfst du Arduino (und alle seine User) für blöd halten. Aber lass dir gesagt sein, dass es in weiten Teilen C++ ist. Nur die Basis Funktionen des Frameworks sind in C, z.B. digtalWrite() und seine Kumpels. C alleine ist da also nicht der Heilsbringer.

OP #7845046
Lesenswert?

Ok ich habe jetzt die letzten Tage damit verbracht, das neue Wissen hier aus dem Forum umzusetzen. Jetzt funktioniert es, aber ohne Timer ISR, sondern weiterhin mit external Interrupts. Schreibe gleich mehr zu meiner Reise.

Ich habe dank euch gelernt, dass Arduino Framework langsam ist und dass es Möglichkeiten gibt, das Framework umzugehen. Dafür bin ich dankbar. Es öffnete sich für mich eine ganz neue Dimension der Arduino Entwicklung!

Es gibt aber ein Grund, weshalb Frameworks existieren. Bessere Code-Lesbarkeit, einfachere Code-Wartung, deutlich einfachere Einstieg für größere Teams. Es gibt ein Grund, weshalb wir nicht mehr in COBOL oder Assembler programmieren. Derzeit dominieren eher Programmiersprachen wie Javascript, Java oder C#.

Kann mir keiner Erzählen, dass dieser Code hier

1
TCCR0 = (1<<WGM01) | (1<<CS01) | (1<<CS00);
2
OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);
3
TIMSK |= 1<<OCIE0;

leserlicher und verständlicher wäre als

1
initTimer(int prescaler);

Selbst die Variablennamen verstoßen den Clean Code. Verstehe aber, dass es eine technische Einschränkung ist, mit dem man leben muss.

Sei es drum, man spielt die Hand, die geteilt wurde. Ich habe zunächst ein ISR Erstellt und mit 3KHz. Aber den habe ich einfach nicht zum Laufen bekommen. Vielleicht können wir gerne auch da ansetzen:

1
void setupTimer() {
2
 noInterrupts();  // Disable global interrupts while setting up the timer
3

4
    // Timer1 Configuration
5
    TCCR1A = 0;  // Reset Timer1 Control Register A
6
    TCCR1B = 0;  // Reset Timer1 Control Register B
7
    TCNT1 = 0;   // Reset Timer1 Counter Register
8

9
    
10
    const uint16_t COMPARE_MATCH_VALUE = 6665; // 16.000 KHz / 3 KHz / 8 (Prescaler) -1
11
    OCR1A = COMPARE_MATCH_VALUE;  
12

13
    // Set Timer1 to CTC mode (Clear Timer on Compare Match)
14
    const uint8_t CTC_MODE = (1 << WGM12);
15
    TCCR1B |= CTC_MODE;
16

17
    // Set prescaler to 8 (16 MHz / 8 = 2 MHz timer clock)
18
    const uint8_t PRESCALER_8 = (1 << CS11);
19
    TCCR1B |= PRESCALER_8;
20

21
    // Enable Timer1 Compare Match A Interrupt
22
    const uint8_t ENABLE_TIMER_INTERRUPT = (1 << OCIE1A);
23
    TIMSK1 |= ENABLE_TIMER_INTERRUPT;
24

25
    interrupts(); // Enable global interrupts
26
}

Wobei ich schon kopieren des Codes merke, dass mein Zähler nicht 3 Khz läuft, sondern nur 300 Hz (16000/3/8 = 666, nicht 6666). Trotzdem hat ein timerCalled++ hat bei einer serial.println() außerhalb der timer und innerhalb des loop() 1 ausgegeben.

Deswegen habe ich auf die Idee von Falk eingesprungen: CHANGE Trigger über Port und Masking:

1
activateInterruptOnPortA() {
2
 cli();  // Disable interrupts while configuring
3

4
    PCICR |= (1 << PCIE0);  // Enable PCINT for PORTA
5
    PCMSK0 |= (1 << PCINT0) | (1 << PCINT2); // Enable PCINT0 (Pin 22) & PCINT2 (Pin 24)
6

7
    sei();  // Enable interrupts
8
}
9

10
ISR(PCINT0_vect) {
11
    called++
12
}

Auch hier blieb called bei 0. Wenn ich aber innerhalb der loop digitalRead(22) oder sogar (PINA & (1 << PA0)) ausgeben lasse, sehe ich aber den Sprung von 0 auf 1.

Ich habe es jetzt gelöst, in dem ich keine digitalReads mehr verwende, sondern direkt auf die Register zugreife (PINA & (1 << PA0)) // digitalRead(22)

Damit kann ich später bei refactorings etc. weiterhin arduino code suchen und profitiere von Leistung der direkt auslesen. Auch das aufteilen von template function hat unerwartet performance gebracht...

1
void handleEncoderMotor0()
2
{
3
  if ((PINA & (1 << PA0)) == LOW) // digitalRead(22)
4
  {
5
    hallCounters[0]--;
6
  }
7
  else
8
  {
9
    hallCounters[0]++;
10
  }
11
}

Sherlock 🕵🏽‍♂️ schrieb:

Die Menschen müssen dringend wieder dahin zurück kommen, sich über eine blinkende LED freuen zu können

Was ist mit Fortschritt? Man sollte eher doch auf die Arbeit der letzten Generation aufbauend weiterentwickeln, nicht?

Falk B. schrieb:

Ja sicher. Ohne Timer keine PWM.

Das habe ich mir ebenfalls angeschaut. Interessanter Ansatz. Hätte ich den Timer zum Laufen bekommen, wäre das eine feine Sache. Zwar brauche ich nicht mehr PWM Pins, ist aber ein top Wissen. Danke dafür Falk!

Falk B. schrieb:

Sicherheitskristische Funktionen, u.a. Endschalter, sollte man eher als reine Hardware aufbauen

Magst du mir erklären bzw in die Richtung richtiger Literatur weiterverweisen bitte? Dankeschön :)

Sherlock 🕵🏽‍♂️ schrieb:

Einfach ist das Datenblatt. Damit sollte man anfangen, wenn man ernsthaft lernen will.

Das ist der Unterschied zwischen ernsthaft lernen und lernen, wie man es anwendet. Man muss kein Verständnis von Verbrennermotoren haben, Stutz, Spur zu verstehen oder Einflüsse verschiedener Öle und Ölfüllmengen kennen, um ein Auto zu fahren. Es hilft! Aber es ist nicht notwendig.

Flunder schrieb:

Ich habe jetzt das Datenblatt zu Deinen Drehgebern leider nicht gefunden, aber anhand des Templates rate ich jetzt mal, dass jeder einen Geschwindigkeits- und einen Richtungsausgang hat

Hallo Flunder,

wie im originalpost beschrieben, sind das nur zwei HALL sensoren, die einen Trigger auslösen, wenn der Magnet in deren Richtung gedreht ist. Also gibt es zwei "Buttons" (C1 & C2), die entweder gleichzeitig HIGH sind oder nur eine HIGH andere LOW. Wenn beide HIGH, dreht es sich in Uhrzeiger, wenn der C1 HIGH und C2 LOW, dann dreht es sich gegen die Uhrzeiteger.

Ich danke euch vielmals für euren Input und investierte Zeit. Ich habe sehr viel über Arduino Entwicklung und neue Alternativen von euch gelernt und ist sicherlich nicht das letzte mal, dass ich bei einem Problem mich melde! Schönen Tag noch!

(Firma: Gast) #7845102
Lesenswert?

Stefan schrieb:

Das ist der Unterschied zwischen ernsthaft lernen und lernen, wie man es anwendet. Man muss kein Verständnis von Verbrennermotoren haben, Stutz, Spur zu verstehen oder Einflüsse verschiedener Öle und Ölfüllmengen kennen, um ein Auto zu fahren. Es hilft! Aber es ist nicht notwendig.

Wenn du an den Registern und an der Hardware rum fummelst, brauchst du das Wissen aus dem Datenblatt. Ohne Wenn und Aber!

Sonst bleibt es ein dümmliches stochern im Nebel.

#7845153
Lesenswert?

Stefan schrieb:

Ok ich habe jetzt die letzten Tage damit verbracht, das neue Wissen hier aus dem Forum umzusetzen. Jetzt funktioniert es, aber ohne Timer ISR, sondern weiterhin mit external Interrupts. Schreibe gleich mehr zu meiner Reise.

Schlecht. Der Timer ist das Mittel der Wahl. Er hat auch noch andere Vorteile. U.a. bietet er eine genaue Zeitbasis für deine Software, die einfacher und leistungsfähiger ist als das millis() vom Arduino-Framework.

Kann mir keiner Erzählen, dass dieser Code hierTCCR0 = (1<<WGM01) | (1<<CS01) | (1<<CS00); OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5); TIMSK |= 1<<OCIE0; leserlicher und verständlicher wäre alsinitTimer(int prescaler);

Das hat keiner behauptet.

Sei es drum, man spielt die Hand, die geteilt wurde. Ich habe zunächst ein ISR Erstellt und mit 3KHz. Aber den habe ich einfach nicht zum Laufen bekommen. Vielleicht können wir gerne auch da ansetzen:

Und wo sehe ich deine ISR?

void setupTimer() { noInterrupts(); // Disable global interrupts while setting up the timer // Timer1 Configuration TCCR1A = 0; // Reset Timer1 Control Register A TCCR1B = 0; // Reset Timer1 Control Register B TCNT1 = 0; // Reset Timer1 Counter Register

Unnötig. Einmal komplett setzen reicht.

1
const uint16_t COMPARE_MATCH_VALUE = 6665; // 16.000 KHz / 3 KHz / 8

(Prescaler) -1 OCR1A = COMPARE_MATCH_VALUE; // Set Timer1 to CTC mode (Clear Timer on Compare Match) const uint8_t CTC_MODE = (1 << WGM12); TCCR1B |= CTC_MODE; // Set prescaler to 8 (16 MHz / 8 = 2 MHz timer clock) const uint8_t PRESCALER_8 = (1 << CS11); TCCR1B |= PRESCALER_8; // Enable Timer1 Compare Match A Interrupt const uint8_t ENABLE_TIMER_INTERRUPT = (1 << OCIE1A); TIMSK1 |= ENABLE_TIMER_INTERRUPT; interrupts(); // Enable global interrupts }

Dass ist akademischer Firlefanz.

ein timerCalled++ hat bei einer serial.println() außerhalb der timer und innerhalb des loop() 1 ausgegeben.

Ja, super. Und wo sehen WIR deinen VOLLSTÄNDIGEN Quelltext? Siehe Netiquette!

Sicherheitskristische Funktionen, u.a. Endschalter, sollte man eher als reine Hardware aufbauen

Magst du mir erklären bzw in die Richtung richtiger Literatur weiterverweisen bitte? Dankeschön :)

Puhhh, gute Frage. Kann ich dir leider so einfach nix nennen. Und in Normen für Maschinensicherheit will man auch nicht lesen, das ist eine Sprache und Welt für sich.

wie im originalpost beschrieben, sind das nur zwei HALL sensoren, die einen Trigger auslösen, wenn der Magnet in deren Richtung gedreht ist.

Das klingt nach den Lagesensoren eines BLDC Motors.

OP #7845160
Lesenswert?

Falk B. schrieb:

Das klingt nach den Lagesensoren eines BLDC Motors.

Genauso ist es!

Falk B. schrieb:

Puhhh, gute Frage. Kann ich dir leider so einfach nix nennen. Und in Normen für Maschinensicherheit will man auch nicht lesen, das ist eine Sprache und Welt für sich.

Ja. Ich dachte mir vielleicht Interrupts. Als Endschalter gibt es bei 3D Drucker eher als Positionsmelder, wohingegen CNC tatsächlich als Not-Aus funktioniert (zumindest mein billig 3018).

Falk B. schrieb:

VOLLSTÄNDIGEN Quelltext

Nicht auf git committed, weil nicht funktioniert. Somit ist es für immer weg. Ich versuche heute Abend nach der Arbeit mal ein Unabhängiges ISR erneut aufzusetzen mit volatile variable. Wenn ich die nicht-Funktionalität reproduzieren kann, werde ich es hier posten.

Falk B. schrieb:

einfacher und leistungsfähiger ist als das millis() vom Arduino-Framework

Glaube ich sofort! Ich probiere heute Abend nach der Arbeit nochmal und poste meine Resultate. Danke Euch allen

#7845185
Lesenswert?

Stefan schrieb:

Puhhh, gute Frage. Kann ich dir leider so einfach nix nennen. Und in Normen für Maschinensicherheit will man auch nicht lesen, das ist eine Sprache und Welt für sich.

Ja. Ich dachte mir vielleicht Interrupts.

Die allermeiste Software wird als unsicher angesehen. Sicherheits gibt es meistens nur durch echte, einfache, robuste Hardware. Sicherheitszertifizierte Software ist sehr aufwändig und teuer.

Nicht auf git committed, weil nicht funktioniert. Somit ist es für immer weg. Ich versuche heute Abend nach der Arbeit mal ein Unabhängiges ISR erneut aufzusetzen mit volatile variable. Wenn ich die nicht-Funktionalität reproduzieren kann, werde ich es hier posten.

Siehe Anhang.

Angehängte Dateien:
Gast #7845273
Lesenswert?

Stefan schrieb:

Kann mir keiner Erzählen, dass dieser Code hier

1
TCCR0 = (1<<WGM01) | (1<<CS01) | (1<<CS00);
2
OCR0 = (uint8_t)(XTAL / 64.0 * 1e-3 - 0.5);
3
TIMSK |= 1<<OCIE0;

leserlicher und verständlicher wäre als

1
initTimer(int prescaler);

Du vergleichst dabei Dinge, die sich nicht vergleichen lassen. Die oberen Zeilen konfigurieren mehrere Funktionen des Mikrocontrollers. Die untere ist die definition einer Funktion, die so alleine erst mal gar nichts macht.

Wenn du vergleichst, musst du den Inhalt der Funktion mit zeigen oder wenigstens detailliert dokumentieren. Und dann ergibt sich, dass die Funktion wahrscheinlich sogar komplexer ist, als die drei einzelnen Zeilen. Mit einem einzigen Aufrufparameter (int prescaler) sind ja nicht mal alle Parameter abgedeckt, die du mit den drei Zeilen einstellst.

Durch Weglassen von Funktionalität sieht die Funktion einfach aus. Wenn man dann auch noch möglichst viele Details in der Dokumentation weg lässt, bist du beim Design-Prinzip von Arduino.

Schau dir als Beispiel mal die Doku von pinMode() an. https://docs.arduino.cc/language-reference/de/en/functions/digital-io/pinMode/

Die Modi beziehen sich offenbar auf eine bestimmte µC Serie. Es ist aber nicht angegeben, welche Pins welche Modi unterstützen. Wer dann meint, er braucht kein Datenblatt/Referenzhandbuch zu lesen, der bleibt schnell stecken.

Beim ESP8266 haben z.B. alle Pins den zusätzlichen Modus INPUT_PULLDOWN. Nur einer nicht, der kann nur INPUT_PULLDOWN_16. Versucht man ihn mit INPUT_PULLDOWN oder INPUT_PULLUP zu konfigurieren, aktiviert man irgendeine völlig andere Funktion. Der Pin ist dann jedenfalls kein Input mehr. Von solchen Fallstricken hat so ziemlich jeder Mikrocontroller etwas zu bieten, der moderner als die alten 8 Bit AVR ist.

Wen etwas erfahrener ist, mag einwenden, dass der ESP8266 kein offizielles Arduino Target ist und man daher in dessen eigener Doku lesen muss. Bitteschön: https://arduino-esp8266.readthedocs.io/en/latest/reference.html#digital-io Dort ist es besser dokumentiert (war nicht immer so). Aber glaube mir, auch diese Doku lässt viele Details aus. Z.B schlagen hier immer wieder Leute auf, die nicht dahinter kommen, warum ihr ESP nicht booten will. Dabei sind alle Voraussetzungen bekannt. Man darf sich nur nicht einbilden, dass Arduino vollständig dokumentiert sei.

Gut, dass man bei Arduino nicht gezwungen ist, alles mit den Bibliotheken zu machen. Man kann immer low-Level programmieren, wenn man will oder muss.

Stefan schrieb:

Was ist mit Fortschritt? Man sollte eher doch auf die Arbeit der letzten Generation aufbauend weiterentwickeln, nicht?

Beides wird gebraucht. Der eine braucht mehr Grundlagen, der andere mehr High-Level.

Man muss kein Verständnis von Verbrennermotoren haben, Stutz, Spur zu verstehen oder Einflüsse verschiedener Öle und Ölfüllmengen kennen, um ein Auto zu fahren.

Um Computer zu benutzen, muss man sie nicht programmieren können. Ohne Grundlagen kann man sie aber nicht gut programmieren. Ich sehe doch ständig auf der Arbeit, wie die Framework Gurus oft keinen blassen Schimmer haben, wie viel Last ihre teilweise dummen Konstrukte auf dem Server produzieren. Die merken das nicht mal, weil sie immer nur alleine manuell testen. Wenn da 1000 User gleichzeitig aktiv sind, kommt es auf ein Megabyte mehr oder weniger durchaus an.

Falk B. schrieb:

Dass ist akademischer Firlefanz.

Schön gesagt. Aber heutzutage muss man ja schon für jeden Kommentar und sprechende Namen dankbar sein.

OP #7845749
Lesenswert?

Falk B. schrieb:

Siehe Anhang.

Ok funktioniert. Vielen Dank! Ich erstelle jetzt ordentliche Module für die Timer - das Projekt wird etwas größer und Ordnung könnte ich gut gebrauchen) Ich baue es jetzt um, wo ich mit diesem Timer die Resultate der PINs 2,3,22,24 etc. auslese und anschließend die Bewegungsmuster der HALLs abbilde statt externe Unterbrecher (attachInterrupt)!

Vielen Dank. Ich dokumentiere anschließend meine Resultate hier!

OP #7845752
Lesenswert?

Sherlock 🕵🏽‍♂️ schrieb:

wahrscheinlich sogar komplexer ist, als die drei einzelnen Zeilen.

Es ist mit Sicherheit komplexer. Muss mich aber nicht darum kümmern. Das ist ja die Idee eines Frameworks. Ein Trade-Off eben. Ich sehe es aber jetzt nach diesem Thread ein, dass dieser Weg für die Enduser-Softwareentwicklung zwar in Zeitalter der starken Hardware in Ordnung ist, in der µC-Embedded-Software allerdings einfach nicht tragbar ist.

Sherlock 🕵🏽‍♂️ schrieb:

wenn man will oder muss.

Der Einstieg in die Arduino Welt ist nun mal einfacher als Registerschieberei.

#7845764
Lesenswert?

Stefan schrieb:

Es ist mit Sicherheit komplexer. Muss mich aber nicht darum kümmern. Das ist ja die Idee eines Frameworks. Ein Trade-Off eben.

Korrekt.

Ich sehe es aber jetzt nach diesem Thread ein, dass dieser Weg für die Enduser-Softwareentwicklung zwar in Zeitalter der starken Hardware in Ordnung ist, in der µC-Embedded-Software allerdings einfach nicht tragbar ist.

Stimmt so nicht. Denn erstens liegt dein Ursprungsproblem nicht in der Nutzung von digitalRead(). Das ist zwar deutlich langsamer als der direkte Portzugriff, ist aber mit ca. 2-3us immer noch schnell genug für die meisten Anwendungen. Und zweitens geht auch bei Embedded System der Trend schon lange zum Hardware Overkill. Man wirft massiv leistungsstarke Hardware auf relativ kleine Probleme, um mit Frameworks schnell ein Ergebnis zu erreichen. Das ist TEILWEISE OK, aber oft auch nicht.

Der Einstieg in die Arduino Welt ist nun mal einfacher als Registerschieberei.

Das ist der Sinn der Sache! Man muss sich nur deren Grenzen bewußt sein!

#7845765
Lesenswert?

Cyblord -. schrieb:

Der Einstieg in die Arduino Welt ist nun mal einfacher als Registerschieberei.

Ich habe noch nie verstanden warum "einfach" ein Kriterium ist. Will ich was einfaches staple ich Bauklötze. Warum will jeder immer "einfach"?

Weil das nun mal ein natürliches Empfinden ist. Kaum einer will was EXTREM schweres am Anfang machen, denn damit schreckt man die allerweisten Leute ab. Klar, wenn man es idiotensicher macht, zieht man viele Idioten an. Das Stichwort lautet Lernkurve.

https://de.wikipedia.org/wiki/Lernkurve

Außerdem ist der innere Antrieb, schwierige, herausfordernde Dinge zu tun, heute in der Masse deutlich geringer. Generation Infotainment. Konsumieren ist einfacher und "angenehmer".

OP #7849561
Lesenswert?

Also ich habe jetzt es auf Timer dank Falks Hilfe umgestellt. Das einzige Limit ist die sehr niedrig eingeschätzte Abtastrate. Alles unter 8kHz ist zu langsam für die beiden Motoren, die jeweils mit 133Hz drehen und pro Umdrehung 110 Triggers verursachen.Nachdem ich den Denkfehler, dass ich pro Sekunde ca 14.000 triggers habe mit einer 64 prescaler 16kHz timer gelöst habe, funktioniert es einwandfrei.

Für die zukünftigen, die auf diesem thread stoßen, sieht die ganze Lösung wie folgt aus:

Falk B. schrieb:

Siehe Anhang.

timer.cpp beinhaltet die init_timer Funktion von Falk. Zusätzlich beinhalteter die ISR:

1
ISR(TIMER1_COMPA_vect)
2
{
3
  vorZuruckController.handleVorZuruckTimerInterrupt();
4
}

Dieser ISR wird später noch von den Entitäten hochRunterController.handlerHochRunterTimerInterrupt(); so wie für links rechts aufrufen. Daher eine ISR ruft mehrere Funktionen auf. Ich habe mich gegen eine inline funktion entschieden hier, weil ich Leisungseinbußen befürchte.

Die TimerInterrupt sieht dann wie folgt aus:

1
void VorZuruckController::handleVorZuruckTimerInterrupt()
2
{
3

4
    bool currentPrimaryHallStates[VOR_ZURUCK_MOTOR_AMOUNT] = {getVorZuruck1PrimaryHall(), getVorZuruck2PrimaryHall()};
5
    bool currentSecondayHallStates[VOR_ZURUCK_MOTOR_AMOUNT] = {getVorZuruck1SecondaryHall(), getVorZuruck2SecondaryHall()};
6

7
    for (int i = 0; i < VOR_ZURUCK_MOTOR_AMOUNT; i++)
8
    {
9
        if (previousPrimaryHallPinResults[i] == LOW && currentPrimaryHallStates[i] == HIGH)
10
        {
11
            if (currentSecondayHallStates[i] == LOW)
12
            {
13
                hallCounters[i]--;
14
            }
15
            else
16
            {
17
                hallCounters[i]++;
18
            }
19
        }
20
        previousPrimaryHallPinResults[i] = currentPrimaryHallStates[i];
21
    }
22
}

die get_____Hall() Funktionen sind einfache inline Funktionen:

1
inline bool getVorZuruck1PrimaryHall() { // 2
2
    return PINE & (1 << PE4);
3
}

Später kann man anhand von der hallCounters eben PID durchziehen und die DC Motoren präzise steuern.

Bei Fragen stehe ich gerne zur Verfügung; bleibe auf diesem Thread weiterhin in Beobachtung.

Danke an alle, die geholfen haben

#7849573
Lesenswert?

Stefan schrieb:

Dieser ISR wird später noch von den Entitäten hochRunterController.handlerHochRunterTimerInterrupt(); so wie für links rechts aufrufen.

Hä? Die ISR wird von den "Entitäten" (warum nur dieses Wort?) aufgerufen oder die ISR soll die /Funktionen/ (oder meinetwegen "Methoden") aufrufen?

Daher eine ISR ruft mehrere Funktionen auf.

Also doch Funktionen. Lass die "Entitäten" weg, dann wird vielleicht auch Deine Formulierung verständlicher.

Ich habe mich gegen eine inline funktion entschieden hier, weil ich Leisungseinbußen befürchte.

Gerade das hier ist ein Beispiel, wo eine Inlinefunktion Leistungeinbußen /vermeiden/ hilft, weil nämlich der Overhead, den der Compiler beim Funktionsaufruf erzeugt, dadurch entfallen kann.

OP #7849575
Lesenswert?

Wastl schrieb:

Ich würde sagen: wenn du die Namen stichhaltig auf die Hälfte oder ein Drittel kürzt verstehst du dein Programm sicherlich besser

Was ist deine Empfehlung?

1
cphs[MA] // array holding current hall states of primary pin

statt

1
currentPrimaryHallStates[VOR_ZURUCK_MOTOR_AMOUNT]

Diese Diskussion führe ich wenn dann mit meiner junior Entwicklern. Ihr seid besser als das. Trotzdem empfehle ich dir die Lektüre Clean Code von mipt Verlag.

OP #7849576
Lesenswert?

Harald K. schrieb:

weil nämlich der Overhead, den der Compiler beim Funktionsaufruf erzeugt, dadurch entfallen kann.

Würde aber die Hinteraneinanderreihung der vier Funktionen nicht dazu führen, dass es ein großer Block Code ohne Stack ausgeführt wird? Ernst gemeinte Frage. Die Speicheroptimierung durch inline Funktionen habe ich neu entdeckt und mir fehlt die praktische Erfahrung.

#7849584
Lesenswert?

Stefan schrieb:

Also ich habe jetzt es auf Timer dank Falks Hilfe umgestellt. Das einzige Limit ist die sehr niedrig eingeschätzte Abtastrate. Alles unter 8kHz ist zu langsam für die beiden Motoren, die jeweils mit 133Hz drehen und pro Umdrehung 110 Triggers verursachen.

Das war ja deine (Falsch)aussage, daß da nur ein Code/Umdrehung reinkommt. Hatte mich sehr gewundert. Normale BLDC Motoren mit 3 Phasen haben 3 Codes/U. Wenn das ein einfacher Drehgeber ist, hat der natürlich mehr Auflösung.

Nachdem ich den Denkfehler, dass ich pro Sekunde ca 14.000 triggers habe mit einer 64 prescaler 16kHz timer gelöst habe, funktioniert es einwandfrei.

Du hast so odert so keine "Triggers", sondern bestenfalls Codes.

Für die zukünftigen, die auf diesem thread stoßen, sieht die ganze Lösung wie folgt aus:

Falk B. schrieb:

Siehe Anhang.

timer.cpp beinhaltet die init_timer Funktion von Falk. Zusätzlich beinhalteter die ISR: ISR(TIMER1_COMPA_vect) { vorZuruckController.handleVorZuruckTimerInterrupt(); }

Toller Name . . .

Dieser ISR wird später noch von den Entitäten hochRunterController.handlerHochRunterTimerInterrupt(); so wie für links rechts aufrufen. Daher eine ISR ruft mehrere Funktionen auf. Ich habe mich gegen eine inline funktion entschieden hier, weil ich Leisungseinbußen befürchte.

Jaja, grman Angst. Ein echter, deutscher Ingenieur würde sowas MESSEN, denn dann hat man eien sichere, OBJEKTIVE Aussage. Denn deine Vermutung isst falsch. Das Aufrufen von Funktionen in einer ISR kostet mehr CPU-Last als eine Inline Function . . .

Die TimerInterrupt sieht dann wie folgt aus: void VorZuruckController::handleVorZuruckTimerInterrupt() { bool currentPrimaryHallStates[VOR_ZURUCK_MOTOR_AMOUNT] =

VOR_ZURUCK_MOTOR_AMOUNT, jaja, Degnglish at its Pest . . .

{getVorZuruck1PrimaryHall(), getVorZuruck2PrimaryHall()}; bool currentSecondayHallStates[VOR_ZURUCK_MOTOR_AMOUNT] = {getVorZuruck1SecondaryHall(), getVorZuruck2SecondaryHall()}; for (int i = 0; i < VOR_ZURUCK_MOTOR_AMOUNT; i++) { if (previousPrimaryHallPinResults[i] == LOW && currentPrimaryHallStates[i] == HIGH) { if (currentSecondayHallStates[i] == LOW) { hallCounters[i]--; } else { hallCounters[i]++; } } previousPrimaryHallPinResults[i] = currentPrimaryHallStates[i]; } }

Deine Dekodierung eines Drehgebers ist immer noch Unfug. Lernresistenz?

Danke an alle, die geholfen haben

Naja, du hast weniger gelernt als du glaubst.

#7849587
Lesenswert?

Stefan schrieb:

Was ist deine Empfehlung? cphs[MA] // array holding current hall states of primary pin

statt currentPrimaryHallStates[VOR_ZURUCK_MOTOR_AMOUNT]

Diese Diskussion führe ich wenn dann mit meiner junior Entwicklern. Ihr seid besser als das. Trotzdem empfehle ich dir die Lektüre Clean Code von mipt Verlag.

Du bist mir ja ein gaaanz Schlauer! Hast gestern das Buch gelesen und verkündest heute schon endgültige Wahrheiten! Respekt!

#7849591
Lesenswert?

Stefan schrieb:

Was ist deine Empfehlung? cphs[MA] // array holding current hall states of primary pin

statt currentPrimaryHallStates[VOR_ZURUCK_MOTOR_AMOUNT]

Es mag Dich überraschen, aber es gibt einen Mittelweg.

Stefan schrieb:

void VorZuruckController::handleVorZuruckTimerInterrupt()

Kann der VorZuruckController noch andere VorZuruck-Interrupts auswerten?

Wenn nein: Dann reicht

void VorZuruckController::handleTimer()

OP #7849594
Lesenswert?

Falk B. schrieb:

Deine Dekodierung eines Drehgebers ist immer noch Unfug. Lernresistenz?

Wie würdest du es denn machen?

Und ich bitte dich. Bevor du mir den Artikel mit das hier schickst...

1
 int8_t code, diff, tmp;
2

3
  tmp  = ENCODER_PIN;
4
  code = 0;
5
  if ( tmp & PHASE_A ) code  = 3;
6
  if ( tmp & PHASE_B ) code ^= 1;   // convert gray to binary
7
  diff = last - code;               // difference last - new
8
  if( diff & 1 ) {                  // bit 0 = value (1)
9
    last = code;                    // store new as next last
10
    enc_delta += (diff & 2) - 1;    // bit 1 = direction (+/-)
11
  }

erkläre es gerne. Dieser Code ist 0 Verständlich und kaum Wartbar für ein µC Programmier-Laie

Viele Grüße

#7849598
Lesenswert?

Stefan schrieb:

Deine Dekodierung eines Drehgebers ist immer noch Unfug. Lernresistenz?

Wie würdest du es denn machen?

Richtig.

Und ich bitte dich. Bevor du mir den Artikel mit das hier schickst... int8_t code, diff, tmp; tmp = ENCODER_PIN; code = 0; if ( tmp & PHASE_A ) code = 3; if ( tmp & PHASE_B ) code ^= 1; // convert gray to binary diff = last - code; // difference last - new if( diff & 1 ) { // bit 0 = value (1) last = code; // store new as next last enc_delta += (diff & 2) - 1; // bit 1 = direction (+/-) }

erkläre es gerne. Dieser Code ist 0 Verständlich

Stimmt. Das ist ultrakompakter Code mit vielen Tricks bei der Optimierung.

und kaum Wartbar für ein µC Programmier-Laie

Ist das ein Maßstab, daß Laien sowas warten können? Laien und Profis benutzen Bibliotheken als Black Box, d.h sie benutzen die Funktion von außen und kümmern sich nicht um den internen Aufbau und Funktion. Das reicht meistens.

Wer es schöner und verständlicher will, nimmt den klassischen Code mit Tabelle. Verständlich und wartbar, auch wenn da so gut wie nie was gewartet werden muss.

https://www.mikrocontroller.net/articles/Drehgeber#Dekoder_f%C3%BCr_Drehgeber_mit_wackeligen_Rastpunkten

Dort gibt es auch die Tabelle ohne wackelige Rastpunkte. Die ist am Ende kaum langsamer als die Superfreakoptimierung.

(Firma: 1984now) #7849661
Lesenswert?

Harald K. schrieb:

Sherlock 🕵🏽‍♂️ schrieb:

Lieber lang als zu kurz.

Zwischen lang und viel zu lang liegen aber Welten.

Und wie ich oben beschrieb, im vorliegenden Fall auch komplett überflüssige Redundanz.

Das sehe ich anders.

"Lange" Symbole sind nicht redundant, sondern beschreiben möglichst genau, was ihre Job ist. Deswegen sind sie eben nicht redundant. Sie enthalten für Menschen brauchbare und nützliche Information.

Sie sind also höchstens redundant für die Toolchain. Aber auch "kurze" Symbole sind für die in aller Regel noch ziemlich redundant. Wenn man hier konsequent auf Maschinenlesbarkeit optimieren wollte, müsste man konsequenterweise Symbole nach dem Muster "Irgendein nichtnumerischer Kennbuchstabe" + irgendeine Zahl verwenden. Ist natürlich völlig idiotisch, weil überhaupt nicht mehr menschenlesbar. Deswegen macht das auch niemand. Ich gehe davon aus: Nichtmal du machst das so...

Alles andere ist nur eine Frage des Komforts. Benutzer von modernen IDEs haben kein Problem mit langen Symbolen: Die Auto-Vervollständigung nimmt ihnen die Tipparbeit ab (zumindest nach dem ersten Erscheinen im scope).

Sprich: Nur die Ewiggestrigen, die heute noch allein mit völlig dummen Editoren hantieren, haben damit ein Problem. Aber: was geht uns das Elend dieser Zurückgebliebenen an?

Deren schwachsinnige Lösung ist halt: Halte es so kurz wie (nach eigenem Empfinden) möglich. Was natürlich die unerträgliche Arroganz dieser Leute offen legt. Wenn jemand anders den Quelltext lesen muß, ist nämlich keinesfalls voraussetzbar, dass er die kruden (und vermutlich nirgendwo hinterlegten) Regeln zur Bildung von Symbolen des ursprünglichen Programmierers kennt oder auf Anhieb nachvollziehen kann.

Um das zu können, muß man mindesten schon recht tief im Projekt stecken. Und oft reicht aber nicht mal das, weil der HERR Programmierer selber die Sache selber schon nicht konsequent durchgezogen hat, es also keine Möglichkeit gibt rückwirkend die "Regeln" zu ermitteln, nach denen er die Symbolnamen festgelegt hat.

So sieht das aus.

#7849669
Lesenswert?

Ob S. schrieb:

Zwischen lang und viel zu lang liegen aber Welten.

Und wie ich oben beschrieb, im vorliegenden Fall auch komplett überflüssige Redundanz.

Das sehe ich anders.

"Lange" Symbole sind nicht redundant, sondern beschreiben möglichst genau, was ihre Job ist. Deswegen sind sie eben nicht redundant. Sie enthalten für Menschen brauchbare und nützliche Information.

Das kann so sein. Aber der Übergang von nützlicher Information zu Geschwätzigkeit ist fließend.

Sie sind also höchstens redundant für die Toolchain.

Davon war nie die Rede! Für den Compiler ist die Länge von Namen für Objekte vollkommen schnuppe. Wenn man will, kann man den sogar in UTF-8 in chinesisch compilieren lassen!

Alles andere ist nur eine Frage des Komforts. Benutzer von modernen IDEs haben kein Problem mit langen Symbolen: Die Auto-Vervollständigung nimmt ihnen die Tipparbeit ab (zumindest nach dem ersten Erscheinen im scope).

Stimm, ist aber gar nicht das Problem. Sondern die Geschwätzigkeit und die damit ab einem bestimmten Punkt sinkende Lesbarkeit! Früher hat man Sprachen wie Pascal & Co für ihre Geschwätzigkeit kritisiert. Teilweise zu recht.

Deren schwachsinnige Lösung ist halt: Halte es so kurz wie (nach eigenem Empfinden) möglich.

Das ist, korrekt angewendet, auch die richtige Zeisetzung! Der Name einer Variable bzw. Funktion sollte so kurz wie möglich und gleichzeitig so aussagekräftig wie möglich sein. Daß man da einen Kompromiss finden muss und das individuell zu verschiedenen Ergebnissen führt, ist klar.

Was natürlich die unerträgliche Arroganz dieser Leute offen legt. Wenn jemand anders den Quelltext lesen muß, ist nämlich keinesfalls voraussetzbar, dass er die kruden (und vermutlich nirgendwo hinterlegten) Regeln zur Bildung von Symbolen des ursprünglichen Programmierers kennt oder auf Anhieb nachvollziehen kann.

Von kruden, unverständlichen Abkürzungen war nie die Rede. Du erfindest nur Aussagen, die nie gemacht wurden, um sie dann "glanzvoll" widerlegen zu können. Ein uralter Trick.

Um das zu können, muß man mindesten schon recht tief im Projekt stecken. Und oft reicht aber nicht mal das, weil der HERR Programmierer selber die Sache selber schon nicht konsequent durchgezogen hat, es also keine Möglichkeit gibt rückwirkend die "Regeln" zu ermitteln, nach denen er die Symbolnamen festgelegt hat.

Solche Projekte gibt es, aber die sind nicht eine Sekunde das Thema hier. Setzen, Sechs! Thema verfehlt!

So sieht das aus.

Nö.

#7849689
Lesenswert?

Harald K. schrieb:

"Lange" Symbole sind nicht redundant, sondern beschreiben möglichst genau, was ihre Job ist.

Betrachte bitte das genaue Beispiel, das ich meine.

Beitrag "Re: Arduino: digitalRead in Interrupt ist unzuverlässig"

In der Tat. Da ist es eher kindliche Naivität, die amüsiert.

1
void VorZuruckController::handleVorZuruckTimerInterrupt()

Jeder Programmierer mit ausreichend Erfahrung und ohne ideologischen Schaden, welcher Art auch immer, würde das deutlich anders nennen. Z.B.

1
void Servo::control()
2
void PosRegler::Zyklus()

Denn das ist ein "Vor Zurück Controller", ein Positionsregler, neudeutsch Servo.

https://de.wikipedia.org/wiki/Servo

Servus! ;-)

Die Kunst der sinnvollen Namensgebung erfordert Übung. Kurz und prägnant sollten Namen sein, keine Romane oder Lebensgeschichten und auch kein Beamtendeutsch! Aber auch keine autistischen Abkürzungen oder Fragmente aus der 8.3 DOS-Dateinamenszeit!

(Firma: 1984now) #7849694
Lesenswert?

Harald K. schrieb:

Ob S. schrieb:

Das sehe ich anders.

"Lange" Symbole sind nicht redundant, sondern beschreiben möglichst genau, was ihre Job ist.

Betrachte bitte das genaue Beispiel, das ich meine.

Beitrag "Re: Arduino: digitalRead in Interrupt ist unzuverlässig"

Habe ich mir angesehen. Und ich würde sagen, dass im gegebenen Kontext für einen Menschen handleVorZuruckTimerInterrupt() deutlich aussagekräftiger ist als handleTimer().

Man sieht sofort: es geht um die Bewegung und die Quelle des Ereignisses ist ein Timer-Interrupt (der eigentlich von seiner Natur her mit einer Bewegung nix zu schaffen hat, sondern hier konkret halt dazu benutzt wird, eben diese zu steuern)

#7849705
Lesenswert?

Cyblord -. schrieb:

Ich habe noch nie verstanden warum "einfach" ein Kriterium ist

weil es sinnvoller ist als jedesmal das Rad neu zu erfinden Räder nutzen ist.

Deswegen sind ja auch Arduino LIBs erfunden worden, unabhängig davon haben auch ander C LIBs für Standardlösungen im Netz eingestellt.

Es ist wirklich nicht sinnvoll alles immer selbst zu erfinden außer zum Lernen, aber die menschliche Restlebenzeit ist nun mal endlich.

Man kann die alten Griechen, Araber, Inder nur danken für Mathematik, denn alles selber rausfinden? Man lebt ja nicht in einer Tonne und hat Gönner die einem ein Leben sponsorn.

#7849714
Lesenswert?

Ob S. schrieb:

Habe ich mir angesehen. Und ich würde sagen, dass im gegebenen Kontext für einen Menschen handleVorZuruckTimerInterrupt() deutlich aussagekräftiger ist als handleTimer().

Du hast geflissentlich unterschlagen, daß das "VorZuruck" bereits im Klassennamen steht.

Und ein Timerinterrupt ist das nicht, sondern nur eine Funktion, die aus einem Timerinterrupt heraus aufgerufen wird. Die sollte man also besser nicht "Interrupt" nennen.

Eine ansatzweise verständigen Menschen ist es geläufig, daß Funktionen aus einem Timerinterrupt heraus aufgerufen werden, und dann ist der Name "handleTimer" nahliegender und aussagekräftiger.

Wenn man "leichte" oder "inklusive" Programmierung verwenden möchte, um genauer zu erklären, was hier passiert, und auch wirklich jeden mitzunehmen, müsste man die Funktion

1
FunctionToHandleVorZuruckCalledFromTimerInterrupt()

nennen.

Man könnte, wenn der Timerinterrupt einen bestimmten Zyklus hat, in dem er aufgerufen wird, die Funktion auch präziser

1
FunctionToHandleVorZuruckCalledFromTimerInterruptEvery1000msec()

nennen ...

#7849732
Lesenswert?

Norbert schrieb:

Nicht jeder ist im Besitz eines 32:9 Monitors. ;-)

Schon mal was von einer Laufschrift gehört? ;-)

So einen Müll haben wir in einer unserer "Software" für ne Gerätesteuerung. Dort ist die Versionsnummer der Software mit Datum und Zeit dargestellt. Und weil das nicht auf den Bildschirm 16:9(!) passt, scrollt das in der Zeile. Auf so einen Schwachsinn kommen nur Softwerker! Unfähige Softwerker, die nur rumspielen, anstatt ihren Job zu machen.

#7849760
Lesenswert?

Ob S. schrieb:

Ja, durchaus. Was spricht dagegen?

Im Grunde genommen haben wir das falsch betrachtet, denn hier gehört natürlich auch noch der Instanzenname des Objekts dazu.

Der Threadstarter schrieb

1
vorZuruckController.handleVorZuruckTimerInterrupt();

wir haben herausgefunden, daß der bessere Name für die Funktion (oder "Methode") FunctionToHandleVorZuruckCalledFromTimerInterruptEvery1000msec()

ist, also

1
vorZuruckController.FunctionToHandleVorZuruckCalledFromTimerInterruptEvery1000msec();

Was jetzt noch unbefriedigend ist, ist der Instanzenname. Der lautet genauso wie der Klassenname, und unterscheidet sich nur durch einen Kleinbuchstaben:

1
VorZuruckController::FunctionToHandleVorZuruckCalledFromTimerInterruptEvery1000msec();

Das wird eindeutig übersichtlicher mit

1
KlasseVorZuruckController::FunctionToHandleVorZuruckCalledFromTimerInterruptEvery1000msec();

und

1
KlasseVorZuruckController ObjektinstanzVonKlasseVorZuruckController;

was dann im Interrupthandler resultiert im Aufruf von

1
ObjektinstanzVonKlasseVorZuruckController.FunctionToHandleVorZuruckCalledFromTimerInterruptEvery1000msec();

Jetzt wird einem doch erst klar, worum es hier überhaupt geht, nicht wahr?

Sprechende Namen verwenden, sprechende Namen!

OP #7849792
Lesenswert?

Ich bedanke mich recht herzlich für diese Diskussion, ob meine Variablen und Funktionen so heißen, wie es manchen von euch gefällt. Ich habe diese Diskussion zur Kenntnis genommen und mit dem Hauptproblem unbeeinflusst weitergemacht.

@Falk: Danke für den Verweis auf die Tabelle. Korrigiere mich bitte, wenn ich was falsch verstanden habe! Ich versuche das ganze zu erlernen :)

Das Beispiel versucht die Zustandsänderungsverhalten vorher zu berechnen und anschließend das nur auslesen und addieren. Eine andere Tabelle versucht die physikalisch übliche Prellungen abzufangen, also sogenannte debouncing für die Drehgeber zu betreiben.

Erst schiebt er den letzten Zustand um 2 bits (wenn es noch kein Zustand gab, dann 00 nach 2 links = immer noch 00) Dann maskiert er den überlauf, damit wir nur den letzten Zustand haben (& 0x0F) Dann ließt der den PINA Register PA1 (auf mega wäre das Pin 23) und speichert in den 1. bit Dann ließt der den PINA Register PA3 (auf mega wäre das Pin 25) und speichert in den 0. bit Dann addiert er das Ergebnis aus der Tabelle für diese vier bit Resultat in die Positionsvariable.

Soweit so gut. Warum macht man das? Bei einer Normalverteilung der Prellungen geht es ja in beide Richtungen und die Wahrscheinlichkeit für hintereinander folgende einseitige Prellungsfehler ist gering genug, dass es nicht auffallen dürfte. Ein Kopplungsfehler würde sich ebenfalls in Grenzen halten, wenn überhaupt. Ist das Auslesen von einem Array deutlich schneller als zwei if abfragen?

Ich frage mich, ob es sich wirklich lohnt den Code unendlich unverständlicher zu machen, wenn es sich hierbei um 1-2 µs handelt.

Viele Grüße

#7849802
Lesenswert?

Stefan schrieb:

Das Beispiel versucht die Zustandsänderungsverhalten vorher zu berechnen und anschließend das nur auslesen und addieren.

Nein. Es ist eine Statemachine, die ein exaktes Verhalten auf die Eingangssignale zeigt.

Eine andere Tabelle versucht die physikalisch übliche Prellungen abzufangen, also sogenannte debouncing für die Drehgeber zu betreiben.

Ja. Ist aber auch eine state machine, nur mit leicht anderem Verhalten.

Erst schiebt er den letzten Zustand um 2 bits (wenn es noch kein Zustand gab, dann 00 nach 2 links = immer noch 00) Dann maskiert er den überlauf, damit wir nur den letzten Zustand haben (& 0x0F)

Den letztzen + neuen.

Dann ließt der den PINA Register PA1 (auf mega wäre das Pin 23) und speichert in den 1. bit Dann ließt der den PINA Register PA3 (auf mega wäre das Pin 25) und speichert in den 0. bit Dann addiert er das Ergebnis aus der Tabelle für diese vier bit Resultat in die Positionsvariable.

Das Schieben und einlesen ist der Zustand und Richtungsvektor der State Machine. Die beiden alten Bits sind der IST-Zustand (Code), die beiden neuen Bits die Richtung (+/- oder nix).

Soweit so gut. Warum macht man das?

Um die beiden Richtungssignale korrekt zu dekodieren.

Bei einer Normalverteilung der Prellungen geht es ja in beide Richtungen und die Wahrscheinlichkeit für hintereinander folgende einseitige Prellungsfehler ist gering genug, dass es nicht auffallen dürfte.

Darum geht es gar nicht. Mit Wahrscheinlichkeiten hat das nix zu tun.

Ein Kopplungsfehler würde sich ebenfalls in Grenzen halten, wenn überhaupt. Ist das Auslesen von einem Array deutlich schneller als zwei if abfragen?

Es ist vor allem logisch korrekt. Deine Abfragen sind es nicht. Du hast das Thema Drehgeber und Dekodierung von Graycode noch nicht verstanden.

Ich frage mich, ob es sich wirklich lohnt den Code unendlich unverständlicher zu machen, wenn es sich hierbei um 1-2 µs handelt.

Der Code ist mit Tabelle ist sehr gut verständlich. Der komische vom Peter sind seine typischen Mikrooptimierungen, die praktisch wenig bringen aber aus der guten, alten Assemblerzeit stammen, wo man jeden Takt und jedes Byte geschätzt hat.

#7849935
Lesenswert?

Nichts Neues unter der Sonne.

Beitrag "Re: Quadraturauslese nach Dannegger"

Hier zum Vergleich die Tabellenversion in C und ASM.

1
#define ENCODER_PIN  PINC
2
#define PHASE_A      (1<<PC1)
3
#define PHASE_B      (1<<PC2)
4

5
ISR( TIMER0_COMPA_vect ) {            // 1ms for manual movement
6
    int8_t tmp, last;
7
    
8
    tmp = ENCODER_PIN;
9
    last = (enc_last << 2)  & 0x0F;
10
    if (tmp & PHASE_A) last |= 2;
11
    if (tmp & PHASE_B) last |= 1;
12
    enc_last = last;
13
    enc_delta += pgm_read_byte(&table[last]);
14
}
1
ISR( TIMER0_COMPA_vect ) {            // 1ms for manual movement
2
  84:   1f 92           push    r1      ; 2
3
  86:   0f 92           push    r0      ; 2
4
  88:   0f b6           in  r0, 0x3f    ; 1
5
  8a:   0f 92           push    r0      ; 2
6
  8c:   11 24           eor r1, r1      ; 2
7
  8e:   8f 93           push    r24     ; 2
8
  90:   9f 93           push    r25     ; 2
9
  92:   ef 93           push    r30     ; 2
10
  94:   ff 93           push    r31     ; 2         
11

12
                                        ; 17 Takte Prolog
13
    int8_t tmp, last;
14
    
15
    tmp = ENCODER_PIN;
16
  96:   86 b1           in  r24, 0x06           ; 1
17
    last = (enc_last << 2)  & 0x0F;
18
  98:   e0 91 00 01     lds r30, 0x0100         ; 2
19
  9c:   ee 0f           add r30, r30            ; 1
20
  9e:   ee 0f           add r30, r30            ; 1
21
  a0:   ec 70           andi    r30, 0x0C       ; 1
22
    if (tmp & PHASE_A) last |= 2;
23
  a2:   99 27           eor r25, r25            ; 1
24
  a4:   87 fd           sbrc    r24, 7          ; 2
25
  a6:   90 95           com r25                 ; 1
26
  a8:   81 fd           sbrc    r24, 1          ; 2
27
  aa:   e2 60           ori r30, 0x02   ; 2     ;     1
28
    if (tmp & PHASE_B) last |= 1;
29
  ac:   82 fd           sbrc    r24, 2          ; 2
30
  ae:   e1 60           ori r30, 0x01   ; 1     ;     1
31
    enc_last = last;
32
  b0:   e0 93 00 01     sts 0x0100, r30         ; 2
33
    enc_delta += pgm_read_byte(&table[last]);
34
  b4:   80 91 01 01     lds r24, 0x0101         ; 2
35
  b8:   ff 27           eor r31, r31            ; 1
36
  ba:   e7 fd           sbrc    r30, 7          ; 2
37
  bc:   f0 95           com r31                 ;     1
38
  be:   ec 5c           subi    r30, 0xCC       ; 1
39
  c0:   ff 4f           sbci    r31, 0xFF       ; 1
40
  c2:   e4 91           lpm r30, Z+             ; 3
41
  c4:   e8 0f           add r30, r24            ; 1
42
  c6:   e0 93 01 01     sts 0x0101, r30         ; 2
43
                                                ; 29 Takte Dekoder mit Tabelle
44
}
45
  ca:   ff 91           pop r31                 ; 2
46
  cc:   ef 91           pop r30                 ; 2             
47
  ce:   9f 91           pop r25                 ; 2
48
  d0:   8f 91           pop r24                 ; 2
49
  d2:   0f 90           pop r0                  ; 2
50
  d4:   0f be           out 0x3f, r0            ; 2
51
  d6:   0f 90           pop r0                  ; 2
52
  d8:   1f 90           pop r1                  ; 2
53
  da:   18 95           reti                    ; 4
54
                                                ; 20 Takte Epilog
Persönliche Seite #7849993
Lesenswert?

Falk B. schrieb:

Hier zum Vergleich die Tabellenversion in C und ASM.

...und hier nochmal mit einem aktuellen Compiler:

1
ISR (TIMER0_COMPA_vect)
2
{            // 1ms for manual movement
3
  10:  8f 93         push  r24
4
  12:  8f b7         in  r24, 0x3f  ; 63
5
  14:  8f 93         push  r24
6
  16:  ef 93         push  r30
7
  18:  ff 93         push  r31
8
Prolog = 9 Ticks
9
    int8_t tmp = ENCODER_PIN;
10
  1a:  86 b1         in  r24, 0x06  ; 6
11
    uint8_t last = (enc_last << 2)  & 0x0F;
12
  1c:  e0 91 01 01   lds  r30, 0x0101  ; 0x800101 <enc_last>
13
  20:  ee 0f         add  r30, r30
14
  22:  ee 0f         add  r30, r30
15
  24:  ef 70         andi  r30, 0x0F  ; 15
16
    if (tmp & PHASE_A) last |= 2;
17
  26:  81 fd         sbrc  r24, 1
18
  28:  e2 60         ori  r30, 0x02  ; 2
19
    if (tmp & PHASE_B) last |= 1;
20
  2a:  82 fd         sbrc  r24, 2
21
  2c:  e1 60         ori  r30, 0x01  ; 1
22
    enc_last = last;
23
  2e:  e0 93 01 01   sts  0x0101, r30  ; 0x800101 <enc_last>
24
    enc_delta += pgm_read_byte(&table[last]);
25
  32:  f0 e0         ldi  r31, 0x00  ; 0
26
  34:  e0 50         subi  r30, 0x00  ; 0
27
  36:  f0 40         sbci  r31, 0x00  ; 0
28
  38:  e4 91         lpm  r30, Z
29
  3a:  80 91 00 01   lds  r24, 0x0100  ; 0x800100 <enc_delta>
30
  3e:  8e 0f         add  r24, r30
31
  40:  80 93 00 01   sts  0x0100, r24  ; 0x800100 <enc_delta>
32
Body = 23 Ticks
33
}
34
  44:  ff 91         pop  r31
35
  46:  ef 91         pop  r30
36
  48:  8f 91         pop  r24
37
  4a:  8f bf         out  0x3f, r24  ; 63
38
  4c:  8f 91         pop  r24
39
  4e:  18 95         reti
40
Epilog = 14 Ticks

Also 9 + 23 + 14 = 46 Ticks im Vergleich zu 17 + 29 + 20 = 66 Ticks mit altem Compiler.

Persönliche Seite #7850078
Lesenswert?

Roland F. schrieb:

Johann L. schrieb:

...und hier nochmal mit einem aktuellen Compiler...

Welcher genau ist das?

v14

Wobei es die kürzeren ISR Prolog+Epilog bereits ab GCC v8 / Binutils v2.29 gibt.

Im Code habe ich "last" als uint8_t definiert anstatt als int8_t. Der Wert ist ja in [0, 15], und mit int8_t bekommt man dann ein überflüssiges Sign-Extend beim Berechnen der Array-Adresse.

...eventuell könnte auch das der Compiler optimieren. Muss ich mir mal anschauen.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren