Forum: Compiler & IDEs Statusvariable als struct?


von Holger Gerwenat (Gast)


Lesenswert?

Hallo,

ich stehe vor folgendem Problem:

Ich lese 2 Ports eines AVR ein. Um das geschickt und platzsparend zu
machen lege ich die Werte in einer einzigen Variable ab. Jedes Bit der
Variable bildet dann ein Port-Pin ab (1 = Pinliegt auf High, 0 = Pin
liegt nach Masse).

->
uint16_t   INPUT=0;

for(;;)
  {
  uint16_t   temp=0;

  temp=PINA;
  temp+=(PINB<<8);
  INPUT=temp;
  }

Nu muss ich die Werte seriell weiterverarbeiten und komme nicht so
richtig an z.B. INPUT(Bit9) ran.

Hab ich also hier im Forum gelesen und gefunden: Stausvariablen legt
man geschickt als Bitfeld über "struct" an. Gesagt, getan:

->

volatile struct {
unsigned char BIT_1:1; // Feld jeweils ein Bit gross
unsigned char BIT_2:1;.
               .
               .
unsigned char BIT_16:1; // 1 Bit für Bit_16
} INPUT;

jetzt kann ich mit dem Punkt-Operator ganz einfach auf jedes Bit
zugreifen und brauche auch wenig Platz für die Variable.

Aber: Wie beschreibe ich denn diesen struct jetzt am besten mit meinem

parallel aliegenden Daten? INPUT=temp; geht ja wohl mehr.

Wie progammiert man (ich) sowas am geschicktesten?

Ich hoffe mein Problem verständlich geschildert zu haben und würde mich
freuen, wenn ihr mir die Richtung weisen könntet.

Danke!

Gruss Holger

von Rufus T. Firefly (Gast)


Lesenswert?

Auf "saubere" (also portierbare) Art und Weise ist ein Beschreiben
mehrerer Bits eines Bitfeldes nicht möglich, da die Behandlung von
Bitfeldern implementationsabhängig (sprich: von Compiler zu Compiler,
von Zielsystem zu Zielsystem unterschiedlich) ist.

Eine ganz und gar nicht portierbare Art der Zuweisung eines
16-Bit-Wertes an ein Bitfeld (mit 16 Bits) sieht so aus:

  int Wert;
  struct
  {
     int Bit0:1;
     int Bit1:1;
     // wie das hier weitergeht, sollte offensichtlich sein
     int Bit15:1;
  } Bitfeld;

  *((int *) ((void *) &Bitfeld)) = Wert;

Das aber sollte man wirklich erst dann anwenden, wenn man GANZ GENAU
weiß, was da passiert.

Mir ist allerdings nicht ganz klar, warum Du überhaupt so vorgehen
willst - speichere doch einfach die beiden 8-Bit-Werte Deiner beiden
8-Bit-Ports in jeweils einem Byte und greife über entsprechende
Bitoperationen auf diese zu.

Statt

   if (Bitfeld.Bit2 && Bitfeld.Bit14)

musst Du halt schreiben

   if ((PortByte0 & 2) && (PortByte1 & 0x40))

(angenommen, daß die beiden Ports in Byte-Variablen namens PortByte0/1
abgelegt sind)

Alternativ kannst Du auch eine 16-Bit-Variable verwenden, allerdings
musst Du beim Übertragen der aus den Ports gelesenen Bytes in diese Dir
Gedanken um die "endianness" Deines Prozessors machen, kannst dann
aber auch schreiben:

   if ((PortInt & 0x4002) == 0x4002)

von mthomas (Gast)


Lesenswert?

Ob Stucts so wirklich die geschickteste Loesung sind, weiss ich nicht.
Alternativ eigentes Status-Register und vordefinierte Bitwerte. Das
finde ich uebersichtlicher, aber ist "Ansichtssache"

#define ZUSTAND1 (1<<0)
#define ZUSTAND2 (1<<1)
uint16_t myStatus;

Status setzen mit den ueblichen Bitoperationen. z.B.
setzen Zust. 1: myStatus |= ZUSTAND1;
löschen 2: myStatus &= ~(ZUSTAND2)
abfrage 1: if (myStatus | ZUSTAND1) ...

