# Interfacing a Toshiba T6963C Equipped GLCD with a PIC Microcontroller 2011-12-13

So you happen to have a GLCD with the Toshiba T6963C display controller and you want to interface it with a PIC and write a driver for it. You've scoured the internet, but the only tutorial you've found that shows how to do it is useless. Well, here I will demonstrate just exactly how to go about doing it.

For this tutorial, our example setup is the Microchip 44-pin Demo board, which features the PIC 16F887, driving a Solomon LM6271FWL 240 x 64 GLCD display. This display has 8K VRAM available as well as both a character generator ROM with built in character map as well as an external CG RAM for user defined characters. This display as you've probably figured out also uses the Toshiba T6963C controller chip.

The Hardware Interface

The physical hardware interface is rather simple. On our GLCD, we have an 8 bit bi-directional data port (lines D0-D7) which you can drive with one of the I/O ports on the PIC. Or if you want a serial interface, you can drive it with a 74HC595 serial in/parallel out latch, but the catch to this is that you cannot read from the display if that's all you use. You could just as well use a 74HC165 parallel in/serial out chip in conjunction with the 74HC595 latch if you need to read from the display. In my example, I am using PORTD on the PIC to direct drive the GLCD's data port.

On the control side of the GLCD, you will have the following lines -

C/D - Command/Data mode select (Command High/Data Low)
CE - Chip Enable (active when low)
READ Mode Select (active when low)
WRITE Mode Select (active when Low)
RESET - Resets the GLCD's controller (reset active when low)
Font Select - Allows you to select either a 6x8 or 8x8 font size (6x8 High/8x8 Low)

On my example display, I'm using the lower 3 bits of PORTA and PORTC to drive these lines as follows -

RA0 - CD
RA1 - RESET
RA2 - Font Select
RC0 - WRITE
RC2 - CE

Before we get into how to initialize the display we first need to talk about the power up sequence. On your GLCD, you will find that you have connections for two power supplies. The first supply is the +5V logic supply. This supply powers the GLCD's controller, VRAM chips and all of the logic stuff that makes the GLCD work. The second supply is an adjustable 0-12V negative supply that powers the LCD screen itself. The reason it is adjustable is to provide a means of adjusting the display contrast. This supply should not be on until the logic supply has come up to voltage and the display controller has been run through the initialization sequence. Once the display initialization sequence is complete, you can then apply power to the GLCD from the second supply. This prevents latch up of the CMOS LSI (the T6963C and the LCD driver LSI).

Now let's talk about PIC speed. The setup and hold time for all of the control signals as well as data input/output on the T6963C is between 10 and 150nS. So as long as your PIC instruction clock does not run faster than 200nS per instruction (20MHz Fosc, which results in a 5MHz instruction clock), we don't have to worry about needing "nop" instructions or running delay loops in between control line switching/data throughput. On my example PIC, I'm running a 16MHz crystal, which results in an instruction clock frequency of 4MHz, and instructions are executed at a rate of 250nS per instruction (1/4MHz = 250nS). You can run slower than this if you want. It's not a requirement to run the PIC that fast.

Now that the power up sequence and PIC speed is understood, we can get into what needs to happen for the initialization sequence of the T6963C.

PIC Initialization Sequence

First, let's write our code to get our PIC set up and initialized. We will be setting up all of the ports as outputs with the exception of PORTD, which is our data in/out port. This port will be operating as a bidirectional data port so we will be defaulting this port to be an input, then switching it to output once we need to write to the GLCD. For this application, it's best to default the PIC data port to input since we have no way of guaranteeing that the data port on the GLCD won't switch to output mode when we're not directly addressing it. But since we have control of the PIC at all times, we can default the PIC data port to input so that it only appears as a high impedance load on the GLCD's data port, which keeps the PIC port from shorting the GLCD data port should the GLCD data port decide it wants to be an output during the time that we're not addressing it.

The other reason for defaulting the port to input is due to what the T6963C controller expects to see prior to writing data to it as per its timing chart. On the timing chart for the T6963C, it shows the data port floating during the setup time of the CE and read/write signals.

So...let's get the PIC set up. This part should be familiar -

Code:
;*************************************************************************************************
;*************************************************************************************************
;**												**
;**				16F887 Graphic LCD Driver					**
;**			     For Toshiba T6963C 240 x 64 GLCD					**
;**				       By Jon Wilder						**
;**			              Date: 12/01/2011						**
;**												**
;*************************************************************************************************
;*************************************************************************************************
;**												**
;**												**
;**												**
;**												**
;** Processor Type: PIC 16F887									**
;** Error Level: -302/Suppress all assembler bank select warnings				**
;** Reference header file P16F887.INC for SFR and Config option labels				**
;**												**
;** Configuration Word 1									**
;**												**
;** In Circuit Debug Off (Default)								**
;** Low Voltage Programming Off									**
;** Fail Safe Clock Monitor Off 								**
;** Internal External Switchover Off								**
;** Brown Out Reset Off										**
;** Data Code Protection Off (Default)								**
;** Code Protection Off (Default)								**
;** RA5 has MCLR Function (Default)								**
;** Power Up Timer On										**
;** Watchdog Timer Off										**
;** High Speed XT Oscillator									**
;**												**
;** Configuration Word 2									**
;**												**
;** Program ROM Write Protection Off (Default)							**
;** Brown Out Reset 4.0V (Default)								**
;**												**
;** Fosc = 16MHz										**
;**												**
;*************************************************************************************************
;*************************************************************************************************

list		p=16F887, r=dec, w=-302
include		<P16F887.INC>
__config	_CONFIG1, _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _PWRTE_ON & _WDT_OFF & _FOSC_HS

;*************************************************************************************************
;**												**
;**				RAM Location Constants						**
;**												**
;*************************************************************************************************

cblock		0x20
TEMP			;temp buffer
DATAL			;instruction data low byte
DATAH			;instruction data high byte
COMMAND			;instruction
TABLECOUNT		;table counter
endc

