Подключение LCD на базе HD44780 к ATmega16 (LM016L LCD 16×2)

Как подключить LСD дисплей на базе HD44780 к ATmega16 или его цифровой аналог LM016L 16×2 в Proteus

Подключение HD44780 к ATmega16 - LM016L LCD 16x2 (3)При работе с Arduino, Atmega, PIC или с другим микроконтроллером часто возникает необходимость вывести какие-либо текстовые данные на дисплей. С цифрами проще, можно использовать 7 сегментный индикатор, а для вывода текста необходимо использовать LCD-дисплеи (ЖКИ). В данной статьи мы рассмотрим подключение LCD-дисплея на базе контроллера HD44780 к ATmega16.

Для подключения LCD-дисплея на базе HD44780 к ATmega16 нам нужно использовать 12 выводов, можно и все 16, но не на всех контроллерах это удастся сделать, ибо физически невозможно, а программно — да:

  • 1 — Vss, земля -> GND
  • 2 — Vdd, питание -> +5 В
  • 3 — Vo (Vee), управление контрастностью напряжением -> выход потенциометра
  • 4 — RS, выбор регистра
  • 5 — R/W, чтение/запись -> земля (режим записи)
  • 6 — E, он же Enable, cтроб по спаду
  • 7-10 — DB0-DB3, младшие биты 8-битного интерфейса; не подключены
  • 11-14 — DB4-DB7, старшие биты интерфейса
  • 15 — A, питание для подсветки -> +5 В
  • 16 — K, земля для подсветки -> GND

Пример программы в Atmel Studio 7

LCD.h

#ifndef LCD_H_
#define LCD_H_

#define LCDDATAPORT        PORTB  // Порт и пины,
#define LCDDATADDR         DDRB   // к которым подключены
#define LCDDATAPIN         PINB   // сигналы D4-D7.
#define LCD_D4             3
#define LCD_D5             4
#define LCD_D6             5
#define LCD_D7             6

#define LCDCONTROLPORT     PORTB  // Порт и пины,
#define LCDCONTROLDDR      DDRB   // к которым подключены
#define LCD_RS             0      // сигналы RS, RW и E.
#define LCD_RW             1
#define LCD_E              2

#define LCD_STROBEDELAY_US 5      // Задержка строба

#define LCD_COMMAND        0
#define LCD_DATA           1

#define LCD_CURSOR_OFF     0
#define LCD_CURSOR_ON      2
#define LCD_CURSOR_BLINK   3

#define LCD_DISPLAY_OFF    0
#define LCD_DISPLAY_ON     4

#define LCD_SCROLL_LEFT    0
#define LCD_SCROLL_RIGHT   4

#define LCD_STROBDOWN      0
#define LCD_STROBUP        1

#define DELAY              1
 
void lcdSendNibble(char byte, char state);
char lcdGetNibble(char state);
char lcdRawGetByte(char state);
void lcdRawSendByte(char byte, char state);
char lcdIsBusy(void);
void lcdInit(void);
void lcdSetCursor(char cursor);
void lcdSetDisplay(char state);
void lcdClear(void);
void lcdGotoXY(char str, char col);
void lcdDisplayScroll(char pos, char dir);
void lcdPuts(char *str);
void lcdPutsf(char *str);
void lcdPutse(uint8_t *str);
void lcdLoadCharacter(char code, char *pattern);
void lcdLoadCharacterf(char code, char *pattern);
void lcdLoadCharactere(char code, char *pattern);

#endif /* LCD_H_ */

 

LCD.c

// Подключение LCD на базе HD44780 к ATmega16 (LM016L LCD 16x2)
// сайт http://micro-pi.ru

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <avr/pgmspace.h>
#include <avr/eeprom.h>
#include <avr/interrupt.h>
#include "LCD.h"