Das löst dann auch das "Problem von alleine", da der Gesamtstatus
weiterhin ein einfacher Datentyp ist. Die üblichen Bitmanipulationen
(siehe wiki Bitmanipulationen und avr-gcc-Tutorial) helfen dann auch
weiter bei Zugriff/Abfrage

von Edvaard Kraal (Gast)


Lesenswert?

Warum fahren hier eigentlich alle darauf ab, Bitkonstanten so

  #define ZUSTAND1 (1<<0)
  #define ZUSTAND2 (1<<1)
  #define ZUSTAND3 (1<<2)

statt

  #define ZUSTAND1 1
  #define ZUSTAND2 2
  #define ZUSTAND3 4


oder

  enum
  {
    ZUSTAND1 = 1,
    ZUSTAND2 = 2,
    ZUSTAND3 = 4,
  ... etc.
  }

zu definieren?

Diesem IMHO seltsamen Stil bin ich anderswo noch nie begegnet ...

von mthomas (Gast)


Lesenswert?

Weil man dann ohne langes Zweierpotenzdurchzaehlen weiss, welches Bit
gemeint ist. Ich find's uebersichtlicher und praktischer. Das ist wie
geschreiben Ansichtssache. Ich fahre auf jeden Fall auf nichts ab was
mit Programmierstil zu tun hat. Zum "drauf abfahren" gibt's was im
"echten Leben".
Enums sind nicht immer eine gute Wahl.

von Rufus T. Firefly (Gast)


Lesenswert?

Hmm. Der "Vorteil", die Zweierpotenzen doch durchzuzählen, ist der,
daß man nach einiger Übung mit einer Debuggerausgabe wie 0x43
unmittelbar was anfangen kann ... Die Schiebenummer ist bequemer,
zugegeben.

Ich habe, wenn ich mir hier so einige Postings durchlese, den Eindruck,
daß das "flüssige" Umgehen mit Zahlen in hexadezimaler Darstellung
etwas ist, was einigen Leuten hier fehlt, wie auch teilweise das
Verständnis dafür, daß 'A' == 0x41 == 0101 == B01000001 nur
unterschiedliche Repräsentationen exakt des gleichen Inhalts sind. Aber
das nur am Rande.

Mit so einem enum kann man alle Bits eines Registers beschreiben und
dem enum sogar noch einen sinnvollen Namen verpassen; wo findest Du
sowas unangebracht?

von Christian Zietz (Gast)


Lesenswert?

@Holger: C bietet sogenannte Unions, da liegen zwei unterschiedliche
Datentypen auf einer Speicheradresse, in Deinem Fall also das Bitfeld
und ein Char/Integer. Ich bin gerade zu faul, die genaue Syntax
rauszusuchen aber ein C-Buch sollte Dir mit dem Stichwort alles Nötige
verraten.

Christian

von Rufus T. Firefly (Gast)


Lesenswert?

Die Verwendug einer Union macht die Sache nicht portierbarer, aber -
zugegebenermaßen - handhabbarer.

union
{
  struct
  {
    unsigned char ErstesByte;
    unsigned char ZweitesByte;
  } ByteStruct,
  struct
  {
    unsigned short Bit0:1,
    unsigned short Bit1:1,
    // bla
    unsigned short Bit15:1
  } Bitfeld
} Variable;

Damit kann man schreiben:

  Variable.Bitfeld.Bit0 = 1;

und

  Variable.ByteStruct.ErstesByte = 0x41;

Allerdings ist die Anordnung der einzelnen Variablen
implementierungsabhängig - welche Bits des Bitfeldes mit welcher der
beiden "Byte"-Variablen korrespondieren, das ist durchaus nicht von
Compiler zu Compiler oder Zielsystem zu Zielsystem übertragbar.
Wenn sizeof (Variable) größer ist als zwei, dann spielt auch noch das
Alignment eine Rolle.

Sicher, in der Nutzung ist sowas praktisch, aber es ist ein
zweischneidiges Brotmesser. Weil's auf 'nem AVR geht, darf man nicht
daraus schließen, daß es auf 'nem x86-PC funktioniert, oder auf 'nem
ARM.

von Jörg Wunsch (Gast)


Lesenswert?

