Forum: Mikrocontroller und Digitale Elektronik [C] Tiny 44 - Rundungsfehler?


von Marko (Gast)


Lesenswert?

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. 
6
7
volt_real = volt_real/100;

Viele Grüße
Marko

von Marko (Gast)


Angehängte Dateien:

Lesenswert?

Hier noch ein Bild vom Aufbau.

von Martin (Gast)


Lesenswert?

vref/1023 wird irgendwas mit 5 geben (integer), aber nicht 4,xx oder 
5,xx, daher der Fehler

von c-hater (Gast)


Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

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. ;-)

: Bearbeitet durch User
von Marko (Gast)


Angehängte Dateien:

Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?


von c-hater (Gast)


Lesenswert?

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...

von (prx) A. K. (prx)


Lesenswert?

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.

von Marko (Gast)


Lesenswert?

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.

von Marko (Gast)


Lesenswert?

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.

von Amateur (Gast)


Lesenswert?

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.

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

1
#include <stdint.h>
2
#include <stdfix.h>
3
4
float compute (uint16_t vref)
5
{
6
    unsigned short accum fak;
7
8
    // Also multiply with number of fractional bits of
9
    // unsigned short accum as `uhkbits' effectively divides
10
    // by 2 ^ USACCUM_FBIT.
11
    fak = 630. / 1023. / 100. * (1 << USACCUM_FBIT);
12
13
    // Bit-bang 16.0 --> 8.8
14
    unsigned short accum f_vref = uhkbits (vref);
15
16
    return f_vref * fak;
17
}

von c-hater (Gast)


Lesenswert?

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...

von Johann L. (gjlayde) Benutzerseite


Lesenswert?

Da ATtiny44 nicht multiplizieren kann wird der Code natürlich nicht so 
kurz wie mit MUL:
1
0000058 <compute>:
2
  58:  63 e9         ldi  r22, 0x93  ; 147
3
  5a:  71 e0         ldi  r23, 0x01  ; 1
4
  5c:  0d d0         rcall  .+26       ; 0x78 <__muluha3>
5
  5e:  03 c0         rjmp  .+6        ; 0x66 <__fractuhasf>
6
7
00000066 <__fractuhasf>:
8
  66:  66 27         eor  r22, r22
9
  68:  78 2f         mov  r23, r24
10
  6a:  89 2f         mov  r24, r25
11
  6c:  99 27         eor  r25, r25
12
  6e:  00 c0         rjmp  .+0        ; 0x70 <__fractusasf>
13
14
00000070 <__fractusasf>:
15
  70:  28 d0         rcall  .+80       ; 0xc2 <__floatunsisf>
16
  72:  91 11         cpse  r25, r1
17
  74:  98 50         subi  r25, 0x08  ; 8
18
  76:  08 95         ret
19
20
00000078 <__muluha3>:
21
  78:  06 d0         rcall  .+12       ; 0x86 <__umulhisi3>
22
  7a:  00 c0         rjmp  .+0        ; 0x7c <__muluha3_round>
23
24
0000007c <__muluha3_round>:
25
  7c:  98 2f         mov  r25, r24
26
  7e:  87 2f         mov  r24, r23
27
  80:  67 fd         sbrc  r22, 7
28
  82:  01 96         adiw  r24, 0x01  ; 1
29
  84:  08 95         ret
30
31
00000086 <__umulhisi3>:
32
  86:  9c 01         movw  r18, r24
33
  88:  44 27         eor  r20, r20
34
  8a:  55 27         eor  r21, r21
35
  8c:  ca 01         movw  r24, r20
36
  8e:  00 c0         rjmp  .+0        ; 0x90 <__mulsi3>
37
38
00000090 <__mulsi3>:
39
  90:  ee 27         eor  r30, r30
40
  92:  ff 27         eor  r31, r31
41
42
00000094 <__mulsi3_helper>:
43
  94:  aa 27         eor  r26, r26
44
  96:  bb 27         eor  r27, r27
45
  98:  08 c0         rjmp  .+16       ; 0xaa <__mulsi3_helper+0x16>
