mirror of
https://github.com/italicsjenga/rp-hal-boards.git
synced 2024-12-24 13:11:30 +11:00
229 lines
6.8 KiB
Rust
229 lines
6.8 KiB
Rust
//! # 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 license 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::<hal::gpio::FunctionI2C>();
|
|
let scl_pin = pins.gpio17.into_mode::<hal::gpio::FunctionI2C>();
|
|
|
|
// 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
|