esp32c3 运行 Rust(五)

489 阅读3分钟

esp32c3 运行 Rust(五) —— 封装 I2C 操作

上篇已经通过 I2C 点亮 SSD1306。让我们封装 SSD1306 操作方法。首先封装 I2C 操作

环境

  • 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.png

使用到的 GPIO

GPIOtitle
GPIO4SDA
GPIO5SCL

创建项目

使用如下命令创建 ssd1306 项目,并修改项目配置

cargo generate --git https://github.com/esp-rs/esp-idf-template cargo

创建细节参见 esp32c3 运行 Rust(〇)创建项目 HelloWorld

  1. 安装相关依赖

    Cargo.toml 中添加相关依赖

    # ...
    [dependencies]
    # ...
    esp-idf-sys = { version = "0.31.1", features = ["binstart"] }
    embedded-hal = "1.0.0-alpha.8" # 添加这行
    esp-idf-hal = "0.36.0" # 添加这行
    anyhow = "1.0.57" #  添加这行
    # ...
    
  2. 创建 i2c_adapter.rs

    定义错误类型

    pub enum SSD1306Error {
        BusWriteErr,
        OutOfBoundsErr,
    }
    
    pub type SSD1306Result = Result<(), SSD1306Error>;
    

    main.rs 中引入

    mod i2c_adapter;
    //...
    
  3. 定义 I2C 相关 trait

    pub trait I2CInterface {
        /// 写入命令
        fn send_cmd(&mut self, cmd: &[u8]) -> SSD1306Result;
        /// 写入数据
        fn send_data(&mut self, buf: &[u8]) -> SSD1306Result;
    }
    
  4. 定义 I2C 接口

    pub struct I2CAdapter<I2C> {
        i2c: I2C,
        addr: u8,
        data_prefix: u8,
    }
    
    impl<I2C> I2CAdapter<I2C>
    where
        I2C: embedded_hal::i2c::blocking::I2c,
    {
        pub fn new(i2c: I2C, addr: u8, data_prefix: u8) -> Self {
            Self { i2c, addr, data_prefix }
        }
    }
    
  5. 实现 I2CInterface trait

    impl<I2C> I2CInterface for I2CAdapter<I2C>
    where
        I2C: embedded_hal::i2c::blocking::I2c,
    {
        fn send_cmd(&mut self, cmd: &[u8]) -> SSD1306Result {
            let mut buf: [u8; 8] = [0; 0];
            buf[1..=cmd.len()].copy_from_slice(&cmd);
            self.i2c.write(self.addr, &buf).map_err(|_| SSD1306Error::BusWriteErr)
        }
    
        fn send_data(&mut self, data: &[u8]) -> SSD1306Result {
            if data.is_empty() {
                return Ok(());
            }
            let mut buf = [0u8; 17];
            buf[0] = self.data_prefix;
    
            data.chunks(16).try_for_each(|c| {
                let len = c.len();
                buf[1..=len].copy_from_slice(c);
                self.i2c.write(self.addr, &buf)
            }).map_err(|_| SSD1306Error::BusWriteErr)
        }
    }
    
  6. 在入口文件中使用

    main.rs 中添加如下代码

    mod i2c_adapter;
    
    use std::{
        thread,
        time::Duration,
    };
    
    use embedded_hal::blocking::i2c::Write;
    use esp_idf_hal::{i2c, peripherals::Peripherals, prelude::*};
    
    use i2c_adapter::{ I2CInterface, I2CAdapter };
    
    use anyhow;
    const SSD1306_ADDRESS: u8 = 0x3c;
    
    fn main() -> anyhow::Result<()> {
        //...
        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 mut i2c = i2c::Master::<i2c::I2C0, _, _>::new(i2c, i2c::MasterPins{sda, scl}, config)?;
    
        let i2c = I2CAdapter::new(i2c, SSD1306_ADDRESS, 0x40);
    
        i2c.send_cmd(&[0xae]).unwrap(); // 关闭显示
        i2c.send_cmd(&[0x00]).unwrap(); // Set lower column address
        i2c.send_cmd(&[0x10]).unwrap(); // Set higher column address
        i2c.send_cmd(&[0x40]).unwrap(); // Set display start line
        i2c.send_cmd(&[0xB0]).unwrap(); // Set page address
        i2c.send_cmd(&[0x81]).unwrap(); // contrast control
        i2c.send_cmd(&[0x7f]).unwrap(); // default contrast is 0x7f
        i2c.send_cmd(&[0xa1]).unwrap(); // Set segment remap
        i2c.send_cmd(&[0xa6]).unwrap(); // 设置显示模式为正常显示
        i2c.send_cmd(&[0xa8]).unwrap(); // Multiplex ratio
        i2c.send_cmd(&[0x3f]).unwrap(); // Duty = 1/64
        i2c.send_cmd(&[0xc8]).unwrap(); // Use remapped COM scan direction
        i2c.send_cmd(&[0xd3]).unwrap(); // Set display offset
        i2c.send_cmd(&[0x00]).unwrap(); // No offset
        i2c.send_cmd(&[0xd5]).unwrap(); // Set display clock division
        i2c.send_cmd(&[0x80]).unwrap(); // divide ratio
        i2c.send_cmd(&[0xd9]).unwrap(); // Set pre-charge period
        i2c.send_cmd(&[0xf1]).unwrap();
        i2c.send_cmd(&[0xda]).unwrap(); // Set COM pins
        i2c.send_cmd(&[0x12]).unwrap();
        i2c.send_cmd(&[0xdb]).unwrap(); // Set vcomh deselect level
        i2c.send_cmd(&[0x40]).unwrap();
        i2c.send_cmd(&[0x8d]).unwrap(); // Set charge pump state
        i2c.send_cmd(&[0x14]).unwrap(); // charge pump enabled
        i2c.send_cmd(&[0xaf]).unwrap(); // 打开显示
        i2c.send_cmd(&[0x20, 0x00]).unwrap(); // 设置显示模式
    
        // 清屏
        i2c.send_data(&[0u8; 1024]).unwrap();
    
        // 写入数据
        i2c.send_data(&[ 
            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();
    }
    

至此已经完成了 I2C 的封装,下篇将进行 SSD1306 的操作的封装。

参考

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