46
  9a:  a2 0f         add  r26, r18
47
  9c:  b3 1f         adc  r27, r19
48
  9e:  e4 1f         adc  r30, r20
49
  a0:  f5 1f         adc  r31, r21
50
  a2:  22 0f         add  r18, r18
51
  a4:  33 1f         adc  r19, r19
52
  a6:  44 1f         adc  r20, r20
53
  a8:  55 1f         adc  r21, r21
54
  aa:  96 95         lsr  r25
55
  ac:  87 95         ror  r24
56
  ae:  77 95         ror  r23
57
  b0:  67 95         ror  r22
58
  b2:  98 f3         brcs  .-26       ; 0x9a <__mulsi3_helper+0x6>
59
  b4:  70 40         sbci  r23, 0x00  ; 0
60
  b6:  a9 f7         brne  .-22       ; 0xa2 <__mulsi3_helper+0xe>
61
  b8:  00 97         sbiw  r24, 0x00  ; 0
62
  ba:  99 f7         brne  .-26       ; 0xa2 <__mulsi3_helper+0xe>
63
  bc:  bd 01         movw  r22, r26
64
  be:  cf 01         movw  r24, r30
65
  c0:  08 95         ret
66
67
000000c2 <__floatunsisf>:
68
  c2:  e8 94         clt
69
  c4:  09 c0         rjmp  .+18       ; 0xd8 <__floatsisf+0x12>
70
71
000000c6 <__floatsisf>:
72
  c6:  97 fb         bst  r25, 7
73
  c8:  3e f4         brtc  .+14       ; 0xd8 <__floatsisf+0x12>
74
  ca:  90 95         com  r25
75
  cc:  80 95         com  r24
76
  ce:  70 95         com  r23
77
  d0:  61 95         neg  r22
78
  d2:  7f 4f         sbci  r23, 0xFF  ; 255
79
  d4:  8f 4f         sbci  r24, 0xFF  ; 255
80
  d6:  9f 4f         sbci  r25, 0xFF  ; 255
81
  d8:  99 23         and  r25, r25
82
  da:  a9 f0         breq  .+42       ; 0x106 <__floatsisf+0x40>
83
  dc:  f9 2f         mov  r31, r25
84
  de:  96 e9         ldi  r25, 0x96  ; 150
85
  e0:  bb 27         eor  r27, r27
86
  e2:  93 95         inc  r25
87
  e4:  f6 95         lsr  r31
88
  e6:  87 95         ror  r24
89
  e8:  77 95         ror  r23
90
  ea:  67 95         ror  r22
91
  ec:  b7 95         ror  r27
92
  ee:  f1 11         cpse  r31, r1
93
  f0:  f8 cf         rjmp  .-16       ; 0xe2 <__floatsisf+0x1c>
94
  f2:  fa f4         brpl  .+62       ; 0x132 <__floatsisf+0x6c>
95
  f4:  bb 0f         add  r27, r27
96
  f6:  11 f4         brne  .+4        ; 0xfc <__floatsisf+0x36>
97
  f8:  60 ff         sbrs  r22, 0
98
  fa:  1b c0         rjmp  .+54       ; 0x132 <__floatsisf+0x6c>
99
  fc:  6f 5f         subi  r22, 0xFF  ; 255
100
  fe:  7f 4f         sbci  r23, 0xFF  ; 255
101
 100:  8f 4f         sbci  r24, 0xFF  ; 255
102
 102:  9f 4f         sbci  r25, 0xFF  ; 255
103
 104:  16 c0         rjmp  .+44       ; 0x132 <__floatsisf+0x6c>
104
 106:  88 23         and  r24, r24
105
 108:  11 f0         breq  .+4        ; 0x10e <__floatsisf+0x48>
106
 10a:  96 e9         ldi  r25, 0x96  ; 150
107
 10c:  11 c0         rjmp  .+34       ; 0x130 <__floatsisf+0x6a>
108
 10e:  77 23         and  r23, r23
109
 110:  21 f0         breq  .+8        ; 0x11a <__floatsisf+0x54>
