Announcing the SH1106 OLED display driver
Similar to the SSD1306 covered previously, the
SH1106 OLED display is a small, self contained module perfect for hacking into a project or
prototype. It's available on AliExpress and eBay for peanuts, but is lacking a Rust driver. Until
now with the sh1106
crate!
The SH1106 crate is compatible with the embedded-hal traits, so should be usable on anything from an ARM micro to a Raspberry Pi. Please note that it currently doesn't use the internal memory of the IC as the display buffer, so could be memory-optimised quite a lot. PRs welcome!
The driver currently only supports I2C as I haven't seen many SPI SH1106 modules in the wild, so let's take a look at the code to hook up the display over I2C. I'll draw the Rust logo as seen in the header image in the following example.
I've left some of the boilerplate out of this code, but you can find the complete example here.
use Image1BPP;
use *;
use *;
use Builder;
let dp = take.unwrap;
let mut flash = dp.FLASH.constrain;
let mut rcc = dp.RCC.constrain;
let clocks = rcc.cfgr.freeze;
let mut afio = dp.AFIO.constrain;
let mut gpiob = dp.GPIOB.split;
let scl = gpiob.pb8.into_alternate_open_drain;
let sda = gpiob.pb9.into_alternate_open_drain;
let i2c = i2c1;
let mut disp: = new.connect_i2c.into;
disp.init.unwrap;
disp.flush.unwrap;
let im = new.translate;
disp.draw;
disp.flush.unwrap;
Simple as that! The image is included with include_bytes!()
and is naturally monochrome to suit
the display. It should be compressed to 1 bit per pixel, which can be done with the following
Imagemagick command:
Now let's break down the example above. First, we need a display in GraphicsMode
, so we can draw
primitives and images.
let mut disp: = new.connect_i2c.into;
This uses the Builder
pattern to connect to the display over I2C. Take a look at the
Builder
docs for more options
like setting display size and rotation. Lastly, we call .into()
to convert the display from
RawMode
into GraphicsMode
. If you want to just draw individual pixels, skip this step.
Next, we need to initialise the display with init()
and flush()
(to clear it).
disp.init.unwrap;
disp.flush.unwrap;
And now, draw the image:
let im = new.translate;
disp.draw;
disp.flush.unwrap;
The image is drawn to the display buffer by using im.into_iter()
. embedded-graphics
tries hard
to use as little memory as possible internally, and iterators really help with that! Currently, the
SH1106 crate uses a framebuffer, but the IC contains its own memory that can be read and written.
This means that, in the future, a microcontroller could consume almost no memory when driving this
display. Awesome. Once the image is in the buffer, a call to disp.flush()
sends the buffer to the
screen.
Please help test this crate out! There are a few examples that should Just Work on an STM32 Blue Pill, but it would be great to test this crate on other devices as well. The SH1106 contains a readable framebuffer which the crate currently doesn't use. This wastes a lot of memory on the microcontroller, so using the builtin framebuffer is a priority for future work.