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

Схема

   Шина данных и управляющие сигналы подключены к разным портам микроконтроллера. Обычно их вешают на один порт, но это не принципиально, главное чтобы софт работал.   
подключение 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 отличается процесс инициализации.

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

При добавлении в комментарий Си кода, заключайте его между тегами [code] [/code]. Иначе он будет отображаться некорректно.


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