/*
  Отправляет младшую половину байта byte в LCD. Если state == 0,
  то передаётся как команда, если нет, то как данные.
*/
void lcdSendNibble(char byte, char state) {
  // Пины управления - на выход
  LCDCONTROLDDR |= 1<<LCD_RS | 1<<LCD_RW | 1<<LCD_E;
  // Пины данных - на выход
  LCDDATADDR |= 1<<LCD_D4 | 1<<LCD_D5 | 1<<LCD_D6 | 1<<LCD_D7;
  // Режим записи, RW = 0
  LCDCONTROLPORT &=  ~(1<<LCD_RW);
  // Устанавливаем 1 в RS
  if (state) {
    // если отдаём данные
	LCDCONTROLPORT |= 1<<LCD_RS;
  } else {
    LCDCONTROLPORT &= ~(1<<LCD_RS);
  }
  // Взводим строб
  LCDCONTROLPORT |= 1<<LCD_E;
  // Обнуляем пины данных
  LCDDATAPORT &= ~(1<<LCD_D4 | 1<<LCD_D5 | 1<<LCD_D6 | 1<<LCD_D7);
  // Записываем младшую
  if (byte & (1<<3)) {
    // половину байта
	LCDDATAPORT |= 1<<LCD_D7;
  }
  // byte в порт вывода данных
  if (byte & (1<<2)) {
    LCDDATAPORT |= 1<<LCD_D6;
  }

  if (byte & (1<<1)) {
    LCDDATAPORT |= 1<<LCD_D5;
  }

  if (byte & (1<<0)) {
    LCDDATAPORT |= 1<<LCD_D4;
  }
  // Пауза
  _delay_us(LCD_STROBEDELAY_US);
  // Опускаем строб. Полубайт ушёл
  LCDCONTROLPORT &= ~(1<<LCD_E);
}

/*
  Читает половину байта из LCD. Если state == 0, то читается команда,
  если нет, то данные.
*/
char lcdGetNibble(char state) {
  char temp = 0;
  // Пины управления - на выход
  LCDCONTROLDDR |=  1<<LCD_RS | 1<<LCD_RW | 1<<LCD_E;
  // Режим чтения
  LCDCONTROLPORT |= 1<<LCD_RW;
  // Устанавливаем 1 в RS
  if (state) {
    // если получаем данные
    LCDCONTROLPORT |=(1<<LCD_RS);
  } else {
    LCDCONTROLPORT &= ~(1<<LCD_RS);
  }
  // Взводим строб
  LCDCONTROLPORT |= 1<<LCD_E;
  // Пины данных - на вход
  LCDDATADDR &= ~(1<<LCD_D4 | 1<<LCD_D5 | 1<<LCD_D6 | 1<<LCD_D7);
  // с подтяжкой
  LCDDATAPORT |= 1<<LCD_D4 | 1<<LCD_D5 | 1<<LCD_D6 | 1<<LCD_D7;
  // Пауза
  _delay_us(LCD_STROBEDELAY_US);
  // Опускаем строб
  LCDCONTROLPORT &= ~(1<<LCD_E);
  // Читаем пины
  if (LCDDATAPIN & (1<<LCD_D7)) {
    // во временную переменную 
    temp |= 1<<3;
  }
    
  if (LCDDATAPIN & (1<<LCD_D6)) {
    temp |= 1<<2;
  }

  if (LCDDATAPIN & (1<<LCD_D5)) {
    temp |= 1<<1;
  }
    
  if (LCDDATAPIN & (1<<LCD_D4)) {
    temp |= 1<<0;
  }
  // возвращаем прочитанное
  return temp;
}

/*
  Читает байт из LCD. Если state == 0, то читается команда,
  если нет, то данные.
*/
char lcdRawGetByte(char state) {
  char temp = 0;
  
  temp |= lcdGetNibble(state);
  temp = temp<<4;          
  temp |= lcdGetNibble(state);
  
  return temp;
}

/*
  Отравляет байт в LCD. Если state == 0, то передаётся как команда,
  если нет, то как данные.
*/
void lcdRawSendByte(char byte, char state) {
  lcdSendNibble((byte>>4), state);          
  lcdSendNibble(byte,state);
}

/*
  Читает состояние LCD, возвращает 0xff, если флаг занятости установлен,
  и 0x00, если нет.
*/
char lcdIsBusy(void) {
/* TODO
  if (lcdRawGetByte(LCD_COMMAND) & (1<<7))
    return 0xff;
  else
    return 0x00;
*/
  _delay_ms(DELAY);
  return 0x00;
}

