Forum: Mikrocontroller und Digitale Elektronik uint32 * uint32 = neg. Zahl ?


von Rick M. (rick00)


Lesenswert?

Hallo!

Versuche gerade mit meinem ATmega32 per ADC Strom und Spannung zu 
messen.
Dabei ist es zur Berechnung wegen der erforderlichen Genauigkeit 
notwendig, daß ich eine 32bit Multiplikation durchführe.
Seltsamerweise ergibt diese Multiplikatione zweier uint32-Zahlen ein 
negatives Ergebnis. Zuerst dachte ich an einen Überlauf, diesen kann ich 
aber ausschließen. Hat jemand von euch eine Idee, woran das liegen kann?

MC: ATmega32
IDE: AVR-Studio 5

In Main:
1
uint32_t voltage_source = 0;    // Variable für Quellenspannung
2
uint32_t voltage_electrodes = 0;  // Elektroden-Spannung
3
uint32_t current_electrodes = 0;  // Elektroden-Strom
4
5
uint16_t volatile adc_value [5]; // Speicher für ADC-Werte
6
7
// Initialisierung:
8
9
adc_value[2]=100;  // Für Testzwecke ADC-Wert für Quellenspg.
10
adc_value[3]=1000;  // Elektrodenspg
11
adc_value[4]=100;  // Elektrodenstrom
12
13
// Hauptschleife:
14
15
read_voltage_electrodes();

Unterprogramm read_voltage_electrodes:
1
/******************************* Konstanten *******************************************
2
**************************************************************************************/
3
4
5
#define VAREF  2130L    // Referenzspannung für AD-Wandler in mV (2,5V -> 2500)
6
7
#define K    25L    // Teilerfaktor für Spannungsteiler zu Spannungsmessung
8
            // Bsp.: Verh.: 1:25 -> Wert=25
9
            
10
#define R_SHUNT 120L  // Widerstandswert für Shunt (Strommessung-Elektroden) im Ohm
11
12
13
#define K_VOLTAGE    (VAREF * K / 1024) // Ergebnis: 52
14
// Korrekturfaktor für Spg.-Messung, Ergebnis in mV !
15
16
#define K_CURRENT    ( (VAREF * 1000000) / (R_SHUNT * 1024) )
17
// Korrekturfaktor für Strom-Messung, Ergebnis in uA !
18
19
20
void read_voltage_electrodes(void)
21
{
22
  uint16_t adc_val;
23
    
24
  adc_val = adc_value[3] - adc_value[4]; // 1000-100 = 900
25
  // Elektrodenspg. = Spg. gesamt - Spg. am Shunt
26
  
27
  voltage_electrodes = ( adc_val * K_VOLTAGE ); // 900*52
28
// Elektrodenspg. = 
29
//          (ADC-Wert * Referenzspg. AD-Wandler * Faktor-Spannungsteiler) / 2^10bit 
30
                  
31
}

Ich habe mir folgende Variablen am LCD ausgeben lassen:

K_VOLTAGE = 52 (korekt)

adc_val = 900 (korrekt)

voltage_electrodes = -18736 ??? falsch ???

Nach meinem Wissen genügt es bei einer Rechenoperation eine Zahl auf 
32bit zu casten, damit der Compiler alles mit 32bit rechnet (Artikel: 
Festkommaarithmetik)
Da ich alle Konstanten zur Berechnung der Variable K_VOLTAGE mit L (Long 
= 32bit) caste wird der ADC-Wert "adc_val" auch automatisch auf 32bit 
vergrößert.
Die Multiplikation lautet dann uitn32 = uint32 * uint32
-> -> -18736 = 900 * 52
Wie kann dann das Ergebnis negativ werden?

Was mich weiters verwirrt ist, daß in den Artikeln bei den 8bit-MC int 
immer als 16bit angenommen wird und long int mit 32bit.
Müßte bei einer 8bit-Plattform int nicht als 8bit gelten und long int 
als 16bit?

Gruß Rick

von M. K. (sylaina)


Lesenswert?

