Forum: Mikrocontroller und Digitale Elektronik 24 Bit signed effizient verarbeiten


von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Hallo zusammen,

ich steh grad wieder mal auf dem Schlauch (kein Wunder um die Uhrzeit)

auf einem AVR (ATmega328) krieg ich von einem per SPI angeschlossenen 
ADC  drei mal 8 bit, die zusammen einen 24 Bit vorzeichenbehafteten Wert 
darstellen. Der Wert ist das Ergebnis einer ratiometrischen 
Widerstandsmessung, skaliert mit einer (bekannten) Verstärkung.

Mein Problem ist: wie krieg ich mit AVR-GCC die 3x8 bit (wobei nur das 
erste byte signed ist) effizient in einen int32_t?

inline assembler muss es grade nicht sein (so zeitkritisch ist es auch 
nicht).

der alte und verpönte trick mit der Union? nur - ich hab "nur" 24 bit, 
und keine 32. Die höchsten 24 bit verwenden, und dann ein >>8? wird wohl 
mit dem signed nicht ganz hinhauen...


bitte um Denkanstöße!



lg Michi

von B. S. (bestucki)


Lesenswert?

Mein Vorschlag:
1
signed int32 resultat=0;
2
unsigned int8 byte[3]; /* byte[0] ist das niederwertigste Byte */
3
unsigned int8 i;
4
5
if(byte[2]&0x80) resultat+=0xFF;
6
for(i=0;i<3;i++)
7
{
8
  resultat<<=8;
9
  resultat+=byte[2-i];
10
}

Ich weiss, diese Datentypen gibt es so in C nicht...

von Anja (Gast)


Lesenswert?

Michael Reinelt schrieb:
> bitte um Denkanstöße!

Ist der Wert überhaupt ein Zweierkomplement oder wird er mit Offset 
0x800000 vom ADC geliefert?

Ansonsten: warum willst Du den Wert überhaupt rechts shiften. Ich 
betrachte ADC-Werte immer als Werte im Bereich +/- 0.0 .. 0.99999 der 
Referenzspannung.

Gruß Anja

von Markus W. (Firma: guloshop.de) (m-w)


Lesenswert?

Michael Reinelt schrieb:
> auf einem AVR (ATmega328) krieg ich von einem per SPI angeschlossenen
> ADC  drei mal 8 bit, die zusammen einen 24 Bit vorzeichenbehafteten Wert
> darstellen.
> (...)
> der alte und verpönte trick mit der Union? nur - ich hab "nur" 24 bit,
> und keine 32. Die höchsten 24 bit verwenden, und dann ein >>8? wird wohl
> mit dem signed nicht ganz hinhauen...

Guten Morgen!

Wenn du die Daten doch eh byteweise vom SPI einsammeln musst, warum 
wirfst du dann nicht einfach jeweils das niederwertigste Byte weg und 
setzt die beiden übrigen dann zu einem 16-Bit-signed-integer zusammen?

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Anja schrieb:

> Ist der Wert überhaupt ein Zweierkomplement oder wird er mit Offset
> 0x800000 vom ADC geliefert?

Ist er, definitiv.

> Ansonsten: warum willst Du den Wert überhaupt rechts shiften. Ich
> betrachte ADC-Werte immer als Werte im Bereich +/- 0.0 .. 0.99999 der
> Referenzspannung.

ja eh :-)

Aber wie am besten/effizientesten mit dem 24-Bit-Wert weiterrechnen bzw. 
umwandeln in [0..1]?

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Markus Weber schrieb:

> Wenn du die Daten doch eh byteweise vom SPI einsammeln musst, warum
> wirfst du dann nicht einfach jeweils das niederwertigste Byte weg und
> setzt die beiden übrigen dann zu einem 16-Bit-signed-integer zusammen?

Weil ich die niederwertigsten Bits auch bezahlt habe :-)

von Karl H. (kbuchegg)


Lesenswert?

Michael Reinelt schrieb:
> Anja schrieb:
>
>> Ist der Wert überhaupt ein Zweierkomplement oder wird er mit Offset
>> 0x800000 vom ADC geliefert?
>
> Ist er, definitiv.

Definitiv was?

Wenn es ein Offset ist, dann benutz halt den union Trick und zieh danach 
0x800000 davon ab, um 0x800000 zu 0 zu machen und signed Werte zu 
erhalten

