Wednesday, 14 December 2011

Serial character LCD driver with Flash eeprom

No sooner had we got our serial character LCD display working than we were moving on to the next stage of the project - reading text from an Atmel AT45DB041D Flash eeprom chip to decide what to display on the screen.

We're already familiar with these chips, from our multiple servo controller board from back in May.
The idea is to store strings of text in these chips, then simply set a pointer (to tell the PIC where to start reading from) and get the microcontroller to read the text back and display it on the LCD.

We did look at I2C and other serial eeprom alternatives (at £1.20 each these Atmel chips are quite expensive for eeprom and at 4Mbit, are much bigger than we're ever going to need!) but most other (non-Flash) chips run at 100kz, or 400khz at best (yes, that's kilohertz, not Mhz). This will obviously make drawing text on the LCD run really slowly which is something we're keen to avoid.




These little Atmels can work up to 20Mhz. Which by happy coincidence is how fast the master chip will be running anyway, so hopefully we won't lose too much time reading and displaying text to the screen.



We wired our Flash eeprom chip up to PORTD on an 18F4550 (using RD.0-RD.3) and hacked some code together to read and write data to/from the chip. Amazingly, it worked first time!



AllDigital
Config PORTC = Output
Config PORTD = Output
Config PORTD.2 = Input

configuration:
       Symbol enablepin = PORTC.0
       Symbol clockpin = PORTC.1
       Symbol datapin = PORTC.2
     
declarations:
       Dim tmp As Byte
       Dim i As Byte
       Dim lcdi As Byte
       Dim lcdbyte As Byte
       Dim lcdbytetosend As Byte
       Dim rs As Bit
       Dim flag As Byte
       Dim mask As Byte
       Dim pageaddr As Word
       Dim byteaddr As Byte
       Dim byteswritten As Word
       Dim bytesread As Word
       Dim streamwrite As Bit
       Dim streamread As Bit
     
init:
       WaitMs 200
     
       Define SPI_CS_REG = PORTD
       Define SPI_CS_BIT = 0
       Define SPI_SCK_REG = PORTD
       Define SPI_SCK_BIT = 1
       Define SPI_SDI_REG = PORTD
       Define SPI_SDI_BIT = 2
       Define SPI_SDO_REG = PORTD
       Define SPI_SDO_BIT = 3
       SPIPrepare

       Gosub initialiselcd
       Gosub initialiseeeprom
     
       pageaddr = 1
       byteaddr = 0
       Gosub readanddisplaydata
            
loop:
     
       pageaddr = 1
       Gosub readanddisplaydata
       WaitMs 4000
       lcdbytetosend = 0x01 'clear screen
       Gosub writelcdcommand
     
       byteaddr = byteaddr + 16
       If byteaddr > 60 Then byteaddr = 0
     
       WaitMs 1000

Goto loop
End

writelcdnibble:
       Low enablepin
       Low clockpin
       If rs = 1 Then High datapin Else Low datapin

       Gosub toggleclockpin
     
       'shift in 4 bits
       mask = 8
       For lcdi = 1 To 4
              flag = lcdbyte And mask
              If flag = 0 Then Low datapin Else High datapin
              Gosub toggleclockpin
              mask = ShiftRight(mask, 1)
       Next lcdi
            
       'now strobe the clock one more time because ST+SC are tied
       Gosub toggleclockpin

       'toggle the enable pin to "flush" the data into the lcd
       Low datapin
       High enablepin
       Low enablepin
     
Return

toggleclockpin:
       'toggle the clock pin
       High clockpin
       Low clockpin
Return

writelcddata:
       rs = 1
       Gosub senddatatolcd
Return

writelcdcommand:
       rs = 0
       Gosub senddatatolcd
Return

senddatatolcd:
       lcdbyte = ShiftRight(lcdbytetosend, 4)
       Gosub writelcdnibble
       lcdbyte = lcdbytetosend And 15
       Gosub writelcdnibble
Return
     
initialiselcd:
       For i = 1 To 3
              WaitMs 50
              lcdbytetosend = 0x20
              Gosub writelcdcommand
       Next i
     
       WaitMs 50
       lcdbytetosend = 0x28 '4 bits, 2 lines, 5x7 font
       Gosub writelcdcommand
     
       WaitMs 50
       lcdbytetosend = 0x0c 'display on, no cursors
       Gosub writelcdcommand
     
       WaitMs 50
       lcdbytetosend = 0x06 'entry mode auto-increment
       Gosub writelcdcommand
     
       WaitMs 50
       lcdbytetosend = 0x01 'clear screen
       Gosub writelcdcommand
     
       'send a space character to the display to test
       WaitMs 50
       lcdbytetosend = 32
       Gosub writelcddata

Return