Die Multiplikation zweier 32bit-Zahlen ergibt eine 64bit-Zahl. Kanns 
sein, dass dein Problem hier her kommt? ;)

von Hubu (Gast)


Lesenswert?

Die LCD-Ausgabefunktion erwartet offenbar eine signed int Variable als 
Übergabeparameter. Dann kommt bei 900*52 genau der Wert raus.

von (prx) A. K. (prx)


Lesenswert?

Vermutlich wird zur Ausgabe das Ergebnis auf 16-Bit "int" 
runterkonvertiert.

von Tux (Gast)


Lesenswert?

Wie kommst du auf den negativen Wert?
Die Variable hat einen unsigned Datentyp, da kann garnichts negatives 
drin stehen. Wenn man aber das Ergebnis von 900*50 als signed int16 
betrachtet dann stimmt deine negative Zahl. Die Rechnung stimmt also.
Ich würde also ehr sagen dass du den Wert falsch ausliest.

von Stefan E. (sternst)


Lesenswert?

Rick M. schrieb:
> Müßte bei einer 8bit-Plattform int nicht als 8bit gelten und long int
> als 16bit?

Nein, denn der C Standard schreib vor, dass ein int mindesten 16 Bit 
groß ein muss.

von Rick M. (rick00)


Lesenswert?

Michael Köhler schrieb:
> Die Multiplikation zweier 32bit-Zahlen ergibt eine 64bit-Zahl. Kanns
> sein, dass dein Problem hier her kommt? ;)

900*52 liegt doch locker im 32bit-Bereich.

Hubu schrieb:
> Die LCD-Ausgabefunktion erwartet offenbar eine signed int Variable als
> Übergabeparameter. Dann kommt bei 900*52 genau der Wert raus.

Die Ausgabe am LCD mache ich wie folgt:
1
char string[10];
2
3
lcd_gotoxy(0,3);              
4
itoa (voltage_electrodes , string , 10);
5
lcd_puts(string);

Für die Ausgabe am Display (4*20 Zeichen mit Standard HD44780 
Controller) benutze ich die Bibliotheken von Perter Fleury.

Gruß Rick

von Hubu (Gast)


Lesenswert?

Dann denk nochmal drüber nach wofür das i bei itoa steht ;-)

richtig, für int

den rest findest du sicher selber raus.

von M. K. (sylaina)


Lesenswert?

War nur meine erste Vermutung dass der Fehler daher kommt und der 
Compiler versucht zu optimieren. Der weiß ja nicht wie groß das Ergebnis 
wird ;)

Durchaus aber auch möglich, dass itoa einen 16 bit Integer nur erwartet 
und du gibst ihm nen 32 bit. Um das raus zu finden lasse itoa einfach 
mal einen 32 bit Integer (z.B. 80.000) umformen und schau ihn dir an ;)

von gerd (Gast)


Lesenswert?

1
char *   itoa (int __val, char *__s, int __radix)

Quelle: 
http://www.nongnu.org/avr-libc/user-manual/group__avr__stdlib.html

- gerd

von gerd (Gast)


Lesenswert?

du brauchst:
1
char* ltoa   (   long int    __val,
2
    char *    __s,
3
    int    __radix 
4
  )

- gerd

von Hubu (Gast)


Lesenswert?

Sag ich doch.

Michael Köhler scheint scih weder die Frage noch die Antworten der 
anderen anzugucken bevor er seinen Qurk in die Runde wirft.

von Hubu (Gast)


Lesenswert?

@gerd auch das ist stringgenommen nicht richtig, denn ltoa ist auch 
signed. Und ultoa ist kein C-Standard.

Ich würde sprintf nehmen, das ist eh sauberer.

von Stefan E. (sternst)


Lesenswert?

utoa

von Hubu (Gast)


Lesenswert?

Stefan Ernst schrieb:
> utoa

FALSCH. Siehe oben.

von M. K. (sylaina)


Lesenswert?

