Recent Changes - Search:

Проекты

Брошенные проекты

SourceForge

edit SideBar

USBLCD2

Графический LCD индикатор, подключаемый к USB с клавиатурой и приемником IR Remote.

- cоздан на основе AVR контроллера Atmega8 и драйвера avrusb (V-USB).

Причины создания.

На самом деле причины три:

  1. необходимость утилизации валяющихся у меня с незапамятных времен: LCD дисплей МЭЛТ МТ12232 и AVR контроллер atMega8;
  2. прикольно иметь небольшой дисплейчик для вывода всякой вспомогательной информации;
  3. давно хотелось это сделать.

На самом деле, в Интернете можно найти множество схем подобных самоделок, однако, большинство из них имеют символьные индикаторы и заточены под работу под конкретные приложения. Писать под них приложения не очень удобно, а об использование в Perl'е речь вообще не идет. Да и гораздо прикольнее придумать что-нибудь самому, чем тупо копировать.

Цели.

И так, чего именно я хотел добиться:

  • сделать само железо;
  • написать драйвер для Linux на C;
  • сделать Perl интерфейс для этого драйвера;
  • по-возможности, интегрировать драйвер в какую-нибудь графическую систему.

Немного подумав, я решил отказаться от последнего пункта, так как ни оконный менеджер, ни какое другое более-менее серьезное приложение на монохромном индикаторе с разрешением 122x32 запустить не получится. Было-бы неплохо сделать интерфейс для Python'а, тогда можно было бы писать плагины для Gnome'а и Rhythmbox'а. Однако, Питона я не знаю.

Железо.

Конструктивно, устройство представляет собой USB гаджет с графическим ЖК индикатором, тремя разноцветными светодиодами, несколькими кнопками для управления и приемником для ИК пульта. Все это управляется микроконтроллером AVR atMega8. Со схемой устройства можно ознакомиться здесь.

USB часть взята целиком из проекта V-USB: та схема, которая со стабилитронами. Из изменений, я, разве что, убрал мегаомный резистор и добавил самовосстанавливающийся предохранитель на 100mA, так как не хотелось спалить компьютер во время монтажа и отладки. Светодиоды подключены к выходам ШИМ, чтобы можно было регулировать их яркость. Так как подсветка ЖК индикатора состоит из нескольких светодиодов я не рискнул подключить ее напрямую к порту микроконтроллера и добавил транзистор. Вполне возможно, что это является лишиним и порт способен справиться с такой нагрузкой. Регулировать яркость подсветки нельзя. Впрочем, она сама по себе не особенно и яркая. К оставшимся портам ввода-вывода я подключил сам ЖКИ модуль: МТ12232 отечественного производителя МЭЛТ. Для кнопок свободных портов не оставалось, поэтому пришлось подключать их на общие линии с ЖК индикатором. Формально, способ подключения клавиатуры позволяет отслеживать одновременное нажатие нескольких кнопок, однако, я решил не загружать схему всякими диодами. Так что текущая реализация позволяет быть нажатой только одной клавише. ИК приемник взят TSOP31328 и подключен к линии внешнего прерывания INT1. Это важно, так как декодер команд ИК пульта работает только при помощи прерываний по фронтам сигнала.

Все это было смонтировано на небольшой печатной плате. На отдельный DIP разъем были выведены сигналы для программирования микроконтроллера.

Уже после изготовления печатной платы выяснилось, что USB порт выдает не честные 5V, а что-то около 4.6V. Этого оказалось недостаточно для ЖК индикатора, так что пришлось добавить в схему DC-DC преобразователь. Картинка появилась, но оказалось, что переменный резистор не позволяет плавно регулировать контрасность. Замена его на постоянный номиналом 10К позволило сократить габариты устройства и оставить контрастность на приеемлемом уровне.

Фотографии собранной платы можно посмотреть: здесь и здесь.

Корпус представляет собой две пластины из оргстекла, прикрепленные к плате с помощью нейлоновых стоек. Пластины имеют разную высоту, благодаря чему, ЖК индикатор располагается под удобным углом к пользователю. Колпачки кнопок приклеены к самим кнопкам термоклеем.

