Forum: Mikrocontroller und Digitale Elektronik Rechnen mit große integer Zahlen beim ATMega328P


von basti (Gast)


Lesenswert?

Hallo Leute,

Ich versuche mich grad in C und die Entwicklung von Programmen für den 
ATMega328P einzuarbeiten. Jetzt habe ich ein kleines Problem mit dem 
Casten von großen Integer Zahlen bzw. mit dem Rechnen großer Integer 
Zahlen. Konkret geht es um diese beiden Zeilen:
1
 uint16_t pwm1 = ((Hall1-Min1)/(Max1-Min1))*1023;
1
 uint8_t pwm2 = ((Hall2-Max2)/(Min2-Max2))*255;

Hall1 und Hall2 sind Analogwerte, die vom Hallsensor mit der ADC_R 
Funktion eingelesen werden. Diese Werte sollen dann mit obiger Formel in 
einen neuen Wertebereich berechnet werden. Hab das so von der Arduino 
IDE übernommen, da gibts nämlich die MAP Funktion, die im Grunde 
genommen das Gleiche ist.


Ich habe nun das Problem, dass bei ((Hall1-Min1)/(Max1-Min1))*1023 das 
Ergebnis 0 wird, weil (Hall1-Min1)/(Max1-Min1) gleich 0, irgendwas ist. 
Wenn ich es anders schreiben, so z. B. (Hall1-Min1)*1023)/(Max1-Min1) 
wird das Zwischenergebnis (Hall1-Min1)*1023) größer als 16 Bit. Um 
später aber meinen PWM Dutycycle einstellen zu können brauche ich eine 
16Bit Zahl.

Ich habe auch schon probiert das Ergebnis in einer 32-Bit variable zu 
speichern. Wenn ich diese dann allerdings an OCR1A übergebe, gibt mir 
meine Uart Schnittstelle keine richtigen Werte aus und meine PWM 
Dutycycle passt auch nicht. Jetzt müsste ich vermutlich mit einem Cast 
arbeiten oder? Ich habe das schon probiert und im Internet etwas 
recherchiert, habe aber noch keine zufriedenstellende Lösung gefunden. 
Kann mir da jemand weiterhelfen? Wahrscheinlich ist das Ganze einfach zu 
lösen, aber ich stehe schon mehrere Stunden auf dem Schlauch und komme 
nicht weiter...

Wäre sehr dankbar, wenn jemand den ein oder anderen Ratschlag dazu 
hätte.


1
while(1)
2
  {
3
    ADC1 = ADC_R(1); //read Hall1 Signal
4
    
5
    char Buffer[20];              
6
    sprintf(Buffer, "ADC1:%i", ADC1); //only for tests
7
    uart_puts(Buffer);
8
    _delay_ms(2000);    //delete for normal operation
9
    uart_puts("\r\n"); 
10
    
11
    
12
    uint16_t pwm1 = ((ADC1-Min1)/(Max1-Min1))*1023; //change of value range
13
    if(pwm1 > 1023){pwm1 = 1023;} //only for testing
14
    else if(pwm1 < 0){pwm1 = 0;}
15
    OCR1A = pwm1; //set duty cycle
16
      
17
      sprintf(Buffer, "PWM1:%i", pwm1);
18
      uart_puts(Buffer);
19
      _delay_ms(3000);    //delete for normal operation
20
      uart_puts("\r\n");
21
22
23
    ADC2 = ADC_R(0); //read Hall2 Signal
24
    
25
    sprintf(Buffer, "ADC2:%i", ADC2);
26
    uart_puts(Buffer);
27
    _delay_ms(2000);    //delete for normal operation
28
    uart_puts("\r\n");
29
    
30
    uint8_t pwm2 = ((ADC2-Max2)/(Min2-Max2))*255; //change of value range
31
    if(pwm2 > 255){pwm2 = 255;} //only for testing
32
    else if(pwm2 < 0){pwm2 = 0;}
33
    OCR0A = pwm2; //set duty cycle
34
35
      //char Buffer[20];              //only for tests
36
      sprintf(Buffer, "PWM2:%i", pwm2);
37
      uart_puts(Buffer);
38
      _delay_ms(3000);    //delete for normal operation
39
      uart_puts("\r\n"); 
40
  }

von GeraldB (Gast)


Lesenswert?

Das ist ja auch von Anfang an eine Integer-Berechnung.
Ich meine, du mußt bei der Multiplikation mit den Konstanten "*1023.0" 
bzw. "*255.0" schreiben. Dann sollte der Compiler daraus eine 
FP-Berechnung machen.

von Peter D. (peda)


Lesenswert?

Wie zeitkritisch sind denn die Berechnungen?
Ich habe mir angewöhnt, Analogwerte in float zu rechnen.
Ist einfach viel bequemer, als sich ständig mit Überläufen und 
Rundungsfehlern ins Knie zu schießen.

von basti (Gast)


Lesenswert?

Nein nichts zeitkritisches. Die Lösung war dann wohl einfacher als 
gedacht. Also mit den Fleißkommawerten funktioniert es jetzt. Danke für 
die Hilfe!!

