Home  |  Contents 
Microcontroller and PC projects
  Forum Index : Microcontroller and PC projects         Section
Subject Topic: MM-all: Display drivers in Basic Post ReplyPost New Topic
Message << Prev Topic | Next Topic >>

Joined: 11 December 2012
Location: United Kingdom
Online Status: Offline
Posts: 1893
Posted: 12 January 2018 at 2:07am | IP Logged Quote matherp

The latest release of the MM2, MM+ and MMX firmware includes the ability to write drivers for all sorts of displays entirely in Basic.

To write a display driver is now simple with no "C" knowledge needed. Of course this comes with a performance impact but for smaller displays and many applications this shouldn't be an issue.

Once a display driver is written all of the in-built commands of the MM2, MM+ and MMX can then be used in the same way as for any in-built display.

One big plus is that the same code can be used on the MM2, MM+ and MMX with no changes needed other than if different pins are used.

A display driver comprises just three routines

1. initialise the display
2. output a filled rectangle to the display (a single pixel is a very small rectangle )
sub MM.USER_RECTANGLE x1%, y1%, x2%, y2%, c%
' code to draw a rectangle with the colour c%
end sub

3. output a rectangular bitmap to the display.
sub MM.USER_BITMAP x1%, y1%, width%, height%, scale%, fc%, bc%, bitmap%
' code to display a bitmap with foreground colour fc% and background bc%
' bitmap% is the address of the bitmap. Use PEEK() to get the data.
' each byte is a horizontal row of pixels starting with the most significant bit
end sub

The initialisation of the display can be done in the main program or in the library and the other two routines are subroutines with defined names and parameters.

This is all best illustrated by looking a Basic display driver for one of the SSD1306 OLEDs, in this case driven over an I2C bus with just a two wire connection.

I've tried to comment this as much as possible. The complexity is always in converting the simple drawing instruction to draw a rectangle or bitmap into the required sequence to implement this on any specific type of display

if you write your own display drivers please add to this thread

line 0,0,mm.hres-1,mm.vres-1
line mm.hres-1,0,0,mm.vres-1
box 0,0,mm.hres,mm.vres
text mm.hres/2,mm.vres/2,"Hello World",CM,8
sub userdisplayinit
' The SSD1306 display is 128 x 64 monochrome pixels
' This is arranged as 128 bytes x 8 bytes so a byte will cover the pixels in 8 rows of the display
' this makes it a bit tricky to program as we have to remember what is in the other 7 rows if we want to update a pixel in any specific row
' In this example we will hold a display image in a string array with 8 elements each 128 bytes long
  option explicit
  option default none
  on error skip 'if OPTION is already set then ignore the error
  on error skip 'if global variabels aren't yet set ignore the error
  erase S$(), tmaskarray(), bmaskarray()
  DIM S$(7) length 128 'this is the array that stores the screen image
  DIM integer tmaskarray(7)=(255,254,252,248,240,224,192,128) ' mask for writing pixels left to right
  DIM integer bmaskarray(7)=(1,3,7,15,31,63,127,255) 'mask for pixels right to left
  local i%
  on error skip ' if I2C already open ignore the error
  i2c open 400,1000
  ' send the intialisation instructions to the display
  OLED.SCmd(&H80)'the suggested ratio &H80
  OLED.SCmd(&H0)'no offset
  OLED.SCmd(&H00)'&H0 act like ks0108
  ' Set the memory image of the display to all zeroes
  for i% = 0 to 7 
  next i%
  ' Refresh the display with the new blank image
  update 0, 0, MM.HRES-1, MM.VRES\8 -1 
end sub
' This routine outputs a rectangle to the display. Thi limiting case is a single pixel
' The calling sequence is defined and must be adhered to
' The parameters are the coordinates of one of the extreme diagonals of the rectangle but we don't know which way diagonal.
sub MM.USER_RECTANGLE(x1%, y1%, x2%, y2%, fcol%)
  local integer i, j, k, l, t, mask, top_row, bottom_row, top_start, bottom_end
'  print "userrectangle ", x1%, y1%, x2%, y2%, fcol%
' this next section of code checks the parameters supplied and organises them
' this code will be common to any driver
  if x2% <= x1%  then 'make sure x1 is less than x2
    t = x1%
    x1% = x2%
    x2% = t
  if y2% <= y1% then 'make sure y1 is less than y2
    t = y1%
    y1% = y2%
    y2% = t
  if x1% < 0 then  x1% = 0
  if x1% >= MM.HRES then x1% = MM.HRES - 1
  if x2% < 0 then x2% = 0
  if x2% >= MM.HRES then x2% = MM.HRES - 1
  if y1% < 0 then  y1% = 0
  if y1% >= MM.VRES then y1% = MM.VRES - 1
  if y2% < 0 then y2% = 0
  if y2% >= MM.VRES then y2% = MM.VRES - 1
  ' establish some useful constants for future calculation
  ' first deal with the special case where the vertical pixels in the rectangle are within a single byte
  if top_row = bottom_row then
    if fcol% then 'create a mask to set or clear the requisite pixels
      mask = (tmaskarray(top_start) AND bmaskarray(bottom_end))
      mask = notmask%(tmaskarray(top_start) AND bmaskarray(bottom_end)) 
    for i=x1% to x2% 'loop through the colums and set or clear the required pixels
      l= peek(var S$(top_row),i+1)
      if (fcol%) then
        l=l OR mask
        l=l AND mask
      poke var S$(top_row),i+1, l
    next i
  ' otherwise deal more generally
    if (top_row+1) <= bottom_row then 
      for  i=x1% to x2%
        'first deal with the top byte affected
        l = peek(var S$(top_row),i+1)
        if (fcol%) then
          l=l OR tmaskarray(top_start)
          l = l AND (notmask%(tmaskarray(top_start)))
        poke var S$(top_row),i+1, l
        ' now deal with bottom byte affected
        l= peek(var S$(bottom_row),i+1)
        if fcol% then
          l=l OR bmaskarray(bottom_end)
          l=l AND (notmask%(bmaskarray(bottom_end)))
        poke var S$(bottom_row),i+1, l
      next i
    'now deal with bytes in the middle that will be set completely on or completely off
    if (top_row+1<bottom_row) then
      for j=top_row+1 to bottom_row-1
        for i=x1% to x2%
          l= peek(var S$(j),i+1)
          if fcol% then
          poke var S$(j),i+1, l
        next i
      next j
  update x1%,top_row, x2%, bottom_row
end sub
' output a bitmap to the screen
' the bitmap is supplied as a pointer to an area of memory so we use
' peek(byte bitmap%+x) to access the x'th byte in the bitmap
' each byte is a horizontal row of pixels starting with the most significant bit
' e.g. for an 8x8 bitmap
'    Byte0Bit7, Byte0Bit6, Byte0Bit5, Byte0Bit4, Byte0Bit3, Byte0Bit2, Byte0Bit1, Byte0Bit0
'    Byte1Bit7, ........., ........., ........., ........., ........., ........., .........
'    Byte2Bit7, ........., ........., ........., ........., ........., ........., .........
'    Byte3Bit7, ........., ........., ........., ........., ........., ........., .........
'    Byte4Bit7, ........., ........., ........., ........., ........., ........., .........
'    Byte5Bit7, ........., ........., ........., ........., ........., ........., .........
'    Byte6Bit7, ........., ........., ........., ........., ........., ........., .........
'    Byte7Bit7, ........., ........., ........., ........., ........., ........., Byte7bit0
sub MM.USER_BITMAP(x1%, y1%, width%, height%, scale%, fcol%, bcol%, bitmap%)

'  print  "userbitmap ", x1%, y1%, width%, height%, scale%, fcol%, bcol%
  local INTEGER i, j, k, mask, m, l, ll, t, tt, vCd, hCd, x, y, a%=height% * width%, ln%, c1%, c2%, c3%, c4%
  vCd = y1%
  if y1% < 0  then y1% = 0                                 ' the y coord is above the top of the screen
  for i = 0 to  height%-1                              ' step thru the font scan line by line
    ln%=i * width%
    for j = 0 to scale%-1                         ' repeat lines to scale the font in the y axis
      if vCd >= 0 then  ' we are above the top of the screen
        y=vCd  - 1 
        c1%=y \ 8  
        mask = 1 << (y MOD 8  )
        if vCd > MM.VRES  then goto D_UP          ' we have extended beyond the bottom of the screen so exit
        hCd = x1%
        for k = 0 to width%-1                         ' step through each bit in a scan line
          c2%=ln% + k
          c4%=(a% - c2% - 1) mod 8
          t=peek(BYTE bitmap% + c2%\8)
          tt = (t >> c4%) AND 1
          for m = 0 to  scale% -1                      ' repeat pixels to scale in the x axis
            hCd = hCd +1' we have not reached the left margin
            if hCd >= 0 then
              x=hCd -1
              if hCd <= MM.HRES  then                 ' check are we beyond the right margin
                ll= peek(var S$(c1%),hCd)
                if tt then
                  if fcol% then
                    ll = ll OR mask
                    ll = ll AND c3%
                  if bcol%<>-1 then 'transparent text 
                    if bcol% then
                      ll = ll OR mask
                      ll = ll AND c3%
                poke var S$(c1%), hCd, ll
          next m  
        next k
    next j
  next i
  update x1%, y1% \ 8, x, y\ 8
end sub
' Send a command to the display
SUB OLED.SCmd(Comnd%)
  I2C WRITE &H3C,0,2,&H00,Comnd%  'address must be hardcoded to use at command line
' Set the cursor position on the display
SUB OLED.SCz(x%,y%) 'set the cursor position on the display
  local xn%=x%
  OLED.SCmd(&HB0+y%)        'set page address
  OLED.SCmd(&H10+(xn%>>4 AND &H0F))  'set high col address
  OLED.SCmd(&H00+(xn% AND &H0f))     'set low col address
' Update the screen between the x coordinates specified and the 8 row block specified
sub update(x1%, tr%, x2% , br%)
  local b$, i%
  if x1%< MM.HRES then
      for i% = tr% to br%
      if i%<MM.VRES\8 then
        b$=CHR$(&H40)+mid$(s$(i%),x1%+1, x2%-x1%+1)
        I2C WRITE &H3C,0,len(b$),b$
    next i%
end sub
' generate the ones complement of a byte
function notmask%(x%)
  notmask% = (-1 XOR x%) AND &HFF
end function

DefineFont #8 
00000000 82200000 00800008 00004551 4F510000 0045F994 2B1CEA21 690000C2 
B04C2090 AA104A62 84608046 00000000 40108410 81400081 00841004 A89C8A00  
82000080 0080203E 30000000 00000042 0000003E 01000000 21000086 00004208 
CAAA2972 82210027 00872008 21842072 42F8800F 00270A04 F824C510 0FFA0041 
00270A02 8A3C0831 21F80027 00044108 8A9C2872 28720027 0027089E 61008601 
86010080 00846000 40208410 0F000081 0000F880 10028140 20720084 00022084 
BA9A2072 28720027 8028FAA2 8ABC28F2 2872002F 00278220 8AA248E2 08FA004E 
800F823C 823C08FA 28720008 80278A2E 8ABE288A 82708028 00872008 12044138 
4A8A0046 8048A230 82200882 6A8B800F 80288AAA 9AAA2C8A 28728028 00278AA2 
82BC28F2 28720008 8046AAA2 A2BC28F2 087A8048 002F081C 200882F8 288A0082 
00278AA2 89A2288A 288A0042 00A5AAAA 5208258A 288A8028 00822094 420821F8 
0471800F 00074110 A9944AA9 4170804A 00471004 00804821 00000000 800F0000 
00008140 07000000 80277A02 8A320B82 0700002F 00278220 8AA62608 07008027 
0007FA22 41382431 E8010004 002778A2 8A320B82 06208028 00872008 12040310 
09820046 0089C228 20088260 0D000087 8028AA2A 8A320B00 07008028 00278A22 
F2220F00 06000008 802078A6 82320B00 07000008 002F7020 41100E41 08000023 
80668AA2 89A20800 08000042 00A5AAA2 21940800 08008048 002778A2 21840F00 
0400800F 0EE24028 
End DefineFont

Back to Top View matherp's Profile Search for other posts by matherp
Frank N. Furter

Joined: 28 May 2012
Location: Germany
Online Status: Offline
Posts: 319
Posted: 14 January 2018 at 5:54am | IP Logged Quote Frank N. Furter

That's fantastic - but... I don't understand anything about this code...

What is tmaskarray() and bmaskarray() about? Can you explain that in more detail?

My goal would be to build a MMBasic wristwatch with a Sharp LS013B4DN04 display - unfortunately the display is the crux for me...

Back to Top View Frank N. Furter's Profile Search for other posts by Frank N. Furter

Joined: 11 December 2012
Location: United Kingdom
Online Status: Offline
Posts: 1893
Posted: 14 January 2018 at 6:06am | IP Logged Quote matherp

What is tmaskarray() and bmaskarray() about? Can you explain that in more detail?

The pixels in this display are arranged as 128 columns of 8 bytes.
So one byte covers 8 rows.

Say I want to illuminate just the pixel in row 5. I need to leave the other pixels untouched and just set bit 5

If we look at the 5th element of arrays tmaskarray() and bmaskarray() and AND them together we get 224 AND 63 or in bits 11100000 AND 00111111 = 00100000. That is easy and could have been done with 1<<5.

Now think about setting bits 3-5 and play with the binary.....
Back to Top View matherp's Profile Search for other posts by matherp

If you wish to post a reply to this topic you must first login
If you are not already registered you must first register

  Search the forums using Google.
In the news...
Post ReplyPost New Topic
Printable version Printable version
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot delete your posts in this forum
You cannot edit your posts in this forum
You cannot create polls in this forum
You cannot vote in polls in this forum

Powered by Web Wiz Forums version 7.8
Copyright ©2001-2004 Web Wiz Guide

This page was generated in 0.1094 seconds.
Privacy Policy     Process times : 0, 0, 0, 0.11