Прошивка.

Прошивку написать оказалось еще проще, чем сделать железку: благо создатели V-USB сделали за нас всю самую сложную работу. Правда, к сожалению, это драйвер не совсем свободный, однако лицензия разрешает его использование бесплатно в некоммерческих целях.

На моей совести остались: протокол взаимодействия высокого уровня, драйвер ЖКИ модуля и светодиодов. Исходный текст прошивки можно взять здесь. Я писал это все дело с помощью IDE Code::Blocks и WinAVR, поэтому Make-файлы отсутствуют. Исходные коды прошивки, драйвера и примеров можно скачать отсюда.

Повозиться пришлось с драйвером ЖК дисплея. Дело в том, что внутренняя видео-память там организованно достаточно запутанно: модуль состоит из двух чипов, каждый из которых управляет половинкой экрана; один байт описывает не строчку из восьми пикселей, а столбец и т.д. Однако, я рассчитывал сделать устройство с плоской моделью памяти, в котором сканирование происходит слева-направо - сверху-вниз и при однобитном разрешении один байт соответствует строчке из восьми пикселей. Поэтому пришлось написать несколько строчек кода для преобразования адресов и повернуть дисплей на 90 градусов: сделать из 122x32 - 32x122. Подробнее об организации видеопамяти будет рассказано в следующем разделе.

Протокол взаимодействия.

Устройство имеет два endpoint'а:

  • 0: CONTROL - стандартный, есть у всех USB устройств;
  • 1: INT IN - нужен для получения события о нажатии кнопки или приема кода от датчика IR Remote.
  • 2: BULK OUT - используется для записи данных в видеопамять: читать из видеопамяти нельзя.

Номер endpoint'а жестко не определен, поэтому драйвер должен определить нужный по типу: INT IN и BULK OUT.

Помимо стандартных команд для подключения и настройки, CONTROL endpoint поддерживает следующие Vendor Specific команды:

  • GETINFO - получить информацию об устройстве: тип, количество видеорежимов и количество светодиодов;
  • GETMODE - получить информацию о видеорежме по его номеру;
  • SETMODE - установить видеорежим;
  • SETPOWER - включить устройство, включить подсветку, выключить устройство;
  • GETLED - получить информацию о светодиоде по его номеру;
  • SETLED - установить яркость свечения светодиода.
  • GETKEY - получить информацию о кнопке по ее номеру.

Рассмотрим их поподробнее.

GETINFO

bmRequestTypebRequestwValuewIndexwLengthData
11000000bFFh0000h0000h0006h*t_usblcd_info

С помощью этого запроса мы узнаем основную информацию об устройстве. Запрос возвращает шестибайтовую структуру t_usblcd_info, которая имеет следующее описание на языке C:

typedef struct {
    uint32_t type;
    uint8_t num_modes;
    uint8_t num_leds;
    uint8_t num_keys;
} __attribute__ ((packed)) t_usblcd_info;
  • type - тип устройства. На данный момент должен быть 0x2.
  • num_modes - количество видеорежимов. Должен быть минимум один.
  • num_leds - количество светодиодов в устройстве.
  • num_keys - количество кнопок в устройстве.

Как видно из структуры, количество светодиодов и ведиорежимов не может превышать 255. Количество кнопок - не больше 32. Наличие датчик для пульта дистанционного управления определить нельзя. Если он отсутствует, то от него просто не будут приходить события.

Поле type использует драйвер для того, чтобы определить как именно нужно общаться с устройством. Устройство с типом 0x1 использует несколько другие структуры и драйвер с ним будет работать некорректно.

GETMODE

bmRequestTypebRequestwValuewIndexwLengthData
11000000bFEh0000hmode number0007h*t_usblcd_mode

Запрос для получения информации об видеорежиме по его номеру. Нумерация ведется с нуля. В результате возвращается следующая структура:

typedef struct {
    uint16_t width;
    uint16_t height;
    uint8_t bpp;
    uint16_t stride;
} __attribute__ ((packed)) t_usblcd_mode;
  • width, height - разрешение видеорежима в пикселях.
  • bpp - цветовое разрешение: количество бит на пиксель.
  • stride - количество байт, которые занимает одна строка.

Поле stride показывает, сколько байт нужно прибавить к текущему адресу видеопамяти, чтобы перейти на следующую строку. В идеале, оно вычисляется по формуле:

` stride = |_ (width * bpp + 7 ) / 8 _| `

Однако, в некоторых устройствах оно может иметь большее значение. Например, в дисплее с разрешением 122x122x8, разработчик хочет, чтобы адрес видеопамяти зависил от координаты пикселя таким образом: YYXXh, то есть, старшый байт слова являлся Y-координатой, а младший - X. В этом случае, значение stride будет равняться 256, несмотря на то, что при данном разрешении строка занимает только 122 байта. "Хвост" строки от 122-го пикселя на экране не отображается.

SETMODE

bmRequestTypebRequestwValuewIndexwLengthData
01000000bFDh0000hmode number0000hNULL

Здесь все относительно ясно: установить текущий видеорежим. Если значение mode number выходит за диапазон допустимых видеорежимов, то ничего не произойдет. Режим по умолчанию (устанавливается по включении питания): 0. Возможность определения текущего видеорежима не предусмотрена.

SETPOWER

bmRequestTypebRequestwValuewIndexwLengthData
01000000bFChmode0000h0000hNULL

Управление питанием устройства. Значение mode может быть следующим:

  • 0 - включены устройство и подсветка дисплея;
  • 1 - устройство включено, светодиоды горят, картинка выводится, но подсветка дисплея выключена;
  • 2 - устройство выключено, подсветка дисплея не горит, светодиоды погашены.

После выключения устройства и последующего включения его состояние не определено. Не гарантируется, что оно будет соответствовать состоянию до выключения или состоянию по умолчанию. Необходимо заново установить видеорежим, яркость светодиодов и загрузить содержимое видеопамяти.

GETLED

bmRequestTypebRequestwValuewIndexwLengthData
11000000bFBh0000hLED number0003h*t_usblcd_led

Получить информацию о светодиоде по его номеру. Возвращает следующую структуру:

typedef struct {
    uint8_t type;
    uint8_t color;
    uint8_t max;
} __attribute__ ((packed)) t_usblcd_led;
  • type - тип информации, которую сообщает горящий светодиод:
    • 0 - не определено;
    • 1 - все в порядке;
    • 2 - предупреждение;
    • 3 - ошибка/авария.
  • color - цвет светодиода:
    • 0 - белый;
    • 1 - красный;
    • 2 - оранжевый;
    • 3 - желтый;
    • 4 - зеленый;
    • 6 - синий.
  • max - максимальное значение яркости. Должно быть от 1 до 255.

Для некоторых приложений важно зажечь именно тот светодиод, который сигнализирует об ошибке. Как правило, он имеет красный цвет. Но, на устройстве может быть установлено два красных светодиода: один с ошибкой, а другой - еще для чего-нибудь. Именно для их различия и было добавлено поле type.

SETLED

bmRequestTypebRequestwValuewIndexwLengthData
01000000bFAhvalueLED number0000hNULL

Установить значение яркости соответствующего светодиода. Если номер светодиода превышает их общее количество, то ничего не произойдет. Если передаваемой значение больше максимального для данного светодиода, то будет установлено максимальное значение яркости. 0 - соответствует состоянию "выключено". Яркость растет пропорционально значению.

GETKEY

bmRequestTypebRequestwValuewIndexwLengthData
11000000bF9h0000hkey number0005h*t_usblcd_key

Получить информацию о кнопке по номеру. Возвращает следующую структуру:

typedef struct {
    uint8_t key;
    uint32_t mask;
} __attribute__ ((packed)) t_usblcd_key;
  • key - назначение кнопки:
    • 0 - не определено
    • 1 - Up
    • 2 - Down
    • 3 - Left
    • 4 - Right
    • 5 - OK
    • 6 - Cancel
    • 7 - F1
    • 8 - F2
    • 9 - F3
    • 10 - F4

