FYI: Comparing the SSD1306 and SH1106 OLED Drivers


Senior Member

The SSD1306 and SH1106 are "similar" Integrated Circuits for driving small OLED displays of up to about 128 x 64 pixels. These displays are of particular interest to PICaxe users because they are broadly compatible with most PICAXEs, being quite low-cost, using a supply range of 3 - 5 volts, a typical current drain of around 10 mA and a two-wire I2C bus capability. A forum search gives very few hits for the SH1106, but over 100 for the SSD1306, although many fail to reach a definitive conclusion. Fortunately, the data sheets for these two devices are available on the Web and reading both can be worthwhile to "fill in some of the gaps". For example the SH1106 data sheet makes a better attempt to explain when a $80 "Continuation" Control Byte should be used instead of a $00 Byte, although this still seems quite confusing (and maybe irrelevant).

The Data Sheets are quite complex, because the raw OLED display panels have almost 200 "connections", i.e. a multiplex with up to 64 "rows" (common/cathode) plus 128+ "columns" (segment) drivers, and then the microcontroller interface has another ~32 pins. Thus, the silicon dies have around 238 - 266 "pads" (in a maximum dimension of about 5 mm ! ). But fortunately, we don't need to work with (or can even see) the "naked dies" which are typically mounted directly onto a flexible "Kapton Foil" Printed Circuit Board via "Solder Bumps". These foils have an "Edge Connector" with a contact pitch of typically 0.7 mm to connect with either a microcontroller's full Parallel bus, or a Serial bus such as SPI and I2C. The foils are clearly visible at the rear of the 128 x 64 pixel displays and are separately available if access to the Parallel Bus (or some other features such as the Slave Address) is required. However, of interest here are the assembled Breakout Boards which bring out just the I2C Bus (4 pins) or an SPI (7 - 8 pins) interface onto a convenient 0.1" pitch header.

It should be emphasised that although the above modules are often described as having an "SPI / I2C interface", they are usually different modules, so it's important to select the correct type (i.e. the 4-pin I2C version). Sometimes the type of Controller chip is not specified and/or the two types may be described as "equivalent", but this is NOT correct. Their differences may be "hidden" inside an Arduino Library file, but they are important for a PICaxe user. Further care is needed because the header pinouts are NOT standardised, so the supply (Vdd) and Ground (Vss) pins may be found to be swapped (some boards even have moveable "zero Ohm" Vdd/Vss links). Both chips can be Hardware-Configured to either I2C Slave address $78 or $7A, but this is rarely a feature on the breakout modules, the majority being fixed at address $78 (or the misleading "7-bit address" $3C).

With both chips the "Display RAM" can be READ via the Parallel Data Bus, but NOT via the "Serial Interface" (SPI). It's arguable whether I2C is a "Serial" Interface, but the SDD1306 can NOT Read the Memory contents, but the SH1106 CAN READ the Display Memory via the I2C Data Bus. This is rarely discussed but confirmed in its Data Sheet and is particularly useful for the "Font in Program Memory" method which I devised for the 08M2 PICaxe, or to simply implement an active "Reversed Video" Cursor, etc.. Conversely, only the SDD1306 has additional internal Hardware to support some "Automatic" Scrolling features (Commands $20 - $2F), but only $20 - $22 appear to be in common use (see later).

Personally, I consider the 128 x 32 (0.91 inch visible diagonal = 23 mm) displays to be the more "useful"; usually a lower cost and able to accommodate 4 rows of 21 normal 5 x 7 pixels characters, emulating the ubiquitous "2004" LCD/OLED character displays (as used in the AXE134). Or a higher resolution font (up to 7 x 16) and/or greater character-spacing giving Double-Height characters can emulate the 2 rows x 16 characters of the popular "1602" displays (as in the AXE033/133 type modules, etc.). Also, these graphics displays have a full 1k of RAM, so it is possible to load a second field invisibly (in the background) and then "Pop" or "Scroll" the screen startline to change the display instantly.

