Displaying text on LCD

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Displaying text on LCD

1,116 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by amlwwalker on Sat Oct 26 10:42:30 MST 2013
Hi,

I am trying to get a font to display on my LCD.

I understand how it works in principle.
You get a font, then run it through an application to convert it to an array, for instance this is the letter A:

0x04, 0x00, //      #     
0x04, 0x00, //      #     
0x0A, 0x00, //     # #    
0x0A, 0x00, //     # #    
0x11, 0x00, //    #   #   
0x11, 0x00, //    #   #   
0x20, 0x80, //   #     #  
0x3F, 0x80, //   #######  
0x40, 0x40, //  #       # 
0x40, 0x40, //  #       # 
0x80, 0x20, // #         #
0x80, 0x20, // #         #
0x00, 0x00, //            
0x00, 0x00, //            
0x00, 0x00, //            
0x00, 0x00, //  


its 11 pixels wide and 32 long.

I want to display it on my LCD which I can already display bitmaps on.

So my approach was
while(i < arrayLength) {
while (bitCounter < 8) {
if (letterArray << bitCounter != 0) {
setPixel(x, y, black);
else
setPixel(x,y, white);
bitCounter++;
x++;
if (x < 15) {
x = 0;
y++;
}
bitCounter = 0;
i++
}
}


However I just get a load of black pixels not resembling the letter A.
Has anyone done this?
Its also really slow to load.
I dont want to use a whole library for it, its quite a small thing.

Any ideas?
Tahnks
Alex
Labels (1)
0 Kudos
10 Replies

988 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Mon Oct 28 13:44:42 MST 2013
This is good. You now have a good working starting point, which you can improve on.

One thing that is important, is that when you read the array, you should read it into the same type of variable as the array is.
Your array is of type uint16_t, but if it only holds bytes, just change it into an uint8_t.
current_byte is a 'char'; this is the one I'm talking about; make it into a uint16_t or a uint8_t, depending on what type newFont[] is.

If all characters fit into an 8-bit array, you'll save a lot of bytes on changing it to uint8_t.

Try optimizing it little by little.

First thing  you could do is to create a bitmask; declare 'mask' at the top...
uint8_t mask;  /* same type as current_byte */


Then switch pos=8 out with:
mask = (1 << 7);


Change your inner for-loop and the if:
while(mask)
{
if(current_byte & mask)
{
GLCD_SetPixel_16bpp(current_x, current_y, Black);
}
mask = mask >> 1;
current_x++;
}


Then run.

Next up: In your descriptors, you have already the vertical starting position and ending position.
But you might want to add the character width in pixels too.
So if you make sure the character is "right-aligned" like this:
...####.
...#...#
...#...#
...####.
...#...#
...#...#
...####.


not left-aligned like this:
####....
#...#...
#...#...
####....
#...#...
#...#...
####....


Then you can load mask this way...
mask = (1 << cwidth);


...where cwidth is a number between 0 and 7 (which you read from your descriptors)

And after the inner loop, you could do this:
current_x -= cwidth;
current_y++;


Finally outside the outer loop:
current_x += cwidth;

0 Kudos

988 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Sun Oct 27 20:52:08 MST 2013
mask should not be (1 << 8), but rather (1 << 7).
Woops!

Eg.

mask = (1 << (8 - 1));
or
mask = (1 << (16 - 1));

-because if you have a byte and you shift 1 8 times to the left, you shift it out, and the byte becomes zero.
Same about a 16-bit int and 16 shifts. ;)
0 Kudos

988 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by MarcVonWindscooting on Sun Oct 27 12:56:21 MST 2013
I did some display output recently. By I use those simple EA-DOG-modules (128x64px, 400uA power consumption).

I do everything in the most inefficent way by starting with a frame buffer structure and pixel read/write operations.
The frame buffer is simply a char array, but I defined bit-offsets for x and y. That way I can lay out the bits as rows or columns of pixels. This helps adapting the frame buffer to real devices.
Based on the frame buffer structure and read/write pixel I defined bitblt to copy one rectangular area to somewhere else (probably even a different frame buffer, with different pixel/memory mapping). Once I had bitblt, I defined fonts with that. And a font is nothing but a big (e.g. 256*width of character) image containing all characters plus a function that maps from character value (0..255) to the characters image position (and width) in the big image. That even allows variable width of the chars.
One thing I should point out: everything works with signed integer 32bit coordinates and positioning outside a frame-buffers range is doesn't generate an error.

