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 */ } SCREENDEVICE;
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) IO_LCD_REG = LCD_REG_RAM; 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:
SCREENDEVICE screen; screen.xres = 160; screen.yres = 100; screen.bpp = 2; init(&screen); for(i=0;i<100;i++) { drawpixel(&screen,i,i,CLR_BLACK); } lcd_render(&screen);
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.