Rust + RaspberryPi Pico + OLEDディスプレイで描画してみる

ラズパイ
この記事は約12分で読めます。

今回はディスプレイ一つ目

OLEDディスプレイと液晶

AliexpressやAmazonで売っている液晶モジュールです

左と中央がI2Cで接続できる単色OLEDモジュールと、右がSPI接続の液晶とSDカードスロットです

OLEDモジュールは大きさが異なりますがどちらも同じOLEDモジュール+ICのSSD1306とIFを固定する基板で構成されています(今回はI2CモデルですがSPIモデルもある様子)

今回はこのOLEDモジュールをRustで動かすサンプルコードを書いてみます

I2Cスキャン

CやMicroPythonでの資料は多いのですがRust版のI2Cスキャンが無かったのでAIを叩きながら作りました

raspberrypi-pico-i2c-scan-rs/src/main.rs at main · zinntikumugai/raspberrypi-pico-i2c-scan-rs
Contribute to zinntikumugai/raspberrypi-pico-i2c-scan-rs development by creating an account on GitHub.

プローブが繋がっているのが前提ですが他に接続せずスキャンできます。

(このソース作成時は4つのデバイスを繋いでますが)この3CがOLEDモジュールです

ちなみに中央のOLEDは裏の抵抗を置き換える事でアドレスを変えることができますが、左のOLEDは変更できないので注意が必要です

接続

先ほどの説明の通りI2Cのアドレスが被ってしまっているので、系等を分けています。

RaspberryPiPicoは2系統i2CがあるのでGPIO16,17とGPIO18,19を使用しています。

ソースコード

#![no_std]
#![no_main]

use defmt::*;
use defmt_rtt as _;
use embedded_hal::delay::DelayNs;
use panic_probe as _;
use rp2040_hal::{self as hal, fugit::RateExtU32};

use hal::pac;
use embedded_graphics::{
    mono_font::{ascii::FONT_6X10, MonoTextStyleBuilder},
    pixelcolor::BinaryColor,
    prelude::*,
    text::{Baseline, Text},
    primitives::{PrimitiveStyleBuilder, Rectangle, Triangle, Circle},
};
use ssd1306::{prelude::*, I2CDisplayInterface, Ssd1306};


#[link_section = ".boot2"]
#[used]
pub static BOOT2: [u8; 256] = rp2040_boot2::BOOT_LOADER_GENERIC_03H;

const XTAL_FREQ_HZ: u32 = 12_000_000u32;



