Как подключить LСD дисплей на базе HD44780 к ATmega16 или его цифровой аналог LM016L 16×2 в Proteus
При работе с 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. Симуляция.
Вам также потребуется добавить резистор номиналом 100-150 Ом к 15-му контакту, чтобы индикатор подсветки не вышел из строя.
Скачать
проект в Atmel Studio 7 LCD 16×2 ATmega16.7z
проект в Proteus LCD 16×2 ATmega16.DSN.7z
В схеме нет токоограничивающего резистора подсветки (≈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, всё заработало тут же!