What I did recently was wasting even more CPU power, by NOT using the display chips scrolling capabilities. I use a memory image (frame buffer) and provide a very fast SPI frame update. The SPI transfer is done mostly by hardware, so that doesn't cost too much. I have to confess, I was tempted by the idea of using 64bit shifts for scrolling the frame buffer up by n pixels, but that is the only optimization that does not perform pixel read/write 0:)

The cool news is: even on a LPC2136 at 12MHz such an approach is still feasible and frame rates of about 5Hz possible (besides running I2C LM73, PCA9555 (8buttons, 8LEDs), 2 digital potentiometers, 4 GPIO buttons, generating beeps). I want to add a 3-Phase PWM motor control, that already ran with the I2C-part and the digital potentiometers at 48MHz up to 60kHz sampling rate/PWM frequency.

I focussed on ease of programming and code size, not speed. And no regret so far.
(if you care, my GPL3 project 'mxli' (www.windscooting.com/softy/mxli.html , lib/c-any/gfxmono*) contains that code in the libraries).

tl;dr

Make sure, you don't optimize a part of your program, that doesn't need that.

I believe, text output doesn't need to be that fast, because a human is supposed to understand that output. Humans are very slow in reading text, at least I am. The output of an under-clocked LPC21 overpowers my brain by a factor of 100 at least. I'm not a gamer, though.
0 Kudos

988 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by amlwwalker on Sun Oct 27 08:10:30 MST 2013
Hi Pacman,
Thanks for getting back to me.

So, I made your mask changes, however now it doesnt display the text!
mask = (1 << 8);
while(mask)
{
if((current_byte & mask) != 0x00)
{
GLCD_SetPixel_16bpp(current_x, current_y, Black);
}
mask = mask >> 1;
current_x++;
}

I can see why however that should be more efficient. BTW I added int he != 0x00 on top of your suggestion as I thought that was what I was checking for, as yours is doing a "is true check" and that didnt work either.

Its almost bug free apart from that (i.e before I made the mask change), however for some reason I get some characters just not working

I am trying to get bigger fonts working which use more than 8bits, but also 'W' in this font uses more than 16 bits:

// @264 'W' (11 pixels wide)
0x00, 0x00, //
0x80, 0x20, // #         #
0x80, 0x20, // #         #
0x44, 0x40, //  #   #   #
0x44, 0x40, //  #   #   #
0x44, 0x40, //  #   #   #
0x2A, 0x80, //   # # # #
0x2A, 0x80, //   # # # #
0x11, 0x00, //    #   #
0x11, 0x00, //    #   #
0x00, 0x00, //
0x00, 0x00, //

So I cant use uint8_t however I have swapped everything for a uint16_t so for this font I am wasting a bit yeah, but if I use bigger ones then I should be able to use the same function - is the only change so I can display this 'W' to make pos = 16 rather than 8, or mask >> 16.

I seem to have to keep text as a char, I cant change it for a uint16_t even though it is that really?

0 Kudos

988 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Sun Oct 27 07:14:46 MST 2013
(I updated the first reply I wrote; fixed a bug and now I subtract 32 from c, to make it start at the <space> character).
0 Kudos

987 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Sun Oct 27 07:13:54 MST 2013
Hi Alex.

You have an error in the line:
if (letterArray << bitCounter != 0)


I believe you meant something like:
if (letterArray & (1 << bitCounter))


...Apart from that... setPixel is slow, you might want to find a faster way one day.

But for now, let's see if we can simplify and optimize the code slightly:

#define CHR_SIZE (16 * 16 / 8)

/* Your letterArray appears to be 8-bit, but we'll use 16-bit pointers instead */
const uint16_t*s;/* 'source' - this is where we read from */
const uint16_t*e;/* 'end' marker */
uint16_tbits;/* but we can do things faster using 16 bits */
uint16_ti;

s = (uint16_t *) &letterArray[(c - 32) * CHR_SIZE];/* point s to the beginning of the array */
e = &s[16];/* point e to the end #!# */
while(s < e)/* while we haven't reached the end */
{
bits = *s++;/* read the bits */
i = 16;/* using our 16 bits, draw a line of 16 pixels */
while(i--)
{
color = (bits & 0x8000) ? black : white;/* pick a color */
setPixel(x++, y, color);/* set pixel and advance x */
bits = bits << 1;/* shift out the bit we've just read */
}
x -= 16;/* go back to original x-position */
y++;/* next line */
}
y -= 16;/* restore y position */
x += 16;/* advance x position, ready for next character */


