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