21 нояб. 2010 г.

Пишем драйвер ЖКИ для стенда SDK1.1

В стенде SDK1.1 есть жидкокристаллический индикатор (ЖКИ), который можно использовать в качестве устройства вывода информации в своих программах. Вот только производитель не поставляет драйверов к SDK1.1 для этого устройства - нужно самим разбираться во внутреннем устройстве ЖКИ и самим писать для него драйвер.
Я уже успел сделать это и в данной статье будет рассмотрен мой драйвер ЖКИ, а также основные принципы работы с ЖКИ, знание которых позволит заинтересованным людям написать свой драйвер.
В статье не будет рассматриваться программная генерация символов - для вывода информации на экран я буду использовать только те символы, что уже "зашиты" в ПЗУ ЖКИ.

Для начала, рассмотрим внутреннее устройство жидкокристаллического индикатора, не затрагивая пока вопросы его сопряжения с SDK1.1.
Экран индикатора может вмещать в себя 32 символа - 2 строки по 16 символов каждая. Для вывода этих символов используется встроенный контроллер - программисту не нужно заботиться об особенностях работы жидкокристаллических экранов - ведь у него уже есть относительно удобный интерфейс.
Пользователю доступны два восьмибитных регистра - регистр команд (IR) и регистр данных (DR). В регистр команд должны записываться коды команд, предназначенных для выполнения, а в регистр данных - данные (коды символов и т.д.).
Например, если записать в DR код символа 'f', то на экране, на текущей позиции курсора, появится символ 'f' и счетчик, указывающий на позицию курсора, увеличится на единицу. Стоит отметить, что здесь под курсором понимается позиция на экране, в которую будет выводится символ при записи его кода в регистр DR.
Строго говоря, все коды символов помещаются из регистра DR во встроенную видеопамять ЖКИ, одна ячейка которой соответствует одному знакоместу на экране (адреса от 0x00 до 0x0f соответствуют верхней строке дисплея, а 0x40-0x4f нижней). А позиция курсора - это указатель на следующую после текущей ячейку видеопамяти.

Как видно на схеме, ЖКИ имеет всего одну 8-битную шину, поэтому нужны еще какие-нибудь сигналы, чтобы разделять между собой регистры IR/DR и производимое с ними действие (чтение/запись).

Этим целям служит регистровый переключатель (RS: 0 - IR,  1 - DR) и сигнал переключения на "чтение" или "запись" (R/W: 0 - запись, 1 - чтение). Кроме того, есть еще один сигнал - E, что значит Enable - данные на шине и сигналы RS и R/W защелкиваются и соответствующие процедуры внутри ЖКИ начинают выполняться.
Внутри устройства есть один важный флаг, состояние которого увы не выведено на отдельную ножку ЖКИ. Это флаг занятости (BF) и получить его состояние можно лишь при помощи специальной команды.

В итоге, "снаружи" ЖКИ выглядит примерно так:
(сигнал E не показан, но подразумевается)
Стрелочками здесь обозначены направления перемещения данных. Так например, новое значение счетчика адреса будет считываться управляющим микроконтроллером ЖКИ из IR и туда же оно будет помещаться при запросе на чтение значения этого счетчика.

Теперь рассмотрим, как можно управлять ЖКИ со стенда SDK1.1. Как видно, у индикатора есть лишь одна 8-битная шина данных и три управляющих сигнала (RS, R/W и E). Соответственно, в ПЛИС'е, к которой подключен ЖКИ, есть один восьмибитный регистр данных (DATA_IND), содержимое которого соответствует тому, что выставлено на шине данных. И есть еще один восьмибитный регистр управления (C_IND), используемыми в нем являются лишь первые три бита - они соответствуют значениям сигналов RS, R/W и E.
Все управление ЖКИ состоит в установке сигналов RS и R/W через регистр C_IND и установке необходимого значения в регистре данных DATA_IND согласно документации по командам ЖКИ. Например, для очистки экрана и возврата курсора в начальную позицию нужно выставить RS и R/W в 0 и в DATA_IND записать 0x01:

write2plis(DATA_IND_ADDR, 0x01);
write2plis(C_IND_ADDR, 0x01); /* E = 1 */
write2plis(C_IND_ADDR, 0x00); /* E = 0 */

Из документации на ЖКИ видно, что время, в течение которого сигнал E должен быть единичным, меньше среднего времени выполнения инструкции в микроконтроллере ADuC812. Поэтому, мы устанавливаем и "сразу же" обнуляем бит E в регистре C_IND.

