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:
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 (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...
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.
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 (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.
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....
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.
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.
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.
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.
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:
Aktiviere Interrupt für den gesamten PCINT Port
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
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.
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.
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?)
In dieser Timer die Zustände aller HAL-Pins von allen Motoren auslesen
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 :)
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.
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.
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?
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.
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.
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.
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:
Aktiviere Interrupt für den gesamten PCINT Port
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
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.
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!
Motordrehzahl ist 133 Umdrehungen * 127 = Einen Timer mit 16Khz
erstellen
Wieso das denn?
(Du hast 1KHz geschrieben, in dem Artikel steht aber Frequenz
Warum ist 8 ausreichend statt wie in Doc 127?)
Weil du was falsch verstanden hast.
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.
In dieser Timer die Zustände aller HAL-Pins von allen Motoren
auslesen
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!
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?
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.
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.
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.
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
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 ;-)
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.
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.
(..) 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.
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.
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.
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
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:
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)
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!
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.
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!
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.
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
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!
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 ...
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
(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.
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).
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.
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.
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.
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.
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.
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.
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!
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.
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!
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.
Außerdem ist der innere Antrieb, schwierige, herausfordernde Dinge zu tun, heute in der Masse deutlich geringer. Generation Infotainment. Konsumieren ist einfacher und "angenehmer".
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:
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.
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.
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.
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.
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?
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!
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.
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.
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!
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!
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)
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.
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
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.
Was jetzt noch unbefriedigend ist, ist der Instanzenname. Der lautet genauso wie der Klassenname, und unterscheidet sich nur durch einen Kleinbuchstaben:
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.
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.
...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.