Broadcast text on the 128x32 monochrome OLED display

published: 25 November 2020 / updated 25 November 2020

Lire cette page en français

 

As we had seen in the previous article, we can display a drawing predefined. To display characters, it won't be much different, each character being similar to a drawing ...

Character management

To begin with, we define a table with the characters in graphic form:

flash 
hex 
create FONTS   \ 5x8 
  00 c, 00 c, 00 c, 00 c, 00 c, \ 
  00 c, 00 c, 4f c, 00 c, 00 c, \ ! 
  00 c, 03 c, 00 c, 03 c, 00 c, \ " 
  14 c, 3e c, 14 c, 3e c, 14 c, \ # 
  24 c, 2a c, 7f c, 2a c, 12 c, \ $ 
  63 c, 13 c, 08 c, 64 c, 63 c, \ % 
  36 c, 49 c, 55 c, 22 c, 50 c, \ & 
  00 c, 00 c, 07 c, 00 c, 00 c, \ ' 
  00 c, 1c c, 22 c, 41 c, 00 c, \ ( 
  00 c, 41 c, 22 c, 1c c, 00 c, \ ) 
  0a c, 04 c, 1f c, 04 c, 0a c, \ * 
  04 c, 04 c, 1f c, 04 c, 04 c, \ + 
  50 c, 30 c, 00 c, 00 c, 00 c, \ , 
  08 c, 08 c, 08 c, 08 c, 08 c, \ - 
  60 c, 60 c, 00 c, 00 c, 00 c, \ . 
  00 c, 60 c, 1c c, 03 c, 00 c, \ / 
  3e c, 41 c, 49 c, 41 c, 3e c, \ 0 
  00 c, 02 c, 7f c, 00 c, 00 c, \ 1 
  46 c, 61 c, 51 c, 49 c, 46 c, \ 2 
\ ...rest of characters table 

We haven't put the full table.

Each character is defined in a 5x8 matrix. Example, for the character "2":

46 c, 61 c, 51 c, 49 c, 46 c, \ 2

We have 5 bytes, here in hexadecimal: 46 61 51 49 46, which in binary would give this:

01000110
01100001
01010001
01001001
01000110

If we replace the "0" by spaces and the "1" by "█", we can better see our character "2". Tilt your head to the right:

 █    █ █
 █ █     █
 █  █    █
 █   █   █
 █    █ █

Access to a character in the character table

Communication with the ARDUINO card is carried out using characters in ASCII format. In this format, each character has a specific code. In hexadecimal, the character "1" has the code ASCII 49 in decimal ($ 31 in hexadecimal), "2" -> code 50, "3" -> code 51, etc ...

To find the 5 bytes of any character stored in our FONTS table, we start with the ASCII code of the character to search for, from which we will subtract 32 (ASCII code of the "space" character).

The result is multiplied by 5 (size of the binary frame of a character). We add the result at the address of FONTS:

\ Translates ASCII to address of bitpatterns: 
: a>bp ( c -- c-adr ) 
    32 max 127 min 
    32 - 5 * FONTS + 
  ; 

This is what the word a>bp does. This word ensures a pre-filtering by prohibiting ASCII codes not included between 32 and 127.

Our character set compiled in FONTS corresponds to the ASCII characters.

Nothing prevents you from defining your own character set for writing in Greek, Russian, or for any other use.

You can even manage multiple character tables. Once a character is extracted from a table and displayed on the OLED screen, if you use another character with the same code, but coming from another table, it will be this other character that will be displayed, but without alter the first character already transmitted with this same character code.

For example, if your second character set matches the Russian alphabet, you can simultaneously write ASCII characters and Russian characters on the OLED display.

Character display

The SSD1306 128x32 OLED display is organized into 4 pages of 128 columns:

To display a character, you must select the starting page and columns and end of display:

0 value currentPage 
: set.line 
    addrSSD1306  i2c.addr.write drop \ send SSD1306 address 
    CTRL_COMMANDS i2c.tx 
    $21 i2c.tx  \ COL START_END 
    $00 i2c.tx  \ start 
    $7f i2c.tx  \ end 
    $22 i2c.tx  \ PAGE START_END 
    currentPage i2c.tx  \ start 
    currentPage i2c.tx  \ end 
    i2c.stop 
  ; 

