Учебный курс. Подключение lcd к микроконтроллеру. Добавляем поддержку 4-ех разрядной шины

19/12/2009 - 00:24

Схема

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

Запись команд/данных

Операция записи для 4-ех разрядной шины выглядит так:

1.    установить RS (1- данные , 0 - команды)
2.    вывести старшую тетраду байта данных на линии шины DB4 – DB7
3.    установить E = 1
4.    программная задержка 1
5.    установить E = 0
6.    вывести младшую тетраду байта данных на линии шины DB4 – DB7
7.    установить E = 1
8.    программная задержка 1
9.    установить E = 0
10.    программная задержка 2

Ничего сложного и можно “в лоб” написать, функцию записи команд.
 
void LCD_WriteCom(unsigned char data)
{
  ClearBit(PORT_SIG, RS);   //RS  = 0

  PORT_DATA = data;            //выводим старшую тетраду        
  SetBit(PORT_SIG, EN);                    
  _delay_us(2);
  ClearBit(PORT_SIG, EN);    

  data = __swap_nibbles(data); //меняем тетрады местами
   PORT_DATA = data;             //выводим младшую тетраду       
  SetBit(PORT_SIG, EN);            
  _delay_us(2);
  ClearBit(PORT_SIG, EN);
  _delay_us(40);
}

unsigned char  __swap_nibbles(unsigned char data) – это встроенная IARовская функция, она меняет 0-3 и 4-7 биты переданного ей байта местами. Эквивалент ассемблерной инструкции swap.     
 
   Функция полностью соответствует описанной выше последовательности, но она не совсем правильная. Старшая часть порта микроконтроллера (7-4 биты) используется как шина данных, а к младшей могут быть подключены управляющие выводы lcd или что-нибудь еще. Когда функция записывает в порт данные, она  повреждает  его младшую часть, а этого не должно происходить. Давайте это исправим.


void LCD_WriteCom(unsigned char data)
{
  ClearBit(PORT_SIG, RS);   //RS  = 0

  unsigned char tmp;
  tmp  = PORT_DATA & 0x0f;
  tmp |= (data & 0xf0);

  PORT_DATA = tmp;            //выводим старшую тетраду        
  SetBit(PORT_SIG, EN);                    
  _delay_us(2);
  ClearBit(PORT_SIG, EN);    

  data =
__swap_nibbles(data);  //меняем тетрады местами
  tmp  = PORT_DATA & 0x0f;
  tmp |= (data & 0xf0);                

   PORT_DATA = data;             //выводим младшую тетраду       
  SetBit(PORT_SIG, EN);            
  _delay_us(2);
  ClearBit(PORT_SIG, EN);
  _delay_us(40);
}

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

В хедер добавляем

#define BUS_4BIT

В сишный файл

#pragma inline = forced
void LCD_CommonFunc(unsigned char data)
{
#ifdef BUS_4BIT
    //код для 4-ех разрядной шины
  unsigned char tmp;
  tmp  = PORT_DATA & 0x0f;
  tmp |= (data & 0xf0);

  PORT_DATA = tmp;                
  SetBit(PORT_SIG, EN);                    
  _delay_us(2);
  ClearBit(PORT_SIG, EN);    

  data = __swap_nibbles(data);  
  tmp  = PORT_DATA & 0x0f;
  tmp |= (data & 0xf0);                

   PORT_DATA = data;             
  SetBit(PORT_SIG, EN);            
  _delay_us(2);
  ClearBit(PORT_SIG, EN);
#else
    //код для 8-ми разрядной шины
       PORT_DATA = data;    
       SetBit(PORT_SIG, EN);    
       _delay_us(2);
       ClearBit(PORT_SIG, EN);    
#endif
}

  Мы использовали директивы условной компиляции и теперь в любой момент можем конфигурировать нашу библиотеку под разный тип подключения. Если в хедер файле мы закомментрируем строчку #define BUS_4BIT, компилироваться будет код расположенный между #else#endif, если оставим ее, то компилироваться будет код между #ifdef BUS_4BIT … #else.