Hubu schrieb:
> Michael Köhler scheint scih weder die Frage noch die Antworten der
> anderen anzugucken bevor er seinen Qurk in die Runde wirft.

itoa erwartet eine int, wie lang aber int ist ist vom System abhängig 
und kann nicht pauschal gesagt werden. Es kann 16 bit sein aber auch 32 
bit.

von Stefan E. (sternst)


Lesenswert?

Hubu schrieb:
> Stefan Ernst schrieb:
>> utoa
>
> FALSCH. Siehe oben.

Und was genau meinst du mit "oben"?

von Rick M. (rick00)


Lesenswert?

lt. avr-libc müsste ultoa() funktionieren.

Leider kapier ich die Notiz für die Puffergröße nicht so ganz...

von M. K. (sylaina)


Lesenswert?

Versuch macht klug ;)

von Stefan E. (sternst)


Lesenswert?

Stefan Ernst schrieb:
> Hubu schrieb:
>> Stefan Ernst schrieb:
>>> utoa
>>
>> FALSCH. Siehe oben.
>
> Und was genau meinst du mit "oben"?

Ach, schon klar, uint32_t.
Habe nicht damit gerechnet, dass er nicht nur signed/unsigned außer Acht 
gelassen hat, sondern auch die Größe.

(Trotzdem war es nicht unbedingt nötig, mich anzuschreien)

von Rick M. (rick00)


Lesenswert?

Die Variable "voltage_electrodes" wird mit der Umwandlungs-Funktion 
ultoa() jetzt korrekt angezeigt.
46800 = 900 * 52
Danke!

Die Displayausgabe dieser Spannung ist jedoch immer noch falsch.
Der Wert 46800 entspricht 46,8V.
Angezeicht wird jedoch 46,32V ?


1
vorkomma = voltage_electrodes / 1000;  // Spannung wird im mV ausgegeben
2
  if(vorkomma < 10)            // wenn Vorkomma kleiner 10 Leerstelle einfügen
3
    lcd_putc(' ');
4
  utoa(vorkomma, string, 10);        // Elektrodenspg. in ASCII umwandeln
5
  lcd_puts(string);            // und ausgeben
6
  
7
  lcd_putc(',');              // Komma ausgeben
8
  
9
  nachkomma = voltage_electrodes % 1000;
10
  utoa(nachkomma, string, 10);
11
  lcd_puts(string);
12
  
13
  lcd_putc('V');              // Volt Symbol ausgeben


Gruß Rick

von J.-u. G. (juwe)


Lesenswert?

Rick M. schrieb:
> Der Wert 46800 entspricht 46,8V.
> Angezeicht wird jedoch 46,32V ?

von welchem Datentyp ist Deine Variable "nachkomma"?

Ich vermute uint8_t, und das wäre zu klein für 800.

Wenn man versucht, 800 in einen uint8_t zu packen, kommt 32 heraus.

von Rick M. (rick00)


Lesenswert?

J.-u. G. schrieb:
> Rick M. schrieb:
>> Der Wert 46800 entspricht 46,8V.
>> Angezeicht wird jedoch 46,32V ?
>
> von welchem Datentyp ist Deine Variable "nachkomma"?
>
> Ich vermute uint8_t, und das wäre zu klein für 800.
>
> Wenn man versucht, 800 in einen uint8_t zu packen, kommt 32 heraus.

Bin gerade selbst draufgekommen, Du hast voll ins Schwarze getroffen 
;-)Jetzt klappts




Vielen Dank für eure schnelle Hilfe!

Gruß Rick

von Karl H. (kbuchegg)


Lesenswert?

Rick M. schrieb:
> J.-u. G. schrieb:
>> Rick M. schrieb:
>>> Der Wert 46800 entspricht 46,8V.
>>> Angezeicht wird jedoch 46,32V ?
>>
>> von welchem Datentyp ist Deine Variable "nachkomma"?
>>
>> Ich vermute uint8_t, und das wäre zu klein für 800.
>>
>> Wenn man versucht, 800 in einen uint8_t zu packen, kommt 32 heraus.
>
> Bin gerade selbst draufgekommen, Du hast voll ins Schwarze getroffen
> ;-)Jetzt klappts
>

