Servus Leute, zur Darstellung von benutzerdefinnierten Schriftarten auf einem Grafik-LCD T6963c verwende ich folgende lib: "GLCD T6963C Font-Tool" Beitrag "GLCD T6963C Font-Tool" Funktioniert auch sehr gur, nur leider dauert ein aufbau eines 5-Zeilingen Textes mit Schriftgröße 13 MS Sans Serif ca. 1 Sekunde. Der Prozessor ist ein M64, mit 16MHz Takt. Meiner Meinung nach ist das ganze so langsam, da die Table mit den Zeichen erst aus dem Flash geholt werden muss und dann Pixel für Pixel ans LCD geschrieben wird. Hat jemand eine Idee wie ich das ganze beschleunigen kann?? Ich kann die Schriftart nicht in den RAM legen, der ist dafür zu klein. Ich hab mein kleines Besispielprogramm mal angehängt, bin für jede Hilfe dankbar Gruß Philipp
Beschreibst du das Display pixelweise (mit SetPixel oder so ähnlich) oder schreibst du direkt Byte für Byte in den RAM? Ich habe ein 240x64px-Display mit dem T6963 an einem PIC 16F876 mit 4MHz hängen. Ein komplettes Bild darzustellen, dauert ca. 80ms. Schriftgrößen sind eine Zeile mit 40x24px und darunter eine mit 16x12px. Die Fontdaten lade ich auch direkt aus dem Flash des Controllers. Programmiert habe ich allerdings in Assembler. Möglicherweise sind die C-Routinen zu langsam.
die Routine die fürs schreiben ans LCD zuständig ist verwendet set_pixel. Wenn ich einen normalen Text mit z.B. glcd_print_ram schreibe sehe ich keine Verzögerung, allerdings ist die Schriftart auf 8x8 beschränkt. Geschickt wäre es, wenn ich dem LCD die Font einprogrammieren könnte und die Texte dann nur mehr mit glcd_print schreiben kann.
Du beschäftgist mit diesem hier
1 | void glcd_cput(int byte) // write command byte to LCD module |
2 | { do |
3 | { ; |
4 | } while ((0x03 & glcd_sget()) != 0x03); // wait until display ready |
5 | |
6 | glcd_DATA_PORT = byte; // present data to LCD on PC's port pins |
7 | |
8 | glcd_cd_high(); // control/status mode |
9 | glcd_rd_high(); // make sure LCD read mode is off |
10 | glcd_wr_low(); // activate LCD write mode |
11 | glcd_ce_low(); // pulse ChipEnable LOW, > 80 ns, enables LCD I/O |
12 | glcd_ce_high(); // disable LCD I/O |
13 | glcd_wr_high(); // deactivate write mode |
14 | |
15 | }
|
16 | |
17 | void glcd_pixel(int column, int row,char show) |
18 | { int addr; // memory address of byte containing pixel to write |
19 | if( (column>=glcd_XMAX) || (row>=glcd_YMAX) )return; |
20 | addr = glcd_G_BASE + (row*glcd_BYTES_PER_ROW) + (column/glcd_FONT_WIDTH); |
21 | glcd_set_address(addr); // set LCD addr. pointer |
22 | if(show) glcd_cput(0xf8 | ((glcd_FONT_WIDTH-1-column%glcd_FONT_WIDTH)) ); // set bit-within-byte command |
23 | else glcd_cput(0xf0 | ((glcd_FONT_WIDTH-1-column%glcd_FONT_WIDTH)) ); // set bit-within-byte command |
24 | }
|
25 | |
26 | void lcd_print2_p(unsigned int x,unsigned int y, const char *in, const struct FONT_DEF *strcut1, unsigned char invers) |
27 | {
|
28 | register unsigned int offset,width; |
29 | register unsigned char i,j,map,ertefah,allwidth=0; |
30 | |
31 | while((map = pgm_read_byte(in++))) |
32 | {
|
33 | map = pgm_read_byte(&strcut1->mapping_table[map]); |
34 | |
35 | width = strcut1->glyph_width; |
36 | if(width == 0) |
37 | width = pgm_read_byte(&strcut1->width_table[map]); |
38 | |
39 | offset = pgm_read_word(&strcut1->offset_table[map]); |
40 | ertefah = strcut1->glyph_height; |
41 | |
42 | |
43 | for(j=0 ; j<ertefah * (((width-1)/8)+1) ; j+=(((width-1)/8)+1) ) |
44 | { // ertefah |
45 | for(i=0 ; i<width ; i++) |
46 | { // width |
47 | if( pgm_read_byte(&strcut1->glyph_table[ offset+j+(i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) ) |
48 | glcd_pixel( x+i+allwidth , y+j/ (((width-1)/8)+1), !invers ); |
49 | else
|
50 | glcd_pixel( x+i+allwidth , y+j/ (((width-1)/8)+1), invers ); |
51 | }//End i |
52 | }// End j |
53 | allwidth+=width; |
54 | }// End K |
55 | }
|
den Prozessor enorm. Von der Theorie her ist da im Grunde nichts auszusetzen, ist alles logisch aufgebaut und nachvollziehbar richtig. Aber eben naiv und ohne Rücksicht auf Verluste programmiert. Da wirst du wohl in den sauren Apfel beissen müssen und dir ein anderes Verfahren ausdenken müssen. Grundsatz: Nicht einzelpixelweise, sondern soviele Pixel in einem Aufwasch, wie nur geht. Im Idealfall willst du komplette Bytes schreiben. Bei dem Code kann man zwar den einen oer anderen Takzyklus noch einsparen, aber Wunder darf man keine erwarten. Klar kann man ein Schwimmbecken mit einem Teelöffel und ständigem Laufen zum Wasserhahn auch voll kriegen, aber egal wie gut du deine Gehtechnik verbesserst, mit einem Schlauch gehts einfach schneller.
ok, ja so was hab ich befürchtet. Du meinst die lcd_print2_p(...) frist zu viel Leistung oder? Ich werd mir die for-Schleife mal genauer anschauen, vielleich kann man die verbessern. Glaubst du, dass die Berechnung in set_pixel addr = glcd_G_BASE + (row*glcd_BYTES_PER_ROW) + (column/glcd_FONT_WIDTH); viel ausmacht?
Das ganze Verfahren, wie der Text in Pixel aufgelöst und jedes Pixel einzeln zum GLCD übertragen wird, ist Müll. Es ist das Verfahren an sich, nicht die Details. Du kannst an den Details schrauben soviel du willst, das wird nie richtig schnell. wenn du so willst: Was geht schneller? Einen Getränke-LKW abladen, indem man mit jeder Flasche einzeln geht oder denselben LKW abladen, indem man sich möglichst volle Getränkekisten schnappt? Dein Code geht mit jeder Flasche einzeln.
Allerdings kann man in dieser Schleife wirklich noch viel machen.
1 | for(j=0 ; j<ertefah * (((width-1)/8)+1) ; j+=(((width-1)/8)+1) ) |
2 | { // ertefah |
3 | for(i=0 ; i<width ; i++) |
4 | { // width |
5 | if( pgm_read_byte(&strcut1->glyph_table[ offset+j+(i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) ) |
6 | glcd_pixel( x+i+allwidth , y+j/ (((width-1)/8)+1), !invers ); |
7 | else
|
8 | glcd_pixel( x+i+allwidth , y+j/ (((width-1)/8)+1), invers ); |
9 | }//End i |
10 | }// End j |
AUch wenn ich als ein starker Verfechter des Optimizers bekannt bin, war dir das nicht selber zu blöd, da ständig ((width-1)/8)+1) zu schreiben? Der Wert ändert sich nie, schaon alleine aus Übersichtsgründen lohnt es sich die Berechnung vor die Schleife zu holen und sich das Ergebnis in einer Variablen aufzuheben
1 | NrBytes = ((width-1)/8)+1; |
2 | |
3 | for(j = 0; j < ertefah * NrBytes; j += Nrbytes ) |
4 | { // ertefah |
5 | for( i = 0; i < width; i++ ) |
6 | { // width |
7 | if( pgm_read_byte( &strcut1->glyph_table[ offset + j + (i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) ) |
8 | glcd_pixel( x+i+allwidth , y + j / NrBytes, !invers ); |
9 | else
|
10 | glcd_pixel( x+i+allwidth , y + j / NrBytes, invers ); |
11 | }//End i |
12 | }// End j |
Sehr sinnig ist es auch, das j immer um NrBytes zu erhöhen, also in Vielfachen von NrBytes zu operieren, nur damit beim glcd_pixel Aufruf der NrBytes Anteil wieder herausdividiert werden muss. Da das j beim pgm_readByte direkt benutzt wird, bietet es sich an 2 Variablen zu benutzen, einmal ein j welches um 1 erhöht wird und eine 2.te die das NrBytes-Vielfache davon enthält.
1 | NrBytes = ((width-1)/8)+1; |
2 | |
3 | for(j = 0, jBytes = offset; j < ertefah; j += Nrbytes, jBytes += NrBytes ) |
4 | { // ertefah |
5 | for( i = 0; i < width; i++ ) |
6 | { // width |
7 | if( pgm_read_byte( &strcut1->glyph_table[ jBytes + (i/8) ]) & (1 << ( 7 - ( i % 8 ) ) ) ) |
8 | glcd_pixel( x+i+allwidth , y + j, !invers ); |
9 | else
|
10 | glcd_pixel( x+i+allwidth , y + j, invers ); |
11 | }//End i |
12 | }// End j |
Die Shift Operation ist eine von der schlimmen Sorte. Man macht auf einem AVR keinen Shift mit einer variablen Anzahl von Bits. Der AVR kann das nicht, sondern diese Operation muss intern mit einer Schleife abgehandelt werden. Die Idee ist an dieser Stelle offensichtlich, sich eine Maske zurechtzulegen, bei der ein 1 Bit nacheinander an den Positionen 7, 6, 5, 4, ... auftaucht.
1 | NrBytes = ((width-1)/8)+1; |
2 | |
3 | for(j = 0, jBytes = offset; j < ertefah; j += Nrbytes, jBytes += NrBytes ) |
4 | { // ertefah |
5 | PixelMask = 1 << 7; |
6 | for( i = 0; i < width; i++ ) |
7 | { // width |
8 | if( pgm_read_byte( &strcut1->glyph_table[ jBytes + (i/8) ] ) & PixelMask ) |
9 | glcd_pixel( x+i+allwidth , y + j, !invers ); |
10 | else
|
11 | glcd_pixel( x+i+allwidth , y + j, invers ); |
12 | PixelMask >>= 1; |
13 | }//End i |
14 | }// End j |
Der Wert von i/8 ändert sich nur nach jedem 8-ten Durchlauf durch die i-Schleife. Es würde sich daher lohnen, die i-Schleife in 2 Schleifen aufzuteilen, damit der pgm_read_byte nicht bei jedem Schleifendurchlauf gemacht werden muss. Eine äussere Schleife, die bis width/8 (gemeint ist an dieser Stelle offenbar die Anzahl der Bytes, die haben wir aber schon, steht in NrBytes) läuft. In dieser Schleife wird der pgm_read_byte gemacht und eine darin eingeschalchtelte Schleife, die das auf die Art gelesene Byte in die einzelnen Bits zerpflückt. Mit der Obergrenze der inneren Schleife beim letzten Durchlauf durch die äussere Schleife muss man ein wenig aufpassen, aber das geht schon. Die y + j kann man noch herausfakturieren, indem ganz eifnfach y nach am Ende jedes Schleifendurchlaufs um 1 erhöht wird. Selbiges, allerdings mit einer Hilfsvariablen, kann man auch mit x und x + i + allwidth machen. Nach dem Muster for( i = 0; i < irgendwas; ++i ) machwas_mit( y + i ); wird zu for( i = 0; i < irgendwas; ++i, ++y ) machwas_mit( y ); oder noch besser grenze = y + irgendwas; for( i = y; i < grenze; ++i ) machwas_mit( i ); (Obwohl: die letzten Optimierungen können unter Umständen auch vom Compiler erledigt werden, Kommt drauf an, ob er davon ausgehen kann ob der Funktionsaufruf machwas_mit irgendwas bzw. y verändert oder nicht) Aufpassen muss man auch noch auf die Datentypen. Fast schon putzig hingegen macht sich die 'register' Definition von offset und width aus. Bloss gut, dass praktisch alle Compiler so eine Definition mitlerweile sowieso ignorieren. Damit hat man schon mal die meisten Berechnungen aus den inneren Schleifen herausgezogen und so verteilt, dass der Compiler nicht ständig Register umladen muss. Aber wie gesagt: erwarte keine Wunder. Das ist Kleinkosmetik. (Und ich hab jetzt hoffentlich beim editieren keine Fehler eingebaut)
So, braucht jetzt statt 400ms nur noch 150ms. Das meiste konnte aus der T6963.c rausgeholt werden. Zeit halbiert. Der Rest so ungefähr nach den Vorschlägen von Karl Heinz.
hey, nicht schlecht, respekt! Ich werds nachm Essen gleich mal ausprobiern, vielein vielen Dank
Upps, nimm dein eigenes makefile! Hab den Prozessor und die Pfade geändert;)
Mit -O2 statt -Os und static inline unsigned char glcd_sget(void) // get LCD display status byte komme ich jetzt auf 130ms.
hey super, läuft einwandfrei! nicht schlecht! Von der Geschwindigkeit auch ausreichend, sieht jetzt super aus! vielen Dank noch mal! Gruß Philipp
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.