von Oliver S. (oliverso)


Lesenswert?

basti schrieb:
> Wenn ich es anders schreiben, so z. B. (Hall1-Min1)*1023)/(Max1-Min1)
> wird das Zwischenergebnis (Hall1-Min1)*1023) größer als 16 Bit. Um
> später aber meinen PWM Dutycycle einstellen zu können brauche ich eine
> 16Bit Zahl.

Nur der Vollständigkeit halber, falls es doch mal zeitkritischr wird: 
Rechne das Zwischenergebnis in 32 bit, und das Endergebnis der Division 
castest du dann wieder nach 16 bit.

Oliver

von Sebastian S. (amateur)


Lesenswert?

Wenn Du das Problem zerlegst, wirst Du feststellen, dass bei der 
Subtraktion nichts passieren kann.
Natürlich nur wenn beide Werte brav im positiven Bereich rumhängen.
Die Sache mit der Multiplikation hast Du aber falsch angepackt.
Hier gilt: Möglichst früh multiplizieren!
Das kann allerdings bedeuten, das der jeweilige Bereich uint16_t bzw. 
uint8_t, VOR der Division nicht, ausreicht.
Also besser:
((Hall1-Min1)*1023)/((Max1-Min1)*1023);
statt:
((Hall1-Min1)/(Max1-Min1))*1023;

Muss aber nicht sein.

von Matthias L. (Gast)


Lesenswert?

1
 uint16_t pwm1 = ((Hall1-Min1)/(Max1-Min1))*1023;
2
 uint8_t pwm2 = ((Hall2-Max2)/(Min2-Max2))*255;

Das muss 1024 bzw 256 sein.

von Wolfgang (Gast)


Lesenswert?

Matthias L. schrieb:
> uint16_t pwm1 = ((Hall1-Min1)/(Max1-Min1))*1023;
>  uint8_t pwm2 = ((Hall2-Max2)/(Min2-Max2))*255;
>
> Das muss 1024 bzw 256 sein.

Ich habe noch keinen 10- bzw. 8-Bit Wandler gesehen, der 1024 bzw. 256 
ausgeben kann.

Nehmen wir ganz einfach mal an:
Max1 = 1023; Min1 = 0; Hall1 = 1023

Warum sollt da 1024 raus kommen?

von Matthias L. (Gast)


Lesenswert?

Wolfgang schrieb:
> Ich habe noch keinen 10- bzw. 8-Bit Wandler gesehen, der 1024 bzw. 256
> ausgeben kann.

Natürlich nicht. Aber das sind die Endwerte. Ein 8bit Wandler skaliert 
die Wert 0..255, was 256 mögliche Werte ergibt. Deshalb.

Wenn also das Ausgangssignal 157 ist, heisst das, das Analogsignal ist 
grösser 157/256, aber kleiner als 158/256-stel der Referenz.

von Pandur S. (jetztnicht)


Lesenswert?

Anstelle von *1023 kann man sich ueberlegen, ob *1024 auch passen 
wuerde, denn das waere dann shift left 10, mit einem Fehler von 0.1%

von Joachim B. (jar)


Lesenswert?

Peter D. schrieb:
> Ich habe mir angewöhnt, Analogwerte in float zu rechnen.

und ich bleibe lieber bei int mit mV(milliV) dV(deziV) oder cV(centiV)

Nötige Konstanten kann man vorher rechnen und einsetzen, dezimal Punkte 
oder Komma kann man hinterher locker als Char einfügen ohne int zu 
verlassen. Die float Lib muss auch nicht eingebunden werden!

von Kevin M. (arduinolover)


Lesenswert?

Joachim B. schrieb:
> und ich bleibe lieber bei int mit mV(milliV) dV(deziV) oder cV(centiV)

Und ich bleibe lieber bei einem µC mit FPU.

von Sebastian S. (amateur)


Lesenswert?

> Ich habe mir angewöhnt, Analogwerte in float zu rechnen.
Wo um alles in der Welt gibt es bei einem ATMega Analogwerte?
Sollte es die wirklich geben, so hast Du diese erst - meist recht 
aufwändig - aus Integerzahlen errechnet. So verbrauchen diese unnötig 
viel Platz und Rechenzeit.

von Peter D. (peda)


Lesenswert?

Joachim B. schrieb:
> Die float Lib muss auch nicht eingebunden werden!

Die ist recht schlank (~1kB), kostet also kaum was.
Float printf/scanf Support ist etwas größer.
Die float Division ist sogar schneller als die für 32 Bit, da float ja 
nur 24 Bit ausrechnen muß.

von Peter D. (peda)


Lesenswert?

Sebastian S. schrieb:
> Wo um alles in der Welt gibt es bei einem ATMega Analogwerte?

Ich hab z.B. eine Strommessung über mehrere Dekaden. Ein 16Bit-ADC wird 
in 4 Bereiche umgeschaltet. Für jeden Bereich erfolgt eine Kalibration 
mit float-Werten (Gain, Offset) im EEPROM. Mit Integer wäre sowas ein 
Albtraum.

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.