Forum: Mikrocontroller und Digitale Elektronik Atmega8: LookUp Table nicht linearen Sensor auswerten


von Lukas .. (luki93)


Lesenswert?

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

von Ulrich (Gast)


Lesenswert?

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.

von Dirk (Gast)


Lesenswert?

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

von U. M. (oeletronika)


Lesenswert?

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

von Wolfgang (Gast)


Angehängte Dateien:

Lesenswert?

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.

von H.p. S. (hps)


Angehängte Dateien:

Lesenswert?

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
von H.p. S. (hps)


Angehängte Dateien:

Lesenswert?

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
von Lukas .. (luki93)


Lesenswert?

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

von Simon (Gast)


Lesenswert?

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.

von Karl (Gast)


Lesenswert?

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.

von S. R. (svenska)


Lesenswert?

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.

von Lukas .. (luki93)


Lesenswert?

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

von Karl (Gast)


Lesenswert?

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.

von Karl (Gast)


Lesenswert?

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.

von Lukas .. (luki93)


Angehängte Dateien:

Lesenswert?

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

von H.p. S. (hps)


Lesenswert?

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

von Karl (Gast)


Lesenswert?

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
Noch kein Account? Hier anmelden.