Forum: Mikrocontroller und Digitale Elektronik Bit in einem Byte setzen/rücksetzen (schnell und möglichst direkt) in c


von Hannes (Gast)


Lesenswert?

Hi,

ich möchte ein Bit in einem Byte ansprechen. Nun liest man immer wieder 
das:
uint8_t bit=3;
uint8_t byte=1;

byte<<=bit;

in eine Schleife umgesetzt wird. Gibt es eine bessere Möglichkeit? Ich 
finde eine Tabelle eigentlich übertrieben - ist es wirklich die einzige 
Möglichkeit?

Hannes

von J.-u. G. (juwe)


Lesenswert?

Hannes schrieb:
> ich möchte ein Bit in einem Byte ansprechen. Nun liest man immer wieder
> das:
> uint8_t bit=3;
> uint8_t byte=1;
>
> byte<<=bit;

Wo liest man denn so etwas in diesem Zusammenhang? Mit dieser Operation 
wird kein Bit angesprochen, sondern der Inhalt der Variable Byte um 3 
Positionen nach links geschoben. Numerisch entspricht das einer 
Multiplikation von Byte mit 8.

Zum Thema Bitmanipulation:
http://www.mikrocontroller.net/articles/Bitmanipulation

von Matthias S. (Firma: matzetronics) (mschoeldgen)


Lesenswert?

1
// Bit setzen:
2
byte |= (1 << bit);
3
// Bit löschen:
4
byte &= ~(1 << bit);

von Max G. (l0wside) Benutzerseite


Lesenswert?

Und das packt man dann am besten in ein Makro BIT_SET() oder so ähnlich.

von Lothar M. (Firma: Titel) (lkmiller) (Moderator) Benutzerseite


Lesenswert?

Max G. schrieb:
> Und das packt man dann am besten in ein Makro BIT_SET() oder so ähnlich.
Warum nur?
BIT_SET wäre für mich eine geeignete /Abfrage/:
   if (BIT_SET(x,y)) {}
Wenn schon, dann sollte das Makro SET_BIT() heißen...

Und weil das Makro auch eine ganze Zeile Code braucht und auch nicht 
schneller ist, kommt es bei mir nicht in ein Makro. Dann sieht jeder, 
auch ohne die Makrodefinition zu suchen, sofort was da gemacht wird.

von Hannes (Gast)


Lesenswert?

Hi,

danke für die Rückmeldung. Ich habe einen Grafikspeicher:

uint8_t RAM[8];

nun möchte ich ein Bit darin setzen.

void grafik_setze_pixel (uint8_t x, uint8_t y)
{
    RAM[y&0x7]|=0x01<<(x&0x07);
}

Nun ist das ein variabler shift. Er wird doch jedes Mal in eine Schleife 
umgesetzt....oder?

Hannes

von UR-Schmitt (Gast)


Lesenswert?

Du hast ein Arry aus 8 Bytes und jedes Byte hat 8 Bit.
Was soll die Funktion genau tun, was ist die GENAUE! Bedeutung von x und 
y in deiner Funktion?

von Rolf Magnus (Gast)


Lesenswert?

Hannes schrieb:
> ich möchte ein Bit in einem Byte ansprechen. Nun liest man immer wieder
> das:
> uint8_t bit=3;
> uint8_t byte=1;
>
> byte<<=bit;
>
> in eine Schleife umgesetzt wird.

Das gilt normalerweise nur für Prozessoren ohne Barrel-Shifter, und auch 
dort nur, wenn das Ergebnis nicht bereits zur Compilezeit berechnet 
werden kann.

Lothar Miller schrieb:
> Max G. schrieb:
>> Und das packt man dann am besten in ein Makro BIT_SET() oder so ähnlich.
> Warum nur?
> BIT_SET wäre für mich eine geeignete /Abfrage/:
>    if (BIT_SET(x,y)) {}
> Wenn schon, dann sollte das Makro SET_BIT() heißen...

