Начну с предыстории, зачем же мне все это нужно. Я задался целью сделать себе HTPC компьютер на базе корпуса от DVD плеера Daewoo DV-500, внешне он мне нравится, и свободного места в нем достаточно для установки необходимого железа внутрь. Но помимо всего, я задумал оставить родной индикатор и задействовать его для отображения различной информации. О том, как я подключал дисплей к микроконтроллеру и расшифровывал протокол обмена этого дисплея, пойдет в этой статье.
Такую работу надо начинать с поиска информации, в первую очередь я нашел схему плеера, проблем с этим, на мое удивление, не возникло. По схеме я смог понять с чем предстоит работать, но найти описания на контроллер дисплея сразу не получилось, т.к. на схеме он подписан неправильно. К сожалению, документацию на контроллер я нашел в последний момент, случайно, но к этому времени я практически полностью разобрал протокол.
Итак, что же мы имеем:
Мы имеем контроллер VFD дисплея NEC D16312 (обратите внимание как подписан контроллер на схеме, не удивительно что я сразу не смог найти о нем инфу), с подключенными к нему VFD дисплеем, клавиатурной матрицей и светодиодом. С «внешним миром» контроллер подсоединятся через последовательный интерфейс посредством разъема CN1.
Для перехвата сообщений от контроллера основной платы в контроллер дисплея я буду использовать логический анализатор. Я, например, пользуюсь китайским клоном USBee AX PRO, свои ф-ции он выполняет на отлично и стоит недорого.
Подключаем выводы Data, Clk и Cs к любым трем выводам логического анализатора, не забываем также соединить землю. Далее, соединяем анализатор по USB с компьютером и запускаем USBee Suite (я опускаю процесс установки драйверов и программного обеспечения для логического анализатора, это выходит за рамки статьи). В настройках Speed and Samples ставим следущие параметры: Sample Rate — 2Msps, Buffer Size — 10M samples. Этого будет достаточно чтоб захватить кадр обмена размером в 5 секунд.
Дальнейшие мои действия таковы, делаю единичный захват (кнопка Capture Once) и сразу-же включаю плеер. Как только появилась первая информация на экране — выключаю захват. После проделывания таких действий для различных вариантов отображаемого текста, я стал анализировать полученную информацию.
Итак, какие же имеются закономерности во всех посылках команд. Во-первых, что бросилось в глаза сразу, это периодичность посылки команды с кодом 0x42 и тремя пустыми байтами после нее. Команда имеет строгую периодичность появления даже когда видеоплеер в состоянии покоя. Я предположил, что это команда опроса клавиатуры, проверить теорию очень легко, зажимаю любую кнопку на плеере и делаю захват кадра в программе.
Сразу же после байта 0x42 появился не нулевой байт, а раньше был 0! Таким образом 0x42 — команда опроса клавиатуры, которая отсылается контроллером основной платы в контроллер дисплея и в ответ на эту команду контроллер дисплея отвечает кодом нажатой клавиши (или 0 если не нажата ни одна).
Следующей на очереди стала команда с кодом 0x40, она появлялась только когда на экран выводилась информация. После нее всегда идут два байта, первый байт всегда начинается с 0xCX, где X изменяющееся значение. Третий байт имеет произвольные значения, но главное что при первом включении он всегда равен 0
На этом скриншоте видно что второй байт растет с каждой посылкой группы 0x40, скорее всего он задает адрес символа и третьим байтом записывается значение символа. В данном случае 0 очищает символ. Всегда после отсылки всех групп с кодом 0x40 последует два байта 0x02 0x8F.
Ну и последней группой данных является команда, начинающаяся с кодом 0x41 и последующим нулевым байтом после нее. Команда появляется только при первом включении видеоплеера.
Анализ данных вырисовал более-менее понятную картинку работы контроллера, осталось только проверить все на практике. Я использовал отладочную платку LPCXpresso, полученною мною «нахаляву» в рамках какого-то конкурса от NXP. Платка оснащена простеньким 32х битным контроллером LPC1114. Т.к. контроллер питается напряжением 3.3В, а контроллер дисплея D16312 от 5ти вольт, то соединять их выводы напрямую я не решился. Возможно, выводы микроконтроллера толерантны к напряжению 5В, но у меня в наличии имелась платка согласования уровней 3.3-5В и я использовал ее.
Итак, из разъема, идущего к основной плате, извлекаем 3 провода, отвечающие за пины Data, Cs и Clk соответственно пины 1, 2 и 3. К плате контроллера дислея припаиваемся проводами к точкам соединения Data, Clk и Cs, а также нам понядобятся выводы GND и +5V VCC. На первое воемя, я присоединил извлеченные от основной платы три контакта с логическим анализатором, чтобы дебажить передаваемую информацию.
Вот так у меня выглядит содениение с отладочной платкой
Вся железная часть готова, пора приступать к софту. Для начала напишем программу, которая будет повторять полученные в ходе анализа, данные. Для LPCXpresso используется одноименная LPCXpresso IDE на базе Eclipse. Запускаем, указываем путь к нашему новому рабочему пространству и импортим в него 2 стандартных библиотеки CMSIS_CORE_LPC11xx и LPC11xx_cmsis2_Lib, они нам понадобятся для разработки. Далее создадим новый проект File->New->C/C++->LPCXpresso C Project далее LPC11 / LPC12 -> LPC11xx /… ->C Project. Задаем имя проекта и в следующем окне выбираем целевой контроллер, в моем случае это LPC1114/302. На следующем шаге в списке уже должна появиться библиотека CMSIS_CORE_LPC11xx, т.к. мы ее импортировали ранее. В окне задания DSP библиотеки ничего не меняем и на следующем шаге оставляем все по умолчанию, жмем Finish.
Добавляем следующий код в сгенеренный файл <имя проекта>.c
#ifdef __USE_CMSIS
#include "LPC11xx.h"
#include "clkconfig.h"
#include "gpio.h"
#endif
#include <cr_section_macros.h>
void Delay(int32_t ticks);
void BeginCommand(uint8_t cmd, uint8_t isSingle);
void EndCommand();
void Clock();
void WriteData(uint8_t data);
#define PORT 3
#define DATA_BIT 2
#define CLK_BIT 0
#define CS_BIT 1
int main(void)
{
GPIOInit();
// Set pins to output
GPIOSetDir(PORT, CLK_BIT, 1);
GPIOSetDir(PORT, CS_BIT, 1);
GPIOSetDir(PORT, DATA_BIT, 1);
while(1)
{
BeginCommand(0x40, 1);
BeginCommand(0xC0, 0);
WriteData(0xFF);
EndCommand();
BeginCommand(0x02, 1);
BeginCommand(0x8F, 1);
Delay(3000000);
}
return 0 ;
}
void Delay(int32_t ticks)
{
volatile int32_t i = ticks;
while(i--);
}
void BeginCommand(uint8_t cmd, uint8_t isSingle)
{
GPIOSetValue(PORT, CLK_BIT, 1);
GPIOSetValue(PORT, CS_BIT, 0);
Delay(10);
WriteData(cmd);
if (isSingle)
{
EndCommand();
}
}
void EndCommand()
{
GPIOSetValue(PORT, CLK_BIT, 1);
GPIOSetValue(PORT, CS_BIT, 1);
Delay(10);
}
void Clock()
{
Delay(10);
GPIOSetValue(PORT, CLK_BIT, 0);
Delay(10);
GPIOSetValue(PORT, CLK_BIT, 1);
}
void WriteData(uint8_t data)
{
GPIOSetDir(PORT, DATA_BIT, 1);
uint8_t i = 0;
for (i = 0; i < 8; ++i)
{
uint8_t isSet = (data & 0x01);
GPIOSetValue(PORT, DATA_BIT, isSet);
Clock();
data = data >> 1;
}
}
Здесь видим несколько ф-ций для работы с последовательным портом. В самом верху файла задаются настройки порта и пинов для данных, синхронизации и строба. В моем случае все висит на порту 3, пин 2 отвечает за сигнал Data, 0 — синхронизацию и 1 — стробирование.
Ф-ция BeginCommand отсылает команду в порт, у этой ф-ции имеется второй интересный параметр. Если присмотреться на графики сигналов выше, то можно увидеть что сигнал стробирования, выставляется в активный (низкий) уровень, перед посылкой данных, и меняется между двумя независимыми командами. Но не переключается, если после отправки команды должны передаваться данные
Так вот, второй параметр говорит что команда является атомарной если isSingle == 1. Для случая неатомарных команд предназначена ф-ция EndCommand, которую следует вызывать после отсылки данных.
Отсылка же данных выполняется в ф-ции WriteData, по очереди, бит-за-битом, начиная с младшего, передаем в пин Data информацию. Каждое выставление данных сопровождается сигналом синхронизации, ф-ция Clock генерирует его.
В ф-ции main сначала инициализируем работу с входами ввода/вывода, далее выставляем пины Data, Clk и Cs на вывод. В бесконечном цикле имитируем команды, которые были получены ранее на этапе анализа данных. Т.о. мы отправили в контроллер дисплея последовательность данных 0x40 0xC0 0xFF 0x02 0x8F, а контроллер ответил выводом _8
Т.о. было установлено что команда:
— 0x42 — отвечает за обработку клавиатуры. После ее отправки в контроллер дисплея нужно выставить порт Data на вход и прочитать с него данные, предварительно синхронизировав сигналом Clk
— 0x40 — команда записи данных в дисплей, после нее должна записываться команда указания адреса символа, а после указания адреса записываются данные
— 0x41 — это команда для управления светодиодом, после нее идет байт с данными какой светодиод должен быть включен или нет
Более подробную информацию вы можете найти в руководстве по данному контроллеру дисплея D16312, там расписаны как должны формироваться команды. Ну, а я оформил всю работу в небольшую библиотечку, которая лежит на гитхабе. Библиотека позволяет отображать текст, управлять спецсимволами, заполнять символ диска в процентном соотношении, читать клавиатуру, менять яркость отображения и управлять светодиодом. На этом все, надеюсь кому-то это чтиво будет полезным и интересным. А на закуску я оставлю видео, с демонстрацией работы библиотеки
Автор: infrapro