cblock		0x70
W_TEMP			;interrupt context save for W
STATUS_TEMP		;interrupt context save for STATUS
PCLATH_TEMP		;interrupt context save for PCLATH
COUNT1			;delay counter 1
COUNT2			;delay counter 2
COUNT3			;delay counter 3
DATA_EE_DATA		;data EEPROM data buffer
endc

;*************************************************************************************************
;**												**
;**				Control/Data Line Labels					**
;**												**
;*************************************************************************************************

WR_EN		EQU		2			;write mode enable
WR_DIS		EQU		7			;write mode disable
RD_EN		EQU		1			;read mode enable
RD_DIS		EQU		7			;read mode disable

;control lines

#define		CD		PORTA,RA0		;GLCD command/data
#define		RST		PORTA,RA1		;GLCD reset
#define		FONT		PORTA,RA2		;GLCD font select (6x8 or 8x8)
#define		WRITE		PORTC,RC0		;GLCD write enable/disable (active low)
#define		CE		PORTC,RC2		;GLCD chip enable/disable (active low)

;data port

#define		D0		PORTD,RD0		;GLCD data port bit 0
#define		D1		PORTD,RD1		;GLCD data port bit 1
#define		D2		PORTD,RD2		;GLCD data port bit 2
#define		D3		PORTD,RD3		;GLCD data port bit 3
#define		D4		PORTD,RD4		;GLCD data port bit 4
#define		D5		PORTD,RD5		;GLCD data port bit 5
#define		D6		PORTD,RD6		;GLCD data port bit 6
#define		D7		PORTD,RD7		;GLCD data port bit 7

;*************************************************************************************************
;**												**
;**				Start of Main Code						**
;**												**
;*************************************************************************************************

org		0x000			;reset vector

org		0x004			;interrupt vector

;*************************************************************************************************
;**												**
;**				Initialization Routine						**
;**												**
;*************************************************************************************************

START		clrf		PORTA			;init ports
clrf		PORTB
clrf		PORTC
clrf		PORTD
clrf		PORTE
banksel		ANSEL			;bank 3
clrf		ANSEL			;all ports digital I/O
clrf		ANSELH
banksel		TRISA			;bank 1
clrf		TRISA			;PORTA, PORTB, PORTC, and PORTE outputs
clrf		TRISB			;PORTD defaults to input
clrf		TRISC
clrf		TRISE
banksel		0			;bank 0
So now we have the basic setup of the PIC itself down. All port latches have been cleared, all ports have been configured for digital I/O, and all ports have been assigned as outputs with the exception of PORTD, which is defaulted to input mode.

Notice in my PIC code I have also pre-defined some RAM constants to use as buffers. DATAL and DATAH are the registers we will use for sending data bytes to the GLCD, COMMAND is the register that will be used to send command bytes to the GLCD, while TEMP and TABLECOUNT are general purpose working buffers that we will use throughout this bit of test code. In common RAM, I have pre-defined the buffers used for our software stack for interrupt context saving, delay counters, and buffers for the data EEPROM address and data buffers. These last two buffers I decided to place in common RAM to minimize the amount of required bank selecting for writing to/reading from the PIC's internal data EEPROM. By placing the delay counters in common RAM, this enables you to run delay loops no matter which bank happens to be the active bank.

The #defines you'll see I have equated to labels which correspond with the name of each of the GLCD's control lines. This makes things easier for two reasons. Reason 1 is that you don't have to remember the physical port/bit number to address everytime you want to set/clear that control line. Reason 2 is that it makes life much easier should we have to reassign that line to a different pin on the PIC. Rather than having to change every instruction which addresses that line, we can simply change the port/bit number that the label points to in the #define statement.

The WRT_EN, WRT_DIS, RD_EN and RD_DIS labels are equated to values which will pull the CE as well as the read/write lines up/down simultaneously upon loading these values into the control PORTC. According to the timing diagram for the T6963C, this is how these lines should be driven.

GLCD Initialization Sequence

Now that we have the base initialization of the PIC down, let's talk about the initialization of the T6963.

Upon power up, the GLCD must be pulled into hard reset for a period of not less than 1mS after the logic supply voltage reaches 4.75V. According to the T6963C data sheet, you have two ways in which to reset the controller. Method 1 is to use a hardware reset comprised of an external 10K pull up resistor and a 0.1uF pull down capacitor on the reset pin of the GLCD. Method 2 is to connect the reset pin of the GLCD to one of the I/O lines on the PIC, and have the PIC pull the GLCD into hard reset in the PIC code. I chose to use Method 2.

In my code, I have a fixed 50mS delay loop that I run after pulling the GLCD into reset, which more than satisfies the "pull into reset for 1mS after Vlogic = 4.75V" requirement. While I have the GLCD in reset, I also take the time to get the control lines set up to their default state while the GLCD is in hard reset mode. I set all of the control lines high with the exception of RESET with 6x8 font selected (font line high). I then run the 50mS delay loop, then disable reset. This initial bit of code will reside directly after our PIC initial setup routine and will look something like this -

Code:
GLCD_INIT	bcf		RST			;reset GLCD
movlw		5 			;Command mode and 6x8 font
movwf		PORTA
movlw		7 			;disable read, write, and chip enable
movwf		PORTC
call		Delay50mS		;wait 50mS
bsf		RST			;disable reset
OK...so far, we have the PIC and the GLCD logic circuit powered on, the PIC has been setup, and the GLCD has been pulled into hard reset, our control lines are set to default states (all high w/6x8 font size), and the reset has now been disabled. Now we need to start thinking about the initialization sequence of the T6963C. The initialization sequence will involve us sending a series of data and instructions to the GLCD to set things like the graphics RAM area, text RAM area, setting the RAM address pointer, etc etc. So before we can begin to do that, we need to first know a little bit about the T6963C instruction set as well as the instruction protocol.

The T6963C Instruction Set and Protocol

The T6963C has an instruction set which is comprised of 3 types of instructions -

Two data byte instruction
One data byte instruction
Instruction Only

On the instruction only instructions, the high nibble of the hex instruction byte is the instruction itself while the low nibble contains the settings for the instruction. On the one and two data byte instructions, the data bytes themselves are the settings that will be internally set by the instruction byte.