Ansichtssache. Ich arbeite gern mit einheitlichen Präfixen, daher 
bevorzuge ich es, wenn am Anfang des Namens steht, auf was sich die 
Funktion/das Marko bezieht (in dem Fall also ein Bit) und danach erst 
die Aktion kommt, die damit durchgeführt wird. Deshalb ist mir ein Name 
wie SET_BIT() eher ein Dorn im Auge. Die Abfrage sollte meines Erachtens 
eher BIT_IS_SET() heißt.

von Hannes (Gast)


Lesenswert?

...x und y sind die Koordinaten des Pixels. Jedes Bit steht für einen 
Pixel auf einem 8x8 Pixel großen Display.

Hannes

von Hannes (Gast)


Lesenswert?

>>Das gilt normalerweise nur für Prozessoren ohne Barrel-Shifter, und auch
>>dort nur, wenn das Ergebnis nicht bereits zur Compilezeit berechnet
>>werden kann.

Ich nutze einen Atmega168. Das Ergebnis steht nicht fest, ansonsten wäre 
es eine gute Lösung. Die Pixel werden aber erst zur Laufzeit berechnet. 
So wie ich das sehe, geht es nur mit einer Tabelle...

In meinem Original sind es zudem uint16_t Variablen...

Hannes

von Hannes (Gast)


Lesenswert?

Tabelle im Progmem:

uint8_t Tabelle[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

von Karl H. (kbuchegg)


Lesenswert?

Hannes schrieb:
>>>Das gilt normalerweise nur für Prozessoren ohne Barrel-Shifter, und auch
>>>dort nur, wenn das Ergebnis nicht bereits zur Compilezeit berechnet
>>>werden kann.
>
> Ich nutze einen Atmega168.

Also keinen Barrelshifter.

> So wie ich das sehe, geht es nur mit einer Tabelle...

Wäre vernünftig.

von J.-u. G. (juwe)


Lesenswert?

Hannes schrieb:
> In meinem Original sind es zudem uint16_t Variablen...

Wie groß ist denn Dein Display?

von Rolf Magnus (Gast)


Lesenswert?

Hannes schrieb:
> ...x und y sind die Koordinaten des Pixels. Jedes Bit steht für einen
> Pixel auf einem 8x8 Pixel großen Display.

Mußt du die Pixel denn beliebig kreuz und quer setzen können? Wenn du da 
z.B. eh immer in einer Schleife durchgehst, kann man sich ja 
Zwischenergebnisse des Shifts merken.
Wenn das nicht geht, wirst du wohl die Tabelle nutzen müssen.

> In meinem Original sind es zudem uint16_t Variablen...

Hast du dann nur vier davon, oder wie sind die angeordnet, wenn es nur 
8x8 Pixel mit je 1 Bit sind?

von Hannes (Gast)


Lesenswert?

...au man.

der 168 ist doch ein 8bitter, oder? Momentan sieht mein Ram etwa so aus:

uint16_t RAM[64];

Ich habe also eine Pixelfläche von 16x64 Pixeln und möchte dort mit zwei 
Funktionen möglichst effektiv, aber dennoch in c Pixel setzen und 
Rücksetzen...

Ich denke darüber nach, dies auf

uint8_t RAM[128] oder RAM[2][64] umzustellen und mit einer 8-wertigen 
Tabelle wie oben gezeigt zu arbeiten...
Die 16 Bit Werte werden doch auch wieder zusammengebastelt, oder?

Hannes

von M. K. (avr-frickler) Benutzerseite


Lesenswert?

Hannes schrieb:
> Tabelle im Progmem:
>
> uint8_t Tabelle[8]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

Schreib dir mal die Binäre Form der Zahlen in deinem Array auf. Dann 
wirst du sehen das in jeden Wert der Tabelle nur 1 Bit gesetzt ist und 
dieses wandern noch von rechts nach links (wenn das LSB ganz rechts 
steht).

Also kannst du die Tabelle durch eine Variable ersetzen die am Start 
deiner Schleife den Wert 1 hat und im jeden Durchlauf um 1 nach links 
geschoben wird.

von Hannes (Gast)


Lesenswert?

>>Wie groß ist denn Dein Display?

16x64 Pixel, hätte ich gleich sagen sollen...

>>Mußt du die Pixel denn beliebig kreuz und quer setzen können?

frei wählbar, es wird alles mögliche dargestellt.

>>Hast du dann nur vier davon

mein Original ist größer, wollte es vereinfachen, um es besser 
verständlich zu machen, war wohl ein Fehler :-)

Hannes

von Peter D. (peda)


Lesenswert?

Hannes schrieb:
> ...x und y sind die Koordinaten des Pixels. Jedes Bit steht für einen
> Pixel auf einem 8x8 Pixel großen Display.

Die Frage ist, ob Du wirklich die Pixel immer völlig durcheinander 
zugreifen mußt, oder das ein Teil einer Schleife über alle Pixel ist.

Im letzteren Fall kann man einfach auch eine Maske in jedem Durchlauf um 
1 weiter schieben.
Oder umgekehrt, man schiebt das Pixelbyte jeweils einmal, bis man alle 8 
Pixel zusammen hat.
Quasi wie bei einem Software-SPI.


Peter

von Hannes (Gast)


Lesenswert?

>>Also kannst du die Tabelle durch eine Variable ersetzen

das macht der Compiler doch schon für mich, das möchte ich ja vermeiden.

Wenn ich 64 Pixel in einer Linie setze, dann ist er nur noch am dhiften, 
für jeden Pixel aufs neue...

Hannes

von Karl H. (kbuchegg)


Lesenswert?

M. K. schrieb:

> Also kannst du die Tabelle durch eine Variable ersetzen die am Start
> deiner Schleife den Wert 1 hat und im jeden Durchlauf um 1 nach links
> geschoben wird.


Genau darum gehts ja: die Schleife durch eine Tabelle mit Indizierung zu 
ersetzen.

von Hannes (Gast)


Lesenswert?

>>einfach auch eine Maske

Ich schreibe konverteierte Serifen-Schriftarten aufs Panel, zeichne 
Kreise und Linien mit dem Bresenham...

Hannes

von Karl H. (kbuchegg)


Lesenswert?

> Ich finde eine Tabelle eigentlich übertrieben

Wart ab, bis du drauf kommst, dass du dir massiv Geschwindigkeit holen 
kannst, wenn du ausnutzt, dass in vielen Operationen nebeneinander 
liegende Pixel angesprochen werden und dieser Fall gar nicht so selten 
ist. Bei jeder waagrechten Linie bzw. bei jedem Rechteck malen kommt 
sowas vor. Und diese Fälle sind gar nicht so selten.

Der Gedanke, müsse nur eine möglichst schnelle SetPixel haben ist zwar 
naheliegend, aber Speed kriegt man mit anderen Mitteln.

von J.-u. G. (juwe)


Lesenswert?

Hannes schrieb:
> Ich schreibe konverteierte Serifen-Schriftarten aufs Panel

Und wie liegen die vor? Als Bitmap? Dann hast Du doch quasi schon ein 
Abbild des "Bildschirmspeichers" und es wäre unnötig jeden Pixel dieser 
Bitmap per Shift in Deinen Ausgabespeicher zu übertragen.

von Rolf Magnus (Gast)


Lesenswert?

Hannes schrieb:
> ...au man.
>
> der 168 ist doch ein 8bitter, oder?

Ja. Deshalb ja auch die Nachfrage. Eine 16Bit-Variable wird auf dem eher 
ineffizenter sein als zweimal 8 Bit, schon alleine, weil dann bei jedem 
Aufruf deiner Pixel-Funktion zwei Bytes aus dem RAM gelesen und wieder 
geschrieben werden müssen statt nur eins.

