From 2f418bbf8fef99fbb7f6af1ac29788b72b2a2291 Mon Sep 17 00:00:00 2001 From: Weird Constructor Date: Wed, 12 Jan 2022 05:07:33 +0100 Subject: [PATCH] Add an example for using the SSD1306 I2C display driver crate with the Raspberry Pi Pico --- boards/rp-pico/Cargo.toml | 2 + .../examples/pico_i2c_oled_display_ssd1306.rs | 185 ++++++++++++++++++ 2 files changed, 187 insertions(+) create mode 100644 boards/rp-pico/examples/pico_i2c_oled_display_ssd1306.rs diff --git a/boards/rp-pico/Cargo.toml b/boards/rp-pico/Cargo.toml index 4e47d6c..f7b048c 100644 --- a/boards/rp-pico/Cargo.toml +++ b/boards/rp-pico/Cargo.toml @@ -31,6 +31,8 @@ heapless = "0.7.9" embedded-sdmmc = { git = "https://github.com/rust-embedded-community/embedded-sdmmc-rs.git" } smart-leds = "0.3.0" ws2812-pio = { git = "https://github.com/ithinuel/ws2812-pio-rs", rev = "4f0d81e594ea9934f9c4c38ed9824ad0cce4ebb5" } +ssd1306 = "0.7.0" +embedded-graphics = "0.7.1" defmt = "0.2.0" defmt-rtt = "0.2.0" diff --git a/boards/rp-pico/examples/pico_i2c_oled_display_ssd1306.rs b/boards/rp-pico/examples/pico_i2c_oled_display_ssd1306.rs new file mode 100644 index 0000000..00fc932 --- /dev/null +++ b/boards/rp-pico/examples/pico_i2c_oled_display_ssd1306.rs @@ -0,0 +1,185 @@ +//! # Pico (monochome) OLED Display with SSD1306 Driver Example +//! +//! See the `Cargo.toml` file for Copyright and licence details. + +#![no_std] +#![no_main] + +// The macro for our start-up function +use cortex_m_rt::entry; + +// Time handling traits: +use embedded_time::duration::*; +use embedded_time::rate::Extensions; + +// CountDown timer for the counter on the display: +use embedded_hal::timer::CountDown; + +// Ensure we halt the program on panic (if we don't mention this crate it won't +// be linked) +use panic_halt as _; + +// A shorter alias for the Peripheral Access Crate, which provides low-level +// register access +use rp_pico::hal::pac; + +// A shorter alias for the Hardware Abstraction Layer, which provides +// higher-level drivers. +use rp_pico::hal; + +// For in the graphics drawing utilities like the font +// and the drawing routines: +use embedded_graphics::{ + mono_font::{ascii::FONT_9X18_BOLD, MonoTextStyleBuilder}, + pixelcolor::BinaryColor, + prelude::*, + text::{Baseline, Text}, +}; + +// The display driver: +use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306}; + +/// Entry point to our bare-metal application. +/// +/// The `#[entry]` macro ensures the Cortex-M start-up code calls this function +/// as soon as all global variables are initialised. +/// +/// The function configures the RP2040 peripherals, +/// gets a handle on the I2C peripheral, +/// initializes the SSD1306 driver, initializes the text builder +/// and then draws some text on the display. +#[entry] +fn main() -> ! { + // Grab our singleton objects + let mut pac = pac::Peripherals::take().unwrap(); + + // Set up the watchdog driver - needed by the clock setup code + let mut watchdog = hal::Watchdog::new(pac.WATCHDOG); + + // Configure the clocks + // + // The default is to generate a 125 MHz system clock + let clocks = + hal::clocks::init_clocks_and_plls( + rp_pico::XOSC_CRYSTAL_FREQ, + pac.XOSC, + pac.CLOCKS, + pac.PLL_SYS, + pac.PLL_USB, + &mut pac.RESETS, + &mut watchdog) + .ok() + .unwrap(); + + // The single-cycle I/O block controls our GPIO pins + let sio = hal::Sio::new(pac.SIO); + + // Set the pins up according to their function on this particular board + let pins = rp_pico::Pins::new( + pac.IO_BANK0, + pac.PADS_BANK0, + sio.gpio_bank0, + &mut pac.RESETS, + ); + + // Configure two pins as being I²C, not GPIO + let sda_pin = pins.gpio16.into_mode::(); + let scl_pin = pins.gpio17.into_mode::(); + + // Create the I²C drive, using the two pre-configured pins. This will fail + // at compile time if the pins are in the wrong mode, or if this I²C + // peripheral isn't available on these pins! + let i2c = hal::I2C::i2c0( + pac.I2C0, + sda_pin, + scl_pin, + 400.kHz(), + &mut pac.RESETS, + clocks.peripheral_clock, + ); + + // Create the I²C display interface: + let interface = I2CDisplayInterface::new(i2c); + + // Create a driver instance and initialize: + let mut display = + Ssd1306::new(interface, DisplaySize128x64, DisplayRotation::Rotate0) + .into_buffered_graphics_mode(); + display.init().unwrap(); + + // Create a text style for drawing the font: + let text_style = MonoTextStyleBuilder::new() + .font(&FONT_9X18_BOLD) + .text_color(BinaryColor::On) + .build(); + + let timer = hal::Timer::new(pac.TIMER, &mut pac.RESETS); + let mut delay = timer.count_down(); + + let mut count = 0; + + let mut buf = FmtBuf::new(); + loop { + buf.reset(); + // Format some text into a static buffer: + core::fmt::write(&mut buf, format_args!("counter: {}", count)).unwrap(); + count += 1; + + // Empty the display: + display.clear(); + + // Draw 3 lines of text: + Text::with_baseline("Hello world!", Point::zero(), text_style, Baseline::Top) + .draw(&mut display) + .unwrap(); + + Text::with_baseline("Hello Rust!", Point::new(0, 16), text_style, Baseline::Top) + .draw(&mut display) + .unwrap(); + + Text::with_baseline(buf.as_str(), Point::new(0, 32), text_style, Baseline::Top) + .draw(&mut display) + .unwrap(); + + display.flush().unwrap(); + + // Wait a bit: + delay.start(500.milliseconds()); + let _ = nb::block!(delay.wait()); + } +} + +/// This is a very simple buffer to pre format a short line of text. +struct FmtBuf { + buf: [u8; 64], + ptr: usize, +} + +impl FmtBuf { + fn new() -> Self { + Self { + buf: [0; 64], + ptr: 0 + } + } + + fn reset(&mut self) { + self.ptr = 0; + } + + fn as_str(&self) -> &str { + core::str::from_utf8(&self.buf[0..self.ptr]).unwrap() + } +} + +impl core::fmt::Write for FmtBuf { + fn write_str(&mut self, s: &str) -> core::fmt::Result { + let len = s.len(); + self.buf[self.ptr..(self.ptr + len)].copy_from_slice(s.as_bytes()); + self.ptr += len; + Ok(()) + } +} + + +// End of file