diff --git a/README.md b/README.md index f43a7339a..d3f28099d 100644 --- a/README.md +++ b/README.md @@ -370,6 +370,7 @@ App|Description [spi_master_slave](spi/spi_master_slave) | Demonstrate SPI communication as master and slave. [max7219_8x7seg_spi](spi/max7219_8x7seg_spi) | Attaching a Max7219 driving an 8 digit 7 segment display via SPI. [max7219_32x8_spi](spi/max7219_32x8_spi) | Attaching a Max7219 driving an 32x8 LED display via SPI. +[ssd1309_spi](spi/ssd1309_spi) | Display text on a SSD1309-driven OLED display via SPI. ### Status LED diff --git a/spi/CMakeLists.txt b/spi/CMakeLists.txt index d61abb2aa..7e6835ab3 100644 --- a/spi/CMakeLists.txt +++ b/spi/CMakeLists.txt @@ -4,6 +4,7 @@ if (TARGET hardware_spi) add_subdirectory_exclude_platforms(spi_dma) add_subdirectory_exclude_platforms(spi_master_slave) add_subdirectory_exclude_platforms(spi_flash) + add_subdirectory_exclude_platforms(ssd1309_spi) add_subdirectory_exclude_platforms(max7219_32x8_spi) add_subdirectory_exclude_platforms(max7219_8x7seg_spi) else() diff --git a/spi/ssd1309_spi/CMakeLists.txt b/spi/ssd1309_spi/CMakeLists.txt new file mode 100644 index 000000000..a9980e395 --- /dev/null +++ b/spi/ssd1309_spi/CMakeLists.txt @@ -0,0 +1,12 @@ +add_executable(ssd1309_spi + ssd1309_spi.c + ) + +# pull in common dependencies and additional spi hardware support +target_link_libraries(ssd1309_spi pico_stdlib hardware_spi) + +# create map/bin/hex file etc. +pico_add_extra_outputs(ssd1309_spi) + +# add url via pico_set_program_url +example_auto_set_url(ssd1309_spi) diff --git a/spi/ssd1309_spi/README.adoc b/spi/ssd1309_spi/README.adoc new file mode 100644 index 000000000..2dd2fe98d --- /dev/null +++ b/spi/ssd1309_spi/README.adoc @@ -0,0 +1,59 @@ += Connecting an OLED display (SSD1309) via SPI + +An example showing simple use of a small monochrome OLED display based on the widely-used SSD1309 controller via the SPI interface. It should also work on compatible displays such as those based on the SSD1306. + +Such displays typically support either I2C or SPI. For this example you will need one configured for SPI. + +The interface uses one of the Pico's onboard SPI peripherals and two extra GPIO pins. The code simply initialises the display and shows some text in a basic font: it aims to be easy-to-follow rather than a full-featured driver. + +For details of the commands supported by the SSD1309 controller and its addressing modes see the manufacturer's datasheet: https://www.hpinfotech.ro/SSD1309.pdf. + +Note that the SPI interface for the SSD1309 is transmit-only. + + +== Wiring information + +Wiring up the device requires seven jumpers as follows: + + * Display CS (chip select) -> Pico GP17 (SPI0 CSn), pin 22 + * Display DC (data/command) -> Pico GP20, pin 26 + * Display RES (reset) -> Pico GP21, pin 27 + * Display SDA (MOSI) -> Pico GP19 (SPI0 TX), pin 25 + * Display SCLK (SPI clock) -> Pico GP18 (SPI0 SCK), pin 24 + * Display VDD (3.3v) -> Pico 3V3_OUT, pin 36 + * Display VSS (0v, gnd) -> Pico GND, pin 23 + +The example uses SPI device 0 and powers the display from the Pico 3.3v output. If you power the display from an external supply then ensure that the Pico's logic pins are not exposed to any voltage higher than 3.3v. + +[NOTE] +====== +The pins on your board may be labelled slightly differently to the list above. Check the documentation for your display. + +You can change the code to use different pins if you like, but the ones for CSn, TX and SCK must match your SPI device: see the GPIO Function Select Table in the datasheet. +====== + + +[[wiring_diagram.png]] +[pdfwidth=75%] +.Wiring Diagram for SSD1309 with SPI interface +image::wiring_diagram.png[] + +== List of Files + +CMakeLists.txt:: A file to configure the CMake build system for the example. +ssd1309_spi.c:: The example code. +ssd1309_font.h:: A basic 8x8 bit font table used by the example. + + +== Bill of Materials + +.A list of materials required for the example +[[SSD1309-bom-table]] +[cols=3] +|=== +| *Item* | *Quantity* | Details +| Breadboard | 1 | generic part +| Raspberry Pi Pico or Pico 2 | 1 | https://www.raspberrypi.com/products/raspberry-pi-pico/ +| SSD1309-based OLED display panel with SPI interface| 1 | generic part +| M/F Jumper wires (assorted colours) | 7 | generic part +|=== \ No newline at end of file diff --git a/spi/ssd1309_spi/ssd1309_font.h b/spi/ssd1309_spi/ssd1309_font.h new file mode 100644 index 000000000..e07fc460d --- /dev/null +++ b/spi/ssd1309_spi/ssd1309_font.h @@ -0,0 +1,55 @@ +/** + * Copyright (c) 2022 Raspberry Pi (Trading) Ltd. + * + * SPDX-License-Identifier: BSD-3-Clause + */ + +// Indices of different parts of the font table, to help +// when converting different types of characters +#define FONT_INDEX_SPACE 0 +#define FONT_INDEX_A 1 +#define FONT_INDEX_0 27 + + +// Vertical bitmaps, A-Z, 0-9. Each is 8 pixels high and wide +// These are defined vertically to make them quick to copy to FB + +static uint8_t font[] = { +0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // Nothing +0x78, 0x14, 0x12, 0x11, 0x12, 0x14, 0x78, 0x00, //A +0x7f, 0x49, 0x49, 0x49, 0x49, 0x49, 0x7e, 0x00, //B +0x7e, 0x41, 0x41, 0x41, 0x41, 0x41, 0x41, 0x00, //C +0x7f, 0x41, 0x41, 0x41, 0x41, 0x41, 0x7e, 0x00, //D +0x7f, 0x49, 0x49, 0x49, 0x49, 0x49, 0x49, 0x00, //E +0x7f, 0x09, 0x09, 0x09, 0x09, 0x01, 0x01, 0x00, //F +0x7e, 0x41, 0x41, 0x41, 0x51, 0x51, 0x72, 0x00, //G +0x7f, 0x08, 0x08, 0x08, 0x08, 0x08, 0x7f, 0x00, //H +0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00, 0x00, //I +0x00, 0x21, 0x41, 0x41, 0x3f, 0x01, 0x01, 0x00, //J +0x00, 0x7f, 0x08, 0x08, 0x14, 0x22, 0x41, 0x00, //K +0x00, 0x7f, 0x40, 0x40, 0x40, 0x40, 0x40, 0x00, //L +0x7f, 0x02, 0x04, 0x08, 0x04, 0x02, 0x7f, 0x00, //M +0x7f, 0x02, 0x04, 0x08, 0x10, 0x20, 0x7f, 0x00, //N +0x3e, 0x41, 0x41, 0x41, 0x41, 0x41, 0x3e, 0x00, //O +0x7f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, //P +0x3e, 0x41, 0x41, 0x49, 0x51, 0x61, 0x7e, 0x00, //Q +0x7f, 0x11, 0x11, 0x11, 0x31, 0x51, 0x0e, 0x00, //R +0x46, 0x49, 0x49, 0x49, 0x49, 0x30, 0x00, 0x00, //S +0x01, 0x01, 0x01, 0x7f, 0x01, 0x01, 0x01, 0x00, //T +0x3f, 0x40, 0x40, 0x40, 0x40, 0x40, 0x3f, 0x00, //U +0x0f, 0x10, 0x20, 0x40, 0x20, 0x10, 0x0f, 0x00, //V +0x7f, 0x20, 0x10, 0x08, 0x10, 0x20, 0x7f, 0x00, //W +0x41, 0x22, 0x14, 0x08, 0x14, 0x22, 0x41, 0x00, //X +0x01, 0x02, 0x04, 0x78, 0x04, 0x02, 0x01, 0x00, //Y +0x41, 0x61, 0x59, 0x45, 0x43, 0x41, 0x00, 0x00, //Z +0x00, 0x3e, 0x41, 0x49, 0x41, 0x3e, 0x00, 0x00, //0 +0x00, 0x00, 0x42, 0x7f, 0x40, 0x00, 0x00, 0x00, //1 +0x00, 0x62, 0x51, 0x49, 0x49, 0x46, 0x00, 0x00, //2 +0x00, 0x49, 0x49, 0x49, 0x49, 0x36, 0x00, 0x00, //3 +0x00, 0x3f, 0x20, 0x78, 0x20, 0x20, 0x00, 0x00, //4 +0x4f, 0x49, 0x49, 0x49, 0x49, 0x30, 0x00, 0x00, //5 +0x3e, 0x49, 0x49, 0x49, 0x49, 0x30, 0x00, 0x00, //6 +0x01, 0x01, 0x61, 0x11, 0x0d, 0x03, 0x00, 0x00, //7 +0x00, 0x36, 0x49, 0x49, 0x49, 0x49, 0x36, 0x00, //8 +0x00, 0x06, 0x09, 0x09, 0x09, 0x7e, 0x00, 0x00 //9 +}; \ No newline at end of file diff --git a/spi/ssd1309_spi/ssd1309_spi.c b/spi/ssd1309_spi/ssd1309_spi.c new file mode 100644 index 000000000..4ffd1303f --- /dev/null +++ b/spi/ssd1309_spi/ssd1309_spi.c @@ -0,0 +1,152 @@ +/** + * Copyright (c) 2025 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause +**/ + +// A simple example to display text on an ssd1309-based OLED display with +// an SPI interface. It should also work on a ssd1306 device (not tested). +// +// To understand the commands and addressing modes for the display refer to +// the manufacturer's datasheet at https://www.hpinfotech.ro/SSD1309.pdf + + +#include +#include "pico/stdlib.h" +#include "hardware/spi.h" +#include "ctype.h" +#include "ssd1309_font.h" + + +// We will use the spi0 peripheral and the following GPIO pins (see the +// GPIO function select table in the RPi Pico datasheet). +#define SPI_DEVICE spi0 +#define PIN_CS 17 // chip select (active low) +#define PIN_SCK 18 // SPI clock +#define PIN_MOSI 19 // SPI data transmit (MOSI) +#define PIN_DC 20 // data/command mode (low for command) +#define PIN_R 21 // reset (active low) + +// ssd1309 accepts a maximum SPI clock rate of 10 Mbit/sec, but for +// this simple example we don't need to go that fast. +#define SPI_BITRATE 1 * 1000 * 1000 + +// dimensions of the display +#define NUM_X_PIXELS 128 +#define NUM_Y_PIXELS 64 + +// modes for the data/command pin +#define DC_COMMAND_MODE 0 +#define DC_DATA_MODE 1 + + +// send a command byte to the display +void send_command(uint8_t cmd_byte) { + gpio_put(PIN_DC, DC_COMMAND_MODE); + spi_write_blocking(SPI_DEVICE, &cmd_byte, 1); +} + +// send a data byte to the display +void send_data(uint8_t data_byte) { + gpio_put(PIN_DC, DC_DATA_MODE); + spi_write_blocking(SPI_DEVICE, &data_byte, 1); +} + +// set the text cursor position (row, col) +// +// `row` is the text row, from 0 (top) to (NUM_Y_PIXELS / 8) - 1 +// `col` is the text column, from 0 (left) to (NUM_X_PIXELS / 8) - 1 +void set_cursor_pos(uint text_row, uint text_col) { + send_command(0xb0 + (text_row & 0x07)); // set the text row ('page') start address + uint col = text_col * 8; + send_command(col & 0x0f); // set the column start address (low nibble) + send_command(0x10 + ((col & 0xf0) >> 4)); // set the column start address (high nibble) +} + +// display a single text character at the cursor position +void display_char(char c) { + uint font_index = FONT_INDEX_SPACE; // default (space) + c = toupper(c); + if (isalpha(c)) { + font_index = c - 'A' + FONT_INDEX_A; // a-z and A-Z + } else if (isdigit(c)) { + font_index = c - '0' + FONT_INDEX_0; // 0-9 + } + gpio_put(PIN_DC, DC_DATA_MODE); + spi_write_blocking(SPI_DEVICE, &(font[font_index * 8]), 8); +} + +// display a null-terminated string at the cursor position +void display_string(const char *str) { + while(*str) { + display_char(*str); + str += 1; + } +} + +// clear the display +void display_clear() { + for (int text_row = 0; text_row < (NUM_Y_PIXELS / 8); text_row += 1) { + set_cursor_pos(text_row, 0); + for (int col = 0; col < NUM_X_PIXELS; col += 1) { + send_data(0x00); + } + } + set_cursor_pos(0, 0); +} + +// initialise the SPI interface and reset the display +void display_init() { + // ssd1309 communicates using Motorola SPI mode 0 + spi_init(SPI_DEVICE, SPI_BITRATE); + spi_set_format(SPI_DEVICE, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); // mode 0 + + // configure and initialise our interface pins + gpio_set_function(PIN_CS, GPIO_FUNC_SPI); + gpio_set_function(PIN_SCK, GPIO_FUNC_SPI); + gpio_set_function(PIN_MOSI, GPIO_FUNC_SPI); + gpio_init(PIN_R); + gpio_set_dir(PIN_R, GPIO_OUT); + gpio_init(PIN_DC); + gpio_set_dir(PIN_DC, GPIO_OUT); + + // send an active-low reset pulse to the display + gpio_put(PIN_R, 0); + sleep_ms(1); + gpio_put(PIN_R, 1); + + // after a short pause, send the 'display on' command + sleep_ms(1); + send_command(0xaf); + display_clear(); +} + + +int main() { + stdio_init_all(); + + // initialise the interface and reset the display + display_init(); + + // Show some text + // + // Note: in the display's default addressing mode, over-length + // strings will just wrap on the same row (see the datasheet) + + set_cursor_pos(0, 0); + display_string("abcdefghijklmnop"); + + set_cursor_pos(1, 3); + display_string("qrstuvwxyz"); + + // show a steadily increasing counter + uint count = 0; + char count_str[10]; + while(true) { + snprintf(count_str, sizeof(count_str), "%u", count); + set_cursor_pos(3, 7); + display_string(count_str); + count += 1; + sleep_ms(250); + } +} diff --git a/spi/ssd1309_spi/wiring_diagram.fzz b/spi/ssd1309_spi/wiring_diagram.fzz new file mode 100644 index 000000000..487d728f7 Binary files /dev/null and b/spi/ssd1309_spi/wiring_diagram.fzz differ diff --git a/spi/ssd1309_spi/wiring_diagram.png b/spi/ssd1309_spi/wiring_diagram.png new file mode 100644 index 000000000..4a9a85fdc Binary files /dev/null and b/spi/ssd1309_spi/wiring_diagram.png differ