If there is a specific need to display up to 8 rows of characters, the 0.96" (24mm diagonal) 128 x 64 displays seem "rather small", but the 1.3" (33 mm display diagonal) versions are sometimes not much more expensive. The two smaller displays (<1") appear to use mainly (or entirely ?) the SDD1306 chip, but a significant number of the 1.3 inch versions use the SH1106. That is how I come to be writing this note: I risked the purchase of a pair of 1.3" "unspecified" display modules from a Chinese seller (who has now updated his listing to indicate that they use the SH1106) which basically "did not work" with a functional PICaxe "SSD1306" Driver Program. Therefore, I examined the relevant Data Sheets and modified the Program Code, to the extent that it now seems "safe" to consider purchasing either of these chips, unless a specific "unique" feature of one of them is required.

A few more differences between the two chips: firstly the SH1106 drives up to 132 columns of pixels, compared with a maximum of 128 for the SDD1306. The former is arranged to map two inactive columns of pixels onto each side of the display, which might seem strange, but allows the horizontal scan to be reversed (i.e. Mirrored) without otherwise affecting the active pixels. Two pixels may seem an insignificant number, but they do represent almost half the width of a single 5 x 7 pixels character. However, the major difference is that the SSD1306 has three "Scan Modes", named "Horizontal", "Vertical" and "Page", (set by command $20 0x) whilst the SH1106 implements ONLY the Page Mode. This latter mode is also the "Default" (Power-Up) for the SSD1306, but the majority of the PICaxe threads have selected the Horizontal mode, making their code incompatible with the SH1106. :(

A "Page" mode may seem the most useful, but the term is used in the "computer" sense, not to describe a full displayed (screen) Page, but only a single horizontal Row of 128 Bytes (i.e. a band 8 pixels High). It is the Horizontal and Vertical modes of the SDD1306 which create a "Window" onto the whole screen, and that Window can be set to contain the FULL screen area. Thus the Horizontal Mode may be slightly more "convenient", particularly for character displays, but the Page/Row Addressing method should be familiar to users of character displays such a the 2004 and 1602, etc.: In principle, it is just necessary to adjust the Cursor's Column and Row Addresses at the end of every Row of character bytes. Therefore, I propose that all my future Program Code written for these displays will use only the Page Mode, to achieve compatibility between the various modules, as far as possible.

A common topic of the forum threads is the "Initialisation" of these displays, which can look quite complex, with long strings of obscure Byte values. However, the Data Sheets indicate that many of these commands are (usually) unnecessary, at least for the 128 x 64 displays when being started from a "Hard Reset" (i.e. after a Power-On). The controllers automatically initialise themselves with "Default" parameters to the extent that a SH1106 might need no more than a simple "Display On" command, i.e. HI2COUT 0 , ($AF) ! The SSD1306 needs also a "Switch Boost Power On", e.g. HI2COUT 0,(CHGPUMP, $14) , and the 32 line displays require some further hardware formatting commands. Then there are "User Options" such as "Mirror" (swap Left and Right), "Flip" (swap Top and Bottom), "Inverse" (swap Light and Dark) and "Brightness", etc.. Finally, it may be necessary to reload ALL the Default values, if corruption of the configuration may have occurred (by accident or intent) and a Hardware Power-Up is not convenient or possible. However, this does risk loading some "incorrect" values if the specific chip/hardware is not accurately known.

Since the SSD1306 cannot be Read via the I2C (or SPI) Bus, it is not possible for software to validate its type. A test of the I2C Bus's "Acknowledge" flag could check the Slave's existence on the bus, but it's probably easier to just run a Bus Search Routine. The SH1106 also doesn't have an "ID register", but the presence of its 1k of RAM at the correct Slave Address should give a good indication. Below, is a simple Test / Demonstration program to determine the controller type (between these two) and fill a 128 x 64 pixel screen with diagonal lines (two to exactly the NW and SE corners).

Reading data from the SH1106 did need some trial and error : Figure 7 in the Data Sheet indicates that the I2C Master needs to transmit only the Slave Address (i.e. HSERIN (data) , but this cannot discriminate between a Status and a RAM Read. In practice, it seems necessary to transmit also a Register number (e.g. HSERIN $40 , (databytes) to read the RAM, however, HSERIN $00 , (databytes) still doesn't appear to read the Status Byte. Also, as mentioned in the data sheet, it seems necessary to perform a Dummy Read, to skip the first received byte ("after the column address being set up"), so a typical input command becomes: HSERIN $40 , (@bptr , @bptrinc , @bptrinc ). To ensure that a specific RAM location is accessed, the Read-Modify-Write commands ($E0 .... $EE ) could be used to prevent auto-incrementing of the data pointer whilst Reading.

Yet again, I've exceeded the forum's 10,000 character limit, so must include the Program Code in my next post.

Cheers, Alan.
Last edited:


Senior Member

So here is my Test / Demonstration Program. It includes all the (main) SDD1306/SH1106 Symbol declarations, but most are not needed.
; Compatible SH1106 and SSD1306 Test code
; AllyCat  June 2021   

; #picaxe 08m2        ; Does not have a Table memory, so limited to font of ~150 characters
#picaxe 14m2    ; And most others
#no_data        ; If DATA/TABLES already downloaded (or not used, as here)
#terminal 4800

symbol OLEDSPEED = I2CFAST_32    ; Assume that SETFREQ M32 may be used
symbol OLEDadd = $78                ; Slave Address ($7A also possible)
symbol FIRSTCOL = s_w1            ; =2 for SH1106, 0 for SSD306
symbol LASTROW = $B7                ; $B3 for 32-line display
symbol tempb = b1
symbol counter = b2
symbol row = b3

; Usable Default values: (Available for Program use) ; Suffix "_" indicates a data parameter is required
symbol BRIGHT_ = $81                ; +128 (Default) ; Any value 0 to 255
symbol SHIFTUP_ = $D3            ; +0 (Default) up to $3F     ; For Vertical Scrolling
symbol ROWZERO = $B0                ; (Default) up to $B7    ; Cursor "PAGE"(Row) location
symbol COLLOW = $00                ; (Default) up to $0F    ; Curor Column address Low nibble
symbol COLHI = $10                ; (Default) up to $18    ; Cursor Column address High nibble
symbol STARTLINE0 = $40            ; (Default) up to $7F    ; For Vertical scrolling
symbol NOFLIP = $C0                ; (Default) Normal (Upright) Scan (Top --> Bottom)
symbol FLIP = $C8                    ; Inverted Scan (Top <--> Bottom) ; Normal = $C0 (Default)
symbol NOMIRROR = $A0             ; (Default) Normal Scan (Left --> Right)
symbol MIRROR = $A1                 ; Mirror Scan (Left <--> Right) ; Normal = $A0 (Default)
symbol NOTALLON = $A4            ; (Default) Cancel All-On command ($A5)
symbol ALL_ON = $A5                ; All Pixels On (regardless of RAM contents)
symbol NORMAL = $A6                ; (Default) Normal Video Polarity, ie Not Reverse Video
symbol INVERSE = $A7                ; Inverse Video Polarity ; REVERSE is a reserved word
symbol NOP = $E3                     ; No Operation

; Hardware settings
symbol DISPOFF = $AE                ; Power Off (Standby)
symbol DIVFREQ_ = $D5            ; +$50 (Default SH1106) or +$80 (Default SSD) (Freq:Divider nibbles)
symbol PRECHARGE_ = $D9            ; +$22 (Default)
symbol COMPADS_ = $DA            ; +$12 (Default) Common Pads +$20 = Enable remap
symbol  SEQ = $02                    ; Common Pads Sequential (for 32 line display), also see FLIP
symbol  ALT = $12                    ; Common Pads Alternate (for 64 line display)
symbol  REMAP = $20                ; Common Pads Remap (SSD1306 only) Default OFF
symbol VCOM_ = $DB                ; +$35 (Default SH1106) or +$20 (Default SSD1306)

; Controller-Specific (SH1106 or SSD1306 only):
symbol PUMP = $32                    ; (Default) Values $30 to $33    (SH1106 ONLY)
symbol DCDC_ = $AD                ; +$8B (Default) ; Off = +$8A (SH1106 ONLY)
symbol READ_WR = $E0                ; Start Read-Modify-Write (SH1106 only)
symbol END_RW = $EE                ; End Read-Modify-Write (& restore Cursor column) SH1106 ONLY
symbol VSCROLL__ = $A3            ; +0 & +63 Scroll Lines: Fixed & Scrolled SSD1306 ONLY:
symbol SCANMODE_ = $20            ; +2 (Default) Page Scan Mode (0=Horizontal,+1=Vertical)  SSD1306 ONLY

; Required Initialisation
symbol MULTIPLEX_ = $A8            ; +$3F (Default)    ; For 64 line screen (32 lines = +$1F)
symbol CHGPUMP_ = $8D            ; Enable Charge pump ($14); Default OFF ($10) SSD1306 ONLY
symbol DISPON = $AF                ; Switch Display ON (Default Off = $AE)
symbol MULTIPLEX64 = $3F        ; (Default) For 64 line display (and 32 lines scrolled?)
symbol MULTIPLEX32 = $1F        ; For 32 line display

    hi2csetup i2cmaster,OLEDadd,OLEDspeed,i2cbyte
    pause 2000                        ; Wait for Terminal Emulator to start
    call CheckSupply
    call CheckRAM
    call Initialise
    call ClearScreen
    pause 5000
    hi2cout 0,(INVERSE)
    pause 2000
    hi2cout 0,(NORMAL)

CheckSupply:                            ; Check the supply voltage (Optional Diagnostic)
symbol CALVDD = 52429                ; 1024*1.024*1000/20  (DAC steps * Ref V / Resolution in mV)
    calibadc10 w1                    ; Measure FVR (nominal 1.024 v) relative to Vdd (1024 steps)
    w2 = w1 / 2 + CALVDD                ; Effectively round up w2 by half a (result) bit
    w2 = w2 / w1                        ; Take the reciprocal to calculate (half) Vdd (tens of mV)
    calibadc10 w1                    ; Read the value again because noise may be present
    w1 = CALVDD / w1 + w2            ; Calculate Vdd/2 again and add the first value
    sertxd(cr,lf,"Vdd= ",#w1,"0 mV")
    sertxd(",PinsB= ",#pinsb,cr,lf)

pullup 24                                ; For 14M2 I2C Bus (if resistors not fitted externally)
hi2cout 0,(CHGPUMP_,$14,DISPON)
pause 100                                ; From SH1106 Data Sheet

    hi2cout $00,(0,$10,$B0)                        ; Check for RAM access
    hi2cout $40,($55,$AA)
    hi2cout $00,(0,$10)
    bptr = 28
    hi2cin $40,(@bptr,@bptrinc,@bptr)        ; Start with dummy byte read
    @bptr = @bptrdec + @bptr                    ; Check sum
    if @bptr = 255 then
        sertxd("SH1106 Read",cr,lf)
        FIRSTCOL = 2                                ; For SH1106 (max 15)
        FIRSTCOL = 0

ClearScreen:    ; Of FILL screen (with something) Clear Screen CLS is reserved word
    for row = $B0 to LASTROW
         hi2cout $00,(FIRSTCOL,$10,row)                    ; Set cursor position
        for counter = 0 to 15
            hi2cout $40,(1, 2, 4, 8, 16, 32, 64, 128)    ; Diagonal Line
[ADDED:] Here's a demonstration of the 1.3" display using a SH1106. I've extended my original 96 characters ASCII font with 48 Greek characters (again derived from Westaust55's thread of 10+ years ago). Coded into a 14M2, but it should still fit into an 08M2. :)

The program writes 7 rows of characters to the screen and then uses the I2C READ capability to vertically stretch the data in each cell of the 7th row to Double-Height (using one 6-byte I2C Read, 10 x EEPROM Table Reads and two pairs of 5-byte I2C Writes). In due course, I should be updating the program in my original thread (linked above) with more character-enlarging and cell-graphics algorithms.


Cheers, Alan.
Last edited:


Senior Member
... the SH1106 data sheet makes a better attempt to explain when a $80 "Continuation" Control Byte should be used instead of a $00 Byte, although this still seems quite confusing ...
I've read through both data sheets again, tried more practical tests and believe I now "understand" the command protocol, although it doesn't seem particularly Logical or Efficient, so maybe I'm still "missing" something. Attached is Table 7 from the SH1107 data sheet, which may be helpful, but it does appear to contain a few "errors". For example, the I2C Slave address is shown as 6 or 7 bits, whilst by definition a serial "Address Byte" should be 8 bits. In PICaxe Basic, the Slave Address (Byte) is declared within the HI2CSETUP command, so is not contained in the HI2CIN/OUT commands (at least with the M2 syntax).


The first "confusing" aspects of this display controller protocol description are that the "Control" byte is NOT the same as a "Command" byte, and the "DATA" bytes are used in 3 or 4 different ways. :( The CONTROL byte contains only two flags, the "RAM/Command" selector (bit 6, having a value of $40) which determines whether the subsequent "DATA" byte represents Display Pixels, or a Command. Then the "Continuation" flag (bit 7, having a value of $80) indicates that ALL subsequent bytes will be "Data", IF it is NOT SET (i.e. 0). However, this "Data" might be interpreted as a "Command", or a "Command Parameter" to form a Command Word, (e.g. "Brightness value = 127" is $81 , $7F ), or a "RAM Data Byte" (Display Pixels) , or (it appears) another Control Byte ($80), if the Continuation flag was previously set.

To fit this into the normal PICaxe/I2C syntax: the Control Byte is the "Location" byte which is (optionally) placed before the brackets ( ) and often used to select a "register number". Then all the Data bytes are listed within the brackets. This format is mandatory for HI2CIN (reading) so that it can identify which bytes are transmitted to and received from the Slave (Display RAM). However, for HI2COUT (writing), all bytes are sent to the Slave, so the Location byte(s) can be placed before or inside the brackets, but it is sensible to retain the "standard" format.

Thus, the normal "Command" format becomes: HI2COUT $0 , ( data bytes ) where the "data bytes" can be a long list of Command and (when appropriate) Parameter bytes. Then, the "Display Data" format is: HI2COUT $40 , ( pixel bytes ) where each byte represents a column of 8 pixels (LSB at the top) running L-R along a horizontal row. It also appears that Command and Pixel bytes can be mixed within a single packet, for example to change the Row number and write pixels, e.g. : HI2COUT $80 , ( row byte , $40 , pixel bytes) .

However, the latter ($80 control byte) seems less "useful" than might be expected. The problem is that the destination column number is split over two nibbles (in different bytes) and auto-increments when each pixels byte is received (required of course to accept a string of data bytes). Thus to start writing a "Double-Height" character into its second row, it may be necessary to change the Row number, the Low nibble and the High nibble Column bytes in a command of the form: HI2COUT $80 , ( Row , $80 , Column Low , $80 , Column High , $40 , Pixel bytes ) , but that transmits more control/command bytes than are needed to transmit two separate command and pixels sequences, even taking into account the additional (duplicated) Slave Address.

That seems to be confirmed by the Logic Analyser screenshot below, but note that the "I2Cspeed" was set to I2CSLOW_32 even for the 4 MHz clock, so the I2C bus speed is much slower than normal (hardly more than 9600 baud serial). The reason for doing this is that (at "normal" speed) the bus data bytes are so much shorter than the PICaxe interpreter delays, that it's impossible to read the data values for a complete packet in the analyser's Address/Data decoding line.


A further issue is that Table 7 above indicates the "Continuation : Command" sequence is "2n bytes", but what if the Command is a Word or longer (the SDD1306 has command packets up to 6 bytes long) ? Is it necessary to interleave a $80 (control byte) between EVERY data byte (command or parameter) ? That would make the protocol very inefficient, but I haven't tried any comprehensive tests, because the continuation byte doesn't appear to offer useful speed benefits anyway (at least with the PICaxe).

An interesting feature (with PICaxe) is that the gap (delay) between the Slave Address and the Control/Location byte(s) is much shorter than between the data bytes. Thus the I2C data transfer appears to execute slightly faster if the Control/Location is retained outside of the brackets, (but perhaps there is a longer delay before the Start bit is transmitted ?). Therefore, the program might execute even faster if the first Data byte were moved to outside of the brackets, to form a Word Address. In particular, the first "pixels" data byte can be the inter-character "Gap" byte (generally zero), which could be logically combined with the Control byte, avoiding the need to include a Gap byte within the Program Subroutine font lists (or elsewhere). However, this is probably only sensible if the main part of the program has been constructed from the start using a Word Address (and the address is stored as a variable, not as a constant).

So finally, here is a "full speed" Logic Analyser screenshot comparing Word and Byte addressing with realistic I2C and PICaxe interpreter speeds (at I2CFAST_4 ) to transmit a "Cursor Position" command (Row number, Low and High nibble Bytes) followed by 5 "Pixels" Bytes (to form a single character). Notice the very short delay between the first 3 or 2 bytes (Slave Address and "Location" word or byte) for each of the four messages.


Cheers, Alan.
Last edited: