Forum: Mikrocontroller und Digitale Elektronik Bitdekodierung aus Bytepuffer


von Mike (Gast)


Lesenswert?

Hallo,

Ich habe einen Eingangspuffer von 15Bytes. Die Informationen sind 
bitkodiert in den 15Bytes enthalten. Sie wurden dabei bestmöglich 
zusammengepackt.
2 Bit Status
6 Bit Modus
1 Bit Zustand
12 Bit Adresse
....
bis 15*8=120Bits

Ich bekomme einen Pointer auf das erste Byte des Puffers und möchte alle 
Informationen nach dem dekodieren in verschiedenen Variablen speichern.
int16_t status
int16_t modus
int16_t zustand
int16_t adresse
....

Das ist jetzt nicht sonderlich schwer aber ich verfalle immer in wildes 
Bitgeschubse und der Code wird sehr unübersichtlich. Auf den 
Eingangspuffer habe ich keinen Einfluss bezüglich Reihenfolge der 
Informationen.

Gibt es für sowas elegante Lösungen?

Gruß
Mike

von Hmmm (Gast)


Lesenswert?

Mike schrieb:
> Gibt es für sowas elegante Lösungen?

Es läuft immer auf Bitwise AND und Bitschieberei hinaus, Du kannst nur 
versuchen, es zu optimieren und halbwegs lesbar zu schreiben.

status = buffer[0] >> 6; /* die obersten beiden Bits, daher kein AND */
modus = buffer[0] & 0x3f; /* die unteren 6 Bits, daher keine 
Bitschieberei */

Wenn die fraglichen Bits mittendrin sind, evtl. zuerst Bitschieberei, 
dann AND, damit Du auf den ersten Blick die Nummer des niederwertigsten 
Bits siehst:

foo = (buffer[1] >> 4) & 0x03; /* foo liegt in Bit 4 und 5 */

von Peter D. (peda)


Lesenswert?

Mike schrieb:
> der Code wird sehr unübersichtlich.

So ist das eben, wenn man sich vorher keine Gedanken macht. Ich hätte 
die 21 Bits auf Pakete zu 24 oder 32 Bits erweitert.

von Einer K. (Gast)


Lesenswert?

Mike schrieb:
> aber ich verfalle immer in wildes
> Bitgeschubse und der Code wird sehr unübersichtlich
Bitfields?
Dann schubst der Kompiler für dich.
1
struct MeinFeld 
2
{
3
 int16_t Status:2;
4
 int16_t Modus:6;
5
 int16_t Zustand:1;
6
 int16_t Adresse:12;
7
// ----
8
};

von Mike (Gast)


Lesenswert?

Ich kopiere mir immer eine Anzahl von Bytes in eine Variable und 
maskiere das dann mit Makros. Das ist denke ich schon ganz gut aber ich 
finde es total unübersichtlich. Hin und wieder wird das Protokoll 
geändert und ich fange dann jedes Mal von vorne an die Bits abzuzählen 
und probiere so lange bis es funktioniert und das nervt. Durch das 
memcpy dreht man dann zusätzlich noch um es maximal unwartbar zu machen.

grobes Beispiel

#define STATUS_MASK(x)  ((0x18000000 & x) >> 27)
#define MODE_MASK(x)  ((0x07E00000 & x) >> 21)

memcpy(&data,buffer,4); //4Bytes aus dem Puffer kopieren
int32_t status = STATUS_MASK(data);

