Hi,
The program below was devised to meet the challenge of using an 08M2 to implement a full (96) ASCII characters set for bit-mapped I2C displays such as the SSD1306. The limitations of the 08M2 largely defined the architecture, but the solution is quite efficient and may be equally useful for the larger PICaxe chips. The program was intended as a "core" or "preamble" for further development, to fit within the forum's 10,000 character limit, per post; but the forum software thinks otherwise, so it must be split over two posts. The code size is about 1400 bytes, so there is space for around another 500 bytes, even with an 08M2; or some/most of the code might be removed in a final application.
The smallest practical font uses 5 x 7 pixels characters, fitted within a 6 x 8 character cell. Often the eighth pixels are used for "descenders" (lower-case: g, j, p, q, y) or for an an underline/cursor. The SSD1306 organises the pixel bytes in columns, so 5 bytes are sufficient for each character, or 480 for a normal 96 (displayable) ASCII character set. This fits neatly into the Table memory of the larger M2 PICaxes, but the 08M2 has no Table memory, its EEPROM is only 256 bytes and can be better-used for other purposes, or left empty (as the bytes are "borrowed" from the program memory space in the 08M2). Therefore, it appears that the only space available for the character font storage is the Program memory itself.
The LOOKUP and POKE commands are slow and inefficient, but embedding the data directly into specific HI2COUT commands seems both relatively fast and compact. This is partially because the original PICaxe Basic program "tokens" were optimised for low-valued bytes (particularly 0 and 1) to use less than a byte of space, so "narrow" characters and punctuation are stored particularly efficiently. An issue is that the program needs to select (and execute) one from at least 96 different instructions (implemented as subroutines) for each character to be displayed. The IF .. THEN and SELECT .. CASE structures are rather slow and inefficient, but the ON .. GOTO command is about 5 times faster. However, it does still use a "list" of sequential tests (not a direct index) so the later labels in each instruction are executed with more delay. The program uses a two-stage "tree", first with 8 branches, each then splitting into 16 sub-branches. It's not worthwhile adding a third level to split the 16 into 4 x 4, because each command has an inherent "overhead" and the calculation of the additional "index" takes a significant time. Actually, since the M2s don't have any specific "shift" (<< or >>) operations, there is little advantage in dividing by binary (power) numbers, so 10 branches with 10 sub-branches might be marginally faster, on average. However, the sub-branch calculation can use an AND operator rather than an // (which is slower), and it's more sensible to retain the binary structure of the ASCII character set.
A disadvantage of embedding the data directly within the HI2COUT command is that the program cannot itself read the data. This prevents any direct processing of the font such as inversion (white/black or flip/mirror/rotation), double-height/width, or even adding an underline/cursor. Unfortunately, the SSD1306 doesn't support the reading back of data (via the I2C bus), but we can send the data to "another" memory. Therefore, all the HI2COUT instructions don't use a "hard-wired" address/register value but a Word Variable. In association with the appropriate I2CSETUP command this means that the data can be sent to almost any I2C device with either register (I2CBYTE) , RAM, or full (E)EPROM (I2CWORD) addressing. In particular, some Real Time Clock modules include a small amount of (battery-backed) RAM, which the program can write and read single characters "on the fly", without "wearing it out". Or, the overall system may include sufficient EEPROM to store the character font(s) either "permanently" or using a circular buffer to "level" the wear over an acceptable time period. It may even be worthwhile to add an external I2C serial EEPROM for "pre-prepared" fonts such as double/quadruple height, higher resolution or reversed characters, etc., with practically unlimited text / menu storage.
Thus, the program is structured with facilities to also send the font data to RAM or EEPROM and then read it back either immediately or at any later time (for the case of EEPROM or NVRAM). The bytes may be close-packed to suit smaller memories (or spaced for easier address calculation), but are arranged to not cross any 16-byte-block boundaries, which exist in some EEPROMS. 64-byte "page" blocks are more common now, but wouldn't accommodate any more characters (in both cases 60 bytes = 12 characters). The program then includes a "double height" algorithm, sending individual characters to either the OLED screen or back to another region of external memory. Simply doubling the height of the (7 x 5) characters makes them rather "tall and thin", but doubling also their width would reduce the displayed rows to only 10 characters each, which may be rather restricting. However, increasing the inter-character gap to 3 pixels (i.e. to a 16 x 8 character cell) gives a pleasing result with a "standard" format of 16 x 2 characters, compared with the original 20 (or strictly 21) x 4 character cells. For Large "High Quality" characters, a "Character Rounding" (or diagonal interpolation) routine has also been developed, to fill a 16 x 12 pixel character cell from the same 7 x 5 data (on-the-fly if necessary) and may be posted soon.
So here is the core program with the complete "tree" subroutine to be included in the subsequent post. A sample SSD1306 initialisation routine is included for completeness, but is not intended to be a comprehensive setup. The program has already been extended to include a RTC / Day / Date / Temperature facilities, complete with monitoring of the battery voltage and processor loading (utilisation) percentage, which gives an acceptable update rate even with a 4 MHz clock rate (around 60 characters / second). I plan to post more details either here or elsewhere in due course.
Cheers, Alan.
The program below was devised to meet the challenge of using an 08M2 to implement a full (96) ASCII characters set for bit-mapped I2C displays such as the SSD1306. The limitations of the 08M2 largely defined the architecture, but the solution is quite efficient and may be equally useful for the larger PICaxe chips. The program was intended as a "core" or "preamble" for further development, to fit within the forum's 10,000 character limit, per post; but the forum software thinks otherwise, so it must be split over two posts. The code size is about 1400 bytes, so there is space for around another 500 bytes, even with an 08M2; or some/most of the code might be removed in a final application.
The smallest practical font uses 5 x 7 pixels characters, fitted within a 6 x 8 character cell. Often the eighth pixels are used for "descenders" (lower-case: g, j, p, q, y) or for an an underline/cursor. The SSD1306 organises the pixel bytes in columns, so 5 bytes are sufficient for each character, or 480 for a normal 96 (displayable) ASCII character set. This fits neatly into the Table memory of the larger M2 PICaxes, but the 08M2 has no Table memory, its EEPROM is only 256 bytes and can be better-used for other purposes, or left empty (as the bytes are "borrowed" from the program memory space in the 08M2). Therefore, it appears that the only space available for the character font storage is the Program memory itself.
The LOOKUP and POKE commands are slow and inefficient, but embedding the data directly into specific HI2COUT commands seems both relatively fast and compact. This is partially because the original PICaxe Basic program "tokens" were optimised for low-valued bytes (particularly 0 and 1) to use less than a byte of space, so "narrow" characters and punctuation are stored particularly efficiently. An issue is that the program needs to select (and execute) one from at least 96 different instructions (implemented as subroutines) for each character to be displayed. The IF .. THEN and SELECT .. CASE structures are rather slow and inefficient, but the ON .. GOTO command is about 5 times faster. However, it does still use a "list" of sequential tests (not a direct index) so the later labels in each instruction are executed with more delay. The program uses a two-stage "tree", first with 8 branches, each then splitting into 16 sub-branches. It's not worthwhile adding a third level to split the 16 into 4 x 4, because each command has an inherent "overhead" and the calculation of the additional "index" takes a significant time. Actually, since the M2s don't have any specific "shift" (<< or >>) operations, there is little advantage in dividing by binary (power) numbers, so 10 branches with 10 sub-branches might be marginally faster, on average. However, the sub-branch calculation can use an AND operator rather than an // (which is slower), and it's more sensible to retain the binary structure of the ASCII character set.
A disadvantage of embedding the data directly within the HI2COUT command is that the program cannot itself read the data. This prevents any direct processing of the font such as inversion (white/black or flip/mirror/rotation), double-height/width, or even adding an underline/cursor. Unfortunately, the SSD1306 doesn't support the reading back of data (via the I2C bus), but we can send the data to "another" memory. Therefore, all the HI2COUT instructions don't use a "hard-wired" address/register value but a Word Variable. In association with the appropriate I2CSETUP command this means that the data can be sent to almost any I2C device with either register (I2CBYTE) , RAM, or full (E)EPROM (I2CWORD) addressing. In particular, some Real Time Clock modules include a small amount of (battery-backed) RAM, which the program can write and read single characters "on the fly", without "wearing it out". Or, the overall system may include sufficient EEPROM to store the character font(s) either "permanently" or using a circular buffer to "level" the wear over an acceptable time period. It may even be worthwhile to add an external I2C serial EEPROM for "pre-prepared" fonts such as double/quadruple height, higher resolution or reversed characters, etc., with practically unlimited text / menu storage.
Thus, the program is structured with facilities to also send the font data to RAM or EEPROM and then read it back either immediately or at any later time (for the case of EEPROM or NVRAM). The bytes may be close-packed to suit smaller memories (or spaced for easier address calculation), but are arranged to not cross any 16-byte-block boundaries, which exist in some EEPROMS. 64-byte "page" blocks are more common now, but wouldn't accommodate any more characters (in both cases 60 bytes = 12 characters). The program then includes a "double height" algorithm, sending individual characters to either the OLED screen or back to another region of external memory. Simply doubling the height of the (7 x 5) characters makes them rather "tall and thin", but doubling also their width would reduce the displayed rows to only 10 characters each, which may be rather restricting. However, increasing the inter-character gap to 3 pixels (i.e. to a 16 x 8 character cell) gives a pleasing result with a "standard" format of 16 x 2 characters, compared with the original 20 (or strictly 21) x 4 character cells. For Large "High Quality" characters, a "Character Rounding" (or diagonal interpolation) routine has also been developed, to fill a 16 x 12 pixel character cell from the same 7 x 5 data (on-the-fly if necessary) and may be posted soon.
So here is the core program with the complete "tree" subroutine to be included in the subsequent post. A sample SSD1306 initialisation routine is included for completeness, but is not intended to be a comprehensive setup. The program has already been extended to include a RTC / Day / Date / Temperature facilities, complete with monitoring of the battery voltage and processor loading (utilisation) percentage, which gives an acceptable update rate even with a 4 MHz clock rate (around 60 characters / second). I plan to post more details either here or elsewhere in due course.
Code:
; Full ASCII character set for SSD1306 using an 08M2
; AllyCat, May 2020
#picaxe 08m2
;#no_data ; After initial load
;#define STOREDH ; Else send to OLED
#define COMPACT ; Fit 96 characters into 2 pages (< 512 bytes)
symbol PULLUPS = %1100 ; %1100 for 08M2 ; %11000 for 14M2 ; %10100000 For 20M2
symbol I2CSPEED = I2CFAST
symbol WRITEDELAY = 5 ; ms at 4 MHz
symbol DHOFFSET = $200 ; Start with Double-Height character 32 in page $400 (16 bytes/char)
symbol tempb = b1
symbol tempw = w1
symbol addr = w2 ; Word needed for external EEPROM address
symbol col = b6
symbol row = b7
symbol index = b8
symbol char = b9
symbol subchar = b10
symbol source = b11
symbol dest = b12
symbol EPROMSAD = $A0 ; Slave ADdress
symbol OLEDSAD = $78
data 0,(0,3,12,15,48,51,60,63,$C0,$C3,$CC,$CF,$F0,$F3,$FC,$FF) ; Lookup for vertical stretch
pullup PULLUPS
pause 1000
hi2csetup i2cmaster,OLEDSAD,I2CSPEED,i2cbyte ; For SSD1306 32 lines OLED display
hi2cout 0,($AE,$D5,$80,$A8,$1F,$D3,0,$40,$8D,$14,$20,0,$A1,$C8,$DA,2)
hi2cout 0,($81,$8F,$D9,$F1,$DB,$40,$A4,$A6,$21,0,127,$22,0,3,$AF)
addr = $40 ; OLED data
tempb = 0 ; Address increment
tempw = 32 ; Repetitions
call CLEARMEM ; Clear screen
hi2cout 0,($21,1,126,$22,0,3) ; Set screen area (21 x 4 chars)
; WRITE FONT TO OLED:
for index = 32 to 127
char = index
call tree
hi2cout addr,(0) ; Inter-character gap
next
pause 5000
; COPY FONT TO EXTERNAL EEPROM/RAM:
hi2csetup i2cmaster,EPROMSAD,I2CSPEED,i2cword
#ifdef COMPACT
symbol FWID = 16 ; Width (active pixels) of 3 chars
symbol MEMOFFSET = $FFE0 ; -32 = Char 32 starts at base of page 000
tempw = 64 ; 2 pages = 512 bytes
addr = 0
#else
symbol FWID = 24
symbol MEMOFFSET = 0 ; Char 32 starts at base of page 100
tempw = 96 ; 3 Pages
addr = $100
#endif
; call CLEARMEM ; (* OPTIONAL) Pre-Clear Memory
col = 0 : row = 1
for index = 32 to 127
hi2csetup i2cmaster,EPROMSAD,I2CSPEED,i2cword
addr = index + MEMOFFSET * FWID / 3 ; Fit within 16 byte page boundaries (e.g. 24LC04)
call STOREFONT ; (* OPTIONAL after first use)
bptr = 100
hi2cin addr,(@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptr) ; Read back
; CREATE DOUBLE-HEIGHT CHARACTER:
stretch:
dest = 109 ; End of Second row
do
subchar = @bptr / 16
char = @bptr AND 15
read char,@bptrdec ; Reload top cell
read subchar,subchar
poke dest,subchar ; Second row
dec dest
loop until bptr = 99
inc bptr
#ifdef STOREDH
addr = index * 16 + DHOFFSET
#else
addr = $40
hi2csetup i2cmaster,OLEDSAD,I2CSPEED,i2cbyte
dest = row + 1 : tempb = col + 7
hi2cout $0,($21,col,tempb,$22,row,dest)
#endif
hi2cout addr,(0,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,0,0,_
0,@bptrinc,@bptrinc,@bptrinc,@bptrinc,@bptrinc,0,0)
col = col + 8 AND 127
next
stop
CLEARMEM: ; Clear Memory/screen
do
hi2cout addr,(0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0) ; Clear 16 byte cell
addr = addr + tempb
pause WRITEDELAY
dec tempw
loop until tempw = 0
return
STOREFONT:
char = index
call tree
pause WRITEDELAY ; EEPROM write time
return
tree: ; Send pixel bytes for ASCII "char" via I2C
; ** ADD SUBROUTINE FROM THE FOLLOWING POST **
return
Cheers, Alan.