Noch nicht wirklich.

Denn wenn deine Anzeige eigentlich 42,008 sein sollte, steht auf deiner 
Anzeige 42,8
Du hast die führenden 0-en vergessen. Im Vorkommaanteil spielen die 
keine Rolle. Im Nachkommaanteil sind die aber wichtig, weil sich dadurch 
die Aussage des Ergebnisses verändert.
Und damit sind wir dann schon in einem Bereich, in dem man darüber 
nachdenken kann sprintf einzusetzen. Auch mit dem Hintergedanken, dass 
es dann ein leichtes ist die Ausgabe in eine bestimmte Feldbreite zu 
packen, was wiederrum dem Benutzerkomfort zugute kommt, weil die Ausgabe 
auf einem LCD dann nicht ständig hin und her wandert, sondern das Komma 
immer an der gleichen Stelle stehen bleibt.
1
   sprintf( string, "%4d,%03d V", (int)(voltage_electrodes/1000),(int)(voltage_electrodes % 1000) );
2
   lcd_puts( string );

Sicher, sprintf hat einen nicht unwesentlichen Flash-Speicherverbrauch. 
Wenn das aber keine Rolle spielt und man seine Features nutzen kann, ist 
es eine gute Wahl. Sofern man gelernt hat, was man mit dem Format-String 
alles anfangen kann.

von Rick M. (rick00)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Denn wenn deine Anzeige eigentlich 42,008 sein sollte, steht auf deiner
> Anzeige 42,8

Daran hab ich bisher noch gar nicht gedacht, stimmt.
Hab bisher herumgetüftelt, wie ich mir ein Unterprogramm schreibe, daß 
auf 1ne oder 2 Nachkommastellen begrenzt und korrekt rundet.
Wollte die Fkt. "my_round" vom Artikel "Festkommaarithmetik benutzen.
Hab sie mir für meine Bedürfnisse adaptiert, leider funktionierts nicht 
richtig und ausserdem ist mir die Vorgehensweise zu komplex.
Für jede Rundung muß man Papier und Bleistift zur Hand nehmen und sich 
erst mal anschauen welche Indexmarken man setzen muß.

Die Fkt. sprintf ist dann natürlich sehr komfortabel, aber ich dachte 
für einen MC ist diese Fkt. nicht brauchbar, da sie viel zu viele 
Resourcen (CPU + Flash)verbraucht.

Daß es aber anscheinend doch praktikabel ist, freut mich, da ich n 
Problem weniger zum Lösen habe.

Ein vorher/nachher Vergelich zeigt mir ja den zusätzl. Speicherverbauch.


Gruß Rick

von Rick M. (rick00)


Lesenswert?

Karl Heinz Buchegger schrieb:
1
 sprintf( string, "%4d,%03d V", (int)(voltage_electrodes/1000),(int)(voltage_electrodes % 1000) );
2
    lcd_puts( string );


Soweit ich mich jetzt schlau gemacht habe bedeutet:

%4d: d steht für dezimal, ein i für integer oder speziell für mich ein u 
für unsigned integer würde auch gehen.
Da ich maximal nur 2 Vorkomma-Stellen habe, kann ich auch %2d, %2i oder 
%2u verwenden.

%03d: die führende Null bedeutet, daß anstelle von Leerstellen mit 
Nullen aufgefüllt wird. Also anstelle von 32,  8 wird 32,008 ausgegeben.
Da ich bei der Spannung nur 1ne Nachkommastelle möchte reicht %1d bei 2 
Nachkommastellen müsste ich dann %02d schreiben.

Richtig gerundet wird hier aber nicht, oder? Jedenfalls weiß ich nur 
davon, wenn man mit dem Typ float arbeitet %.2f

Gruß Rick

von Karl H. (kbuchegg)


Lesenswert?

Rick M. schrieb:

