10 KiB
VFD — управление дисплеями в Epson-совместимом режиме
Небольшая Rust-библиотека для двухстрочных VFD-дисплеев покупателя, работающих через serial-порт в Epson/ESC-совместимом режиме. Проект ориентирован на дисплеи семейства PD-2600/PD-2800 и похожие модели с поддержкой команд позиционирования, яркости и таблицы символов CP866.
Библиотека подходит для часов, локальных дашбордов, кассовых приложений, уведомлений, индикаторов состояния и других проектов, где данные нужно обновлять без мерцания всей строки.
Возможности
- вывод текста в первую или вторую строку;
- печать с координаты
(x, y); - обновление только изменившихся диапазонов строки через
print_line_diff; - фоновая бегущая строка с регулируемой скоростью и паузой;
- четыре уровня яркости;
- кириллица через CP866;
- потокобезопасный интерфейс
VfdHandle, который можно клонировать; - автоматическая обрезка текста по правому краю и дополнение строк пробелами.
Поддерживаемый протокол
При открытии устройства библиотека использует следующие параметры и команды:
| Параметр | Значение |
|---|---|
| Скорость serial-порта | 9600 baud |
| Таблица символов | CP866, команда ESC t 6 |
| Инициализация | ESC @ |
| Очистка | FF (0x0C) |
| Координаты | US $ x y, нумерация от 1 |
| Яркость | US X n, где n = 1..4 |
| Число строк | 2 |
| Допустимая ширина | 1..=255 символов |
Перед запуском убедитесь, что дисплей переведён DIP-переключателями в совместимый
режим. Точные настройки конкретной модели смотрите в документации из каталога
docs.
Подключение
Для локального проекта добавьте библиотеку как path-зависимость:
[dependencies]
m = { path = "../vfd" }
anyhow = "1"
Имя crate сейчас — m, поэтому импорт начинается с m::.
Быстрый старт
use anyhow::Result;
use m::vfd::VfdConfig;
use m::worker::VfdWorker;
use std::time::Duration;
fn main() -> Result<()> {
let config = VfdConfig::new("/dev/cu.usbmodem101")
.with_width(20);
// Serial-порт открывается до запуска фонового потока,
// поэтому ошибка подключения вернётся из start().
let worker = VfdWorker::start(config)?;
let display = worker.handle();
display.clear();
display.set_brightness(2);
display.print_line_diff(1, "Привет!")?;
display.print_line_diff(2, "VFD готов")?;
// Команды выполняются асинхронно: не завершаем программу сразу после отправки.
std::thread::sleep(Duration::from_secs(2));
Ok(())
}
VfdWorker последовательно выполняет команды в отдельном потоке. При удалении worker
отправляет команду завершения и дожидается остановки потока. Сохраняйте worker в
области видимости, пока дисплей используется.
Основной API
Конфигурация
use m::vfd::VfdConfig;
use std::time::Duration;
let config = VfdConfig::new("/dev/ttyUSB0")
.with_width(20)
.with_timeout(Duration::from_millis(250));
Ширина автоматически ограничивается диапазоном 1..=255, потому что координаты
протокола передаются одним байтом.
Полная и дифференциальная запись
display.print_line(1, "Полная перезапись")?;
display.print_line_diff(2, "Изменились цифры: 42")?;
print_line_diff хранит последний кадр каждой строки и группирует соседние изменения.
Если текст не изменился, serial-команда не отправляется. Это полезно для часов и
дашбордов, которые обновляются часто.
Печать по координатам
display.print_at(7, 1, "21.50")?;
display.print_at(16, 1, "1200")?;
Координаты начинаются с единицы. Допустимы строки 1 и 2; текст, выходящий за правую
границу, обрезается по символам без повреждения UTF-8.
Бегущая строка
use std::time::Duration;
display.set_marquee_text("Длинное сообщение для посетителя");
display.start_marquee(
2, // строка
8, // символов в секунду
Duration::from_millis(1500), // пауза после полного прохода
);
// При необходимости:
display.stop_marquee();
Обычные команды записи в строку, занятую marquee, игнорируются, чтобы два источника не перезаписывали друг друга.
Примеры
Все примеры принимают два общих аргумента:
- serial-порт, по умолчанию
/dev/cu.usbmodem101; - ширина дисплея, по умолчанию
20.
Дополнительные аргументы зависят от примера:
| Пример | Что демонстрирует | Дополнительные аргументы |
|---|---|---|
clock |
Дату, время, день недели и часть суток | [brightness=2] |
marquee |
Фоновую бегущую строку | [cps=8] [pause_ms=1500] [brightness=2] |
brightness |
Перебор уровней яркости 1..=4 |
[delay_ms=800] |
position |
Движение символа по второй строке | [delay_ms=80] |
update_at |
Частичное обновление полей дашборда | [delay_ms=200] |
blink |
Мигание текста в заданной позиции | [delay_ms=400] [x=1] [y=1] [text=BLINK] |
Примеры запуска:
cargo run --example clock -- /dev/cu.usbmodem101 20 2
cargo run --example marquee -- /dev/cu.usbmodem101 20 8 1500 2
cargo run --example brightness -- /dev/cu.usbmodem101 20 800
cargo run --example position -- /dev/cu.usbmodem101 20 80
cargo run --example update_at -- /dev/cu.usbmodem101 20 200
cargo run --example blink -- /dev/cu.usbmodem101 20 400 1 2 BLINK
update_at использует фиксированную 20-символьную раскладку и завершится с понятной
ошибкой, если передать меньшую ширину.
Те же программы доступны через Task:
task vfd:clock
task vfd:marquee
task vfd:brightness
task vfd:position
task vfd:update_at
task vfd:blink
Значения можно переопределять переменными, например:
task vfd:marquee VFD_PORT=/dev/ttyUSB0 VFD_WIDTH=20 VFD_CPS=10
Кодировка текста
Перед записью библиотека заменяет несколько типографских символов на совместимые аналоги:
…→.;—и–→-;№→#;- типографские кавычки → обычные кавычки;
- табуляция → пробел.
Остальной текст кодируется с помощью encoding_rs::IBM866.
Структура проекта
src/vfd.rs низкоуровневые команды и CP866
src/worker.rs очередь команд, diff и marquee
examples/common/mod.rs общие CLI-утилиты примеров
examples/*.rs демонстрационные программы
docs/ руководства для дисплеев
taskfile.yml команды запуска примеров
Разработка и проверка
cargo fmt --all -- --check
cargo clippy --all-targets --all-features --locked -- -D warnings
cargo test --all-targets --locked
cargo check --examples --locked
Автоматические тесты проверяют Unicode, CP866-санацию, ширину строк, обновление кэша, группировку diff-диапазонов, таймер marquee и функции примера часов. Для полной проверки нужен отдельный smoke-test с физическим дисплеем и фактическим serial-портом.
Ограничения
- поддерживаются только две строки;
- скорость подключения фиксирована на
9600 baud; - очередь команд в worker не ограничена по размеру;
- аппаратные ошибки после запуска worker выводятся в stderr из фонового потока;
- пример
clockиспользует системную командуdateи рассчитан на Unix-подобную среду; - библиотека не определяет serial-порт автоматически.