Теперь уже можно перейти непосредственно к написанию драйвера ЖКИ. Нам потребуются следующие функции:
  • lcd_bfstate() - получение состояния флага занятости. До тех пор, пока флаг не сброшен, никаких иных действий с ЖКИ производить нельзя.
  • lcd_acstate() - получение текущего значения счетчика адреса для DDRAM. Если бы встроенным микроконтроллером ЖКИ учитывалось, что вторая строчка символов расположена в видеопамяти начиная с адреса 0x40, а не с 0x20, то эта функция была бы не нужна. А так, приходится отслеживать значение счетчика адреса и корректировать по необходимости.
  • lcd_set_ddram_addr() - установка нового значения счетчика адреса для DDRAM.
  • lcd_clear() - очистка экрана ЖКИ. Нужна, по меньшей мере, при старте программы, чтобы стереть с экрана надпись "SDK1.1 Waiting..." помещенную туда загрузчиком.
  • lcd_putchar() - вывод символа на экран
  • lcd_puts() - вывод строки на экран. Если строка не укладывается в 32 символа, то печатается лишь ее часть.

/**
 * @brief Значение BF
 *
 * Функция возвращает значение флага занятости - BF
 */
unsigned char lcd_bfstate() {
    unsigned char retval = 0x00;

    write2plis(C_IND_ADDR, 0x03); /* RS=0 RW=1 E=1 */
    retval = read_from_plis(DATA_IND_ADDR);
    write2plis(C_IND_ADDR, 0x02); /* RS=0 RW=1 E=0 */
    retval = retval >> 7;

    return retval;
}

/**
 * @brief Значение AC
 *
 * Функция возвращает значение счетчика адреса для DDRAM
 */
unsigned char lcd_acstate() {
    unsigned char retval = 0x00;

    write2plis(C_IND_ADDR, 0x03); /* RS=0 RW=1 E=1 */
    retval = read_from_plis(DATA_IND_ADDR);
    write2plis(C_IND_ADDR, 0x02); /* RS=0 RW=1 E=0 */
    retval = retval & 0x7f;

    return retval;
}

/**
 * @brief Установка адреса в DDRAM
 */
void lcd_set_ddram_addr(const unsigned char addr) {
    while(lcd_bfstate()) {}
    write2plis(DATA_IND_ADDR, addr | 0x80);
    write2plis(C_IND_ADDR, 0x01);
    write2plis(C_IND_ADDR, 0x00);
    return;
}

/**
 * @brief Функция для очистки экрана ЖКИ
 *
 * В данной функции происходит очистка экрана и 
 * возврат курсора в начало строки.
 */
void lcd_clear() {
    while(lcd_bfstate()) {}
    write2plis(DATA_IND_ADDR, 0x01);
    write2plis(C_IND_ADDR, 0x01);
    write2plis(C_IND_ADDR, 0x00);

    return;
}

/**
 * @brief Вывод символа на дисплей
 */
void lcd_putchar(const char c) {
    unsigned char ddram_addr = lcd_acstate();

    /* Контроль за позицией курсора */
    if ((ddram_addr > 0x0f) && (ddram_addr < 0x40)) {
        lcd_set_ddram_addr(0x40);
    }
    while(lcd_bfstate()) {}
    write2plis(DATA_IND_ADDR, c);
    write2plis(C_IND_ADDR, 0x05);
    write2plis(C_IND_ADDR, 0x04);
    return;
}

/**
 * @brief Вывод строки на дисплей
 *
 * Данная функция выводит С-строку символов на дисплей. 
 * Если строка "не влезает" в ЖКИ, то печатается лишь ее часть
 */
void lcd_puts(const char * s) {
    unsigned char wr_chars = 0;

    while ((s[wr_chars] != '\0') && (wr_chars <= 32)) {
        lcd_putchar(s[wr_chars++]);
    }

    return;
}
Как видно, в коде драйвера отсутствует функция для инициализации ЖКИ -  все уже сделано загрузчиком за нас и до нас.
Осталась нерешенной лишь одна проблема - вывод русских символов на экран ЖКИ. Этот девайс использует какую-то свою таблицу символов - так, мне не удалось найти кодировку, в которой символу 'б' соответствовал бы код 0xb2. Конечно можно написать свою функцию перекодировки строк, которая будет менять код каждого кирилллического символа. Но тут встает вопрос разнообразия используемых кодировок на машинах, на которых могут компилировать проект; кроме того, в случае использования UTF8 кириллический символ занимает вообще 2 байта в стандартной C-строке.
Конечно, можно написать свою реализацию этой функции, заточенную только под CP1251, но переносимость подобного кода оставляет желать лучшего...
Этот вопрос оставлен мной в долгом ящике до лучших времен. В получившемся драйвере работает вывод лишь латинских символов и знаков препинания на экран.

Скачать архив с драйвером ЖКИ (hdblog-lsd.tar.gz 5.22 Кб)