Hallo zusammen,
ich bins nochmal mit meinem Tiny 44 und einem Rechenfehler, den ich mir
nicht erklären kann.
Ich will eine Spannung bis 30V messen. Der Spannungsteiler ist 82K zu
13K.
Lege ich eine Spannung von 8,05V an, so ergibt das einen ADC Wert von
223.
Lege ich eine Spannung von 16,55V an, so ergibt das einen ADC Wert von
447.
Berechne ich daraus die Spannung am ADC Eingang, so komme ich für 227
auf 1,095V und für 447 auf 2,184V (gemessen) und rechnerisch auf 1,095V
und 2,195V. Als Referenz nehme ich 5029mV vom 7805.
Das Verhältnis von 82/13 ist 6,307. Multipliziere ich die oben
gemessenen Spannungen am ADC mit 630 so komme ich auf viel zu geringe
Spannungen, besipielsweis ist 2,184 x 630 nur 13,830V statt auf 16,55V.
Passe ich den Faktor 630 an, so stimmt zwar die Messung bei 16,55V, aber
bei allen anderen Spannungen kommt dann wieder Mist raus.
Wo liegt mein Denkfehler? Die Spannungen am ADC stimmen fast exakt mit
den berechneten Werten überein, nur nach der Berechnung der realen
Spannung kommt Mist dabei raus.
Hier noch der Codeabschnitt:
1
volt_adc=ADC_Read_Avg(1,4);
2
3
volt_u=volt_adc*Vref/1023;
4
5
volt_real=volt_u*630;//Hier kommen jetzt 13809mV statt 16550mV raus.
Marko schrieb:> ich bins nochmal mit meinem Tiny 44 und einem Rechenfehler, den ich mir> nicht erklären kann.
Wie sollen wir dir den denn erklären können, wenn du nicht mal den Code
postest, der falsch rechnet?
Also: entweder du postest deinen fehlerverseuchten Code oder du machst
es dir selbst.
PS: Fotos vom Aufbau helfen bei dem Problem ganz sicher nicht weiter.
Jedem, der sich die Hose nicht mit der Kneifzange anzieht, ist das auf
Anhieb klar.
Marko schrieb:> Der Spannungsteiler ist 82K zu 13K.
Ein Spannungsteiler aus diesen beiden Werten?
Dann: Vadc = Vin * (13/(13+82))
Dein Faktor ist also (82+13)/13, nicht 82/13
c-hater schrieb:> PS: Fotos vom Aufbau helfen bei dem Problem ganz sicher nicht weiter.> Jedem, der sich die Hose nicht mit der Kneifzange anzieht, ist das auf> Anhieb klar.
Da macht einer sich mal die Mühe, mehr Info zu liefern als "tut nicht",
sondern zeigt den ganzen Gedankengang, und wird derart abgebügelt. Ok,
beim Code hätte er mehr liefern sollen, aber der gute Wille war immerhin
erkennbar. Und tatsächlich war die Information doch ausreichend. ;-)
A. K. schrieb:> Dann: Vadc = Vin * (13/(13+82))> Dein Faktor ist also (82+13)/13, nicht 82/13
Erstmal danke dafür! Jetzt komme ich schonmal auf 16001mV, wir nähern
uns :).
Martin schrieb:> vref/1023 wird irgendwas mit 5 geben (integer), aber nicht 4,xx oder> 5,xx, daher der Fehler
Das macht natürlich Sinn, aber wie umgehe ich das ganze clever? Nochmal
mit paar Zehnerpotenzen multiplizieren vor der Division?
Code hängt mit an.
Marko schrieb:> Das macht natürlich Sinn, aber wie umgehe ich das ganze clever? Nochmal> mit paar Zehnerpotenzen multiplizieren vor der Division?
Und/oder durch Umstellung der Rechnung. So kommt bei
a*(b/c)
oft ein anderes Ergebnis raus als bei
(a*b)/c
auch wenn dein Mathelehrer das anders sieht.
Marko schrieb:> Das macht natürlich Sinn, aber wie umgehe ich das ganze clever? Nochmal> mit paar Zehnerpotenzen multiplizieren vor der Division?
Zehnerpotenzen sind für doofe Menschen, denn µC rechnen im Binärsystem.
Ansonsten ist die Idee, den Zähler zu erweitern, aber durchaus brauchbar
und zielführend.
Also Erweitern ja, aber in Zweierpotenzen. Und zwar genau um genau so
viele, wie unbedingt nötig, nicht mehr und nicht weniger.
Dazu muß man bloß festlegen, wie genau das Ergebnis eigentlich
dargestellt werden muss...
c-hater schrieb:> Zehnerpotenzen sind für doofe Menschen, denn µC rechnen im Binärsystem.
Zehnerpotenzen können sinnvoll sein, wenn man hinterher Nachkommastellen
anzeigen will.
A. K. schrieb:> Schau mal da rein:> https://www.mikrocontroller.net/articles/Festkommaarithmetik
Hatte ich schon, daher kam ich ja auch auf die Idee das ganze ohne
Kommazahlen zu lösen und alles in mV mA und mW anzugeben :).
c-hater schrieb:> Also Erweitern ja, aber in Zweierpotenzen. Und zwar genau um genau so> viele, wie unbedingt nötig, nicht mehr und nicht weniger.
Also soll ich statt durch 100 zu teilen durch 2^7 = 128 teilen? Wird da
die Abweichung nicht zu groß?
> Dazu muß man bloß festlegen, wie genau das Ergebnis eigentlich> dargestellt werden muss...
Es wird eine Abschaltung bei Überspannung in einem 24V Batterie Netz.
Mir würde es also reichen wenn die erste Nachkommastelle in etwa stimmt.
A. K. schrieb:> Zehnerpotenzen können sinnvoll sein, wenn man hinterher Nachkommastellen> anzeigen will.
Nunja, mir reicht eigetlich die Angabe in mV. Es muss nicht 24,4V sein.
Sollte das allerdings eleganter sein, dann gehe ich den Weg.
In der Ganzzahlarithmetik sollte man IMMER erst multiplizieren und dann
erst dividieren.
Natürlich sollte man die Maxima (nicht die aus der Bulle-Wahr-Zeitung)
im Auge behalten.
A. K. schrieb:> Zehnerpotenzen können sinnvoll sein, wenn man hinterher Nachkommastellen> anzeigen will.
Dann darf man aber auch nicht binär rechnen, sondern muß das komplett im
(simulierten) Dezimalsystem tun, also BCD o.ä. als Zahlenformat
verwenden.
Ob der OP DAS wirklich will und auch zu leisten vermag...
Johann L. schrieb:> Da ATtiny44 nicht multiplizieren kann
Kann er das nicht?
Scheint doch bis jetzt ganz gut zu klappen, oder wie darf ich das
verstehen?
Das du mir den Assembler Code gibst ist sehr nett, allerdings kann ich
daraus nicht all zu viele Schlüsse ziehen :(.
Ich habe jetzt mal die Division rausgehauen und durch eine
Multiplikation ersetzt, aber ich bekomme immer nur noch zu wenig
angezeigt.
Statt der 16550 zeigt er trotzdem nur 15888, da haut doch was nicht hin.
Code anbei.
Marko schrieb:> Scheint doch bis jetzt ganz gut zu klappen, oder wie darf ich das> verstehen?
Er hat keinen Maschinenbefehl dafür, was die Sache etwas umständlicher
macht.
Marko schrieb:> Statt der 16550 zeigt er trotzdem nur 15888, da haut doch was nicht hin.
Nochmal kurz zur Hardware - der ADC muss aus deinem Spannungsteiler den
S&H Kondensator laden, dewegen steht im Datenblatt auch, das der
Innenwiderstand der ADC Quelle nicht höher als etwa 10k liegen sollte.
Dein 82k in der Highside wird beim S&H Vorgang schlicht einknicken.
Schalte da wenigstens einen kleinen Kondensator zwischen ADC Eingang und
Masse. So etwa 470pF - 4,7nF sollten ok sein.
Hallo Marko,
es gibt zwei Appication Notes von Atmel zur ADC-Wendergenauigkeit, und
der notwendigen ADC-Beschaltung.
Diese helfen Dir das vorher ausgeführte zu verstehen.
Marko schrieb:> Der Spannungsteiler ist 82K zu> 13K
Nutzt Du Präzisions-Messwiderstände?
Normalerweise haben die eine gewisse Toleranz (am letzten Ring erkennbar
:-) )- und die Wahrheit liegt um max. +/- x % daneben - bei beiden
Widerständen.
Du musst das ernst nehmen, dass du Divisionen nach hinten schieben
willst.
Auch die hier!
1
amp_u=amp_diff*Vref/1024;
Nimm dir einen Zettel. Nimm einen Bleistift. Schreib dir die
Einzeloperationen auf und dann benutzt du dein Mathewissen um die ganzen
Berechnungen in eine einzige Formel zu bringen.
Divisionen zum Schluss!
Der interessante Teil besteht jetzt darin, die ganzen Operationen zu
verfolgen, ob du irgendwo den Zahlenbereich verlässt. Da du long benutzt
hast du ein wenig Spielraum. Aber geprüft werden muss es.
ultoa ist für 'unsigned long'. Darum beginnt der Name mit ul.
Die korrekte Funktion für einen long wäre ltoa.
Dann klappt das auch mit dem Vorzeichen bei negativen Zahlen.
chars[sizeof("4294967295")];//Buffer für Ausgabe über LCD
2
chart[sizeof("4294967295")];//Buffer für Ausgabe über LCD
3
charu[sizeof("4294967295")];//Buffer für Ausgabe über LCD
4
charv[sizeof("4294967295")];//Buffer für Ausgabe über LCD
du brauchst doch nicht für jeden Aufruf von ltoa (bzw. ultoa) einen
eigenen Buffer! Ein einziger genügt! Der wird bei jeder Ausgabe
wiederverwendet, da du ja sowieso das nach ASCII konvertierte Ergebnis
sofort ausgibst!
Sinnigerweise schreibt man sich eine Hilfsfunktion die eine einzige
Aufgabe hat: Einen long auszugeben bzw. einen unsigned long auszugeben.
In Anlehnung an die restlichen lcd Funktionen in deinem Vorrat an
Funktionen schreib ich mal
1
voidlcd_putl(longvalue)
2
{
3
charbuffer[9];
4
5
ltoa(value,buffer,10);
6
lcd_puts(buffer);
7
}
8
9
voidlcd_putul(unsignedlongvalue)
10
{
11
charbuffer[9];
12
13
ultoa(value,buffer,10);
14
lcd_puts(buffer);
15
}
Mit diesen Funktionen vereinfachtz sich dann dein Code zu
1
intmain()
2
{
3
...
4
// buffer Variablen weg
5
...
6
7
...
8
9
// Das kommt da weg. Initialisiert wird einmal und nicht dauernd
10
// lcd_init();
11
12
lcd_xy(0,0);
13
lcd_puts("mA:");
14
lcd_putul(amp_real);
15
16
lcd_xy(8,0);
17
lcd_puts("mV:");
18
lcd_putul(volt_real);
19
20
lcd_xy(0,1);
21
lcd_puts("mW:");
22
lcd_putul(power);
23
24
lcd_xy(8,1);
25
lcd_puts("ADC:");
26
lcd_putul(volt_adc);
27
28
_delay_ms(2000);
die 3-er Sequenz
1
lcd_xy(...
2
lcd_puts("
3
lcd_putul(
könnte man auch noch in eine eigene Funktion zusammenfassen, so dass
dann letzten Endes da steht
und jetzt fängt das ganze an übersichtlich zu werden.
Sei nicht zu faul, dir Funktionen zu schreiben! Du machst dir eine Menge
Mehrarbeit, wenn du es nicht tust.
Und rück deinen Code ordentlich ein!
Das Display löschen wollen wir da eigentlich auch nicht haben. Im Moment
ist es noch notwendig, weil deine Ausgabezahlen in unterschiedlichen
Schleifendurchläufen nicht gleich viele 'BUchstaben' haben werden.
Genau das willst du aber erreichen. Aus 2 Gründen
* zum einen willst du ein LCD nicht dauernd löschen und neu beschreiben.
(Hier spielt es noch keine Rolle, aber wenn das schnell genug passiert,
dann flackert das wie Sau)
* zum anderen ist das ergonomisch eine einzige Katastrophe, wenn mir die
Zahlen beim lesen dauernd nach links bzw. rechts wegrutschen, nur weil
da ein Übergang von zb 99 auf 100 drinnen ist. Die Einerstelle soll in
einer Anzeige immer an der gleichen Position auftauchen! Dann kann man
das auch gut lesen.
Marko schrieb:> Das du mir den Assembler Code gibst ist sehr nett, allerdings kann ich> daraus nicht all zu viele Schlüsse ziehen :(.
Ok, Code zu posten bringt nix wenn er nicht gelesen wird...
Beitrag "Re: [C] Tiny 44 - Rundungsfehler?"
Marko schrieb:> Lege ich eine Spannung von 8,05V an, so ergibt das einen ADC Wert von> 223.> Lege ich eine Spannung von 16,55V an, so ergibt das einen ADC Wert von> 447.>> Berechne ich daraus die Spannung am ADC Eingang, so komme ich für 227> auf 1,095V und für 447 auf 2,184V (gemessen) und rechnerisch auf 1,095V> und 2,195V. Als Referenz nehme ich 5029mV vom 7805.
Versuchen wir doch mal auszurechnen, wie groß des Teilerverhältnis des
Spannungsteilers tatsächlich ist:
8,05V / 1,095V = 7,35
16,55V / 2,184V = 7,58
Ja was jetzt? Die beiden Werte unterscheiden sich um 3%!
Mögliche Erklärungen:
- Dein Spannungsteiler ist sehr nichtlinear, was aber ausgeschlossen
werden kann, da gewöhnliche Widerstände ziemlich genau das Ohmsche
Gesetz befolgen, solange sie keinen größeren Temperaturschwankungen
ausgesetzt sind.
- Die angelegte Spannung ist keine saubere Gleichspannung, so dass die
parasitären Kapazitäten des ADC das Ergebnis verfälschen.
- Du hast ganz einfach falsch gemessen oder die Messergebnisse falsch
abgeschrieben. So gibst du bspw. für den ADC-Wert bei der kleineren
Eingangsspannung erst 223 und etwas weiter unten 227 an. Ist das ein
Flüchtigkeitsfehler? Wenn ja, könnte ein solcher vielleicht auch bei
den gemessenen Spannungen passiert sein?
Der beschriebene Fehler kann selbst durch eine noch so genaue Arithmetik
im AVR nicht korrigiert werden ;-)
Ganz abgesehen davon, dass es in einem Batteriewächter wohl wirklich
keine Rolle spielt Floating Point Arithmetik zu benutzen (Vorausgesetzt
das geht sich in den 4K Flash aus. Aber davon geh ich mal aus).
Wer einen
1
_delay_ms(2000);
im System hat, dem spreche ich das Recht ab, sich über Laufzeiten von
Berechnungen den Kopf zu zerbrechen. Und das man für nicht genutztes
Flash von Atmel kein Geld zurückbekommt, hat sich auch schon
rumgesprochen.
Allerdings ist es auch hier sinnvoll, erst mal seine Hausaufgaben zu
machen und die ganzen Berechnungen zusammenzufassen und auf einen
gemeinsamen Bruch zu bringen.
Karl H. schrieb:> Du musst das ernst nehmen, dass du Divisionen nach hinten schieben> willst.>> Auch die hier!>
1
>amp_u=amp_diff*Vref/1024;
2
>
>
aber die Division ist doch hinten und nach der Compiler wertet das von
links nach rechts aus, oder etwa nicht?
Karl H. schrieb:> amp_real = ( ( amp_adc - amp_offset ) Vref 1515 ) / ( 100 * 1024> );
wer nach 2 Wochen noch weiß warum da 1515 steht kriegt von mir einen
Punkt
Walter S. schrieb:> Karl H. schrieb:>> Du musst das ernst nehmen, dass du Divisionen nach hinten schieben>> willst.>>>> Auch die hier!>>
1
>>amp_u=amp_diff*Vref/1024;
2
>>
>>>> aber die Division ist doch hinten
Diese Einzelberechnung: ja
Aber mit dem Wert wird noch weiter gerechnet!
1
amp_u = amp_diff * Vref/1024;
2
3
|
4
+-----------+
5
|
6
v
7
amp_real = amp_u * 1515;
"Divisionen nach hinten schieben" betrachtet die komplette Berechnung.
Von den Ausgangswerten bis zum endgültigen Endergebnis. In diesem
kompletten Prozess will man die Divisionen so spät wie möglich machen.
Jegliche abgeschnittene Kommastelle, die in der ersten Berechnung flöten
geht, fehlt dann bei der Multiplikation mit 1515.
> wer nach 2 Wochen noch weiß warum da 1515 steht kriegt von mir einen> Punkt
Das kann sich der TO selbst überlegen, ob er dafür nicht einen
vernünftigen Namen einführen will.
Marko schrieb:> Mir würde es also reichen wenn die erste Nachkommastelle in etwa stimmt.Marko schrieb:> Ich will eine Spannung bis 30V messen.
also ist dein größter Wert 300dV!
(oder 3000cV CentiVolt)
dazu reicht doch ein uint16_t
also multipliziere alles in uint von mir aus statt 16 auch 32 und achte
darauf das du weder uint16_t/32 weder überschreitest noch durch
ungeschickte Division NULL bekommst.
Rechne deine Konstanten auch passend für dV (deziVolt) und setze im
Ergebnis einfach an der vorletzten (oder vorvorletzten) Stelle ein Komma
ein für die Anzeige (Stringoperation)
Für deinen Fall ist die Rechnung ganz einfach, um aus
30,0 V etwa 3000 als Ergebnis zu bekommen!
ADC-Wert * 4
(2 * shift left)
Diesen Wert mit 58.794 multiplizieren
(16 * 16 Bit unsigned Multiplikation)
Vom Ergebnis (32 Bit = 4 Byte) nur die oberen 2 Byte nehmen,
also die unteren 2 Byte verwerfen.
30,0 V ergeben dann etwa 3000 als Ergebnis.
Wenn es nicht genau stimmt, liegt es am Spannungsteiler, oder
der Ref-Spannung - dazu muss man den Multiplikator 58.794
in einer Cal-Routine beim ersten Einschalten einmalig anpassen.
(Dafür ist das EEPROM gut!)
Hier die Erklärung:
Für die möglichst genaue Aufbereitung von ADC-Werten mit
reiner Integer-Rechnung empfiehlt es sich:
1) Das Pferd von hinten aufzuzäumen!
Was soll herauskommen?
Hier ist es die Maximalspannung von 30,00 V
Da wäre es doch toll, wenn man im letzten Multiplikations-
Ergebnis 3.000 als 16-Bit-Wert zu stehen hätte.
2) Alle Rechenschritte vorher sollten möglichst kein Bit
des ADC-Werts durch Ganzzahl-Division, oder Rechts-Schieben
verloren gehen lassen.
3) Man sollte keine 16 * 16 Bit Multiplikation scheuen, die
kostet wenig Code und (bei 8 MHz) nur < 30 µs.
Dein Fall:
0...30 V über Spannungsteiler 82K zu 13K
ergibt Uin * 0,1368421
30,00 V * 0,1368421 = 4,105263 V
ADC: 1024 * 4,105263 V / 5,029 V = 836
16,55 V * 0,1368421 = 2,264736 V
ADC: 1024 * 2,264736 V / 5,029 V = 461
8,05 V * 0,1368421 = 1,101578 V
ADC: 1024 * 1,101578 V / 5,029 V = 224
Wie bekommt man 3000 aus 836?
Durch Multiplikation mit 3,5885167
Doof! Keine Ganzzahl!
Egal:
ADC-Wert verdoppeln (shift left) und Multiplikator
halbieren, bis der Multiplikator gerade < 1 wird.
836 * 3,5885167 =
1.672 * 1,7942584 =
3.344 * 0,8971292
Jetzt kommt der Trick, um den Multiplikator möglichst genau
zu machen! Wir blasen ihn mächtig 2^16 auf:
0,8971292 * 65.536 = 58.794 (16-Bit-Ganzzahl)
3.344 * 58.794 = 196.607.136 (16 * 16 Bit)
196.607.136 / 65536 = 2.999
Hier ist die Luft wieder raus! - Und der Rechenfehler
viel kleiner, als die ADC-Auflösung!
Yalu X. schrieb:> Mögliche Erklärungen:
Kann nicht auch "gain error / non-linearity" (AVR 120) mit ursächlich
sein? Keine Ahnung, wie groß der/die bei einem ATTiny sein kann ...
Dieter F. schrieb:> Kann nicht auch "gain error / non-linearity"
klar, oder ein Offset weswegen ich immer die Geradengleichung wähle.
Sollte es ein Linearitätsfehler sein könnte man kleinere Abschnitte der
Geradengleichung wählen bis hin zur Tabelle, jedem ADC Wert eine
Spannung zuordnen, alles nach belieben.
Oldie schrieb:> 196.607.136 / 65536 = 2.999
Und wenn man jetzt noch den alten Trick für cleveres Runden anwendet,
die Hälfte des Divisors vor der Division zum Dividenden zu addieren,
ergibt sich auch 3.000 als Ergebnis.
@ Felix Pflaum (fixxl)
Einfacher:
Das obere der "wegzuwerfenden" Bytes auf NEGATIV testen -
wenn ja, Ergebnis um 1 erhöhen.
Ist nicht verkehrt und es passt auch in diesem Beispiel - aber
hier sollte man nicht zu viel Wert auf die letzte Stelle legen!
Die Auflösung ist doch nur 30 V / 836 = 0,036 V.
Joachim B. schrieb:> Oldie schrieb:>> 196.607.136 / 65536 = 2.999>> und warum dividiere ich nicht gleich durch 2^16-1 oder 0xFFFF spart eine> uint32_t Variable oder cast
weil bei einer DIvision durch 2^16 überhaupt nicht gerechnet werden
muss. Das ist einfach nur Bitschieberei, bzw. in diesem Fall: das
Low-Word wird verworfen, das High-Word ist das Ergebnis.
Karl H. schrieb:> weil bei einer DIvision durch 2^16 überhaupt nicht gerechnet werden> muss.
grrr..... du hast ja so Recht, aber das sah ich nicht weil:
196.607.136 / 65536 = 2.999
dann würde ich eher schreiben:
196.607.136 >>=16 = 2.999
OMG
Joachim B. schrieb:> Karl H. schrieb:>> weil bei einer DIvision durch 2^16 überhaupt nicht gerechnet werden>> muss.>> grrr..... du hast ja so Recht, aber das sah ich nicht weil:>> 196.607.136 / 65536 = 2.999>> dann würde ich eher schreiben:>> 196.607.136 >>=16 = 2.999
und genau das würde ich nicht.
Wenn sich die Sache mit Schiebeoperationen durchziehen lässt, dann macht
das der Compiler schon von alleine. Konzeptionell will ich hier
dividieren. Und genau so schreibe ich das daher auch hin: als Division.
Überlass solche Low-Level Optimierungen dem Compiler. Die machen das
seit 40 Jahren zuverlässig, wenn es möglich ist. Denn: Der Compiler
übersieht dabei nichts.
Zb. das du vergessen hast, den Datentyp unsigned zu machen. Denn: bei
negativen Zahlen kommt bei der Schieberei ein falsches Ergebnis raus.
Der Compiler weiss, dass er in diesem Fall die Division eben nicht durch
eine Schiebeoperation ersetzen kann und tut das daher auch nicht.
Karl H. schrieb:> Überlass solche Low-Level Optimierungen dem Compiler. Die machen das> seit 40 Jahren zuverlässig, wenn es möglich ist. Denn: Der Compiler> übersieht dabei nichts.
hmmm, muss ich glauben (obwohl mir glauben sehr schwer fällt,
Kirchenaustritt mit 12 und 16 Jahren) weil ich mir seit Z80 und 6502
(lange her) keinen Asm Code mehr angesehen habe.
Ich war doch immer so optimistisch das >>= definitiv schneller ist als /
ob ich spassenshalber mal Ports setzte in beiden Fällen und die Zeit
messe ob der gcc deiner Meinung ist?
Joachim B. schrieb:> Karl H. schrieb:>> Überlass solche Low-Level Optimierungen dem Compiler. Die machen das>> seit 40 Jahren zuverlässig, wenn es möglich ist. Denn: Der Compiler>> übersieht dabei nichts.>> hmmm, muss ich glauben (obwohl mir glauben sehr schwer fällt,> Kirchenaustritt mit 12 und 16 Jahren)
Kann ich verstehen.
Aber: die Sache ist für einen Compilerbauer so dermassen trivial zu
implementieren, dass es schon fast an fahrlässigen Compilerbau grenzt,
diese simplen Optimierungen nicht zu machen.
> Ich war doch immer so optimistisch das >>= definitiv schneller ist als /
Ist es auch (auf einem AVR)
Aber: Das weiss auch der Compilerbauer und damit auch der Compiler.
Diese ganzen "Low-Level-Optimiertricks" sind alle seit der Steinzeit
bekannt. Bei den meisten ist eine Übernahme in den Compiler absolut kein
Problem (so wie zb hier). Dies Optimierung ist eine einfache
Substituierung im Expression Tree:
wenn die Operation / ist UND der Datentyp unsigned ist UND es sich beim
Divisor um eine 2-er Potenz handelt DANN ersetzt die Division durch ein
Rechts-Schieben mit dem Exponenten der 2-er Potenz
Dasselbe sinngemäss mit Multiplikation und Links-Schieben.
> ob ich spassenshalber mal Ports setzte in beiden Fällen und die Zeit> messe ob der gcc deiner Meinung ist?
Assembler Listing ansehen reicht.
Karl H. schrieb:> Der Compiler weiss, dass er in diesem Fall die Division eben nicht durch> eine Schiebeoperation ersetzen kann und tut das daher auch nicht.
Oder er tut es, korrigiert aber ggf.
Karl H. schrieb:>> Ich war doch immer so optimistisch das >>= definitiv schneller ist als />> Ist es auch (auf einem AVR)> Aber: Das weiss auch der Compilerbauer und damit auch der Compiler.> Diese ganzen "Low-Level-Optimiertricks" sind alle seit der Steinzeit> bekannt. Bei den meisten ist eine Übernahme in den Compiler absolut kein> Problem (so wie zb hier). Dies Optimierung ist eine einfache> Substituierung im Expression Tree:> wenn die Operation / ist UND der Datentyp unsigned ist UND es sich beim> Divisor um eine 2-er Potenz handelt DANN ersetzt die Division durch ein> Rechts-Schieben mit dem Exponenten der 2-er Potenz>> Dasselbe sinngemäss mit Multiplikation und Links-Schieben.
Compiler haben noch ganz andere 'Tricks' drauf.
Zb. hier
1
volatileuint8_ti=0;
2
3
intmain()
4
{
5
PORTB=i*10;
6
}
sehen wir uns mal an, wie der Compiler die Multiplikation mit 10
realisiert hat.
1
PORTB = i * 10;
2
7c: 80 91 60 00 lds r24, 0x0060
3
80: 88 0f add r24, r24
4
82: 98 2f mov r25, r24
5
84: 99 0f add r25, r25
6
86: 99 0f add r25, r25
7
88: 89 0f add r24, r25
8
8a: 88 bb out 0x18, r24 ; 24
Huch!
Keine Multiplikation weit und breit.
Der Compiler weiss, dass er eine Multiplikation mit 10
1
x * 10
auch durch
1
( 8 * x ) + ( 2 * x )
ersetzen kann (wobei er eine Multiplikation 2*a durch a+a ersetzt). Auch
benutzt er das bereits berechnete 2*x um daraus durch 2 malige
Verdopplung 8*x zu errechnen
1
y = x + x // *2
2
z = y + y // *4
3
t = z + z // *8
4
result = t + y // *8 + *2 -> *10
Der gcc hat für viele kleine Zahlen gute Strategien mit, wie er die
Berechnung durchführen kann, ohne durch eine Multiplikation gehen zu
müssen.
Mal ganz doof gefragt:
Was hat der TO von Compiler-Optimierungen und hin- und
her-"shiftereien", wenn er nicht auf das gewünschte Ergebnis kommt?
Mich würde mal eine Messreihe von mind. 5 Werten (gemessen vs. ADC-Wert)
interessieren. Ggf. kann der TO das ja mal in die Diskussion einstreuen.
@Marko(Gast): Ist das möglich?
c-hater wird da natürlich wieder meckern, weil 3 der lds sowie die
beiden eor unnötig sind. Das der gcc mit uint32_t einiges manchmal auf
der Strecke liegen lässt, ist bekannt.
Bleibt man bei den meistbenutzten Datentypen uint8_t bzw. uint16_t, dann
sieht es schon besser aus.
1
volatile uint16_t i = 0;
2
3
int main()
4
{
5
PORTB = i / 256;
6
7c: 80 91 60 00 lds r24, 0x0060
7
80: 90 91 61 00 lds r25, 0x0061
8
84: 98 bb out 0x18, r25 ; 24
9
}
Dass hier beide lds übrig bleiben ist auf das 'volatile' zurückzuführen.
Ohne volatile fällt dann auch noch einer der lds weg.
1
uint16_t i = 0;
2
3
int main()
4
{
5
PORTB = i / 256;
6
7c: 80 91 61 00 lds r24, 0x0061
7
80: 88 bb out 0x18, r24 ; 24
besser kriegen das auch unsere Assembler-über-alles Verfechter nicht
hin.
Karl H. schrieb:> Bleibt man bei den meistbenutzten Datentypen uint8_t bzw. uint16_t, dann> sieht es schon besser aus.
Ja, wenn man statt mit 32 Bit mit 16 oder 8 Bit (auf einem 8 Bit
Prozessor) rechnet wird es übersichtlicher.
Ich bin kein Assembler-Verfechter - aber warum führst Du das auf?
Und nochmal: Was hat der TO davon?
Dieter F. schrieb:> Ich bin kein Assembler-Verfechter - aber warum führst Du das auf?
weil Jürgen wieder mal in die 'Ich optimiere die Division selber in
Schieben um, weil dem Compiler kann man nicht trauen' Ecke abgedriftet
ist.
> Und nochmal: Was hat der TO davon?
Nichts.
Ich habs weiter oben schon mal geschrieben. Für diesen konkreten Fall
würde ich da gar nicht lange umtun und das Ding so rechnen wie es
logisch erscheint: mit float.
Ist für den Zweck allemal schnell genug. Schlimmstenfalls wird eben aus
dem _delay_ms(2000) ein _delay_ms(1990) um die Zeit für die Berechnung
zu kompensieren.
Dieter F. schrieb:> Yalu X. schrieb:>> Mögliche Erklärungen:>> Kann nicht auch "gain error / non-linearity" (AVR 120) mit ursächlich> sein? Keine Ahnung, wie groß der/die bei einem ATTiny sein kann ...
Ja, das spielt auch alles eine gewisse Rolle, ändert aber nichts an der
Tatsache, dass bereits die mit dem Multimeter gemessene Eingangsspannung
des ADC einen bislang nicht geklärten Fehler aufweist. Dieser Fehler ist
völlig unabhängig davon, wie der ADC die Spannung in einen Digitalwert
umsetzt.
Die Nichtlinearität des ADC und die Rundungsfehler der Auswertung
addieren sich zum beschriebenen Fehler natürlich noch hinzu, wobei die
Nichtlinearität von allen diskutierten Fehlerquellen im Moment noch die
kleinste darstellt. Und wie man die in der softwaremäßigen Auswertung
die Rundungsfehler verringert, wurde ja schon ausführlich gezeigt.
Dieter F. schrieb:> Karl H. schrieb:>> Jürgen>> Wer ist das - kommt im Thread nicht vor ...
Ach entschuldigung. Mein Gedächtnis ist auch nicht mehr das.
Joachim B., nicht Jürgen
Yalu X. schrieb:> ändert aber nichts an der> Tatsache, dass bereits die mit dem Multimeter gemessene Eingangsspannung> des ADC einen bislang nicht geklärten Fehler aufweist.
Ja, kann das nicht
Dieter F. schrieb:> Marko schrieb:>> Der Spannungsteiler ist 82K zu>> 13K>> Nutzt Du Präzisions-Messwiderstände?>> Normalerweise haben die eine gewisse Toleranz (am letzten Ring erkennbar> :-) )- und die Wahrheit liegt um max. +/- x % daneben - bei beiden> Widerständen.
schlicht darauf beruhen?
Karl H. schrieb:> weil Jürgen wieder mal in die 'Ich optimiere die Division selber in> Schieben um, weil dem Compiler kann man nicht trauen' Ecke abgedriftet> ist.Karl H. schrieb:> Joachim B.
ist halt historisch gewachsen, in ASM hätte ich das mit rightshift
gemacht einfach weil es für mich logisch ist und natürlich vielfache von
2er Potenzen genommen.
Als ich C lernte habe ich das erst mal übernommen weil es zu den
Grundlagen gehört und bin dabei geblieben weil ich nix verwerfliches
dabei fand.
Ehrlich ob das mittlerweile die Compiler selber können ist für mich eine
andere Baustelle die ich nie untersuchen wollte oder musste, ich bin
halt von Haus aus kein Progger, das mache ich nur im Rahmen "meiner"
Projekte.
Dieter F. schrieb:> Ja, kann das nicht>> Dieter F. schrieb:>> Marko schrieb:>>> Der Spannungsteiler ist 82K zu>>> 13K>>>> Nutzt Du Präzisions-Messwiderstände?>>>> Normalerweise haben die eine gewisse Toleranz (am letzten Ring erkennbar>> :-) )- und die Wahrheit liegt um max. +/- x % daneben - bei beiden>> Widerständen.>> schlicht darauf beruhen?
Nein. Selbst wenn die beiden Widerstandswerte völlig daneben lägen,
müsste trotzdem ihr Verhältnis zueinander unabhängig von der angelegten
Spannung sein. Das ist es aber laut den Messugen des TE nicht, denn bei
der ersten Messung ist das Verhältnis von Eingangs- zu Ausgangsspannung
des Spannungsteilers 7,35, bei der zweiten Messung aber 7,58. Ich störe
mich nicht daran, dass dieses Verhältnis nicht dem Ideal von (82kΩ +
13kΩ) / 13kΩ = 7,31 entspricht, sondern nur der recht große Unterschied
zwischen den beiden Messungen.
Ich vermute, dass die erste Messung (8,05V vor und 1,095V nach dem
Spannungsteiler) richtig ist und dass bei der zweiten Messung die 2,184V
stimmen. Die bei der zweiten Messung angelegte Spannung ist dann aber
nicht 16,55V, sondern 2,184V · 7,35 = 16,05V.
Der TE sollte sich also nicht wundern, wenn selbst mit der verbesserten
Umrechnung der Digital- in Analogwerte immer noch nicht die erwarteten
16550mV im Display angezeigt werden.
Liebe Mit-Helfer für das Problem von Marko (Gast),
mit euren Offset Gain Linearity other Errors
habt ihr euch viel Mühe gegeben, aber deren Einfluss
weit überschätzt!
Ohne AN121 zu bemühen, kann man im Datenblatt Tiny24..84
lesen:
> Absolute accuracy (Including INL, DNL, and Quantization,> Gain and Offset Errors) 2 LSB
Das gilt bei sauberem Aufbau (mit CerKo 100 nF), Ri < 10 kOhm
und f-ADC < 200 kHz. Ist doch toll, für kleiner 2 EU dazu auch
noch einen µC mitgeliefert zu bekommen! ;-)
Der Teiler 82 k / 13 k hat 11 kOhm - also fast 10 kOhm.
Das eine kOhm macht keinen Fehler von 14 LSB!
Meine Erfahrung mit den ADCs im Mega8 und diversen Tiny-µCs
kann die typischen 2 LSB (auch mal 3 LSB) Wandlerfehler
bei Einhaltung der Design-Regeln nur bestätigen.
Oldie schrieb:
> 16,55 V * 0,1368421 = 2,264736 V> ADC: 1024 * 2,264736 V / 5,029 V = 461>> 8,05 V * 0,1368421 = 1,101578 V> ADC: 1024 * 1,101578 V / 5,029 V = 224
Oldie ergänzt:
16,05 V * 0,1368421 = 2,196316 V
ADC: 1024 * 2,196316 V / 5,029 V = 447
Marko (Gast) schrieb:
> Lege ich eine Spannung von 16,55V an,> so ergibt das einen ADC Wert von 447.
und
> Lege ich eine Spannung von 8,05V an,> so ergibt das einen ADC Wert von 223.
KOMISCH:
Bei 8,05 V ist Markos ADC-Wert 223 (Theorie: 224) - Passt!
Bei 16,55 V ist Markos ADC-Wert 447 (Theorie: 461) - Fehler?
Bei 16,05 V ist Markos ADC-Wert ??? (Theorie: 447) - Passt!
Wie hieß es früher?
Mit Brille wär das nicht passiert!
(Oder Markos DVM gehört in den Elektro-Schrott)
Yalu X. schrieb:> Ich vermute, dass die erste Messung (8,05V vor und 1,095V nach dem> Spannungsteiler) richtig ist und dass bei der zweiten Messung die 2,184V> stimmen. Die bei der zweiten Messung angelegte Spannung ist dann aber> nicht 16,55V, sondern 2,184V · 7,35 = 16,05V.
Das könnte ca. hinkommen.
Denn er schreibt
> Lege ich eine Spannung von 8,05V an, so ergibt das einen ADC Wert von 223.> Lege ich eine Spannung von 16,55V an, so ergibt das einen ADC Wert von 447.
447 ist das doppelte von 223 (ok, die +1 seien geschenkt, das kann auch
ein Abtastfehler sein).
Aber 16.55 ist nicht das Doppelte von 8.05
Von einem können wir aber ausgehen: ein Spannungsteiler hat lineare
Proportionen. Bei 0V am Eingang kommen am Abgriff auch 0V raus. Kommt
bei einer Spannung x am Eingang am Ausgang die Spannung y raus, dann
ergibt sich für eine Eingangsspannung x/2 am Ausgang auch y/2.
D.h. bereits hier, in den ADC Werten zeigt sich eine Nichtlinearität von
den Spannungen zu den ADC Werten. Die Umrechnung in eine Spannung ist da
(noch) aussen vor.
Die andere Frage lautet: Womit wurde eigentlich die Vergleichsmessung
gemacht? Ist dieses Messgerät zuverlässig?
Joachim B. schrieb:> hmmm, muss ich glauben (obwohl mir glauben sehr schwer fällt,> Kirchenaustritt mit 12 und 16 Jahren)
Mit 12 und 16 Jahren? Muß man Kirchenaustritte auch entprellen? ;)
Martin L. schrieb:> Mit 12 und 16 Jahren? Muß man Kirchenaustritte auch entprellen? ;)
leider ja,
Mit 12 war man religionsmündig und musste nicht zum Religionsunterricht.
Mit 16 im 2ten Lehrjahr wurden zum ersten mal Lohnsteuer fällig und ich
war schockiert als man mir vom kargen Azubisalär noch Kirchensteuer
abzog ergo 2ter Austritt oder wie du sagst, Entprellung.
Matthias S. schrieb:> Dein 82k in der Highside wird beim S&H Vorgang schlicht einknicken.
Nö.
82k || 13k = 11k, da macht sich der ADC noch lange nicht ins Hemd.