Wednesday, 15 October 2014

Nokia 5110 screen module with PIC microcontroller and Oshonsoft

A little while back, we played with a neat little - cheap - LCD screen from an old-fashioned Nokia phone; now resold as "hacker's toys" online as SPI modules, these screens are great for displaying something a bit more interesting than just 16x2 lines of character-based text.

How these displays work is explained in a little more detail in our earlier post http://nerdclub-uk.blogspot.co.uk/2014/08/sending-bender-over-air-to-nokia-5110.html.

But we'd been using some of Steve's lovely Arduino libraries for a lot of it, which meant a lot of the low-level stuff was missing. There are loads of libraries available online to draw text and numbers onto these displays, but they almost always use fixed-width fonts.

Having left the 1980's behind, we decided to write our own "libraries" to write text onto the Nokia 5100 display, using a PIC microcontroller. The code below was written for a 16F877a (because it was the first chip we found already on a breadboard, with a programming header connected) but would work equally well on any PIC microcontroller with enough RAM for the font table.

Define CONFIG = 0x3f32
Define CLOCK_FREQUENCY = 20

AllDigital
Symbol led_pin = PORTB.7
Symbol lcd_sck = PORTD.4
Symbol lcd_data = PORTD.5
Symbol lcd_command = PORTD.6
Symbol lcd_reset = PORTD.7

definitions:
     
     Define STRING_MAX_LENGTH = 30
     
     Dim i As Byte
     Dim j As Byte
     Dim k As Byte
     
     Dim li As Byte
     Dim lj As Byte
     Dim lk As Byte
     
     Dim si As Byte
     Dim sk As Byte
     
     Dim byte_to_send As Byte
     Dim x_value As Byte
     Dim y_value As Byte
     Dim character_to_send As Byte
     Dim char(8) As Byte
     Dim move_x As Byte
     Dim string_to_write As String
     
     Dim invert_display As Bit
     Dim wrap_text As Bit
           
init:
     ConfigPin PORTB = Output
     ConfigPin PORTD = Output
     
     invert_display = 0
     wrap_text = 0
     
     WaitMs 100
     
     Gosub init_lcd
     Gosub lcd_clear
     
     x_value = 2 'columns are individual pixels
     y_value = 0 'must be a row number, which is a multiple of 8
     Gosub lcd_goto_xy
     Gosub draw_band
     
     
     x_value = 2
     y_value = 1
     Gosub lcd_goto_xy
     invert_display = 1
     wrap_text = 0
     string_to_write = "{ ABCDEFGHIJKLMNOPQRSTUVWXYZ"
     Gosub lcd_write_string
     invert_display = 0
     
     x_value = 2
     y_value = 3
     wrap_text = 1
     Gosub lcd_goto_xy
     string_to_write = "abcdefghijklmnopqrstuvwxyz"
     Gosub lcd_write_string
     
     x_value = 2
     y_value = 5
     Gosub lcd_goto_xy
     string_to_write = "123.45.67.89:0"
     Gosub lcd_write_string

loop:
     
     High led_pin
     WaitMs 2000
     Low led_pin
     WaitMs 2000
                       
Goto loop
End

init_lcd:
     Low lcd_reset
     WaitMs 10
     High lcd_reset
     
     'put into command mode
     Low lcd_command
     
     byte_to_send = 0x21 'lcd extended commands
     Gosub lcd_write_byte
     byte_to_send = 0xb1 'set lcd contrast
     Gosub lcd_write_byte
     byte_to_send = 0x04 'set temp coefficient
     Gosub lcd_write_byte
     byte_to_send = 0x14 'lcd bias mode 1:48
     Gosub lcd_write_byte
     byte_to_send = 0x0c 'put lcd into normal mode
     Gosub lcd_write_byte
     byte_to_send = 0x20 'dunno
     Gosub lcd_write_byte
     byte_to_send = 0x0c 'dunno
     Gosub lcd_write_byte
     WaitMs 5
     
     'put into data mode
     High lcd_command

Return