> Ich denke darüber nach, dies auf
>
> uint8_t RAM[128] oder RAM[2][64] umzustellen und mit einer 8-wertigen
> Tabelle wie oben gezeigt zu arbeiten...

Gute Idee.

> Die 16 Bit Werte werden doch auch wieder zusammengebastelt, oder?

Ja. Der Prozessor hat ja nur 8 Bit große Register.

Hannes schrieb:
>>>Also kannst du die Tabelle durch eine Variable ersetzen
>
> das macht der Compiler doch schon für mich, das möchte ich ja vermeiden.
>
> Wenn ich 64 Pixel in einer Linie setze, dann ist er nur noch am dhiften,
> für jeden Pixel aufs neue...

Nunja, genau da wäre der Punkt, wo es sich lohnen kann, sich 
Zwischenwerte zu merken. Du malst die Pixel der Linie ja nicht in 
zufälliger Reihenfolge, sondern einen nach dem anderen. Da mußt du ja 
immer nur dann, wenn du einen Pixel weitergehst, um ein Bit shiften.

Hannes schrieb:
>>>einfach auch eine Maske
>
> Ich schreibe konverteierte Serifen-Schriftarten aufs Panel, zeichne
> Kreise und Linien mit dem Bresenham...

Für Kreise gilt eigentlich das gleich wie für die Linien. Und bei den 
Schriftarten kann man sich noch bessere Optimierungen ausdenken (ganze 
Bytes auf einmal schreiben), wenn man die Schriftgröße und Position 
passend wählt.

von Norbert (Gast)


Lesenswert?

Ich weiss, im Titel steht 'möglichst in C'
aber das schließt Inline Assembler ja nicht aus;-)

Gab's das nicht mal diese wunderschönen SBR/CBR Mnemonics?

3 Zeilen Inline Assembler könnten vielleicht die Lösung sein.
Datenbyte holen, Bit reinschiessen und wieder weg damit...

von Peter D. (peda)


Lesenswert?

Die Schriften werden ja als Bitmap vorliegen. Und da wäre es Unsinn, die 
Bitmap in Pixel zu zerlegen und fürs GLCD dann wieder zusammen zu 
setzen.
Man kopiert besser die Bitmap byteweise.

Ein Problem ist nur, wenn das Zeichen nicht Byte-aligned anfängt.
Da schreibt man sich am besten 8 optimierte Routinen, für Anfang an 
Pixel 0 .. 7.


Peter

von Rolf Magnus (Gast)


Lesenswert?

Norbert schrieb:
> Gab's das nicht mal diese wunderschönen SBR/CBR Mnemonics?

Die gibt's, aber die bekommen die Bitnummer als Immediate. Eine zur 
Laufzeit ermittelte Bitnummer kann man denen nicht übergeben.

von Norbert (Gast)


Lesenswert?

Bei einem 16*64 Pixel Display, das nun selbst unter besten vorstellbaren 
Bedingungen nur 1024 Pixel darstellen kann, muß man meiner Meinung nach 
nicht mit Gewalt optimieren.
So eine Sequenz zum Setzen eines Bits im SRAM dauert:

LDS 2cycl
SBR 1cycl
STS 2cycl

Wenn man vielleicht die 128 Bytes des Displays als Kopie im SRAM hält 
und das Display zum Schluss in einem Rutsch beschreibt,
so reden wir von 1024 * (5 + was sonst noch so im Loop gebraucht wird) 
cycles.

von Norbert (Gast)


Lesenswert?

Rolf Magnus schrieb:
> Die gibt's, aber die bekommen die Bitnummer als Immediate. Eine zur
> Laufzeit ermittelte Bitnummer kann man denen nicht übergeben.

Ahh, OK, also noch eine kleine Sprungtabelle mit IJMP
Braucht insgesamt dann ein paar cycles mehr.
Man muß wirklich schauen/untersuchen wer besser optimiert, Mensch oder 
Maschine.

von Hannes (Gast)


Lesenswert?

Hi,

