/** * Copyright (c) 2021 Raspberry Pi (Trading) Ltd. * * SPDX-License-Identifier: BSD-3-Clause */ #include #include #include #include #include "pico/stdlib.h" #include "pico/binary_info.h" #include "hardware/i2c.h" #include "raspberry26x32.h" #include "ssd1306_font.h" /* Example code to talk to an SSD1306-based OLED display The SSD1306 is an OLED/PLED driver chip, capable of driving displays up to 128x64 pixels. NOTE: Ensure the device is capable of being driven at 3.3v NOT 5v. The Pico GPIO (and therefore I2C) cannot be used at 5v. You will need to use a level shifter on the I2C lines if you want to run the board at 5v. Connections on Raspberry Pi Pico board, other boards may vary. GPIO PICO_DEFAULT_I2C_SDA_PIN (on Pico this is GP4 (pin 6)) -> SDA on display board GPIO PICO_DEFAULT_I2C_SCL_PIN (on Pico this is GP5 (pin 7)) -> SCL on display board 3.3v (pin 36) -> VCC on display board GND (pin 38) -> GND on display board */ // Define the size of the display we have attached. This can vary, make sure you // have the right size defined or the output will look rather odd! // Code has been tested on 128x32 and 128x64 OLED displays #define SSD1306_HEIGHT 32 #define SSD1306_WIDTH 128 #define SSD1306_I2C_ADDR _u(0x3C) // 400 is usual, but often these can be overclocked to improve display response. // Tested at 1000 on both 32 and 84 pixel height devices and it worked. #define SSD1306_I2C_CLK 400 //#define SSD1306_I2C_CLK 1000 // commands (see datasheet) #define SSD1306_SET_MEM_MODE _u(0x20) #define SSD1306_SET_COL_ADDR _u(0x21) #define SSD1306_SET_PAGE_ADDR _u(0x22) #define SSD1306_SET_HORIZ_SCROLL _u(0x26) #define SSD1306_SET_SCROLL _u(0x2E) #define SSD1306_SET_DISP_START_LINE _u(0x40) #define SSD1306_SET_CONTRAST _u(0x81) #define SSD1306_SET_CHARGE_PUMP _u(0x8D) #define SSD1306_SET_SEG_REMAP _u(0xA0) #define SSD1306_SET_ENTIRE_ON _u(0xA4) #define SSD1306_SET_ALL_ON _u(0xA5) #define SSD1306_SET_NORM_DISP _u(0xA6) #define SSD1306_SET_INV_DISP _u(0xA7) #define SSD1306_SET_MUX_RATIO _u(0xA8) #define SSD1306_SET_DISP _u(0xAE) #define SSD1306_SET_COM_OUT_DIR _u(0xC0) #define SSD1306_SET_COM_OUT_DIR_FLIP _u(0xC0) #define SSD1306_SET_DISP_OFFSET _u(0xD3) #define SSD1306_SET_DISP_CLK_DIV _u(0xD5) #define SSD1306_SET_PRECHARGE _u(0xD9) #define SSD1306_SET_COM_PIN_CFG _u(0xDA) #define SSD1306_SET_VCOM_DESEL _u(0xDB) #define SSD1306_PAGE_HEIGHT _u(8) #define SSD1306_NUM_PAGES (SSD1306_HEIGHT / SSD1306_PAGE_HEIGHT) #define SSD1306_BUF_LEN (SSD1306_NUM_PAGES * SSD1306_WIDTH) #define SSD1306_WRITE_MODE _u(0xFE) #define SSD1306_READ_MODE _u(0xFF) struct render_area { uint8_t start_col; uint8_t end_col; uint8_t start_page; uint8_t end_page; int buflen; }; void calc_render_area_buflen(struct render_area *area) { // calculate how long the flattened buffer will be for a render area area->buflen = (area->end_col - area->start_col + 1) * (area->end_page - area->start_page + 1); } #ifdef i2c_default void SSD1306_send_cmd(uint8_t cmd) { // I2C write process expects a control byte followed by data // this "data" can be a command or data to follow up a command // Co = 1, D/C = 0 => the driver expects a command uint8_t buf[2] = {0x80, cmd}; i2c_write_blocking(i2c_default, SSD1306_I2C_ADDR, buf, 2, false); } void SSD1306_send_cmd_list(uint8_t *buf, int num) { for (int i=0;istart_col, area->end_col, SSD1306_SET_PAGE_ADDR, area->start_page, area->end_page }; SSD1306_send_cmd_list(cmds, count_of(cmds)); SSD1306_send_buf(buf, area->buflen); } static void SetPixel(uint8_t *buf, int x,int y, bool on) { assert(x >= 0 && x < SSD1306_WIDTH && y >=0 && y < SSD1306_HEIGHT); // The calculation to determine the correct bit to set depends on which address // mode we are in. This code assumes horizontal // The video ram on the SSD1306 is split up in to 8 rows, one bit per pixel. // Each row is 128 long by 8 pixels high, each byte vertically arranged, so byte 0 is x=0, y=0->7, // byte 1 is x = 1, y=0->7 etc // This code could be optimised, but is like this for clarity. The compiler // should do a half decent job optimising it anyway. const int BytesPerRow = SSD1306_WIDTH ; // x pixels, 1bpp, but each row is 8 pixel high, so (x / 8) * 8 int byte_idx = (y / 8) * BytesPerRow + x; uint8_t byte = buf[byte_idx]; if (on) byte |= 1 << (y % 8); else byte &= ~(1 << (y % 8)); buf[byte_idx] = byte; } // Basic Bresenhams. static void DrawLine(uint8_t *buf, int x0, int y0, int x1, int y1, bool on) { int dx = abs(x1-x0); int sx = x0= dy) { err += dy; x0 += sx; } if (e2 <= dx) { err += dx; y0 += sy; } } } static inline int GetFontIndex(uint8_t ch) { if (ch >= 'A' && ch <='Z') { return ch - 'A' + 1; } else if (ch >= '0' && ch <='9') { return ch - '0' + 27; } else return 0; // Not got that char so space. } static uint8_t reversed[sizeof(font)] = {0}; static uint8_t reverse(uint8_t b) { b = (b & 0xF0) >> 4 | (b & 0x0F) << 4; b = (b & 0xCC) >> 2 | (b & 0x33) << 2; b = (b & 0xAA) >> 1 | (b & 0x55) << 1; return b; } static void FillReversedCache() { // calculate and cache a reversed version of fhe font, because I defined it upside down...doh! for (int i=0;i SSD1306_WIDTH - 8 || y > SSD1306_HEIGHT - 8) return; // For the moment, only write on Y row boundaries (every 8 vertical pixels) y = y/8; ch = toupper(ch); int idx = GetFontIndex(ch); int fb_idx = y * 128 + x; for (int i=0;i<8;i++) { buf[fb_idx++] = reversed[idx * 8 + i]; } } static void WriteString(uint8_t *buf, int16_t x, int16_t y, char *str) { // Cull out any string off the screen if (x > SSD1306_WIDTH - 8 || y > SSD1306_HEIGHT - 8) return; while (*str) { WriteChar(buf, x, y, *str++); x+=8; } } #endif int main() { stdio_init_all(); #if !defined(i2c_default) || !defined(PICO_DEFAULT_I2C_SDA_PIN) || !defined(PICO_DEFAULT_I2C_SCL_PIN) #warning i2c / SSD1306_i2d example requires a board with I2C pins puts("Default I2C pins were not defined"); #else // useful information for picotool bi_decl(bi_2pins_with_func(PICO_DEFAULT_I2C_SDA_PIN, PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C)); bi_decl(bi_program_description("SSD1306 OLED driver I2C example for the Raspberry Pi Pico")); printf("Hello, SSD1306 OLED display! Look at my raspberries..\n"); // I2C is "open drain", pull ups to keep signal high when no data is being // sent i2c_init(i2c_default, SSD1306_I2C_CLK * 1000); gpio_set_function(PICO_DEFAULT_I2C_SDA_PIN, GPIO_FUNC_I2C); gpio_set_function(PICO_DEFAULT_I2C_SCL_PIN, GPIO_FUNC_I2C); gpio_pull_up(PICO_DEFAULT_I2C_SDA_PIN); gpio_pull_up(PICO_DEFAULT_I2C_SCL_PIN); // run through the complete initialization process SSD1306_init(); // Initialize render area for entire frame (SSD1306_WIDTH pixels by SSD1306_NUM_PAGES pages) struct render_area frame_area = { start_col: 0, end_col : SSD1306_WIDTH - 1, start_page : 0, end_page : SSD1306_NUM_PAGES - 1 }; calc_render_area_buflen(&frame_area); // zero the entire display uint8_t buf[SSD1306_BUF_LEN]; memset(buf, 0, SSD1306_BUF_LEN); render(buf, &frame_area); // intro sequence: flash the screen 3 times for (int i = 0; i < 3; i++) { SSD1306_send_cmd(SSD1306_SET_ALL_ON); // Set all pixels on sleep_ms(500); SSD1306_send_cmd(SSD1306_SET_ENTIRE_ON); // go back to following RAM for pixel state sleep_ms(500); } // render 3 cute little raspberries struct render_area area = { start_page : 0, end_page : (IMG_HEIGHT / SSD1306_PAGE_HEIGHT) - 1 }; restart: area.start_col = 0; area.end_col = IMG_WIDTH - 1; calc_render_area_buflen(&area); uint8_t offset = 5 + IMG_WIDTH; // 5px padding for (int i = 0; i < 3; i++) { render(raspberry26x32, &area); area.start_col += offset; area.end_col += offset; } SSD1306_scroll(true); sleep_ms(5000); SSD1306_scroll(false); char *text[] = { "A long time ago", " on an OLED ", " display", " far far away", "Lived a small", "red raspberry", "by the name of", " PICO" }; int y = 0; for (int i = 0 ;i < count_of(text); i++) { WriteString(buf, 5, y, text[i]); y+=8; } render(buf, &frame_area); // Test the display invert function sleep_ms(3000); SSD1306_send_cmd(SSD1306_SET_INV_DISP); sleep_ms(3000); SSD1306_send_cmd(SSD1306_SET_NORM_DISP); bool pix = true; for (int i = 0; i < 2;i++) { for (int x = 0;x < SSD1306_WIDTH;x++) { DrawLine(buf, x, 0, SSD1306_WIDTH - 1 - x, SSD1306_HEIGHT - 1, pix); render(buf, &frame_area); } for (int y = SSD1306_HEIGHT-1; y >= 0 ;y--) { DrawLine(buf, 0, y, SSD1306_WIDTH - 1, SSD1306_HEIGHT - 1 - y, pix); render(buf, &frame_area); } pix = false; } goto restart; #endif return 0; }