Среди множества различных LCD дисплеев наиболее простыми являются текстовые (символьные) дисплеи на основе контроллера hd44780 с параллельным 4-или 8-битным интерфейсом. В сравнении с 7-сегментными индикаторами, дисплеи на базе HD44780 обладают на порядок большими возможностями - количество строк на экране у разных моделей - 1,2 или 4; число видимых символов в строке: 8,10,16,20,24,30,32 или 40, один символ на дисплее представляет собой матрицу размером 5x8 точек, за отрисовку символов отвечает сам HD44780, ток потребления контроллера без подсветки очень мал - 100…200 мкА. Обычно хорошей практикой для разработчиков является ознакомление с официальной документацией.
Для управления HD44780 производителем предусмотрены следующие выводы:
-
RS - дисплей определяет, что именно к нему поступает (данные/команда)
-
RW - направление данных (чтение/запись)
-
D0-D7 - шина команд/данных
-
E - стробирование (синхронизация команд/данных)
Вывод RW можно не использовать вообще если нет практической нужды принимать данные из ЖК (это обычная практика), зачастую достаточно только записывать данные в HD44780. Тогда всё управление сводится к следующей последовательности. Ведущий микроконтроллер выставляет признак RS - 0 указывает на команды, 1 - на данные, выставляет на линиях DB7…DB0 8-разрядный код, после чего формирует на выводе E стробирующий импульс (активный фронт – задний). Контроллеру HD44780 может понадобится какое-то время чтобы переварить информацию в зависимости от типа принятой команды - все это расписано в доке ровно как и правильная последовательность команд для инициализации после подачи питания. Пора бы уже написать какой-нибудь код:
HD44780.h
#ifndef HD44780_h
#define HD44780_h
// commands
#define LCD_CLEARDISPLAY 0x01
#define LCD_RETURNHOME 0x02
#define LCD_ENTRYMODESET 0x04
#define LCD_DISPLAYCONTROL 0x08
#define LCD_CURSORSHIFT 0x10
#define LCD_FUNCTIONSET 0x20
#define LCD_SETCGRAMADDR 0x40
#define LCD_SETDDRAMADDR 0x80
// flags for display entry mode
#define LCD_ENTRYRIGHT 0x00
#define LCD_ENTRYLEFT 0x02
#define LCD_ENTRYSHIFTINCREMENT 0x01
#define LCD_ENTRYSHIFTDECREMENT 0x00
// flags for display on/off control
#define LCD_DISPLAYON 0x04
#define LCD_DISPLAYOFF 0x00
#define LCD_CURSORON 0x02
#define LCD_CURSOROFF 0x00
#define LCD_BLINKON 0x01
#define LCD_BLINKOFF 0x00
// flags for display/cursor shift
#define LCD_DISPLAYMOVE 0x08
#define LCD_CURSORMOVE 0x00
#define LCD_MOVERIGHT 0x04
#define LCD_MOVELEFT 0x00
// flags for function set
#define LCD_8BITMODE 0x10
#define LCD_4BITMODE 0x00
#define LCD_2LINE 0x08
#define LCD_1LINE 0x00
#define LCD_5x10DOTS 0x04
#define LCD_5x8DOTS 0x00
#endif
Как видно всего команд не так уж и много LCD_CLEARDISPLAY...LCD_SETDDRAMADDR
, но для некоторых могут быть дополнительные флаги - например для LCD_DISPLAYCONTROL
это LCD_DISPLAYON...LCD_BLINKOFF
.
lcd.c
#include <xc.h>
#include "HD44780.h"
#define LCD_EN_PIN PORTEbits.RE1
#define LCD_RS_PIN PORTEbits.RE2
#define LCD_PORT PORTD
#define _LCD_DEFAULT_STATE_DISPLAYCONTROL (LCD_DISPLAYON | LCD_CURSORON | LCD_BLINKON)
#define _LCD_DEFAULT_STATE_DISPLAYMODE (LCD_ENTRYLEFT | LCD_ENTRYSHIFTDECREMENT)
static void _lcd_wr(const unsigned char val) {
LCD_PORT = val;
}
static void _lcd_en_pulse() {
__delay_ms(3);
LCD_EN_PIN = 0;
__delay_ms(3);
LCD_EN_PIN = 1;
}
void lcd_cmd(const unsigned char val) {
_lcd_wr(val);
LCD_RS_PIN = 0;
_lcd_en_pulse();
}
void lcd_dat(const unsigned char val) {
_lcd_wr(val);
LCD_RS_PIN = 1;
_lcd_en_pulse();
}
void lcd_init() {
// SEE PAGE 45/46 FOR INITIALIZATION SPECIFICATION!
// according to datasheet, we need at least 40ms after power rises above 2.7V
__delay_ms(40);
LCD_RS_PIN = 0;
LCD_EN_PIN = 1;
// this is according to the hitachi HD44780 datasheet
// page 45 figure 23
lcd_cmd(LCD_FUNCTIONSET | LCD_8BITMODE);
__delay_ms(5);
lcd_cmd(LCD_FUNCTIONSET | LCD_8BITMODE);
__delay_ms(1);
lcd_cmd(LCD_FUNCTIONSET | LCD_8BITMODE);
// finally, set # lines, font size, etc.
lcd_cmd(LCD_FUNCTIONSET | LCD_8BITMODE | LCD_2LINE);
// turn the display off
lcd_cmd(LCD_DISPLAYCONTROL | LCD_DISPLAYOFF);
lcd_cmd(LCD_CLEARDISPLAY); // clear display, set cursor position to zero
__delay_ms(2); // this command takes a long time!
// turn the display on with default cursor and blinking
lcd_cmd(LCD_DISPLAYCONTROL | _LCD_DEFAULT_STATE_DISPLAYCONTROL);
// Initialize to default text direction (for romance languages)
lcd_cmd(LCD_ENTRYMODESET | _LCD_DEFAULT_STATE_DISPLAYMODE);
}
При инициализации lcd_init()
необходимо соблюдать ритуал с временными задержками, описанный в документации к HD44780.
main.c
/*
xc8 --chip=18f4620 main.c
*/
#define _XTAL_FREQ 5e4
#include <xc.h>
#include "config-4620.h"
#include "lcd.c"
static void lcd_print(const char *str) {
while (*str) {
lcd_dat(*str++);
}
}
int main() {
// lcd
PORTD = TRISD = 0;
PORTE = 0;
TRISE = 1;
lcd_init();
lcd_cmd(LCD_SETDDRAMADDR | 0); // first line 0 column
lcd_print("Hey PICSim.js !");
lcd_cmd(LCD_SETDDRAMADDR | 0x40); // second line 0 column
lcd_print("Life is good :)");
while(1) {}
return 0;
}
У PIC18 довольно много битов конфигурации, поэтому для них отдельный файл config.h
. Первым делом с помощью управляющих команд lcd_cmd
дисплей настраивается, затем текст пересылается в виде данных lcd_dat
. Хотя видимых символов в строке дисплея в нашем случае 16, всего их там 64 (0x40) - остальные просто находятся в памяти HD44780 контроллера.
При работе HD44780 трудно держать все управляющие комбинации в голове, поэтому код не помешает упростить, например привести к виду arduino библиотеки «LiquidCrystal».
config.h | liquid-crystal.c | hex | picsim.js
/*
xc8 --chip=18f4620 main.c
*/
#define _XTAL_FREQ 5e4
#include <xc.h>
#include <stdint.h>
#include "config-4620.h"
#include "liquid-crystal.c"
static void clear_print(const char *str){
lcd_clear(); // clear display, set cursor position to zero
lcd_print(str);
}
int main() {
// lcd
PORTD = TRISD = 0;
PORTE = 0;
TRISE = 1;
lcd_init();
while(1) {
clear_print("PICSim.js :)");
lcd_set_cursor(3, 1); // second line third column
lcd_print("Show time !");
__delay_ms(4444);
clear_print("No CURSOR");
lcd_no_cursor(); // hide cursor
__delay_ms(2000);
lcd_cursor(); // show cursor
clear_print("No BLINK");
lcd_no_blink(); // blink OFF
__delay_ms(2000);
lcd_blink(); // blink ON
clear_print("ON/OFF");
for (uint8_t i = 0; i < 4; i++) {
lcd_display(); // display ON
__delay_ms(1000);
lcd_no_display(); // display OFF
__delay_ms(1000);
}
lcd_display();
clear_print("SCROLL right");
for (uint8_t i = 0; i < 10; i++) {
__delay_ms(500);
lcd_scroll_display_right();
}
clear_print("SCROLL left");
for (uint8_t i = 0; i < 10; i++) {
__delay_ms(500);
lcd_scroll_display_left();
}
// PICSimLab: not implemented ?
// lcd_right_to_left()
// lcd_autoscroll()
// lcd_create_char()
}
return 0;
}
Данный код заточен на дисплей, состоящий из двух строк - это задаётся на этапе инициализации один раз LCD_8BITMODE | LCD_2LINE
. Также для экономии ног ввода-вывода микроконтроллера можно достаточно легко перейти с 8-битного на 4-битный LCD_4BITMODE
формат передачи команд/данных, попутно немного подправив функцию _lcd_wr
.
Далее аналого-цифровой преобразователь (АЦП).