Now the data bytes and the instruction bytes must be sent to the T6963C in a specific order. On the instructions which have data bytes, you will send the 1st data byte first, then the second data byte, then the instruction byte last. The CD line must be driven low when sending the data bytes (data mode), and must be driven high when sending instruction bytes (command mode).

Prior to sending each byte, it is required that you check the lower 2 bits of the T6963C's status register to ensure that it is ready to receive data. These bits are known as STA0 and STA1 and both of these bits must be high before sending data to the GLCD. Having said that, the order of operations will go as such -

For Two Data Byte Instructions

STA0 & STA1 = 1 ---> Send 1st Data Byte ---> STA0 & STA1 = 1 ---> Send 2nd Data Byte ---> STA0 & STA1 = 1 ---> Send Instruction Byte

For One Data Byte Instructions

STA0 & STA1 = 1 ---> Send Data Byte ---> STA0 & STA1 = 1 ---> Send Instruction Byte

For Instruction Only Instructions

STA0 & STA1 = 1 ---> Send Instruction Byte

How To Send Instructions/Data to the GLCD

Since this is a function that we will be performing repeatedly, it would be best to can these code routines as a subroutine that will get called from main code. This is where our DATAL, DATAH, and COMMAND buffers will come in. We will load these buffers with the data and the instructions to be written to the GLCD in main code, then the subroutine will access these buffers when we call it to send the data/instructions.

On two data byte instructions, the first variable to get sent to the GLCD will be DATAL while the second variable to get sent will be DATAH. COMMAND will be the last to get sent.

Since we also need to do a check on status bits STA0 and STA1 in the GLCD's status register prior to sending each byte, it would be best to also can this routine as a subroutine that we can call repeatedly as well. As much as we will have to perform these tasks while making the GLCD work, this will save us LOTS of code space.

First we will write 2 subroutines to enable/disable status read mode -

Code:
movwf		PORTC
return

movwf		PORTC
return
Then we will code up the status check routine -

Code:
;check GLCD status

btfss		D1
goto		$-2 ;no, check again call StatReadDis ;disable status read return The first instruction in LCDstat calls the subroutine that places the GLCD in command mode and drives the CE and READ lines low simultaneously with the RD_EN operand that we created in our equates table, which places the GLCD in read mode. When the GLCD is in command mode while in read mode, this tells the T6963C that you want to read the status register in the T6963C. The T6963C then places the current state of its status bits on the GLCD's data port where they are available to be read. The next 3 instructions continuously poll data lines D0 and D1, which is where the current state of the T6963C's status bits STA0 and STA1 are placed. It continuously polls these lines until they are both high. Once they are both high, the next instruction calls the status read disable subroutine, which disables read mode by setting both READ and CE high simultaneously with the RD_DIS operand, then the return instruction returns back to the code segment that called the LCDStat subroutine and our status check is complete. Now let's take a look at our data/instruction write routine - Code: ;command/data write Command bsf CD ;command mode goto$+2
DWrite		bcf		CD			;data mode
movwf		PORTD			;place write data on data port latch
movlw		WR_EN			;chip enable low
movwf		PORTC			;enable write
banksel		TRISD			;bank 1
clrf		TRISD			;RD0-RD7 output
banksel		0			;bank 0
movlw		WR_DIS			;disable write
movwf		PORTC			;chip enable high
banksel		TRISD			;bank 1
comf		TRISD,F			;RD0-RD7 input
banksel		0			;bank 0
return
You will notice that there are two labels in this subroutine: Command and Dwrite. We can call this routine using both of these labels depending on whether we are writing a data byte or a command byte. This way we have one subroutine for both rather than having to can two write routines just for the sake of setting up the CD line, which again saves code space.

Now the way this works is that the data to be written to the GLCD must first be preloaded in the W register prior to calling either Command or DWrite.

Let's walk through the code. Assume we are writing the data byte that resides in DATAL, we would execute these instructions -

Code:
movfw		DATAL
call		DWrite
The first instruction at the label DWrite sets the CD line low, which places the GLCD in data mode. The next instruction takes the data in W that came from the DATAL buffer and drops it into the PORTD output latch. Then the CE and WRITE lines are dropped simultaneously by dropping the WR_EN value into the PORTC control port. We are almost ready to write, but since our data port driver PORTD is defaulted to input mode, we must first set it to output mode.

So the next instruction selects RAM bank 1. We then clear the TRISD register, which sets PORTD up as an output. Since we preloaded the PORTD output latch prior to doing this, the data we are sending is available on the PORTD pins immediately after switching PORTD to output mode, thereby providing a clean data write. We then bank select back to bank 0.

Once in bank 0, we raise both CE and WRITE back high simultaneously by dropping the value of WRT_DIS into the PORTC control register. We then bank select back to bank 1, compliment the TRISD register which switches all of the TRISD bits back to all 1's, making PORTD an input again. We then bank select back to bank 0, then return back to the code segment that called the subroutine.

Now, let's assume that we're writing a command. Our command will be preloaded in the COMMAND buffer. We will first preload W with the data in the COMMAND buffer, then we would call Command with these two instructions -