Инициализация lcd

   Функции инициализации lcd для 8-ми и 4-ех разрядных шин почти не отличаются. Разница лишь в конфигурировании порта и первой команде - Function set. Эта команда задает разрядность шины, количество строк дисплея и шрифт. Согласно описанию на контроллер ks0066 - для 4-ех разрядной шины команду Function set нужно посылать дважды. Причем первый раз посылается только половина команды.
   Как показала практика, можно обойтись и однократной подачей команды Function set, что я обычно и делаю.

  DDRX_DATA |= 0xf0;  
  PORT_DATA |= 0xf0;
  DDRX_SIG |= (1<<RW)|(1<<RS)|(1<<EN);
  PORT_SIG |= (1<<RW)|(1<<RS)|(1<<EN);
  ClearBit(PORT_SIG, RW);
  _delay_ms(40);
  LCD_WriteCom(0x28); //4-ех разрядная шина, 2 строки
  LCD_WriteCom(0xf);  //0b00001111 - дисплей, курсор, мерцание включены
  LCD_WriteCom(0x1);  //0b00000001 - очистка дисплея
  _delay_ms(2);
  LCD_WriteCom(0x6);  //0b00000110 - курсор движется вправо, сдвига нет

А так будет выглядеть исправленная функции инициализации нашей библиотеки.

void LCD_Init(void)
{
#ifdef BUS_4BIT
  DDRX_DATA |= 0xf0;
  PORT_DATA |= 0xf0;
  DDRX_SIG |= (1<<RW)|(1<<RS)|(1<<EN);
  PORT_SIG |= (1<<RW)|(1<<RS)|(1<<EN);
  ClearBit(PORT_SIG, RW);
  _delay_ms(40);
  LCD_WriteCom(0x28); //4-ех разрядная шина, 2 строки
#else
  DDRX_DATA |= 0xff;
  PORT_DATA |= 0xff;
  DDRX_SIG |= (1<<RW)|(1<<RS)|(1<<EN);
  PORT_SIG |= (1<<RW)|(1<<RS)|(1<<EN);
  ClearBit(PORT_SIG, RW);
  _delay_ms(40);
  LCD_WriteCom(0x38); //0b00111000 - 8 разрядная шина, 2 строки
#endif
  LCD_WriteCom(0xf);  //0b00001111 - дисплей, курсор, мерцание включены
  LCD_WriteCom(0x1);  //0b00000001 - очистка дисплея
  _delay_ms(2);
  LCD_WriteCom(0x6);  //0b00000110 - курсор движется вправо, сдвига нет
}

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

Чтение флага занятости lcd

Для 4-ех разрядной шины цикл чтения выглядит следующим образом

1.    Сделать порт входом
2.    Установить RW = 1
3.    Установить RS = 0
4.    Установить EN = 1
5.    Програмная задержка 1
6.    Считать старшую тетраду байта данных
7.    Установить EN = 0
8.    Установить EN = 1
9.    Програмная задержка 1
10.    Считать младшую тетраду байта данных
11.    Установить EN = 0

Код на Си будет такой

  unsigned char data;
  DDRX_DATA &= 0x0f;          //конфигурируем порт на вход
  PORT_DATA |= 0xf0;          //включаем pull-up резисторы
  SetBit(PORT_SIG, RW);         //RW в 1 чтение из lcd
  ClearBit(PORT_SIG, RS);         //RS в 0 команды
 
    SetBit(PORT_SIG, EN);    
    _delay_us(2);
    data = PIN_DATA & 0xf0;      //чтение данных с порта
    ClearBit(PORT_SIG, EN);
    data = __swap_nibbles(data);   
    SetBit(PORT_SIG, EN);    
    _delay_us(2);
    data |= PIN_DATA & 0xf0;      //чтение данных с порта
    ClearBit(PORT_SIG, EN);
    data = __swap_nibbles(data);   

7 бит считанного байта – будет флагом занятости BF.

Исправим функцию ожидания флага BF
Без "начинки" она будет выглядеть так:

void LCD_Wait(void)
{
#ifdef CHECK_FLAG_BF
       #ifdef BUS_4BIT
     
             //код для 4-ех разрядной шины
      
       #else
 
            //код для 8-ми разрядной шины

       #endif    
#else


      _delay_us(40);

#endif  
}

  Если опрос флага занятости не используется (в заголовочном файле закомментирована строчка #define CHECK_FLAG_BF), то для обоих вариантов подключения lcd функция LCD_Wait() будет представлять из себя программную задержку  _delay_us(40). Если флаг занятости используется и #define BUS_4BIT не закомментирован – будет компилироваться код для 4-ех разрядной шины. Ну и в последнем случае (когда #define BUS_4BIT закомментирован), функция будет “заточена” под 8-ми разрядную шину.

Окончательный вариант функции LCD_Wait() будет таким:

#pragma inline = forced
void LCD_Wait(void)
{
#ifdef CHECK_FLAG_BF
  #ifdef BUS_4BIT
 
  unsigned char data;
  DDRX_DATA &= 0x0f;     //конфигурируем порт на вход
  PORT_DATA |= 0xf0;     //включаем pull-up резисторы
  SetBit(PORT_SIG, RW);     //RW в 1 чтение из lcd
  ClearBit(PORT_SIG, RS);    //RS в 0 команды
  do{
    SetBit(PORT_SIG, EN);    
    _delay_us(2);
    data = PIN_DATA & 0xf0;      //чтение данных с порта
    ClearBit(PORT_SIG, EN);
    data = __swap_nibbles(data);
    SetBit(PORT_SIG, EN);    
    _delay_us(2);
    data |= PIN_DATA & 0xf0;      //чтение данных с порта
    ClearBit(PORT_SIG, EN);
    data = __swap_nibbles(data);
  }while((data & (1<<FLAG_BF))!= 0 );
  ClearBit(PORT_SIG, RW);
  DDRX_DATA |= 0xf0;
 
  #else
  unsigned char data;
  DDRX_DATA = 0;              //конфигурируем порт на вход
  PORT_DATA = 0xff;          //включаем pull-up резисторы
  SetBit(PORT_SIG, RW);     //RW в 1 чтение из lcd
  ClearBit(PORT_SIG, RS);    //RS в 0 команды
  do{
    SetBit(PORT_SIG, EN);    
    _delay_us(2);
    data = PIN_DATA;            //чтение данных с порта
    ClearBit(PORT_SIG, EN);    
  }while((data & (1<<FLAG_BF))!= 0 );
  ClearBit(PORT_SIG, RW);
  DDRX_DATA = 0xff;
  #endif    
#else

  _delay_us(40);
#endif  
}

Все. Библиотека исправлена, вот что получилось - драйвер символьного lcd.

Файлы

Подключение LCD по 4-ех разрядной шине. Проект для IARa.
Подключение LCD по 4-ех разрядной шине. Проект для WINAVR.

P.S: Символьные lcd с контроллером HD44780 будут работать с этой библиотекой только по 8-ми разрядной шине и без проверки флага занятости. У контроллера HD44780 отличается процесс инициализации.

Comments   

# foxit 2009-12-21 18:52
А под HD44780 с 4-ехбитной шиной?
# Pashgan 2009-12-22 04:39
скоро будет...
# Silverio 2009-12-23 12:59
У меня уже есть доработка данной библиотеки для HD44780, не могу прикрепить файл на странице :(
скажите куда отправить .
# Pashgan 2009-12-23 16:51
pashgan"собака" hotbox.ru
# foxit 2009-12-28 16:33
А есть ли уже либа от Silverio?
Хочется на нее посмотреть.
# Pashgan 2009-12-28 22:16
выложил на форум
# foxit 2009-12-29 04:30
спасибо
# Guest 2010-06-04 20:01
"...можно обойтись и однократной подачей команды Function set, что я обычно и делаю."

А вот протеус в это верить отказывается напрочь, поэтому пришлось всё же прописать отправку по частям. На практике пока не проверял.
# Guest 2010-09-30 13:49
на асме не кто не встречал библеотеку??
# Guest 2010-10-05 19:28
В разделе "программирован ие на Асм" есть проект в котором создается меню на ассемблере. В проекте есть библиотека для lcd на ассемблере.Посм отри ее,может подойдет
# Анатолий S 2011-02-02 12:39
Подскажите в чем "подвох" при управлении от приведенного драйвера для IAR с четырех строчным (16*4)индикатор ом WH1604
по 4-х разрядной шине не удается вывести 2-ю и 4-ю строку. Инициализируетс я он как 2-х строчный, "условно первая" строка (32 символа)в нем выводится как 1-я и 3-я, а "условно вторую" не удается вывести как 2-ю и 4-ю. Как подправить драйвер для работы с 4-х строчным индикатором? Инфа по работе с таким индикатором практически полностью отсутствует в интернете!
# Pashgan 2011-02-02 20:31
Сделай тему на форуме там разберемся и подправим код.
# нечто 2011-02-20 17:29
вместо asm функции можно добавить макрос

#define __swap_nibbles( Data) Data=((Data&0x0 f)4)
# нечто 2011-02-20 17:30
#define SwapNibbles(Dat a) Data=((Data&0x0 f)4)
# нечто 2011-02-20 17:32
та, твою же мать!
# Pashgan 2011-02-23 13:53
Код заключай между тегами. Тогда резать не будет!!!
# uuu 2011-02-21 21:28
LCD_WriteCom((( ((y)& 1)*0x40)+((x)& 7))|128)

растолкуйте, что это все значит?
# Pashgan 2011-02-23 14:07
Это макрос для позиционировани я курсора.
Y - может принимать значения 0 и 1. Поэтому для безопасности добавлен код y&1. Какой бы Y вы туда не подставили, его значение не превысит единицу.

x - может принимать значения от 0 до 7. Поэтому добавлен код, ограничивающий его возможные значения - x&7.

Нижняя строка дисплея начинается с адреса 0x40, верхняя 0x00. Умножая значение Y на 0x40 мы получаем соответствующий адрес строки, а прибавляя значение x&7, получаем адрес определенного знакоместа.

|128 - установка 7-ого разряда в единицу. Он сообщает контроллеру дисплея, о том что передаваемая команда является DDRAM адресом. Ну по сути адресом в который должен встать курсор дисплея.
# uuu 2011-02-27 14:04
большое спасибо, а то не мог понять почему курсор дальше восьмого символа не идет...( у меня дисплей на 16 символов)
...x ведь может принимать значение до 40 (реальная длина строки дисплея)
# Pashgan 2011-03-06 21:29
Я и сам на эти грабли наступал...
# Flint 2012-02-17 15:28
Что-то не пойму как использовать. Например мне надо поставить курсор в позицию - первое знакоместо в первой строке (вроде бы х,у 0 и 0, так?)
Второй пример - предположим вторая строка, пятое знакоместо. (х,у 1 и 4)
Что-то у меня по этой логике не срабатывает
# нечто 2011-02-28 09:50
Code:#define SwapNibbles(Data) (((Data)&0x0f)<<4)+(((Data)&0xf0)>>4))
# Pashgan 2011-03-06 21:28
точно!.. что-то не пришло в голову
# AGE 2015-12-09 04:49
Некропостинг :-)
Нельзя для объединения битовых полей использовать сложение, только битовое "или", т.е. |
Хоть во многих случаях это прокатывает, как и в представленном случае, но однажды можно нарваться на переустановку уже установленного бита и часть поля после этого бита (включая его) поедет влево.
# КуВукул 2011-05-02 10:31
А какой LCD можно таким образом подключить?
# DigRo 2013-03-25 10:57
Мне кажется не правильно присваивать значение переменной всему порту:
Code:PORT_DATA = tmp;
Аналогично:
Code:PORT_DATA = 0b0000XXXX;
а не отдельным его пинам(PD4-PD7) при 4х разрядном подключении. Ведь PD0-PD3 могут использоваться для какихто других целей, например включать какую-нибудь нагрузку, что приведет к её отключении.
# DigRo 2013-03-25 11:07
Извините, не заметил:
Code:
unsigned char tmp;
tmp = PORT_DATA & 0x0f;
tmp |= (data & 0xf0);
# Ivani4 2013-04-25 17:00
Попробывал написать свои функции для 4 битного режима HD44780. Не получается пока. Не могу понять, после инициализации дисплея не отображается на экране вообще ничего, только символ курсора двигается последовательно по экрану (как будто перебирается по ячейкам памяти). Может кто подскажет где ошибка. Исходный текст выложил на файлообменник ( хттп://yadi.sk/ d/TZvp9nRS4LlTI ).
# HES 2015-03-03 19:29
Можно ли с помощью данной библиотеки подключить ЖКИ к одному порту ВВ контроллера (шина управления, шина данных к примеру подключены к порту А)?

У вас недостаточно прав для комментирования.