Hallo Leute, kurz zu meiner Aufgabenstellung. Atmega8 kommuniziert über Uart mit einem weiteren Gerät dass 0 - 5V Eingänge hat. An diesem 0 - 5V hängt ein Drucksensor von VDO welcher eine nichtlineare Kennlinie hat. Vom Sensor habe ich bereits eine passende Kennlinie bzw. Tabelle mit den Passenden werten. Hier die Kennlinie. Bar Ohm 0,0 11 0,5 29 1,0 47 1,5 65 2,0 82 2,5 100 3,0 117 3,5 134 4,0 151 4,5 167 5,0 184 An meinem Atmega8 bekomme ich denn Spannungswert als 10 Bit Wert. 0V -- 0 5V -- 1023 Mit einem Spannungsteiler messe ich die Spannung welche am Sensor abfällt und kann diese dann weiters in Ohm umrechnen. Als "Vorwiderstand" habe ich mal 150Ohm gewählt. Am Mega sehe ich via Debugger auch dass die Gemessene Spannung bzw. der daraus errechnete Widerstand stimmt. Diese Kennlinie möchte ich gerne mit Stückweiser linearisierung linearisieren. Soweit eigentlich kein Problem aus den zwei Punkten zwischen den sich der Aktuelle Ohm Wert gerade befindet kann ich die Geradengleichung berechnen. Die einzelnen Ohmwerte habe ich in ein Array abgegelegt. int PressureTable[11] = {11, 29, 47, 65, 82, 100, 117, 134, 151, 167, 184}; Mein Problem ist nur dass ich nicht weiß wie ich zu meinem Arrayindex komme. Z.B. habe ich den Widerstandswert von 110 Ohm. Somit müsste ich zwischen Arrayindex 5 und 6 Linear Interpolieren. Eigentlich zwischen 2,5 und 3 Bar interpolieren. Wie würdet ihr das Problem lösen ? Oder liege ich mit meinem Ansatz völlig Falsch ? Ich wäre dankbar wenn ihr mich etwas unterstützen könntet. Danke ! :) MfG Lukas
Du hast ein sortiertes Array, das kann man sehr schnell mit einer binären Suche durchsuchen. https://de.wikipedia.org/wiki/Bin%C3%A4re_Suche Nachdem es jedoch nur 11 Stützstellen sind kann man auch mittels einer linearen Suche zu schnellen Ergebnissen kommen. Dein Messwert wird sich wahrscheinlich nicht sprungartig von einer Messung auf die andere ändern. Damit kannst du auch eine lineare Suche ausgehend vom letzten Array wert implementieren, was zu einer Durchschnittlich sehr schnellen Implementierung führen könnte.
Lukas .. schrieb: >welcher eine nichtlineare Kennlinie hat. >... > Hier die Kennlinie. je nach Definition von linear ist die Kennlinie doch relativ linear. http://www.xuru.org/rt/LR.asp gibt max, 1,5 Ohm als Abweichung aus, da könnte fast die linearisierte Kennlinie den 'realeren' Wert haben (vom merkwürdigen Wortkonstrukt abgesehen) Bei 11 Stützstellen dürfte jegliche Optimierung eine Überanpassung sein. Einfach (sofern der pseudo-pseudo-code verständlich ist)
1 | w[I=10](184) < W ==> W=>I/2+0,5 |
2 | .... |
3 | w[I=5](100) < W ==> W = I/2+ 0,5 (W-w[I=5)/ (w[i=6]-w[I=5])+0,5 |
praktisch 0,5* (Wert-untere Stützstelle) /differenz_Stützstelle_o_u
Hallo, > Lukas .. schrieb: > Drucksensor von VDO welcher > eine nichtlineare Kennlinie hat. > Hier die Kennlinie. > Bar Ohm > 0,0 11 > 0,5 29 > 1,0 47 > 1,5 65 > 2,0 82 > 2,5 100 > 3,0 117 > 3,5 134 > 4,0 151 > 4,5 167 > 5,0 184 > Diese Kennlinie möchte ich gerne mit Stückweiser linearisierung > linearisieren. Warum so umständlich. Die Kennlinie ist fast linear, hat nur einen ganz kleinen Bogen. Wenn dir ca. 1% Genauigkeit ausreichen würden, wäre eine einfache lin. Regression schon ok. Man kann es mit einer quadratischen Regression noch besser nachbilden. Hier z.B. eine passende Funktion: y[bar] = a*x² + b*x + c mit x in [V]; a= -0,38 ; b= 36,5 ; c= 11 Das hat auch den Vorteil, dass kleine Ausreißer in der Kennlinie glatt gebügelt werden, welche sehr wahrscheinlich durch Messfehler bei der Aufnahme der Kennlinie entstanden sind. Z.B. die Werte 82 bar und 167 bar liegen ein wenig daneben. > Die einzelnen Ohmwerte habe ich in ein Array abgegelegt. Wie schon geschrieben, kannst du einmal die Regression bestimmen (z.B. mit MS Excel oder OpenOffice-Calc) und führst dann im Programm nur noch die 3 Parameter (a,B,c) ein. Der Rest ist einfache Rechnung. > Wie würdet ihr das Problem lösen ? > Oder liege ich mit meinem Ansatz völlig Falsch ? Nein, das nicht. Aber es geht IMHO einfacher und besser nachvollziehbarer. Gruß Öletronika
Lukas .. schrieb: > An diesem 0 - 5V hängt ein Drucksensor von VDO welcher > eine nichtlineare Kennlinie hat. Wie kommst du drauf, dass die Kennlinie nichtlinear ist? Einen perfekteren linearen Zusammenhang gibt es eigentlich praktisch nicht.
Du musst auch bedenken, dass du durch den Vorwiderstand eine "weitere" Nichtlinearität bekommst ... je höher der Sensorwiderstand, umso weniger Strom fließt ja ... Bei einem 470 Ohm Serienwiderstand (m.E. vorteilhafter als 150 Ohm; Daumenwert ca. 5fache des Mittenbereichs) bekommst Du bei 5V Versorgung Werte zwischen 0,11V und 1,41V. Schau mal, ob der ADC-Bereich beim AT-Mega nicht doch bei ca. 0 bis 2,5V liegt !?! Dann würden erst mal die Spannungswerte reinpassen... Am besten die Sensor-Werte in eine Exceltabelle eintragen und die Strom-/Spannungswerte mit dem Vorwiderstand berechnen lassen... dann daraus Grafik erzeugen (markieren, einfügen Diagramm ...). Da sieht man dann sehr gut die (tatsächliche) Nichtlinearität. Eleganter ist es natürlich, eine Konstantstromquelle (statt eines Serienwiderstandes) zu verwenden. Müsste bei 5V Versorgung und ADC-Bereich 0 bis 2,5V gehen ... Gruss HPS
:
Bearbeitet durch User
Noch ein Tipp: Stichwort "Linearisierung im Arbeitspunkt": Wenn dein Arbeitspunkt z.B. 2,5bar ist (6ter Wert in der Liste), kannst Du eine "optimale Gerade" dort durchlegen (siehe Grafik obere beide Kurven) und erhältst um den Arbeitspunkt herum eine Messung/Regelung mit sehr geringem Fehler. Dies reicht in den meisten Anwendungsfällen völlig aus. VG HPS
:
Bearbeitet durch User
Vielen dank für die ganzen Antworten. :)) Ihr habt mir schonmal weitergeholfen. U. M. schrieb: > Warum so umständlich. > Die Kennlinie ist fast linear, hat nur einen ganz kleinen Bogen. > Wenn dir ca. 1% Genauigkeit ausreichen würden, wäre eine einfache lin. > Regression schon ok. Ja da hast du recht. Eine einfache gerade würde auch ausreichen. Ich will halt das Kennfeld in einem Array ablegen damit die Druckberechnung einfach schnell ohne Rechnerrei geändert werden kann. Ulrich schrieb: > Du hast ein sortiertes Array, das kann man sehr schnell mit einer > binären Suche durchsuchen. > > https://de.wikipedia.org/wiki/Bin%C3%A4re_Suche > > Nachdem es jedoch nur 11 Stützstellen sind kann man auch mittels einer > linearen Suche zu schnellen Ergebnissen kommen. Ich habs jetzt mal mit der Binären Suche gelöst. ;) Ich hab halt gedacht dass man einfach durch Bitshift des Ohmwertes auf den Arrayindex kommt. So habe ich es schonmal gesehen aber leider habe ich mit dieser Methode kein richtiges Ergebnis erhalten. Ganz kurz anderes Thema. Rein aus Interesse. Die Binäre suche könnte man auch Rekursiv umsetzten. Wie reagiert denn der Atmega8 auf Rekursionen ? Das sollte doch ohne Probleme funktionieren oder ? Bzw. ist dann ende wenn der Stack voll ist. Das Druckproblem ist für mich mal abgehakt. Danke für die vielen Anregungen. :) Auch das Mit den Vorwiderstand werde ich nochmals ändern. Jetzt spinne ich mal weiter. Zusätzlich habe ich noch einen NTC Temperaturgeber bis 150° Celsius. Temp.°C | R(Ohm) -20.0 25042.64 -15.0 18749.57 -10.0 14095.92 -5.0 10710.62 .0 8220.03 5.0 6340.08 10.0 4934.67 15.0 3874.20 20.0 3066.43 25.0 2437.07 30.0 1951.37 35.0 1573.61 40.0 1277.55 45.0 1043.72 50.0 857.97 55.0 709.47 60.0 590.02 65.0 490.70 70.0 410.30 75.0 344.87 80.0 291.32 85.0 246.75 90.0 209.98 95.0 180.02 100.0 155.00 105.0 133.71 110.0 115.80 115.0 100.67 120.0 87.84 125.0 76.91 130.0 67.56 135.0 59.54 140.0 52.63 145.0 46.66 150.0 41.49 Ebenfalls Spannungsteiler mit Vorwiderstand ich Sag jetzt mal 1k. Diese Kennlinie ist jetzt wirklich nicht linear. Auch hier würde ich wieder die Stückweise Linearisierung wählen. Wie würdet ihr in diesem Fall vorgehen um auf den richtige Arrayindex zu gelangen. Vorausgesetzt man hat den ADC Wert von 0 - 1023 bzw. auch den errechneten Ohm Wert. MfG Lukas
Am einfachsten ist es, wenn die Eingangswerte der Tabelle Linear verteilt sind, also den gleichen Abstand haben. Dann findest du deine beiden Indizes nämlich durch eine simple Multiplikation mit einem Faktor. Bei stark nichtlinearen Zusammenhängen wie dem NTC, kommt, soweit ich weiß, nur eine binäre Suche ausgehend vom letzten Wert in Frage. Es gibt übrigens auch für den NTC eine einfache Formel, für den Zusammenhang zwischen Temperatur und Widerstand, die bei bekannten Parametern zu relativ wenig Abweichung führen sollte.
Vorarbeit! 1. Überlege Dir vernünftige Wertebereiche für Deine Größen. Der AVR kann nur sehr schwer Fließkomma-Arithmetik. Also musst Du mit Festpunkt arbeiten. Da native Festkomma-Arithmetik auch ein Sackgang ist, nimmst Du Ganzzahlen und "denkst" Dir das Komma. Sinnvolle Skalierungen zum Beispiel: 1000mbar statt 1,0bar. Dann hättest Du Werte bis 5000, passt prima in ein Word. 150d°C statt 15.0°C. Passt auch in ein Word. Du rechnest intern immer mit 1/10°C und gibst am Ende einfach vor der letzten Ziffer das Dezimalzeichen aus. 2. Rechne Deine Tabelle um. Du willst mbar aus ohm. Genaugenommen willst Du mbar aus V. Ganz genau genommen willst Du mbar aus ADC-Counts. Also stelle Deine Tabelle so um, dass als X-Werte ADC-Counts und als Y-Werte der zugehörige Druck rauskommen. 2.a) Mache das Ganze auch für den NTC. Beachte dabei den Spannungsteiler. Du misst keinen Widerstand, Du misst einen ADC-Wert. Und nebenbei wirst Du sehen, dass die sehr krumme Kennlinie des NTC in ADC-Counts umgerechnet sehr viel angenehmer wird. 3. Mache die Stützwerte controllerfreundlich. Du erhältst für die Interpolation eine Funktion a la y = mx + n. Bzw. P(i) = m(i) * (adc - adcnull(i)) + n(i). Du musst also den ADC-Nullpunkt jeder Stützstelle abziehen. Das geht deutlich angenehmer, wenn Du die Stützstellen äquidistant machst und als Vielfache von 2^N machst, also bei 0, 64, 128, 192... 1024. 4. Jetzt kannst Du entweder Steigung m und Offset n als Wertepaare zu jeder Stützstelle abspeichern, oder... moment, die Steigung ist ja Offset des folgenden Stützpunktes - Offset des Stützpunktes durch Abstand, und letzterer ist äquidistant und controllerfreundlich durch 2^N teilbar. Du musst also nur die Offsets n(i) abspeichern. 4.a) Da beim Teilen durch den Abstand der Stützwerte wieder eine Kommazahl rauskommt, und wir nicht mit Kommas rechnen wollen, skalierst Du die Werte so, dass Du auf sinnvolle Ganzzahlen kommst. Im einfachsten Fall mit dem Abstand der Stützpunkte. 5. Jetzt musst Du nur die geeignete Stützstelle finden. Da die Stützpunkte äquidistant sind, ist für einen Abstand von 64 Counts die Stützstelle i einfach ADC / 64. Der Wert ist dann ((n(i+1) - n(i)) * (adc - i * 64) + n(i)) / 64. Alles mit Addition, Subtraktion, Mul und Shift machbar. Aufpassen, wenn negative Werte möglich, Asr nehmen. Ja, es erfordert etwas Vorarbeit im Calc. Aber der Controller wird es Dir danken.
Lukas .. schrieb: > Die Binäre suche könnte man auch Rekursiv umsetzten. > Wie reagiert denn der Atmega8 auf Rekursionen ? Er führt sie so aus, wie du es erwartest. Wenn du einen rekursiven Algorithmus ohne großen Aufwand als Iteration beschreiben kannst, dann ist das aber in der Regel schneller. Du sparst Speicher (weil kein Stack für die Adressen nötig ist), also sparst du dir auch Speicherzugriffe (auf einem AVR eher egal, auf einem PC nicht). Außerdem sparst du dir Funktionsaufrufe (die mitunter teuer sind, je nachdem, wie viele Register zusätzlich noch auf den Stack gesichert werden müssen). Gute Compiler erkennen bestimmte Rekursionsprobleme und lösen sie, wenn möglich, bereits zur Compilezeit in eine Iteration auf. Rekursionen sind theoretisch interessant, aber praktisch eher langsam, wenn man sie nicht auflöst.
Wow vielen Dank für die ausführliche Antwort Karl :) !!! Alles klar man muss ein bisschen vorarbeiten. Ich hab jetzt im Excel bisschen was gerechnet und bin dann auf die ADC Werte der Stützstellen gekommen. Soweit so gut. Ich kann die Werte einfach in ein Array Eintragen und mit der Binären suche den Array Index feststellen. Was halt nicht ganz so effizient ist wie eine Division. Was mir leider noch nicht so klar ist wie ich die Stützstellen, sagen wir mal 32, gleichmäßig über die Kennlinie verteilen kann. Ich hab da mal mein Excel angehängt. S. R. schrieb: > Gute Compiler erkennen bestimmte Rekursionsprobleme und lösen sie, wenn > möglich, bereits zur Compilezeit in eine Iteration auf. Rekursionen sind > theoretisch interessant, aber praktisch eher langsam, wenn man sie nicht > auflöst. Alles klar, danke auch für deine Antwort !! :) Grüße Lukas
Lukas .. schrieb: > Ich kann die Werte einfach in ein Array > Eintragen und mit der Binären suche den Array Index feststellen. Nix binäre Suche. Lineare Suche reicht völlig, so viele Stützstellen sind das ja nicht. Lukas .. schrieb: > Ich hab da mal mein Excel angehängt. Nee, hast Du irgendwie nicht. Lukas .. schrieb: > Was mir leider noch nicht so klar ist wie ich die Stützstellen, sagen > wir mal 32, gleichmäßig über die Kennlinie verteilen kann. Naja, wenn y = m*x + n, dann ist x = (y - n) / m. Wenn Dirdas ausreicht oder die Kennlinie es hergibt, kannst Du zwischen den vorhandenen Stützwerten linear interpolieren. Wenn Du es genauer brauchst, müsstest Du über mehrere Stützwerte quadratisch oder kubisch interpolieren.
Obige Variante hat den Charme, dass Du im Controller nur durch 2^N teilen musst. Wenn Du genug Zeit für eine Ganzzahldivision hast, kannst Du auch die originalen Stützwerte hernehmen. Das ist das Array für 12-bit-ADC-Werte eines 10k-NTC als Spannungsteiler mit 10k Festwiderstand für -40°C (erster Wert) bis 150°C (vorletzter Wert) mit 5K Schritten:
1 | Cntclen = 39; // Länge Kalibrierung 39 Werte |
2 | Pntccal : array [0..Cntclen] of int16 = ( |
3 | 3976, 3932, 3875, 3802, 3711, 3600, 3466, 3311, |
4 | 3134, 2937, 2725, 2502, 2275, 2048, 1828, 1619, |
5 | 1424, 1246, 1086, 943, 817, 706, 611, 528, |
6 | 457, 396, 344, 298, 260, 227, 198, 174, |
7 | 152, 134, 118, 105, 93, 82, 73, 0); // NTC -40..150°C |
Und das ist die Berechnung:
1 | for k := 0 to Cntclen do begin // NTC Kalibrierung |
2 | cmin := load_pgm_word(@Pntccal[k]); // Stützpunkt ADC holen |
3 | if cmin < value then begin |
4 | if k > 0 then begin // wenn nicht erster Stützpunkt |
5 | cmax := Pntccal[k - 1]; // vorherigen Stützwert holen |
6 | temp := 50 * (cmax - value) div (cmax - cmin) + 50 * k - 450; |
7 | end else begin |
8 | temp := -400; |
9 | end; |
10 | break; |
11 | end; |
12 | end; |
Die Vergleiche sind hier auf kleiner als, weil die Kennlinie fallend ist (NTC). Für eine steigende Kennlinie musst Du das umstellen. Die 50 sind die 5.0K Distanz zwischen den Stützwerten, die -450 sind die -40.0°C - 5.0°C Offset, bei dem die Kennlinie beginnt. Als Ergebnis bekommst Du die Temperatur in 0.1°C Auflösung als Festpunktzahl mit Komma an erster Stelle raus. Wertebereich der Variablen beachten.
Jetzt müsste das Excel File auch dabei sein. Bin mir nicht sicher ob wir nicht aneinander vorbeireden. Wenn ich aus meinem Array die richtige Stützstellen gefunden habe ist es für mich kein Problem zwischen der vorherigen und der darauffolgenden Stützstelle zu interpolieren. Das Funktioniert auch soweit. Die Stützstellen nur durch eine, wie du schon geschrieben hast, einfach 2^n Division zu finden bereitet mir Probleme. Da muss man die Stützstellen doch anfangs anpassen damit man mit einer Division durch 2^n an die Richtige Stützstelle gelangt. Auf dieser Seite kann man einen C Code für einen NTC erstellen lassen. http://www.sebulli.com/ntc/index.php?lang=de Mfg Lukas
Hallo Lukas, die obige theoretische Berechnung und Vorgehensweise mag ja durchaus interessant sein, für die Praxis sind in der Regel bestimmte andere Dinge relevant: - Der ADC geht womöglich nur bis 4096 (12 bit), Du müsstest also einen größeren Vorwiderstand nehmen, um den Bereich nicht zu sprengen - Ein Temp.Messung von genauer als 0,5 Grad ist meist unnötig; oft reicht 1 Grad Genauigkeit, insbes. bie weiten Messbereichen wie bei der Öltemperatur - bei 1 Grad Ziel-Genauigkeit kann man die Kurve durch 3 oder 5 Geradenabschnitte so annähern, dass der Fehler minimal ist. - man braucht also keine quadratischen oder kubischen Gleichungen - Man muss dann nur einfache Mult. und Div. für die jeweilige Steilheit der Geradenabschnitte anwenden ... - Trick: Bei Zweierpotenzen kann man auch Links- oder Rechtsshift für Mult. oder Div. verwenden (deutlich schneller...). Praxis: Die eigentliche Optimierung sollte dahingehen, dass man den Vorwiderstand(!) so wählt, dass man möglichst viel mit den Shift-Operationen berechnen kann (also Steigungen von 2; 4; 8; ... bekommt), um die Geradenabschnitte festzulegen !!! Hoffe, es ist nachvollziehbar ... VG HPS
Lukas .. schrieb: > Da muss man die > Stützstellen doch anfangs anpassen damit man mit einer Division durch > 2^n an die Richtige Stützstelle gelangt. Ja. Und die Anpassung machst Du durch Interpolation zwischen zwei bekannten Stützstellen. Dreh mal das Diagramm um 90°, dann sollte der Groschen fallen. Momentan hast Du Stützstellen ADC = f(T) für T von -20 bis 150°C in Schritten zu 5K. Was Du suchst sind Stützstellen T = f(ADC) für ADC von 0 bis 8192cts in Schritten zu 128cts. Du musst also die inverse Funktion finden, was aber komplizierter klingt als es ist, Du schaust einfach für welche T auf Deine Geradenabschnitten der ADC-Wert jeweils 128, 256, 384... wird. Diese T sind Deine neuen Stützstellen. Musst Du aber nicht machen, nimm einfach den Algo von Oben, passe den auf Deinen ADC-Bereich und Deinen NTC an. Tipp: Da die Kennlinie der NTC auf 25°C optimiert ist, empfiehlt es sich für den Spannungsteiler einen Widerstand zu nehmen, der dem NTC-Widerstand bei 25°C entspricht*. Bei Dir also 3,3kohm. In diesem Bereich ist auch die Auflösung am besten. Über 100°C geht die Auflösung und Genauigkeit drastisch in den Keller, wie Du anhand der Werte siehst. *) Es sei denn man will bevorzugt in abseitigen Temperaturbereichen messen, aber dann würde ich eh lieber zu einem PT1000 greifen.
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.