110
 112:  9e e8         ldi  r25, 0x8E  ; 142
111
 114:  87 2f         mov  r24, r23
112
 116:  76 2f         mov  r23, r22
113
 118:  05 c0         rjmp  .+10       ; 0x124 <__floatsisf+0x5e>
114
 11a:  66 23         and  r22, r22
115
 11c:  71 f0         breq  .+28       ; 0x13a <__floatsisf+0x74>
116
 11e:  96 e8         ldi  r25, 0x86  ; 134
117
 120:  86 2f         mov  r24, r22
118
 122:  70 e0         ldi  r23, 0x00  ; 0
119
 124:  60 e0         ldi  r22, 0x00  ; 0
120
 126:  2a f0         brmi  .+10       ; 0x132 <__floatsisf+0x6c>
121
 128:  9a 95         dec  r25
122
 12a:  66 0f         add  r22, r22
123
 12c:  77 1f         adc  r23, r23
124
 12e:  88 1f         adc  r24, r24
125
 130:  da f7         brpl  .-10       ; 0x128 <__floatsisf+0x62>
126
 132:  88 0f         add  r24, r24
127
 134:  96 95         lsr  r25
128
 136:  87 95         ror  r24
129
 138:  97 f9         bld  r25, 7
130
 13a:  08 95         ret

von Marko (Gast)


Angehängte Dateien:

Lesenswert?

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.

von (prx) A. K. (prx)


Lesenswert?

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.

von Walter S. (avatar)


Lesenswert?

Marko schrieb:
>
> Code anbei.
wenn du die ganzen Magic-numbers durch was lesbares ersetzt schasue ich 
mir den Code noch Mal an

von Marko (Gast)


Lesenswert?

Lesbar im Sinne von "es fehlen Kommentare" oder meinst du die 
Variablennamen?

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

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.

von Marko (Gast)


Lesenswert?

Danke für die Antwort! Habe gerade einmal 680pF dazugeschaltet, leider 
keine Besserung der Situation. Aber das mit der 10K Regel werde ich mir 
merken!

von Uwe (de0508)


Lesenswert?

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.

von Dieter F. (Gast)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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!
1
amp_real = ( ( amp_adc - amp_offset ) * Vref * 1515 ) / ( 100 * 1024 );

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.

: Bearbeitet durch User
von Karl H. (kbuchegg)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

1
char s[sizeof("4294967295")];      //Buffer für Ausgabe über LCD
2
char t[sizeof("4294967295")];        //Buffer für Ausgabe über LCD
3
char u[sizeof("4294967295")];        //Buffer für Ausgabe über LCD
4
char v[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
void lcd_putl( long value )
2
{
3
  char buffer[9];
4
5
  ltoa( value, buffer, 10 );
6
  lcd_puts( buffer );
7
}
8
9
void lcd_putul( unsigned long value )
10
{
11
  char buffer[9];
12
13
  ultoa( value, buffer, 10 );
14
  lcd_puts( buffer );
15
}

Mit diesen Funktionen vereinfachtz sich dann dein Code zu
1
int main()
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
1
void output_value( uint8_t col, uint8_t line, const char* label, long value )
2
{
3
  lcd_xy( col, line );
4
  lcd_puts( label );
5
  lcd_putl( value );
6
}
7
8
int main()
9
{
10
  const int amp_offset = 514; 
11
  const long Vref = 5029;
12
13
  int amp_adc = 0;
14
  long amp_real = 0;
15
16
  long volt_adc = 0;
17
  long volt_real = 0;
18
19
  long power = 0;
20
21
              // wenn du da einen Kommentar dazuschreiben musst,
22
              // dann bedeutet das hauptsaechlich eines:
23
              // Dein von dir gewaehlter Funktionsname ist Scheisse
24
              //   |
25
              //   v
26
  initial();  // Initialisierung Ports etc.
27
  ADC_Init();
28
  lcd_init();
29
30
  while(1)
31
  {
32
    amp_adc = ADC_Read_Avg(0,4);
33
    amp_real = ( ( amp_adc - amp_offset ) * Vref * 1515 ) / ( 100 * 1024 );
34
35
    volt_adc = ADC_Read_Avg(1,4);
36
    volt_real = ( volt_adc * Vref * 760 ) / ( 100 * 1024 );
37
38
    power = ( volt_real * amp_real ) / 1000;
39
40
41
    if (volt_real > 10000)
42
      PORTB = 0b00000010;
43
    else if (volt_real < 9000)
44
      PORTB = 0b00000000;
45
46
    lcd_clr();    // Display löschen
47
    output_value( 0, 0, "mA:", amp_real );
48
    output_value( 8, 0, "mV:", volt_real );
49
    output_value( 0, 1, "mW:", power );
50
    output_value( 8, 1, "ADC:", volt_adc );
51
52
    _delay_ms(2000);
53
  }
54
}

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.

: Bearbeitet durch User
von Johann L. (gjlayde) Benutzerseite


Lesenswert?

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?"

von Yalu X. (yalu) (Moderator)


Lesenswert?

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 ;-)

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Walter S. (avatar)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von Joachim B. (jar)


