The SSD1306, a well used OLED driver for arduino projects is only a black and white (on/off) driver.
Meaning, in theory, it can only display 1 bit images. Images where pixels are only on or off...
However, with some caveats and restrictions, it can also be made to display 3 "color" images. Images with pixels 1/2 on...
Here is a link to the file where you will find all the source code that I have used. This code has 2 things in it. A snippet of code to use in your esp32 project. And windows code that will convert a png B&W image into the C struct that you need.
Anyhow, the image here does not show off well what you get as my camera is crap, but you do indeed get some half tones with this technique.
Anyhow: the grayscale trick is as follow:
If you alternate 2 images very fast, and a pixel is on in one image and off in the other, it apears 1/2 lit to the user eye...
So we use 2 images which will have some pixels on in only one of the 2 images and flip them fast.
We will put image 1 in the lines 0 to 31 of the driver framebuffer and image 2 lines 32 to 64 of the framebuffer.
This ONLY works because our screen is 128*32 and not 128*64 as the driver is designed to handle 64 lines screen but we only use 1/2 of them. This gives us enough memory to store the "hidden", 2nd image...
To flip them, we use the 'vertical scroll' feature, scrolling 32 lines (the height of the screen) every 2 frames (which is the fastest we can go). This will lead to image 1 being displayed, then image 2 and then image 1 again and again! Exactly what we want to create the grayscale illusion...
The ONE issue is that the SSD1306 can NOT do only vertical scroll, there is ALWAYS some horizontal scroll, which can be selected to be sets of 8 lines (meaning, from line a*8 to b*8)...
In our case, we choose to scroll the last 8 lines, which correspond to lines 24 to 31 of the 2nd image..
Since our image is for an astronomy club, I populated these last 8 lines with a starfield which gives the impression of moving stars... Works well.
Alternatively, you can let the whole screen scroll horizontally in grayscale mode... This also works (replace the 7,7,7 in the command bellow by 0,7,7...
I hope that you will be able to design similarly interesting images!
Anyhow, here is the "magic command":
// send magic command!
uint8_t const scrollOn[]={
0, // must be here to indicate command
0x29, 0, // vertical scroll command + dummy 0
7, // indicate horizontal scroll start lines (7*8=line 56)
7, // indicate the number of frames between scroll. Here 2 frames... no logic to the numbering
7, // indicate horizontal scroll stop lines (7*8+8=line 64)
32, // indicate the number of lines to scroll vertically. Here 32 to go from image 1 to 2...
0x2f}; // starts scrolling...
display.send(scrollOn, 8); // send 8 bytes through I2C...