Шаг назад. Делаем дисплей для LCD4Linux
ChipTerm потихоньку пишется, но попутно проверяются возникающие идеи. Так в один из вечеров была предпринята попытка использования аппаратной платформы в качестве дисплея для LCD4Linux. При этом стояла цель использовать не терминальный режим, а некий «специализированный» (так как в терминальном режиме порой возникает неприятное мигание из-за прокрутки дисплея).
Опытным разработчикам заметка будет не интересна. Новичкам может пригодится как отправная точка для своего проекта.
Протокол обмена
Как и все велосипедостроители я ринулся разрабатывать свой протокол обмена, ориентированный на применяемые контроллер и дисплей, чтобы по максимуму использовать возможности. Но здравый смысл победил лень взяла свое. Ведь при создании своего протокола потребуется писать и драйвер для LCD4Linux. Занятие это пусть даже и простое, но требует самостоятельной сборки и отладки приложения. Но самое главное здесь то, что вы не сможете подключить дисплей к платформе с уже установленным пакетом LCD4Linux (например, роутеру на OpenWRT).
И вот, после недолгого изучения исходных кодов, были выбраны несколько кандидатов в протоколы для реализации. Самым простым оказался протокол LCDTerm (точнее только использовавшиеся от него команды). Про сам дисплей можно почитать на странице проекта LCDTerm.
Для общения драйвер LCD4Linux использует следующие посылки:
- 0x03 - очистить экран и установить позицию вывода в левый верхний угол;
- 0x12 cmd_byte - отправить команду на символьный дисплей;
- 0x14 data_byte - отправить данные на символьный дисплей.
А я не говорил что LCDTerm использует знакосинтезирующий индикатор совместимый с HD44780? В прочем об этом не сложно догадаться. Основная масса дисплеев используют знакосинтезирующие и графические индикаторы WinStar’а.
В общем, мы могли бы реализовать как весь функционал HD44780, так и весь функционал LCDTerm. Вычислительной мощности и памяти у нас хватает. Но зачем? Мы ставим перед собой определенную задачу: «подключиться к LCD4Linux, представившись LCDTerm». А для её реализации нам достаточно обработать только следующие запросы:
- очистка экрана;
- установка позиции для вывода;
- вывод символа на дисплей со смещением позиции вывода;
- установка образа символа в cgram.
«Схема» подключения
Схемы в обычном её представлении пока не предполагается, только в двух словах: что и куда подключить. Нам потребуется дисплей от Nokia 1202 и микроконтроллер STM8S103F3P (если в коде UART1 сменить на UART2, то можно взять и плату stm8s-discovery). Применение MAX232 или FTDI зависит от вас (куда вы будите подключать схему). Питать надо от 3.3В (дисплей не выдержит большее).
Дисплей выводом SCK подключается к порту PC5 микроконтроллера, DAT – к PC6, SEL – к PA3, RES – к PC4. За распиновкой как и раннее отправляю вас на ste2007 в wiki. Не смотря на то, что у микроконтроллера используются выводы с SPI функцией, я использую простой "ногодрыг". От UART’а же нам понадобится только вывод RX. Вот собственно и всё, кварцевый резонатор не нужен, но и не помешает. Не забудьте только стандартную обвязку из конденсаторов на питании.
Добавил самую простую схему подключения:
Описание программы
Так же кратко взглянем на код программы представленной в архиве ("AS IS", что хотите то и делайте, но я не при чем).
Начнем с начала (функция main):
int main( void )
{
CLK_CKDIVR = 0; // Отключаем (Fsys = Fosc = 16MHz)
control = DISPLAY_ON | CURSOR_MOVE | CURSOR_ON | CURSOR_BLINK;
address = 0x80;
UART_init();
// Run dispatcher as soon as possible
__enable_interrupt();
// HSE_init();
LCD_init();
while(1) {
HD44780_ScreenRedraw();
_delay_ms(10);
}
__disable_interrupt();
return 0;
}
Отключаем предделитель тактовой частоты для работы на всех 16МГц внутреннего RC-генератора. После чего функцией UART_init настраиваем UART на 115200 бод, 8 бит данных без четности и 1 стоп бит. Разрешаем работу и прерывания по приёму данных. Сразу же после этого разрешаем прерывания. Это позволит нам проводить приём и обработку посылок даже во время длительных операций по инициализации и перерисовки дисплея.
Для работы нам не требуется кварцевый резонатор, но желающие могут на него переключиться, использую закомментированный вызов функции HSE_init. Но как показала практика, стабильности встроенного RC-генератора вполне достаточно для нормальной работы (даже притом, что из-за 16МГц тактового сигнала у нас получаются не точные 115200 бод).
Далее следует длительная (~0.3сек) инициализация дисплея (функция LCD_init) и цикл постоянной перерисовки дисплея (функция HD44780_ScreenRedraw). Задержки не точные и реализованы программно функцией _delay_ms.
Перерисовка осуществляется последовательным выводом символовна экран (из массива DDRAM). Позиция в буфере очередного выводимого символа рассчитывается нехитрой формулой формирования адреса вывода для дисплея HD44780 (где i/16 ни что иное, как номер строки, а i%16 - номер столбца):
dataPos = ((i/16) % 2) * 64 + ((i/16) / 2) * 20 + (i%16);
Изображение символа берётся либо из встроенного шрифта (массив font), либо из задаваемых пользователем (массив CGRAM). Попутно выполняется прорисовка курсора (добавлением переменной add к изображению символа), что не обязательно, т.к. LCD4Linux делает курсор невидимым.
Я не формировал шрифт в кодировки HD44780, а просто использовал имеющийся windows-1251. Учитывайте при использовании частей данного проекта.
Так же выводится «обрамление экрана» (строка "-=LCDTerm emul=-" и горизонтальные полосы). Это просто заполнение пустого пространства дисплея. Возникновение такого пространства обусловлено способом адресации у знакосинтезирующих дисплеев. Мы ограничены только 6-ю строками (в оригинале дисплеи представляют максимум 4 строки). Ограничение в 16 символов в строке уже введено нами, т.к. на дисплей от Nokia 1202 не поместится больше. Это пространство в нормальном проекте можно использовать для вывода напряжение питания, индикаторов обмена данными, показаний температуры с подключенных датчиков и многое другое.
В теории можно было бы адресовать и 8 строк по 16 символов. Однако «особый» режим адресации с 16 символами в строке применяется только для дисплея 1604, и нам бы пришлось править имеющийся драйвер (чего мы хотим избежать).
Как я уже говорил, прием данных осуществляется в прерывании:
#pragma vector=UART1_R_RXNE_vector
__interrupt void UART1_R_RXNE_handler(void)
{
static unsigned char state = 0;
if(!(UART1_SR & MASK_UART1_SR_RXNE) )
{
return;
}
char c = UART1_DR;
// Implement used LCDTerm codes
switch(state) {
case 0:
if(c == 0x03)
{
HD44780_cmd(1);
}
else if(c == 0x12)
{
state = 0x12;
}
else if(c == 0x14)
{
state = 0x14;
}
break;
case 0x12:
HD44780_cmd(c);
state = 0;
break;
case 0x14:
HD44780_dat(c);
state = 0;
break;
default:
state = 0;
}
}
Тут и описывать не чего. Просто извлекаем очередной принятый байт и обрабатываем его. Для байта 0x03 выполняем сброс дисплея (аналогично приёму команды HD44780 с кодом 0x01). Для байт 0x12 и 0x14 передаем следующий байт для обработки как команды HD44780 или данных, соответственно. Всё остальное просто игнорируем (при работе с LCD4Linux остальное просто невозможно).
Обработку команд HD44780 производим функцией:
void HD44780_cmd(char c)
{
// TODO: dispatch HD44780 commands
if(c & 0x80) // DDRAM address
{
cursor = c & 0x7F;
address = 0;
}
else if(c & 0x40) // CGRAM address
{
address = c;
}
else if(c & 0x20) // function
{
}
else if(c & 0x10) // shift
{
}
else if(c & 0x08) // display
{
if(c & 0x04)
control |= DISPLAY_ON;
else
control &= ~DISPLAY_ON;
if(c & 0x02)
control |= CURSOR_ON;
else
control &= ~CURSOR_ON;
if(c & 0x01)
control |= CURSOR_BLINK;
else
control &= ~CURSOR_BLINK;
}
else if(c & 0x04) // entry mode
{
}
else if(c & 0x02) // home
{
address = 0x80;
}
else if(c & 0x01) // clear
{
HD44780_clear();
address = 0x80;
control |= INCREMENT_MODE;
}
else // nop
{
}
}
Как видно обрабатываем мы не все команды, а только необходимые. Бонусом обрабатывается и команда 0b00001XXX для установки курсора и дисплея, что в принципе излишне. Адреса для вывода символов и задания образа символа просто запоминаются в соответствующие переменные.
Данные для HD44780 обрабатываем функцией:
void HD44780_dat(char c) // Output char with code (any)
{
if(address & 0x40)
{
// put cgram
unsigned int ascii = (address & 0x3f) >> 3;
unsigned char bit = 1 << (address & 0x07);
unsigned char *ptr = &CGRAM[ascii * 5];
if(c & 0x10)
*ptr |= bit;
else
*ptr &= ~bit;
ptr++;
if(c & 0x08)
*ptr |= bit;
else
*ptr &= ~bit;
ptr++;
if(c & 0x04)
*ptr |= bit;
else
*ptr &= ~bit;
ptr++;
if(c & 0x02)
*ptr |= bit;
else
*ptr &= ~bit;
ptr++;
if(c & 0x01)
*ptr |= bit;
else
*ptr &= ~bit;
address = 0x40 | ((address + 1) & 0x3F);
}
else
{
// put ddram
DDRAM[cursor & 0x7F] = c;
cursor = (cursor + 1) & 0x7F;
}
}
Некоторого пояснения требует задание образа символа (запись в CGRAM). Поскольку HD44780 хранит символы построчно (сверху вниз 8 байт по 5 бит данных), а на дисплей от Nokia 1202 мы выводим образ по столбцам (слева направо 5 байт по 8 бит данных), то нам требуется принятый образ повернуть. Поворот осуществляем самым «прямым» способом, устанавливая/сбрасывая биты в соответствующих ячейках памяти. На оптимальность не претендуем, времени хватает с головой.
Вывод в DDRAM производится простым копированием принятого байта по ранее установленному адресу. «Перетасовка» строк осуществляется в функции перерисовки экрана.
В обоих случаях (приёма данных для CGRAM и DDRAM) производится безусловное увеличение соответствующего адреса (LCD4Linux устанавливает данный инкремент в команде, которую мы игнорируем).
Результат
В итоге получилось такое творение (кликабельно на 2.5МБ)
Все вопросы как обычно через контакты, либо в комменты в сообществе.
Файлы: lcdterm2.zip, lcdterm2.srec, DSC03209.JPG