The value currentPage is included in the range 0..3. The page 0 corresponds to the first display line. The word set.line selects columns 0 to 127, then page 0.

Once the page has been selected, you must empty its content:

: line.clear ( ---) 
    addrSSD1306  i2c.addr.write drop \ send SSD1306 address 
    CTRL_DATAS i2c.tx 
    DISPLAY_WIDTH 
    for 
        $00 i2c.tx  \ send commands or datas 
    next 
    i2c.stop 
  ; 

The word line.clear sends a character of $ 00 content 128 times.

And now, here's how to send a character from the FONTS table:

\ Draw character: 
: char.tx ( c --) 
    addrSSD1306  i2c.addr.write drop \ send SSD1306 address 
    CTRL_DATAS i2c.tx 
    a>bp        \ start addr 
    5 
    for 
        c@+     \ get byte and inc addr 
        i2c.tx  \ transmit byte 
    next 
    drop 
    $00 i2c.tx  \ transmit 'blank' 
    i2c.stop 
  ; 

To send a character to our OLED display, just run char.tx preceded by the code of the character to be transmitted.

In this drawing, we see a column in yellow:

To transmit our character "2", we transmit the 5 bytes constituting this character, taken from our FONTS table. It is not necessary address the display byte by byte. Each transmitted byte automatically increments the display pointer in the current page.

Transmission of a character string

From a character string of which we know the starting address and the number of characters, we extract the content of this string, character by character:

\ display text compiled with s" 
: string.tx ( adr len --) 
    for 
        c@+ char.tx 
    next 
    drop 
  ; 

Exemple:

\ display text compiled with s" 
: welcome ( --) 
    s" WELCOME with FORTH" 
    string.tx 
  ; 

To go to the next line:

: crLine ( ---) 
    currentPage 1+ 3 and to currentPage 
    set.line 
    line.clear 
  ; 

This word crLine increments currentPage modulo 3. If the value reaches 4, the value returns to zero.

We can integrate this return to the following line by modifying the word char.tx as follows:

\ Draw character: 
: char.tx ( c --) 
    \ if 'cr' go to next line 
    dup $0d = 
    if 
        crLine drop 
        exit 
    then 
    \ otherwise, display character 
    addrSSD1306  i2c.addr.write drop \ send SSD1306 address 
    CTRL_DATAS i2c.tx 
    a>bp        \ start addr 
    5 
    for 
        c@+     \ get byte and inc addr 
        i2c.tx  \ transmit byte 
    next 
    drop 
    $00 i2c.tx  \ transmit 'blank' 
    i2c.stop 
  ; 

Display vectorization

To start, here is a word that will display a text on several lines:

Example:

: .scores ( --) 
    ." -- YOUR SCORES: --" cr 
    ." Allan   : " 36 . cr 
    ." Mike    : " 42 . cr 
    ." ..press BUTTON..." 
  ; 

Without changing a single line of code, how can we send the text displayed by executing the word .scores on our OLED display?

Do you think that's impossible?

So if you plan to do the following in C language, it will be mission impossible.

With FlashForth, all that generate a display go through the vector 'emit. When FlashForth starts, this vector contains the word tx0. If you do:

65 emit   \ display A 
65 tx0    \ display A 

The word emit will search the vector 'emit for the word to be executed to display the character "A". If we hijack the vector 'emit to use the word char.tx, FlashForth will no longer transmit any characters to the terminal via the serial port, but to the OLED screen. Here is how we will destroy the vector:

' char.tx 'emit ! 

Ouch.... nothing is displayed on the terminal screen!!!

Don't panic. Look at the OLED screen. Your FORTH orders are displayed there!

We're going to run .scores:

disp.clear 100 ms .scores key drop 

And here is what we have on the display of our OLED screen:

To restore the display on the terminal screen:

' tx0 'emit ! 

If your application uses the OLED display, it will be able to use the words FORTH standards to display text thanks to this vectorization.

If your application evolves and changes display with different characteristics, you won't have to rewrite your application.

The full listing is available here:
Display text on OLED SCREEN SSD1306 128x32