1. Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.
    Dismiss Notice
Jon Wilder

Interfacing a Toshiba T6963C Equipped GLCD with a PIC Microcontroller

So you happen to have a GLCD with the Toshiba T6963C display controller and you want to interface i

  1. Jon Wilder
    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
    RC1 - READ
    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 (text):

    ;*************************************************************************************************
    ;*************************************************************************************************
    ;**                                             **
    ;**             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                    **
    ;**                                             **
    ;*************************************************************************************************

    ;nemonics used for read/write enable/disable

    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     READ        PORTC,RC1       ;GLCD read 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
            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

     
    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 (text):

    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 (text):

    StatReadEn  bsf     CD          ;command mode
            movlw       RD_EN           ;enable read
            movwf       PORTC
            return

    StatReadDis movlw       RD_DIS          ;disable read
            movwf       PORTC
            return
    Then we will code up the status check routine -

    Code (text):

    ;check GLCD status

    LCDStat     call        StatReadEn      ;enable status read
            btfsc       D0          ;is GLCD ready?
            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 (text):


    ;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 (text):

            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 (text):


            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 (text):


    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 (text):

    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 (text):

    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 (text):

    ;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 (text):

    ;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):

    ;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):

    ;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 (text):


    ;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 (text):

    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 (text):

    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 (text):

    DARStat     call        StatReadEn      ;enable status read
            btfss       D3          ;auto write ready?
            goto        $-1
            call        StatReadDis     ;disable status read
            return

    DARStat     call        StatReadEn      ;enable status read
            btfss       D2          ;auto write ready?
            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 (text):

    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 (text):

    ;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 (text):

    ;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 (text):

    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 (text):

    ;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 (text):

    ;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 (text):


    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 (text):

    ;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 (text):


    ;*************************************************************************************************
    ;**                                             **
    ;**             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 (text):


    ;*************************************************************************************************
    ;*************************************************************************************************
    ;**                                             **
    ;**             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

    LCDStat     call        StatReadEn      ;enable status read
            btfsc       D0          ;is GLCD ready?
            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
            call        StatReadDis     ;disable status read
            return

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

    ;GLCD status read enable

    StatReadEn  bsf     CD          ;command mode
            movlw       RD_EN           ;enable read
            movwf       PORTC
            return

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

    ;GLCD status read disable

    StatReadDis movlw       RD_DIS          ;disable read
            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 -

    [​IMG]
    ercan517 likes this.