> Nullen aufgefüllt wird. Also anstelle von 32,  8 wird 32,008 ausgegeben.
> Da ich bei der Spannung nur 1ne Nachkommastelle möchte

Warum "modulierst" du dann durch 1000?
Da musst du dir noch was einfallen lassen.

> Richtig gerundet wird hier aber nicht, oder?

runden musst du selber.
Aber Vorsicht, dass machst du im Idealfall schon bei voltage_electrodes.
Deine eine Rundung in den Nachkommastellen, kann sich auch auf den 
Vorkommaanteil auswirken :-)

  429999     also 42,999

gerundet ergibt

  430000     also 43,000

von Karl H. (kbuchegg)


Lesenswert?

> da sie viel zu viele Resourcen (CPU + Flash)verbraucht.

definiere 'viel zu viel'
Für nicht verbrauchten Flash-Speicher kriegst du von Atmel kein Geld 
zurück.

von Rick M. (rick00)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Warum "modulierst" du dann durch 1000?
> Da musst du dir noch was einfallen lassen.

modulieren?
Hab meine Info von hier: (Flags)
http://home.htw-berlin.de/~junghans/cref/FUNCTIONS/format.html
Vielleicht hab ich da auch was falsch vertanden:
Zitat:" 0      Felder mit 0 ausfüllen (an Stelle von Leerzeichen). "

Karl Heinz Buchegger schrieb:
> runden musst du selber.
> Aber Vorsicht, dass machst du im Idealfall schon bei voltage_electrodes.
> Deine eine Rundung in den Nachkommastellen, kann sich auch auf den
> Vorkommaanteil auswirken :-)
>
>   429999     also 42,999
>
> gerundet ergibt
>
>   430000     also 43,000

Womit ich wieder am Anfang bin :-(

Hab mal ausprobiert, wieviel sprintf "schluckt".
Für diese 1ne Zeile werden 1440bytes mehr vergraucht. Ganz schön happig.

Gruß Rick

von Karl H. (kbuchegg)


Lesenswert?

Übrigens ist das hier

#define K_VOLTAGE    (VAREF * K / 1024) // Ergebnis: 52


  voltage_electrodes = ( adc_val * K_VOLTAGE ); // 900*52

nicht wirklich schlau.,

Nach der Expansion steht da

     adc_val * (2130L * 25L / 1024)

das bedeutet aber auch, dass der Klammerausdruck zuerst ausgewertet 
wird.
Er ergibt 52.00195, also keine ganze Zahl, wird aber als ganze Zahl 
berechnet. Und erst mit dieser ganzen Zahl wird dann dein adc_val 
multipliziert. Du hast also in deiner Multiplikation einen maximalen 
Fehler von 1023 (weil adc_val nicht größer werden kann) * 0.00195, oder 
ausgerechnet 1.99

Multiplizierst du aber zuerst alles aus und dividierst erst ganz zum 
Schluss, dann hast du diesen Fehler nicht.

Du möchtest das also so berechnen
1
     adc_val * 2130L * 25L  / 1024

Divisionen immer so weit wie möglich in der Berechnung nach hinten 
schieben. Einzige Ausnahme: Wenn Überlauf droht.

Inwiefern dich dieser Fehler jetzt stört, musst du entscheiden. Ich 
halte es auch ein wenig sinnfrei, Millivolt auszurechnen, wenn du dann 
sowieso nur Zehntelvolt ausgibst.

von Rick M. (rick00)


Lesenswert?

Ich hab das so gemacht, damit der MC nicht immer 2 Multiplikationen und 
eine Division durchführen muß, sondern nur eine Multiplikation.
Steh da momentan auf n Schlauch, da der Fehler zwischen 52 und 52,00195 
bei 0,2% liegt.
Hab mich da an den Artikel "Festkommaarithmetik" gehalten.
Stichwort "Korrekturfaktor" Ich rechne ja in mV!
Der maximale Fehler beträgt also 2mV und das ist akzeptabel.

Aus demselben Grund multilpiziere ich bei der Strommessung auch mit 
1000000 und rechne in uA.

Gruß Rick

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.