Примечание: Кнопки с кодом 0 быть не должно. Кнопки F1 - F4 - это функциональные кнопки и могут иметь произвольное назначение. Обычно, они располагаются ниже дисплея и их функция отображается на дисплее над ними.

  • mask - маска, которая определяет бит, отвечающий за эту кнопку в сообщении о событии с кнопками, которое приходит по INT IN Endpoint.

Загрузка данных в видеопамять.

Общая структура видеопамяти устройства показана на рисунке.

Адрес видеопамяти имеет длину 32 бита, начинается с нуля и вычисляется по формуле:

` addr = y * stride + (x * bpp + 7) / 8 `

В случае, если количество бит на пиксель меньше восьми, то левее отображаются пиксели, закодированные старшими битами. Если количество бит на пиксель больше восьми (16, 24, 32), то значение цвета кодируется в форммате little endian - так, как это удобно x86 процессору. Цвет кодируется в формате RGB: красный цвет - в старших битах, голубой - в младших.

Загрузка данных в видеопамять происходит через Output Bulk Endpoint. Для этого массив данных делится на куски длиною не более 255 байт и из них формируются некие команды, которые отправляются на этот Endpoint. Кроме этого, существует особая команда, с помощью которой можно установить определенное значение цвета для целой строки. Формат этих команд описан ниже.

HLINE

SizeNameValue
1type69h
4addrstarting address
1sizeline length
4colorcolor

Рисует горизонтальную линию длиной line length в байтах, начиная с адреса starting addres цветом color. Начальный адрес и длина должны быть выровнены по формату пикселя. Формат поля color зависит от установленного видеорежима: если цветовое разрешение меньше 32-х бит, то старшие биты этого поля не используются. Основное назначение команды: быстрая очистка экрана и рисование прямоугольников.

RAW

SizeNameValue
1type68h
4addrstarting address
1sizedata size
1..255dataimage data

Копирует байты из image data длиной data size в видеопамять устройства, начиная с адреса starting address. Из таблицы видно, что максимальный блок данных, который мы можем скопировать одной командой не может превышать 255 байт. Адрес и длину выравнивать по цветовому разрешению текущего видеорежима не нужно: мы работаем с байтами, а не пикселями. Формат данных зависит от установленного видеорежима.

Клавиатура и пульт дистанционного управления.

События о нажатии и отпускании кнопки, а также, о получении кода пульта дистанционного управления от ИК датчика приходят через INT IN Endpoint. В случае возникновения события, устройство отправляет следующую структуру:

typedef struct {
    uint8_t type;
    uint32_t value;
} __attribute__ ((packed)) t_usblcd_event;
  • type - тип события:
    • 0 - клавиши;
    • 1 - ИК пульт.
  • value - код события.

Если событие пришло от кнопок, то поле value содержит битовый флаг, который показывает, какие кнопки нажаты в данное время. Кнопка нажата, если соответствующий бит установлен. Напомню, что соответствие между битом и кнопкой определяется маской, которую возвращает команда GETKEY.

Если событие пришло от пульта дистанционного управления, то value определяет 32-х битный код команды. Код команды не определен, однако можно утверждать, что он один и тот же для одной кнопки пульта, и уникален для каждой. В текущей версии прошивки, устройство понимает только кодировку NEC и value является расшифрованным пакетом этого протокола.

Драйвер.

Драйвер представляет собой динамически подключаемую библиотеку, которая содержит функции для работы с дисплеем.

Для работы с USB драйверу нужна библиотека libusb. Для того, чтобы собрать драйвер нужно, чтобы была установленная не только сама библиотека но и ее заголовочные файлы. Для Ubuntu или Debian необходимо установить пакеты: libusb и libusb-dev. Во FreeBSD заголовочные файлы устанавливаются вместе с соответствующим пакетом, однако, в директорию: /usr/local/include. Учтите это, запуская скрипт ./configure. Программы, написанные с использованием этого драйвера должны запускаться с привилегиями суперпользователя: иначе доступа в USB не получить.

