PICSim.js - символьный дисплей на базе HD44780

Среди множества различных 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 может понадобится какое-то время чтобы переварить информацию в зависимости от типа принятой команды - все это расписано в доке ровно как и правильная последовательность команд для инициализации после подачи питания. Пора бы уже написать какой-нибудь код:

config.h | hex | picsim.js

screenshot

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.

screenshot

Далее аналого-цифровой преобразователь (АЦП).

links

social