lcd_write_byte:
     For li = 0 To 7
           lk = 7 - li
           lj = 1
           lj = ShiftLeft(lj, lk)
           lj = lj And byte_to_send
           If lj = 0 Then
                 'send a zero bit
                 Low lcd_data
           Else
                 'send a one bit
                 High lcd_data
           Endif
           
           'toggle the clock line
           High lcd_sck
           ASM: nop
           Low lcd_sck
           
     Next li
Return

lcd_clear:

     'put the cursor at 0,0
     x_value = 0
     y_value = 0
     Gosub lcd_goto_xy
     
     'put the lcd into data mode
     High lcd_command
     
     'send 504 (48*84/8) blank pixels
     For i = 0 To 5
           For j = 0 To 83
                 byte_to_send = 0x00
                 If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
                 Gosub lcd_write_byte
           Next j
     Next i
     
     'just give everything a sec or two
     WaitMs 10
     
Return

lcd_goto_xy:
     Low lcd_command
     byte_to_send = 0x80
     byte_to_send = byte_to_send Or x_value
     Gosub lcd_write_byte
     
     byte_to_send = 0x40
     byte_to_send = byte_to_send Or y_value
     Gosub lcd_write_byte
     High lcd_command
Return

lcd_get_character:

     'set the default character to zero
     char(0) = 0x00
     char(1) = 0x00
     char(2) = 0x00
     char(3) = 0x00
     char(4) = 0x00
     char(5) = 0x00
     char(6) = 0x00
     move_x = 0

     Select Case character_to_send
           Case "A"
           char(0) = 01111110b
           char(1) = 00001001b
           char(2) = 00001001b
           char(3) = 01111110b
           move_x = 5
           
           Case "B"
           char(0) = 01111111b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00110110b
           move_x = 5

           Case "C"
           char(0) = 00011100b
           char(1) = 00100010b
           char(2) = 01000001b
           char(3) = 01000001b
           move_x = 5

           Case "D"
           char(0) = 01111111b
           char(1) = 01000001b
           char(2) = 01000001b
           char(3) = 00100010b
           char(4) = 00011100b
           move_x = 6
                       
           Case "E"
           char(0) = 01111111b
           char(1) = 01001001b
           char(2) = 01000001b
           move_x = 4
           
           Case "F"
           char(0) = 01111111b
           char(1) = 00001001b
           char(2) = 00001001b
           move_x = 4

           Case "G"
           char(0) = 00011100b
           char(1) = 00100010b
           char(2) = 01000001b
           char(3) = 01001001b
           char(4) = 01111010b
           move_x = 6

           Case "H"
           char(0) = 01111111b
           char(1) = 00001000b
           char(2) = 00001000b
           char(3) = 01111111b
           move_x = 5
           
           Case "I"
           char(0) = 01000001b
           char(1) = 01111111b
           char(2) = 01000001b
           move_x = 4

           Case "J"
           char(0) = 00100000b
           char(1) = 01000000b
           char(2) = 01000001b
           char(3) = 00100001b
           char(4) = 00011111b
           move_x = 6

           Case "K"
           char(0) = 01111111b
           char(1) = 00001000b
           char(2) = 00010100b
           char(3) = 01100011b
           move_x = 5

           Case "L"
           char(0) = 01111111b
           char(1) = 01000000b
           char(2) = 01000000b
           move_x = 4
           
           Case "M"
           char(0) = 01111111b
           char(1) = 00000001b
           char(2) = 00000010b
           char(3) = 00001100b
           char(4) = 00000010b
           char(5) = 00000001b
           char(6) = 01111111b
           move_x = 8

           Case "N"
           char(0) = 01111111b
           char(1) = 00000100b
           char(2) = 00001000b
           char(3) = 00010000b
           char(4) = 01111111b
           move_x = 6

           Case "O"
           char(0) = 00011100b
           char(1) = 00100010b
           char(2) = 01000001b
           char(3) = 01000001b
           char(4) = 00100010b
           char(5) = 00011100b
           move_x = 7

           Case "P"
           char(0) = 01111111b
           char(1) = 00010001b
           char(2) = 00010001b
           char(3) = 00001110b
           move_x = 5
           
           Case "Q"
           char(0) = 00011100b
           char(1) = 00100010b
           char(2) = 01000001b
           char(3) = 01010001b
           char(4) = 00100010b
           char(5) = 01011100b
           move_x = 7
           
           Case "R"
           char(0) = 01111111b
           char(1) = 00011001b
           char(2) = 00101001b
           char(3) = 01000110b
           move_x = 5

           Case "S"
           char(0) = 00100010b
           char(1) = 01000101b
           char(2) = 01001001b
           char(3) = 00110010b
           move_x = 5
           
           Case "T"
           char(0) = 00000001b
           char(1) = 00000001b
           char(2) = 01111111b
           char(3) = 00000001b
           char(4) = 00000001b
           move_x = 6
           
           Case "U"
           char(0) = 00111111b
           char(1) = 01000000b
           char(2) = 01000000b
           char(3) = 01111111b
           move_x = 5
           
           Case "V"
           char(0) = 00000011b
           char(1) = 00011100b
           char(2) = 01100000b
           char(3) = 00011100b
           char(4) = 00000011b
           move_x = 6
           
           Case "W"
           char(0) = 00111111b
           char(1) = 01000000b
           char(2) = 00100000b
           char(3) = 00011000b
           char(4) = 00100000b
           char(5) = 01000000b
           char(6) = 00111111b
           move_x = 8

           Case "X"
           char(0) = 01100011b
           char(1) = 00010100b
           char(2) = 00001000b
           char(3) = 00010100b
           char(4) = 01100011b
           move_x = 6
           
           Case "Y"
           char(0) = 00000011b
           char(1) = 00000100b
           char(2) = 01111100b
           char(3) = 00000111b
           move_x = 5
           
           Case "Z"
           char(0) = 01111001b
           char(1) = 01001001b
           char(2) = 01001101b
           char(3) = 01001011b
           move_x = 5
                       
           Case "a"
           char(0) = 00100000b
           char(1) = 01010100b
           char(2) = 01010100b
           char(3) = 01111000b
           move_x = 5

           Case "b"
           char(0) = 01111111b
           char(1) = 01000100b
           char(2) = 01000100b
           char(3) = 00111000b
           move_x = 5
           
           Case "c"
           char(0) = 00111000b
           char(1) = 01000100b
           char(2) = 01000100b
           char(3) = 01000100b
           move_x = 5
           
           Case "d"
           char(0) = 00111000b
           char(1) = 01000100b
           char(2) = 01000100b
           char(3) = 01111111b
           move_x = 5
           
           Case "e"
           char(0) = 00111000b
           char(1) = 01010100b
           char(2) = 01010100b
           char(3) = 01011100b
           move_x = 5
           
           Case "f"
           char(0) = 01111110b
           char(1) = 00001001b
           char(2) = 00000001b
           move_x = 4
           
           Case "g"
           char(0) = 10011000b
           char(1) = 10100100b
           char(2) = 10100100b
           char(3) = 01111100b
           move_x = 5
           
           Case "h"
           char(0) = 01111111b
           char(1) = 00000100b
           char(2) = 00000100b
           char(3) = 01111000b
           move_x = 5
           
           Case "i"
           char(0) = 01000100b
           char(1) = 01111101b
           char(2) = 01000000b
           move_x = 4
           
           Case "j"
           char(0) = 10000100b
           char(1) = 10000101b
           char(2) = 01111100b
           move_x = 4
           
           Case "k"
           char(0) = 01111111b
           char(1) = 00010000b
           char(2) = 00010000b
           char(3) = 01101100b
           move_x = 5
           
           Case "l"
           char(0) = 01000001b
           char(1) = 01111111b
           char(2) = 01000000b
           move_x = 4
           
           Case "m"
           char(0) = 01111100b
           char(1) = 00000100b
           char(2) = 00000100b
           char(3) = 01111000b
           char(4) = 00000100b
           char(5) = 00000100b
           char(6) = 01111000b
           move_x = 8
           
           Case "n"
           char(0) = 01111100b
           char(1) = 00000100b
           char(2) = 00000100b
           char(3) = 01111000b
           move_x = 5
           
           Case "o"
           char(0) = 00111000b
           char(1) = 01000100b
           char(2) = 01000100b
           char(3) = 00111000b
           move_x = 5
           
           Case "p"
           char(0) = 11111100b
           char(1) = 00100100b
           char(2) = 00100100b
           char(3) = 00011000b
           move_x = 5
           
           Case "q"
           char(0) = 00011000b
           char(1) = 00100100b
           char(2) = 00100100b
           char(3) = 11111100b
           move_x = 5
           
           Case "r"
           char(0) = 01111000b
           char(1) = 00000100b
           char(2) = 00000100b
           char(3) = 00000100b
           move_x = 5
           
           Case "s"
           char(0) = 01001000b
           char(1) = 01010100b
           char(2) = 01010100b
           char(3) = 01010100b
           char(4) = 00100000b
           move_x = 6
           
           Case "t"
           char(0) = 00111111b
           char(1) = 01000100b
           char(2) = 01000100b
           move_x = 4
           
           Case "u"
           char(0) = 00111100b
           char(1) = 01000000b
           char(2) = 01000000b
           char(3) = 01111100b
           move_x = 5
           
           Case "v"
           char(0) = 00011100b
           char(1) = 00100000b
           char(2) = 01000000b
           char(3) = 00100000b
           char(4) = 00011100b
           move_x = 6
           
           Case "w"
           char(0) = 00111100b
           char(1) = 01000000b
           char(2) = 00100000b
           char(3) = 00010000b
           char(4) = 00100000b
           char(5) = 01000000b
           char(6) = 00111100b
           move_x = 8
           
           Case "x"
           char(0) = 01101100b
           char(1) = 00010000b
           char(2) = 00010000b
           char(3) = 01101100b
           move_x = 5
           
           Case "y"
           char(0) = 10001100b
           char(1) = 10010000b
           char(2) = 10010000b
           char(3) = 01111100b
           move_x = 5
           
           Case "z"
           char(0) = 01100100b
           char(1) = 01010100b
           char(2) = 01001100b
           move_x = 4
           
           Case "0"
           char(0) = 00111110b
           char(1) = 01000001b
           char(2) = 01000001b
           char(3) = 00111110b
           move_x = 5
           
           Case "1", 1
           char(0) = 01000010b
           char(1) = 01111111b
           char(2) = 01000000b
           move_x = 4
           
           Case "2", 2
           char(0) = 01100010b
           char(1) = 01010001b
           char(2) = 01010001b
           char(3) = 01001110b
           move_x = 5
           
           Case "3", 3
           char(0) = 01000010b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00110110b
           move_x = 5
           
           Case "4", 4
           char(0) = 00011111b
           char(1) = 00010000b
           char(2) = 00010000b
           char(3) = 01111100b
           char(4) = 00010000b
           move_x = 6
           
           Case "5", 5
           char(0) = 00100111b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00110001b
           move_x = 5
           
           Case "6", 6
           char(0) = 00111110b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00110010b
           move_x = 5
           
           Case "7", 7
           char(0) = 00000001b
           char(1) = 01110001b
           char(2) = 00011001b
           char(3) = 00000111b
           move_x = 5
           
           Case "8", 8
           char(0) = 00110110b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00110110b
           move_x = 5
           
           Case "9", 9
           char(0) = 00100110b
           char(1) = 01001001b
           char(2) = 01001001b
           char(3) = 00111110b
           move_x = 5
                             
           Case "."
           char(0) = 01100000b
           char(1) = 01100000b
           move_x = 3
           
           Case ":"
           char(0) = 01100110b
           char(1) = 01100110b
           move_x = 3

           Case 32
           move_x = 4
           
           Case "{"
           Gosub draw_wifi
           
     EndSelect
     
Return

send_character_to_lcd:
     
     'put the lcd into data mode
     High lcd_command

     For i = 0 To 6
           byte_to_send = char(i)
           If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
           Gosub lcd_write_byte
     Next i
           
Return

lcd_write_string:

     sk = Len(string_to_write) - 1
     For si = 0 To sk
     
           'convert the requested character into an array of bits to display
           character_to_send = string_to_write(si)
           Gosub lcd_get_character
           
           'check the character will fit on the screen
           k = x_value + move_x
           
           If k > 82 And wrap_text = 0 Then
                 'don't write anything more on this line
                 x_value = 84
           Else
                 If k > 82 Then
                       x_value = 2
                       y_value = y_value + 1
                       Gosub lcd_goto_xy
                 Endif
           
                 'send the character to the lcd
                 Gosub send_character_to_lcd

                 'update the "cursor x" position
                 x_value = x_value + move_x
                 Gosub lcd_goto_xy
           Endif
                 
     Next si
Return

draw_band:
     x_value = 0
     Gosub lcd_goto_xy
     
     'put the lcd into data mode
     High lcd_command

     byte_to_send = 11110000b
     If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
     For i = 0 To 83
           Gosub lcd_write_byte
     Next i
     
     byte_to_send = 0xff
     If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
     For i = 0 To 83
           Gosub lcd_write_byte
     Next i
     
     byte_to_send = 00000111b
     If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
     For i = 0 To 83
           Gosub lcd_write_byte
     Next i
     
     
