Forum: Mikrocontroller und Digitale Elektronik Erklärung ADC Code


von Matthias M. (Gast)


Lesenswert?

Hi, ich hoffe mir kann hier jemand weiterhelfen, verstehe die Rechnung 
des unten stehenen Codes nicht.

Angenommen mein ADC liefert mir einen Wert von 20.

dann sieht die Rechnung doch so aus:

x=4.8876*20; -> 97,752 (x kann keine Kommazahlen da int, also bleibt 97, 
oder?)

vorkomma = x/1000 -> 97/1000 = 0,97 (... schon wieder eine Kommazahl, 
wird daraus nun eine 0 oder eine 1?)

Ich denke weiter brauch ich nicht rechnen, ich komm einfach nicht drauf 
wie hier mit int. Typen gerechnet wird?!

Gleich vorweg, in der praxis läuft es! :) Ich würde nur gerne verstehen 
wie und warum?! :)

1
#include <avr/io.h>
2
#include <stdio.h>
3
#include <inttypes.h>
4
5
#define F_CPU 1000000 // CPU-Frequenz in Hz
6
#include <util/delay.h>
7
8
#include "messen.h"
9
#include "lcd-routines.h"
10
11
12
int main(void){
13
    int x=0, vorkomma, nachkomma;
14
    char messwertstring[20];
15
16
        DDRB |= _BV(PB0); PORTB &= ~_BV(PB0);  // PB0 ist Status-LED Ausgang
17
   
18
    lcd_init();
19
    lcd_clear();
20
   
21
    set_cursor (12, 1); lcd_string ("Volt");
22
   
23
    while (1)    {
24
   
25
        x=4.8876*MESSWERT(0);
26
       
27
        vorkomma=x/1000;
28
        nachkomma=x-(vorkomma*1000);
29
        nachkomma=nachkomma/10;
30
       
31
        set_cursor (12, 2); lcd_string (" .  ");
32
       
33
       
34
        sprintf(messwertstring, "%d", vorkomma);
35
        set_cursor (12, 2);
36
        lcd_string (messwertstring);
37
       
38
        sprintf(messwertstring, "%d", nachkomma);
39
        set_cursor (14, 2); if (nachkomma<10) lcd_string ("0");
40
        lcd_string (messwertstring);
41
       
42
        _delay_ms(255);
43
    }   
44
 
45
    return(0);
46
}

Danke, Gruß
Matthias

von Peter II (Gast)


Lesenswert?

Matthias M. schrieb:
> Angenommen mein ADC liefert mir einen Wert von 20.
>
> dann sieht die Rechnung doch so aus:
>
> x=4.8876*20; -> 97,752 (x kann keine Kommazahlen da int, also bleibt 97,
> oder?)

woher sollen wir denn wissen was 4.8876 ist?

Wenn du 0.3 Volt haben willst, dann denke einfach ein milliVolt. Dann 
sind es 300mV. Und schon passt es in eine Int zahl.

von BT (Gast)


Lesenswert?

Hallo,

das passiert hier schon. Hier wird in mV gerechnet:

5V / 1023 = 0,00488...  (Annahme: 5V - Referenzspannung)


Das ist im Code  4,8876

Hoffe es ist jetzt etwas klarer geworden.

Gruß
BT

von Matthias L. (Gast)


Lesenswert?

>5V / 1023 = 0,00488...  (Annahme: 5V - Referenzspannung)

1024! Nicht 1023.

von Martin (Gast)


Lesenswert?

Ich gehe mal davon aus, dass MESSWERT() einen int zurückliefert.

Dein Compiler macht aus dem 4.8876 wahrscheinlich einfach eine 4.
Danach landet in vorkomma je nach messwert ein 250stel des Messwertes 
(Nachkommastellen werden abgeschnitten).

Messwert  20 --> x =   80 --> vorkomma = 0, nachkomma = 8
Messwert 250 --> x = 1000 --> vorkomma = 1, nachkomma = 0
Messwert 345 --> x = 1380 --> vorkomma = 1, nachkomma = 38

von Martin (Gast)


Lesenswert?

Sorry, die 4.88... werden doch nicht zum int gecastet... also wird da 
böserweise sogar eine Float-Multiplikation gemacht.

Also:

Messwert  20 --> x =   97 --> vorkomma = 0, nachkomma = 9
...

von Matthias M. (Gast)


Lesenswert?

Martin schrieb:
> Dein Compiler macht aus dem 4.8876 wahrscheinlich einfach eine 4.

Ok das dacht ich mir eben dass da was nicht stimmt, ich muss auch sagen 
dass die Messergebnisse schon etwas ungenau sind.

sollte ich x dann besser zum Datentyp double machen?

von Eumel (Gast)


