Differences

This shows you the differences between two versions of the page.

Link to this comparison view

cybiko:directaccessoflcd [2009/11/27 17:54] (current)
Line 1: Line 1:
 +======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}}
 +<​code>​
 +#define IO_LCD_REG ​ *(volatile u8*)0x00600000
 +#define IO_LCD_DATA *(volatile u8*)0x00600001
 +</​code>​
 +There are 17 useful registers that can be manipulated at the IO_LCD_REG address.
 +<​code>​
 +#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
 +</​code>​
 +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.
 +<​code>​
 +// 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
 +</​code>​
 +
 +A structure to hold information about the LCD device, this will be passed around to various functions.
 +<code c>
 +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;​
 +</​code>​
 +A small helper routine to bundle up the common access that we use when talking to the LCD memory addresses.
 +<​code>​
 +#define lcd_regdata(rg,​dt) IO_LCD_REG=rg;​ IO_LCD_DATA=dt
 +</​code>​
 +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.
 +<code c>
 +static init(PSD psd) {
 +   ​lcd_regdata( LCD_REG_CR1,​ LCD_CR1_PWR | LCD_CR1_AMP |  LCD_CR1_ADC | LCD_CR1_DISP);​
 +</​code>​
 +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)
 +<code c>
 + psd->​linelen = (psd->​xres * psd->​bpp) / 8;
 + psd->​size = psd->​yres * psd->​linelen;​
 + psd->​addr = (ADDR8)malloc(psd->​size);​
 + bzero(psd->​addr,​ psd->​size);​
 +</​code>​
 +X address is incremented for each access (raster access). ​ This optimisation saves us incrementing this ourselves. ​ The driver chip will do it for us.
 +<code c>
 +              lcd_regdata( LCD_REG_CR2,​ LCD_CR2_INC);​
 +}
 +</​code>​
 +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.
 +<code c>
 +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));​
 +}
 +</​code>​
 +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.
 +<code c>
 +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++;
 + }
 + }
 +}
 +</​code>​
 +So putting it all together with some sort of access might look something like this:
 +<code c>
 +  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);​
 +</​code>​
 +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.
 +{{tag>​cybiko}}