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
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)
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
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 ...
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.
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?
@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
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.
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.)
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
@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.
@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
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.
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
Ja. Geht doch. C ist gar nicht so schwer, bloß keine Angst davor haben. Auf dem richtigen Weg bist Du jetzt.
>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.
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.
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
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 ...
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.
@Rufus: Es ist die 2.Ausgabe! Es gibt auch ein Kapitel "Bitmanipulationen" freu Gruss Holger
@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 */ [..]
>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
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.