Универсальный драйвер LCD

Универсальный драйвер LCD

 dslev@yandex.ru                                    

                                Если ничто другое не помогает, прочтите наконец инструкцию!
                                                                                       Аксиома Кана и Орбена

Первая проблема LCD
Вторая проблема LCD
Третья проблема LCD
Тестирование

Читатель, открывший эту страницу,  может сказать: "… опять изобретение велосипеда, этих драйверов (библиотек) полно в Интернете". Не торопитесь, драйверов LCD много, но также много и сообщений о не работающих драйверах. В статье вы найдете решение нескольких проблем, препятствующих написанию своего драйвера. Меня настолько удивил этот простой, многократно описанный прибор, что я решил потратить некоторое время и поделится с читателями своими решениями. И кстати, в данном случае эпиграф оказался ложным, чтение инструкции не помогает.

Несколько лет назад мне пришлось заниматься разработкой модуля GSM сигнализации на основе микроконтроллера PIC24FJ256DA210. По завершению работ мне в качестве бонуса от фирмы заказчика достался   LCD BC1602ABNHCB$ в комплекте с драйвером (4-х битный) и словами "… он у нас не пошел". Первый LCD BC1602 был выполнен в виде готовой конструкции с клавиатурой,  а следовательно предполагался режим чтения, рисунок 2.  Второй LCD BC2402AGPLCH$ был приобретен у другой фирмы практически за бесценок и по аналогичной причине. Оба LCD с русской прошивкой, достаточно дорогие оригиналы фирмы Bolymin. Какого либо применения я им не нашел и они пролежали несколько лет.

Недавно решил добавить экран в модуль GSM сигнализации и использовать его в качестве охранной сигнализации автомобиля. Однако LCD BC1602A категорически не пожелал работать с подаренным драйвером. Второй LCD BC2402A также не стал работать с этим драйвером. Оба LCD при включении отображали результат внутреннего тестировании, были исправны.

Рисунок 1

Поиски драйвера в Интернете завершились закачкой нескольких, часто встречающихся экземпляров библиотек. Однако ни одна библиотека не стала работать с LCD. Желания переделывать чужой код не было никакого. Может эти библиотеки и работали с какими-то клонами неизвестных производителей, но с оригиналами фирмы Bolymin ничего не получалось. Возможно, что эти проблемы присущи только LCD с русской кодовой страницей, иначе трудно объяснить почему оригинальные LCD не работают с драйверами для LCD с англо-китайскими прошивками. Может быть контроллеры имеют разные версии. Пришлось писать свой драйвер. В процессе работы проявились не документированные особенности LCD без учета которых невозможно написать полноценный драйвер. Было решено написать универсальный драйвер, позволяющий легкую и простую настройку на основе нескольких функций, реализующих временные диаграммы LCD.

Рисунок 2

Основой драйвера (4-х битного режима) являются три функции, которые формируют временную диаграмму режимов записи, чтения и проверки занятости контроллера LCD. Эти функции учитывают недокументированные особенности LCD и  допускают легкую подстройку временных диаграмм. Функции могут служить основой для реализации драйвера для других типов микроконтроллеров.

 Примечание
Использовалась среда разработки MPLABX V3.2, программатор/отладчик ICD3, модуль на основе PIC24FJ256DA210, компилятор XC16 V1.35, логический анализатор.

Первая проблема LCD

Опираясь на временную диаграмму записи (рисунок 3) был написан код для режима записи данных.

Рисунок 3

 Если посмотреть на диаграмму передачи данных (DB0-DB7) на этом рисунке, то совершенно непонятно в каком состоянии должна быть шина данных после передачи. Кроме того эта диаграмма приведена для режима 8 битной передачи. Предполагая, что нужно просто повторить цикл передачи, а шина данных может быть в любом состоянии после завершения передачи и была написана функция, реализующая временную диаграмму. Единственное описание 4 битного режима, подтверждающее правильность предположения, встретилось в техописании на контроллер KS0066U, но и там временная диаграмма не отображала однозначного положения шины данных после завершения передачи. Драйвер не работал, LCD ничего не отображал, либо отображал, что попало не взирая любые попытки изменять времена сигналов.