/*
  Выполняет начальную инициализацию дисплея. Четырёхбитный режим. 
*/
void lcdInit(void) {
  while (lcdIsBusy()) ;  
  lcdSendNibble(0b0010, LCD_COMMAND);
  while (lcdIsBusy()) ;
  lcdRawSendByte(0b00101000, LCD_COMMAND);
  while (lcdIsBusy()) ;
  lcdRawSendByte(0b00000001, LCD_COMMAND);
  while (lcdIsBusy()) ;
  lcdRawSendByte(0b00000110, LCD_COMMAND);
  while (lcdIsBusy()) ;
  lcdRawSendByte(0b00001100, LCD_COMMAND);
}

/*
  Устанавливает режим курсора: 0 - выключен, 2 - включен, 3 - моргает.
  Если на момент запуска LCD был выключен (lcdSetDisplay), то он будет включен.
*/
void lcdSetCursor(char cursor) {
  while (lcdIsBusy());
  
  lcdRawSendByte((0b00001100 | cursor), LCD_COMMAND);
}

/*
  Включает или выключает отображение символов LCD.
  При каждом вызове выключает курсор.
*/
void lcdSetDisplay(char state) {
  while (lcdIsBusy());
  
  lcdRawSendByte((0b00001000 | state), LCD_COMMAND);
}

/*
  Очищает LCD.
*/
void lcdClear(void) {
  while (lcdIsBusy()) ;
  
  lcdRawSendByte(0b00000001, LCD_COMMAND);
}

/*
  Устанавливает курсор в заданную позицию.
*/
void lcdGotoXY(char str, char col) {
  while (lcdIsBusy());
  
  lcdRawSendByte((0b10000000 | ((0x40 * str) + col)), LCD_COMMAND);
}

/*
  Сдвигает область отображения на указанное количество символов
  вправо или влево.
*/
void lcdDisplayScroll(char pos, char dir) {
  while (pos){
    while (lcdIsBusy()) ;
  
    lcdRawSendByte((0b00011000 | dir), LCD_COMMAND);
    pos--;
  }  
}

/*
  Выводит строку из RAM в позицию курсора.
*/
void lcdPuts(char *str) {
  while (*str){
    while (lcdIsBusy()) ;
    
    lcdRawSendByte(*str++, LCD_DATA);
  }
}

/*
  Выводит строку из flash в позицию курсора.
*/
void lcdPutsf(char *str) {
  while (pgm_read_byte(str)){
    while (lcdIsBusy()) ;
    
    lcdRawSendByte(pgm_read_byte(str++), LCD_DATA);
  }
}

/*
  Выводит строку из eeprom в позицию курсора.
*/
void lcdPutse(uint8_t *str) {
  while (eeprom_read_byte(str)){
    while (lcdIsBusy()) ;
    
    lcdRawSendByte((char)(eeprom_read_byte(str++)), LCD_DATA);
  }
}

/*
  Загружает символ в знакогенератор.
*/
void lcdLoadCharacter(char code, char *pattern) {
  while (lcdIsBusy());
  
  lcdRawSendByte((code<<3) | 0b01000000, LCD_COMMAND);
  
  for (char i = 0; i <= 7; i++){
    while (lcdIsBusy()) ;
        
    lcdRawSendByte(*pattern++, LCD_DATA);
  }
  while (lcdIsBusy());
  lcdRawSendByte(0b10000000, LCD_COMMAND);
}

/*
  Загружает символ из flash в знакогенератор.
*/
void lcdLoadCharacterf(char code, char *pattern) {
  while (lcdIsBusy());
  
  lcdRawSendByte((code<<3) | 0b01000000, LCD_COMMAND);
  
  for (char i = 0; i <= 7; i++){
    while (lcdIsBusy());

    lcdRawSendByte(pgm_read_byte(pattern++), LCD_DATA);
  }
  while (lcdIsBusy());
  lcdRawSendByte(0b10000000, LCD_COMMAND);
}

