Hallo zusammen.
ich will die Drehzahl mit Hilfe eines Encoders (mit Stirnrad auf der
Motorwelle) messen und die Drehzahlwerte über CAN senden.
Als Hardware habe ich ein Arduino uno board mit aufgesteckten CAN sgield
genommen, weil das gerade verfügbar war.
Im Prinzip funktioniert die Messung und das Senden, allerdings gibt es
zwei Probleme.
1)Ab und zu scheint die Messung nicht zu funktionieren. Bei einer festen
Drehzahl von 500 rpm werden über CAN ab und zu mal falsche Werte
übermittelt. Das peaks von etwa 525-535 rpm. Das passiert auch bei
anderen Drehzahlen.
2)Bei einer Drehzahl < 60 rpm werden "Phantasiewerte", z.B. 1325, über
CAN zurückgemeldet. Diese springen auch ziemlich.
Im Code nutze ich den input capture um die Zeitstempel für den Beginn
und das Ende einer Umdrehung zu messen (24 Pulse/Umdrehung). Aus der
Zeitdifferenz wird dann die Drehzahl berechnet. So der Plan.
Hat jemand eine Idee was da schief läuft?
Falls es auch andere Verbesserungsvorschläge für die Umsetzung und den
Code gibt, wäre ich dankbar, wenn ihr es mich wissen lassen würdet.
Hi
ich hab mal kurz in den Code geschaut. Ich arbeite zwar nicht mit
Arduion, aber mir ist aufgefallen, das Du an der Anzahl der Pulss sowohl
im Interrupt als auch im Main Veränderungen (Pulse reset) vornimst. Dies
kann dazu führen, das die Interruptroutine mehr pulse zählt als deine
24.
Gruß Bernd
ISR(TIMER1_OVF_vect)
{
}
Hier fehlt der entscheidene Inhalt.
Laß Timer1 mit voller Geschwindigkeit laufen und beachte die Überläufe.
Dann klappt das auch.
Code für Arduino: http://mino-elektronik.de/fmeter/fm_software.htm#bsp7
Vielen Dank für die Rückmeldungen.
Ich weiß nicht so richtig, was ich in ISR(TIMER1_OVF_vect) machen soll,
weil mich die Überläufe doch gar nicht interessieren. Oder sollten sie
das?
Daher habe ich den ovf interrupt mal komplett ausgeschaltet.
Das ändert aber rein gar nichts, immer noch peaks.
Aufrund BerndBs Hinweis habe ich den code so geändert, dass nur noch in
ISR(TIMER1_CAPT_vect) die Variable verändert wird und nicht zusätzlich
noch im Main.
Das hat an der Peak Thematik auch nichts geändert.
Sollte ich das Ganze vll so aufziehen, dass ich einfach nach einem
festen Zeintintervall die bis dahin gezählten Pulse auswerte und mit
Bezug auf das Zeitintervall und den Pulsen/Umdrehung die Dehzahl
berechne?
M. H. schrieb:> Ich weiß nicht so richtig, was ich in ISR(TIMER1_OVF_vect) machen soll,> weil mich die Überläufe doch gar nicht interessieren. Oder sollten sie> das?
Willst Du nicht mal lesen, was ich Dir geschrieben habe?
> if (pulse==PulseProUmdrehung+1)
Da ist es reiner Zufall dass deine while()-Schleife diese Bedingung mal
trifft. Was passiert wenn innerhalb eines Durchlaufs deiner
while()-Schleife die ISR mehrmals zuschlägt?
M. H. schrieb:> 2)Bei einer Drehzahl < 60 rpm werden "Phantasiewerte", z.B. 1325, über> CAN zurückgemeldet. Diese springen auch ziemlich.
TCNT1 läuft über. 16 MHz/256/65536 = 0,95 Hz -> 57 rpm
M. H. schrieb:> Im Code nutze ich den input capture um die Zeitstempel für den Beginn> und das Ende einer Umdrehung zu messen (24 Pulse/Umdrehung).
Eine Umdrehung hat für mich prinzipiell keinen Anfang und kein Ende.
Und warum schaltest du die zu zählende Flanke um?
Ich würde immer nur von der selben Flanke zur selben Flanke messen
(steigend oder fallend, je nach dem, was definierter ist).
Und du musst zur Auswertung nicht 24 Impulse abwarten. Im Prinzip weißt
du schon nach der zweiten steigenden Flanke (und bei jeder einzelenen
nachfolgenden Flanke) die Drehzahl: die ist 24mal niedriger als die per
Capture gemessene Zeit und die daraus berechnete Frequenz.
Du musst also einfach immer nur den Zeitpunkt der letzten Flanke vom
Zeitpunkt der aktuellen Flanke abziehen und hast damit ein 24stel der
Zeit bestimmt, die eine Umdrehung braucht. Dann merkst du dir die
aktuelle Zeit für die nächste Berechnung bei der nächsten Flanke.
> rpm = 60000 / ((timeStamp2-timeStamp1)*0.016) ; // Prescaler 256
Abgesehen vom relativ nutzlosen Kommentar: lass den zeitaufwändigen
Umweg über diese float-Berechnung. Schreib die Zeile besser so:
1
rpm=3750000/(long)(timeStamp2-timeStamp1);// Magic Number 3750000 = 60000/0.016
das liest sich alles ziemlich einleuchtend. Danke für die Hinweise.
Die Umschaltung der zählenden Flanke war ein Versuch, da sich nichts
geändert hat, habe ich im Code gelassen.
Ich habe den Code abgeändert, allerdings komme ich nicht zum richtigen
Messwert.
M. H. schrieb:> Ich habe den Code abgeändert
Immer noch recht holprig...
Und hier:
timeStamp2 = Capt2 + ueberlauf*256;
Frage ich mich: ist der Timer 1 nicht ein 16 Bit-Zähler? Dann kommt ein
Überlauf erst nach 65536 und nicht schon nach 256...
> allerdings komme ich nicht zum richtigen Messwert.
Du darfst die rpm auch nicht mit 24 mutliplizieren, denn die Überlaufe
an sich kommen ja schon um das 24fache zu oft...
Ich würde an deiner Stelle aus dem Capture und dem Überlauf einen 32Bit
Wert basteln und damit weiterrechnen:
1
unsignedlongtimeStampAkt,timeStampLast,zeit_diff;
2
ISR(TIMER1_CAPT_vect)
3
{
4
timeStampAkt=(ueberlauf_count<<16)+ICR1;// aus ICP und Überlaufz. einen 32 Bit Zähler basteln
5
rpm=1234567/(timeStampAkt-timeStampAlt);// Magic Number anpassen... ;-)
6
timeStampAlt=timeStampAkt;// fürs nächste Mal merken
Lothar M. schrieb:> rpm = 1234567 / (timeStampAkt-timeStampAlt); // Magic Number> anpassen... ;-)
Nein, sondern die Auswertung zum einen nicht in der ISR erledigen und
zum anderen mit float rechnen.
Lothar M. schrieb:> lass den zeitaufwändigen> Umweg über diese float-Berechnung.
Der Zeitaufwand ist minimal und kein Grund auf float zu verzichten.
Das muß wohl mal wieder gesagt werden ;-)
m.n. schrieb:> Der Zeitaufwand ist minimal und kein Grund auf float zu verzichten.> Das muß wohl mal wieder gesagt werden ;-)
Dann sage ich halt: "nimm einen 32Bit Integer weil der genauer ist!",
denn ein 32-Bit-float hat nur 7 signifikante Stellen. Und die
Dynamik, die ein float bietet, wird bei der Aufgabe hier nicht
benötigt.
> Nein, sondern die Auswertung zum einen nicht in der ISR erledigen
Wenn die "Auswertung" nur noch die 3 verbliebenen Zeilen ist, dann kann
man die auch in der ISR machen.
Aber soll jeder nach seiner Fasson selig werden. Über Software streite
ich genausowenig wie über Schönheit... ;-)
Lothar M. schrieb:> timeStamp2 = Capt2 + ueberlauf*256;> Frage ich mich: ist der Timer 1 nicht ein 16 Bit-Zähler? Dann kommt ein> Überlauf erst nach 65536 und nicht schon nach 256...
Denkfehler, danke für die Aufklärung.
Ich habe mit dem Tipp von Ikmiller mal zwei Varianten versucht.
1) Auswertung der Zeit von zwei aufeinander folgenden Pulsen
1
voidStartTimer1(void)
2
{
3
TCCR1B|=(1<<CS12);//STARTING TIMER WITH PRESCALER 256
timeStampAkt=(ueberlauf_count<<16)+ICR1;// aus ICP und Überlaufz. einen 32 Bit Zähler basteln
17
rpm=3750000/(timeStampAkt-timeStampLast);// Magic Number 3750000 = 60000/0.016
18
ueberlauf_count=0;
19
pulse=0;
20
}
21
}
22
23
ISR(TIMER1_OVF_vect)
24
{
25
ueberlauf_count++;// Ueberlaeufe von T1 ergeben obere 16bit der Messzeit
26
}
Zusätzlich sind die jeweiligen Codevarianten komplett angehängt.
Außerdem noch die CAN plots der rpm Werte Bei einer Drehzahl von 647
rpm.
Variante 2 läuft stabiler, allerdings springt die Drehzahl gelegntlich
auf 671.
Ein ähnliches Problem hatte ich beim ursprünglichen code.
Bei beiden Varianten springt der rpm Wert regelmäßig auf 0.
Eric B. schrieb:> werden das high und low Byte von ICR1 separat ausgelesen und ausgewertet> gerade um Overflow-situationen glatt zu bügeln.
Das separate Auslesen ist nicht der Punkt. Der TO muß erst erkennen, daß
ein Überlauf auch dann auftreten kann, wenn die TIMER1_CAPT-ISR gerade
aktiv ist. Beispielcode hat er ja genug.
Lothar M. schrieb:> Dann sage ich halt: "nimm einen 32Bit Integer weil der genauer ist!",> denn ein 32-Bit-float hat nur 7 signifikante Stellen.
Nimm eine Divison x/y. x und y liegen im Bereich 1 - 999. Wo ist denn
hier die Integer-Berechnung genauer als die mit float? Einfaches
Beispiel: x = 10 und y = 3.
int-Ergebnis = 3
float-Ergebnis = 3.333333
> Und die> Dynamik, die ein float bietet, wird bei der Aufgabe hier nicht> benötigt.
Da irrst Du. Wenn man nicht aufpaßt und seine Wertebereiche strikt
eingrenzt, hat man mit uin32_t Werten schnell Über- oder Unterläufe -
selbst, wenn man nur 4-stellige Ergebnisse braucht.
Lothar M. schrieb:> Wenn die "Auswertung" nur noch die 3 verbliebenen Zeilen ist, dann kann> man die auch in der ISR machen.
Gut, aber zuvor bitte wieder mit sei(); andere ISRs zulassen, damit
diese durch größere Ausführungszeiten nicht blockiert werden. Das
Gesamtprogramm ist ja noch nicht fertig.
Es geht mir nicht um Schönheit sondern um Pferdefüße ;-)
M. H. schrieb:> ich will die Drehzahl mit Hilfe eines Encoders (mit Stirnrad auf der> Motorwelle) messen und die Drehzahlwerte über CAN senden.
Das würde ich bleiben lassen, denn jeder Eingriff in die Elektrik führt
zum Erlöschen der Betriebserlaubnis! Zudem möchte ich nicht in deiner
Haut stecken, wenn du wegen Signalmanipulation einen Unfall auslöst.
Anton schrieb:> Das würde ich bleiben lassen, denn jeder Eingriff in die Elektrik führt> zum Erlöschen der Betriebserlaubnis! Zudem möchte ich nicht in deiner> Haut stecken, wenn du wegen Signalmanipulation einen Unfall auslöst.
Den einzigen "Unfall", den ich damit auslösen kann, ist ein falscher
Messwert, der niemanden (außer mich) interessiert.
Ich will nur die Drehzahl überprüfen und über CAN ausgeben, damit dieser
Wert im gleichen CAN plot landet (aus Dokumentationszwecken), wie alle
anderen Werte eines Umrichters, der den Motor antreibt.
Eric B. schrieb:> werden das high und low Byte von ICR1 separat ausgelesen und ausgewertet> gerade um Overflow-situationen glatt zu bügeln.
Das habe ich auch ausprobiert.
sieht schon deutlich besser aus, aber ausreißer sind immer noch drin.
(Nicht wundern, Drehzahl wurde ein wenig erhöht)
Eric B. schrieb:> im Beispielcode unter> https://rn-wissen.de/wiki/index.php/Timer/Counter_(Avr)#Input_Capture> werden das high und low Byte von ICR1 separat ausgelesen
Das macht der Compiler automatisch richtig.
M. H. schrieb:> Variante 2 läuft stabiler
Vergiss hier, irgendwelche "stabilere" oder "bessere" Varianten
hinzubasteln. Die Aufgabe muss einfach einmal "richtig" gelöst werden.
Das geht. So wie es aussieht, sind die Probleme weitestgehend zyklisch
und das hat dann mit irgendwelchen Überläufen und Datentransfers zu tun.
M. H. schrieb:> Bei beiden Varianten springt der rpm Wert regelmäßig auf 0.
Das ist der eigentliche Witz hier: du hast irgendein Semaphorenproblem.
Der Zähler verstolpert sich. Das könnte daher kommen, dass der ICP und
der Überlauf-Zähler sich irgendwie "verheddern".
Und natürlich hast du das noch das übliche Semaphorenproblem, denn du
liest hier:
message.data[0] = rpm;
message.data[1] = (rpm>>8);
einen 16 Bit Wert in 2 Schritten aus. Zwischen diesen 2 Schritten kann
ein Interrupt kommen und einen neuen Wert in rpm schreiben. Dann geht
auf den CAN zur Hälfte der Neue und zur Hälfte der alte Wert. Und wenn
der alte Wert rpm = 256 war und der Neue rpm = 255 ist, was geht dann
auf den Bus?
> Außerdem noch die CAN plots
Hast du auch qualifizierte Messgeräte (mehrkanaliges digitales
Speicheroszilloskop oder Logicanalyzer) zur Hand? Denn du musst das
Problem mal direkter an der Quelle anpacken und z.B. mal das
Interrupt-Timing messen, indem du zu Beginn jeder ISR einen Ausgangspin
setzt und am Ende wieder zurücksetzt (idealerweise aber nicht mit der
schnarchlangsamen Arduino-Funktion, sondern durch direkten
Registerzugriff).
M. H. schrieb:> Eric B. schrieb:>> werden das high und low Byte von ICR1 separat ausgelesen> Das habe ich auch ausprobiert.> sieht schon deutlich besser aus
Vermutlich sieht es deshalb "besser aus", weil du die Frequenz ein wenig
hochgedreht hast. Dann ist der Zyklus der Ausreißer logischerweise
anders. Mit ein wenig Glück findest du sogar eine Drehzahl, wo gar kein
Fehler sichtbar ist.
EDIT:
m.n. schrieb:> Gut, aber zuvor bitte wieder mit sei(); andere ISRs zulassen, damit> diese durch größere Ausführungszeiten nicht blockiert werden. Das> Gesamtprogramm ist ja noch nicht fertig.
Ja, aber dabei aufpassen, damit ein zwischendurch auftretender
Timer-Overflow nicht wieder den 16 Bit Zugriff auf ueberlauf_count
korrumpiert.
> Nimm eine Divison x/y. x und y liegen im Bereich 1 - 999. Wo ist denn> hier die Integer-Berechnung genauer als die mit float? Einfaches> Beispiel: x = 10 und y = 3.> int-Ergebnis = 3> float-Ergebnis = 3.333333
Ja klar, das ist jetzt aber auch nicht das Problem, das ich meinte.
> Wo ist denn hier die Integer-Berechnung genauer als die mit float?
Die Mantisse hat nur 24 Bits, also kann sie 0..16777215 darstellen. Das
sind effektiv 7 Stellen. Und bei 32 Bit kann ich den Zahlenbereich
0..429497295 abbilden. Das sind gut 2 Dezimalstellen mehr.
Also rechne einfach mal 4444444444+1 in 32-Bit-float und in
32-Bit-Integer. Das ist mit "signifikanten Stellen" gemeint.
> Nimm eine Divison x/y. x und y liegen im Bereich 1 - 999. Wo ist denn> hier die Integer-Berechnung genauer als die mit float?> Beispiel: x = 10 und y = 3.> int-Ergebnis = 3> float-Ergebnis = 3.333333
Wenn ich die Nachkommastellen brauche, dann rechne ich einfach rechne
also z.B. statt in Volt in µV oder statt in Metern in µm und damit
10000000/3000000 und bekomme 3333333. Und dann denke ich mir den
Dezimalpunkt an die passende Stelle und gut ist.
Und dann bekomme ich auch noch bei 1000000000/3000000 (entspricht
1000.0/3.0) bis zur letzten "mikro-Nachkommastelle" das richtige
Ergebnis.
>> Und die Dynamik, die ein float bietet, wird bei der Aufgabe hier nicht>> benötigt.> Da irrst Du
Nein, alle Zahlen, die bei der Aufgabe hier zu berechnen sind, passen
wunderbar in den Zahlenbereich eines 32 Bit Integers.
Lothar M. schrieb:> Die Aufgabe muss einfach einmal "richtig" gelöst werden.
da bin ich deiner Meinung.
Lothar M. schrieb:> Und natürlich hast du das noch das übliche Semaphorenproblem, denn du> liest hier:> message.data[0] = rpm;> message.data[1] = (rpm>>8);> einen 16 Bit Wert in 2 Schritten aus. Zwischen diesen 2 Schritten kann> ein Interrupt kommen und einen neuen Wert in rpm schreiben. Dann geht> auf den CAN zur Hälfte der Neue und zur Hälfte der alte Wert. Und wenn> der alte Wert rpm = 256 war und der Neue rpm = 255 ist, was geht dann> auf den Bus?
das Problem habe ich auch schon gesehen. Das müsste aber doch weg sein,
wen man die Interrupts vor dem ganzen Sendevorgang ausschalte und danach
wieder einschalte, oder nicht?
Lothar M. schrieb:> Hast du auch qualifizierte Messgeräte (mehrkanaliges digitales> Speicheroszilloskop oder Logicanalyzer) zur Hand? Denn du musst das> Problem mal direkter an der Quelle anpacken und z.B. mal das> Interrupt-Timing messen
Ja, das werde ich machen.
Lothar M. schrieb:> Und natürlich hast du das noch das übliche Semaphorenproblem, denn du> liest hier:> message.data[0] = rpm;> message.data[1] = (rpm>>8);> einen 16 Bit Wert in 2 Schritten aus. Zwischen diesen 2 Schritten kann> ein Interrupt kommen und einen neuen Wert in rpm schreiben. Dann geht> auf den CAN zur Hälfte der Neue und zur Hälfte der alte Wert. Und wenn> der alte Wert rpm = 256 war und der Neue rpm = 255 ist, was geht dann> auf den Bus?
Wenn ich den sendevorgang in die ISR verlagere, sehe ich keine Ausreißer
mehr im plot.
Ich weiß, man sollte möglichst wenig in der ISR tun, aber für meinen
Fall ist das doch eigentlich nicht wichtig wie lange die ISR braucht,
oder?
1
ISR(TIMER1_CAPT_vect)// Flanke an ICP pin
2
{
3
if(pulse==0)
4
{
5
cap.i8l=ICR1L;// low Byte zuerst, high Byte wird gepuffert
6
cap.i8m=ICR1H;
7
timestamp=cap.i32;
8
}
9
pulse++;//INCREMENTING FLAG
10
if(pulse==PulseProUmdrehung)
11
{
12
cap.i8l=ICR1L;// low Byte zuerst, high Byte wird gepuffert
13
cap.i8m=ICR1H;
14
// overflow verpasst, wenn ICR1H klein und wartender Overflow Interrupt
15
if((cap.i8m<128)&&(TIFR1&(1<<TOV1)))
16
{// wartenden timer overflow Interrupt vorziehen
17
++ueberlauf_count;
18
TIFR1=(1<<TOV1);// timer overflow int. löschen, da schon hier ausgeführt
19
}
20
cap.high=ueberlauf_count;// obere 16 Bit aus Software Zähler
21
zeitdifferenz=cap.i32-timestamp;
22
rpm=3750000/zeitdifferenz;// Magic Number 3750000 = 60000/0.016
M. H. schrieb:> Wenn ich den sendevorgang in die ISR verlagere, sehe ich keine Ausreißer> mehr im plot.> Ich weiß, man sollte möglichst wenig in der ISR tun, aber für meinen> Fall ist das doch eigentlich nicht wichtig wie lange die ISR braucht,> oder?M. H. schrieb:> wen man die Interrupts vor dem ganzen Sendevorgang ausschalte und danach> wieder einschalte
Man soll Interrupts nur ganz kurz sperren. Denn ein Interrupt ist wie
der Postbote: der klingelt an der Haustelefonanlage und das Klingeln
wird vom Haustelefon gespeichert. Wenn du jetzt zu lange auf dem Klo
gesessen hast, ist der Postbote mitsamt dem wichtigen persönlich zu
übergebenden Brief schon wieder weg, obwohl der "Interrupt" gespeichert
wurde.
> wen man die Interrupts vor dem ganzen Sendevorgang ausschalte und danach> wieder einschalte
Du musst ja nur diesen Zugriff auf die 16-Bit-Variable (und die Zugriffe
auf andere Variablen breiter als 1 char, die in ISR verändert werden)
atomar machen. Also reicht es, die Interrupts direkt vorher zu sperren
und am Besten sofort danach wieder freizugeben:
cli();
message.data[0] = rpm;
message.data[1] = (rpm>>8);
sei();
M. H. schrieb:> Wenn ich den sendevorgang in die ISR verlagere, sehe ich keine Ausreißer> mehr im plot.
Messen, nicht basteln!
Mach das mit den Pins in deinen Code und schließ das Oszi an.
Eric B. schrieb:> sondern um diese Auswertung:
An dieser Stelle hast du ggfs. sowieso schon Interrupts verpasst, weil
das du ja 24 Pulse abwartest und in der Zwischenzeit das Überlaufflag
schon "zweimal" gesetzt wurde. Das Hochzählen des Überlaufzählers gehört
direkt in die Overflow-ISR...
Eric B. schrieb:> ++ueberlauf_count;> TIFR1 = (1<<TOV1); // timer overflow int. löschen, da
Das ist riskant. Sobald in der ISR zu TOV1 noch mehr ausgeführt wird,
gibt es neue Fehler.