von Mike (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Bitfields?
> Dann schubst der Kompiler für dich.
> struct MeinFeld
> {
>  int16_t Status:2;
>  int16_t Modus:6;
>  int16_t Zustand:1;
>  int16_t Adresse:12;
> // ----
> };

Das klingt interessant, kannst Du vielleicht ganz kurz erläutern was da 
passiert, ich habe es auf die schnelle nicht verstanden.

von Bitfelder (Gast)


Lesenswert?


von A. S. (Gast)


Lesenswert?

Wenn es nicht kompatibel sein muss:


Schaue, ob es mit einer Bitstruktur und einem 16/32-Bit typen 
einigermaßen passt. Notfalls halt die 2 oder 3 Stukturen, die über die 
Grenzen ragen, per SonderCode aufdröseln.

Überlege Dir ine Struktur mit {ptr, startbit, bitsize}, die Du füllst 
und dann per kleinem code abarbeitest. So in der Art für uint8 data[15] 
und low byte first:
1
uint8 data[20]; /* davon die ersten 15 gefüllt */
2
struct sDsc {int16 *z; int s; int n;};
3
struct sDsc Dsc[]={{&status, 0,2}, ...,{0,0,0}}; /* Null-terminiert */
4
struct sDsc *p;
5
6
  for(p=Dsc; p->z; p++)
7
  {    
8
  signed long i;
9
     
10
      memcpy(i, data + p->s/8, sizeof(i));
11
      i<<=sizeof(i)-p->n-p->s%8;
12
      i>>=sizeof(i)-p->n;
13
      p->z=(int16) i;
14
  }

Nur für int16 als Ziel und wohl weder lauffähig noch kompatibel sondern 
so runtergetippt. Ich bin mir aber sicher, dass man das in wenigen 
Minuten auch richtig machen kann.

von leo (Gast)


Lesenswert?

Mike schrieb:
> Arduino Fanboy D. schrieb:
>> Bitfields?
...
>
> Das klingt interessant, kannst Du vielleicht ganz kurz erläutern was da
> passiert, ich habe es auf die schnelle nicht verstanden.

Dazu sollte man aber anmerken, dass das "implementation dependent" ist. 
D.h. bei einem Compilerwechsel (auch Version) kann das schon mal nicht 
mehr funktionieren.

leo

von Endianer (Gast)


Lesenswert?

Arduino Fanboy D. schrieb:
> Bitfields?
> Dann schubst der Kompiler für dich.
Ich wusste es gleich als ich die Überschrift dieses Threads gelesen 
habe...
Irgendjemand kommt wieder mit Bitfeldern um die Ecke, ohne sie genauer 
zu erklären.

Bitfelder sind auf den ersten Blick reizvoll und der Code ist auch 
absolut aufgeräumt. ABER: Bitfelder sind abhängig vom Alignment des 
Prozessors.
Das beudeutet: Wenn dein Decoder auch auf anderen Prozessoren laufen 
soll oder generell mal der Prozessor gewechselt wird funktioniert es 
u.U. nicht mehr. Bitfelder sind also nicht portabel.

Was steht denn dazu in den nicht-funktionalen Anforderungen für dein 
System?

von Philipp K. (philipp_k59)


Lesenswert?

Man könnte jedes Datenfeld in einer Schleife in ein 32bit Integer 
verschieben und dann etwas übersichtlicher maskieren.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Serialisierung
https://github.com/Erlkoenig90/uSer kapselt die Bit-Operationen 
komfortabel aber standard-komform & portabel.

von N. M. (mani)


Lesenswert?

Niklas G. schrieb:
> Serialisierung
> https://github.com/Erlkoenig90/uSer kapselt die Bit-Operationen
> komfortabel aber standard-komform & portabel.

Deine Lib in allen Ehren... Aber du empfiehlst jetzt nicht einem 
Anfänger der wahrscheinlich einen Mini uC hat, der noch nicht Mal 
Bitfelder kennt den Einsatz dieser Lib, oder?
Gibt es überhaupt einen Cpp17 Compiler für seinen uC?
Hat er Datenkonstrukte mit denen die Lib umgehen kann?
Du schreibst auf der GitHub Seite selbst dass es Einschränkungen gibt.

Meine Meinung dazu an den TO:
Verwende wie oben geschrieben Bitfelder.
Macht es für meinen Geschmack übersichtlicher als Bitschieberei.
Bei komplexen Strukturen achte darauf ob du packed benötigst.

SOLLTEST du wirklich Mal in die Bedrängnis kommen einen anderen Compiler 
verwenden zu müssen (bei allen uC die ich seither verwendet habe gab es 
immer einen Gcc und auf allen laufen meine Bitfelder ohne Anpassungen), 
mach einen Error rein dass das Alignement nicht stimmt oder dass es 
nicht der richtige Compiler ist. Im schlimmsten Fall muss man die 
Strukturen und Bitfelder dann einmal drehen und Plattformabhängig 
einbinden.

Edit: Typo

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

N. M. schrieb:
> Deine Lib in allen Ehren... Aber du empfiehlst jetzt nicht einem
> Anfänger der wahrscheinlich einen Mini uC hat, der noch nicht Mal
> Bitfelder kennt den Einsatz dieser Lib, oder?

Warum nicht? Sie ist gut dokumentiert. Wer sagt dass er Anfänger ist?

N. M. schrieb:
> Gibt es überhaupt einen Cpp17 Compiler für seinen uC?

Er hat den µC nicht genannt.

N. M. schrieb:
> Hat er Datenkonstrukte mit denen die Lib umgehen kann?

Die Lib kann mit einer ganzen Reihe Konstrukte umgehen, inklusive den im 
Ausgangspost genannten.

N. M. schrieb:
> Du schreibst auf der GitHub Seite selbst dass es Einschränkungen gibt.

Logisch. Aber aus dem Eingangs-Post sind diese nicht ersichtlich. Soll 
ich erst langwierig die genauen Umstände erfragen um dann die Library zu 
nennen, oder vielleicht einfach den Link posten sodass der TO selbst die 
Eignung prüfen kann?

N. M. schrieb:
> bei allen uC die ich seither verwendet habe gab es
> immer einen Gcc

Wenn es einen GCC gibt, gibt es mit hoher Wahrscheinlichkeit auch C++17.

von TotoMitHarry (Gast)


Lesenswert?

So könnte man vorsortieren.. allerdings ist das aus dem Kopf, soll es 
nur veranschaulichen.. Bitshifting ist lange her, müsste selbst erst 
googeln wie es richtig gemacht wird :D
1
int datatemp[10];
2
3
while (i < sizeof(datatp)) {
4
datatemp[cnt] = int((unsigned char)(datapt++) << 24 |
5
            (unsigned char)(datapt++) << 16 |
6
            (unsigned char)(datapt++) << 8 |
7
            (unsigned char)(datatp));
8
            int check=test*8%21;
9
            if (test*8%21!=0){
10
            cnt++;
11
            datatemp[cnt](unsigned char)(datatp++)<< check);
12
            }else cnt++;
13
}

