Hallo, ich habe immer noch ein Problem mit meiner Drehzahlmessung über eine Gabellichtschranke. Ich möchte den Wert erst ausgeben wenn 28 Messungen erfolgt sind. Das macht er auch aber auf der 7-Segmentanzeige kommen viel zu hohe Werte raus: So in der Art: 0250 2401 8921 3456 9999 Aber warum? Die F_CPU habe ich richtig eingestellt. Ich messe das gerade über einen Akkuschrauber in dem eine CD eingespannt ist mit einer Kerbe drin. Ist zwar aus der Hand raus nicht das stabilste aber die Werte sollten trotzdem annähernd passen.
> if (Ausgabe==TRUE) > // Das Display wurde mit den Ergebnissen der vorhergehenden Den Fall würde ich mit einer Doppelpufferung der Zeitvariablen lösen, statt die ISR auf die Ausgabe Rücksicht nehmen zu lassen. Die ISR misst stur durch. Am Ende einer Messung werden die Ergebnisse für die Anzeige bereitgestellt (StartTime und EndTime). Zu Beginn eines Anzeigezykluses liest die Anzeigefunktion diese Werte (atomar, siehe Interrupt) und schreibt sie sich in einen Zwischenpuffer. > TIMSK = (1<<TICIE1) | (1<<TOIE1); // Interrupts akivieren, Capture + Overflow > ISR( TIMER1_OVF_vect ) Dieser Interrupt läuft bei dir nach dem Ende der Messzeit weiter! NrOverflows muss zum Zeitpunkt der Ausgabe nicht mehr den Zustand wie bei der Messung der EndTime haben. Durch das Verfahren oben kann man dieses Problem ohne Stoppen des Timers lösen. Zusätzlich zu StartTime und EndTime übergibt man der Anzeigefunktion auch eine Kopie von NrOverflows zum Zeitpunkt der Messung von EndTime. Bzw. man übergibt gleich den berechneten Wert (NrOverflows * 65536L + EndTime). ICR1 würde ich in der Capture ISR einmal und zwar möglichst früh in eine temp. Variable auslesen und dann in der Fallunterscheidung kopieren. Das verringert Probleme mit unterschiedlichen Laufzeiten in den verschiedenen Zweigen. Mehr habe ich nicht geprüft. Man muss nicht auf dem Dach arbeiten, wenn der Keller Löcher hat.
Angenommen ich nehme die Warteroutine in der ISR raus und übergebe die Overflows in einen Puffer:
1 | ISR ( TIMER1_CAPT_vect ) |
2 | |
3 | |
4 | {
|
5 | |
6 | if( ErsteFlanke == TRUE ) |
7 | {
|
8 | StartTime = ICR1; // Startzeit= ICR 1 |
9 | NrOverflows = 0; |
10 | ErsteFlanke = FALSE; // Die naechste Flanke ist das Ende der Messung |
11 | |
12 | |
13 | }
|
14 | |
15 | // das ist die zweite Flanke im Messzyklus. Die Messung wird gestoppt
|
16 | |
17 | else
|
18 | {
|
19 | EndTime = ICR1; // Stopzeit für die Messung= ICR1 |
20 | Ausgabe = TRUE; // Eine vollst?ndige Messung. Sie kann ausgewertet werden |
21 | ErsteFlanke = TRUE; // Bei der naechsten Flanke beginnt der naechste Messzyklus |
22 | Puffer = NrOverflows; |
23 | |
24 | }
|
25 | |
26 | }
|
Dann springt er als nächstes in die Berrechnungsfunktion. Dort verbiete ich kurzzeitig Interrupts dann müsste er doch aufallefälle mal ein richtiges Ergebnis zusammenbekommen?! Dieses Teilprogramm haben schon zig Leute vor mir verwendet und bei denen hat es anscheinend geklappt der einzige Unterschied ist das ich kein LCD verwende sondern eine 7-Segmentanzeige.
> Dieses Teilprogramm haben schon > zig Leute vor mir verwendet und bei denen hat es anscheinend geklappt Kann ich mir nicht vorstellen. Diesem Hinweis solltest du nach gehen: >> (atomar, siehe Interrupt) > Erg = (NrOverflows * 65536) + (EndTime - StartTime); Kannst du diese Rechnung erklären? > der einzige Unterschied ist das ich kein LCD verwende sondern eine > 7-Segmentanzeige. Dann würde ich mir die Rohwerte z.B. über RS232 ausgeben lassen. Und damit nachrechnen, ob die Berechnung der 7-Segmentanzeige funktioniert.
Angenommen bei einem CPU Takt von 8Mhz würde das Timeregister pro Sekunde 8.000.000x erhöht werden. Sprich es würden 122 Overflows stattfinden? Erg = (122 * 65536) + (32500-20000) = 8007892 Für Start&Endtime hab ich irgendeinen Wert genommen jenachdem wo der Timer gerade steht. Erg = F_CPU / Erg; Erg = 8000000/8007892 = 1 Drehzahl= Erg*60 Drehzahl= 1*60= 60 1/min
Welchen Wert hat NrOverflows zum Zeitpunkt der Rechnung? Nur eher zufällig den gleichen wie beim Zuweisen des Zeitstempel 2. Flanke (EndTime)...
Also in der ISR eine Kopie von NrOverflows anlegen. 2. Hast du den Fall StartTime > EndTime mal gerechnet? Ich würde so rechnen: Erg = (NrOVF * 65536UL + EndTime) - (0 * 65536UL + StartTime) oder verkürzt: Erg = (NrOVF * 65536UL + EndTime) - StartTime
NrOverflow wird ja nach der ersten fallenden Flanke auf 0 gesetzt. Eigentlich bräuchte man den Wert wenn die 2. fallende Flanke ansteht. Jetzt zählt er noch geringfügig weiter bis die Rechnung kommt das stimmt. Ich hab versucht NrOverflow nach der 2. Flanke in eine Puffervariable zu packen leider änderte es nichts am Ergebniss Gruss Ralph
Eigentlich sollte es nichts ausmachen da die Variablen vorzeichenlos sind sollte das gleiche rauskommen?
1 | #include <util/atomic.h> |
2 | |
3 | volatile uint32_t Gemessene_Zeit; |
4 | |
5 | ISR ( TIMER1_CAPT_vect ) |
6 | {
|
7 | static uint8_t ErsteFlanke = TRUE; // global entfällt |
8 | if( ErsteFlanke == TRUE ) |
9 | {
|
10 | Ausgabe = FALSE; |
11 | StartTime = ICR1; |
12 | NrOverflows = 0; |
13 | ErsteFlanke = FALSE; |
14 | }
|
15 | else
|
16 | {
|
17 | EndTime = ICR1; |
18 | Gemessene_Zeit = (NrOverflows * 65536UL + EndTime) - StartTime; |
19 | ErsteFlanke = TRUE; |
20 | // Eine vollständige Messung. Sie kann ausgewertet werden
|
21 | Ausgabe = TRUE; |
22 | }
|
23 | }
|
24 | |
25 | void Messwerte_auslesen(void) |
26 | {
|
27 | if ( Ausgabe == FALSE ) |
28 | return; |
29 | |
30 | // http://www.nongnu.org/avr-libc/user-manual/group__util__atomic.html
|
31 | // mikrocontroller.net Artikel Interrupt
|
32 | ATOMIC_BLOCK(ATOMIC_FORCEON) |
33 | {
|
34 | Erg = Gemessene_Zeit; |
35 | }
|
36 | |
37 | // Aus der Messzeit ergibt sich dann die Frequenz f = 1 / t
|
38 | // Drehzahl berechnen aus der Frequenz f * 60 sec = U/min
|
39 | zahl = (60UL * F_CPU) / Erg; |
40 | |
41 | // Über 28 Einzelmessungen mitteln
|
42 | Mittelwert += zahl; |
43 | schleife++; |
44 | if ( schleife == 28 ) |
45 | {
|
46 | drehzahl = Mittelwert / schleife; |
47 | Mittelwert = 0; |
48 | schleife = 0; |
49 | }
|
50 | |
51 | // Max. mögliche Anzeige begrenzen
|
52 | if ( drehzahl > 9999 ) |
53 | drehzahl = 9999; |
54 | }
|
55 | |
56 | main() |
57 | {
|
58 | ...
|
59 | while(1) |
60 | {
|
61 | Messwerte_auslesen(); |
62 | Messwerte_ausgeben(); |
63 | }
|
64 | }
|
Die Zahl der globalen volatile Variablen kann drastisch verringert werden. Volatile müssen sein: Gemessene_Zeit, Ausgabe und NrOverflows.
Hallo Stefan, ich dank dir für deine Arbeit und den Code. Der sieht doch gleich viel sauberer aus und das mit dem atomaren Auslesen war mir auch völlig neu. Ich hab aber noch das Problem das mir ziemlich oft die 9999 angezeigt wird oder auch bei langsame Drehzahlen viel zu hohe Werte. Das kann ja eigentlich nur am Wert von NrOverflow liegen der viel zu hoch ist?
Prüfe die Datentypen deiner Variablen. Hier zwei Beispiele Was passiert weil NrOverflows 8-Bit und nicht 16-Bit ist? Gemessene_zeit ist mit NrOverflows 8-Bit max. 16777215. d.h. die Mindestdrehzahl ist 28 rpm. Was passiert weil zahl signed und nicht unsigned ist? Wenn Erg (= Gemessene_zeit) kleiner wird als 14648 (<1,83 ms, >32768 rpm), wird zahl durch Integeroverflow negativ.
Derzeit (Mittelwert im Userprogramm) kann es vorkommen, dass man Werte verpasst, weil die ISR bereits eine neue Messung angefangen hat. Deine ursprüngliche Lösung hatte das Problem nicht, weil die ISR einfach nicht gemessen hat, so lange noch nicht ausgegeben wurde. auch nicht ideal. Auch muss man beachten, ob man lange genug misst. Beispiel bei 28 rpm und Mittelwert aus 28 Messungen liegt ein gültiger Mittelwert frühstens nach 1 Minute vor! IMHO würde es Sinn machen, die Mittelwertbildung in der ISR zu machen. Und günstig wäre eine gleitende Mittelwertbildung statt einer fixen. Ich könnte mir gut vorstellen, dass die ISR z.B. Mittelwerte der Zeiten aus 2, 4, 8, 16,... Messungen anbietet. 2er Potenzen um die Divisionen schnell zu machen (Rechtsshift). Die aufwändigere Berechnung der Drehzahl und die Aufbereitung der Anzeige wird wie bisher im Userprogramm gemacht.
Hallo Stefan, man bin ich betrunken. Schau mal in die Interrupinitialisierung. "|=" Das im Zusammenhang mit dem ICN1 Filter und deiner Version des Programms bringt mir zumindest jetzt ganz passable Werte :) Jetzt muss ich nur noch die Anzeige auf 0000 abfallen lassen wenn in einer bestimmten Zeit keine Interrupts mehr auftauchen. Die Bohrmaschine hängt an einem FU und besitzt eine Abfallrampe von 2sek. Minimaldrehzahl = 200 1/min. Da könnte ich theoretisch sagen wenn die Drehzahl < 200 = 0. Aber wenn ein Not-Aus betätigt wird steht die Maschine abrupt. Dann würde die Anzeige noch einen Wert anzeigen. Wie löst man sowas am Besten? Gruss Ralph
Hmm, mich würde es stutzig machen, wenn überhaupt noch eine Anzeige zu sehen ist, wenn der Not-Aus gedrückt ist. Not-Aus heisst für mich: Da ist kein Saft mehr drauf. Nirgends. Auch bei dem Nullen per Software unterhalb der Mindestdrehzahl 200 rpm wäre ich vorsichtig. Die 0 würde ich nur anzeigen, wenn das Teil sich wirklich < 1 rpm bewegt also NrOverflows > 7324 (d.h. NrOverflows auf uint16_t erweitern). Das Unterschreiten der Mindestdrehzahl könnte man anzeigen, in dem man z.B. die Anzeige blinken lässt. Eine andere Methode wäre z.B. ---- anzuzeigen, aber nicht 0000. 0000 würde für mich heissen: Maschine dreht nicht mehr bzw. < 1 rpm. > man bin ich betrunken. Schau mal in die Interrupinitialisierung. > "|=" Passiert. Bei mir im Simulator ist das nicht schlimm. Das Testprogramm läuft dennoch. Merke: Simulieren ist nicht Testen.
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.