Шаг назад. Делаем дисплей для LCD4Linux

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

Опытным разработчикам заметка будет не интересна. Новичкам может пригодится как отправная точка для своего проекта.

Протокол обмена

Как и все велосипедостроители я ринулся разрабатывать свой протокол обмена, ориентированный на применяемые контроллер и дисплей, чтобы по максимуму использовать возможности. Но здравый смысл победил лень взяла свое. Ведь при создании своего протокола потребуется писать и драйвер для LCD4Linux. Занятие это пусть даже и простое, но требует самостоятельной сборки и отладки приложения. Но самое главное здесь то, что вы не сможете подключить дисплей к платформе с уже установленным пакетом LCD4Linux (например, роутеру на OpenWRT).

И вот, после недолгого изучения исходных кодов, были выбраны несколько кандидатов в протоколы для реализации. Самым простым оказался протокол LCDTerm (точнее только использовавшиеся от него команды). Про сам дисплей можно почитать на странице проекта LCDTerm.

Для общения драйвер LCD4Linux использует следующие посылки:

  1. 0x03 - очистить экран и установить позицию вывода в левый верхний угол;
  2. 0x12 cmd_byte - отправить команду на символьный дисплей;
  3. 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МБ)
Пародия LCDTerm для LCD4Linux

Все вопросы как обычно через контакты, либо в комменты в сообществе.

Файлы: lcdterm2.zip, lcdterm2.srec, DSC03209.JPG