Nur nebenbei, auch wenn's für den GCC auf AVR eher irrelevant ist: die
Verwendung einer union macht den Code zwar in der Tat nicht
portierbarer, aber sie macht erstmal gültigen C-Code draus, von dem im
Prinzip sogar garantiert ist, dass das Beschreiben des entsprechenden
integer-Werts überhaupt auch wirklich die Bits des Bitfeldes ändert
(auch wenn es in der Tat implementierungsspezifisch ist, welche Bits
wie geändert werden).

Für das wilde Herum-casten von Zeigern hat der C-Standard nur ein
müdes Lächeln übrig.  Lediglich der Cast eines beliebigen Zeigers in
einen void * und von da wieder zurück in den originalen Zeiger ist
eine definierte Operation.  (In der Praxis hat das natürlich nur bei
sehr exotischen Maschinen eine Bedeutung.)

von Holger Gerwenat (Gast)


Lesenswert?

Hallo,

erstmal Danke für die Antworten. Obwohl ich leider überhaupt nicht
weiss, was "casten eines Zeigers in ein void*" überhaupt ist, noch wo
ich sowas nachlesen kann. Den Weg mit "union" werd ich mal probieren.

Eigentlich wollte ich nur folgendes programmieren:
12 Taster an 2 AVR Ports sollten abgefragt und entprellt werden.
(Variante ähnlich wie sie Peter Danegger hier schon vorgeschlagen hat.
Vier gültige Werte sind nötig, um eine Taste wirklich als "gedrückt"
zu akzeptieren.)
Der AVR sollte dann einfach nur an zwei anderen Ports die 1:1
zugeordneten Bits toggeln, also 1. Tastendruck auf "L" 2. Tastendruck
wieder auf"H".
Klingt ganz einfach und ich weiss eigentlich genau was ich mit jedem
einzelnen Bit machen möchte und warum.
Das mich das vor solche Implementierungs-(Sprach)Probleme stellt, hätt
ich nicht gedacht.

Eine Frage stellt sich mir jetzt allerdings doch noch:

Ich lege mir ein Bitfeld mit "struct" an. Der AVR  wird diese
Konstruktion ja irgendwo verwalten. Könnte ich jetzt nicht irgendwie
die Adresse rauskriegen und meine Variable dann dort hinschreiben?
Es geht ja nur darum einen 16Bit Wert an die richtige Stelle zu
schreiben. Geht das mit irgendeiner Form von Zeigern?

Gruss Holger

von Rufus T. Firefly (Gast)


Lesenswert?

@Holger: Deine Frage ist mit dem von mir beschriebenen und absolut nicht
portierbaren (und von Jörg als vom Standard mit müdem Lächeln bedachtes
Vorhaben) Pointercasten exakt so umgesetzt worden, wie auch mein erstes
Sourcebeispiel zeigte (erste Antwort in diesem Thread).

Lass' da aber besser die Finger von.

Ob es überhaupt sinnvoll ist, die Portzustände eines Ports irgendwo
abzuspeichern, wenn sie doch sowieso wieder an einen anderen Port
ausgegeben werden sollen, entzieht sich hier gänzlich meiner Kenntnis.
Auch, ob es denn überhaupt sinnvoll ist, derartigen Aufriss mit
Bitfeldern etc. zu veranstalten und nicht einfach für jeden Port ein
Byte zu verwenden und fertig.

Das aber zu entscheiden überlasse ich Dir.

von Holger Gerwenat (Gast)


Lesenswert?

@Rufus:
>   *((int *) ((void *) &Bitfeld)) = Wert;
Sorry, ich hab die Antwort einfach nicht verstanden -nur "Bahnhof".
>   Lass' da aber besser die Finger von.
Das ist ne Antwort, mit der ich was anfangen kann!

