/* * Arduino library to drive an LCD display that uses two NEC uPD7228 (or uPD16434) LCD controller chips * Based on the structure of the standard LiquidCrystal.cpp routines for the Hitachi HD44780 chip * Some hardware functions of the HD44780 chip were emulated in software * This resulting library is therefore loosely usable as a replacement for the LiquidCrystal library * but my interpretation of how scrolling etc should work, means it is not 100% compatible ;-) * * Thanks to H.Imayuki for a program, DM2020.c, demonstrating how to drive these chips * Author Tony Wills * Licensed CC-BY-SA * * 20180128 Initial release version 1.00 * 20180129 Version 1.01 - fixed init routine for chips by explicitly setting LCD frame rate * see http://www.arduino.scorchingbay.nz (or an archive.org copy) for more information */ #include "LiquidCrystal_7228.h" #include #include #include #include "Arduino.h" #include LiquidCrystal_7228::LiquidCrystal_7228(uint8_t bsy, uint8_t stb, uint8_t cs, uint8_t c_d, uint8_t rst, int clk, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) { init(bsy, stb, cs, c_d, rst, clk, d0, d1, d2, d3); } void LiquidCrystal_7228::init(uint8_t bsy, uint8_t stb, uint8_t cs, uint8_t c_d, uint8_t rst, int clk, uint8_t d0, uint8_t d1, uint8_t d2, uint8_t d3) { _busy_pin = bsy; _strobe_pin = stb; _select_pin = cs; _command_pin = c_d; _reset_pin = rst; outPin = clk; _data_pins[0] = d0; _data_pins[1] = d1; _data_pins[2] = d2; _data_pins[3] = d3; //begin(16,1); } //----- subfunction to Initialise 7228 LCD display void LiquidCrystal_7228::begin(uint8_t cols, uint8_t lines) { num_cols = cols; num_rows = lines; text_dir = left; autoscroll_on = FALSE; cursor_on = FALSE; wrap_on = FALSE; pinMode(_busy_pin, INPUT); pinMode(_strobe_pin, OUTPUT); pinMode(_select_pin, OUTPUT); pinMode(_command_pin, OUTPUT); pinMode(_reset_pin, OUTPUT); pinMode(outPin, OUTPUT); //clock output pinMode(_data_pins[0], OUTPUT); pinMode(_data_pins[1], OUTPUT); pinMode(_data_pins[2], OUTPUT); pinMode(_data_pins[3], OUTPUT); //setup clock for 7228 lcd chip Timer1.initialize(period); //initialize timer1 Timer1.pwm(9, duty); //setup pwm on pin 9, 50% duty cycle //perform reset and set parallel I/O mode setting digitalWrite(_select_pin, HIGH); //chip select off digitalWrite(_strobe_pin, HIGH); //strobe off digitalWrite(_reset_pin, HIGH); //reset on digitalWrite(_data_pins[1], HIGH); //set parallel mode delayMicroseconds(4); //reset duration digitalWrite(_reset_pin, LOW); //reset off delayMicroseconds(5); //hold duration //col and row are the position of the next character display //col 0 is on the left end of the display for left-to-right data entry //col 0 is on the right end of the display for right-to-left data entry row = 0; col = 0; //left chip initialization chip_init(); col = num_cols/2; //right chip initialization chip_init(); // fillDisplay();delay(1000); //for testing clear(); //blank all chars and cursors } //********** High level commands, for the user of this library //***** function to Clear the display and home cursor void LiquidCrystal_7228::clear() { uint8_t i,j; //Clear cursor, for num_cols chars over all rows cursor_on = FALSE; for (j=0; j < num_rows; j++) { setCursor(0, j); for (i = 0; i < num_cols; i++) { lcd_cmd(LCD_CLCURS); //CLCURS } } //Clear data, write num_cols space chars over all rows for (j=0; j < num_rows; j++) { setCursor(0, j); for (i = 0; i < num_cols; i++) { lcd_char(' '); //write a space char } } home(); } //***** function to Set the cursor back to the start of the display void LiquidCrystal_7228::home() { setCursor(0, 0); //NB column 0 will be at the left or right end depending upon data entry direction setting } //***** function to Position cursor void LiquidCrystal_7228::setCursor(uint8_t cur_x, uint8_t cur_y) { uint8_t i, line; if (cur_x < 0) cur_x = num_cols + cur_x; //if column negative, wrap it around to the other end if (cur_x < num_cols) { //columns numbered left most = 0 for left-to-right entry, or right most = 0 for right-to-left if (text_dir == left) { //if in visible area if (cur_x < (num_cols/2)) i = (num_cols/2 - cur_x) * 5 - 1; else i = (num_cols - cur_x) * 5 - 1; } else { if (cur_x < (num_cols/2)) i = (cur_x * 5); else i = (cur_x - (num_cols/2)) * 5; } col = cur_x; row = cur_y; line = cur_y & 0x01; lcd_cmd(LCD_LDPI | line << 6 | i); //bank y } //else do nothing, ignore out of range request } // Turn the display on/off (quickly) void LiquidCrystal_7228::noDisplay() { } //not supported, do nothing void LiquidCrystal_7228::display() { } //not supported, do nothing //***** function to Turn off the underline cursor void LiquidCrystal_7228::noCursor() { cursor_on = FALSE; lcd_cmd(LCD_CLCURS); //clear cursor at current location setCursor(col - 1, row); //move cursor back to current entry point } //***** function to Turn on the underline cursor void LiquidCrystal_7228::cursor() { cursor_on = TRUE; lcd_cmd(LCD_WRCURS); //turn on underline at current location setCursor(col - 1, row); //move cursor back to current entry point } //***** function to turn off character blink void LiquidCrystal_7228::noBlink() { } //not supported, do nothing //***** function to turn on character blink void LiquidCrystal_7228::blink() { } //not supported, do nothing //***** function to Scroll the display left void LiquidCrystal_7228::scrollDisplayLeft(void) { //how we move the characters depends upon text entry direction if (text_dir == left) { scrollBackward(); } else { scrollForward(); } } //***** function to Scroll the display right void LiquidCrystal_7228::scrollDisplayRight(void) { //how we move the characters depends upon text entry direction if (text_dir == left) { scrollForward(); } else { scrollBackward(); } } //***** function to Set Left to Right text flow void LiquidCrystal_7228::leftToRight(void) { text_dir = left; col = num_cols/2; lcd_cmd(LCD_SCML); //set left to right on right chip col = 0; lcd_cmd(LCD_SCML); //set left to right on left chip setCursor(0,row); //move cursor to start of line } //***** function to Set Right to Left text flow void LiquidCrystal_7228::rightToLeft(void) { text_dir = right; col = num_cols/2; lcd_cmd(LCD_SCMR); //set left to right on left chip col = 0; lcd_cmd(LCD_SCMR); //set left to right on right chip setCursor(0,row); //move cursor to start of line } //***** function to Turn autoscroll on void LiquidCrystal_7228::autoScroll(void) { autoscroll_on = TRUE; } //***** function to Turn autoscroll off void LiquidCrystal_7228::noAutoscroll(void) { autoscroll_on = FALSE; } //***** function to Create custom characters, but not available on this chipset void LiquidCrystal_7228::createChar(uint8_t location, uint8_t charmap[]) { } //not supported, do nothing //***** function to Turn on wrapping of long lines back to start of line void LiquidCrystal_7228::lineWrap(void) { wrap_on = TRUE; } //***** function to Turn off wrapping of long lines back to start of line void LiquidCrystal_7228::noLinewrap(void) { wrap_on = FALSE; } /*********** mid level commands, for sending data/cmds */ //***** function to Send a command to LCD display inline void LiquidCrystal_7228::command(uint8_t value) { lcd_cmd(value); } //***** function to Send uint8_t to display inline size_t LiquidCrystal_7228::write(uint8_t value) { int i,j; int current_col; if (value==0x0D) { //ignore, non printing } else if (value==0x0A) { //treat LF as CR, LF for (i=col; i < num_cols; i++) lcd_char(' '); //clear to end of line setCursor(0,row+1); //move down one line } else { if (row >= num_rows) { //trying to write off bottom of display, so scroll up current_col = col; //save cursor column position setCursor(0,0); for (j=1; j < num_rows; j++) { for (i=0; i < num_cols; i++) { lcd_char(buffer[i][j]); } } //clear bottom line setCursor(0,num_rows-1); for (i=0; i < num_cols; i++) { lcd_char(' '); } setCursor(current_col,num_rows-1); //restore cursor location } //finally just send character to the display lcd_char(value); } return 1; //assume success } /************ low level data pushing commands **********/ /* void setup_nibble(uint8_t value); void write_nibble(uint8_t code, uint8_t chip, uint8_t flag); void lcd_cmd(uint8_t cmd); void lcd_char(char asci); void chip_init(); void fillDisplay(void); void scrollBackward(void); void scrollForward(void); */ //----- subfunction to Setup 4bit parallel data/command void LiquidCrystal_7228::setup_nibble(uint8_t value) { for (int i = 0; i < 4; i++) { digitalWrite(_data_pins[i], (value >> i) & 0x01); } } //----- subfunction to Write nibble of data to chip void LiquidCrystal_7228::write_nibble(uint8_t code, uint8_t chip, uint8_t flag) { digitalWrite(_command_pin, flag); //flag 0: data, 1: command setup_nibble(chip); //chip left, right digitalWrite(_strobe_pin, LOW); //strobe on setup_nibble(code); //command or data output digitalWrite(_strobe_pin, HIGH); //strobe off } //----- subfunction to Send a command to chip void LiquidCrystal_7228::lcd_cmd(uint8_t cmd) { uint8_t chip; if (((cmd == LCD_CLCURS) || (cmd == LCD_WRCURS)) && (col >= num_cols) && (wrap_on)) { setCursor(col - num_cols, row ); //wrap cursor to start of display } if (((cmd == LCD_CLCURS) || (cmd == LCD_WRCURS)) && (col > num_cols)) { //allow positioning of cursor off screen //but do nothing as not displayable } else { //determine which chip we're writing to, but column numbering depends upon which direction we're going if (text_dir == left) { if (col < (num_cols/2)) chip = left; else chip = right; } else { if (col < (num_cols/2)) chip = right; else chip = left; } digitalWrite(_select_pin, LOW); //chip select on write_nibble(cmd >> 4, chip, cmd_flag); //upper 4 bit output write_nibble(cmd, chip, cmd_flag); //lower 4 bit output delayMicroseconds(4); while (! digitalRead(_busy_pin)) { //wait for busy off } digitalWrite(_select_pin, HIGH); //chip select off if ((cmd == LCD_CLCURS) || (cmd == LCD_WRCURS)) { //cursor clear/set comand increments addr counter, so treat it like character display col++; if (col == (num_cols/2)) setCursor(num_cols/2, row); //use setCursor to change chip selection } } } //----- subfunction to Write one character to display void LiquidCrystal_7228::lcd_char(char asci) { uint8_t chip; if ((col >= num_cols) && (autoscroll_on)) { //scroll if off end of line autoscroll_on = FALSE; //turn off autoscroll flag while scrolling as it recursively calls this function scrollBackward(); autoscroll_on = TRUE; setCursor(num_cols-1, row); } if ((col >= num_cols) && (wrap_on)) { //or wrap if off end of line setCursor(col - num_cols, row); } if (col < num_cols) { //in visible area so display something if (cursor_on) { //if using a visible cursor, clear it before displaying new character lcd_cmd(LCD_CLCURS); //clear cursor setCursor(col - 1, row); } //determine which chip should be written to if (text_dir == left) { if (col < (num_cols/2)) chip = left; else chip = right; } else { if (col < (num_cols/2)) chip = right; else chip = left; } buffer[col][row] = asci; //save a copy, used when scrolling //write the character to the display digitalWrite(_select_pin, LOW); //chip select on write_nibble(asci >> 4, chip, data_flag); //upper 4 bits output write_nibble(asci, chip, data_flag); //lower 4 bits output delayMicroseconds(4); while (! digitalRead(_busy_pin)) { //wait for busy off } digitalWrite(_select_pin, HIGH); //chip select off col++; //increment character cursor position if (col == (num_cols/2)) setCursor(num_cols/2, row); //use setCursor to change chip selection if (cursor_on) { //using a visible cursor, so display it on screen lcd_cmd(LCD_WRCURS); //write cursor next location setCursor(col - 1, row); //writing cursor moves column, so move back again } } //else ignore write as off end of display } //----- subfunction to Initialise a single LCD chip void LiquidCrystal_7228::chip_init() { uint8_t i; //SFF (Set Frame Frequency) lcd_cmd(LCD_SFF); //should default to Fclk/2^14, but set it anyway as some chips don't seem to init properly //SMM (Set Multiplexing Mode) if (col) lcd_cmd(LCD_SMM | 0x04); //slave, 16 divs SYNC input R8 - R15 else lcd_cmd(LCD_SMM | 0x06); //master, 16 divs SYNC output R0 - R7 //SWM (Set Write Mode) lcd_cmd(LCD_SWM | 0x01); //write mode, auto decrement //SCML (Set Character Mode with Left entry) lcd_cmd(LCD_SCML); //left entry, character mode //DISP ON lcd_cmd(LCD_DISP_ON); //display ON } //----- subfunction to Turn on every display element, for testing void LiquidCrystal_7228::fillDisplay(void) { int i,j; for (j=0; j < num_rows; j++) { setCursor(0, j); for (i = 0; i < num_cols; i++) { lcd_cmd(LCD_WRCURS); //write cursor } } for (j=0; j < num_rows; j++) { setCursor(0, j); for (i = 0; i < num_cols; i++) { lcd_char(255); //write a solid block charater } } } //----- subfunction to Scroll line of text in reverse of current text direction void LiquidCrystal_7228::scrollBackward(void) { uint8_t i, current, save_cursor; char ist; ist = buffer[0][row]; current = col; //save current cursor position save_cursor = cursor_on; cursor_on = FALSE; setCursor(0, row); for (i = 1; i < num_cols; i++) { lcd_char(buffer[i][row]); //lcd_char prints char and updates the buffer too } if (wrap_on) { lcd_char(ist); //if wrapping back to start, scroll off one end and back on the other } else { lcd_char(' '); } setCursor(current, row); //restore cursor location cursor_on = save_cursor; } //----- subfunction to Scroll line of text in same direction as current text direction void LiquidCrystal_7228::scrollForward(void) { uint8_t i, current, save_cursor; char line[20]; for (i = 0; i < num_cols; i++) line[i] = buffer[i][row]; current = col; //save current cursor position save_cursor = cursor_on; cursor_on = FALSE; setCursor(0, row); if (wrap_on) { lcd_char(line[num_cols-1]); //if wrapping back to start, scroll off one end and back on the other } else { lcd_char(' '); } for (i = 1; i < num_cols; i++) { lcd_char(line[i - 1]); //lcd_char prints char and updates the buffer too } setCursor(current, row); //restore cursor location cursor_on = save_cursor; }