Undocumented grayscale mode on the ssd1306

Hi everyone, i discovered a undocumented register that turns on a 128x32 grayscale mode on the ssd1306

this is no temporal dither, the display does all by itself

i am looking for volunteers to test if this works on every ssd1306

the demo is configured for megas2560, the i2c ports can be changed in tinymulios.h

demo and registersettings:


Is it similar to ArduboyG, grayscale library that Dark & Under II - another dungeon crawler uses?

no, its a undocumented register, i can unplug the i2c

the resolution is 128x32, two pixels on y get summed up to one, so its 3 gray levels.

up until now i have 1 report of this working on a other ssd1306.

This sounds interesting! Some questions:

  1. How did you find the register? By fuzzing the SSD1306?
  2. I presume it will work over spi?
  3. What happens if you change the addressing mode (vertical/horizontal)?

Summoning @brow1067 :mage:

Holy moly! Yeah how did you find this?

How is the pixel color sent in the data?

Should be easy to set the register that’s just a command in spi but the display() function would have to be rewritten (obviously)

i only tested it on i2c,

i did the following, i send invert + command + un-invert

if the uninvert does not work i add a byte after the command… until it works

then i wrote 0,255, 1, 2,4,8… to these parameters…

i found several registers, but this one is the only one that seems to do something usefull, but there are more, including several registers taking 3 bytes as parameter, one has a slight effect on the brightness, but i am not yet finished probing, so i might find more

this is one of the registers where i do not know what is really happening:

1 Like

the pixel format is simple 2 pixels on y become one, summed up pixel at (0,0) = 1 , pixel at (0,1) = 0 → both pixels get 0.5 brightness if both are 1 its 1 brightness for both (and for 0 the same)

So it’s got like a virtual 128x128 buffer that is summed down to 128x64?

EDIT: I suppose it’s probably doing the summing in line, so probably wrong to call it a true virtual size, but this is how many pixels you would send out

the display function is just my dithering engine, it should be enough to send:

0x9a,2,0xd6,1 and have a image in a correct format in the vram…

128x64 becomes 128x32, by joining line 0 and 1, line 2 and 3 and so on

Haha wow, incredible, so like it’s “2-bit vertical interleaved”? is maybe what it could be called?

in this test i switch it on and off:



i think the movement is caused by some bug in my code, probably i set some register to a bad value.

the adressing mode is irrelevant, i work in vertical, but it just depends on the contents of the vram, no matter how you write them.

1 Like

Nice !! I always hoped someone would fuzz the chip! :star2:
There’s some rumoured instructions: to do a hard reset without using the electrical reset pin, also to do single pixel scrolling.

one more test:

i found several registers for scrolling on the y axis. probably placing a window in the center of the screen and scrolling it is no problem, i am just not interested in scrolling, because my engine allways writes the whole screen.

I have just granted you the newly created badge:


Hardware grayscale prophet. You have shown us the way.

First person to get it working on arduboy hardware can also get the badge!


The Arduboy’s SSD1306 is SPI rather than I2C, but hopefully that won’t make a difference.


Even Y Bit Odd Y Bit Result
0 0 Black
0 1 Grey
1 0 Grey
1 1 White

I.e. when only one bit is set it produces the same colour, regardless of which bit is set.

I’m checking because there’s another meaning to ‘summing’ bits, and because having three shades rather than four is a bit surprising.

Considering the screen has 128x64 physical pixels, does that mean:

  • That 128x32 worth of data is actually filling two pixels one above the other, effectively giving rectangular (1x2) pixels?
  • Two 128x64 frames of data must be sent to fill the whole screen?
    • (I.e. one for each horizontal half of the screen.)
  • A single 128x128 frame of data must be sent to fill the whole screen?

I skimmed the code briefly, but it’s quite a complex example so I wasn’t able to discern much from such a quick look.

What are the actual commands involved in terms of opcode value and payload?
(Preferably in hex, but decimal is fine too.)

1 Like

Oh I didn’t catch that! It’s vertically doubled pixels!! Ok very weird indeed!

EDIT: I wonder if what the hardware is doing is just drawing a row of 2 pixels and just flipping back and forth between the data from either row.

UPDATE: I’ll email https://www.solomon-systech.com/

1 Like

Bear in mind that the SSD1306 actually draws rows of columns…

Image Format

Or at least that’s how the frame buffer is laid out so I presume that’s the order that the screen processes the pixels in.