von Einer K. (Gast)


Lesenswert?

Niklas G. schrieb:
> Wenn es einen GCC gibt, gibt es mit hoher Wahrscheinlichkeit auch C++17.
Es wird eingangs ja noch nicht mal eine Sprache genannt.

Wenn ich mir gefallen lassen muss, dass Bitfields problematisch sein 
können, dann musst du den Einlauf einstecken, dass dein Zeugs weder in 
C, noch auf AVR mit C++17 funktioniert.

von Niklas G. (erlkoenig) Benutzerseite


Lesenswert?

Arduino Fanboy D. schrieb:
> Wenn ich mir gefallen lassen muss, dass Bitfields problematisch sein
> können, dann musst du die Kröte schlucken, dass dein Zeugs weder in C,
> noch auf AVR mit C++17 funktioniert.

Es lässt sich in C-Projekte integrieren, wenn man eine Datei mit C++ 
kompilieren kann. Aber ja, auf AVR funktioniert es nicht, weil der 
AVR-GCC keine Standard-Bibliothek mitliefert. Man könnte sich einiges 
selber basteln aber das macht wenig Spaß. Aber ob es hier überhaupt um 
AVR geht...

von A. S. (Gast)


Lesenswert?

Hallo Mike,

kannst du denn schon nähere Einschränkungen zur Kodierung machen?

>  2 Bit Status
>  6 Bit Modus
>  1 Bit Zustand
> 12 Bit Adresse

little endian? Also wenn der Status 1 ist (und alles andere 0), ist das 
erste Byte 1? Wenn Adresse 1 ist, (alles andere 0), ist das zweite Byte 
2?

Signed oder unsigned oder variabel? (Status 0..3 oder -2..1)