Lesenswert?

Matthias M. schrieb:
> sollte ich x dann besser zum Datentyp double machen?

Nein, du sollst einfach in mV rechnen und bei int bleiben. Dann ist 
alles ausreichend genau und trotzdem schön flott und der code bleibt 
klein.

von Matthias M. (Gast)


Lesenswert?

Eumel schrieb:
> Nein, du sollst einfach in mV rechnen und bei int bleiben. Dann ist
> alles ausreichend genau und trotzdem schön flott und der code bleibt
> klein.

Hmm aber sobald ich mit 4,8876 multipliziere verwende ich doch eine 
Kommazahl und kann mit int nicht rechnen. Oder sollte ich mit 48876 
multiplizieren? Dann wäre aber doch der Beispielcode falsch, oder?

von Matthias L. (Gast)


Lesenswert?


von Eumel (Gast)


Lesenswert?

Matthias M. schrieb:
> Hmm aber sobald ich mit 4,8876 multipliziere verwende ich doch eine
> Kommazahl und kann mit int nicht rechnen. Oder sollte ich mit 48876
> multiplizieren? Dann wäre aber doch der Beispielcode falsch, oder?

Dann schieb das Komma halt noch ein paar Stellen nach rechts bis es dir 
genau genug ist:
http://www.mikrocontroller.net/articles/Festkommaarithmetik

von Karl H. (kbuchegg)


Lesenswert?

Matthias M. schrieb:

> Hmm aber sobald ich mit 4,8876 multipliziere verwende ich doch eine
> Kommazahl und kann mit int nicht rechnen. Oder sollte ich mit 48876
> multiplizieren? Dann wäre aber doch der Beispielcode falsch, oder?

Das ist er sowieso, weil die 4.8876 an sich schon falsch sind.

Wo kommt die Zahl den eigentlich her?

Sie entsteht aus 5 / 1024

Warum 5?
Weil deine Referenzspannung 5V ist.
Und warum 1024?
Weil der ADC den Bereich 0 bis Referenzspannung (die 5V) in 1024 Stufen 
(Bereiche) auflöst.

Ein derartiger Bereich ist daher 5/1024 oder eben 0.00488828125 V groß. 
Die Zahl weicht deshalb ab, weil der Originalautor nicht mit 1024, 
sondern mit 1023 gerechnet hat. Was aber eigentlich nicht ganz richtig 
ist (allerdings auch keinen großen Unterschied macht).

ANstatt
1
   x = 0.0048828 * Messwert * 1000.0;
hindert dich nichts und niemand, stattdessen
1
   x = 4.8828 * Messwert;
zu schreiben. (Die 1000 kommen daher, weil du ja das Ergebnis in 
Millivolt haben willst und nicht in Volt)

Es hindert dich allerdings auch nichts und niemand, anstatt der 
0.0048828 den Ausdruck zu schreiben, aus dem der Wert entstanden ist
1
   x = 5 / 1024 * Messwert * 1000;
Allerdings sollte man ihn etwas umformen, damit die DIvision zum Schluss 
gemacht wird und nicht zwischendurch der Nachkommaanteil "verloren geht"
1
  x = 5 * Messwert * 1000 / 1024;
Jetzt kann man noch die 5 und die 1000 zusammenfassen und landet bei
1
  x = 5000 * Messwert / 1024;
und hat damit einen Ausdruck, der komplett ohne Floating Point 
Arithmetik auskommt. Einfach nur indem man ein wenig darüber nachdenkt, 
wo die Zahlen eigentlich herkommen und indem man ein wenig umformt.

Ein (potentielles) Problem gibt es noch, dem man sich widmen muss. Und 
das ist ein potentieller Overflow. Wird im Zahlenraum int gerechnet (16 
Bit), dann darf kein Ergebnis, auch kein Zwischenergebnis größer als 
32767 sein. Rechnet man unsigned (also ohne Vorzeichen), dann liegt die 
Grenze bei 65535.
Also sehen wir uns das mal näher an.
Einen Overflow kann es nur bei der Multiplikation geben, denn nur dort 
kommen ja größere Ergebnisse raus.
1
  5000 * Messwert
Wie groß kann Messwert maximal werden?
Messwert kann Werte im Bereich 0 bis 1023 annehmen. Größer geht nicht. 
Die größte Zahl die da also entstehen kann, lautet daher
1
   5000 * 1023
und das ist ausgerechnet 5115000, welches wiederrum zu groß für ein 16 
Bit Ergebnis ist. Man könnte jetzt natürlich den Faktor 1000 
entsprechend kleiner machen und so von Millivolt auf Hundertselvolt oder 
gar Zehntelvolt gehen. Man kann aber auch einfach dafür sorgen, dass 
diese Berechnung nicht in 16 Bit sondern in 32 Bit gemacht wird. Dann 
passt es wieder und es entsteht kein Overflow

