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..c3d4fef --- /dev/null +++ b/boards/rp-pico/examples/pico_i2c_oled_display_ssd1306.rs @@ -0,0 +1,228 @@ +//! # Raspberry Pi Pico (monochome) 128x64 OLED Display with SSD1306 Driver Example +//! +//! This example assumes you got an 128x64 OLED Display with an SSD1306 driver +//! connected to your Raspberry Pi Pico. The +3.3V voltage source of the +//! Raspberry Pi Pico will be used, and the output pins 21 and 22 of the board +//! (on the lower right). +//! +//! It will demonstrate how to get an I2C device and use it with the ssd1306 crate. +//! Additionally you can also see how to format a number into a string using +//! [core::fmt]. +//! +//! The following diagram will show how things should be connected. +//! These displays usually can take 3.3V up to 5V. +//! +//! ```text +//! VCC SCL +//! /------------\ /----------\ +//! | GND \ / SDA | +//! _|USB|_ | /-----\ | | /--------+--\ +//! |1 R 40| | / __|__|__|__|___ | | +//! |2 P 39| | / | ____________ | | | +//! |3 38|- GND --+-/ | |Hello worl| | | | +//! |4 P 37| | | |Hello Rust| | | | +//! |5 I 36|-+3.3V -/ | |counter: 1| | | | +//! |6 C | | | | | | | +//! |7 O | | """""""""""" | | | +//! | | """"""""""""""" | | +//! | | (SSD1306 128x64 OLED Display) | | +//! ......... / / +//! | | / / +//! | 22|-GP17 I2C0 SCL---------------------/ / +//! |20 21|-GP16 I2C0 SDA-----------------------/ +//! """"""" +//! Symbols: +//! - (+) crossing lines, not connected +//! - (o) connected lines +//! ``` +//! +//! See the `Cargo.toml` file for Copyright and licence details. + +#![no_std] +#![no_main] + +// For string formatting. +use core::fmt::Write; + +// 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::*, 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 driver, 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 = ssd1306::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: + write!(&mut buf, "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 +/// limited arbitrarily to 64 bytes. +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 rest_len = self.buf.len() - self.ptr; + let len = if rest_len < s.len() { + rest_len + } else { + s.len() + }; + self.buf[self.ptr..(self.ptr + len)].copy_from_slice(&s.as_bytes()[0..len]); + self.ptr += len; + Ok(()) + } +} + +// End of file