Home PcOnHand

OnHand Pc Details








onHand Manual: SED1560 lcd controller



The onHand/Ruputer has an EPSON SED1560 lcd controller. The interface to this controller is accomplished by two I/O registers: a control register and a data register. The address of these registers in the onHand/Ruputer address space are:

Register Name Address
Control Alcd_c 0x0010
Data Alcd_d 0x0014

By writing to the control register we can set some internal variables of the microcontroller and turn the display on or off. Here are the funtions selected by the control register:

Function Value to write Explanation
LCD on/off
1010 111x
Turns the LCD display ON (x=1) or OFF (x=0)
Display start line
01xx xxxx
xxxxxx should be 1. Sets the RAM display line for the first line in LCD
1011 xxxx
Sets the current page
Column (high-order 4bit)
0001 xxxx
Sets the higher 4bits (nibble) of current column (see below)
Column (low-order 4bit)
0000 xxxx
Sets the lower 4bits of current column (see below)

On the other hand the data register is used to read the contents of the LCD controller RAM and to write new values in it. Normally we only need to write into the data register (draw in the LCD). We only write because of performance reasons, as we will draw the desired contents of the screen into a memory buffer and then transfer the entire buffer to the LCD so if we needed to read the screen contents, we would read from memory buffer as it's faster (and easier).


Structure of the LCD memory

The LCD has as video memory some RAM in which every bit maps directly to a point of the display (magic done by the hardware of the controller).

The way this memory maps into the display is, at least, curious: the display is divided in "pages" of 8-bit height and 102 columns. Each page is handled independently and the bits maps to pixels of a page from top to bottom and then from left to right. This way, each byte represents a column of the page. The display of the onHand/Ruputer is a dot-matrix of 102x64; as we have a 64 pixels height, the display has 8 pages of 8 pixels height each.

That is:

        | Page 0, 102 bytes                                  |
        | Page 1, 102 bytes                                  |
        | Page 2, 102 bytes                                  |
        | Page 3, 102 bytes                                  |
        | Page 4, 102 bytes                                  |
        | Page 5, 102 bytes                                  |
        | Page 6, 102 bytes                                  |
        | Page 7, 102 bytes                                  |
Oddly enough, this is exactly the format of Seiko's MMP ignoring the MMP header.

In the page numbers we don't have any problem: thay start with the number 0 and continues thru number 7, the last one. But the column numbers are a little more tricky. They start with column number 0x40 so the last column is 0xa5. To translate from the desired x-coodinate to the real lcd column we have to add 0x40 (and remember that we pass the column number with two writes, one for the high nibble and another for the low nibble).


Writing directly to the LCD

The first thing we have to do is to set the page and the column we are willing to write. Here a little caveat: we can write only those parts of the screen that have changed, but to write all the screen we have to output only 816 bytes, so considering the overhead that page/column set implies, and the losing of some precious machine cycles because of branches, it's a better idea and often faster to just mantain a "screenfull" image in a buffer and then fire it up to the display in one shot.

In the most common case we will do:

  1. Write to the control register the page "n", initially 0, and the column 0x40
  2. In a loop write all the columns of the current page to the data register
  3. if there are more pages, increment "n" and go to (1.).

To speed up this a bit, we will write the data in words rather than bytes, and the loop to write all columns of the current page should be partially unrolled.

In the examples I've have found, they unroll the column loop to do 3 times 16 word writes, and then, outside the loop, the last three words (I don't know why they refuse to do 3 times 17 words writes, as the limits for the "goto" are not exceeded, but this is the way it's done in examples and in wallcrush sources.)

I've not mentioned that within a page, the column counter in the LCD controller autoincrements each time we do a writing, so we don't care about it but when setting a new page. Considering this, tha code is rather simple.


Code example

Here is the example to write a screen directly to the LCD as published in Iwaki's ruputer page:

# ===============
# ===============

                mov             il_vram,a0
                mov             Alcd_c,a1
                mov             Alcd_d,a2
                mov             0x0014,d3

                mov             Clcd_page,d2
                movb            d2,(0,a1)
                mov             d3,(a1)

                mov             3,d1
                mov             (a0),d0
                mov             d0,(a2)
                mov             (2,a0),d0
                mov             d0,(a2)
                mov             (4,a0),d0
                mov             d0,(a2)
                mov             (6,a0),d0
                mov             d0,(a2)
                mov             (8,a0),d0
                mov             d0,(a2)
                mov             (10,a0),d0
                mov             d0,(a2)
                mov             (12,a0),d0
                mov             d0,(a2)
                mov             (14,a0),d0
                mov             d0,(a2)
                mov             (16,a0),d0
                mov             d0,(a2)
                mov             (18,a0),d0
                mov             d0,(a2)
                mov             (20,a0),d0
                mov             d0,(a2)
                mov             (22,a0),d0
                mov             d0,(a2)
                mov             (24,a0),d0
                mov             d0,(a2)
                mov             (26,a0),d0
                mov             d0,(a2)
                mov             (28,a0),d0
                mov             d0,(a2)
                mov             (30,a0),d0
                mov             d0,(a2)
                add             32,a0
                sub             1,d1
                bne             il_lcd_display_02

                mov             (a0),d0
                mov             d0,(a2)
                mov             (2,a0),d0
                mov             d0,(a2)
                mov             (4,a0),d0
                mov             d0,(a2)
                add             6,a0

                add             1,d2
                cmp             Clcd_page+8,d2
                bne             il_lcd_display_01

In the code example:

  • il_vram is a pointer to the buffer which holds the desired image contents.
  • Alcd_c is the address of the LCD control register, that is 0x0010.
  • Alcd_d is the address of the LCD data register, that is 0x0014.
  • Clcd_page is a constant with the value 0xb0 used to set the page
  • 0x0014 is a constant used to move to the first column, that is 0x40
    (0x00: set lower nibble to 0; 0x14: set higher nibble to 4)


Reading LCD contents

We have said before that normally we won't want to read the LCD contents, as we have used a intermediate buffer to write them, so we already have the the contents of the display. Anyway it's rather simple, so for the sake of completeness I'll describe it here too ;-).

Reading the LCD contents is very similar to writing to screen, as the first thing you have to do is setting the page to read and the column using the control register.

Then you have to do a dummy read from the data port (you read one byte from the data port and discard the contents). Finally, subsequent read operations provides us with the contents of the LCD controller memory.



Synchronizing with the vertical retrace

Well, it doesn't seem to have what I understand for vertical retrace, but there is something similar. The LCD have 64 lines, named in the documentation as COM0..COM63. The CPUport4 (?) has a bit that is set when the display is processing COM63, that is, the last line, cero otherwise. This register is located in address 0xFFD4, and the bit to look is b6.

Using the internal RAM of the microprocessor

The onHand/Ruputer has the following memory:

Description Address
1kB microprocessor embedded RAM 0xf000-0xf3ff
16kB microprocessor embedded ROM 0x80000-0x83FFF
128kB external SRAM 0x1000000-0x11FFFF
512kB external MROM 0x200000-0x27FFFF

Apart of this, of course, it has the external Flash memory that it uses as secondary storage medium (e.g. hard disk).

Normally, user programs are confinated to the last 64kB of external RAM, but in practice there is some free space left in the much-faster internal RAM.

The last 384 bytes of the internal RAM are free (0xf280-0xf3ff) and we can copy a routine to that memory and execute directly from internal RAM. Executing from internal RAM is very, very fast compared to SRAM; approximately 4 times faster, so it's the right place to put the sample code explained above that transfer the buffer to LCD.

Transforming the crisp b/w LCD display into a flashing 4-gray LCD

This is a trick that has been used since the early days of computing... if you bit-blit some pixel from white to black very fast it appears gray, its intensity being funtion of the time it was black over the time it was white.

In the wallcrush game this is used to obtain 4 shades of gray. The method is rather simple, but one have to be careful in the coding.


  1. The 4-shade rendering engine is operating all the time, hooked to the 64-hz interrupt
  2. Every time a 64-hz interrupt is triggered, it can change the image that is being displayed in the LCD. The simplest way is two cicles display the high order image and one cicle display the low order one, then repeat. This way there are two buffers of the entire screen: high order (its displayed 2/3 of the time) and low order (it's displayed 1/3 of the time).
  3. There have to be an API to change simultaneously both images; this can be done by exchanging pointers to the new images.

Information from SED1560 manual, and various code examples (wallcrush gamesources, Copyleft by
This text is distributed under the GNU FDL. Last updated (dd/mm/yyyy): 21/10/2001