Eigentlich hast du Recht, warum soll ich so knapsen? Es sollte wirklich
genug Platz sein, um jeweils ein Byte zu verwenden.
Vielleicht sollte ich noch erklären, was ich eigentlich vor hatte:
Es gibt doch diese Stromstossrelais. Man drückt auf einen Taster und
das Licht geht an. Man drückt nochmal auf den Taster, plopp das Licht
ist aus. Oder man drückt auf den anderen Knopf im Flur - Das Licht
würde auch an und aus gehen. So ein Relais kostet 12-25 EUR im EK. Ist
ne Menge Geld wenn man mehrere braucht. Also dachte ich: AVR und
Optokoppler und Triacs -> fertig ist der Baustein. Kann ich doch später
noch Dimmfunktionen implementieren oder einen kompletten Bus ankoppeln
zur Fernsteuerung. Super Lichtschalter!!!
Nun müssen allerdings die Lichttaster unbedingt entprellt werden. Das
scheint allerdings als parallel-Verarbeitung am einfachsten zu gehen.
Peter Danegger hat es in der Codesammlung mal beschrieben. Also gleich
ALLE Eingänge mit einem Mal einlesen und entprellen. Nur zu diesem
toggeln, muss ich die Bits wieder auseinander klamüsern. Ich sag es
nochmal: hätte nicht gedacht, dass ich damit auf solche Probleme bei
der Umsetzung stosse.

NOCHMALS DANKE! für die Mühe die Ihr Euch mit mir gebt!

Gruss Holger

von Rufus T. Firefly (Gast)


Lesenswert?

Ob Du nun 16 Bits in einem Bitfeld oder in zwei Bytes unterbringst,
läuft vom Platzbedarf her auf exakt das gleiche heraus.

Der Zugriff auf ein einzelnes Bit in einem Byte ist nicht wirklich
kompliziert, weder beim Lesen, noch beim Schreiben; es ist schon fast
aufwendiger, sich zu merken, wie "klamüsern" geschrieben wird.

a) Bit setzen:

  Wert |= (1 << zusetzendebitnummer);

b) Bit löschen

  Wert &= ~(1 << zusetzendebitnummer);

c) Bit testen

  if (Wert & (1 << zutestendebitnummer)
    gesetzt
  else
    nicht gesetzt

Der Wertebereich von zusetzendebitnummer geht von 0 bis 7.

von Holger Gerwenat (Gast)


Lesenswert?

Warte mal Rufus,

dann geht ja z.B.

if (meine Inputvariable & (1 << 4))
    PORTC ^=(1<<4);

will sagen: jedesmal wenn PORTA,4 richtig erkannt(entprellt) ist, ist
             meine Variable,4 gesetzt -> dann drehe PORTC,4 um

So einfach ist das? Tut mir leid, bin eben ein C-Embryo! Eins hab ich
sowieso schon beschlossen: BOL.de hat mir einen 10.- Gutschein
geschickt da bestell ich mir jetzt einen Kernigam/Ritschi

Gruss Holger

von Rufus T. Firefly (Gast)


Lesenswert?

Ja. Geht doch.

C ist gar nicht so schwer, bloß keine Angst davor haben. Auf dem
richtigen Weg bist Du jetzt.

von Thomas (Gast)


Lesenswert?

>Lediglich der Cast eines beliebigen Zeigers in
>einen void * und von da wieder zurück in den originalen Zeiger ist
>eine definierte Operation.  (In der Praxis hat das natürlich nur bei
>sehr exotischen Maschinen eine Bedeutung.)

So exotisch sind Maschinen bei denen Probleme auftreten können gar
nicht. Nimm den folgenden Code:

unsigned int *intptr = (unsigned int *) irgendein_ptr_auf_char;
unsigned int wert = *intptr;

Dann geht mit nem ARM oder PPC ziemlich sicher irgendwann mal was
schief beim Lesen von *intptr, nämlich dann wenn irgendein_ptr_auf_char
nicht auf eine durch 4 teilbare Addresse zeigte. Diese Prozessoren
können halt 16 Bit Werte nur von geraden, 32 Bit Werte nur von durch 4
teilbaren Adressen lesen.

von Thomas (Gast)


Lesenswert?

Da kommt mir gerade noch so in den Sinn, das oben von mir gezeigte
Problem tritt warscheinlich bei mehr 16/32 Bit Prozessoren auf, als es
es nicht tut.

Die 80x86 sind da eher eine Ausnahme, bei denen sitzt zwischen der CPU
und dem Bus die Bus Interface Unit, welche nicht ausgerichtete 16/32
Bit Zugriffe in einzelne 8 Bit Zugriffe aufteilt. Ein nicht
ausgerichteter 32 Bit Lesezugriff auf einem 386 und aufwärts wird damit
in 4 einzelne Lesezugriffe aufgeteilt.

von Holger Gerwenat (Gast)


Lesenswert?

Nochmals Danke an Alle!

geht so wie ichs mir vorgestellt habe!

P.S. gerade mit DHL gekommen: "Programmieren in C mit Referenzmanual
in deutscher Sprache von Brian W.Kernighan und Dennis M.Ritchie"

Gruss Holger

von Rufus T. Firefly (Gast)


Lesenswert?

Na, viel Spaß beim Lesen.
Und die Daumen seien Dir gedrückt, daß es nicht ein antiquarisches
Exemplar der ersten Auflage (mitte der 80er Jahre) ist ...
die scheint maschinell übersetzt* zu sein und Source-Beispiele sind in
einer Proportionalschrift gesetzt (yuck!).
Auch beschreibt dieses Buch das gnadenlos veraltete K&R-C ohne
Prototypen und (fast) ohne Typüberprüfungen.

Die zweite Auflage (~ 1990) ist vorzüglich übersetzt, Source-Beispiele
sind, wie es sich gehört, in einer nichtproportional-Schrift gesetzt
und es wird ANSI-C (mit Prototypen und Typprüfung) beschrieben.


*) das war zwar mitte der 80er noch kaum vorstellbar, aber der Text ist
so grauenerregend ...