Теоретически, драйвер может быть скомпилирован для Windows, однако в релиз данная возможность не вошла.

В драйвер входит тестовая программа, по которой можно изучить API - он сам по себе довольно простой. Для примера, программа, рисующая прямоугольник может выглядеть так:

#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>

#include <usblcddrv.h>

int main(int argc, char *argv[]) {
    t_usblcd *dev;
    int err;

    /* подключаем usblcd устройство */
    dev = usblcd_claim();
    if(dev == NULL) {
        printf(stderr, "can't get usblcd device\n");
        return -1;
    }

    /* установить видеорежим по-умолчанию */
    if(err = usblcd_setmode(dev, 0)) {
        fprintf(stderr, "usblcd_setmode() return: %d\n", err);
        return -1;
    }

    /* очищаем дисплей */
    if(err = usblcd_fillrect(dev, 0, 0, dev->mode[0].width, dev->mode[0].height, 0)) {
        fprintf(stderr, "usblcd_fillrect() return: %d\n", err);
        return -1;
    }

    /* включаем дисплей и подсветку */
    if(err = usblcd_setpower(dev, USBLCD_POWER_ON)) {
        fprintf(stderr, "usblcd_setpower() return: %d\n", err);
        return -1;
    }

    /* рисуем прямоугольник */
    if(err = usblcd_fillrect(dev, 8, 8, 16, 8, 1)) {
        fprintf(stderr, "usblcd_fillrect() return: %d\n", err);
        return -1;
    }

    /* освобождаем usblcd устройство */
    usblcd_unclaim(dev);

    return 0;
}

Подробней стоит рассказать о координатах и размерах, которые задаются в функциях usblcd_fillrect() и usblcd_bitblt(). Поскольку обе функции, фактически, занимаются пересылкой байтов в видеопамять, то при цветовом разрешении меньше 8-ми бит на пиксель указывать координаты и размеры с точностью до пикселя не представляется возможным. Таким образом, в текущей реализации, где один пиксель занимает один бит, координаты должны быть кратными 8-ми.

Perl-модуль.

В дистрибьютив USBLCD входит Perl модуль, который служит интерфейсом между драйвером и языком Perl. Думаю, многие со мной согласятся, что в некоторых случаях на Perl'е писать проще, чем на C. Главной "фишкой" данного модуля является то, что в качестве буффера с картинкой методу bitbltgd() передается объект GD::Image, в котором, предварительно, можно нарисовать все, что угодно.

Интерфейс модуля во многом похож и интерфесом C-шного драйвера и легко изучается по тестовой программе test.pl. Например, рисование прямоугольника может быть выполнено так:

#!/usr/bin/perl

use strict;

use GD;
use USBLCD;

# создаем GD::Image объект и рисуем в нем прямоугольник.
my $im = new GD::Image(16, 8);
my $black = $im->colorAllocate(0,0,0);
my $white = $im->colorAllocate(255, 255, 255);
$im->rectangle(0, 0, 15, 7, $white);

# создаем объект для работы с USBLCD
my $usblcd = new USBLCD;
die "Can't claim the USB LCD device.\n" unless defined $usblcd;

# получаем текущий видеорежим
my $mode_ref = $usblcd->getmode;

# очищаем экран
$usblcd->fillrect(0, 0, $mode_ref->{'width'}, $mode_ref->{'height'}, 0);

# поворачиваем экран на 90 градусов (ландшафтная ориентация)
$usblcd->setrotate(USBLCD_ROTATE90);

# включаем дисплей и подсветку
$usblcd->setpower(USBLCD_POWER_ON);

# выводим картинку
$usblcd->bitbltgd(24, 8, $im);

В архиве с исходными кодами вы, также, найдете полноценную программу на Perl: sysinfo.pl, которая выводит информацию о загруженности процессора и оперативной памяти, и рисует график.

Файлы.

Edit - History - Print - Recent Changes - Search
Page last modified on August 20, 2018, at 11:28 PM