Code:
movfw		COMMAND
call		Command
The first instruction at the label "Command" sets the CD line high, placing the GLCD in command mode. The next instruction tells the program counter to jump ahead two lines of code ($+2 = Current line + 2), thereby skipping over the instruction that would otherwise clear the CD line to place it in data mode. It then drops the data that is in W that came from the COMMAND buffer into the PORTD output latch, and resumes in the same fashion that it does for a data byte write. Clever isn't it? Now...we need a final subroutine that we will call in main code that will call the status check, command write and data write subroutines above in order to make all of this work. However, since we have 3 types of instructions (two data byte, one data byte and command only), we need to make this subroutine work for all 3 instruction types. This subroutine should look like this - Code: TwoData call LCDStat movfw DATAL call DWrite OneData call LCDStat movfw DATAH call DWrite NoData call LCDStat movfw COMMAND call Command return Now how we will use this is we will first load our data bytes into the DATAL and DATAH buffers. DATAL will get preloaded with the first data byte while DATAH will get loaded with the second data byte. If it is a one data byte instruction, we will treat the data byte as if it were a second data byte by loading it into the DATAH buffer. We will then preload the instruction into the COMMAND buffer. Let's assume that we want to send the "Set Text Home Address instruction. This is a two data byte instruction that sets the starting address of the text RAM. The first data byte is the low byte of the address while the second data byte is the high byte of the RAM address. Let's say for example that we want to set the text home address to 0x1700. We would first load 0x00 into DATAL, then we would load 0x17 into DATAH. We would then load 0x40 into the COMMAND buffer, which is the hex instruction for Set Text Home address. We would then call the TwoData subroutine - Code: TextHome movlw 0x00 movwf DATAL movlw 0x17 movwf DATAH movlw 0x40 movwf COMMAND call TwoData The first two instructions preload buffer DATAL with the low address byte of the text RAM start address. The next two instructions preload buffer DATAH with the high byte of the text RAM start address. The next two preload buffer COMMAND with the hex value of the "Set Text RAM Home Address" instructions while the last instruction calls the above subroutine TwoData. The first instruction in TwoData calls our status bit check subroutine labeled LCDStat above. Once the status bits are checked and it returns from this subroutine, the next instruction preloads W with the data in the DATAL buffer, then calls the DWrite subroutine, which writes the data byte to the GLCD. Upon completing the write, it returns and then calls LCDStat again to check the GLCD status bits again. Once done with this, it returns, preloads W with the data in buffer DATAH, then calls Dwrite again. Upon completing the write, it returns to run LCDStat again to check the status bits. Upon completion of the status bit check, it returns, preloads W with the instruction code in buffer COMMAND, then calls Command, which placed the LCD in command mode, then writes the instruction to the GLCD. Upon completing the write, it returns back, then returns back to our main code. Now, if this were a one data byte instruction, we would follow the same routine in main code, but we would preload the data byte into DATAH, then call "OneData", which skips the routine that would send the data in DATAL to the GLCD. If this were a command only instruction, we would call "NoData" instead, which skips the part of the subroutine which writes the data bytes to the GLCD. Again, clever isn't it? Now imagine if everytime we wanted to write to the GLCD we had to code out all of those instructions? This would take up LOTS of program ROM space really quick. By having these 3 subroutines, they only have to reside in program ROM once, which saves us valuable code space on the chip. So we now have some code that will send data and instructions to the GLCD. Now we will talk about the instructions which will be required to initialize the GLCD. T6963C Initialization Routine & Instructions There are 5 instructions which we will use to initialize the T6963C. In order in which we will send them, they are - Mode Set (0x80) - This instruction is an "instruction only" instruction which sets the operating mode of the GLCD. The high nibble (8) tells the T6963C that it is a mode set instruction while the low nibble will tell it the operating mode. The available operating modes are - * Low Nibble = 0 - Text logically "OR'ed" with graphics with CG ROM on * Low Nibble = 1 - Text logically "XOR'ed" with graphics with CG ROM on * Low Nibble = 3 - Text logically "AND'ed" with graphics with CG ROM on * Low Nibble = 4 - Text Attribute mode with CG ROM on * Low Nibble = 8 - Text logically "OR'ed" with graphics with CG RAM on * Low Nibble = 9 - Text logically "XOR'ed" with graphics with CG RAM on * Low Nibble = B - Text logically "AND'ed" with graphics with CG RAM on * Low Nibble = C - Text Attribute mode with CG RAM on When CG ROM is on, character data values 0x00-0x7F correspond with the built in character map of the T6963C while character data vaules of 0x80-0xFF correspond with user defined characters which reside in the external CG RAM. When CG RAM mode is on, character codes 0x00 - 0xFF correspond only with user defined characters which reside in the external CG RAM. As or goal here is to init the display and get it ready to display something and it would take a whole other article to explain all of the operating modes in detail, for now we are going to set Text Attribute mode with CG ROM on for the sake of simplicity. So for the first code routine, we will send the mode set instruction value of 0x84, which will place the GLCD in Text Attribute Mode with CG ROM on. Turning on the CG ROM gives us access to the T6963C's built in character map while also allowing us to program in 128 user defined characters - Code: ModeSet movlw 0x84 movwf COMMAND call NoData Graphics Home Address Set (0x42) - This is a two data byte instruction that sets the starting address of the graphics RAM in VRAM. The T6963C is hardwired to allocate 5KB (5120 bytes) of the VRAM space for graphics RAM while allowing us to send the address where we want this space to start at. In our code, we will start the graphics RAM space at the first address 0x0000. So we will first load the address low byte into DATAL as the first data byte. We will then load DATAH with the address high byte. Lastly, we will load COMMAND with 0x42, then call the TwoData subroutine - Code: ;graphics RAM start address 0x0000 GraphicsHome movlw 0 movwf DATAL movlw 0 movwf DATAH movlw 0x42 movwf COMMAND call TwoData Now our graphics RAM space occupies the address range of 0x0000 - 0x13FF in VRAM. Graphics Area Set (0x43) - This is a two data byte instruction which sets the number of columns of graphic data the display will support. The required data value for this instruction will vary with display size. Since our display is 240x64, there are a total of 40 columns. This means that we will send the hex value of 0x28 (decimal 40) as the first data byte. On this instruction, the second data byte is always 0x00. Code: ;graphics area 40 rows for 240x64 display GraphicsArea movlw 0x28 movwf DATAL movlw 0 movwf DATAH movlw 0x43 movwf COMMAND call TwoData Text Home Address Set (0x40) - This is a two data byte instruction that sets the starting address of the text RAM in VRAM. The T6963C is hardwired to allocate 1.2KB (1280 bytes) of the VRAM space for text RAM while allowing us to send the address where we want this space to start at. In our code, we will start the text RAM space at address 0x1700. So we will first load the address low byte into DATAL as the first data byte. We will then load DATAH with the address high byte. Lastly, we will load COMMAND with 0x40, then call the TwoData subroutine - Code: ;text RAM start address 0x1700 TextHome movlw 0 movwf DATAL movlw 0x17 movwf DATAH movlw 0x40 movwf COMMAND call TwoData Text Area Set (0x42) - This is a two data byte instruction which sets the number of columns of text data the display will support. The required data value for this instruction will vary with display size. Since our display is 240x64, there are a total of 40 columns. This means that we will send the hex value of 0x28 (decimal 40) as the first data byte. On this instruction, the second data byte is always 0x00. Code: ;text area 40 rows for 240x64 display TextArea movlw 0x28 movwf DATAL movlw 0 movwf DATAH movlw 0x41 movwf COMMAND call TwoData Offset Register Set (0x22) - This is a two data byte instruction that sets the starting address of the CG RAM space. The T6963C is hard wired to allocate 1KB (1024 bytes) of the VRAM for character generator RAM space and writing a value to the offset register sets the starting address of this space. The values to write to this register will reserve the following address spaces for CG RAM - 0x00 - CG RAM occupies address locations 0x0000 - 0x07FF 0x01 - CG RAM occupies address locations 0x0800 - 0x0FFF 0x02 - CG RAM occupies address locations 0x1000 - 0x17FF 0x03 - CG RAM occupies address locations 0x1800 - 0x1FFF We are going to allocate locations 0x1800 - 0x1FFF for our CG RAM space. So the first data byte will be 0x03. The second data byte on this instruction is always 0 - Code: ;set offset register CGROMSet movlw 0x03 movwf DATAL movlw 0 movwf DATAH movlw 0x22 movwf COMMAND call TwoData Once we have the graphics and text home addresses set, graphics and text area set for the correct size screen, and the CG RAM space allocated, the base display initialization is complete and we are now ready to write data to the display. Clearing The GLCD Screen The one thing I had to figure out the hard way was that the VRAM locations in both the text and graphics memory spaces comes up in random states when the GLCD powers up. So we need to code up a routine that will zero out the VRAM. If we don't do this, your display will come up with just a bunch of gibberish and garbage characters. But before we do this, we need to cover the address pointer set as well as the data auto read/write instructions. Address Pointer Set (0x24) - This is a two byte instruction that allows you to place the address pointer at a specific VRAM address. Any data read/write instructions will read from/write to the VRAM memory location that the address pointer is set to. The first data byte is the address low byte while the second data byte is the address high byte. With the exception of the GLCD's status register, the address pointer must be set to the address of the VRAM location you want to read from/write to prior to executing a read or a write instruction. Since setting the address pointer is a task that we will have to do repeatedly, it's best to can it as a subroutine as well. Such a subroutine should look as such - Code: ADDR_PTR movlw 0x24 movwf COMMAND call TwoData return Then in main code, you would load DATAL and DATAH with the low byte and high byte of the address that you're setting the pointer to respectively, then call the ADDR_PTR subroutine. For example, say we want to position the address pointer to the first VRAM location of the text memory space, which is addres 0x1700 - Code: ADDRPointer movlw 0x00 movwf DATAL movlw 0x17 movwf DATAH call ADDR_PTR This will then set the address pointer to the first location in text RAM, and writing a character code to this location will position the character in the very top left hand corner of the display. The only time we really have to play with the address pointer though is when we're starting a new line of data on the screen. The T6963C has instructions which will autoincrement the pointer as you write data to the display, which makes things convenient. Data Auto Read/Write (0xB0 - 0xB2) - These instructions are a command only instruction but it works different from all of the other T6963C instructions. What this instruction does is allows us to continuously send data bytes to display consecutively without having to send a "data write" instruction in between each byte. It writes the first byte of data to the display, then autoincrements the address pointer, then writes the next data byte that we send to the display. It continuously does this until we send the Data Auto Reset instruction. There are 3 instructions in this category - 0xB0 - Data Auto Write Set 0xB1 - Data Auto Read Set 0xB2 - Data Auto Reset When we want to use auto write mode, we first need to set the address pointer to the starting location in the section of VRAM that we will be writing to, check status bits STA0 and STA1 in the GLCD's status register, then we send the Data Auto Write Set instruction. We then need to check bits STA0 and STA1 again. Once they go high, we then need to check GLCD status bit 3, which is the Data Auto Write Ready status bit DAWRDY. Once this bit is high, we can send the first display data to write, then continuously send the bytes to be written consecutively, checking the DAWRDY bit in between each byte sent, without having to send a write instruction or reposition the address pointer in between each one. In between each data byte that we send to the display in auto write mode, we will be checking the DAWRDY bit instead of STA0 and STA1, which are invalid in Data Auto Read or Data Auto Write mode. For data auto read mode, the same thing applies but we would want to check bit 2 in the GLCD's status register, which is the DARRDY bit. So now we need subroutines that will check the DAWRDY and DARRDY bits - Code: DARStat call StatReadEn ;enable status read btfss D3 ;auto write ready? goto$-1
return