wenn ich zu den Schriften komme, muss ich noch mal sehen wie ich das 
mache. Zur Zeit male ich Linien und Kreise, und setze/rücksetze ein paar 
Pixel.

>>Gute Idee.

ist es egal ob RAM[128] oder RAM[2][64]. Ich weiß nicht wie es umgesetzt 
wird, aber ich schätze das 2 Indizies einfach miteinander multipliziert 
werden o.ä... dann kann ich auch gleich ersteres nehmen...

>>im Titel steht 'möglichst in C'
Naja, es steht eigentlich nicht da, aber .-) Ich würde gerne c 
beibehalten. Eigentlich ist es schön blöde sich Gedanken um die internen 
Abläufe zu machen, genau das sollte c ja eigentlich abnehmen... naja, 
wenn man es mit einem anderen Gedankenansatz jedoch viel besser angehen 
kann, reicht mir das schon völlig...

>>Thema: Organisation:

Meine Routine sieht zur Zeit so:

uint16_t mtx_data[64];

dsp_set_pixel( x, y)
{
   mtx_data[((y^0x0f)&0x0f)|(x&0x30)]|=0x01<<(x&0x0f);
}

aus. in 5 min mal eben schnell dahingepfuscht, da möchte ich dran 
arbeiten....

Leider ist es hadrwaremäßig so angeordnet:

Register 1 Bit7 Zeile15--LED(0,15)---LED(1,15)-...-LED(63,15)-
...                        ...          ...           ...
Register 1 Bit0 Zeile8 --LED(0,8)----LED(1,8)--...-LED(63,8)--
Register 2 Bit7 Zeile7 --LED(0,7)----LED(1,7)--...-LED(63,7)--
...                        ...          ...           ...
Register 2 Bit0 Zeile0 --LED(0,0)----LED(1,0)--...-LED(63,0)--
                       R3,0 - Sp.0  R3,1 - Sp.1   R10,7 - Sp63

Es sind 10 hintereinandergeschaltete 74HC595. Die ersten beiden machen 
das Zeilensignal, schalten also die jeweilige Zeile ein, die hinteren 8 
enthalten dann die 64 darzustellenden bits....

In meinem Beispiel liegen die 16Bit werte nun immer in blöcken 
waagerecht hintereinander:

word15 word31 word47 word63
...    ...    ...    ...
word0  word16 word32 word48

deswegen rutscht ein Teil der x Koordinate mit in den Index des 
Speichers:

                  V
[((y^0x0f)&0x0f)|(x&0x30)]

um das richtige Word zu treffen...

>>Thema Merken:

Wenn ich mir das nun merke, dann ist es doch schon wichtig sich zu 
merken welche Koordinate gerade angespochen wurde, ob sich die nächste 
noch im Word oder Byte befindet etc... dann ist es auch wichtig ob ich 
Linien von links nach rechts zeichne, oder umgekehrt...Wieviel sollte 
ich mir denn merken..?

Hannes

von Rolf Magnus (Gast)


Lesenswert?

Hannes schrieb:
>>>Thema Merken:
>
> Wenn ich mir das nun merke, dann ist es doch schon wichtig sich zu
> merken welche Koordinate gerade angespochen wurde, ob sich die nächste
> noch im Word oder Byte befindet etc... dann ist es auch wichtig ob ich
> Linien von links nach rechts zeichne, oder umgekehrt...Wieviel sollte
> ich mir denn merken..?

Du versteifst dich hier zu sehr auf die setpixel-Funktion. Wenn du eine 
Linie zeichnest, rufst du die nicht auf, sondern setzt die Pixel direkt 
aus der Linienzeichen-Funktion. Die weiß ja, wo sie gerade steht und in 
welche Richtung sie läuft. Statt die Koordinate zu inkrementieren, 
schiebst du  einfach die Maske um eins nach links, und wenn die dann 0 
ist (die 1 ist rausgefallen), setzt du sie wieder auf 1 und gehst um ein 
Byte weiter.

von Karl H. (kbuchegg)


Lesenswert?