initialiseeeprom:
       Gosub set256pagesize
       Gosub chiperase
     
       pageaddr = 1
       byteaddr = 0
       WaitMs 100
     
       Gosub startstreamwrite
       For i = 0 To 63
              tmp = LookUp("Blood Bowl Griff Oberwald Varag Ghoulchew The Mighty Zug "), i
              SPISend tmp
       Next i
       Gosub endstreamwrite
     
Return

startstreamread:
       streamread = 1
       SPICSOn
       SPISend 0xe8 'stream read (legacy mode)
       SPISend pageaddr.HB
       SPISend pageaddr.LB
       SPISend byteaddr
       SPISend 0x00 'four don't care bytes
       SPISend 0x00
       SPISend 0x00
       SPISend 0x00
       bytesread = 0
Return

endstreamread:
       SPICSOff
       streamread = 0
Return

startstreamwrite:
       SPICSOff
       SPICSOn
       SPISend 0x82 '0x82 'write THROUGH buffer1 command
       SPISend pageaddr.HB '5 don't care bits + 11 address bits
       SPISend pageaddr.LB 'is the same as sending page address as hb/lb
       SPISend byteaddr 'last eight bits are buffer start byte
       byteswritten = 0
       streamwrite = 1
       WaitMs 5
Return

endstreamwrite:
       SPICSOff 'this causes the flash buffer to automatically get written to memory
       WaitMs 5
       streamwrite = 0
Return

set256pagesize:
       SPICSOn
       SPISend 0x3d
       SPISend 0x2a
       SPISend 0x80
       SPISend 0xa6
       SPICSOff
Return

chiperase:
       SPICSOn
       SPISend 0xc7
       SPISend 0x94
       SPISend 0x80
       SPISend 0x9a
       SPICSOff
       WaitMs 100
Return

readanddisplaydata:
       lcdbytetosend = 0x01 'clear screen
       Gosub writelcdcommand

       Gosub startstreamread
       For i = 1 To 16
              SPIReceive lcdbytetosend
              'for testing
              tmp = byteaddr + i
              Write tmp, lcdbytetosend
              Gosub writelcddata
       Next i
       Gosub endstreamread
Return



Note that in the initialiseeeprom routine we actually blank the Flash chip and write some data to it. In the final version of this code, we'll transfer data to Flash eeprom via a USB connection, but read it back just the same. Although this is just a basic test, it proves the idea of us storing character/player information in eeprom and calling it up and displaying it on a character-based LCD. All with just a few extra wires from the master microcontroller.

 [photo or video goes here]

Note that in this example we're using a rather tiny 2x16 character LCD (because we had some hanging around). We've got our fingers crossed that Santa will bring some larger 4x20 displays, like the one below which uses the same Hitachi 44780 controller chip, so the code should work for both.


With four lines of twenty characters, we can split player names over two lines (to allow really long forenames/surnames) and write player stats (MA, ST, AG, AV) on a single line - each stat (0-9) would take up four characters (XX-digit-space) so the output could look something like


 GRIFF
 OBERWALD

 MA4 ST5 AG8 AV9


Or, if we can get the player's name on a single line, something like:

 GRIFF OBERWALD

 MA   ST   AG   AV  
  4    5    8    9  


2 comments:

  1. How can i remove the lcd and place rs232 on it and using terminal to displat the data and save it to a text file.

    please mail me conrayvb@gmail.com

    ReplyDelete
  2. You're after removing the LCD altogether, then capturing the data being sent to the LCD to pass an ascii character back to a PC? It's possible, but will take a bit of effort. First up you'll need to understand how the LCD works - the four data lines hold four bits of an ascii character, then the enable pin is taken from low to high, then another 4 bits are put onto the lines, and the enable bit toggled again.

    You'll probably need to use a microcontroller (PIC/AVR/Arduino) in place of the LCD with either serial comms (and perhaps an FTDI serial-to-usb if your PC doesn't have a serial port) or with native USB support.
    You can then monitor the enable line, when it goes from low to high, read the 4 parallel data pins and put this value into a variable. Bitshift left four times, then wait for the enable line to go low to high again, read the 4 input pins and add these to the variable. This should give you the ascii code that was sent to the LCD. Pass this onto your PC through the serial (or usb) connection.

    Don't forget that you'll have no idea how fast the LCD display is being controlled - I use a 20Mhz crystal on my devices, so the lcd is probably running around 4mhz-5mhz. Someone using a slower crystal might only be sending data to an LCD at around 1mhz (a lot of PICs have built-in 4Mhz resonators, Arduino/AVR sometimes has 8Mhz internal clocks and so on).

    It'd be a nice project to work on - I understood how LCDs work because I had to write my first pic program in assembly; that way I got to know exactly the pin sequence used to operate them, so de-coding the data is quite straight-forward. In fact, if you can create your own program to send data to a character LCD display using only pin high/low commands (instead of any pre-built libraries) this will give you the level of understanding needed to achieve what you want - and will also form the basis for your decoding device.

    If you can't you're facing an uphill struggle!!

    ReplyDelete