Изыскания продолжались несколько дней, пока в Интернете я нашёл старую статью "Алфавитно-цифровые индицирующие ЖК-модули на основе контроллера HD44780". В статье было маленькое замечание, что на шине данных после передачи следует обязательно устанавливать высокий уровень. Это замечание и разрешило первую проблему. Ниже приводится код функции, реализующий временную диаграмму записи 4 бит данных (strob_write(uint8_t data, uint8_t flCmd) и функция записи одного символа, которая дважды вызывает функцию strob_write. Полная диаграмма записи с двумя стробами (Е) не реализована по следующей причине. Алгоритм инициализации  4 битного режима предполагает сначала передачу данных в 8 битном режиме с одиночным стробом E.

После запуска LCD в 8-ми битном режиме запуск LCD в 4-х битном режиме (запись) уже не являлся сложной задачей. Период следования двух стробов по условию производимеля должен быть не менее 500 ns. Реальный период виден на рисунке 3, его можно уменьшить до рекомендуемого, но это не повышает заметно производительность модуля. В эспериментах уменьшение периода до рекомендуемого вообще было не заметно для ускорения вывода полной строки. Далее приводится код формирования одного строба. Для микроконтроллера PIC24FJ256DA210 время выполнения однотактной операции 62,5 ns.

void strob_write(uint8_t data, uint8_t flCmd/* write command=0 */)
{
    DATA_OUT = data & 0x0f; // RS=0,RW=0,E=0, установка данных, запись команды

    if (flCmd == RG_CMD)
    { // команда
        E_Hi(); // строб >= 250 ns
        E_Hi();
        E_Hi();
        E_Hi();
        E_Lo();
    }

    if (flCmd == RG_DATA)
    {
        RSHi(); // режим записи данных (RS=1,RW=0,E=0)
        E_Hi();
        E_Hi(); // строб >= 250 ns
        E_Hi();
        E_Hi();
        E_Lo();
    }
    // >=250ns завершение строба, формирование периода E происходит за
    // счет повторной передачи данных (>= 1 мкс)

    DATA_OUT = 0x0f; // исходное состояние
}

Обратите внимание на установку исходного состояния по завершению функции. Шину управляющих сигналов следует устанавливать в нуль. Шина данных и сигналов управления подключена к одному порту.

uint8_t write_LCD(uint8_t v, uint8_t flCmd)
{
    if (is_busyAC() == 0xff)
        return 0xff;

    strob_write(v >> 4, flCmd); // hi bits
    strob_write(v & 0x0f, flCmd); // lo bits

    return 1;
}

В этой функции сначала вызывается функция, ожидающая освобождения контроллера. Ниже на рисунке 4 показана временная диаграмма режима логического анализатора.

Рисунок 4.

Вторая особенность касается команды чтения флага BF и адресного счетчика АС. Команда фактически не описана в техническом описании и для нее нет временной диаграммы. То, что видите на рисунке 5  и есть описание команды, можно встретить дополнительные описания, но их информативность такая же.

Рисунок 5.

В то же время для выполнения команды очень важно состояние строба Е, который должен быть установлен в единицу наряду с сигналами RS=0 и RW=1. Далее приведен код функции is_busyAC().

int8_t is_busyAC()
{
    BUS_CTRL(0, 1, 1); // установка состояния чтения флага BF (RS=0,WR=1,E=1)
    BUS_DATA_IN();
    uint8_t AC1, AC2;
    uint16_t noLCD = 0;

    // ждать освобождения LCD
    do
    { // защита от зависания при отключенном (неисправном) LCD
        if (noLCD > 65000) // > 300ms, ждать BF=0
        {
            noLCD = 0;
            return 0xff; // если LCD отключен, выход по ошибке
        }
        ++noLCD;
    }
    while (IS_BUSY == 1); // если LCD отключен, BF всегда 1

    // BF=0, 
    E_Lo(); // завершение 1-го строба
    E_Lo();
    E_Lo();

    // читаются старшие биты (4-7) АС, BF еще равен 0
    AC1 = DATA_READ;

    // 2 строб 125ns, по фронту этого строба выставляются младшие 4 бита AC.
    // Флаг  BF заменяется на данные
    E_Hi();
    E_Hi();
    E_Hi();
 
    E_Lo();

    //read AC, читаюся  биты 0-3
    AC2 = DATA_READ & 0x0f;

    // формируется код АС, это позиция вывода следующего символа
    AC1 >>= 4;
    AC1 |= AC2;
    BUS_DATA_OUT();
    DATA_OUT = 0x0f; // исходное состояние шины данных/сигналов

    return AC1;
}

Если LCD используется только для вывода и не предполагается взамодействие с пользователем, тогда можно ограничится частью функции (цикл  do). Иначе нужно прочитать младшие четыре бита счетчика адреса, которые устанавливаются по фронту второго строба. Далее по спаду второго строба читаются младшие биты и формируется код АС. На рисунке 6 показана временная диаграмма для функции is_BusyAC().

Рисунок 6.

Третья особенность касается чтения данных из памяти RAM. Снова обратимся к техническому описанию и посмотрим на временную диаграмму чтения.


Рисунок 7

Эта диаграмма совершенно не соответствует четырех битному режиму, скорее всего она соответствует восьми битному режиму. Я проштудировал пять технических описаний и нигде не нашел временной диаграммы или просто временных данных. Все источники приводят одни и те же рисунки и цифровые данные с не существенными отличиями для фронтов импульсов. Если использовать основные параметры, Т цикла 500 нсек и t строба 250 нсек, то никого положительного результата не удавалось получить. Проблема разрешилась подбором длительности строба и периода. Длительность строба составила 375 нсек, период 1375 нсек. Вся проблема заключается в том, что данные устанавливаются по фронтам стробов, и требуется больше 230 ns для уверенного чтения по спаду строба.Ниже приведена диаграмма логического анализатора, функция формирования строба чтения и функция чтения одного символа.

/******************************************************************************
 Description: 
 * Создает временную диаграмму чтения. Чтение требует выставлять более широкий 
 * строб для LCD BC2402. Настройку следует производить для полной строки.
 *******************************************************************************/
uint8_t strob_read()
{
    BUS_CTRL(1, 1, 0); // read, RS=1,RW=1,E=0
    
    // строб > 375 ns для BC2402
    int i;
    for (i = 0; i < 7; i++)
    {
       E_Hi(); // строб = 437.5 ns
    }

    E_Lo(); // период формируется за счет повторного вызова функции

    return (uint8_t) DATA_READ; // читаем порт E
}

/******************************************************************************
 Description:
 * Читает дважды порт Е
 Param: адрес памяти
 *******************************************************************************/
uint8_t read_LCD(uint8_t adr)
{
    uint8_t data, t;

    if (is_busyAC() == 0xff)
        return 0xff;

    BUS_DATA_IN();
    data = strob_read() << 4; // старшие 4 бита
    t = strob_read() & 0x0f; // младшие 4 бита
    data = data | t;

    BUS_DATA_OUT();
    DATA_OUT = 0x0f; // исходное состояние, E=0, RS=RW=0 

    return data;
}


Рисунок 8

Тестирование.
Выполняется два теста, для чистой латиницы и смешанного текста, киррилица и латиница. Каждый тест записывается в первую строку, буфер очищается, затем читается первая строка и переписывается во вторую строку.

    /*тест перезаписи из первой строки во вторую */
    wchar_t buff[25] = {L"QWERTY-8 9 йцукен 3 4"}; //
    LCD_write_wchar(buff, ADR_LINE_1, 0);
    memset(buff,0,25);
    E_MARK_1;
    __delay_us(2);
    E_MARK_0;
    LCD_read_wchar(buff, ADR_LINE_1, 16);
    E_MARK_1;
    __delay_us(2);
    E_MARK_0;
    LCD_write_wchar(buff, ADR_LINE_2, 0);
    /* тест перезаписи из первой строки во вторую 
    uint8_t buff[25] = {"QWERTY-8 9 10 11 1 2 3 4"}; //
    LCD_write_str(buff, ADR_LINE_1, 0);
    memset(buff,0,25);
    E_MARK_1;
    __delay_us(2);
    E_MARK_0;
    LCD_read_str((uint8_t*) buff, ADR_LINE_1, 16);
    E_MARK_1;
    __delay_us(2);
    E_MARK_0;
    LCD_write_str(buff, ADR_LINE_2, 0);
     */


Скачать файлы исходников и техописания или здесь. Статья драйвер I2C для LCD находится здесь.