Крейты, которые вы будете использовать
Теперь, когда вы понимаете, что такое SSD1306 и как работает связь по I2C, давайте посмотрим, как на самом деле рисовать графику на дисплее. Возможно, вы думаете, что для каждого пикселя нужно отправлять сырые команды. Хорошая новость в том, что этого делать не нужно. Экосистема Rust предоставляет отличные инструменты, которые сильно упрощают эту задачу.
Рисование на дисплее
При работе с дисплеем SSD1306 на Rust вы будете использовать два основных крейта, которые работают вместе:
embedded-graphics— библиотека рисования, которая позволяет создавать фигуры, текст и изображенияssd1306— драйвер, который управляет самим оборудованием
Что такое embedded-graphics?
embedded-graphics — лёгкая библиотека двумерной графики, созданная для встраиваемых систем с ограниченной памятью. Вместо того чтобы самому устанавливать отдельные пиксели, вы используете команды рисования высокого уровня. Например:
#![allow(unused)]
fn main() {
Circle::new(Point::new(20, 20), 30)
.into_styled(PrimitiveStyle::with_stroke(BinaryColor::On, 1))
.draw(&mut display)?; // display реализует DrawTarget
}
Этот код рисует окружность, и вам не нужно вычислять позиции пикселей или разбираться, как дисплей хранит свои данные внутри.
Что предоставляет embedded-graphics:
- Графические примитивы, такие как окружности, прямоугольники, линии и треугольники
- Вывод текста — отображение текста разными шрифтами
- Поддержку изображений и иконок через совместимые крейты для изображений
- Параметры стиля для цвета, толщины линии и заливки
Ключевой принцип дизайна
embedded-graphics полностью не зависит от дисплея. Он ничего не знает о вашем конкретном оборудовании (будь то SSD1306, ST7789 или любой другой дисплей). Он просто умеет описывать, что нужно нарисовать. А драйвер дисплея берёт на себя детали конкретного оборудования и превращает эти инструкции в реальные пиксели.
Такой дизайн означает, что код рисования можно написать один раз и использовать с разными дисплеями, просто поменяв драйвер.
Подробнее можно узнать в официальной документации: https://docs.rs/embedded-graphics/latest/embedded_graphics/
Что такое крейт ssd1306?
Крейт ssd1306 — аппаратный драйвер для дисплеев, которые используют микросхему контроллера SSD1306. Он выполняет всю низкоуровневую работу, необходимую для связи с дисплеем. Это инициализация экрана, отправка правильных команд I2C или SPI, управление внутренним буфером при необходимости и обновление пикселей на аппаратуре.
Графический режим
Драйвер ssd1306 поддерживает несколько режимов, но для рисования графики вы будете использовать BufferedGraphicsMode. Чтобы войти в этот режим, вызовите:
#![allow(unused)]
fn main() {
let display = ssd1306::Ssd1306::new(i2c, size, rotation)
.into_buffered_graphics_mode();
}
В этом режиме драйвер хранит внутренний буфер в RAM и реализует трейт DrawTarget из embedded-graphics-core. Именно это позволяет двум крейтам работать вместе.
Как они работают вместе: трейт DrawTarget
Если вы дошли до этого места, то, вероятно, уже понимаете трейты Rust. Трейт в общих чертах описывает, что что-то умеет делать, а реализация решает, как именно это делается.
DrawTarget — трейт, который позволяет embedded-graphics отправлять команды рисования драйверу дисплея. Когда драйвер ssd1306 реализует этот трейт, по сути он говорит:
«Я могу принимать пиксели, которые создаёт embedded-graphics, и я знаю, как вывести эти пиксели на экран SSD1306».
Вот что на самом деле происходит, когда вы что-то рисуете:
- Вы создаёте фигуры, текст или изображения с помощью примитивов embedded-graphics.
- Вы вызываете
.draw(&mut display), чтобы их отрисовать. embedded-graphicsгенерирует пиксели, которые нужно нарисовать.- Драйвер
ssd1306берёт эти пиксели и сохраняет их во внутреннем буфере. - Когда вы вызываете
display.flush(), драйвер отправляет обновлённые пиксели на OLED-аппаратуру.
Вот простой пример:
#![allow(unused)]
fn main() {
// Создаём окружность с помощью embedded-graphics
let circle = Circle::new(Point::new(64, 32), 20)
.into_styled(PrimitiveStyle::with_fill(BinaryColor::On));
// Рисуем её на драйвере дисплея
circle.draw(&mut display)?; // Записываем данные пикселей в буфер драйвера
display.flush()?; // Отправляем буфер на настоящий экран
}
Асинхронный режим
Крейт ssd1306 также поддерживает асинхронную работу, если включить feature async. Это полезно, если вы используете Embassy.
Чтобы использовать асинхронный режим, добавьте feature в Cargo.toml:
ssd1306 = { version = "0.10.0", features = ["async"] }
Когда feature async включён, драйвер использует трейты embedded-hal-async вместо обычных блокирующих трейтов embedded-hal. Это позволяет выполнять обмен по I2C/SPI асинхронно, что удобно, когда вы хотите заниматься другими задачами, пока обновляется дисплей.
Главное отличие в том, что такие методы, как .init() и .flush(), становятся асинхронными и их нужно вызывать с .await:
#![allow(unused)]
fn main() {
// Асинхронная версия
let mut display = Ssd1306Async::new(interface, DisplaySize128x64, DisplayRotation::Rotate0)
.into_buffered_graphics_mode();
display.init().await?; // Обратите внимание на .await
// Рисование работает так же
circle.draw(&mut display)?;
display.flush().await?; // Теперь это тоже асинхронно
}