esp32c3 运行 Rust(七)

308 阅读5分钟

esp32c3 运行 Rust(七)

上篇已经封装 SSD1306 相关操作指令,开始封装初始化操作。

环境

  • OS: Ubuntu 20.04.4 LTS

  • Rust: rustc 1.62.0-nightly

  • IDE: VsCode

  • ESP32C3: ESP-C3-13-Kit 开发板

    esp32c3.png

  • SSD1306: 0.96 寸 OLED 模块

    ssd1306

使用到的 GPIO

GPIOtitle
GPIO4SDA
GPIO5SCL

创建项目

使用上篇创建的项目工程。

  1. 创建 ssd1306.rs 文件

    定义 SSD1306 结构体

    pub struct SSD1306<Adapter, SIZE, FrameBuf> {
        adapter: Adapter,
        addr_mode: AddrMode,
        size: SIZE,
        frame: FrameBuf,
    }
    
  2. 定义创建实例 new 方法

    impl<Adapter, SIZE> SSD1306<Adapter, SIZE, FrameBuffer<SIZE>>
    where
        Adapter: I2CInterface,
        SIZE: DisplaySize,
    {
        pub fn new(adapter: Adapter, size: SIZE) -> Self {
            Self { 
                adapter,
                addr_mode: AddrMode::Horizontal,
                size,
                frame: FrameBuffer::new()
            }
        }
    }
    
  3. 定义 init 方法

    pub fn init(&mut self) -> SSD1306Result {
        self.clear();
        Command::DisplayOn(false).send(&mut self.adapter)?;
        Command::DisplayClockDivide(0x8, 0x0).send(&mut self.adapter)?;
        Command::Multiplex(SIZE::HEIGHT - 1).send(&mut self.adapter)?;
        Command::DisplayOffset(0).send(&mut self.adapter)?;
        Command::StartLine(0).send(&mut self.adapter)?;
    
        Command::ChangePump(true).send(&mut self.adapter)?;
        Command::AddressMode(self.addr_mode).send(&mut self.adapter)?;
    
        self.size.configure(&mut self.adapter)?;
        // 旋转
        Command::SegmentRemap(true).send(&mut self.adapter)?;
        Command::ComOutputScanDirection(true).send(&mut self.adapter)?;
        // 亮度
        self.set_brightness(Brightness::default())?;
    
        Command::VcomhDeselect(VcomhLevel::Auto).send(&mut self.adapter)?;
        Command::OnAll(false).send(&mut self.adapter)?;
        Command::Invert(false).send(&mut self.adapter)?;
        Command::ScrollEnable(false).send(&mut self.adapter)?;
        Command::DisplayOn(true).send(&mut self.adapter)?;
        Ok(())
    }
    
  4. 定义清屏 clear 方法

    /// 清屏
    pub fn clear(&mut self) {
        for b in self.frame.buffer.as_mut() {
            *b = 0;
        }
    
        let (w, h) = self.dimensions();
        self.frame.min_x = 0;
        self.frame.max_x = w - 1;
        self.frame.min_y = 0;
        self.frame.max_y = h - 1;
    
    }
    
  5. 定义亮度调节 set_brightness 方法

    /// 设置亮度
    pub fn set_brightness(&mut self, brightness: Brightness) -> SSD1306Result {
        debug_assert!(
            0 < brightness.precharge && brightness.precharge <= 15,
            "precharge 的取值范围为 1~15。"
        );
        Command::PreChargePeriod(1, brightness.precharge).send(&mut self.adapter)?;
        Command::Contrast(brightness.contrast).send(&mut self.adapter)
    }
    
  6. 定义获取屏幕尺寸 dimensions 方法

    /// 获取屏幕尺寸
    pub fn dimensions(&self) -> (u8, u8) {
        (SIZE::WIDTH, SIZE::HEIGHT)
    }
    
  7. 定义设置绘制区域 set_draw_area 方法

    /// 设置绘制区域
    pub fn set_draw_area(&mut self, start: (u8, u8), end: (u8, u8)) -> SSD1306Result {
        Command::ColumAddr(start.0, end.0.saturating_sub(1)).send(&mut self.adapter)?;
        
        if self.addr_mode != AddrMode::Page {
            Command::PageAddr(start.1.into(), (end.1.saturating_sub(1)).into()).send(&mut self.adapter)?;
        }
        
        Ok(())
    }
    

    修复报错

    error[E0277]: the trait bound `Page: From<u8>` is not satisfied
    --> src/ssd1306.rs:77:33
    |
    77 |       Command::PageAddr(start.1.into(), (end.1.saturating_sub(1)).into()).send(&mut self.adapter)?;
    |                                 ^^^^ the trait `From<u8>` is not implemented for `Page`
    |
    = note: required because of the requirements on the impl of `Into<Page>` for `u8`
    

    Page 实现 From trait

    impl From<u8> for Page {
        fn from(val: u8) -> Page {
            match val / 8 {
                0 => Self::Page0,
                1 => Self::Page1,
                2 => Self::Page2,
                3 => Self::Page3,
                4 => Self::Page4,
                5 => Self::Page5,
                6 => Self::Page6,
                7 => Self::Page7,
                _ => panic!("页地址溢出"),
            }
        }
    }
    
  8. 定义设置像素 set_pixel 方法

    /// 设置像素
    pub fn set_pixel(&mut self, x: u32, y: u32, value: bool) {
        let value = value as u8;
    
        let idx = ((y as usize) / 8 * SIZE::WIDTH as usize) + (x as usize);
        let bit = y % 8;
        if let Some(byte) = self.frame.buffer.as_mut().get_mut(idx) {
            self.frame.min_x = self.frame.min_x.min(x as u8);
            self.frame.max_x = self.frame.max_x.max(x as u8);
    
            self.frame.min_y = self.frame.min_y.min(x as u8);
            self.frame.max_y = self.frame.max_y.max(x as u8);
    
            *byte = *byte & !(1 << bit) | (value << bit)
        }
    }
    
  9. 定义刷新屏幕 flush 方法

    /// 将数据写入显示屏
    pub fn flush(&mut self) -> SSD1306Result {
        if self.frame.max_x < self.frame.min_x || self.frame.max_y < self.frame.min_y {
            return Ok(());
        }
        let (w, h) = self.dimensions();
        let display_min_x = self.frame.min_x;
        let display_min_y = self.frame.min_y;
        let display_max_x = (self.frame.max_x + 1).min(w);
        let display_max_y = (self.frame.max_y | 7).min(h);
    
        self.frame.min_x = 255;
        self.frame.max_x = 0;
        self.frame.min_y = 255;
        self.frame.max_y = 0;
    
        self.set_draw_area(
            (display_min_x + SIZE::OFFSETX, display_min_y + SIZE::OFFSETY),
            (display_max_x + SIZE::OFFSETX, display_max_y + SIZE::OFFSETY),
        )?;
    
        Self::flush_buffer_chunks(
            &mut self.adapter,
            self.frame.buffer.as_mut(),
            w as usize,
            (display_min_x, display_min_y),
            (display_max_x, display_max_y),
        )
    }
    
  10. 其他部分

    impl<Adapter, SIZE> SSD1306<Adapter, SIZE, FrameBuffer<SIZE>>
    where
        Adapter: I2CInterface,
        SIZE: DisplaySize,
    {
        /// 将数据写入显示屏
        fn flush_buffer_chunks(
            adapter: &mut Adapter,
            buffer: &[u8],
            width: usize,
            upper_left: (u8, u8),
            lower_right: (u8, u8),
        ) -> SSD1306Result {
            let num = ((lower_right.1 - upper_left.1) / 8) as usize + 1;
            let start = (upper_left.1 / 8) as usize;
            let lower = upper_left.0 as usize;
            let upper = lower_right.0 as usize;
    
            buffer
                .chunks(width)
                .skip(start)
                .take(num)
                .map(|s| &s[lower..upper])
                .try_for_each(|c| adapter.send_data(&c))
                .map_err(|_| SSD1306Error::BusWriteErr)
        }
    }
    
    pub trait Zerod {
        fn zerod() -> Self;
    }
    impl<const N: usize> Zerod for [u8; N] {
        fn zerod() -> Self {
            [0u8; N]
        }
    }
    
    pub trait DisplaySize {
        const WIDTH: u8;
        const HEIGHT: u8;
        const COLS: u8 = 128;
        const ROWS: u8 = 64;
        const OFFSETX: u8 = 0;
        const OFFSETY: u8 = 0;
        type Buffer: AsMut<[u8]> + Zerod;
    
        fn configure(&self, iface: &mut impl I2CInterface) -> SSD1306Result;
    }
    
    pub struct SSD1306Size;
    impl DisplaySize for SSD1306Size {
        const WIDTH: u8 = 128;
        const HEIGHT: u8 = 64;
    
        type Buffer = [u8; Self::WIDTH as usize * Self::HEIGHT as usize / 8];
    
        fn configure(&self, iface: &mut impl I2CInterface) -> SSD1306Result {
            Command::ComPinConfig(true, false)
                .send(iface)
                .map_err(|_| SSD1306Error::BusWriteErr)
        }
    }
    
    pub struct FrameBuffer<SIZE>
    where
        SIZE: DisplaySize,
    {
        buffer: SIZE::Buffer,
        min_x: u8,
        max_x: u8,
        min_y: u8,
        max_y: u8,
    }
    
    impl<SIZE> FrameBuffer<SIZE>
    where
        SIZE: DisplaySize,
    {
        pub fn new() -> Self {
            Self {
                buffer: Zerod::zerod(),
                min_x: 255,
                max_x: 0,
                min_y: 255,
                max_y: 0,
            }
        }
    }
    
    /// 亮度
    pub struct Brightness {
        pub precharge: u8,
        pub contrast: u8,
    }
    impl Default for Brightness {
        fn default() -> Self {
            Brightness::NORMAL
        }
    }
    impl Brightness {
        /// 最低亮度
        pub const DARKEST: Brightness = Brightness::custom(0x1, 0x00);
        pub const DARKER: Brightness = Brightness::custom(0x2, 0x2f);
        pub const NORMAL: Brightness = Brightness::custom(0x2, 0x5f);
        pub const BRIGHTER: Brightness = Brightness::custom(0x2, 0x9f);
        pub const BRIGHTEST: Brightness = Brightness::custom(0x2, 0xff);
    
        const fn custom(precharge: u8, contrast: u8) -> Self {
            Self {
                precharge,
                contrast,
            }
        }
    }
    
  11. 测试

    use esp_idf_sys as _; // If using the `binstart` feature of `esp-idf-sys`, always keep this module importemod i2c_adapter;
    
    use std::{
        thread,
        time::Duration,
    };
    
    use esp_idf_hal::{i2c, peripherals::Peripherals, prelude::*};
    mod i2c_adapter;
    use i2c_adapter::{I2CAdapter};
    
    const SSD1306_ADDRESS: u8 = 0x3c;
    
    mod command;
    use command::{Command};
    mod ssd1306;
    
    use anyhow;
    fn main() -> anyhow::Result<()> {
        // Temporary. Will disappear once ESP-IDF 4.4 is released, but for now it is necessary to call this function once,
        // or else some patches to the runtime implemented by esp-idf-sys might not link properly.
        esp_idf_sys::link_patches();
    
        let peripherals = Peripherals::take().unwrap();
        let sda = peripherals.pins.gpio4;
        let scl = peripherals.pins.gpio5;
    
        let i2c = peripherals.i2c0;
        let config = <i2c::config::MasterConfig as Default>::default().baudrate(100.kHz().into());
        let i2c = i2c::Master::<i2c::I2C0, _, _>::new(i2c, i2c::MasterPins { sda, scl }, config).unwrap();
    
        let i2c = I2CAdapter::new(i2c, SSD1306_ADDRESS, 0x40);
    
        let mut display = ssd1306::SSD1306::new(i2c, ssd1306::SSD1306Size);
    
        display.init().unwrap();
    
        display.draw(&[0u8; 1024]).unwrap();
    
        // 写入数据
        display.draw(&[
            0x00, 0x7F, 0x08, 0x08, 0x08, 0x7F, 0x00, 0x00, // H
            0x00, 0x38, 0x54, 0x54, 0x54, 0x18, 0x00, 0x00, // e
            0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00, // l
            0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00, // l
            0x00, 0x38, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, // o
            0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //
            0x00, 0x3F, 0x40, 0x38, 0x40, 0x3F, 0x00, 0x00, // W
            0x00, 0x38, 0x44, 0x44, 0x38, 0x00, 0x00, 0x00, // o
            0x00, 0x00, 0x7C, 0x08, 0x04, 0x00, 0x00, 0x00, // r
            0x00, 0x41, 0x7F, 0x40, 0x00, 0x00, 0x00, 0x00, // l
            0x00, 0x38, 0x44, 0x44, 0x48, 0x7F, 0x00, 0x00, // d
            0x00, 0x00, 0x5F, 0x00, 0x00, 0x00, 0x00, 0x00, // !
        ])
        .unwrap();
    
        loop {
            thread::sleep(Duration::from_millis(1000));
        }
    }
    
  12. 编译并上传

    连接开发板,使用如下命令编译并上传

    cargo espflash --release --monitor /dev/ttyUSB0
    

至此已经完成了 SSD1306 基本操作已经封装完成,下篇将使用 embedded_graphics crate 在 SSD1306 上显示文字和图案。

参考

  1. Embedded Rust on Espressif
  2. embedded-hal
  3. esp-idf-hal