Files
vfd/README.md
T

10 KiB
Raw Blame History

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, игнорируются, чтобы два источника не перезаписывали друг друга.

Примеры

Все примеры принимают два общих аргумента:

  1. serial-порт, по умолчанию /dev/cu.usbmodem101;
  2. ширина дисплея, по умолчанию 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-порт автоматически.