In order to communicate with the display you need to set up pico's i2c communication
target_link_libraries(project_name
hardware_i2c)
This is explained in readme.md section 1
Pico has 2 i2c controllers. You can use any of these, just remember to pass according i2c instance in code
for this example pins 12 and 13 are used for sda and scl lines. These are connected to i2c0 controller. You can use any pins capable of i2c communication but if they are connected to i2c1 controller you'll need to change the code when initializing i2c communication and when creating a display object
you need
#include "hardware/i2c.h"
#include "pico-ssd1306/ssd1306.h"
i2c init and pin init:
i2c_init(i2c0, 1000000); // Init i2c0 and set baud rate to 1Mhz (max supported speed by pico)
// lower baud rates make the communication slower, and since this lib uses blocking writing,
// lower baud rates lower maximal achievable frame rate
gpio_set_function(12, GPIO_FUNC_I2C); // Set pins 12 and 13 for i2c role
gpio_set_function(13, GPIO_FUNC_I2C);
gpio_pull_up(12); // Pull up the pins for proper communication
gpio_pull_up(13);
When creating a display object you need to provide 3 arguments. Witch i2c controller you want to use (the one you initialized above), display address (usually 0x3C or 0x3D with ssd1306) and display size. This library defines 2 available sizes Size::W128xH64 and Size::W128xH32
pico_ssd1306::SSD1306 display = pico_ssd1306::SSD1306(i2c0, 0x3D, pico_ssd1306::Size::W128xH64);
From now on you can use the display object to draw anything you want to the display.
Core of ssd1306 library exposes 2 functions for drawing (documentation of all functions is here doxygen).
setPixel()
and addBitmapImage()
but just calling these won't draw anything on screen. These functions only affect the buffer.
To actually display something you need to first draw to buffer (by setPixel()
or addBitmapImage()
) and then sending it to the
display with sendBuffer()
. Same is true for clear()
function. It just clears the buffer, not the screen. so
calling clear()
and sendBuffer()
will actually clear the display.
setPixel()
is pretty self-explanatory. It modifies the state of exactly 1 pixel. Just give it the x and y coordinates,
optionally change the write mode and done. If you're confused about write mode see doxygen
section about write mode. tldr write mode decides whether a pixel is to be set on, off or inverted (on to off and vice versa)
this example set pixel at x = 0 and y = 0 (top left corner) on
//Create a new display object
pico_ssd1306::SSD1306 display = pico_ssd1306::SSD1306(I2C_PORT, 0x3D, pico_ssd1306::Size::W128xH64);
display.setPixel(0, 0);
display.sendBuffer();
Well you can't just shove a png file into c++ and expect it to work. We need a bitmap containing data on which pixels to turn on. This library scans bytes in bit map left to right, top to bottom, so :
#define IMAGE_WIDTH 32
#define IMAGE_HEIGHT 32
uint8_t image[128] = {
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00111111, 0b11111111, 0b11111111, 0b11111100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100,
0b00100000, 0b00000001, 0b00000000, 0b00000100
};
contains such an image:
It's best to keep width to a number divisible by 8. Otherwise, errors occur while shifting bytes and rendering fails. Height does not have such a limitation.
addBitmapImage()
function does pretty much all the work for us. we need to tell it what and where to draw and done.
So what do we need? x and y coordinated on where to anchor an image (anchor is top left point of the image), image and width, height.
display.addBitmapImage(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, image);
display.sendBuffer();
Should draw this image to display at point 0, 0
Entire display now should look like this:
Also note that addBitmapImage()
in default write mode doesn't turn pixels off, so overlaying images is possible, so:
display.addBitmapImage(0, 0, IMAGE_WIDTH, IMAGE_HEIGHT, image);
display.addBitmapImage(8, 0, IMAGE_WIDTH, IMAGE_HEIGHT, image);
display.sendBuffer();
will overlay the same image with 8 pixel to right image, producing:
This function can take an optional argument of write mode, this is same as in setPixel()