Broadcast text on the 128x32 monochrome OLED display
published: 25 November 2020 / updated 25 November 2020
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:
- a column represents one byte. There are therefore 128 addressable bytes in a page;
- a page represents a display line. There are 4 pages on our OLED display.
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