Return

draw_wifi:

     For i = 0 To 14
           Select Case i
                 Case 0, 14
                 byte_to_send = 00001000b
           
                 Case 1, 13
                 byte_to_send = 00000100b
           
                 Case 2, 12
                 byte_to_send = 00000010b
           
                 Case 3, 11
                 byte_to_send = 00010010b
           
                 Case 4, 10
                 byte_to_send = 00001001b
           
                 Case 5, 9
                 byte_to_send = 00100101b
           
                 Case 6, 8
                 byte_to_send = 10010101b
           
                 Case 7
                 byte_to_send = 11010101b
           EndSelect
           
           If invert_display = 1 Then byte_to_send = byte_to_send Nand 0xff
           Gosub lcd_write_byte
     Next i
     
     x_value = x_value + 16
     Gosub lcd_goto_xy

Return

We've written our font "table" as a select case statement.
When it come to being compiled down, a look up table and a select case statement are very similar anyway (and we just prefer using IF statements - just ask Steve!).

The font values are written in long-hand binary, rather than in decimal or their short-hand hex values. This makes no difference whatsoever to the final code (since the compiler treats all constant values the same, however they are written).

We stuck with long-hand binary, as it helps us to "see" the fonts and graphics inside the source code;


Drawing to the screen is as simple as sending an x and a y co-ordinate value (x is any number 0-83 and y is any "row" from 0 to 5, representing an actual co-ordinate value of 0, 8, 16, 32, 40 or 48). The next eight bits (single byte) are then used to draw "upwards" from the cursor position. If more than 8 bits are sent, the cursor position returns to the original y axis value, and the x-position shifts across one pixel.

There are also a couple of extras built into this example.
You can set the "inverted" flag on and off, to draw white text over a black background, or black text on a white background (the background colour should be fully drawn beforehand).
There's also a simple routine to draw a horizontal bar across the screen, to allow inverted text to be seen against a plain/blank background. An example of this in use would be a menu highlight system.

We also added a "text wrap" toggle, which determines - if the text is two large to fit onto one row - whether or not to break the work/string and draw it across two or more rows.

So far we've got upper and lower case letters, numbers and a few punctuation marks.
We're also using special characters to draw bitmaps at the cursor location (this isn't the correct use of the "library" - we were just keen to see how non-alphanumeric characters appeared!)

Heres' a photo of the screen in action:


We feel that the non-fixed width font just looks that little bit more "friendly" and less "hacky" and "homemade" than other library-based fonts.

The code above could easily be ported to another platform (such as Arduino).
We deliberately avoided using the hardware SPI peripheral, to allow it to be ported to any other microcontroller, without spoiling existing libraries or limit it to only those devices with a free hardware SPI module.

We really like these displays and - for a quid more than a 16x2 line character LCD display - we're looking at using these in a future wifi-based project (just as soon as our ESP8266 modules arrive!)