Nabend,
das Thema Zündung wurde hier zwar schon oft diskutiert, jedoch geht's
meist eher um die Hardware. Ich habe jedoch Probleme mit der
Zündzeitpunkt-Berechnung und konnte bisher keinen passenden Beispielcode
finden.
Der Arduino Nano soll per Interrupt0 an D2 auf einen Hallsensor
reagieren, der einmal pro Umdrehung getriggert wird. Verzögert (mit
konstantem Winkel) soll dann die Zündung auslösen auf D3. Die Zeit
zwischen zwei Triggern wird per micros() gemessen und entsprechend dem
vorgegeben ZZP (vorerst als Konstante) die benötigte Verzögerung
berechnet (IgnDelay). Mit delayMicroseconds() wird die berechnete Zeit
gewartet und dann auf D3 der Zündpuls ausgegeben.
Ich habe das einerseits mit einer rotierenden Winkelscheibe getestet, an
der ein Target für den Hallsensor angebracht ist und eine LED über der
Winkelskala, die über D3 angesteuert wird, sodass die Scheibe quasi
"abgeblitzt" wird. Bei einigen Drehzahlbereichen funktioniert das super
(stehendes Bild auf der Skala), in anderen Bereichen kommen jedoch wirre
Werte raus.
Also habe ich einen zweiten Arduino als Signalgenerator benutzt, um die
Hallsensor-Pulse zu simulieren. Die berechneten Werte des ersten Arduino
schaue ich mir dann auf der Konsole an. Auch hier ist zu erkennen, dass
die berechnete Drehzahl in manchen Bereichen schwankt und
interessanterweise auch die Pausendauer, die mit delayMicroseconds()
generiert wird.
Es macht den Eindruck, als würde einfach zu viel Zeit in der ISR
verbracht werden, sodass irgendwas überläuft. Da aber eigentlich das
ganze Programm nur aus der ISR besteht, dürfte das eigentlich nicht
kritisch sein?!
Die ISR wird beispielsweise bei 3000 rpm alle 20 ms aufgerufen. Das
Delay vor dem Zündimpuls hat dann aber gerade mal ca. 1500 us, also
nicht mal 1/10. Wenn ich die Pausendauer durch einen festen Wert
ersetze, stelle ich fest, dass es ab etwa 1000 us zu Problemen kommt.
Hier der Sourcecode:
1
const int ZZP = 30; // Standard ZZP in Grad bei Drehzahl 0.
Timestamp2 = micros() - Timestamp0; // for debug only
48
49
delayMicroseconds(IgnDelay);
50
51
Timestamp3 = micros() - Timestamp0; // for debug only
52
53
digitalWrite(IgnOut, HIGH);
54
delayMicroseconds(20);
55
digitalWrite(IgnOut, LOW);
56
}
Die Ausgabe auf der Konsole sieht beispielsweise so aus:
TimeDiff: 17416 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 512
IgnDelay: 1451
TimeDiff: 17412 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 516
IgnDelay: 1451
TimeDiff: 17412 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 516
IgnDelay: 1451
TimeDiff: 18440 Drehzahl: 3253 Timestamp2: 80 Timestamp3: 1620
IgnDelay: 1536
TimeDiff: 17416 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 512
IgnDelay: 1451
TimeDiff: 17412 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 516
IgnDelay: 1451
TimeDiff: 18436 Drehzahl: 3254 Timestamp2: 84 Timestamp3: 600
IgnDelay: 1451
TimeDiff: 17412 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 516
IgnDelay: 1451
TimeDiff: 18440 Drehzahl: 3253 Timestamp2: 80 Timestamp3: 1620
IgnDelay: 1536
TimeDiff: 18440 Drehzahl: 3253 Timestamp2: 80 Timestamp3: 1620
IgnDelay: 1536
TimeDiff: 17416 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 512
IgnDelay: 1451
TimeDiff: 17412 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 516
IgnDelay: 1451
TimeDiff: 18436 Drehzahl: 3254 Timestamp2: 84 Timestamp3: 1624
IgnDelay: 1536
TimeDiff: 17420 Drehzahl: 3444 Timestamp2: 84 Timestamp3: 516
IgnDelay: 1452
TimeDiff: 17416 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 512
IgnDelay: 1451
TimeDiff: 18440 Drehzahl: 3253 Timestamp2: 84 Timestamp3: 1624
IgnDelay: 1536
TimeDiff: 18440 Drehzahl: 3253 Timestamp2: 84 Timestamp3: 1620
IgnDelay: 1536
TimeDiff: 18436 Drehzahl: 3254 Timestamp2: 84 Timestamp3: 1624
IgnDelay: 1536
TimeDiff: 17416 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 1536
IgnDelay: 1536
TimeDiff: 17412 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 516
IgnDelay: 1451
TimeDiff: 17412 Drehzahl: 3445 Timestamp2: 84 Timestamp3: 516
IgnDelay: 1451
Die Drehzahl von ca. 3253 wäre korrekt, bei 3445 ist etwas schief
gegangen. Das ist auch daran zu erkennen, dass bis zu TimeStamp3 viel zu
wenig Zeit vergangen ist. Etwa 1500 us wären hier korrekt, so wie es in
IgnDelay auch immer korrekt berechnet wird.
Nur die Ausführung von delayMicroseconds() scheint manchmal
"überzulaufen", was dann auch zu einem falschen TimeDiff führt. Wie
gesagt treten keine Fehlmessungen auf, wenn ich für delayMicroseconds()
eine Konstante unter 1000 nehme.
Ich habe auch schon versucht, nur noch die Timestamps in der ISR zu
setzen und alles Andere in der main zu machen, führte aber zum selben
Ergebnis. Auch ein deaktivieren aller Interrupts mit cli() am Anfang und
sei() am Ende der ISR nützt nichts. Ebenso habe ich delayMicroseconds
durch eine eigene Routine mit For-Schleife ersetzt, trotzdem kein
Unterschied im Verhalten.
Und jetzt danke für eure Hinweise!
Lösung gefunden! Nachdem ich nun den ganzen Tag gesucht habe ...
Während der ISR wird der Überlauf des Timers, der für micros() verwendet
wird, nicht detektiert, da der dafür zuständige Interrupt dann
deaktiviert ist. Ein sei() am Anfang der ISR führt endlich zu korrekten
Timings :)
Istan schrieb:> Der Code ist Murks!
Da kann ich erstmal nicht widersprechen ;)
Könnt ihr mir mal grob skizzieren, was in der ISR zu tun ist (vermutlich
nur den Timer lesen) und wie ich das dann in der main verarbeite? Woher
weiß die main, dass ein Interrupt aufgetreten ist? Dass könnte man ja
nur pollen ... und wie kriegt man dann noch präzises Timing hin?
H.Joachim S. schrieb:> Nutze die OutputCompare-Register für Beginn Ladung Zündspule und Zündung> selbst.
Hast Du dafür mal ein Beispiel? Und wie gehe ich mit Überlauf der Timer
um? Bei langsamen Drehzahlen kann zwischen zwei Pulsen schon mal mehr
als eine Sekunde liegen.
Zündspule laden brauch ich nicht. Ist eine Kondensatorzündung, da muss
nur der Thyristor getriggert werden.
Moin,
ok, nach einer schlaflosen Nacht habe ich nun eine grobe Vorstellung:
Die ISR misst die Zeit für eine Umdrehung und berechnet die Zeit bis zur
Zündung (wie bisher auch). Damit wird ein Compare-Timer aufgezogen, der
dann seinen Interrupt auslöst und darin wird gezündet. Das macht Sinn
oder?
Bleibt die Frage nach dem Überlauf des Timers ...
Und spricht etwas dagegen, zur Zeitmessung (für die Drehzahlermittlung)
weiterhin micros() zu verwenden? Oder gibt's da auch noch irgendwelche
Schweinereien?
Istan schrieb:> Eigentlich benötigst Du nur ein retriggerbares Monoflop.> Das geht auch in Hardware?!
Naja, dann kann ich bei der Original-Zündung bleiben. Es geht ja
letztlich darum, drehzahlabhängige Zündzeitpunkte zu konfigurieren.
Ben B. schrieb:> Schraubst Du einfach Auspuff ab und montierst Du großen SLS-SRB,> schon hast Du schnellstens Mopped im Dorf.
Keine Angst, hab ich schon längst. Aber mit Feststoffboostern kommt eher
schwierig durch den TÜV.
Robert S. schrieb:> Bei langsamen Drehzahlen kann zwischen zwei Pulsen schon mal mehr> als eine Sekunde liegen.
Ist das ein Lanz?
Aber nehmen wir ruhig mal die 1s.
ATMega mit 16MHz, 16bit-Timer mit 62,5kHz clk: >1s Laufzeit, Auflösung
16µs. Wenn das reicht, brauchst du dich um den Überlauf gar nicht
kümmern.
2 Möglichkeiten:
ICP setzt den Zähler zurück, berechnet OCR für die Zündung aus dem
Messwert. Es tritt niemals ein Overflow auf (bzw. wenn der kommt, steht
der Motor).
Noch besser: gar nicht am Zählerstand manipulieren, also durchlaufen
lassen und mit Differenzen arbeiten. Dann stört der OV auch nicht.
Beispiel: Sensor auf OT (oder irgendwo anders, völlig egal, Hauptsache
bekannt). Du bildest nun einfach die Differenz zwischen dem letzten und
dem aktuellen ICP-Wert.
alt: 4000, neu 8000 -> Differenz 4000
alt: 65000, neu 3465 (also mit Überlauf), liefert mit
unsigned-Subtraktion ebenfalls 4000, spielt also keine Rolle.
Genaus so geht es mit der Zündzeitpunkt berechnung: OCR auf aktuellen
ICP + x setzen (x=2000 wären bei der gemessenen Geschwindigkeit also
180°). Passt auch ohne Berücksichtigung EINES evtl. zwischenzeitlichen
Überlaufs. Berücksichtigen musst du also nur, wenn der Zähler zweimal
überläuft. Kommt aber bei 62,5kHz Takt nicht vor.
Mahlzeit,
ah danke, mit den Erläuterungen kann ich arbeiten :) Ich fang dann mal
an zu fummeln ...
H.Joachim S. schrieb:> Ist das ein Lanz?
Nee, aber wenn man den Kickstarter nur streichelt ... Eine Fehlzündung
wäre dann doof. Das kann dann schon mal weh tun im Fuß.
H.Joachim S. schrieb:> Auflösung 16µs
Das wären dann beispielsweise bei 10.000 rpm noch 0,96 °. Müsste
reichen.
Du brauchst denn Zündzeitpunkt und den Schließwinkel bzw. Schließzeit
also 2 Interrupts oder muss diese eine Interrupt Routine immer
umkonfigurieren bzw. ein Bit setzen damit du dann auf "ein" oder "aus"
verzweigst.
Das einzige, wo ich mich noch unwohl fühle, sind die Datentypen für
TimeDiff und IgnDelay. Aber so funktioniert zumindest die Berechnung:
IgnDelay = TimeDiff * (ZZP-Advance) / 360 - CalcCorrection;
Aber ich vermute, da könnte man noch eleganterweise den ein oder anderen
Typecast einbauen?!
Sonst noch Verbesserungsvorschläge?
Nochmals vielen Dank!
Die 20µs delay kannst du auch noch raussschmeissen. Macht in deinem Fall
zwar wahrscheinlich nichts, ist aber trotzdem bäh in einer ISR. Immerhin
rund 300 Takte, in denen der MC komplett blockiert ist - gar nicht erst
angewöhnen. So ein vermeidbarer Kram holt einen irgendwann immer ein.
Und es gibt für weitere Verbesserungen auch noch die Möglichkeit, dass
ein OCR-Ereignis direkt auf einen Pin wirkt (set, clear, toggle) ohne
Zutun der Software. Vermeidet Latenzen/Jitter, falls sich der MC gerade
in einer anderen ISR+delay befindet :-)
ISR(TIMER1_CAPT_vect) {
TimeDiff = (ICR1 - Timestamp1);
Timestamp1 = ICR1;
IgnDelay = TimeDiff * (ZZP-Advance) / 360 - CalcCorrection;
OCR1A = IgnDelay + ICR1;
OCR1B=OCR1A+PULS_LENGTH;
}
ISR(TIMER1_COMPA_vect) {
digitalWrite(IgnOut, HIGH);
digitalWrite(LED_BUILTIN, HIGH);
}
ISR(TIMER1_COMPB_vect) {
digitalWrite(IgnOut, LOW);
digitalWrite(LED_BUILTIN, LOW);
}
Danke für Tipps, bin begeistert!
Werde das einbauen ...
H.Joachim S. schrieb:> int Timestamp1 = 0;>> das müsste dir eigentlich um die Ohren fliegen, muss unsigned int sein.
Habe ich geändert. Ging aber trotzdem, vermutlich wegen
Zweierkomplement-Subtraktion?!
Außerdem führe ich die Berechnungen in Grad jetzt als float durch, damit
auch kleinere Werte als 1° verwendet werden können und damit die
Berechnung von IgnDelay genauer wird. Hatte zuerst Angst, dass die
Berechnung mit float zu langsam wird, aber es scheint problemlos zu
gehen.
Kannst Du vielleicht nochmal drüber schauen, ob die Datentypen so alle
Sinn machen?
1
const float ZZP = 36.3; // Standard ZZP in Grad bei Drehzahl 0.
Wenn ich Deine Tipps von oben befolge, kann ich vermutlich auch das
CalcCorrection raus schmeißen, das ich in praktischen Versuchen mit 7
ermittelt habe. :)
float bringt dir nicht viel, du bekommst den Messsignal mit 16µs
Auflösung und kannst auch Zeitpunkte nur in eben dieser Quantelung
ausgeben, also nur Ganzzahlen.
1000 Rpm -> Messwert 3750, ZZP 36,3° -> float-Ergebnis 378,125, also 378
für OCR.
Kannst genausogut 3750*363/3600 in Ganzzahl rechnen.
Einen Vorteil hätte float: wenn du vor der int-Umwandlung noch 0.5
addierst bekommst du eine Rundung, sonst nur stumpf den Ganzzahlteil.
edit: wenn der Motor einmal läuft, kannst du auch auf 250kHz Takt
umschalten, damit bist du dann bei 4µs Auflösung (Mindestdrehzahl 230
rpm)
Moin,
H.Joachim S. schrieb:> Kannst genausogut 3750*363/3600 in Ganzzahl rechnen.
Ja richtig, das hatte ich sogar kurzzeitig so implementiert. Dann muss
man als "Anwender" halt nur wissen, dass man alles in 1/10 Grad
eintragen muss.
H.Joachim S. schrieb:> Einen Vorteil hätte float: wenn du vor der int-Umwandlung noch 0.5> addierst bekommst du eine Rundung, sonst nur stumpf den Ganzzahlteil.
Jo, ist mir auch aufgefallen. Eigentlich wäre mir der Ganzzahlteil sogar
lieber, als im Zweifelsfall eine Aufrundung. Lieber ein bisschen zu früh
als zu spät zünden ;)
Von daher werde ich wohl zu der Lösung mit 1/10 Grad zurück gehen.
H.Joachim S. schrieb:> wenn der Motor einmal läuft, kannst du auch auf 250kHz Takt> umschalten
Au ja! Es kommen nach und nach immer bessere Ideen :)
Allerdings wäre die eine Zündung, in der der Teiler umgeschaltet wird,
dann falsch. Schließlich muss erst die Drehzahl berechnet und erst dann
entschieden werden, ob der Teiler umgeschaltet wird. In der Zeit ist der
Timer ja schon wieder weiter ... Oder hast Du dafür womöglich auch noch
eine elegante Lösung?
Nochmals besten Dank!
Das geht ohne nennenswerte Fehler/grob daneben liegenden Zündfunken.
Aber da kannst du dir selbst Gedanken machen, ist nicht schwer :-)
Du hast an anderer Stelle viel grössere Fehler: die Kurbelwellendrehung
ist trotz Schwungmasse nicht gleichförmig, sondern hat ständige
Beschleunigungs- und Bremsphasen, abhängig von Zylinderzahl, Hubraum, 2
oder 4Takt, Schwungmasse, Last und sicher noch ein paar andere.
Das ist auch der Grund, warum ausser in Einfachstmotoren der
Kurbelwellensensor eine viel bessere Position der Kurbelwelle liefert,
besonders häufig das 60-2 Polrad, also alle 6° einen Impuls, die 2
fehlenden Zähne für die OT-Erkennung. Das machen die nicht zum Spass
:-), sondern weil eben eine Messung pro Umdrehung nicht ausreicht, dem
Rechner ein halbwegs reales Abbild der Kurbelwellenposition zu liefern.
H.Joachim S. schrieb:> abhängig von Zylinderzahl, Hubraum, 2 oder 4Takt
Dann rate ich mal:
6 Zylinder, 5 Liter Hubraum, 4-Takt
müsste doch passen für ein Moped, oder? ;-)
Robert S. schrieb:> Moped-Zündung mit Arduino