esp32c3 运行 Rust(七)
上篇已经封装 SSD1306 相关操作指令,开始封装初始化操作。
环境
-
OS: Ubuntu 20.04.4 LTS
-
Rust: rustc 1.62.0-nightly
-
IDE: VsCode
-
ESP32C3: ESP-C3-13-Kit 开发板
-
SSD1306: 0.96 寸 OLED 模块
使用到的 GPIO
| GPIO | title |
|---|---|
| GPIO4 | SDA |
| GPIO5 | SCL |
创建项目
使用上篇创建的项目工程。
-
创建
ssd1306.rs文件定义 SSD1306 结构体
pub struct SSD1306<Adapter, SIZE, FrameBuf> { adapter: Adapter, addr_mode: AddrMode, size: SIZE, frame: FrameBuf, } -
定义创建实例
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() } } } -
定义
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(()) } -
定义清屏
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; } -
定义亮度调节
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) } -
定义获取屏幕尺寸
dimensions方法/// 获取屏幕尺寸 pub fn dimensions(&self) -> (u8, u8) { (SIZE::WIDTH, SIZE::HEIGHT) } -
定义设置绘制区域
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实现Fromtraitimpl 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!("页地址溢出"), } } } -
定义设置像素
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) } } -
定义刷新屏幕
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), ) } -
其他部分
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, } } } -
测试
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)); } } -
编译并上传
连接开发板,使用如下命令编译并上传
cargo espflash --release --monitor /dev/ttyUSB0
至此已经完成了 SSD1306 基本操作已经封装完成,下篇将使用 embedded_graphics crate 在 SSD1306 上显示文字和图案。