Forum: Mikrocontroller und Digitale Elektronik 16-Bit-Array in 8-Bit-Array zerlegen


von Type Pun Intended (Gast)


Lesenswert?

Guten Morgen,

ich habe ein 16-Bit-Array, das ich in ein 8-Bit-Array doppelter Länge 
verpacken will, also so:
1
void put8(uint8_t data[], size_t len);
2
3
4
void foo(void)
5
{
6
    // Existiert schon:
7
    uint16_t var1[] = {0x1234, 0x5678, 0x9ABC};
8
9
    // Soll so gelesen werden:
10
    uint8_t var2[] = {0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC};
11
}

Meine Plattform (ARM32) ist little-endian, also fällt eine einfache 
Kopie mit memcpy weg. Immerhin scheint das Array dicht gepackt. Der 
folgende kleine Test macht zumindest das, was ich erwarte:
1
enum {
2
    big_endian    = 0,
3
    little_endian = 1,
4
    unpacked      = 2,
5
};
6
7
uint_fast8_t checkTypPunning(void)
8
{
9
    uint16_t tst1[] = {0x1234, 0x5678, 0x9ABC};
10
    uint8_t *tst2 = tst1;
11
    bool be0 = (tst2[0] == 0x12);
12
    bool be1 = (tst2[1] == 0x34);
13
    bool be2 = (tst2[2] == 0x56);
14
    bool be3 = (tst2[4] == 0x78);
15
    bool le0 = (tst2[0] == 0x34);
16
    bool le1 = (tst2[1] == 0x12);
17
    bool le2 = (tst2[2] == 0x78);
18
    bool le3 = (tst2[3] == 0x56);
19
20
    if( be0 & be1 & be2 & be3 )
21
        return big_endian;
22
    else if( le0 & le1 & le2 & le3  )
23
        return little_endian;
24
    else
25
        return unpacked;
26
}

Das einzige, was mir standardkonform einfällt, wäre:
1
    // Existiert schon:
2
    uint16_t var1[] = {0x1234, 0x5678, 0x9ABC};
3
4
    // Konvertierung
5
    size_t len = sizeof(var1);
6
    uint8_t *var2 = (uint8_t *) malloc(len);
7
    for( size_t i = 0; i<len; i++ ) {
8
        var2[i] = ntohs(var1[i]);
9
    }
10
11
    // Nutzung
12
    put8(var2, len);
malloc() würde ich allerdings gern weitestgehend vermeiden; das Array 
darf auch zerstört werden.  Was ist jetzt die richtige™ Vorgehensweise?

Grüße
Punny

von Dirk B. (dirkb2)


Lesenswert?

Type Pun Intended schrieb:
> Immerhin scheint das Array dicht gepackt.

Das ist eine Grundvoraussetzung für Arrays.

> Das einzige, was mir standardkonform einfällt, wäre:
1
 var2[i] = ntohs(var1[i]);

ntohs gehört zum Posix-Standard und nicht zum C-Standard.

Du kannst dir aber selber eine Funktion mit Bitoperationen basteln.

Deine Konvertierung funktioniert so aber nicht.
Du holst 16-Bit-Werte raus und schreibst sie nach 8-Bit.
Dabei gehst du über die Grenzen deines 16-Bit Arraya hinaus.


