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
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
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.
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
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.
>>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
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.
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?
...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
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.
>>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
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
>>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
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.
> 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.
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.
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.
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...
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
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.
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.
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.
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
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.
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'.
...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
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.
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
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.