goto		$-1 call StatReadDis ;disable status read return Like the LCDStat routine, this one calls StatReadEn to enable status read mode. It then continuously polls bit 3 of the GLCD's status register until it goes high. Once high, it then calls the StatReadDis subroutine to disable status read mode, then returns back to the code that called it. We now have a way to poll the DAWRDY bit in between each byte we send to the display. We can continuously send bytes to write until we send the Data Auto Write Reset instruction. So...back to our clear display routine. So that we can call this routine whenever we want to clear the display, we will do it up as a subroutine - Code: DisplayClear movlw 160 ;init counter movwf COUNT1 ;to clear 320 VRAM locations call DAWSet call DAWStat movlw 0 ;write 0 to display call DWrite decfsz COUNT1,F ;decrement COUNT1 and continue writing goto$-4			;data if COUNT1 > 0
movlw		160			;re-init COUNT1
movwf		COUNT1			;decrement COUNT2 and continue writing
decfsz		COUNT2,F		;data if COUNT2 > 0
goto		$-8 call DAWReset return ;done Then in main code, we will load DATAL and DATAH with the starting address byte, load COUNT2 with the number of times we need to run the DisplayClear routine (20x for graphics memory as 160 x 20 = 5120 bytes/8 times for text memory as 160 x 8 = 1280 bytes), then call this routine - Code: ;clear graphics RAM movlw 0 ;set address pointer to 0x0000 movwf DATAL movlw 0x00 movwf DATAH call ADDR_PTR movlw 20 movwf COUNT2 call DisplayClear ;clear text RAM movlw 0 ;set address pointer to 0x1700 movwf DATAL movlw 0x17 movwf DATAH call ADDR_PTR movlw 8 movwf COUNT2 call DisplayClear The first group of instructions loads DATAL and DATAH with 0x0000, then calls the display clear routine. This will clear out our graphics RAM. Then once it returns, it loads DATAL and DATAH with address 0x1700, which is the start address of the text RAM space, then calls the display clear routine once more. Now let's walk through the display clear routine itself. Our 240x64 display has a total of 320 character spaces when using a 6x8 font (40 columns x 8 rows). So we need to clear out a total of 320 RAM locations in both the graphics and text memory space. So I have two counters...COUNT1 and COUNT2. COUNT1 is loaded with the value of 160 while COUNT2 is loaded with the value of 2. COUNT1 tells the code to repeat what it's doing 160 times. Once the code has ran 160 times, COUNT1 is preloaded with 160 again and COUNT2 is decremented, then makes it loop back once more to clear out an additional 160 VRAM locations. Now, DATAL and DATAH should already have the address bytes loaded. So we then call the address pointer set subroutine to set our address pointer to the starting location of the VRAM space that we're clearing.We then send the Data Auto Write instruction. Once we have sent the Data Auto Write instruction, we then check status bits STA0 and STA1 again. Once those are both high, we then check the DAWRDY bit in the GLCD status register. Once this bit is high, we send the value of 0 to be written to the GLCD, which will write just a blank character to the first display location. Then COUNT1 gets decremented, and the code continuously loops...checking DAWRDY, then sending a 0 to the GLCD and decrementing COUNT1 each time. Once COUNT1 = 0, it reloads COUNT1 with 160 again, decrements COUNT2, then loops back to continue writing all 0's to each memory location sequentially. Once COUNT2 = 0, then we check DAWRDY once more, then we send the Data Auto Write Reset instruction, which cancels data auto write mode. So now the graphics RAM is cleared, but we still need to clear out text RAM. So in main code, we load DATAL with 0x00 and DATAH with 0x17, then call the display clear routine again. The address pointer will then be positioned at the starting location of text RAM. Cursor Now we'll get a cursor up on our display. The first instruction to cover with this is the "Cursor Pattern Set" instruction. Cursor Pattern Set (0xA0 - 0xA7) - This is an instruction only instruction that sets the cursor thickness. You can have a single line cursor, a 2 line cursor, a 3 line cursor, etc etc...all the way up to an 8 line cursor which is a "block" style cursor. The 0xA is the instruction while the 0-7 is the cursor pattern - Low Nibble = 0 - 1 line cursor Low Nibble = 1 - 2 line cursor Low Nibble = 2 - 3 line cursor Low Nibble = 3 - 4 line cursor Low Nibble = 4 - 5 line cursor Low Nibble = 5 - 6 line cursor Low Nibble = 6 - 7 line cursor Low Nibble = 7 - 8 line cursor So let's set our cursor pattern - Code: ;single line cursor CSRPattern movlw CSR1 movwf COMMAND call NoData Cursor Pointer Set (0x21) - This is a two byte instruction that allows us to tell the GLCD where to position the cursor on the display. The first data byte controls the horizontal movement of the cursor and should be the column number you wish to place the cursor in. The second data byte controls the vertical movement of the cursor and should be the row/line number you wish to place the cursor in. We will be positioning the cursor at the top left corner of the screen so we would set DATAL and DATAH both to 0. Since we will be moving the cursor around the screen repeatedly, I have canned the cursor pointer set instruction as a subroutine as well - Code: CSR_PTR movlw CSR_POINTER movwf COMMAND call TwoData return Now we will load DATAL and DATAH with the column/row numbers to position the cursor, then call the cursor pointer set subroutine - Code: ;set cursor pointer on line 0 column 0 CSRPointer movlw 0 movwf DATAL movlw 0 movwf DATAH call CSR_PTR The next thing we need to do is set our address pointer to a default memory location. We'll set it to the start of text RAM at address 0x1700 - Code: ;position address pointer at start of line 2 ADDRPointer movlw 0x0 movwf DATAL movlw 0x0 movwf DATAH call ADDR_PTR Now there is one more thing we need to do before our display will work. We need to set the display mode with the Display Mode Set instruction. Display Mode Set (0x90 - 0x9F) - This is an instruction only instruction that controls the active/inactive state of the text RAM, graphics RAM, cursor, and cursor blink. The available display modes are as follows - Low Nibble = 0 - Display Off Low Nibble = 4 - Text RAM Only, no cursor Low Nibble = 6 - Text RAM only, cursor on, cursor blink off Low Nibble = 7 - Text RAM only, cursor on, cursor blink on Low Nibble = 8 - Graphics RAM only, no cursor Low Nibble = A - Graphics RAM only, cursor on, cursor blink off Low Nibble = B - Graphics RAM only, cursor on, cursor blink on Low Nibble = C - Text and Graphics RAM, no cursor Low Nibble = E - Text and Graphics RAM, cursor on, cursor blink off Low Nibble = F - Text and Graphics RAM, cursor on, cursor blink on In text attribute mode, both the graphics and text RAM must be on since the T6963C stores the attribute data in graphics RAM. No graphics are possible in this mode. So we will be setting this up for mode F, which will turn on both text and graphics RAM as well as the cursor and the cursor blink - Code: DisplayOn movlw 0x9F movwf COMMAND call NoData goto$
Now the last things to get added to our code are our delay subroutines and the starting template for the interrupt handler just in case we plan to do interrupts. The way I do it is that I have one delay routine that is a fixed 50mS delay loop. Then I have a 2nd delay loop that is variable that calls the 50mS delay multiple times for longer delays. How it works is that you preload W with the number of times you need to run the 50mS delay loop, then you call the variable delay subroutine. Once in that subroutine, the preloaded value in W gets dropped into a third counter that counts how many times you call the 50mS delay loop -

