Forum: Mikrocontroller und Digitale Elektronik Arduino: digitalRead in Interrupt ist unzuverlässig


von Stefan (drotalion)


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

: Bearbeitet durch User
von Harald K. (kirnbichler)


Lesenswert?

Stefan schrieb:
> Hier (Beitrag "Re: Drehknopf mit Arduino auslesen") hat jemand
> vollständig auf Interrupts verzichtet.

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

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

von Stefan (drotalion)


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.

von Roland E. (roland0815)


Lesenswert?

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...

von Falk B. (falk)


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.

von Falk B. (falk)


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...

Auf einem einfachen AVR-Arduino läuft keinerlei OS, bestenfalls ein 1kHz 
Timer.

von Falk B. (falk)


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...

Beitrag "Re: Arduino Nano, SD Card, PCM"

22kHz PWM Audioausgabe direkt von SD-Karte auf einen normalen Ardino 
Uno/Mega.

: Bearbeitet durch User
von IR R. (interkalation_r)


Lesenswert?

Soweit ich weiß ist doch bei dem 328P jeder Pin mit dem 
PinChange-Interrupt nutzbar oder nicht? Bzw das Flag wird dann für das 
entsprechende Portregister gesetzt....

von Falk B. (falk)


Lesenswert?

IR R. schrieb:
> Soweit ich weiß ist doch bei dem 328P jeder Pin mit dem
> PinChange-Interrupt nutzbar oder nicht?

Ja, das löst das Problem aber nicht.

von Falk B. (falk)


Lesenswert?

Beitrag "Re: Arduino Mega 11 PWM-Pins mit 1kHz"

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

von Harald K. (kirnbichler)


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.

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

IR R. schrieb:
> Soweit ich weiß ist doch bei dem 328P jeder Pin mit dem
> PinChange-Interrupt nutzbar oder nicht?

Ja!
Bis auf A6 und A7

von Falk B. (falk)


Lesenswert?

Arduino F. schrieb:
> Ja!
> Bis auf A6 und A7

Hehe, stimmt. Da bin vor kurzen mal wieder reingefallen. Das sind nur 
analoge Eingänge ;-)

von Arduino F. (Firma: Gast) (arduinof)


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.

von Sebastian W. (wangnick)


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

von Sherlock 🕵🏽‍♂️ (rubbel-die-katz)


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

: Bearbeitet durch User
von Sherlock 🕵🏽‍♂️ (rubbel-die-katz)


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.

von Peter D. (peda)


Lesenswert?

Stefan schrieb:
> Ich habe ein System mit 6 DC12V Motoren,
> jeweils mit einer HAL Sensor.

Hast Du mal das Datenblatt zu diesen unbekannten Sensoren.
Sind die überhaupt digital und entprellt?

Stefan schrieb:
> Input für 4 HX711

Wozu sollen diese 24Bit ADC dienen?

von Stefan (drotalion)


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!

: Bearbeitet durch User
von Rainer W. (rawi)


Lesenswert?

Stefan schrieb:
> 2) In dieser Timer die Zustände aller HAL-Pins von allen Motoren
> auslesen

Es schein, als ob du Hall mit HAL verwechselst
https://de.wikipedia.org/wiki/Hall-Effekt
https://de.wikipedia.org/wiki/HAL_(Software)

von Sherlock 🕵🏽‍♂️ (rubbel-die-katz)


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.

: Bearbeitet durch User
von Falk B. (falk)


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
> * 127. 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.

> 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.

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?

von Stefan S. (seife)


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.

von Sherlock 🕵🏽‍♂️ (rubbel-die-katz)


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.

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Sherlock 🕵🏽‍♂️ schrieb:
> Wieso zum Teufel "braucht" man eine alternative Bibliothek, um Port-
> Register zu lesen?

Portabilität!
Einer der Vorteile der Arduino Welt.

Nicht an jeder Ecke möglich, aber die genannte Lib macht das schon gar 
nicht so schlecht.

Was du tust:
Mal wieder aus den Büschen gesprungen kommen, und Arduino Bashing 
betreiben.

von Joachim B. (jar)


Lesenswert?

Stefan S. schrieb:
> digitalRead() ist schon eher gemächlich.

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

von Stefan S. (seife)


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.

von Joachim B. (jar)


Lesenswert?

Stefan S. schrieb:
> Der war flach, der Witz.

mag sein, der hier besser?
https://www.codeproject.com/Articles/732646/Fast-digital-I-O-for-Arduino

ansonsten macht man es selbst ohne framework (ist ja bei Arduino auch 
möglich)

von Stefan S. (seife)


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 ;-)

von Christoph M. (mchris)


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.

von Flunder (flunder)


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.

von Gregor J. (Firma: Jasinski) (gregor_jasinski)


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.

von Mark S. (voltwide)


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.

von Arduino F. (Firma: Gast) (arduinof)


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.

: Bearbeitet durch User
von Stefan (drotalion)


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!

