Русский текст на дисплее с контроллером HD44780 и японским знакогенератором

    Нестандартный способ управления дисплеем на контроллере HD44780 для отрисовки русских шрифтов при любой собственной кодовой таблице дисплея.


    Сама идея такого управления родилась из задачи русификации устройства с кастомным дисплеем на основе контроллера HD44780. Рассмотрев вариант с подменой русских символов на похожие по начертанию латинские, решил с этим не связываться, ибо выглядит это нетоварно, а устройство коммерческое.

    В отличие от OLED дисплеев на базе драйвера WS0010 от Winstar, дисплеи на драйверах HD44780 или их аналогах не имеют графического режима работы. Но имеют так называемую CGRAM память, в которую можно записать до 8 символов в графическом представлении.

    Сам алгоритм работы получается довольно простым:
    1. Запускаем таймер отрисовки на приемлемую по нагрузке на процессор частоту прерываний.
    2. В прерывании таймера:
      • стираем(заполняем пробелами) всё что было на дисплее;
      • загружаем требуемую графическую информацию в первые 8 знакомест CGRAM памяти контроллера;
      • выводим эти символы на дисплей по заданному смещению и выходим из прерывания.



    Код выглядит приблизительно вот так:
    // Cham map 0x80 - 0xFF, with filler
    uint8_t cmap[] = {
      0x04, 0x0a, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00, // russian A
      0x1f, 0x10, 0x10, 0x1e, 0x11, 0x11, 0x1e, 0x00, // russian Be
      0x1e, 0x11, 0x11, 0x1e, 0x11, 0x11, 0x1e, 0x0,  // russian Ve
      0x1f, 0x10, 0x10, 0x10, 0x10, 0x10, 0x10, 0x00, // russian Ge
      0x0e, 0x0a, 0x0a, 0x0a, 0x0a, 0x1f, 0x11, 0x00, // russian De
      0x1f, 0x10, 0x10, 0x1e, 0x10, 0x10, 0x1f, 0x00, // russian E
      0x15, 0x15, 0x15, 0x0e, 0x15, 0x15, 0x15, 0x00, // russian Zhe
      0x0e, 0x11, 0x01, 0x06, 0x01, 0x11, 0x0e, 0x00, // russian Ze
      0x11, 0x11, 0x11, 0x13, 0x15, 0x19, 0x11, 0x00, // russian I
      0x0e, 0x00, 0x11, 0x13, 0x15, 0x19, 0x11, 0x00, // russian Ij
      0x11, 0x12, 0x14, 0x18, 0x14, 0x12, 0x11, 0x00, // russian Ka
      0x07, 0x09, 0x09, 0x09, 0x09, 0x09, 0x11, 0x00, // russian L
      0x11, 0x11, 0x1b, 0x15, 0x15, 0x11, 0x11, 0x00, // russian M
      0x11, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x11, 0x00, // russian N
      0x0e, 0x11, 0x11, 0x11, 0x11, 0x11, 0x0e, 0x00, // russian O
      0x1f, 0x11, 0x11, 0x11, 0x11, 0x11, 0x11, 0x00, // russian Pe
      0x1e, 0x11, 0x11, 0x11, 0x1e, 0x10, 0x10, 0x00, // russian Re
      0x0e, 0x11, 0x10, 0x10, 0x10, 0x11, 0x0e, 0x00, // russian Se
      0x1f, 0x04, 0x04, 0x04, 0x04, 0x04, 0x04, 0x00, // russian Te
      0x11, 0x11, 0x11, 0x0f, 0x01, 0x11, 0x0e, 0x00, // russian U
      0x0e, 0x15, 0x15, 0x15, 0x0e, 0x04, 0x04, 0x00, // russian Fe
      0x11, 0x0a, 0x04, 0x04, 0x0a, 0x0a, 0x11, 0x00, // russian He
      0x12, 0x12, 0x12, 0x12, 0x12, 0x1f, 0x01, 0x00, // russian Ce
      0x11, 0x11, 0x11, 0x0f, 0x01, 0x01, 0x01, 0x00, // russian Che
      0x11, 0x15, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x00, // russian She
      0x11, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x01, 0x00, // russian Sche
      0x18, 0x08, 0x08, 0x0e, 0x09, 0x09, 0x0e, 0x00, // russian Tverduy znak
      0x11, 0x11, 0x19, 0x15, 0x15, 0x15, 0x19, 0x00, // russian bI
      0x10, 0x10, 0x1e, 0x11, 0x11, 0x11, 0x1e, 0x00, // russian Myagkiy znak
      0x1e, 0x01, 0x01, 0x0f, 0x01, 0x01, 0x1e, 0x00, // russian Ee
      0x17, 0x15, 0x15, 0x1d, 0x15, 0x15, 0x17, 0x00, // russian You
      0x0f, 0x11, 0x11, 0x0f, 0x05, 0x09, 0x11, 0x00, // russian Ya
      //
      0x00, 0x00, 0x0e, 0x01, 0x0f, 0x11, 0x0f, 0x00, // russian small A
      0x01, 0x0e, 0x10, 0x1e, 0x11, 0x11, 0x0e, 0x00,
      0x00, 0x00, 0x1e, 0x11, 0x1e, 0x11, 0x1e, 0x00,
      0x00, 0x00, 0x1e, 0x10, 0x10, 0x10, 0x10, 0x00,
      0x00, 0x00, 0x0e, 0x0a, 0x0a, 0x1f, 0x11, 0x00,
      0x00, 0x00, 0x0e, 0x11, 0x1e, 0x10, 0x0e, 0x00,
      0x00, 0x00, 0x15, 0x15, 0x0e, 0x15, 0x15, 0x00,
      0x00, 0x00, 0x1e, 0x01, 0x0e, 0x01, 0x1e, 0x00,
      0x00, 0x00, 0x11, 0x13, 0x15, 0x19, 0x11, 0x00,
      0x0e, 0x00, 0x11, 0x13, 0x15, 0x19, 0x11, 0x00,
      0x00, 0x00, 0x09, 0x0a, 0x0c, 0x0a, 0x09, 0x00,
      0x00, 0x00, 0x07, 0x09, 0x09, 0x09, 0x11, 0x00,
      0x00, 0x00, 0x11, 0x1b, 0x15, 0x11, 0x11, 0x00,
      0x00, 0x00, 0x11, 0x11, 0x1f, 0x11, 0x11, 0x00,
      0x00, 0x00, 0x0e, 0x11, 0x11, 0x11, 0x0e, 0x00,
      0x00, 0x00, 0x1f, 0x11, 0x11, 0x11, 0x11, 0x00, // russian small Pe
      //
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      //
      0x00, 0x00, 0x1e, 0x11, 0x11, 0x1e, 0x10, 0x00, // russian small Re
      0x00, 0x00, 0x0e, 0x11, 0x10, 0x11, 0x0e, 0x00,
      0x00, 0x00, 0x1f, 0x04, 0x04, 0x04, 0x04, 0x00,
      0x00, 0x00, 0x11, 0x11, 0x0f, 0x01, 0x0e, 0x00,
      0x00, 0x00, 0x1f, 0x15, 0x15, 0x1f, 0x04, 0x00,
      0x00, 0x00, 0x11, 0x0a, 0x04, 0x0a, 0x11, 0x00,
      0x00, 0x00, 0x12, 0x12, 0x12, 0x1f, 0x01, 0x00,
      0x00, 0x00, 0x11, 0x11, 0x0f, 0x01, 0x01, 0x00,
      0x00, 0x00, 0x15, 0x15, 0x15, 0x15, 0x1f, 0x00,
      0x00, 0x00, 0x15, 0x15, 0x15, 0x1f, 0x01, 0x00,
      0x00, 0x00, 0x18, 0x0e, 0x09, 0x09, 0x0e, 0x00,
      0x00, 0x00, 0x11, 0x11, 0x1d, 0x15, 0x1d, 0x00,
      0x00, 0x00, 0x10, 0x1e, 0x11, 0x11, 0x1e, 0x00,
      0x00, 0x00, 0x1e, 0x01, 0x0f, 0x01, 0x1e, 0x00,
      0x00, 0x00, 0x17, 0x15, 0x1d, 0x15, 0x17, 0x00,
      0x00, 0x00, 0x0d, 0x13, 0x0f, 0x05, 0x09, 0x00, // russian small Ya
      0x0a, 0x00, 0x1f, 0x10, 0x1e, 0x10, 0x1f, 0x00, // russian Yo
      0x0a, 0x00, 0x0e, 0x11, 0x1e, 0x10, 0x0f, 0x00, // russian small Yo
      //
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00, // filler
      0x00, 0x04, 0x0a, 0x15, 0x0a, 0x04, 0x00, 0x00  // filler
    };
    
    
    
    static uint8_t lcd_draw_part = 0;
    static uint8_t lcd_ch;
    static uint8_t lcd_draw_string[16] = { 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
                                           0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87 };
    
    
    
    void TIM4_IRQHandler() 
    {
      if (TIM_GetITStatus(TIM4, TIM_IT_Update) != RESET)
      {
        TIM_ClearITPendingBit(TIM4, TIM_IT_Update);
    
        // стираем то что светили ранее, part = 0 - 0..7, part = 1 - 8..15
        LCD_gotoxy(0, (lcd_draw_part ? 0 : 8));   
        LCD_write_data(0x20);
        LCD_write_data(0x20);
        LCD_write_data(0x20);
        LCD_write_data(0x20);
        LCD_write_data(0x20);
        LCD_write_data(0x20);
        LCD_write_data(0x20);
        LCD_write_data(0x20);
      
        // загружаем в CGRAM до 8 символов
        for(uint8_t i = 0; i < 8; i++)
        {
          lcd_ch = lcd_draw_string[i + (lcd_draw_part ? 8 : 0)];
          if(lcd_ch > 127)
            LCD_load_symbol(i, cmap + 8 * (lcd_ch & 0x7f) );
        }
        // рисуем
        LCD_gotoxy(0, (lcd_draw_part ? 8 : 0));
        for(uint8_t i = 0; i < 8; i++)
        {
          lcd_ch = lcd_draw_string[i + (lcd_draw_part ? 8 : 0)];
         if(lcd_ch < 128)
            LCD_write_data(lcd_ch);
          else
            LCD_write_data(i);
        }
        
        lcd_draw_part ^= 0x01;
      }
    }
    
    void LCD_start_fps()
    {
      RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
      TIM_TimeBaseInitTypeDef timerInitStructure;
      timerInitStructure.TIM_Prescaler = 48-1;
      timerInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
      timerInitStructure.TIM_Period = 20000;                      //  20ms/isr
      timerInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
      timerInitStructure.TIM_RepetitionCounter = 0;
      TIM_TimeBaseInit(TIM4, &timerInitStructure);
      TIM_Cmd(TIM4, ENABLE);
      TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
      
      NVIC_InitTypeDef nvicStructure;
      nvicStructure.NVIC_IRQChannel = TIM4_IRQn;
      nvicStructure.NVIC_IRQChannelPreemptionPriority = 0;
      nvicStructure.NVIC_IRQChannelSubPriority = 2;
      nvicStructure.NVIC_IRQChannelCmd = ENABLE;
      NVIC_Init(&nvicStructure);
    }
    



    В данном случае очень важно правильно настроить управление дисплеем, и использовать близкие к минимальным тайминги обмена, а также по возможности 8-битный интерфейс к дисплею(хотя по тестам и 4хбитный не сильно нагружает контроллер). Также необходимо использовать чтение сигнала BUSY по линии D7 из дисплея, для сокращения времени ожидания его реакции.

    Как это выглядит на реальном дисплее можно посмотреть на видео ниже:



    В верхняя строка управляется методом быстрой перерисовки и в ней отображается информация из CGRAM. В строке проходят графически отрисованные символы из кодовой таблицы CP866 с кодами 0x80-0xFF, псевдографика заменена на шахматные ромбики. В нижней строке символы встроенного знакогенератора дисплея и рядом их коды.

    Недостатки способа:
    При частоте обновления ниже 25Гц, на экране при взгляде под углом заметно мерцание, при прямом взгляде вертикально его не видно.
    Также чуть падает контрастность картинки с дисплея, но это почти не заметно.
    Поделиться публикацией

    Похожие публикации

    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 12

      +1
      Думал как-то в сторону руссификации этого дисплея. Мне пришла только в голову идея пожертвовать прописными буквами. С заглавными же поступить следующим образом, та часть, что совпадает по написанию с латинским алфавитом, подменять на символы из последнего. Та же часть, что не встречается в знакогенераторе дисплея, записывать ему в память в зависимости от того, какие символы сейчас необходимы. Уж не знаю, на сколько вероятно в двух строках по 16 символов использовать все символы с оригинальном начертанием, но если не планируется выводить какой-то текст из вне, то всякие менюшки можно разрабатывать с учётом такого ограничения.
      В итоге до реализации так и не дошёл, т.к. не придумал, зачем мне нужен этот дисплей, да ещё и обязательно с русским языком.
        0
        Кстати да! Начертания более десятка заглавных и несколько строчных букв кириллицы можно найти в латинице.
        Мне кажется, чуть усложнив код, можно было бы уменьшить мерцание, ибо более чем в половине случаев оставшиеся буквы укладывались бы в 8 символов.
          0
          Да, в интернете множество библиотек готовых, которые используют 8 загружаемых символов + похожие начертания, но это выглядит очень отвратительно., если нужны все буквы, и текст может быть любым.
          В случае использования такого хака с перерисовкой, выводить можно полностью свой шрифт и даже графику.
            0
            Ну графика — допустим.
            Но вы же сами жалуетесь на некоторое мерцание.
            А здесь — бесплатный способ его уменьшить. Разве нет?
              0
              Как вариант да. Вот например готовый ваш способ
              Просто мой внутренний перфекционист решил, что мерцание меньшее зло, по сравнению с кривыми символами.
                0
                Но я же не предлагаю кривых символов. Я предлагаю совместить подходы!
                Например, сейчас, для отрисовки всего дисплея с русскими буквами вам потребуется 4 кадра, а если использовать частично латиницу (только совпадающие буквы, никаких компромиссов!) — вполне вероятно, что можно уместиться в 3, а то и в 2!
        0
        Можно пасьянсы раскладывать. Результат примерно такой же — тренировка мозга.
          0
          На каком контроллере 25Гц получается?
          Если у меня будут обработки других прерываний или блоков непрерываемой обработки будет мигание?
            0
            — На любом контроллере. Лишь бы успевал. Конкретно я пробовал на stm32f373/48MHz и ATMEGA328/16MHz. Продукция ST намного удобнее в плане настраиваемого контроллера и вложенных прерываний, да и таймеров побольше.
            — Если работа в других прерываниях занимает всё свободное время контроллера, то обязательно увеличится мерцание. Если нужно ещё больше освободить время под другие процессы, и есть контроллер с высокой тактовой частотой, то можно сделать прерывание на каждый фронт синхросигнала E дисплея.
            0

            Скажите, у вас на нем глюков в виде задержки после инициализации, перед работой и при очистке дисплея нет?

              0
              Очистка дисплея происходит не медленной командой очистки, а записью 0x20(пробела). А глюков с инициализацией никогда не встречал на 44780-подобных. На графических бывало.
                0

                У меня просто дисплей от мэлта, и там после инициализации надо 100 миллисекунд где-то выждать, но в даташите об этом ни слова. Я случайно наткнулся… а все библиотеки от ардуино нормально с ним работают, вот и думаю-это глюк или как?


                Я имел ввиду команду полной очистки дисплея, мой просто после неё перестаёт отвечать на комманды, приходится переинициализировать каждый раз...

            Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

            Самое читаемое