Festkommaarithmetik

#5575230
Lesenswert?

Hallo ich habe den obigen Beitrag gelesen und will mich auch daran 
halten. Aber mein Prozessor wohl nicht.

Ich habe eine Schaltung mit einem Mega 16 aufgebaut. Die geht auch.
Am Port PA0 will ich eine Spannung messen (Zwischen 0 und 5 Volt)
Da die 5V aus 1024 Stücken bestehen, muss ich den ADC Wert *5 /1024 
nehmen.
Ich habe am Mega 16 auch eine JTAG Schnittstelle, so dass ich die 
Operationen verfolgen kann
adc16 ist das Korrekte Ergebnis vom AD Programm (uint16_t)
Der Wert soll * 5000 genommen werden und dann durch 1024 Dividiert 
werden
Dann hat man das Ergebnis im mV

        adc16 = (adcval=ADC_Read_Avg(0,10));   Das geht bei fast 4V = 
987
  adc64 =  (adc16 * 5000);               adc64 uint64_t
  adc32 = (adc16 * 5000);                adc32 uint32_t
    adcul = (adc16 * 5000);                adcul unsigned long

Nach dem schrittweisen Ausführen steht bei Wert (Watch Ansicht)
bei allen 3 Variablen 19800    anstatt  4935000   ???
Kann mir jemand helfen was ist falsch? Ich brauche nur eine Zeile, die 
aber richtig rechnet.
Gast #5575440
Lesenswert?

Viele Wege führen nach Rom:

1024 und 5000 kann man maximal um 8 kürzen, am besten durch 4, es 
bleiben
256 und 1250
d.h. statt der Division durch 256 lässt man das unterste Byte weg.

Aber es gibt noch eine elegantere Lösung, wenn man sich die Bits und 
deren Wertigkeit ansieht:
1
Bit Wert mV
2
 9  512  2500
3
 8  256  1250
4
 7  128   625
5
 6   64   312,5
6
 5   32   156,25
7
 4   16    78,125
8
 3    8    39,0625
9
 2    4    19,53125
10
 1    2     9,765625
11
 0    1     4,8828125
1
uint16_t mask = 512, add = 2500, mV = 0;
2
do{
3
  if(adc & mask) mV+=add;
4
  add >>=1;
5
  }while((mask>>=1)!=0);
6
//Dann ist die maximale Summe: 2500+1250+625+312+156+78+39+19+9+4= 4992
7

8
//Genauer ist wegen Runden:
9
uint16_t mask = 512, add = 2500, mV = 0;
10
do{if(adc & mask) mV+=add; add >>=1;} while((mask>>=1)>4);add = 20;  // mask ist jetzt 4
11
do{if(adc & mask) mV+=add; add >>=1;} while((mask>>=1)!=0);
12

13
//Dann ist die maximale Summe: 2500+1250+625+312+156+78+39+20+10+5= 4995
14

15
//Man kann auch Loop-Unrolling machen, ist auch nicht viel länger, aber schneller:
16
uint16_t mV = 0;
17
  if(adc & 512) mV+=2500;
18
  if(adc & 256) mV+=1250;
19
  if(adc & 128) mV+=625;
20
  if(adc &  64) mV+=312;
21
  if(adc &  32) mV+=156;
22
  if(adc &  16) mV+=78;
23
  if(adc &   8) mV+=39;
24
  if(adc &   4) mV+=20;
25
  if(adc &   2) mV+=10;
26
  if(adc &   1) mV+=5;
27

28
Noch schneller wird es, wenn wir den ADC-Wert in zwei Bytes zerlegen:
29
uint16_t mV = 0;adch = ADCH; adcl = ADCL; 
30
//zwischenspeichern, damit sich nichts während der Abfrage ändert
31
  if(adch &   2) mV+=2500;
32
  if(adch &   1) mV+=1250;
33
  if(adcl & 128) mV+=625;
34
  if(adcl &  64) mV+=312;
35
  if(adcl &  32) mV+=156;
36
  if(adcl &  16) mV+=78;
37
  if(adcl &   8) mV+=39;
38
  if(adcl &   4) mV+=20;
39
  if(adcl &   2) mV+=10;
40
  if(adcl &   1) mV+=5;
Alles aus dem Stegreif und ungetestet.
Gast #5575540
Lesenswert?

Wenn man bei adc 1023  5000mV angezeigt haben will:
1
  if(adch &   2) mV+=2500;
2
  if(adch &   1) mV+=1250;
3
  if(adcl & 128) mV+= 626;