von Sebastian W. (wangnick)


Lesenswert?

Stefan schrieb:
> Auch hier blieb called bei 0.

Man kann aus den Codeschnipseln leider nicht erkennen, ob die zur 
Kommunikation zwischer ISR und Hauptprogramm genutzten Variablen als 
volatile markiert sind, und ob die Zugriffe auf diese Variablen im 
Hauptprogramm entsprechend abgesichert sind.

LG, Sebastian

von Arduino F. (Firma: Gast) (arduinof)


Lesenswert?

Stefan schrieb:
> Auch hier blieb called bei 0.

Sebastian W. schrieb:
> als volatile markiert sind,

Die ATOMIC Makros sind bestens geeignet.
Sie sperren die Interrupts  und bauen die nötige Memory Barrier
Volatile ist nicht nötig oder angebracht, da der Wert ja nicht von der 
Hardware geändert wird, sondern in der ISR

: Bearbeitet durch User
von Arduino F. (Firma: Gast) (arduinof)


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.

von Stefan (drotalion)


Lesenswert?

Falk B. schrieb:
> Oder bist du eine Maschine? Oder ein Chat
> GPT-Bot?

Funfact: als jemand, der in der AI-Branche relativ viel arbeitet. Der 
neue Turing-Test für AI-Chatbots ist witzigerweise political 
correctness. Ich würde aber ungern mein Account verlieren ...

von Falk B. (falk)


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.

>     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.

von Stefan (drotalion)


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

: Bearbeitet durch User
von Cyblord -. (cyblord)


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.

Die Register sind das Lenkrad.

von Falk B. (falk)


Angehängte Dateien:

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.

von Sherlock 🕵🏽‍♂️ (rubbel-die-katz)


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.

: Bearbeitet durch User
von Stefan (drotalion)


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!

: Bearbeitet durch User
von Stefan (drotalion)


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.

von Cyblord -. (cyblord)


Lesenswert?

Stefan 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"?

von Falk B. (falk)


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!

von Falk B. (falk)


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".

von Stefan (drotalion)


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

von Wastl (hartundweichware)


Lesenswert?

Bei solch langen Variablen- und Funktionsnamen geht mir
das Herz auf ....

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

von Harald K. (kirnbichler)


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.

von Stefan (drotalion)


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.

von Stefan (drotalion)


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.

von Falk B. (falk)


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.

von Falk B. (falk)


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!

von Harald K. (kirnbichler)


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()

von Stefan (drotalion)


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

: Bearbeitet durch User
von Falk B. (falk)


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.

von Sherlock 🕵🏽‍♂️ (rubbel-die-katz)


Lesenswert?

Wastl schrieb:
> Bei solch langen Variablen- und Funktionsnamen geht mir
> das Herz auf ....

Lieber lang als zu kurz.

von Harald K. (kirnbichler)


Lesenswert?

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.

von Ob S. (Firma: 1984now) (observer)


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.

von Falk B. (falk)


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ö.

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Falk B. schrieb:

> Solche Projekte gibt es

Ja. Viel zu viele davon gibt es. Es ist eher die Regel als die Ausnahme.

> Thema verfehlt!

Nein. Eben weil das so ist, wie es ganz offensichtlich ist, passt das 
nur zu genau zum Thema.

von Harald K. (kirnbichler)


Lesenswert?

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"

von Falk B. (falk)


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!

von Ob S. (Firma: 1984now) (observer)


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)

von Joachim B. (jar)


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.

: Bearbeitet durch User
von Harald K. (kirnbichler)


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 ...

von Ob S. (Firma: 1984now) (observer)


Lesenswert?

Harald K. schrieb:

> Man könnte, wenn der Timerinterrupt einen bestimmten Zyklus hat, in dem
> er aufgerufen wird, die Funktion auch präziser
>
1
> FunctionToHandleVorZuruckCalledFromTimerInterruptEvery1000msec()
2
>
> nennen ...

Ja, durchaus. Was spricht dagegen?

von Falk B. (falk)


Lesenswert?

Ob S. schrieb:
> Ja, durchaus. Was spricht dagegen?

Der gesunde Menschenverstand eines Programmierers. Aber Geschätzigkeit 
liegt ja im Trend, auch in der IT.

von Norbert (der_norbert)


Lesenswert?

Ob S. schrieb:
> Ja, durchaus. Was spricht dagegen?

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

von Falk B. (falk)


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.

von Harald K. (kirnbichler)


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!

von Stefan (drotalion)


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

: Bearbeitet durch User
von Falk B. (falk)


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.

von Falk B. (falk)


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

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


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.

: Bearbeitet durch User
von Roland F. (rhf)


Lesenswert?

Hallo,
Johann L. schrieb:
> ...und hier nochmal mit einem aktuellen Compiler...

Welcher genau ist das?

rhf

von Johann L. (gjlayde) Benutzerseite


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.

Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.