> das Array> darf auch zerstört werden.
1
for( size_t i = 0; i<sizeof(var1)/sizeof(*var1); i++ ) {
2
  var1[i] = ntohs(var1[i]);

von A. (Gast)


Lesenswert?

Wenn auch folgendes erlaubt sein darf:
1
uint8_t var2[] = {0x34, 0x12, 0x78, 0x56, 0xBC, 0x9A};

Dann interpretiere die Variable einfach als uint8_t Array.
1
uint16_t var1[] = {0x1234, 0x5678, 0x9ABC};
2
uint8_t * pvar2 = &var1[0];
3
4
//hier dein code z.B:
5
if(pvar2[0] == 0x34){
6
//..
7
}

Dann musst du halt überall wissen, dass die Bytes verkehrtherum 
angeordnet sind. Dann könntest du z.B. eine Zugriffsfunktion schreiben 
(finde ich jedoch unelegant!), wie:
1
uint8_t get(uint8_t * arr, uint8_t pos){
2
  if(pos % 2 == 0)
3
    return arr[pos + 1];
4
  else
5
    return arr[pos - 1];
6
}

Alles nicht geprüft.

von A. (Gast)


Lesenswert?

Nachtrag:

Es geht dann z.B. auch folgendermaßen. Dann kommt man ganz ohne memcpy 
aus und das Array wird auch nicht zerstört. Wenn du hingegen viele 
Zugriffe hast, würde ich mir schon überlegen das Array zu formatieren, 
braucht man weniger Rechenleistung.
1
uint16_t var1[] = {0x1234, 0x5678, 0x9ABC};
2
3
uint8_t get(uint8_t * arr, uint8_t pos){
4
  if(pos % 2 == 0)
5
    return arr[pos + 1];
6
  else
7
    return arr[pos - 1];
8
}
9
10
uint8_t x = get((uint8_t*) &var1[0],2);

von Jonas B. (jibi)


Lesenswert?

>if( be0 & be1 & be2 & be3 )

Unabhängig vom Problem, das macht sicher nicht das was du denkst...

Gruß J

von Markus F. (mfro)


Lesenswert?

Du versuchst, die "strict-aliasing"-Regel zu erfüllen. Das ist 
grundsätzlich sehr löblich, hier jedoch völlig unnötig (zumindest in C).

Hier darf man ungestraft jeden beliebigen Zeigertyp auf einen 
char-Zeiger casten (nur andersrum nicht):
1
var2 = (uint8_t *) var1;

wär' hier also völlig ausreichend.

Ob die Bytes in der für dich richtigen oder falschen Reihenfolge stehen, 
ist ein anderes Problem und mit ntohxxx()-Funktionen nicht sauber zu 
erschlagen. Die können nur (weil sie eben so definiert ist) von 
Network-Byteorder (big endian) auf Host-Byteorder wandeln. Auf einer 
Big-Endian Plattform machen die folglich einfach genau nix (was 
wahrscheinlich nicht das ist, was Du haben willst).

von Jim M. (turboj)


Lesenswert?

Markus F. schrieb:
> Auf einer
> Big-Endian Plattform machen die folglich einfach genau nix (was
> wahrscheinlich nicht das ist, was Du haben willst).

Doch, genau das wollte der OP laut seinem 1. Beitrag haben.

von A. (Gast)


Lesenswert?

Markus F. schrieb:
> Du versuchst, die "strict-aliasing"-Regel zu erfüllen. Das ist
> grundsätzlich sehr löblich, hier jedoch völlig unnötig (zumindest in C).

Ich behaupte jetzt mal einfach etwas:

Sowas passiert, wenn man ursprünglich aus der PC-Programmierung kommt, 
nur vorgefertigte Bibliotheken nimmt, die einen ST z.B. für die STM32 
zur Verfügung stellt und noch nie auf Registerebene programmiert hat.
Ich persönlich programmiere z.B. komplett ohne HAL und Stdlib etc.

Auf µC-Ebene sind eben auch sehr direkte Wege erlaubt.

von Type Pun Intended (Gast)


Lesenswert?

Dirk B. schrieb:
> Du holst 16-Bit-Werte raus und schreibst sie nach 8-Bit.
> Dabei gehst du über die Grenzen deines 16-Bit Arraya hinaus.

Jetzt, wo ich meinen Kaffee hatte, sehe ich's auch.

Das ist gemeint:
1
    for( size_t i = 0; i<len; i+=2 ) {
2
        uint16_t tmp = var1[i/2];
3
        var2[i]   = tmp>>8;
4
        var2[i+1] = tmp & 0xFF;
5
    }

Und dann sollte ich noch etwas nachliefern, sonst bin ich den ganzen Tag 
nervös:
1
    free(var2);


Jonas B. schrieb:
>>if( be0 & be1 & be2 & be3 )
>
> Unabhängig vom Problem, das macht sicher nicht das was du denkst...
Ja.... vielleicht hätte ich vorher richtig wach werden sollen.


A. schrieb:
> Wenn auch folgendes erlaubt sein darf:
> uint8_t var2[] = {0x34, 0x12, 0x78, 0x56, 0xBC, 0x9A};

Neee, das ist genau nicht gewünscht. Eine Zugriffsfunktion im 
Pilgerschritt wird mich spätestens bei der übernächsten Codeänderung 
verwirren.

Dirk B. schrieb:
> Das ist eine Grundvoraussetzung für Arrays.

Ich finde im C-Standard lediglich, dass Zeiger-Arithmetik immer den 
tatsächliche Abstand der Elemente berücksichtigt. Wo finde ich denn, 
dass sizeof(uint16_t[2]) == sizeof(uint8_t[4]) ?

Markus F. schrieb:
> Auf einer
> Big-Endian Plattform machen die folglich einfach genau nix (was
> wahrscheinlich nicht das ist, was Du haben willst).

Hm... ich gehe davon aus, dass auf eine Big-Endian-Plattform meine 
16-Bit-Arrays schon von sich aus richtig herum im Speicher liegen. 
Brauche ich echt noch einen zweiten Kaffee?

von Type Pun Intended (Gast)


Lesenswert?

> Brauche ich echt noch einen zweiten Kaffee?
Meine Rechtschreibung spricht da vermutlich eine klare Sprache.

von Markus F. (mfro)


Lesenswert?

A. schrieb:
> Auf µC-Ebene sind eben auch sehr direkte Wege erlaubt.

Mit µC oder nicht hat das nicht das Geringste zu tun. C ist C - hier wie 
dort.
Was das mit Registern zu tun haben soll, erschliesst sich mir 
ebensowenig.

von A. (Gast)


Lesenswert?

Na, wahrscheinlich programmierte er bisher nur dicke Anwendungen für 
Windows/Linux.

Nun kommt er daher und will einen µC programmieren. Ist doch super, 
sowas wie CubeMX initialisiert einen alles so wie man will und man macht 
nur noch Klicki-Bunti.

Und dann kommen solche Fragen! Auch auf einem ARM32 benutze ich kein 
malloc oder ähnliches. Hier reicht eine einfache Zeigerarithmetik, 
Kopierfunktion, Transformationsfunktion oder Zugriffsfunktion.

Wenn man auf Registerebene programmiert setzt man sich häufig auch mit 
der Struktur von µC auseinander und man lernt effizient zu 
programmieren.

von Markus F. (mfro)


Lesenswert?

Type Pun Intended schrieb:
> Hm... ich gehe davon aus, dass auf eine Big-Endian-Plattform meine
> 16-Bit-Arrays schon von sich aus richtig herum im Speicher liegen.
> Brauche ich echt noch einen zweiten Kaffee?

Definiere "richtig herum" (auch wenn ich selbst zur m68k-Generation 
gehöre, der little endian immer noch wie Teufelswerk vorkommt...).

Ich bin davon ausgegangen, dass Du eine universelle 
"Byte-verdreh"-Routine suchst, die abhängig von der Host-Byteorder immer 
das richtige tut.

Die gibt's nicht fertig.

von Dirk B. (dirkb2)


Lesenswert?

Type Pun Intended schrieb:
> Ich finde im C-Standard lediglich, dass Zeiger-Arithmetik immer den
> tatsächliche Abstand der Elemente berücksichtigt. Wo finde ich denn,
> dass sizeof(uint16_t[2]) == sizeof(uint8_t[4]) ?

Der C-Standard verlangt, das bei Arrays von Objekten keine zusätzlichen 
Füllbytes eingebaut werden - die sind dann schon im eigentlichen Objekt 
vorhanden.

Du möchtest auf der einen Seite einen 16-Bit Typ haben, auf der anderen 
einen 8-Bit Typ. 16 = 2*8
Wenn es einen davon nicht gibt, meckert der Compiler.
Anders sieht es bei uint_fast8_t oder uint_least8_t. Die können auch 
mehr Bits haben.

von Dirk B. (dirkb2)


Lesenswert?

Type Pun Intended schrieb:
> Das ist gemeint:    for( size_t i = 0; i<len; i+=2 ) {
>         uint16_t tmp = var1[i/2];
>         var2[i]   = tmp>>8;
>         var2[i+1] = tmp & 0xFF;
>     }

Das ist die standardkonforme und kompatible Vorgehensweise.
Moderne Compiler erkennen das und machen daraus dann auch ein 
Assembler-Byteswap, wenn es nötig ist.

von Type Pun Intended (Gast)


Lesenswert?

Hier scheinen wieder menschliche Wesen den Turing-Test nicht zu 
bestehen. Sobald das Wort "ARM" fällt, wird direkt auf "Klicki-Bunti", 
"CubeMX" und Wasweisich geschlossen. "Ein richtiger Commander kennt jede 
Schraube und jede Schweißnaht seines Schiffes und ein richtiger 
Programmierer arbeitet grundsätzlich mit Registern." Nein. Das ist alles 
nicht das Thema.

Es gibt ein Array, das von der Größe so Richtung 1/10 SRAM geht. Also 
gross genug, dass es sinnlos ist, in Registern zu denken, und noch klein 
genug, dass es schön wäre, eine Kopie zu vermeiden, aber nicht absolut, 
um jeden Preis notwendig ist.

Das Problem ist so elementar, dass es garantiert ziemlich tief im 
Quelltext-Berg verschwindet und irgendwann einmal, wenn vielleicht alle 
µC auf irgendeinem SPARC18-Kern basieren, garantiert nicht mehr als 
Fehlerursache vermutet wird. Oder alternativ schon nächste Woche wieder 
gelöscht sein.

Also suche ich die 
Jetzt-und-dann-Ruhe-für-immer-richtige™-Vorgehensweise.

von A. (Gast)


Lesenswert?

Es gibt hier kein falsch oder richtig. Viele Wege führen nach Rom. Über 
die Randbedingungen wissen wir hier ja nichts.

Du kannst das Ding kopieren und dabei gleich richtig formatieren. Du 
kannst auch das bestehende Array formatieren und überschreiben. Du 
kannst auch eine Funktion nehmen um die bestehenden Daten formatiert 
auszulesen, auch ok. Auch kannst du gar nichts machen und das Array als 
8bit interpretieren (hier musst du gar nichts rechnen, nur müssen deine 
restlichen programmcodes wissen, dass es big endian ist).

Was du machst liegt doch in deiner Entscheidung? Ich (also .. ich) würde 
den Weg gehen, der am wenigsten Rechenzeit verbraucht, also entweder 
einmal formatieren oder die Daten einfach so lassen und bei jedem 
zugriff formatieren (wenn es die Rechenleistung zulässt).

Was du machst, das liegt bei dir..

von Franz D. (h4z3l)


Lesenswert?

Vorschlag:
1
union array {
2
     uint16_t all;
3
     uint8_t u8[2];
4
}
5
6
union array var1[] = {0x1234, 0x5678, 0x9ABC};

von Type Pun Intended (Gast)


Lesenswert?

Dirk B. schrieb:
> Das ist die standardkonforme und kompatible Vorgehensweise.
> Moderne Compiler erkennen das und machen daraus dann auch ein
> Assembler-Byteswap, wenn es nötig ist.

Danke für die Info. Ich erwarte allerdings irgendwelchen unterwarteten 
Ärger, wenn ich die Ersetzung In-Place vornehme:
1
    // Existiert schon:
2
    uint16_t var1[] = {0x1234, 0x5678, 0x9ABC};
3
4
    // Konvertierung
5
    size_t len = sizeof(var1);
6
    uint8_t *var2 = var1;
7
    for( size_t i = 0; i<len; i+=2 ) {
8
         uint16_t tmp = var1[i/2];
9
         var2[i]   = tmp>>8;
10
         var2[i+1] = tmp & 0xFF;
11
    }
12
13
    // Nutzung
14
    put8(var2, len);
Ich verletze an der Stelle ja die Strict-Alias-Regel. Würde ich das 
Ganze nur über eine Variable machen, um die Regel nicht zu verletzten 
also so:
1
    // Existiert schon:
2
    uint16_t var1[] = {0x1234, 0x5678, 0x9ABC};
3
4
    // Konvertierung
5
    size_t len = sizeof(var1);
6
    bool byteswap = // Ausdruck, der zur Compilezeit berechnet wuerde, waere toll
7
    if( byteswap ) {
8
       for( size_t i = 0; i<len/n; i++) {
9
            uint16_t tmp = var1[i];
10
            var1[i]   = (tmp>>8) | (tmp<<8) ;
11
        }
12
    }
13
14
15
    // Nutzung
16
    put8(var2, len);
bräuchte ich ja irgendeinen Ausdruck, mit dem ich feststellen kann, ob 
die Vertauschung überhaupt notwendig ist. Am besten irgendetwas, was 
schon vom Compiler ausgewertet werden kann.

von A. (Gast)


Lesenswert?

Type Pun Intended schrieb:
> Danke für die Info. Ich erwarte allerdings irgendwelchen unterwarteten
> Ärger, wenn ich die Ersetzung In-Place vornehme:

Warum? Interrupts, die zwischendurch auf die Daten zugreifen wollen?

von Markus F. (mfro)


Lesenswert?

Vielleicht habe ich ja das Problem noch nicht verstanden.

ist es

- ein int16_t-Array von (irgendeiner) byte order in ein int16_t-Array 
mit Host-Byteorder
- ein int16_t-Array von Network-Byteorder in ein int16_t-Array mit 
Host-Byteorder
- ein int16_t-Array in ein int8_t-Array

zu wandeln? Oder nochmal was anderes.

Oder in anderen Worten: wo kommen die Daten her und was willst Du damit 
machen?

von Markus F. (mfro)


Lesenswert?

Type Pun Intended schrieb:
> bräuchte ich ja irgendeinen Ausdruck, mit dem ich feststellen kann, ob
> die Vertauschung überhaupt notwendig ist. Am besten irgendetwas, was
> schon vom Compiler ausgewertet werden kann.

Das ist simpel:
1
static inline bool host_is_be(void)
2
{
3
    const uint16_t check = 1;
4
5
    if ( ((uint8_t *) (&check))[1] == 1)
6
        return true;
7
    return false;
8
}

Daraus macht der Compiler bei Optimierung einfach ein true oder false, 
je nach Host-Plattform.

von Type Pun Intended (Gast)


Lesenswert?

Markus F. schrieb:
> Das ist simpel:

Danke! Habe den Baum vor lauter Ästen nicht gesehen. Dann ist mein 
Problem gelöst.
1
void msbFirst16(uint16_t *array, size_t len)
2
{
3
    // Byte-Order feststellen
4
    const uint16_t check = 1;
5
    bool lsbfirst = ((uint8_t *) (&check))[1] == 1;
6
7
    // Bei little-endian Byte-Order vertauschen
8
    if( lsbfirst ) {
9
       for( size_t i = 0; i<len; i++ ) {
10
            uint16_t tmp = *array;
11
            *array   = (tmp>>8) | (tmp<<8) ;
12
            array++;
13
        }
14
    }
15
}
16
17
void foo(void) 
18
{
19
    // Existiert schon:
20
    uint16_t var1[] = {0x1234, 0x5678, 0x9ABC};
21
22
    // Konvertierung
23
    size_t len = sizeof(var1)/sizeof(uint16_t);
24
    msbFirst16(var1, len);
25
26
    // Nutzung
27
    put8((uint8_t*) var1, len/2);
28
}

von A. (Gast)


Lesenswert?

Wenn du von Anfang nur wissen wolltest, ob eine Konvertierung notwendig 
ist, warum fragst du dann nicht direkt nach so einer Funktion?

1. Ist Konvertierung notwendig?
2. Dann konvertieren.

Letztendlich hast du dich dann jetzt für ein swap entschieden, also die 
bestehenden Daten zu konvertieren - falls dies notwendig ist.

Was war die ursprungsfrage nochmal?

von Type Pun Intended (Gast)


Lesenswert?

Type Pun Intended schrieb:
> bool lsbfirst = ((uint8_t *) (&check))[1] == 1;

Fehler meinerseits:
1
bool lsbfirst = ((uint8_t *) (&check))[0] == 1;
ist richtig.

von Axel S. (a-za-z0-9)


Lesenswert?

Type Pun Intended schrieb:
> ... das ist genau nicht gewünscht. Eine Zugriffsfunktion im
> Pilgerschritt wird mich spätestens bei der übernächsten Codeänderung
> verwirren.

Dann verkapsele sie halt hypsch.
1
uint16_t var1[] = {0x1234, 0x5678, 0x9ABC};
2
3
#ifdef PLATFORM_IS_LITTLE_ENDIAN
4
#define var1_as_byte(i) ((uint8_t *)var1)[(i)^1]
5
#else
6
#define var1_as_byte(i) ((uint8_t *)var1)[(i)]
7
#endif
8
9
for (size_t i=0, j=0; j<sizeof(var1)/sizeof(uint16_t); i+=2, j++ )
10
{
11
   printf("Wort: %x, 1. Byte: %x, 2. Byte: %x\n", 
12
          var1[j], var1_as_byte(i), var1_as_byte(i+1));
13
}


Nachtrag:
1
/tmp $cat test.c 
2
#include <stdio.h>
3
#include <stdint.h>
4
5
#define as_byte(i) ((uint8_t *)var1)[(i)^1]
6
7
int main(void)
8
{
9
  uint16_t var1[] = {0x1234, 0x5678, 0x9ABC};
10
  for (size_t i=0, j=0; j<sizeof(var1)/sizeof(uint16_t); i+=2, j++ )
11
  {
12
    printf("Wort: %x, 1. Byte: %x, 2. Byte: %x\n", 
13
           var1[j], as_byte(i), as_byte(i+1));
14
  }  
15
}
16
17
/tmp $gcc -std=gnu99 -Wall test.c 
18
/tmp $./a.out 
19
Wort: 1234, 1. Byte: 12, 2. Byte: 34
20
Wort: 5678, 1. Byte: 56, 2. Byte: 78
21
Wort: 9abc, 1. Byte: 9a, 2. Byte: bc

: Bearbeitet durch User
von Dirk B. (dirkb2)


Lesenswert?

Bei der Längenermittlung von einem Array, dividiert man nicht durch die 
Größe vom Typ, sondern durch die Größe eines Elementes vom Array.

sizeof(var1)/sizeof(var1[0]);
oder
sizeof(var1)/sizeof(*var1);

Das kann man dann auch als Makro machen
1
#define SIZEOFARRAY(array) (sizeof(array)/sizeof(*array))

Dann braucht man den Typ nur bei der Definition ändern.

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.