> Weil ich die niederwertigsten Bits auch bezahlt habe :-)
Und dein elektrischer Aufbau ist so, dass die untersten 8 Bit nicht 
einfach nur Zufallszahlen sind?
Für einen 16 Bit ADC muss man sich elektrisch schon ordentlich ins Zeug 
legen. Für einen 24Bit ADC gilt das noch viel mehr. Immerhin sind das 
bei 5V Referenz schon 0.3 Millionstel Volt Auflösung.

von Falk B. (falk)


Lesenswert?

@  Michael Reinelt (fisa)

>ADC  drei mal 8 bit, die zusammen einen 24 Bit vorzeichenbehafteten Wert

>Mein Problem ist: wie krieg ich mit AVR-GCC die 3x8 bit (wobei nur das
>erste byte signed ist) effizient in einen int32_t?

Wo ist das Problem?
1
signed int32 resultat=0;
2
unsigned int8 byte[3]; /* byte[0] ist das niederwertigste Byte */
3
4
resultat = (int32_t)byte[2]<<16 + (int32_t)byte[1]<<8 + (int32_t)byte[0];
5
if (bytes[2] & 0x80) result |= 0xFF000000;   // Vorzeichen erweitern

>der alte und verpönte trick mit der Union? nur - ich hab "nur" 24 bit,
>und keine 32.

Ja und? Das Rumgemurkse mit 24 Bit bringt nur Stress. Pack es wie oben 
gezeigt vorzeichenrichtig in 32 Bit und fertig.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Karl Heinz Buchegger schrieb:
> Michael Reinelt schrieb:
>> Anja schrieb:
>>
>>> Ist der Wert überhaupt ein Zweierkomplement oder wird er mit Offset
>>> 0x800000 vom ADC geliefert?
>>
>> Ist er, definitiv.
>
> Definitiv was?

Sorry, das war leider mißverständlich: Es ist definitiv ein 
Zweierkomplement:
0x000000 => 0, 0x7fffff => +max, 0x800000 => -max

> Wenn es ein Offset ist, dann benutz halt den union Trick und zieh danach
> 0x800000 davon ab, um 0x800000 zu 0 zu machen und signed Werte zu
> erhalten
siehe oben, es ist kein Offset.

>> Weil ich die niederwertigsten Bits auch bezahlt habe :-)
> Und dein elektrischer Aufbau ist so, dass die untersten 8 Bit nicht
> einfach nur Zufallszahlen sind?
> Für einen 16 Bit ADC muss man sich elektrisch schon ordentlich ins Zeug
> legen. Für einen 24Bit ADC gilt das noch viel mehr. Immerhin sind das
> bei 5V Referenz schon 0.3 Millionstel Volt Auflösung.

Ist mir klar, es geht mir aber ein Stück weit auch ums Prinzip ;-)

Momentan habe ich folgende Lösung:

Ich mach den 16-Bit-Trick "in die andere Richtung", d.h. ich betrachte 
die 24-Bit-Zahl als 32 bit, mit den untersten 8 Bit auf 0 gesetzt. Auf 
diese Art kann ich direkt die 4 Byte des int32 setzen, drei vom ADC, das 
letzte Byte auf 0.

Mein Wert ist dann zwar um Faktor 256 zu groß, aber das macht nix: ich 
lese auf dieselbe Art auch den Referenzwert vom ADC, und da ich sowieso 
normalisieren/dividieren muss (ratiometrische Messung), kürzt sich der 
Faktor 256 raus.

ich wär aber trotzdem neugierig, obs eine trickreiche Variante gibt mit 
solchen 24-bit zahlen zu arbeiten....


lg Michi

von Falk B. (falk)


Lesenswert?

Mist, man sollte nicht blind alles kopieren.
1
int32_t resultat;
2
uint8_t byte[3]; /* byte[0] ist das niederwertigste Byte */
3
4
resultat = (int32_t)byte[2]<<16 + (int32_t)byte[1]<<8 + (int32_t)byte[0];
5
if (bytes[2] & 0x80) resultat |= 0xFF000000;   // Vorzeichen erweitern

von Helmut S. (helmuts)


Lesenswert?

> ich wär aber trotzdem neugierig, obs eine trickreiche Variante gibt mit
solchen 24-bit zahlen zu arbeiten

