Recent Changes - Search:

Главная страница

Загрузки

Домашний WiFi роутер

Проекты

Статьи

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

GitHub

SourceForge

edit SideBar

USBLCD

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

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

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

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

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

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

Цели.

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

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

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

Железо.

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

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

Все это было смонтированно на небольшой макетной плате. На отдельный DIP разъем были выведены сигналы для программирования микроконтроллера (на схеме не указан). Конденсаторы C1 и C2 нужно установить поближе к выводам микроконтроллера 7 (VCC), 8 (GND) и 20 (AVCC), 22 (AGND) соответственно.

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

Прошивка.

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

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

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

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

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

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

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

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

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

GETINFO

bmRequestTypebRequestwValuewIndexwLengthData
11000000bFFh0000h0000h0006h*t_usblcd_info

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

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

Как видно из стуктуры, количество светодиодов и ведиорежимов не может превышать 255.

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

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 - соответствует состоянию "выключено". Яркость растет пропорционально значению.

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

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

Адрес видеопамяти имеет длину 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 байт. Адрес и длину выравнивать по цветовому разрешению текущего видеорежима не нужно: мы работаем с байтами, а не пикселями. Формат данных зависит от установленного видеорежима.

Драйвер.

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

Для работы с 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 June 17, 2016, at 05:46 PM