Und so landen wir bei
1
   x = 5000L * Messwert / 1024;
Gänzlich ohne Floating Point Rechnerei.

von Karl H. (kbuchegg)


Lesenswert?

Im übrigen:

Wenn du hier
1
        set_cursor (12, 2); lcd_string (" .  ");
2
       
3
       
4
        sprintf(messwertstring, "%d", vorkomma);
5
        set_cursor (12, 2);
6
        lcd_string (messwertstring);
7
       
8
        sprintf(messwertstring, "%d", nachkomma);
9
        set_cursor (14, 2); if (nachkomma<10) lcd_string ("0");
10
        lcd_string (messwertstring);

sowieso schon sprintf benutzt, dann kannst du dir (und dem Rechner) auch 
eine Menge Arbeit sparen, indem du das benutzt, was dir sprintf an 
Formatiermöglichkeiten anbietet. Es ist ein bischen witzlos, das ganze 
aus Einzelteilen am LCD zusammenzusetzen und sich dann auch noch selbst 
um die notwendigen führenden 0-en kümmern zu müssen, wenn sprintf das 
alles auch mit dem richtigen Formatierstring ganz alleine kann.
1
        sprintf( messwertstring, "%d.%02d", vorkomma, nachkomma );
2
        set_cursor (12, 2);
3
        lcd_string (messwertstring);

von Matthias M. (Gast)


Lesenswert?

@Karl Heinz Buchegger: DANKE!!! Schade dass das so nicht auch im 
Tutorial steht, echt super schön erklärt!

Eine Frage bleibt mir allerdings, Variable x (als int.) wird ja bei der 
Rechnerei zwarngsläuftig eine Kommazahl. Das Komma wird dann einfach 
verworfen, richtig?

Sprich bei Messert 450 des ADC ergibt sich für x = 2197,265625. Werden 
die Nachkommastellen dann einfach ignoriert oder passiert da noch 
irgendwas anderes? Wäre es nicht besser das direkt zu verhindern dass 
erst gar keine Nachkommastellen entstehen, z.b. durch eine Rundung oder 
mach ich mir da zu viele Gedanken?

von Karl H. (kbuchegg)


Lesenswert?

Matthias M. schrieb:
> @Karl Heinz Buchegger: DANKE!!! Schade dass das so nicht auch im
> Tutorial steht, echt super schön erklärt!
>
> Eine Frage bleibt mir allerdings, Variable x (als int.) wird ja bei der
> Rechnerei zwarngsläuftig eine Kommazahl.

Nein.
Eine Variable hat einen Datentyp. Und der ändert sich nicht.
In deinem Original
1
        x=4.8876*MESSWERT(0);

hast du erst mal eine Zuweisung.
Und die besteht aus 2 Teilen:
Dem Teil links vom =
und dem Teil rechts vom =

der Teil rechts vom =, der arithmetische Ausdruck, wird ausgewertet und 
kommt mit einem Ergebnis hoch. Dieses Ergebnis besteht aus 2 Teilen: 
EInem Zahlenwert und einem Datentyp.
Der Ausdruck
1
     4.8876*MESSWERT(0)
ergibt irgendeinen Zahlenwert (je nach Messwert), aber er ist auf jeden 
Fall vom Datentyp 'double'. Das alles hat noch nichts mit dem x zu tun.

Das kommt erst jetzt ins Spiel, wenn dann die Zuweisung tatsächlich 
durchgeführt wird. Dann stehen rund um das = die Datentypen
1
   int = double
und bei einer derartigen Zuweisung werden die Nachkommastellen des 
double einfach abgeschnitten und verworfen.

und damit bekommt dann x (der int auf der linken Seite der Zuweisung) 
dieses bereinigte Ergebnis zugewiesen.


> Wäre es nicht besser das direkt zu verhindern dass erst gar
> keine Nachkommastellen entstehen, z.b. durch eine Rundung
> oder mach ich mir da zu viele Gedanken?

Du kannst natürlich runden wenn du willst. Allerdings bezweifle ich, 
dass deine Referenzspannung genau genug ist, bzw. das ADC Ergebnis genau 
genug ist, dass die Millivolt-Stelle überhaupt stimmt. Ob da also im x 
jetzt 2197 oder 2198 steht, ist höchst wahrscheinlich Jacke wie Hose, 
denn deine Spannung war mit einiger Sicherheit sowieso nicht 2.198V

von Matthias M. (Gast)


Lesenswert?

Danke für die super ausführliche Erklärung! Es funktionniert nun 
wunderbar!!

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.