Lesenswert?

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)

: Bearbeitet durch User
von Oldie (Gast)


Lesenswert?

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!

von Wire (Gast)


Lesenswert?

Oldie schrieb:
> 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

elegant! :)

von Dieter F. (Gast)


Lesenswert?

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 ...

von Bernd N (Gast)


Lesenswert?


von Joachim B. (jar)


Lesenswert?

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.

von Paul B. (paul_baumann)


Lesenswert?

@Oldie
Das ist richtig schön und übersichtlich erklärt. Schönen Dank dafür, 
auch wenn ich die Frage nicht stellte.

mfG Paul

von Felix P. (fixxl)


Lesenswert?

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.

von Oldie (Gast)


Lesenswert?

@ 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.

von Joachim B. (jar)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Joachim B. (jar)


Lesenswert?

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

von Karl H. (kbuchegg)


Lesenswert?

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.

von Joachim B. (jar)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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.

: Bearbeitet durch User
von (prx) A. K. (prx)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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
volatile uint8_t i = 0;
2
3
int main()
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.

: Bearbeitet durch User
von Dieter F. (Gast)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

Aber um auf die Division zurückzukehren
1
volatile uint32_t i = 0;
2
3
4
5
  PORTB = i  / 65536;
6
  7c:  80 91 60 00   lds  r24, 0x0060
7
  80:  90 91 61 00   lds  r25, 0x0061
8
  84:  a0 91 62 00   lds  r26, 0x0062
9
  88:  b0 91 63 00   lds  r27, 0x0063
10
  8c:  cd 01         movw  r24, r26
11
  8e:  aa 27         eor  r26, r26
12
  90:  bb 27         eor  r27, r27
13
  92:  88 bb         out  0x18, r24  ; 24

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.

: Bearbeitet durch User
von Dieter F. (Gast)


Lesenswert?

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?

von Karl H. (kbuchegg)


Lesenswert?

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.

von Dieter F. (Gast)


Lesenswert?

Karl H. schrieb:
> Jürgen

Wer ist das - kommt im Thread nicht vor  ...

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Karl H. (kbuchegg)


Lesenswert?

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

von Dieter F. (Gast)


Lesenswert?

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?

von Joachim B. (jar)


Lesenswert?

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.

von Yalu X. (yalu) (Moderator)


Lesenswert?

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.

von Oldie (Gast)


Lesenswert?

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)

von Karl H. (kbuchegg)


Lesenswert?

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?

von Oldie (Gast)


Lesenswert?

SO EIN MIST!

 Yalu X. (yalu) (Moderator) hats früher gepostet...

Gratuliere!

von Martin L. (martin_l795)


Lesenswert?

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? ;)

von Joachim B. (jar)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

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.

von Peter D. (peda)


Lesenswert?

Mach doch erstmal alles in float, das sind nur einmalig 1kB Flash für 
die Lib.
Und wenns läuft, kannst Du ja immer noch optimieren.

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.