4
  if(adcl &  64) mV+= 313;
5
  if(adcl &  32) mV+= 157;
6
  if(adcl &  16) mV+=  79;
7
  if(adcl &   8) mV+=  40;
8
  if(adcl &   4) mV+=  20;
9
  if(adcl &   2) mV+=  10;
10
  if(adcl &   1) mV+=   5;
11

12
Wenn bei adc 1023  3300 mV angezeigt werden soll:
13
  if(adch &   2) mV+=1650;
14
  if(adch &   1) mV+= 826;
15
  if(adcl & 128) mV+= 413;
16
  if(adcl &  64) mV+= 207;
17
  if(adcl &  32) mV+= 104;
18
  if(adcl &  16) mV+=  52;
19
  if(adcl &   8) mV+=  26;
20
  if(adcl &   4) mV+=  13;
21
  if(adcl &   2) mV+=   6;
22
  if(adcl &   1) mV+=   3;
23

24
Wenn bei adc 1023  3297 mV angezeigt werden soll:
25
  if(adch &   2) mV+=1650;
26
  if(adch &   1) mV+= 825;
27
  if(adcl & 128) mV+= 413;
28
  if(adcl &  64) mV+= 206;
29
  if(adcl &  32) mV+= 103;
30
  if(adcl &  16) mV+=  52;
31
  if(adcl &   8) mV+=  26;
32
  if(adcl &   4) mV+=  13;
33
  if(adcl &   2) mV+=   6;
34
  if(adcl &   1) mV+=   3;
#5575557
Lesenswert?

@eProfi (Gast)

>Alles aus dem Stegreif und ungetestet.

Und auch völlig daneben. Der Op wollte nicht mehr als einen ADC-Wert in 
ein eine Meßgröße umrechnen und nicht irgendwelche Exkurse in die Welt 
der Micro- und Nonsensoptimierung unternehmen. Das Ziel erreicht man mit 
einer einfachen, übersichlichtlichen Zeile C-Code, die zu 99% auch 
vollkommen ausreicht.
Gast #5575611
Lesenswert?

Es geht noch schneller, wenn man die Reihenfolge umdreht und die ersten 
Additionen (solange die Summe in 8 Bit passt), mit 8 Bit ausführt:
1
typedef union{
2
  uint16_t W; /Word
3
  struct{
4
    uint8_t L; //Lo-Byte (Reihenfolge je nach Endianess)
5
    uint8_t H; //Hi-Byte (Reihenfolge je nach Endian-ness)
6
  };
7
} un_i16;
8

9
  un_i16 mV;
10
  mV.W=0;
11
  if(adcl &   1) mV.L+=   5; //8-Bit-Addition
12
  if(adcl &   2) mV.L+=  10;
13
  if(adcl &   4) mV.L+=  20;
14
  if(adcl &   8) mV.L+=  40;
15
  if(adcl &  16) mV.L+=  79;
16
  if(adcl &  32) mV.W+= 157; //16-Bit-Addition
17
  if(adcl &  64) mV.W+= 313;
18
  if(adcl & 128) mV.W+= 626;
19
  if(adch &   1) mV.W+=1250;
20
  if(adch &   2) mV.W+=2500;
21

22
//bei 3300 geht noch eine Zeile mehr mit .L:
23
  mV.W=0;
24
  if(adcl &   1) mV.L+=   3; //8-Bit-Addition
25
  if(adcl &   2) mV.L+=   6;
26
  if(adcl &   4) mV.L+=  13;
27
  if(adcl &   8) mV.L+=  26;
28
  if(adcl &  16) mV.L+=  52;
29
  if(adcl &  32) mV.L+= 104;
30
  if(adcl &  64) mV.W+= 207; //16-Bit-Addition
31
  if(adcl & 128) mV.W+= 413;
32
  if(adch &   1) mV.W+= 826;
33
  if(adch &   2) mV.W+=1650;
Um den vollen 16-bit-Wertebereich zu nutzen, kann man die addierten 
Werte mit 8 (nur 4 shifts), 10 (human readable), 13 (65535/5000=13,107), 
16 (nur 4 shifts) oder 19 (65535/3300=19,859) multiplizieren (dadurch 
werden sie genauer) und nach den Additionen wieder dividieren, bringt 
aber fast nichts.

Antwort schreiben

Bitte melde dich an, um einen Beitrag zu schreiben.

oder

Mit Google-Account einloggen

Die Registrierung ist kostenlos und dauert nur eine Minute.

Jetzt registrieren