von thkais (Gast)


Lesenswert?

Ich möchte noch etwas zum Entprellen loswerden: Ich frage die Eingänge
in einem Interrupt mit ca. 20Hz - 50Hz (je nachdem, was als "Abfall"
bei einem Timer so herauskommt) ab. Die Prellzeiten sind definitiv
kürzer, also werden Tastendrücke einwandfrei erkannt.
Zwar könnte theoretisch ein Tastendruck nicht erkannt werden, wenn ich
exakt zwischen zwei Interrupt-Aufrufen den Taster drücke und wieder
loslasse - aber innerhalb von 20 ms kriegt man das nur selten hin.
Diese Methode funktioniert bei mir in der Praxis schon seit Jahren,
kein Bitgeschiebe oder abgefrage, das Ergebnis der Eingänge liegt
mundgerecht für das Hauptprogramm bereit.

von Holger Gerwenat (Gast)


Lesenswert?

@Rufus:

Es ist die 2.Ausgabe! Es gibt auch ein Kapitel "Bitmanipulationen"
freu

Gruss Holger

von OldBug (Gast)


Lesenswert?

@Thomas:
>>Lediglich der Cast eines beliebigen Zeigers in
>>einen void * und von da wieder zurück in den originalen Zeiger ist
>>eine definierte Operation.  (In der Praxis hat das natürlich nur bei
>>sehr exotischen Maschinen eine Bedeutung.)
>
>So exotisch sind Maschinen bei denen Probleme auftreten können gar
>nicht. Nimm den folgenden Code:
>
>unsigned int *intptr = (unsigned int *) irgendein_ptr_auf_char;
>unsigned int wert = *intptr;

Damit schiesst Du voll an der Aussage von Jörg vorbei!
Du machst eine Typwandlung von char nach int* und weist einem int den
Inhalt zu. Das war aber nicht das, was Jörg beschrieben hat:

int *iptr;
int val1;
int val2;
void *ptr;

[..]
  iptr = &val1;       /* iptr zeigt auf die Adr. von val1  */
  ptr = iptr;         /* ptr zeigt auf die Adr. von val1   */
  val2 = *(int *)ptr; /* kopiere Inhalt von val1 nach val2 */
[..]

von Thomas (Gast)


Lesenswert?

>Du machst eine Typwandlung von char nach int* und weist einem int den
>Inhalt zu. Das war aber nicht das, was Jörg beschrieben hat:

Darum gehts ja. Was ich mach, ist eine Typumwandlung, wie sie, wie Jörg
erwähnt hat, nicht definiert ist, mit dem Hinweis, dass solcher Code auf
mehr Maschinen in die Hose geht, als man zunächst mal ahnt.

Der Grund wieso ich vom int-Pointer lese ist der, dass durch den Cast
alleine (ist ja nur eine Zuweisung von einer Variable zur anderen) noch
nichts schiefgeht, aber wenn du danach auf den int-Pointer zugreifst,
und der ursprüngliche char-Pointer war misaligned, dann hast du auf
vielen Prozessoren ein Problem.

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.