Direct LCD access

The cybiko LCD can be controlled directly by accessing the memory areas that are mapped to the driver chip. Device specifications can be found here hd66421.pdf

#define IO_LCD_REG  *(volatile u8*)0x00600000
#define IO_LCD_DATA *(volatile u8*)0x00600001

There are 17 useful registers that can be manipulated at the IO_LCD_REG address.

#define LCD_REG_CR1           0x00
#define LCD_REG_CR2           0x01
#define LCD_REG_ADDR_X        0x02
#define LCD_REG_ADDR_Y        0x03
#define LCD_REG_RAM           0x04
#define LCD_REG_START_Y       0x05
#define LCD_REG_BLINK_START   0x06
#define LCD_REG_BLINK_END     0x07
#define LCD_REG_COLOR_1       0x0C
#define LCD_REG_COLOR_2       0x0D
#define LCD_REG_COLOR_3       0x0E
#define LCD_REG_COLOR_4       0x0F
#define LCD_REG_CONTRAST      0x10

The CR1 (control register 1) is manipulated to power up the LCD unit. Here are some defines for various functions that can be performed by manipulating the control registers.

// R0 - control register 1
#define LCD_CR1_RMW      0x80
#define LCD_CR1_DISP     0x40
#define LCD_CR1_STBY     0x20
#define LCD_CR1_PWR      0x10
#define LCD_CR1_AMP      0x08
#define LCD_CR1_REV      0x04
#define LCD_CR1_HOLT     0x02
#define LCD_CR1_ADC      0x01

// R1 - control register 2
#define LCD_CR2_BIS1     0x80
#define LCD_CR2_BIS0     0x40
#define LCD_CR2_WLS      0x20
#define LCD_CR2_GRAY     0x10
#define LCD_CR2_DTY1     0x08
#define LCD_CR2_DTY0     0x04
#define LCD_CR2_INC      0x02
#define LCD_CR2_BLK      0x01

A structure to hold information about the LCD device, this will be passed around to various functions.

typedef unsigned char * ADDR8;
typedef struct _mwscreendevice *PSD;
typedef struct _mwscreendevice {
	MWCOORD xres; // X screen res
	MWCOORD yres; // Y screen res
	int	bpp;         /* # bpp*/
	int	linelen;	/* line length in bytes for bpp */
	int	size;	/* size of memory allocated */
	ADDR8	addr;	/* address of memory allocated */

A small helper routine to bundle up the common access that we use when talking to the LCD memory addresses.

#define lcd_regdata(rg,dt) IO_LCD_REG=rg; IO_LCD_DATA=dt

Now we are ready to power up and initialise the LCD structure. To power up the unit we can do something like the following. I have taken the liberty of using u8 to represent an unsigned char.

static init(PSD psd) {
   lcd_regdata( LCD_REG_CR1, LCD_CR1_PWR | LCD_CR1_AMP |  LCD_CR1_ADC | LCD_CR1_DISP);

This will enable power and enable the display.

Compute some other values for the struct from what we know; that is the X resolution, Y resolution and the BPP. (160x100x2)

	psd->linelen = (psd->xres * psd->bpp) / 8;
	psd->size = psd->yres * psd->linelen;
	psd->addr = (ADDR8)malloc(psd->size);
	bzero(psd->addr, psd->size);

X address is incremented for each access (raster access). This optimisation saves us incrementing this ourselves. The driver chip will do it for us.

              lcd_regdata( LCD_REG_CR2, LCD_CR2_INC);

Drawing a pixel requires computing the offset in the memory segment and enabling the correct bit to represent 1 of 4 possible colours. Those being in the range 0-3 and representing WHITE, LTGRAY, DKGRAY and BLACK. To render a pixel on the device we must convert the X,Y coordinate to a 2 bit packed byte. As the display is a 2 Bit Per Pixel (bpp) device.

static unsigned char notmask[4] = { 0x3f, 0xcf, 0xf3, 0xfc};
/* Set pixel at x, y, to pixelval c*/
static void drawpixel(PSD psd, int x, int y, u8 c)
	ADDR8	addr = psd->addr;
	addr += (x>>2) + y * psd->linelen;
	*addr = (*addr & notmask[x&3]) | (c << ((3-(x&3))<<1));

The only thing that remains is to render the memory address to the LCD unit. Somewhere in the LCD initialization routine we would need to indicate to the control register 2 that each access to LCD_REG_RAM will automatically increment the X address.

void lcd_render( PSD psd )
	u8 x, y;
	u8 xend;
	ADDR8 addr = psd->addr;
	xend = psd->linelen >> 1;
	for (y=psd->yres-1; y!=0; y--)
		lcd_regdata( LCD_REG_ADDR_Y, y);
		// Unrolled loop x2 (for performance)
		for (x=0; x < xend; x++) {
			IO_LCD_DATA = *addr++;
			IO_LCD_DATA = *addr++;

So putting it all together with some sort of access might look something like this:

  screen.xres = 160;
  screen.yres = 100;
  screen.bpp = 2;
  for(i=0;i<100;i++) {

Hope these little code fragments give you some ideas about how to get direct screen access and by-pass the cybiko API.

I have implemented a basic LCD driver using native H8 code and pieces of the cyborn project to get it to boot and run on the cybiko. There should be no reason why this sort of code won't run under the bytcode interpretter but I have not tested it. I can make the full source and .boot image available if people are interested in more details.

  • cybiko/directaccessoflcd.txt
  • Last modified: 2009/11/27 17:54
  • by