Als man noch alles in Assembler programmierte, hätte man sich eine 24bit 
Arithmetik(+,-,*,:) geschrieben.
Da du aber in C programmierts und wahrscheinlich kein Assemblerprogramm 
drin haben willst, bleibt nur die 32bit Variante.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Falk Brunner schrieb:
> int32_t resultat;
> uint8_t byte[3]; /* byte[0] ist das niederwertigste Byte */
>
> resultat = (int32_t)byte[2]<<16 + (int32_t)byte[1]<<8 +
> (int32_t)byte[0];
> if (bytes[2] & 0x80) resultat |= 0xFF000000;   // Vorzeichen erweitern

Genauso hatte ich das erst angedacht, aber der avr-gcc macht da sehr 
suboptimalen Code draus...

Meine Lösung sieht jetzt so aus:
1
uint8_t rtd_receive(int32_t * result)
2
{
3
    SPI_send(0x12);    // read data once
4
    *((uint8_t *) result + 3) = SPI_receive();
5
    *((uint8_t *) result + 2) = SPI_receive();
6
    *((uint8_t *) result + 1) = SPI_receive();
7
    *((uint8_t *) result + 0) = 0;
8
    SPI_send(0xff);    // send NOP to force DOUT/DRDY high
9
    return 1;
10
}

von Basti (Gast)


Lesenswert?

Hab den IC auch gerade in der Mangel:
Ich mache es einfach so:
1
uint8_t buf[4] = {0};
2
int32_t temp;
3
int8_t *zeiger;
4
  
5
zeiger = &temp;
6
7
8
usart_spi_read_packet(&USARTC0,buf,3);
9
10
    
11
zeiger[0] = buf[2];
12
zeiger[1] = buf[1];
13
zeiger[2] = buf[0];
14
zeiger[3] = 0;

Wenn wir über den selben IC reden, dann ließt der eh mit max 80 Hz, dann 
ist so ein bisschen ineffektivität wohl wenig schlimm...

von Klaus W. (mfgkw)


Lesenswert?

Die 0 rechts anzuhängen und mit einem zu großen Wert rechnen, ist die 
eine Variante.
Die andere ist die wie von Falk Brunner vorgegeschlagen mit einer 
Fallunterscheidung anhand des obersten gelesenen Bits.
Allerdings würde ich dann das Feld mit den Bytes nicht 3 groß machen, 
sondern 4 und bereits darin das höherwertigste Byte setzen.
Dann vermeidet man die 32-Bit-Rechnerei ganz und bekommt zum Schluß den 
fertigen Wert:
1
    uint8_t byte[4]; /* byte[0] ist das niederwertigste Byte */
2
    // bytes[0] bis bytes[2] irgendwie vom Wandler holen...
3
    bytes[3] = (bytes[2] & 0x80) ? 0xFF : 0x00; // Vorzeichen erweitern
4
    ... (*(int32_t*)&bytes[0]) ... // das ist der resultierende Wert

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

Michael Reinelt schrieb:
> ich wär aber trotzdem neugierig, obs eine trickreiche Variante gibt mit
> solchen 24-bit zahlen zu arbeiten....

Erstens, es gibt Compiler die auch 24-Bit Datentypen haben.

Zweitens, es gibt sogar eine C-Erweiterung für Festkommaarithmetik die 
auch 24-Bit Datentypen hat.

Drittens, GCC ist da in beiden Fällen schlecht ausgestattet, obwohl die 
Festkommaarithmetik langsam ihren Weg in den Compiler-Source findet.

Viertens, eine vom MSB abwärts zusammengesetzten 32-Bit Variable kann 
man selbstverständlich auch durch 256 teilen oder um 8 Bit mit 
Sign-Extension nach rechts schieben, damit der Wert dem der 24-Bit Zahl 
entspricht.

Fünftens, eine vom LSB an aufwärts zusammengesetzten 32-Bit Variable 
muss man sign-extenden. Hier 
Beitrag "Re: 24 Bit signed effizient verarbeiten" hat 
Falk schon alles gesagt. Natürlich kann man auch den Wilden Mann 
markieren und nutzlos in die Trickkiste greifen:

1
struct {
2
    int i24:24; // Logisch 24 Bits, physikalisch 24 oder mehr (32) Bits
3
} stemp;
4
5
//
6
// 3x8 Bit von unten in eine 32-Bit Variable schreiben
7
//
8
int32_t temp = ((int32_t)byte[2] << 16) && ((int32_t)byte[1] << 8) && ((int32_t)byte[0]);
9
10
//
11
// Das unhandliche Ding in eine normale 32-Bit Variable umpacken.
12
// Dabei hat der Compiler sich durch den Umweg über das Bitfeld um
13
// die Sign-Extension zu kümmern
14
//
15
int32_t exended = stemp.i24 = temp;

In echtem Code sollte man natürlich alle Kommentare und halbwegs 
sprechende Variablenbezeichnungen weglassen, um die Codewartung maximal 
zu erschweren.

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

Gutes neues Jahr an alle hier!

Es wird nun Falk's Vorschlag. Danke euch allen! Ihr seid wunderbar!


Basti schrieb:
> Hab den IC auch gerade in der Mangel:

Hast Interesse an Schaltplan- und Code-Austausch?


Außerdem:

Hannes Jaeger schrieb:
> In echtem Code sollte man natürlich alle Kommentare und halbwegs
> sprechende Variablenbezeichnungen weglassen, um die Codewartung maximal
> zu erschweren.

Bitte, das ist eh klar! Ich mach seit 25 Jahren Software (wenn auch 
nicht am uC), die Grundregel "real programmers write programs, not 
documentation" habe ich schon lange verinnerlicht! :-)



lg Michi

von apr (Gast)


Lesenswert?

Wie wäre es mit?:
1
#ifdef __INT24_MAX__
2
typedef __int24 int24_t;
3
#else
4
/* fallback on 32-bit integers */
5
typedef int32_t int24_t;
6
#endif
7
8
int main()
9
{
10
  volatile int24_t foo = 4;
11
  int i;
12
  for(i = 0; i < 5; ++i)
13
    ++foo;
14
  return 0;
15
}

Bzw. spricht grundsätzlich etwas dagegen die 24 Bit auf dem avr zu 
verwenden oder ist das ganz viel zu unausgereift/böse/nicht portabel?

von Michael R. (Firma: Brainit GmbH) (fisa)


Lesenswert?

apr schrieb:
> Bzw. spricht grundsätzlich etwas dagegen die 24 Bit auf dem avr zu
> verwenden oder ist das ganz viel zu unausgereift/böse/nicht portabel?

Geht das auch mit avr-gcc? ich hab mal alles unter /usr/lib/avr nach 
int24 gegrept, aber nix in der Form int24 gefunden...

von Falk B. (falk)


Lesenswert?

@  apr (Gast)

>Bzw. spricht grundsätzlich etwas dagegen die 24 Bit auf dem avr zu
>verwenden

Ja, der nicht vorhandene Standard.

> oder ist das ganz viel zu unausgereift/böse/nicht portabel?

Keine Ahnung. Aber man löst sinnvollerweise keine Probleme, die nicht 
das sind. Wen einer handvoll Variablen lohnt es sich nicht, 1 Byte 
einzusparen. Darüber macht man sich Gedanken, wenn man hunderte oder 
tausende Datensätze verarbeiten will.

http://www.mikrocontroller.net/articles/AVR-GCC-Codeoptimierung#Prinzipien_der_Optimierung

von Reinhard Kern (Gast)


Lesenswert?

apr schrieb:
> Bzw. spricht grundsätzlich etwas dagegen die 24 Bit auf dem avr zu
> verwenden oder ist das ganz viel zu unausgereift/böse/nicht portabel?

Das müsste man zuvor klären: es ist gut möglich, dass ein Compiler alles 
was > 16 bit und <= 32 bit ist, eben mit 32 bit-Funktionen rechnet, dann 
bringt das nichts. Ganz sicher gibt es keine Arithmetik-Library mit 
speziellen Rechenfunktionen für jede Zahlenlänge von 4 bis 64 bit.

Gruss Reinhard

von Basti (Gast)


Lesenswert?

>Hast Interesse an Schaltplan- und Code-Austausch?

Hm, so kompliziert ist der IC ja nicht... aber kann ja nicht verkehrt 
sein...
Ich schreib dich an...

Grüße

Basti

von Klaus W. (mfgkw)


Lesenswert?

Selbstgespräche? :-)

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.