ATmega8: простая программа управления ЖК-дисплеем HD44780

разделы: AVR , HD44780 , дата: 9 сентября 2015г.


Управление ЖК-дисплеем HD44780 довольно простое. Он имеет параллельную шину на 8 пин и три управляющих линии.

  1. Контакты D0-D7 это 8-битная шина. Может работать как в 8-битном режиме, так и в 4-битном. В последнем случае задействуются "старшие" пины D4-D7.
  2. RS выбор регистра. В контроллере дисплея имеется два регистра: регистр команд IR и регистр данных DR. В зависимости от того, что мы пишем в контроллер данные или подаем команды, с помощью RS линии мы выбираем "адресата". При RS равном нулю посылаются команды, при RS равном единице пишутся данные.
  3. E выполняет роль клавиши "Enter". Можно в каком угодно порядке и последовательности менять значения пинов контроллера дисплея, но когда управляющий пин E устанавливается в единицу, то всё, данные уходят в контроллер дисплея. Чтобы записать очередные данные следует E вновь сбросить в ноль.
  4. RW не используемый в данном случае пин, который переключает режимы дисплея с чтения на запись и обратно. В данном примере RW запитан на массу(землю), т.е. установлен в ноль, что означает работу дисплея только на прием, на получение и отображение данных и команд. При RW равном единице дисплей переводится в режим передачи, и тогда возможно прочитать BF(busy flag) - флаг занятости. Дело в том, что дисплей довольно медлительное устройство, и при записи в него команд или данных он становится на некоторое время недоступен. Чтобы понять, когда в него снова можно писать следует ждать когда сбросится BF. В данном примере вместо опроса BF используются задержки с гарантированным временем готовности дисплея. С опросом BF флага, программа работала бы конечно быстрее, но это, как говорится, палка о двух концах. Усложнение ради упрощения.

Команды дисплея

Их можно посмотреть, например, в Вики, я бы рекомендовал посмотреть datasheet на HD44780U, там они очень подробно рассмотрены, даже с иллюстрациями. Еще полезная штука - онлайн эмулятор HD44780, в котором мышкой можно "подергать" пины и научиться подавать команды. Очень помогает разобраться, что к чему.

Подключение дисплея к микроконтроллеру

В Arduino есть дефолтовая библиотека LiquidCrystal, в которой, в конструкторе можно задать какими именно GPIO подключаться к дисплею. Однако, для 8-разрядного микроконтроллера предпочтительно использовать 4-битный режим работы дисплея, и подключать его к GPIO одного порта. Тогда писать в дисплей можно будет "пакетами" по 6 бит. В ATmega8/168/328 есть порт D первые два пина которого заняты UART'ом, остаются еще 6 - т.е. ровно столько, сколько нужно для дисплея. Т.о. подключение будет таким:

т.е.

RS <==> PD2 (он же pin2 Arduino);
E  <==> PD3 (он же pin3 Arduino);
D4 <==> PD4 (он же pin4 Arduino);
D5 <==> PD5 (он же pin5 Arduino);
D6 <==> PD6 (он же pin6 Arduino);
D7 <==> PD7 (он же pin7 Arduino);
RW <==> GND;

Дело за малым. Текст программы:

/* for ATmega8/168/328 display HD44780 support
site: http://countzero.weebly.com

avr-gcc -mmcu=atmega8 -Wall -Os -o lcd.elf lcd.c
avr-objcopy -O ihex lcd.elf lcd.hex*/

#define F_CPU 16000000UL
#include <avr/io.h>
#include <util/delay.h>

#define LCD_RS	2
#define LCD_E	3
#define LCD_D4	4
#define LCD_D5	5
#define LCD_D6	6
#define LCD_D7	7

#define LCD_PORT DDRD
#define LCD	 PORTD

#define CMD 0 // command
#define DTA 1 // data

#define LCD_CLEAR	0x01
#define LCD_OFF    	0x08
#define LCD_ON    	0x0C
#define LCD_RETURN    	0x02

// for 4bit mode
static int  send_lcd(uint8_t value, uint8_t mode)
{
	LCD&=0x03; // clear
	LCD|=(value&0xF0)|(mode<<LCD_RS)|(1<<LCD_E);
	LCD&=0x03;

	LCD|=(value<<4)|(mode<<LCD_RS)|(1<<LCD_E);
	LCD&=~(1<<LCD_E);
	_delay_ms(5);

	return 0;
}

static int print_lcd(char* str)
{
	uint8_t i;
	i=0;
	while(str[i] !=0 && i<255)
	{
		send_lcd(str[i],DTA);
		i++;
	};

	return i;
};

static int init_lcd()
{
	LCD_PORT=0xFC; // pin 2,3,4,5,6,7 in OUTPUT mode
	_delay_ms(50);

	// 4bit mode
	LCD&=0b11; // clear
	LCD |=(1<<LCD_D5)|(1<<LCD_E);
	LCD&=0b11; // clear
	LCD |=(1<<LCD_D5);
	_delay_ms(5);

	send_lcd(0x28,CMD); // mode: 4bit, 2 lines
	send_lcd(LCD_OFF,CMD);
	send_lcd(LCD_CLEAR,CMD);
	send_lcd(0x06,CMD); // seek mode: right
	send_lcd(LCD_ON,CMD);
	return 0;
}

int main(void)
{

	init_lcd();
	print_lcd("Hello");
	send_lcd(0xC0,CMD); // position on second line
	print_lcd("     World!");
	send_lcd(LCD_RETURN,CMD);
	for(;;){};
	return 0;
}

Программа только выводит "Hello World!" на дисплей и больше ничего не делает. Самое сложное в ней, это функция send_lcd которая посылает байт на контроллер дисплея в 4-битном режиме. В этом режиме посылаются сначала старшие четыре разряда, затем младшие. Т.к. нельзя трогать PD0/PD1 которые заняты под UART, приходится использовать битовые операции вместо присваивания. Обнуление в сочетании с логическим сложением аналогично присваиванию. Остальное, думаю, понятно. Итоговая прошивка для ATmega8 занимает около 330 байт.