Code:
;fixed 50mS delay

Delay50mS	movlw		0xFF
movwf		COUNT1
movwf		COUNT2
decfsz		COUNT1,F
goto		$-1 decfsz COUNT2,F goto$-3
return

;*************************************************************************************************

;variable delay

Delay		movwf		COUNT3
call		Delay50mS
decfsz		COUNT3,F
goto		$-2 return This delay code as written will yield a 50mS delay loop assuming a 16MHz Fosc (4MHz instruction clock). To adjust it for slower clock speeds, add an instruction that loads buffer COUNT2 with the following values - Fosc = 4MHz - 0x40 Fosc = 8MHz - 0x7F Fosc = 12MHz - 0xC0 And last but not least, here is a suitable template to build on for an interrupt handler - Code: ;************************************************************************************************* ;** ** ;** Interrupt Handler ** ;** ** ;************************************************************************************************* ;interrupt context save ISR movwf W_TEMP swapf STATUS,W banksel 0 ;bank 0 movwf STATUS_TEMP movfw PCLATH movwf PCLATH_TEMP ;interrupt code goes here ;interrupt context restore ISRExit movfw PCLATH_TEMP movwf PCLATH swapf STATUS_TEMP,W movwf STATUS swapf W_TEMP,F swapf W_TEMP,W retfie The Complete Code OK...if you've been following along with the code examples and typing out your own version of this code, you should end up with a completed code file that resembles this - Code: ;************************************************************************************************* ;************************************************************************************************* ;** ** ;** 16F887 Graphic LCD Driver ** ;** For Toshiba T6963C 240 x 64 GLCD ** ;** By Jon Wilder ** ;** Date: 12/01/2011 ** ;** ** ;************************************************************************************************* ;************************************************************************************************* ;** ** ;** Header Information/Config Options ** ;** ** ;** ** ;** ** ;** Processor Type: PIC 16F887 ** ;** Default Radix: Decimal ** ;** Error Level: -302/Suppress all assembler bank select warnings ** ;** Reference header file P16F887.INC for SFR and Config option labels ** ;** ** ;** Configuration Word 1 ** ;** ** ;** In Circuit Debug Off (Default) ** ;** Low Voltage Programming Off ** ;** Fail Safe Clock Monitor Off ** ;** Internal External Switchover Off ** ;** Brown Out Reset Off ** ;** Data Code Protection Off (Default) ** ;** Code Protection Off (Default) ** ;** RA5 has MCLR Function (Default) ** ;** Power Up Timer On ** ;** Watchdog Timer Off ** ;** High Speed XT Oscillator ** ;** ** ;** Configuration Word 2 ** ;** ** ;** Program ROM Write Protection Off (Default) ** ;** Brown Out Reset 4.0V (Default) ** ;** ** ;** Fosc = 16MHz ** ;** ** ;************************************************************************************************* ;************************************************************************************************* list p=16F887, r=dec, w=-302 include <P16F887.INC> __config _CONFIG1, _LVP_OFF & _FCMEN_OFF & _IESO_OFF & _BOR_OFF & _PWRTE_ON & _WDT_OFF & _FOSC_HS ;************************************************************************************************* ;** ** ;** RAM Location Constants ** ;** ** ;************************************************************************************************* cblock 0x20 TEMP ;temp buffer DATAL ;instruction data low byte DATAH ;instruction data high byte COMMAND ;instruction TABLECOUNT ;table counter endc cblock 0x70 W_TEMP ;interrupt context save for W STATUS_TEMP ;interrupt context save for STATUS PCLATH_TEMP ;interrupt context save for PCLATH COUNT1 ;delay counter 1 COUNT2 ;delay counter 2 COUNT3 ;delay counter 3 DATA_EE_ADDR ;data EEPROM address buffer DATA_EE_DATA ;data EEPROM data buffer endc ;************************************************************************************************* ;** ** ;** Control/Data Line Labels ** ;** ** ;************************************************************************************************* ;control lines #define CD PORTA,RA0 ;GLCD command/data #define RST PORTA,RA1 ;GLCD reset #define FONT PORTA,RA2 ;GLCD font select (6x8 or 8x8) #define WRITE PORTC,RC0 ;GLCD write enable/disable (active low) #define READ PORTC,RC1 ;GLCD read enable/disable (active low) #define CE PORTC,RC2 ;GLCD chip enable/disable (active low) WR_EN EQU 2 WR_DIS EQU 7 RD_EN EQU 1 RD_DIS EQU 7 ;data port #define D0 PORTD,RD0 ;GLCD data port bit 0 #define D1 PORTD,RD1 ;GLCD data port bit 1 #define D2 PORTD,RD2 ;GLCD data port bit 2 #define D3 PORTD,RD3 ;GLCD data port bit 3 #define D4 PORTD,RD4 ;GLCD data port bit 4 #define D5 PORTD,RD5 ;GLCD data port bit 5 #define D6 PORTD,RD6 ;GLCD data port bit 6 #define D7 PORTD,RD7 ;GLCD data port bit 7 ;************************************************************************************************* ;** ** ;** Start of Main Code ** ;** ** ;************************************************************************************************* org 0x000 ;reset vector goto START ;jump to start of main code org 0x004 ;interrupt vector goto ISR ;jump to start of interrupt handler ;************************************************************************************************* ;** ** ;** Initialization Routine ** ;** ** ;************************************************************************************************* START clrf PORTA ;init ports clrf PORTB clrf PORTC clrf PORTD clrf PORTE banksel ANSEL ;bank 3 clrf ANSEL ;all ports digital I/O clrf ANSELH banksel TRISA ;bank 1 clrf TRISA ;PORTA, PORTB, PORTC, and PORTE outputs clrf TRISB ;PORTD defaults to input clrf TRISC clrf TRISE banksel 0 ;bank 0 GLCD_INIT bcf RST ;reset GLCD movlw 5 ;Command mode and 6x8 font movwf PORTA movlw 7 ;disable read, write, and chip enable movwf PORTC call Delay50mS ;wait 50mS bsf RST ;disable reset ;set operating mode ModeSet movlw 0x84 ;text attribute mode with CG ROM on movwf COMMAND call NoData ;graphics RAM start address 0x0000 GraphicsHome movlw 0 movwf DATAL movlw 0 movwf DATAH movlw 0x42 movwf COMMAND call TwoData ;graphics area 40 rows for 240x64 display GraphicsArea movlw 0x28 movwf DATAL movlw 0 movwf DATAH movlw 0x43 movwf COMMAND call TwoData ;text RAM start address 0x1700 TextHome movlw 0 movwf DATAL movlw 0x17 movwf DATAH movlw 0x40 movwf COMMAND call TwoData ;text area 40 rows for 240x64 display TextArea movlw 0x28 movwf DATAL movlw 0 movwf DATAH movlw 0x41 movwf COMMAND call TwoData ;set offset register CGROMSet movlw 0x03 movwf DATAL movlw 0 movwf DATAH movlw 0x22 movwf COMMAND call TwoData ;clear graphics RAM movlw 0 ;set address pointer to 0x0000 movwf DATAL movlw 0x00 movwf DATAH call ADDR_PTR movlw 20 movwf COUNT2 call DisplayClear ;clear text RAM movlw 0 ;set address pointer to 0x1700 movwf DATAL movlw 0x17 movwf DATAH call ADDR_PTR movlw 8 movwf COUNT2 call DisplayClear ;single line cursor CSRPattern movlw 0xA0 movwf COMMAND call NoData ;set cursor pointer on line 0 column 0 CSRPointer movlw 0 movwf DATAL movlw 0 movwf DATAH call CSR_PTR ;position address pointer at start of line 2 ADDRPointer movlw 0x0 movwf DATAL movlw 0x0 movwf DATAH call ADDR_PTR ;text and graphics RAM on, cursor on with cursor blink DisplayOn movlw 0x9F movwf COMMAND call NoData goto$

