esp32c3 运行 Rust(六)

363 阅读5分钟

esp32c3 运行 Rust(六)

上篇已经封装 I2C 操作,让我们开始封装 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. 创建 command.rs 文件

    定义命令枚举

    pub enum Command {
        //**************************
        // 充电泵命令表
        ChangePump(bool),
        //**************************
        // 基本命令
        /// 对比度
        Contrast(u8),
        /// 打开/关闭正显示屏,忽略内存中的数据
        OnAll(bool),
        /// 反转显示
        Invert(bool),
        /// 开启/关闭显示
        DisplayOn(bool),
        //**************************
        // 滚动命令
        /// 水平滚动
        HScrollSetup(HScrollDirection, Page, Page, ScrollStep),
        /// 垂直滚动
        VScrollSetup(VHScrollDirection, Page, Page, ScrollStep, u8),
        /// 开启/关闭滚动
        ScrollEnable(bool),
        /// 设置重置滚动区域
        ScrollArea(u8, u8),
        //**************************
        // 地址设置命令
        /// 设置起始低地址,仅耶模式下可用
        LowerColStart(u8),
        /// 设置起始高地址,仅耶模式下可用
        UpperColStart(u8),
        /// 设置内存地址模式
        AddressMode(AddrMode),
        /// 设置列地址,仅在 AddrMode::Horizontal/AddrMode::Vertical 可用
        ColumAddr(u8, u8),
        /// 设置页地址,仅在 AddrMode::Horizontal/AddrMode::Vertical 可用
        PageAddr(Page, Page),
        /// 设置起始页,仅在 AddrMode::Page 可用
        PageStart(Page),
        //**************************
        // 硬件配置(分辨率和布局)
        /// 显示起始行
        StartLine(u8),
        /// 设置区段重绘
        SegmentRemap(bool),
        /// 设置重复率
        Multiplex(u8),
        /// 设置COM输出扫描方向
        ComOutputScanDirection(bool),
        /// 设置显示偏移量
        DisplayOffset(u8),
        /// 设置COM引脚硬件配置
        ComPinConfig(bool, bool),
        //**************************
        /// 计时和驱动方案设置命令
        DisplayClockDivide(u8, u8),
        /// 设置预充电周期
        PreChargePeriod(u8, u8),
        /// 调整 VCOMH 的输出
        VcomhDeselect(VcomhLevel),
        /// 空操作
        Nop,
    }
    

    定义滚动命令相关参数枚举

    • 水平滚动方向

      pub enum HScrollDirection {
          LeftToRight = 0,
          RightToLeft = 1,
      }
      
    • 垂直+水平滚动

      pub enum VHScrollDirection {
          /// 垂直向右滚动
          VRight = 0b01,
          /// 垂直向左滚动
          VLeft = 0b10,
      }
      
    • 滚动帧率

      pub enum ScrollStep {
          Frame2 = 0b111,
          Frame3 = 0b100,
          Frame4 = 0b101,
          Frame5 = 0b000,
          Frame25 = 0b110,
          Frame64 = 0b001,
          Frame128 = 0b010,
          Frame256 = 0b011,
      }
      

    定义页地址枚举

    pub enum Page {
        Page0 = 0b000,
        Page1 = 0b001,
        Page2 = 0b010,
        Page3 = 0b011,
        Page4 = 0b100,
        Page5 = 0b101,
        Page6 = 0b110,
        Page7 = 0b111,
    }
    

    定义地址模式枚举

    #[derive(Debug, Clone, Copy, PartialEq)]
    pub enum AddrMode {
        Horizontal = 0b00,
        Vertical = 0b01,
        Page = 0b10,
    }
    

    VCOMH 的输出级别

    pub enum VcomhLevel {
        V65 = 0b000,
        V77 = 0b010,
        V83 = 0b011,
        Auto = 0b100,
    }
    
  2. 实现将命令写入 SSD1306

    impl Command {
        pub fn send<I2C>(self, iface: &mut I2C) -> SSD1306Result
        where
            I2C: I2CInterface
        {
            let (data, len) = match self {
                Self::ChangePump(pump) => ([0x8D, 0x10 | ((pump as u8) << 2), 0, 0, 0, 0, 0], 2),
                Self::Contrast(val) => ([0x81, val, 0, 0, 0, 0, 0], 2),
                Self::OnAll(all) => ([0xa4 | (all as u8), 0, 0, 0, 0, 0, 0], 1),
                Self::Invert(inverse) => ([0xa6 | (inverse as u8), 0, 0, 0, 0, 0, 0], 1),
                Self::DisplayOn(on) => ([0xae | (on as u8), 0, 0, 0, 0, 0, 0], 1),
                Self::HScrollSetup(direction, start, end, step) => ([
                    0x26 | (direction as u8),
                    0,
                    start as u8,
                    step as u8,
                    end as u8,
                    0,
                    0xff,
                ], 7),
                Self::VScrollSetup(direction, start, end, step, offset) => ([
                    0x28 | (direction as u8),
                    0,
                    start as u8,
                    step as u8,
                    end as u8,
                    offset,
                    0,
                ], 6),
                Self::ScrollEnable(enable) =>([0x2e|(enable as u8), 0, 0, 0, 0, 0, 0], 1),
                Self::ScrollArea(top, bottom) => ([0xa3, top, bottom, 0, 0, 0, 0], 3),
                Self::LowerColStart(addr) => ([0xf & addr, 0, 0, 0, 0, 0, 0], 1),
                Self::UpperColStart(addr) => ([0x10 | (0xf & addr), 0, 0, 0, 0, 0, 0], 1),
                Self::AddressMode(mode) => ([0x20, mode as u8, 0, 0, 0, 0, 0], 2),
                Self::ColumAddr(start, end) => ([0x21, start, end, 0, 0, 0, 0], 3),
                Self::PageAddr(start, end) => ([0x22, start as u8, end as u8, 0, 0, 0, 0], 3),
                Self::PageStart(page) => ([0xb0 | (page as u8), 0, 0, 0, 0, 0, 0], 1),
                Self::StartLine(line) => ([0x40 | (0x3f & line), 0, 0, 0, 0, 0, 0], 1),
                Self::SegmentRemap(remap) => ([0xA0 | (remap as u8), 0, 0, 0, 0, 0, 0], 1),
                Self::Multiplex(ratio) => ([0xa8, ratio, 0, 0, 0, 0, 0], 2),
                Self::ComOutputScanDirection(rev) => ([0xc0|((rev as u8) << 3), 0, 0, 0, 0, 0, 0], 1),
                Self::DisplayOffset(offset) => ([0xd3, offset, 0, 0, 0, 0, 0], 2),
                Self::ComPinConfig(alt, lr) => ([0xda, 0x2 | ((alt as u8) << 4) | ((lr as u8) << 5), 0, 0, 0, 0, 0], 2),
                Self::DisplayClockDivide(fosc, div) => ([0xd5, (((0xf & fosc) << 4) | (0xf & div)), 0, 0, 0, 0, 0], 2),
                Self::PreChargePeriod(p1, p2) => ([0xd9, (((0xf & p1) << 4) | (0xf & p2)), 0, 0, 0, 0, 0], 2),
                Self::VcomhDeselect(level) => ([0xdb, (level as u8) << 4, 0, 0, 0, 0, 0], 2),
                Self::Nop => ([0xe3, 0, 0, 0, 0, 0, 0], 1),
            };
    
            iface.send_cmd(&data[..len]).map_err(|_| SSD1306Error::BusWriteErr)
        }
    }
    
  3. 测试命令

    修改上篇的 main.rs

    //...
    mod command;
    use command::Command;
    //...
    fn main() -> anyhow::Result<()> {
        //...
        let mut i2c = I2CAdapter::new(i2c, SSD1306_ADDRESS, 0x40);
    
        Command::DisplayOn(false).send(&mut i2c).unwrap(); // 关闭显示
        Command::DisplayClockDivide(0x8, 0x0).send(&mut i2c).unwrap();
        Command::Multiplex(63).send(&mut i2c).unwrap();
        Command::DisplayOffset(0).send(&mut i2c).unwrap();
        Command::StartLine(0).send(&mut i2c).unwrap();
    
        Command::ChangePump(true).send(&mut i2c).unwrap();
        Command::AddressMode(self.addr_mode).send(&mut i2c).unwrap();
    
        Command::ComPinConfig(true, false).send(&mut i2c).unwrap();
        // 旋转
        Command::SegmentRemap(true).send(&mut i2c).unwrap();
        Command::ComOutputScanDirection(true).send(&mut i2c).unwrap();
        // 亮度
        Command::PreChargePeriod(1, 0x2).send(&mut i2c).unwrap();
        Command::Contrast(0x5f).send(&mut i2c).unwrap();
    
        Command::VcomhDeselect(VcomhLevel::Auto).send(&mut i2c).unwrap();
        Command::OnAll(false).send(&mut i2c).unwrap();
        Command::Invert(false).send(&mut i2c).unwrap();
        Command::ScrollEnable(false).send(&mut i2c).unwrap();
        Command::DisplayOn(true).send(&mut i2c).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();
    
        loop {
            thread::sleep(Duration::from_millis(1000));
        }
    }
    
  4. 编译并上传

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

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

至此已经完成了 SSD1306 命令的封装,下篇将进行 SSD1306 的初始化的封装。

参考

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