> int16_t status
> int16_t modus
nur int16_t oder beliebigee Zeile? (float, int, unsigned, 16 oder 32 
Bit)

Mike schrieb:
> Gibt es für sowas elegante Lösungen?
Was hälst du denn von dem Code oben? Also der Beschreibung als
 {Variable, Bit-Anfang, Bit-Count}

A. S. schrieb:
> Nur für int16 als Ziel und wohl weder lauffähig noch kompatibel sondern
> so runtergetippt. Ich bin mir aber sicher, dass man das in wenigen
> Minuten auch richtig machen kann.

Wenn Du die Fragen beantwortest, kann ich das gerne testen und anpassen.

von N. M. (mani)


Lesenswert?

Niklas G. schrieb:
> Warum nicht? Sie ist gut dokumentiert.

Das bezweifle ich nicht. Und ehrlich gesagt schaue ich mir die auch 
selbst Mal an ob ich da was gebrauchen kann.

Niklas G. schrieb:
> Wer sagt dass er Anfänger ist?

Wenn jemand die einfachen Dinge wie Bitfelder nicht kennt, sorry aber 
dann kennt sich derjenige in der Sprache einfach nicht aus. Und genau 
das sehe ich dann auch das Problem. Da fehlen Grundlagen.

Niklas G. schrieb:
> Soll ich erst langwierig die genauen Umstände erfragen

Nö, garantiert nicht. Davon gibt es schon genug im Forum.

Niklas G. schrieb:
> oder vielleicht einfach den Link posten sodass der TO selbst die Eignung
> prüfen kann

Wenn das der TO kann, ja. Wenn er aber noch nochmal ein Bitfeld kennt 
Stelle ich in Frage ob er das selbst kann.

Niklas G. schrieb:
> Wenn es einen GCC gibt, gibt es mit hoher Wahrscheinlichkeit auch C++17.

Das stimmt leider nicht immer. Oft gibt es nur einen 11er. Wenn 
überhaupt.

von W.S. (Gast)


Lesenswert?

Mike schrieb:
> Gibt es für sowas elegante Lösungen?

Ja.
Fasse all deine Bytes in deinem Feld auf als teile eines gemeinsamen 
Bitstreams. Also bei dir:
Bits 0..7 in Byte 0,
Bits 8..15 in Byte 1,
usw.

und nun schreibst du dir eine Funktion zum Holen der nächsten n Bits. 
Fertig. Ich hatte mir sowas vor Zeiten mal als Teil einer 
Dekodierroutine für gepackte Bilder am µC geschrieben.

Hier mal sowas aus dem Stegreif (ohne Gewähr):
1
long  BitPos;  // wieviel noch im BitBuf steht
2
long  BitBuf;  // der Schiebepuffer
3
byte* MapBuf;  // zeiger auf die empfangenen Bytes
4
5
// Vorbereitung nach Empfang:
6
    BitPos  = 0;
7
    BitBuf  = 0;
8
9
10
word PxGet (int shift)
11
{ long Q, mask;
12
  word result;
13
14
  mask = (1<<shift) - 1;
15
  if (BitPos < shift)      
16
  { Q = *MapBuf++;
17
    BitBuf = BitBuf | (Q<<BitPos);
18
    BitPos += 8;     // ist hier ja immer byteweise
19
  }
20
  result = BitBuf & mask;
21
  BitBuf = BitBuf >> shift;
22
  BitPos -= shift;
23
  return result;
24
}

So etwa. Du fängst an bei den niedrigsten Bitpositionen:
// für 2 Bit Status
 Status = PxGet(2);
// dann 6 Bit Modus
 Modus = PxGet(6);
// und so weiter
 Zustand = PxGet(1);
 Adresse = Pxget(12);
etc.

So, hoffe mich nicht verschrieben zu haben.

W.S.

von TotoMitHarry (Gast)


Lesenswert?

Mein vorheriges Beispiel war etwas komisch..

Ich finde so könnte man das machen.. ist übersichtlicher finde ich, auf 
das Bitshifting keine Gewähr :D.