Rolf Magnus schrieb:
> Hannes schrieb:
>>>>Thema Merken:
>>
>> Wenn ich mir das nun merke, dann ist es doch schon wichtig sich zu
>> merken welche Koordinate gerade angespochen wurde, ob sich die nächste
>> noch im Word oder Byte befindet etc... dann ist es auch wichtig ob ich
>> Linien von links nach rechts zeichne, oder umgekehrt...Wieviel sollte
>> ich mir denn merken..?
>
> Du versteifst dich hier zu sehr auf die setpixel-Funktion. Wenn du eine
> Linie zeichnest,

Es lohnt auch, für die häufigen Sonderfälle "waagrecht"/"senkrecht" 
eigene Linienfunktionen zu machen. Gerade wenn man eine GUI baut, kommen 
die oft vor.

Graphik ist nur in der Theorie 'ganz einfach'.

von Hannes (Gast)


Lesenswert?

...meine Linienfunktion zeichnet auch schräge Linien...dann müsste ich 
dafür ja tiefer in diese Funktion eingreifen...

Ich organisiere gerade meinen Speicher um:

Ein Speicher mit 2 Indizies führt zu folgender Funktion:


uint8_t mtx_data[8][16];

dsp_set_pixel( x, y)
{
mtx_data[(x>>3)&0x07][y&0x0f]|=0x01<<(x&0x07);
}

ich muss einen Teil aus x holen und dem Index zuführen, der wird sicher 
im Hintergrund eh wíeder ausmultiplisziert mit dem y Anteil [zweiter 
index].

Die Frage wäre ob:

uint8_t mtx_data[128];

dsp_set_pixel( x, y)
{
   mtx_data[((x<<1)&0x70)|(y&0x0f)]|=0x01<<(x&0x07);
}

besser ist....

Hannes

von Hannes (Gast)


Lesenswert?

>>Graphik ist nur in der Theorie 'ganz einfach'.

da ist was dran - danke für die Tips und Eure Zeit bis hierher :-)

Hannes

von Rolf Magnus (Gast)


Lesenswert?

Hannes schrieb:
> ...meine Linienfunktion zeichnet auch schräge Linien...dann müsste ich
> dafür ja tiefer in diese Funktion eingreifen...

Ja. Es kommt dann eben drauf an, wie stark deine Grafikroutinen optimert 
sein sollen und wieviel Aufwand du reinstecken willst. Du kannst 
natürlich mit einer einzigen Linienzeichenroutine alles abdecken. Dann 
sind deine horizontalen und vertikalen Linien halt nicht schneller als 
diagonale.
Du kannst mit generischen Funktionen alles machen, aber wenn du das 
Maximum an Geschwindigkeit rausholen willst, wird es sich immer lohnen, 
für häufig vorkommende Spezialfälle noch mal separate genau dafür 
optimierte Routinen zu bauen.

von Hannes (Gast)


Lesenswert?

ok, das habe ich verstanden.

Ich möchte im ersten Schritt erstmal das offensichtliche verbessern. Die 
Punkte wären:

A)Speicherorganisation ideal wählen
B)Bit im Byte setzen/rücksetzen effektiver machen.

Nächste Schritte wären für mich

C)Grafikroutinen erweitern mit "häufigen" fällen
D)Textroutinen erweitern

Ich habe verstanden das man an den Stellen C und D viel drehen kann udn 
dort eine Menge rausholen kann, ich denke aber auch in A und B kann ich 
DInge ideal wählen, um es erstmal grundsätzlich richtig zu machen und 
darum geht es mir momentan primär.

Hannes

von byte statt bit (Gast)


Lesenswert?

Wie schon mehrfach gesagt wurde versteif dich nicht auf das setzen 
einzelner pixel. Bearbeite einfach immer ganze bytes.

D.h. byte lesen, betroffene pixel ändern, byte zurück scheiben.

Das ist unabhängig davon was bzw in welche Richtung gezeichnet wird.

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.