Учебный курс. Подключение 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 отличается процесс инициализации.

Комментарии   

# foxit 21.12.2009 18:52
А под HD44780 с 4-ехбитной шиной?
Ответить | Ответить с цитатой | Цитировать
# Pashgan 22.12.2009 04:39
скоро будет...
Ответить | Ответить с цитатой | Цитировать
# Silverio 23.12.2009 12:59
У меня уже есть доработка данной библиотеки для HD44780, не могу прикрепить файл на странице :(
скажите куда отправить .
Ответить | Ответить с цитатой | Цитировать
# Pashgan 23.12.2009 16:51
pashgan"собака" hotbox.ru
Ответить | Ответить с цитатой | Цитировать
# foxit 28.12.2009 16:33
А есть ли уже либа от Silverio?
Хочется на нее посмотреть.
Ответить | Ответить с цитатой | Цитировать
# Pashgan 28.12.2009 22:16
выложил на форум
Ответить | Ответить с цитатой | Цитировать
# Guest 04.06.2010 20:01
"...можно обойтись и однократной подачей команды Function set, что я обычно и делаю."

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

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

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

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

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

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

Добавить комментарий

Защитный код
Обновить