diff --git a/README.md b/README.md index f43a7339a..c4a3b2b46 100644 --- a/README.md +++ b/README.md @@ -368,6 +368,7 @@ App|Description [spi_dma](spi/spi_dma) | Use DMA to transfer data both to and from the SPI simultaneously. The SPI is configured for loopback. [spi_flash](spi/spi_flash) | Erase, program and read a serial flash device attached to one of the SPI controllers. [spi_master_slave](spi/spi_master_slave) | Demonstrate SPI communication as master and slave. +[ssd1309_stdout_spi](spi/ssd1309_stdout_spi/) | Display text from stdout and simple graphics on an SSD1309-based OLED panel via SPI. [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. diff --git a/spi/CMakeLists.txt b/spi/CMakeLists.txt index d61abb2aa..83fb5d9a2 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_stdout_spi) add_subdirectory_exclude_platforms(max7219_32x8_spi) add_subdirectory_exclude_platforms(max7219_8x7seg_spi) else() diff --git a/spi/ssd1309_stdout_spi/CMakeLists.txt b/spi/ssd1309_stdout_spi/CMakeLists.txt new file mode 100644 index 000000000..239a1d4ee --- /dev/null +++ b/spi/ssd1309_stdout_spi/CMakeLists.txt @@ -0,0 +1,16 @@ +add_executable(ssd1309_stdout_spi + ssd1309_stdout_spi.c + ) + +# pull in common dependencies and additional hardware support +target_link_libraries(ssd1309_stdout_spi + pico_stdlib + hardware_spi + hardware_dma + ) + +# create map/bin/hex file etc. +pico_add_extra_outputs(ssd1309_stdout_spi) + +# add url via pico_set_program_url +example_auto_set_url(ssd1309_stdout_spi) \ No newline at end of file diff --git a/spi/ssd1309_stdout_spi/README.adoc b/spi/ssd1309_stdout_spi/README.adoc new file mode 100644 index 000000000..88842953d --- /dev/null +++ b/spi/ssd1309_stdout_spi/README.adoc @@ -0,0 +1,59 @@ += Simple graphics and stdout text on an OLED display via SPI + +This example displays text and graphics on one of the widely-available small OLED panels based on the *SSD1309* controller. It should also work with compatible devices such as the *SSD1306* (not tested). + +These modules typically support either I2C or SPI. For this example you will need one configured for SPI. + +The code renders content onto a frame buffer that is transferred to the SPI port in the background, by a DMA channel under the control of a frame timer. A simple driver is used to copy _stdout_ to the frame buffer and some basic graphics functions are provided. + +The interface uses one of the Pico's onboard SPI peripherals and two extra GPIO pins as shown below. Note that in SPI mode the SSD1309 is a receive-only device. + +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. + + +== 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_stdout_spi.c:: The example code. +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 +|=== diff --git a/spi/ssd1309_stdout_spi/font.h b/spi/ssd1309_stdout_spi/font.h new file mode 100644 index 000000000..fe894b961 --- /dev/null +++ b/spi/ssd1309_stdout_spi/font.h @@ -0,0 +1,118 @@ +/** + * Copyright (c) 2026 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause +**/ + +// Vertical bitmaps for commonly-used characters. + +// Each bitmap is 8 pixels wide and 8 pixels high, with the least significant bit +// at the top. The bitmaps were translated from one of the original IBM BIOS fonts +// released over 45yrs ago (see https://int10h.org/oldschool-pc-fonts/) + +#include + +#define FONT_INDEX_UNDEF 0 +#define FONT_INDEX_START 1 +#define FONT_CODE_FIRST 32 +#define FONT_CODE_LAST 126 +#define FONT_BYTES_PER_CODE 8 + +static const uint8_t font[] = { + 0xff, 0xfd, 0xfe, 0xfe, 0xae, 0xf6, 0xf9, 0xff, // 00 (code -1) default + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // 01 (code 32) space + 0x00, 0x00, 0x0c, 0xbe, 0x0c, 0x00, 0x00, 0x00, // 02 (code 33) exclam + 0x00, 0x00, 0x0e, 0x00, 0x00, 0x0e, 0x00, 0x00, // 03 (code 34) quotedbl + 0x00, 0x28, 0xfe, 0x28, 0x28, 0xfe, 0x28, 0x00, // 04 (code 35) numbersign + 0x00, 0x48, 0x54, 0xd6, 0xd6, 0x54, 0x24, 0x00, // 05 (code 36) dollar + 0x00, 0x8c, 0x4c, 0x20, 0x10, 0xc8, 0xc4, 0x00, // 06 (code 37) percent + 0x60, 0x94, 0x8a, 0x9a, 0x64, 0x90, 0x90, 0x00, // 07 (code 38) ampersand + 0x00, 0x00, 0x08, 0x06, 0x00, 0x00, 0x00, 0x00, // 08 (code 39) quotesingle + 0x00, 0x38, 0x44, 0x82, 0x00, 0x00, 0x00, 0x00, // 09 (code 40) parenleft + 0x00, 0x00, 0x82, 0x44, 0x38, 0x00, 0x00, 0x00, // 10 (code 41) parenright + 0x10, 0x54, 0x38, 0x38, 0x38, 0x54, 0x10, 0x00, // 11 (code 42) asterisk + 0x00, 0x10, 0x10, 0x7c, 0x10, 0x10, 0x00, 0x00, // 12 (code 43) plus + 0x00, 0x00, 0x80, 0x60, 0x00, 0x00, 0x00, 0x00, // 13 (code 44) comma + 0x00, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, // 14 (code 45) hyphen + 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, // 15 (code 46) period + 0x00, 0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x00, // 16 (code 47) slash + 0x00, 0x7c, 0xc2, 0xa2, 0x92, 0x8a, 0x7c, 0x00, // 17 (code 48) zero + 0x00, 0x88, 0x84, 0xfe, 0x80, 0x80, 0x00, 0x00, // 18 (code 49) one + 0x00, 0xc4, 0xa2, 0xa2, 0x92, 0x92, 0xcc, 0x00, // 19 (code 50) two + 0x00, 0x44, 0x82, 0x92, 0x92, 0x92, 0x6c, 0x00, // 20 (code 51) three + 0x20, 0x30, 0x28, 0xa4, 0xfe, 0xa0, 0x20, 0x00, // 21 (code 52) four + 0x00, 0x4e, 0x8a, 0x8a, 0x8a, 0x8a, 0x72, 0x00, // 22 (code 53) five + 0x00, 0x78, 0x94, 0x92, 0x92, 0x92, 0x60, 0x00, // 23 (code 54) six + 0x00, 0x06, 0x02, 0xe2, 0x12, 0x0a, 0x06, 0x00, // 24 (code 55) seven + 0x00, 0x6c, 0x92, 0x92, 0x92, 0x92, 0x6c, 0x00, // 25 (code 56) eight + 0x00, 0x0c, 0x92, 0x92, 0x92, 0x52, 0x3c, 0x00, // 26 (code 57) nine + 0x00, 0x00, 0x00, 0xcc, 0x00, 0x00, 0x00, 0x00, // 27 (code 58) colon + 0x00, 0x00, 0x80, 0x66, 0x00, 0x00, 0x00, 0x00, // 28 (code 59) semicolon + 0x00, 0x10, 0x28, 0x44, 0x82, 0x00, 0x00, 0x00, // 29 (code 60) less + 0x00, 0x48, 0x48, 0x48, 0x48, 0x48, 0x48, 0x00, // 30 (code 61) equal + 0x00, 0x00, 0x00, 0x82, 0x44, 0x28, 0x10, 0x00, // 31 (code 62) greater + 0x00, 0x04, 0x02, 0x02, 0xa2, 0x12, 0x0c, 0x00, // 32 (code 63) question + 0x00, 0x7c, 0x82, 0xba, 0xaa, 0xaa, 0x3c, 0x00, // 33 (code 64) at + 0x00, 0xf8, 0x24, 0x22, 0x22, 0x24, 0xf8, 0x00, // 34 (code 65) A + 0x00, 0x82, 0xfe, 0x92, 0x92, 0x92, 0x6c, 0x00, // 35 (code 66) B + 0x00, 0x38, 0x44, 0x82, 0x82, 0x82, 0x44, 0x00, // 36 (code 67) C + 0x00, 0x82, 0xfe, 0x82, 0x82, 0x44, 0x38, 0x00, // 37 (code 68) D + 0x00, 0x82, 0xfe, 0x92, 0xba, 0x82, 0xc6, 0x00, // 38 (code 69) E + 0x00, 0x82, 0xfe, 0x92, 0x3a, 0x02, 0x06, 0x00, // 39 (code 70) F + 0x00, 0x38, 0x44, 0x82, 0xa2, 0xa2, 0xe4, 0x00, // 40 (code 71) G + 0x00, 0xfe, 0x10, 0x10, 0x10, 0x10, 0xfe, 0x00, // 41 (code 72) H + 0x00, 0x00, 0x82, 0xfe, 0x82, 0x00, 0x00, 0x00, // 42 (code 73) I + 0x00, 0x60, 0x80, 0x80, 0x82, 0x7e, 0x02, 0x00, // 43 (code 74) J + 0x00, 0x82, 0xfe, 0x10, 0x28, 0x44, 0x82, 0x80, // 44 (code 75) K + 0x00, 0x82, 0xfe, 0x82, 0x80, 0x80, 0xc0, 0x00, // 45 (code 76) L + 0x00, 0xfe, 0x02, 0x04, 0x08, 0x04, 0x02, 0xfe, // 46 (code 77) M + 0x00, 0xfe, 0x02, 0x04, 0x08, 0x10, 0xfe, 0x00, // 47 (code 78) N + 0x00, 0x38, 0x44, 0x82, 0x82, 0x44, 0x38, 0x00, // 48 (code 79) O + 0x00, 0x82, 0xfe, 0x92, 0x12, 0x12, 0x0c, 0x00, // 49 (code 80) P + 0x00, 0x3c, 0x42, 0x42, 0x62, 0x42, 0xbc, 0x80, // 50 (code 81) Q + 0x00, 0x82, 0xfe, 0x92, 0x32, 0x52, 0x8c, 0x00, // 51 (code 82) R + 0x00, 0x4c, 0x92, 0x92, 0x92, 0x92, 0x64, 0x00, // 52 (code 83) S + 0x00, 0x06, 0x02, 0x82, 0xfe, 0x82, 0x02, 0x06, // 53 (code 84) T + 0x00, 0x7e, 0x80, 0x80, 0x80, 0x80, 0x7e, 0x00, // 54 (code 85) U + 0x00, 0x1e, 0x20, 0x40, 0x80, 0x40, 0x20, 0x1e, // 55 (code 86) V + 0x00, 0x7e, 0x80, 0x80, 0x70, 0x80, 0x80, 0x7e, // 56 (code 87) W + 0x00, 0x82, 0x44, 0x28, 0x10, 0x28, 0x44, 0x82, // 57 (code 88) X + 0x00, 0x02, 0x04, 0x88, 0xf0, 0x88, 0x04, 0x02, // 58 (code 89) Y + 0x00, 0x86, 0xc2, 0xa2, 0x92, 0x8a, 0x86, 0xc2, // 59 (code 90) Z + 0x00, 0xfe, 0x82, 0x82, 0x82, 0x00, 0x00, 0x00, // 60 (code 91) bracketleft + 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x00, // 61 (code 92) backslash + 0x00, 0x82, 0x82, 0x82, 0xfe, 0x00, 0x00, 0x00, // 62 (code 93) bracketright + 0x10, 0x08, 0x04, 0x02, 0x04, 0x08, 0x10, 0x00, // 63 (code 94) asciicircum + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, // 64 (code 95) underscore + 0x00, 0x00, 0x00, 0x06, 0x08, 0x00, 0x00, 0x00, // 65 (code 96) grave + 0x00, 0x40, 0xa8, 0xa8, 0xa8, 0xa8, 0xf0, 0x80, // 66 (code 97) a + 0x00, 0x02, 0xfe, 0x60, 0x90, 0x90, 0x90, 0x60, // 67 (code 98) b + 0x00, 0x70, 0x88, 0x88, 0x88, 0x88, 0x50, 0x00, // 68 (code 99) c + 0x00, 0x60, 0x90, 0x90, 0x90, 0x62, 0xfe, 0x80, // 69 (code 100) d + 0x00, 0x70, 0xa8, 0xa8, 0xa8, 0xa8, 0x30, 0x00, // 70 (code 101) e + 0x00, 0x00, 0x90, 0xfc, 0x92, 0x02, 0x04, 0x00, // 71 (code 102) f + 0x00, 0x98, 0xa4, 0xa4, 0xa4, 0xa4, 0x78, 0x04, // 72 (code 103) g + 0x00, 0x82, 0xfe, 0x10, 0x08, 0x08, 0xf0, 0x00, // 73 (code 104) h + 0x00, 0x00, 0x88, 0xfa, 0x80, 0x00, 0x00, 0x00, // 74 (code 105) i + 0x00, 0x60, 0x80, 0x80, 0x80, 0x84, 0x7d, 0x00, // 75 (code 106) j + 0x00, 0x02, 0xfe, 0x20, 0x50, 0x88, 0x80, 0x00, // 76 (code 107) k + 0x00, 0x00, 0x82, 0xfe, 0x80, 0x00, 0x00, 0x00, // 77 (code 108) l + 0x00, 0xf8, 0x08, 0x08, 0xf0, 0x08, 0x08, 0xf0, // 78 (code 109) m + 0x00, 0xf8, 0x10, 0x08, 0x08, 0x08, 0xf0, 0x00, // 79 (code 110) n + 0x00, 0x70, 0x88, 0x88, 0x88, 0x88, 0x70, 0x00, // 80 (code 111) o + 0x00, 0x84, 0xfc, 0x98, 0x24, 0x24, 0x18, 0x00, // 81 (code 112) p + 0x00, 0x18, 0x24, 0x24, 0x98, 0xfc, 0x84, 0x00, // 82 (code 113) q + 0x00, 0x88, 0xf8, 0x90, 0x08, 0x08, 0x30, 0x00, // 83 (code 114) r + 0x00, 0x90, 0xa8, 0xa8, 0xa8, 0xa8, 0x48, 0x00, // 84 (code 115) s + 0x00, 0x08, 0x08, 0x7e, 0x88, 0x88, 0x40, 0x00, // 85 (code 116) t + 0x00, 0x78, 0x80, 0x80, 0x80, 0x40, 0xf8, 0x00, // 86 (code 117) u + 0x00, 0x18, 0x20, 0x40, 0x80, 0x40, 0x20, 0x18, // 87 (code 118) v + 0x00, 0x78, 0x80, 0x80, 0x70, 0x80, 0x80, 0x78, // 88 (code 119) w + 0x00, 0x88, 0x50, 0x20, 0x50, 0x88, 0x00, 0x00, // 89 (code 120) x + 0x00, 0x9c, 0xa0, 0xa0, 0xa0, 0xa0, 0x7c, 0x00, // 90 (code 121) y + 0x00, 0x88, 0xc8, 0xa8, 0x98, 0x88, 0x00, 0x00, // 91 (code 122) z + 0x00, 0x10, 0x10, 0x6c, 0x82, 0x82, 0x00, 0x00, // 92 (code 123) braceleft + 0x00, 0x00, 0x00, 0xee, 0x00, 0x00, 0x00, 0x00, // 93 (code 124) bar + 0x00, 0x00, 0x82, 0x82, 0x6c, 0x10, 0x10, 0x00, // 94 (code 125) braceright + 0x00, 0x04, 0x02, 0x02, 0x04, 0x04, 0x02, 0x00 // 95 (code 126) asciitilde +}; \ No newline at end of file diff --git a/spi/ssd1309_stdout_spi/ssd1309_stdout_spi.c b/spi/ssd1309_stdout_spi/ssd1309_stdout_spi.c new file mode 100644 index 000000000..5c3de42aa --- /dev/null +++ b/spi/ssd1309_stdout_spi/ssd1309_stdout_spi.c @@ -0,0 +1,235 @@ +/** + * Copyright (c) 2026 mjcross + * + * SPDX-License-Identifier: BSD-3-Clause +**/ + +// Copy stdout and/or simple graphics to an OLED display panel based on the ssd1309 +// (or compatible) controller, using a frame buffer transferred by DMA over an SPI interface. +// +// For details of the display controller and its commands and addressing modes refer to the +// manufacturer's datasheet at https://www.hpinfotech.ro/SSD1309.pdf + +#include +#include +#include +#include "pico/stdio/driver.h" +#include "pico/stdlib.h" +#include "hardware/dma.h" +#include "hardware/spi.h" +#include "font.h" + +// dimensions of the display panel +#define NUM_X_PIXELS 128 +#define NUM_Y_PIXELS 64 +#define PIXELS_PER_BYTE 8 +#define TABSTOPS 4 + +// how often we want to refresh the display from the frame buffer +#define FRAME_PERIOD_MS 20 // 50 Hz + +// clock rate for the SPI interface +// the ssd1309 is specified up to 10 Mbit/sec +#define DISPLAY_SPI_BITRATE 10 * 1000 * 1000 + +// pins to use for the SPI interface +// we will use the spi0 peripheral and the following GPIO pins (see the +// GPIO function select table in the 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) + +// modes for the ssd1309 data/command pin (see the datasheet) +#define DC_COMMAND_MODE 0 +#define DC_DATA_MODE 1 + +// global variables +uint8_t frame_buffer[ NUM_X_PIXELS * NUM_Y_PIXELS / PIXELS_PER_BYTE ]; +int dma_ch_transfer_fb; +volatile bool display_needs_refresh = false; +volatile uint fb_cursor_index = 0; + + +// configure a dma channel to send the frame buffer over SPI +void dma_init() { + dma_ch_transfer_fb = dma_claim_unused_channel(true); + dma_channel_config_t c = dma_channel_get_default_config(dma_ch_transfer_fb); + channel_config_set_transfer_data_size(&c, DMA_SIZE_8); + channel_config_set_dreq(&c, spi_get_dreq(SPI_DEVICE, true)); + dma_channel_configure( + dma_ch_transfer_fb, + &c, // the channel_config above + &spi_get_hw(SPI_DEVICE)->dr, // write address (doesn't increment) + frame_buffer, // initial read address + dma_encode_transfer_count(count_of(frame_buffer)), + false // don't trigger yet + ); +} + +// initialise the SPI interface +void interface_init() { + // configure the SPI controller for 8-bit transfers and Motorola SPI mode 0 + spi_init(SPI_DEVICE, DISPLAY_SPI_BITRATE); + spi_set_format(SPI_DEVICE, 8, SPI_CPOL_0, SPI_CPHA_0, SPI_MSB_FIRST); + + // configure 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_DC); + gpio_set_dir(PIN_DC, GPIO_OUT); + gpio_init(PIN_R); + gpio_set_dir(PIN_R, GPIO_OUT); +} + +// reset and initialise the display +void display_reset() { + // send active-low reset pulse + gpio_put(PIN_R, 0); + sleep_ms(1); + gpio_put(PIN_R, 1); + sleep_ms(1); + + // wake up the display and set horizontal addressing mode + gpio_put(PIN_DC, DC_COMMAND_MODE); + uint8_t cmd_list[] = { 0xaf, 0x20, 0x00 }; + spi_write_blocking(SPI_DEVICE, cmd_list, sizeof(cmd_list)); + gpio_put(PIN_DC, DC_DATA_MODE); + + // clear the frame buffer and flag it to be transferred + memset(frame_buffer, 0x00, sizeof(frame_buffer)); + fb_cursor_index = 0; + display_needs_refresh = true; +} + + +// a simple stdout driver for the display +void fb_out_chars(const char *buf, int len) { + uint font_index; + while (len) { + char code = *buf; + buf += 1; + len -= 1; + while (fb_cursor_index >= sizeof(frame_buffer)) { + // scroll frame buffer (on RP2350 you could use a decrementing dma transfer but it's probably overkill) + memmove(frame_buffer, frame_buffer + NUM_X_PIXELS, sizeof(frame_buffer) - NUM_X_PIXELS); + fb_cursor_index -= NUM_X_PIXELS; + memset(&frame_buffer[sizeof(frame_buffer) - NUM_X_PIXELS - 1], 0x00, NUM_X_PIXELS); + } + // handle control codes + if (code == '\n') { + fb_cursor_index = (fb_cursor_index / NUM_X_PIXELS + 1) * NUM_X_PIXELS; + } else if (code == '\t') { + fb_cursor_index = (fb_cursor_index / (TABSTOPS * FONT_BYTES_PER_CODE) + 1) * TABSTOPS * FONT_BYTES_PER_CODE; + } else { + // handle alphanumeric codes + if (code < FONT_CODE_FIRST || code > FONT_CODE_LAST ) { + font_index = FONT_INDEX_UNDEF; + } else { + font_index = FONT_BYTES_PER_CODE * (FONT_INDEX_START + code - FONT_CODE_FIRST); + } + // copy bitmap to frame buffer and advance cursor + memcpy(&frame_buffer[fb_cursor_index], &font[font_index], FONT_BYTES_PER_CODE); + fb_cursor_index += FONT_BYTES_PER_CODE; + } + } + display_needs_refresh = true; +} + + +// some simple graphics funtions (for the display memory layout see the datasheet) +void set_pixel(uint x, uint y) { + if (x < NUM_X_PIXELS && y < NUM_Y_PIXELS) { + frame_buffer[x + (y / 8) * NUM_X_PIXELS] |= (1 << (y % 8)); + display_needs_refresh = true; + } +} + +void clear_pixel(uint x, uint y) { + if (x < NUM_X_PIXELS && y < NUM_Y_PIXELS) { + frame_buffer[x + (y / 8) * NUM_X_PIXELS] &= ~(1 << (y % 8)); + display_needs_refresh = true; + } +} + +void draw_line(int x0, int y0, int x1, int y1) { + int dx = abs (x1 - x0), sx = x0 < x1 ? 1 : -1; + int dy = -abs (y1 - y0), sy = y0 < y1 ? 1 : -1; + int err = dx + dy, e2; + while(true) { + set_pixel (x0, y0); + if (x0 == x1 && y0 == y1) break; + e2 = 2 * err; + if (e2 >= dy) { err += dy; x0 += sx; } + if (e2 <= dx) { err += dx; y0 += sy; } + } + display_needs_refresh = true; +} + + +// set the text output position +// rows go from 0 at the top to NUM_Y_PIXELS/8 - 1 and columns go from 0 on the left to NUM_X_PIXELS/8 - 1 +void set_cursor_pos(uint text_row, uint text_col) { + if (text_row < NUM_Y_PIXELS / PIXELS_PER_BYTE && text_col < NUM_X_PIXELS / FONT_BYTES_PER_CODE) { + fb_cursor_index = text_row * NUM_X_PIXELS + text_col * FONT_BYTES_PER_CODE; + } +} + +// the callback function for our frame-rate timer +bool frame_refresh_callback(__unused struct repeating_timer *t) { + if (display_needs_refresh) { + dma_channel_set_read_addr(dma_ch_transfer_fb, frame_buffer, true); // reset and trigger the dma channel + display_needs_refresh = false; + } + return true; // reschedule the timer +} + + +int main(){ + stdio_init_all(); + + // initialise everything + dma_init(); + interface_init(); + display_reset(); + + // start refreshing the display + struct repeating_timer timer; + add_repeating_timer_ms(FRAME_PERIOD_MS, frame_refresh_callback, NULL, &timer); + + // install our simple driver to copy stdout to the frame buffer + stdio_driver_t fb_stdio_driver = { fb_out_chars }; + stdio_set_driver_enabled(&fb_stdio_driver, true); + + // display some text + set_cursor_pos(0, 2); + printf("Hello, World\n"); + + // show a moving 'snake' + int head_x = NUM_Y_PIXELS - 1, head_y = NUM_Y_PIXELS - 1, head_dx = 1, head_dy = 1; + int tail_x = FONT_BYTES_PER_CODE + 1, tail_y = FONT_BYTES_PER_CODE + 1, tail_dx = 1, tail_dy = 1; + while(true) { + set_pixel(head_x, head_y); + clear_pixel(tail_x, tail_y); + if (head_x + head_dx < 0 || head_x + head_dx >= NUM_X_PIXELS) { + head_dx = -head_dx; + } + head_x += head_dx; + if (head_y + head_dy <= FONT_BYTES_PER_CODE || head_y + head_dy >= NUM_Y_PIXELS) { + head_dy = -head_dy; + } + head_y += head_dy; + if (tail_x + tail_dx < 0 || tail_x + tail_dx >= NUM_X_PIXELS) { + tail_dx = -tail_dx; + } + tail_x += tail_dx; + if (tail_y + tail_dy <= FONT_BYTES_PER_CODE || tail_y + tail_dy >= NUM_Y_PIXELS) { + tail_dy = -tail_dy; + } + tail_y += tail_dy; + sleep_ms(5); + } +} diff --git a/spi/ssd1309_stdout_spi/wiring_diagram.fzz b/spi/ssd1309_stdout_spi/wiring_diagram.fzz new file mode 100644 index 000000000..487d728f7 Binary files /dev/null and b/spi/ssd1309_stdout_spi/wiring_diagram.fzz differ diff --git a/spi/ssd1309_stdout_spi/wiring_diagram.png b/spi/ssd1309_stdout_spi/wiring_diagram.png new file mode 100644 index 000000000..4a9a85fdc Binary files /dev/null and b/spi/ssd1309_stdout_spi/wiring_diagram.png differ