;*************************************************************************************************
;**												**
;**				Delay Loops							**
;**												**
;*************************************************************************************************

;fixed 50mS delay

Delay50mS	movlw		0xFF
movwf		COUNT1
movwf		COUNT2
decfsz		COUNT1,F
goto		$-1 decfsz COUNT2,F goto$-3
return

;*************************************************************************************************

;variable delay

Delay		movwf		COUNT3
call		Delay50mS
decfsz		COUNT3,F
goto		$-2 return ;************************************************************************************************* ;** ** ;** Position Address Pointer ** ;** ** ;************************************************************************************************* ADDR_PTR movlw 0x24 movwf COMMAND call TwoData return ;************************************************************************************************* ;** ** ;** Position Cursor Pointer ** ;** ** ;************************************************************************************************* CSR_PTR movlw 0x21 movwf COMMAND call TwoData return ;************************************************************************************************* ;** ** ;** Command Send Routines ** ;** ** ;************************************************************************************************* TwoData call LCDStat movfw DATAL call DWrite OneData call LCDStat movfw DATAH call DWrite NoData call LCDStat movfw COMMAND call Command return ;************************************************************************************************* ;** ** ;** GLCD Read/Write/Status Check ** ;** ** ;************************************************************************************************* ;command/data write Command bsf CD ;command mode goto$+2
DWrite		bcf		CD			;data mode
movwf		PORTD			;place write data on data port latch
movlw		WR_EN			;chip enable low
movwf		PORTC			;enable write
banksel		TRISD			;bank 1
clrf		TRISD			;RD0-RD7 output
banksel		0			;bank 0
movlw		WR_DIS			;disable write
movwf		PORTC			;chip enable high
banksel		TRISD			;bank 1
comf		TRISD,F			;RD0-RD7 input
banksel		0			;bank 0
return