[c]
// Example program
#include <iostream>
#include <string>
#include <cstring>
#include <bitset>
int
main ()
{
  int i = 0;
  unsigned int datatemp[11];
  unsigned int data[] =
    { 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 
0x00000000,
0xFFFFFFFF, 0x00000000, 0xFFFFFFFF, 0x00000000 };
  unsigned int *pdata = data;
  char buffer[40];
  char *pbuffer = buffer;
  memcpy (buffer, pdata, 40);
  unsigned int status[10];
  unsigned int modus[10];
  while (i < 10)
    {

      int start = 15 * i / 8;
      int shiftalign = 15 * i % 8;

      std::memcpy (&datatemp[i], buffer + start, 4);
      datatemp[i] = (datatemp[i] >> (shiftalign));
      std::string name;
      unsigned int dtemp = datatemp[i];
      status[i] = (dtemp & 3);//erste 2 bits
      modus[i] = (modus[i]>>2);//status wegschieben
      modus[i] =(dtemp&63);//Jetzt erste 6bits kopieren
      std::cout << shiftalign << std::endl;
      std::cout << std::bitset < 32 > ((unsigned int) status[i]) << 
std::endl;
      std::cout << std::bitset < 32 > ((unsigned int) modus[i]) << 
std::endl;
      i++;      //
    }
}

[c]

von W.S. (Gast)


Lesenswert?

TotoMitHarry schrieb:
> ist übersichtlicher finde ich

So? Jetzt müßtest du bloß noch erklären, wozu das Ganze gut sein soll.

W.S.

von A. S. (Gast)


Lesenswert?

W.S. schrieb:
> TotoMitHarry schrieb:
>> ist übersichtlicher finde ich
>
> So? Jetzt müßtest du bloß noch erklären, wozu das Ganze gut sein soll.

hat er doch. Übung zum Bitschiften:

TotoMitHarry schrieb:
> Bitshifting ist lange her, müsste selbst erst
> googeln wie es richtig gemacht wird

Mit der Aufgabe des TO hat es wenig gemeinsam, nur 3 Bitfelder sind frei 
daran angelehnt.

Beitrag #6409363 wurde von einem Moderator gelöscht.
Beitrag #6409374 wurde von einem Moderator gelöscht.
von Mark B. (markbrandis)


Lesenswert?

Mike schrieb:
> 2 Bit Status
> 6 Bit Modus
> 1 Bit Zustand
> 12 Bit Adresse
> ....
> int16_t status
> int16_t modus
> int16_t zustand
> int16_t adresse

Welchen Sinn soll es denn haben, für Status und Zustand jeweils 
Variablen mit einer Größe von 16 Bit vorzusehen, wenn die bei weitem 
nicht benötigt werden?

Und warum um alles in der Welt vorzeichenbehaftete Variablen? 🤔

von A. S. (Gast)


Lesenswert?

Mark B. schrieb:
> Welchen Sinn soll es denn haben, für Status und Zustand jeweils
> Variablen mit einer Größe von 16 Bit vorzusehen, wenn die bei weitem
> nicht benötigt werden?

weil RAM billig und oft auch alligned ist und eine Sonderbehandlung (8 
oder 16 Bit je nach Anzahl der Bits) schlecht wartbar.

> Und warum um alles in der Welt vorzeichenbehaftete Variablen? 🤔
vielleicht weil es gebraucht wird? wenn alles unsigned ist, ist es 
einfacher.

von Mikro 7. (mikro77)


Lesenswert?

W.S. schrieb:
> So etwa. Du fängst an bei den niedrigsten Bitpositionen:
> // für 2 Bit Status
>  Status = PxGet(2);
> // dann 6 Bit Modus
>  Modus = PxGet(6);
> // und so weiter
>  Zustand = PxGet(1);
>  Adresse = Pxget(12);

Gefällt mir am besten von den hier geposteten Antworten.

Es ist übersichtlich, dokumentiert sich quasi selbst und fängt Fehler ab 
(wenn es bei/nach der Deserialisierung zu einem Over/Underrun kommt).

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.