/*
  Загружает символ из eeprom в знакогенератор.
*/
void lcdLoadCharactere(char code, char *pattern) {
  while (lcdIsBusy());
  
  lcdRawSendByte((code<<3) | 0b01000000, LCD_COMMAND);
  
  for (char i = 0; i <= 7; i++){
    while (lcdIsBusy()) ;
        
    lcdRawSendByte(eeprom_read_byte(pattern++), LCD_DATA);
  }
  while (lcdIsBusy()) ;
  lcdRawSendByte(0b10000000, LCD_COMMAND);
}

 

main.c

// Подключение LCD на базе HD44780 к ATmega16 (LM016L LCD 16x2)
// сайт http://micro-pi.ru

#define F_CPU 8000000UL
#include <avr/io.h>
#include <util/delay.h>
#include <string.h>
#include "LCD.h"

int main(void) {
  _delay_ms(100);
  lcdInit();
  lcdClear();
  lcdSetDisplay(LCD_DISPLAY_ON);
  lcdSetCursor(LCD_CURSOR_OFF);
  
  char text[17];
  strcpy(text, "  Hello World!  ");
  lcdGotoXY(0, 0);
  lcdPuts(text);
  strcpy(text, "site:micro-pi.ru");
  lcdGotoXY(1, 0);
  lcdPuts(text);    

  while (1);
}

Схема подключения LCD на базе HD44780 к ATmega16 в ISIS 7 Professional — Proteus. Симуляция.

Схема подключения HD44780 к ATmega16 - LM016L LCD 16x2 (1)

Вам также потребуется добавить резистор номиналом 100-150 Ом к 15-му контакту, чтобы индикатор подсветки не вышел из строя.

Подключение LCD на базе HD44780 к ATmega16

Скачать
проект в Atmel Studio 7 LCD 16×2 ATmega16.7z
проект в Proteus LCD 16×2 ATmega16.DSN.7z

Похожие записи

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

  • В схеме нет токоограничивающего резистора подсветки (≈100 Ом). Без него подсветке индикатора быстро придёт конец.

    • Вы правы, этот момент я пропустил, в Протеусе нету 15-го вывода, чтобы подключить этот резистор. Я добавлю маленький комментарий по этому поводу. Спасибо, что подсказали.

      • Здравствуйте admin,
        в Proteus 8.9 не воспроизводится часть кода ( lcd.c )
        Proteus выдаёт такие ошибки:

        avr-gcc.exe -Wall -gdwarf-2 -fsigned-char -MD -MP -DF_CPU=1000000 -O1 -mmcu=atmega16 -o «lcd.o» -c «../lcd.c»
        ../lcd.c:4:1: warning: «F_CPU» redefined
        : warning: this is the location of the previous definition
        ../lcd.c: In function ‘lcdLoadCharacter’:
        ../lcd.c:251: error: ‘for’ loop initial declaration used outside C99 mode
        ../lcd.c: In function ‘lcdLoadCharacterf’:
        ../lcd.c:268: error: ‘for’ loop initial declaration used outside C99 mode
        ../lcd.c: In function ‘lcdLoadCharactere’:
        ../lcd.c:285: error: ‘for’ loop initial declaration used outside C99 mode
        ../lcd.c:288: warning: pointer targets in passing argument 1 of ‘__eerd_byte_m16’ differ in signedness
        make: *** [lcd.o] Error 1

        Код ошибки 2

        Можете сказать способ решения данных ошибок?

  • Огромное спасибо за код lcd. Нужно было проверить дисплей и на атмеге8 запустился с 1го раза.

  • Аккуратный, понятный код. Всё сразу заработало как надо. Спасибо вам!

  • Спасибо большое из 2021, всё заработало тут же!

  • Здравствуйте. Получаю предупреждение:
    288: warning: pointer targets in passing argument 1 of ‘__eerd_byte_m16’ differ in signedness.
    на это часть кода lcdRawSendByte(eeprom_read_byte(pattern++), LCD_DATA);
    Как можно это исправить?

Добавить комментарий для GM74 Отменить ответ

Ваш e-mail не будет опубликован. Обязательные поля помечены *