;*************************************************************************************************

;check GLCD status

btfss		D1
goto		$-2 ;no, check again call StatReadDis ;disable status read return ;************************************************************************************************* ;check data auto write ready bit DAWStat call StatReadEn ;enable status read btfss D3 ;auto write ready? goto$-1
return

;*************************************************************************************************

movwf		PORTC
return

;*************************************************************************************************

movwf		PORTC
return

;*************************************************************************************************
;**												**
;**				    Clear Display						**
;**												**
;*************************************************************************************************

DisplayClear	movlw		160			;init counter
movwf		COUNT1			;to clear 320 VRAM locations
call		DAWSet
call		DAWStat
movlw		0			;write 0 to display
call		DWrite
decfsz		COUNT1,F		;decrement COUNT1 and continue writing
goto		$-4 ;data if COUNT1 > 0 movlw 160 ;re-init COUNT1 movwf COUNT1 ;decrement COUNT2 and continue writing decfsz COUNT2,F ;data if COUNT2 > 0 goto$-8
call		DAWReset
return					;done

;*************************************************************************************************
;**												**
;**				Interrupt Handler						**
;**												**
;*************************************************************************************************

;interrupt context save

ISR		movwf		W_TEMP
swapf		STATUS,W
banksel		0			;bank 0
movwf		STATUS_TEMP
movfw		PCLATH
movwf		PCLATH_TEMP

;interrupt code goes here

;interrupt context restore

ISRExit		movfw		PCLATH_TEMP
movwf		PCLATH
swapf		STATUS_TEMP,W
movwf		STATUS
swapf		W_TEMP,F
swapf		W_TEMP,W
retfie

end
Now...if you are using the PIC16F887, you go and build the code and load it into your PIC, you have your PIC hooked up as I have described above and you follow the power up sequence exactly as we discussed it above, you should end up with a blank display with a flashing single line cursor in the top left hand corner of the display. If you're not using the PIC16F887, some minor adjustments will need to be made to the config word and init routines to port it over to another PIC, but it shouldn't be too difficult to do. We have just initialized the display and it is now ready to accept data and display text.

As a test, I wrote up some code to display a test message -

ercan517
Author
Jon Wilder
Views
1,251
First release
Last update
Rating
0 ratings