(update: bug fixed in the line marked with #!#, now subtracting 32 from c. Comments also lining up)

What I do here, is that I use a pointer instead of an array. I read it into a register once, then keep using the register (which is technically either same speed or faster than keeping reading from memory; general rule, works for all CPUs).

I try to do as little as possible in the loop. Remember: CPUs like doing as little work as they can.
We don't need a counter for 's'. As the pointer is advanced anyway, we can just use this for comparing against an end-marker. The result is the same, but we'll save a few clock cycles incrementing a counter in parallel with updating the pointer, plus we'll save a register as well.
0 Kudos

987 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by amlwwalker on Sun Oct 27 02:29:51 MST 2013
So I've got my version working now, its not as elegant as yours but it now looks like this:

void color_lcd_print_char(char text, int color, unsigned char x, unsigned char y)
{

if((text > 31)&&(text < 127))
{
unsigned char current_x = x;
unsigned char current_y = y;
char current_byte;
char i, pos;
uint16_t startAt = descriptors[text - 63][1];
uint16_t endAt = descriptors[text - 62][1];

char string[256];
  sprintf(string, "text: %c, text: %d, startAt: %d, endAt: %d\n", text, text, startAt, endAt);
  UARTPuts_(LPC_UART0, string);
while (startAt < endAt)
{
current_byte = newFont[startAt];
pos = 8;
while (pos--)
{
if(((current_byte>>pos) & 0x01)==0x01)
{
GLCD_SetPixel_16bpp(current_x,current_y, Black);
}
current_x++;
}
current_x=x;
current_y++;
startAt++;
}
}
}
0 Kudos

987 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by amlwwalker on Sun Oct 27 01:29:05 MST 2013
Wow Pacman, thanks for your advice, this is great.

I've tried your first suggestion and I cant get anything to appear, so I thought I would get something working and then perhaps you could advise me on making it more efficient as your code is clearly a little cleverer than mine.
I haven't yet looked at making "draw pixel" more efficient yet, although I want to get onto that as I can just see a line being drawn across the screen. Its quite fast at drawing bmp images out of an array though.

Anyway, to shed some more light:

My font array looks like this:

const uint16_t newFont[] =
{
// @0 'A' (7 pixels wide)
0x10, //    #
0x10, //    #
0x28, //   # #
0x28, //   # #
0x44, //  #   #
0x44, //  #   #
0x7C, //  #####
0x82, // #     #
0x82, // #     #
0x00, //
0x00, //

// @11 'B' (5 pixels wide)
0xF0, // ####
0x88, // #   #
0x88, // #   #
0x88, // #   #
0xF0, // ####
0x88, // #   #
0x88, // #   #
0x88, // #   #
0xF0, // ####
0x00, //
0x00, //
//etc etc for rest of alphabet
};

I then have an array that holds the position of the start of each letter and the width in pixels for each letter - to get spacing right, although for now I am just using an arbitrary 10 pixels until I get it right:

const uint16_t descriptors[][2] =
{
{7, 0}, // A
{5, 11}, // B
{6, 22}, // C
//etc etc for the rest of the alphabet
};

Then the code I have managed to get working (almost....) is this:

void color_lcd_print_char(char text, int color, unsigned char x, unsigned char y)
{

if((text > 31)&&(text < 127))
{
unsigned char current_x = x;
unsigned char current_y = y;
char current_byte;
char i, pos;
uint16_t startAt = descriptors[text - 65][1];
uint16_t endAt = descriptors[text - 64][1];
for(i = 0; i < endAt; i++)
{
current_byte = newFont[startAt + i];
for(pos = 0; pos < 8; pos++)
{
if(((current_byte>>pos) & 0x01)==0x01)
{
GLCD_SetPixel_16bpp(current_x,current_y, Black);
}
current_x++;
}
current_x=x;
current_y++;
}
}
}


Its sort of an amagamation of what you were doing and some first principles. As you can see there's no clever pointer stuff going on...

When I try yours I get nothing on the screen. When I use mine I get the letters, but they are mirrored also it seems to plot them in a grid, and then goes a bit nuts at the end...

In fact I also wrote this function for strings:

void color_lcd_print_string(char * text, char length, int color, unsigned char x, unsigned char y)
{
char i = 0;
while((text != '\0')&&(i<length))
{
color_lcd_print_char(text, color, x+i*10, y);
i++;
}
}

so if I pass that a string:
color_lcd_print_string("ABCabc", 6, Black, 10, 10);


I get the picture attached.

Side question, I am not sure I am storing the arrays in flash, I think they are possibly being stored in ram. On an 8 bit AVR I could use the Progmem keyword to store them in flash, is there an equivelent on ARM?

With regards to setPixel function, this is what its currently doing:
__inline void GLCD_SetPixel_16bpp(uint16_t Xpos, uint16_t Ypos, uint16_t color) {
volatile uint16_t *pLCDbuf = (uint16_t *) LCD_VRAM_BASE_ADDR; /* LCD buffer start address */

pLCDbuf[Ypos * GLCD_X_SIZE + Xpos] = color;
}
0 Kudos

987 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Sat Oct 26 18:46:07 MST 2013
...And it's possible that the 'A' will be displayed upside down.
If that's the case, you'll need either to read the array from the bottom (eg. while(s < e) { bits = *--e; ... }) or write it from bottom to top (subtracting skip instead of adding it; remember to add linesize * CHR_HEIGHT before the loop then).
Try and see which way is the fastest.
0 Kudos

987 Views
lpcware
NXP Employee
NXP Employee
Content originally posted in LPCWare by Pacman on Sat Oct 26 18:37:30 MST 2013

Quote: amlwwalker
Its also really slow to load.



I didn't go into details in my first reply.

What you need to do, is to get rid of setPixel.

These are the basics for drawing in any kind of screen-buffer, big or small; it's always the same. You can optimize and get faster results, depending on how you do things, but that might make this example over-complicated.
Outside all loops, you should get the address of the screen's top/right corner.

Let's call the screen's top/right corner address 'screenBase'.

In this code, I assume that the size of a pixel (color depth) is 16 bits. It may be 8-bit, which is also easy, it may be 24-bit, which is a little more messy, unless it uses all 32 bits. If it's some other depth, such as 3, 5 or 13, it could be necessary to do some bit-shifting instead.

If on the other hand, the bit-depth is divisible by 8, it should be fairly straight-forward to make a real fast routine for outputting the graphics. I'll assume it's 16 bits per pixel in this example.

The tricky part is to figure out what the screen address is. Try looking at the source-code for setPixel, if you have it. It might be the same address that the lpc177x_8x_lcd driver is using, though.
Take a look at lpc177x_8x.c (especially LCD_SetImage, LCD_PutPixel and LCD_SetBaseAddress)
LCD_SetImage tells you something about the width,height, screen address, bit size, etc.

linesize should be the size of a line in pixels (eg. same as lcd_hsize from LCD_SetImage).


#define CHR_WIDTH 16
#define CHR_HEIGHT 16
#define CHR_SIZE ((CHR_WIDTH * CHR_HEIGHT + 7) >> 3)

uint16_t *screenBase;
uint16_t *d;
uint32_t skip;
uint32_t linesize;
const uint16_t pe;

screenBase = ????;/* try and find this in the setPixel code */
linesize = ????;/* perhaps 1024 * ((BIT_DEPTH + 7) >> 3) */

d = screenBase + y * linesize * CHR_HEIGHT + x * CHR_WIDTH;
/* have a look at LCD_GetWordOffset and LCD_GetBitOffset; you should be able to find the screen address and the pixel address then. */

Instead of updating x and y in the loop, you now only update 'd'.

skip = linesize * CHR_HEIGHT - CHR_WIDTH;
while(s < e)
{
bits = *s++;
pe = &d[16];
while(d < pe)
{
*d++ = (bits & 0x8000) ? black : white;
}
d += skip;
}


Remember to check boundaries before you start, so you don't write to any addresses outside the screen area, and also consider checking for left and right side.

Why is setPixel so slow ?
-It has to figure out where the screen is every time, check X and Y against the screen boundaries, convert the X and Y positions into an offset and add the offset to the base address, then it has to figure out the pixel size (8, 16, 24 or any other strange depth), before it actually writes the pixel.
0 Kudos