Guten Abend,
aktuell Versuche mit einem Arduino Uno den Encoder eines Faulhaber
Motors auszulesen und daraus die Drehzahl zu bestimmt.
Der folgende Motor wird verwendet: 2342S012CR
und der folgende Encoder: IE3-32
Das Signal wird über den Interrupt-Pin 2 ausgelesen, allerdings
entspricht die angezeigte Drehzahl nicht der tatsächlichen, diese bewegt
sich nur in einem kleinen nicht nachvollziehbaren Bereich.
Der Encoder hat drei Ausgangssignale, zwei um 90 Grad verschobene
Rechtecksignale und einen Indeximpuls.
Bisher kam das Auslesen des Indexumpulses den tatsächlichen Werten am
nächsten.
Der Wechsel des Interrupt-Modus hat auch keinen Erfolg gebracht.
Da ich noch Neuling bin habe ich mich an der folgenden Anleitung für die
Messung der Drehzahl orientiert:
https://arduino-projekte.info/drehzahlmesser/
Das ist mein aktuelles Programm:
1
// Variablen
2
intMotorSpeed;// Geschwindigkeit Motor
3
4
// Variablen Encoder
5
byteDZM_InputPin=2;
6
volatileunsignedlongRPM_T2,RPM_Count;
7
unsignedlongRPM,RPM_T1;
8
9
10
// Eingaenge
11
intPoti=A3;// Analogwert fuer Drehzahlvorgabe durch Potentiometer
Der Motor kann bis zu 6000 1/min drehen, wird allerdings im späteren
Betrieb nicht schneller als 3000 1/min drehen.
Zu der Auswertung mit Timern hatte ich bisher noch nichts gelesen, die
Interrupt-Variante erschien mir bisher schlüssig.
Die Frage ist halt ob deine Zeitmessung genau genug ist. Ich kenne mich
beim Atmel nicht so mit den Interrupts und der Priorisierung aus aber
ich meine millis gibt den Wert einer Variable zurück die ebenfalls in
einem Interrupt hochgezählt wird (Bin mir nicht absolut sicher). Je nach
Gitter von deinem Pin und Millis Interrupt kann es sein das die Messung
zu ungenau ist. Für die Drehzahl zu messen sollte der Indeximpuls
ausreichend sein über die verschobenen Rechtecke kannst du zusätzlich
noch Richtung und Rotor Winkel bestimmen. Ich würde das Ganze mit einem
Timer machen. Such mal nach Timer Input Capture.
Hallo,
volatile nutzt er schon, haut hin. Nur liest er diese Werte in der loop
ohne atomaren Zugriffsschutz aus und hat deswegen Datenverstümmelung.
Serial.print würde ich auch nur aller 1s machen.
Wie kann ich den "atomaren Zugriffsschutz" einbringen, mit der Funktion
"noInterrupts()"?
Serial.print ist aktuelle nur ein Platzhalter für ein LCD Display, werde
die Zeit auf eine Sekunde erhöhen.
Maik H. schrieb:> RPM_T2 = millis();
Die Zeitmessung erscheint mir doch ein wenig zu grob, und warum die Zeit
im Interrupt gelesen wird, leuchtet mir nicht ein.
Der Encoder liefert bei 6000 UPM eine Ausgangsfrequenz von 3,2 kHz.
Wieviele Werte werden den pro Sekunde benötigt? Die einfachste Messung
(mit fester Torzeit) würde eine Sekunde warten und dann das Ergebnis aus
RPM_Count ermitteln.
Aber bei potentiell auch niedrigen Drehzahlen wäre eine reziproke
Messung doch wesentlich sinnvoller. Das läuft dann über Timer1 mit dem
T1-Capture-Eingang, wodurch sich eine hohe Auflösung bei hoher Messrate
ergibt.
Nur fürchte ich, daß man sich das nicht aus Arduino-LIBs zusammenklicken
kann ;-)
Maik H. schrieb:> Wie kann ich den "atomaren Zugriffsschutz" einbringen, mit der Funktion> "noInterrupts()"?
Nein. atomic block RESTORE
https://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
Und das mit der Zeitmessung überlegste dir auch nochmal. Wurde schon
angesprochen. millis wäre auch zu ungenau, müßtest schon micros
verwenden. Aber wie schon gesagt wurde ist diese Vorgehensweise nicht
optimal, weil du nur von Puls zu Puls misst.
Die Umsetzung mit dem "T1-Capture-Eingang" ist mir nicht klar geworden,
gibt es eventuell ein nachvollziehbares Beispiel?
Aktuell habe ich den Code nochmal verändert, dabei nutze ich micros und
zähle eine Sekunde lang die Impulse, die dann der Drehzahlbestimmung
dienen.
Dabei dient der Kanal A des Encoders als Signal, da der Indeximpuls
keine vernünftigen Werte liefert .
Aktuell erhalte ich hiermit Werte im Bereich von 60 bis 4440 RPM.
Bei der Ansteuerung mit dem Potentiometer fällt auf, dass im oberen
Stellbereich eine geringere Drehzahländerung vorhanden ist.
Sind das die folgende des nicht genutzten "T1 Caputere Eingangs"?
1
// Variablen
2
intMotorSpeed;// Geschwindigkeit Motor
3
4
// Variablen Encoder
5
// Motorpulse pro Umdrehung
6
#define ENC_COUNT_REV 1
7
// Encoder Input
8
#define ENC_IN 2
9
// Pulse vom Encoder
10
volatilelongencoderValue=0;
11
// Intervall 1 Sekunde
12
intinterval=1000000;
13
// Zähler für Microsekunden
14
longpreviousMicros=0;
15
longcurrentMicros=0;
16
// Umdrehungen pro Minute
17
intrpm=0;
18
19
// Eingaenge
20
intPoti=A3;// Analogwert fuer Drehzahlvorgabe durch Potentiometer
Hallo,
a) noch ohne atomic
b) Zeile 12
warning: overflow in conversion from 'long int' to 'int' changes value
from '1000000' to '16960' [-Woverflow]
12 | int interval = 1000000;
zusätzlich unsigned, damit der Vergleich später hinhaut, also unsigned
long
c) previousMicros und currentMicros müssen unsigned long sein
Maik H. schrieb:> Die Umsetzung mit dem "T1-Capture-Eingang" ist mir nicht klar geworden,> gibt es eventuell ein nachvollziehbares Beispiel?
Durchaus!
Aber ob Nachvollziehbar?
Das liegt mehr an dir, als am Beispiel.
Hier mal ein Ereigniszähler für einen Arduino mit ATMega328p
Man kann damit Ereignisse bis in den MHz Bereich zählen lassen.
Den Interval Code lasse ich mal weg, der sorgt nur dafür, dass jede
Sekunde eine Anzeige kommt.
Hallo,
den Trick mit dem union finde ich nett. Spart das shiften bzw.
multiplizieren. Mußte das erstmal binär nachbauen und schauen ob das
wirklich so funktioniert wie ich dachte. :-) Jetzt habe ich dadurch 2
Fragen.
a) Warum ist das union/struct AVR only?
b) Ist der Vorteil wenn der "Timer Source T1" zählt der, dass dadurch
beim zählen keine zusätzlichen Interrupts erzeugt werden? Im Vergleich
wenn ein "normaler" Interrupteingang jeden Puls zählen müßte.
a)
Eigentlich ist das Verhalten laut C++ undefiniert.
Der Gcc garantiert allerdings, dass das so funktioniert.
Bei anderen µC haben haben die Daten evtl. einen anderen Fußabdruck im
Speicher, dann wird das so erstmal nix.
Wenns universell werden soll, dann eben schieben oder multiplizieren.
b)
Ja.
Wenn für jede Flanke ein Interrupt feuert, ist bei wenigen kHz Ende im
Gelände. So geht selbst bei 1MHz kaum Rechenzeit drauf.
Veit D. schrieb:> den Trick mit dem union finde ich nett. Spart das shiften bzw.> multiplizieren.
Der Compiler wird den Code ohne Multiplikation schon selber hinbekommen.
Veit D. schrieb:> Ist der Vorteil, wenn der "Timer Source T1" zählt der, dass dadurch> beim Zählen keine zusätzlichen Interrupts erzeugt werden?
Das ist ein normaler torzeitgesteuerter Zähler, die Auflösung bei
niedrigen Frequenzen bleibt daher relativ gering. Bei 1 Hz schwankt die
Anzeige zwischen 0 und 1. T1-Capture wird hier garnicht verwendet.
Arduino Fanboy D. schrieb:> Wenn für jede Flanke ein Interrupt feuert, ist bei wenigen kHz Ende im> Gelände.>= 100 kHz sind kein Problem: 10 µs sind schon eine lange Zeit. In Assembler
programmiert geht es bis 1 MHz Eingangsfrequenz.
m.n. schrieb:> >= 100 kHz sind kein Problem: 10 µs sind schon eine lange Zeit. In Assembler> programmiert geht es bis 1 MHz Eingangsfrequenz.
16 Takte für Call, iret Pus Pop und 32Bit Zähler hochsetzen?
Sportlich!
Sehr sportlich.
Wenn dann noch mehr ISR ihr Unwesen treiben, kann es so schnell mal ein
Ereignis "übersehen".
Hach...
Assembler steht hier, glaube ich, erstmal nicht zu Debatte.
Der TE verwendet attachInterrupt().
Das ist einerseits recht bequem, erfordert andererseits einen Call in
der ISR und führt so zu langen Push Pop Kaskaden.
Ja, lass die 100kHz die Grenze sein.... (nicht getestet), welche dann
den armen kleinen 328p zur Vollbeschäftigung treibt.
Während er bei meinem Vorschlag noch reichlich "Luft" hat. Und eher kein
Ereignis übersehen wird.
Arduino Fanboy D. schrieb:> Ja, lass die 100kHz die Grenze sein.... (nicht getestet)
Das habe ich schon getestet.
Arduino Fanboy D. schrieb:> Während er bei meinem Vorschlag noch reichlich "Luft" hat. Und eher kein> Ereignis übersehen wird.
Der TO braucht Luft nach unten ;-)
Vielen Dank für die informativen Antworten und besonderen Dank an Veit
D. für den angepassten Code.
Die Bibliothek #include <util/atomic.h> habe ich bisher nicht in den
Bibliotheken für den Arduino in dieser Form gefunden.
Nach dem folgenden Link habe ich nun die Bibliothek #include
<SimplyAtomic.h> eingefügt und im Code einen Teil angepasst.
https://github.com/wizard97/SimplyAtomic
1
ATOMIC()// neuer Befehl für den Aufruf
2
{
3
tempEncoderValue=encoderValue;
4
encoderValue=0;
5
}
Spricht etwas gegen diese Bibliothek oder wird diese die gleiche
Funktionalität beinhalten?
Maik H. schrieb:> Die Bibliothek #include <util/atomic.h> habe ich bisher nicht in den> Bibliotheken für den Arduino in dieser Form gefunden.
Was auch kein Wunder ist, gehört sie doch zum Lieferumfang des GCC für
AVR.
Natürlich darfst du den lieferumfang des Gcc ausblenden, bzw. unbeachtet
lassen. Aber dann geht dir einiges/viel nützliches durch die Lappen.
Maik H. schrieb:> Spricht etwas gegen diese Bibliothek oder wird diese die gleiche> Funktionalität beinhalten?
Beide liegen im Quellcode vor!
Du könntest da rein schauen, und selber vergleichen.
Aber Vorsicht!
Auf die Art könnten sich ein Erkenntnisgewinn einstellen.
(hoffentlich kannst du damit umgehen)
Hallo,
> Die Bibliothek #include <util/atomic.h> habe ich bisher nicht in den> Bibliotheken für den Arduino in dieser Form gefunden.
also ich kompiliere immer erst und schaue ob was fehlt
> Spricht etwas gegen diese Bibliothek oder wird diese die gleiche> Funktionalität beinhalten?
Muss leider mit einer Gegenfrage antworten. Warum eine fremde Lib
verwenden wenn man das Original hat? Die Frage müßte zugleich die
Antwort sein.
Ich denke jedoch die Idee mit der fremden Lib beruht auf dem
Missverständnis das obige Lib angeblich fehlen würde.
"util/atomic.h" ist nur der letzte Teil vom Suchpfad und Suchinfo für
den Compiler. Du siehst daran nur das die Headerdatei atomic.h in einem
Verzeichnis namens util liegt. Wenn man jetzt einfach auf C: nach
atomic.h suchen würde, dann erscheinen unzählige Angaben, je nachdem was
alles an Programmiersoftware installiert ist. Deswegen gehe in den
Arduino Installationspfad und starte dort die Suche nach atomic.h,
sollte nur einmal vorkommen.
Welche du verwenden solltest sollte damit klar sein.
Wegen dem Sketch. Kein Problem, so lang war der nicht.
Die Lösung von ArduinoFanboy solltest du dir allerdings auch anschauen.
Sind paar tolle Features drin. Man kann immer wieder von anderen lernen.
Am Ende muss man das nutzen was man versteht und lesen kann und das noch
nicht verstandene aber versuchen im Hinterkopf zubehalten. Irgendwann
versteht man es und wird so Stück für Stück immer schlauer.
Ja, es ist ein Missverständnis meinerseits. Bisher bin ich davon
ausgegangen, dass alle Lib´s in "libaries" abgelegt sein müssen.
Die Antwort erweitert mein Verständnis dazu.
Die Datei habe ich im folgenden Pfad gefunden.
C:\Program Files (x86)\Arduino\hardware\tools\avr\avr\include\util
Mehrere Versuche diesen nun in für "until" in #include <util/atomic.h>
einzusetzen schlugen fehl.
Wie sollte dieser Pfad-Aufruf aufgebaut sein?
#include <???/atomic.h>
Die Antworten von ArduinoFanboy sind bestimmt gut, allerdings kann ich
aktuell wirklich nicht die Inhalte lesen und verstehen.
Leider fehlt mir aktuell die Zeit um dies in der Form nachzuvollziehen
wie ich es gerne würde, vielleicht kann ich das nochmal nachholen.
Maik H. schrieb:> Wie sollte dieser Pfad-Aufruf aufgebaut sein?
So wie es in meinem Programm steht!
Auch der Veit D hats vollkommen korrekt in seinem Programm.
Selbst auf der Dokuseite zu atomic.h findet sich das so.
Der Fehler ist behoben.
Durch das Einbinden der der #include <SimplyAtomic.h> Bibliothek wurde
die <util/atomic.h> Bibliothek blockiert und konnte nicht eingebunden
werden. Durch entfernen von SimplyAtomic.h, die dann nicht mehr benötigt
wird, ist das Einbinden nun möglich.
Mein Gedanke war, dass ich die Bibliothek die nicht in dem libaries
Verzeichnis liegt über den zugehörigen Pfad einbinden muss, damit es
funktioniert.
Vielen Dank für die Mühe von euch in diesem Forum :-)