#[rp2040_hal::entry]
fn main() -> ! {
    info!("Program start!");
    let mut pac = pac::Peripherals::take().unwrap();

    let mut watchdog = hal::Watchdog::new(pac.WATCHDOG);

    let clocks = hal::clocks::init_clocks_and_plls(
        XTAL_FREQ_HZ,
        pac.XOSC,
        pac.CLOCKS,
        pac.PLL_SYS,
        pac.PLL_USB,
        &mut pac.RESETS,
        &mut watchdog,
    )
    .ok()
    .unwrap();

    let mut timer = rp2040_hal::Timer::new(pac.TIMER, &mut pac.RESETS, &clocks);

    let sio = hal::Sio::new(pac.SIO);

    let pins = hal::gpio::Pins::new(
        pac.IO_BANK0,
        pac.PADS_BANK0,
        sio.gpio_bank0,
        &mut pac.RESETS,
    );


    let sda_pin_a = pins.gpio16.into_pull_up_input().into_function::<hal::gpio::FunctionI2C>();
    let scl_pin_a = pins.gpio17.into_pull_up_input().into_function::<hal::gpio::FunctionI2C>();

    let sda_pin_b = pins.gpio18.into_pull_up_input().into_function::<hal::gpio::FunctionI2C>();
    let scl_pin_b = pins.gpio19.into_pull_up_input().into_function::<hal::gpio::FunctionI2C>();
    
    // I2C0 の初期化(400kHz)
    let i2c0 = hal::I2C::i2c0(
        pac.I2C0,
        sda_pin_a,
        scl_pin_a,
        400u32.kHz(),
        &mut pac.RESETS,
        &clocks.system_clock,
    );

    // 0x3C アドレスのデバイスに接続
    let interface_a = I2CDisplayInterface::new_custom_address(i2c0, 0x3Cu8);
    let mut display_a = Ssd1306::new(
        interface_a,
        DisplaySize128x64,
        DisplayRotation::Rotate0,
    ).into_buffered_graphics_mode();
    display_a.init().unwrap();
    
    // I2C1の初期化
    let i2c1 = hal::I2C::i2c1(
        pac.I2C1,
        sda_pin_b,
        scl_pin_b,
        400u32.kHz(),
        &mut pac.RESETS,
        &clocks.system_clock,
    );
    let interface_b = I2CDisplayInterface::new(i2c1);
    let mut display_b = Ssd1306::new(
        interface_b,
        DisplaySize128x32,
        DisplayRotation::Rotate0,
    ).into_buffered_graphics_mode();
    display_b.init().unwrap();
    
    let yoffset = 8;

    let style = PrimitiveStyleBuilder::new()
        .stroke_width(1)
        .stroke_color(BinaryColor::On)
        .build();
    
    let text_style = MonoTextStyleBuilder::new()
        .font(&FONT_6X10)
        .text_color(BinaryColor::On)
        .build();


    // display A
    Text::with_baseline("Hello world!", Point::new(0, 32), text_style, Baseline::Top)
        .draw(&mut display_a)
        .unwrap();

    Text::with_baseline("Hello Rust!", Point::new(0, 40), text_style, Baseline::Top)
        .draw(&mut display_a)
        .unwrap();

    Text::with_baseline("zinntikumugai.com", Point::new(0, 48), text_style, Baseline::Top)
        .draw(&mut display_a)
        .unwrap();
    
    // screen outline
    // default display size is 128x64 if you don't pass a _DisplaySize_
    // enum to the _Builder_ struct
    Rectangle::new(Point::new(0, 0), Size::new(127, 31))
        .into_styled(style)
        .draw(&mut display_a)
        .unwrap();

    // triangle
    Triangle::new(
        Point::new(16, 16 + yoffset),
        Point::new(16 + 16, 16 + yoffset),
        Point::new(16 + 8, yoffset),
    )
    .into_styled(style)
    .draw(&mut display_a)
    .unwrap();

    // square
    Rectangle::new(Point::new(52, yoffset), Size::new_equal(16))
        .into_styled(style)
        .draw(&mut display_a)
        .unwrap();

    // circle
    Circle::new(Point::new(88, yoffset), 16)
        .into_styled(style)
        .draw(&mut display_a)
        .unwrap();

    // 

    display_a.flush().unwrap();

    // Display b
    Text::with_baseline("Hello world!", Point::new(0, 32), text_style, Baseline::Top)
        .draw(&mut display_b)
        .unwrap();

    Text::with_baseline("Hello Rust!", Point::new(0, 40), text_style, Baseline::Top)
        .draw(&mut display_b)
        .unwrap();
    
    // screen outline
    // default display size is 128x64 if you don't pass a _DisplaySize_
    // enum to the _Builder_ struct
    Rectangle::new(Point::new(0, 0), Size::new(127, 31))
        .into_styled(style)
        .draw(&mut display_b)
        .unwrap();

    // triangle
    Triangle::new(
        Point::new(16, 16 + yoffset),
        Point::new(16 + 16, 16 + yoffset),
        Point::new(16 + 8, yoffset),
    )
    .into_styled(style)
    .draw(&mut display_b)
    .unwrap();

    // square
    Rectangle::new(Point::new(52, yoffset), Size::new_equal(16))
        .into_styled(style)
        .draw(&mut display_b)
        .unwrap();

    // circle
    Circle::new(Point::new(88, yoffset), 16)
        .into_styled(style)
        .draw(&mut display_b)
        .unwrap();

    // 

    display_b.flush().unwrap();


    timer.delay_ms(5000);

    loop {
        for i in 1u8..254 {

            display_b.set_brightness(Brightness::custom(1u8, i)).unwrap();
            timer.delay_ms(100);
        }
        timer.delay_ms(2000);

    }
}

二つのOLEDディスプレイそれぞれに図形とテキストを描画しています。

右のOLEDはフルサイズのため図形とテキストを描画できますが、左のOLEDはハーフサイズのため切り取られて描画されます。

ディスプレイの設定でDisplaySize128x32とありますが、DisplaySize128x64とすると液晶のサイズを128×64として半分のビット数で描画するため文字が潰れます。

左のOLED、大きさに対して画面の縦方向が若干見かけに対して内側にあるようで、文字を描画できそうな隙間がありますができませんでした。

このままではつまらないので一応コントラスト比の変更をしてみました。

いつも通りGitHubはこちら

GitHub - zinntikumugai/raspberrypi-pico-i2c-oled-display-rs
Contribute to zinntikumugai/raspberrypi-pico-i2c-oled-display-rs development by creating an account on GitHub.

参考

OLED 0.91inch 128x32dot datasheetを読む読む (SSD1306)
OLED displayをarduinoに取り付けたい. 中華通販から買った@¥150ぐらいのがこれ.128x32dotらしい.IFはI2Cである. 繋げば動くArduinoであるから、どこかのスケッチをDLすれば動くのだろう.しかし疑問は...
単色OLEDモジュールを使う(SSD1306 / I2C)
RP2040(Raspberry Pi pico)のI2C接続、ESP32(BananaPi PicoW)のI2C接続での表示をします。Raspberry Pi PicoでのDual Monitor と I2Cを2系統使用した表示をします。
rp-hal-boards/boards/rp-pico/examples/pico_i2c_oled_display_ssd1306.rs at main · rp-rs/rp-hal-boards
Board Support Packages for RP2040 based PCBs. Contribute to rp-rs/rp-hal-boards development by creating an account on Gi...

コメント