/* * 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 * 20180131 Version 1.10 - added data plot mode * 20180201 Version 1.20 - added bar graph mode * 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; data_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; } //***** function to Turn off Data writing mode void LiquidCrystal_7228::noDataMode(void) { uint8_t save_col; uint8_t save_row; save_col = col; save_row = row; data_on = FALSE; setCursor(0,0); //left chip if (text_dir == left) { lcd_cmd(LCD_SCML); //left entry, character mode } else { lcd_cmd(LCD_SCMR); //right entry, character mode } setCursor(num_cols/2,0); //right chip if (text_dir == left) { lcd_cmd(LCD_SCML); //left entry, character mode } else { lcd_cmd(LCD_SCMR); //right entry, character mode } setCursor(save_col,save_row); } //***** function to Turn on Data writing mode void LiquidCrystal_7228::dataMode(uint8_t auto_mode) { data_on = TRUE; //set data mode for (uint8_t chip=left; chip<=right; chip++) { digitalWrite(_select_pin, LOW); write_nibble((LCD_SWM | auto_mode) >> 4, chip, cmd_flag ); write_nibble((LCD_SWM | auto_mode) , chip, cmd_flag ); delayMicroseconds(4); while(!digitalRead(_busy_pin)) { } digitalWrite(_select_pin, HIGH); } } void LiquidCrystal_7228::dataPlot(uint8_t x, uint8_t y, uint8_t plot_type) { uint8_t chip, data_col, data_row, bank; //As an x,y data field there are blank columns between characters and blank rows //between characters and blank rows between the characters and their cursor underscore //where we can't display a pixel. This produces the odd exceptions coded for here. //There are 20 chars x 5 pixels plus 19 intercharacter spaces = 119 columns total. //There are 2 lines of 7 pixel high characrers plus their blank row above the cursor row //plus the cursor row, additionally two blank rows betweeen the character lines = 20 rows total. /* y,bit#, columns * 49 48 47 46 45 44 43 42 ... * 19 0 O O O O O O O bank 0 * 18 1 O O O O O O O * 17 2 O O O O O O O * 16 3 O O O O O O O * 15 4 O O O O O O O * 14 5 O O O O O b O O * 13 6 O O O O O l O O * 12 blank O O O O O a O O * 11 7 O O O O O n O O ________ * 10 blank O O O O O k O O bank 1 * 09 blank O O O O O O O * 08 0 O O O O O O O * 07 1 O O O O O O O * 06 2 O O O O O O O * 05 3 O O O O O O O * 04 4 O O O O O O O * 03 5 O O O O O O O * 02 6 O O O O O O O * 01 blank O O O O O O O * 00 7 O O O O O O O */ if ((x<120) && (y<20) && (x>=0) && (y>=0)) { //only do anything if data in plottable range if (x<60) { chip = left; } else { chip = right; x = x-60; } if (((x+1) % 6) == 0) { //do nothing as in vertical inter-character space } else { data_col = 49-(x-((x+1)/6)); if (1==1) { //originally didn't do an processing if on blank rows, but actually need to // clear/set lower bank etc // if ((y!=1) && (y!=9) && (y!=10) && (y!=12)) { //not on blank row if (y<11) { bank = 1; } else { bank = 0; } data_row = y; if (y>=11) { data_row = data_row - 11; //handle top and bottom 8 lines the same, just switch bank } if (data_row >0) data_row--; //skip 1 row, the gap between the cursor and character //set data cursor column position for next write digitalWrite(_select_pin, LOW); write_nibble((LCD_LDPI | bank << 6 | data_col) >> 4, chip, cmd_flag ); write_nibble((LCD_LDPI | bank << 6 | data_col) , chip, cmd_flag ); delayMicroseconds(4); while(!digitalRead(_busy_pin)) { } digitalWrite(_select_pin, HIGH); //either plot a verticlal bar, or a single pixel if (plot_type == 1) { //work out pattern to write for vertical bar int tmp = ~((0x01 << (7-data_row)) -1); write_data( tmp, chip ); } else { //plot a single point if ((y!=1) && (y!=9) && (y!=10) && (y!=12)) { write_data( 0x01 << (7-data_row), chip ); } else { write_data( 0x00, chip ); //plotting on blank row, need to clear other bits in the column } } //clear column on other line bank (top or bottom) //first position cursor on other bank digitalWrite(_select_pin, LOW); write_nibble((LCD_LDPI | (1-bank) << 6 | data_col) >> 4, chip, cmd_flag ); write_nibble((LCD_LDPI | (1-bank) << 6 | data_col) , chip, cmd_flag ); delayMicroseconds(4); while(!digitalRead(_busy_pin)) { } digitalWrite(_select_pin, HIGH); if ((bank==0) && (plot_type==1)) { //if data was plotted into bank 0, and it was of the bar type write_data( 0xFF, chip ); //plot rest of bar in lower bank (cursor already re-positioned) } else { write_data( 0x00, chip ); //single datapoint plot; lower bank of the screen is just cleared } } } } } void LiquidCrystal_7228::barGraph(uint8_t x, uint8_t bank) { uint8_t chip, bar_col, display_col; for (bar_col=0; bar_col < 40; bar_col++) { if (bar_col <= x ) { if (bar_col < 20) { chip = left; display_col = 49 - ((bar_col*3) - (bar_col / 2)); } else { chip = right; display_col = 49 - (((bar_col-20)*3) - ((bar_col-20) / 2)); } //position display's cursor digitalWrite(_select_pin, LOW); write_nibble((LCD_LDPI | (bank) << 6 | display_col) >> 4, chip, cmd_flag ); write_nibble((LCD_LDPI | (bank) << 6 | display_col) , chip, cmd_flag ); delayMicroseconds(4); while(!digitalRead(_busy_pin)) { } digitalWrite(_select_pin, HIGH); //write 2 column bar write_data( 0x7F, chip ); //write line of bar and auto inc write_data( 0x7F, chip ); //write line of bar and auto inc } else { //blank reset of line if (bar_col < 20) { chip = left; display_col = 49 - ((bar_col*3) - (bar_col / 2)); } else { chip = right; display_col = 49 - (((bar_col-20)*3) - ((bar_col-20) / 2)); } //position display's cursor digitalWrite(_select_pin, LOW); write_nibble((LCD_LDPI | (bank) << 6 | display_col) >> 4, chip, cmd_flag ); write_nibble((LCD_LDPI | (bank) << 6 | display_col) , chip, cmd_flag ); delayMicroseconds(4); while(!digitalRead(_busy_pin)) { } digitalWrite(_select_pin, HIGH); //write 2 column bar write_data( 0x00, chip ); //write line of bar and auto inc write_data( 0x00, chip ); //write line of bar and auto inc } } } /*********** 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); void write_data(uint8_t aByte, uint8_t chip); */ //----- 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 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 //turn off autoscroll flag while scrolling as it recursively calls this function autoscroll_on = FALSE; 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 } } //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) { //if wrapping back to start, scroll off one end and back on the other lcd_char(line[num_cols-1]); } else { lcd_char(' '); } for (i = 1; i < num_cols; i++) { //lcd_char prints char and updates the buffer too lcd_char(line[i - 1]); } setCursor(current, row); //restore cursor location cursor_on = save_cursor; } //subfunction to Write 8 bit data to screen void LiquidCrystal_7228::write_data(uint8_t aByte, uint8_t chip) { //set data mode /* code moved to dataMode() digitalWrite(_select_pin, LOW); write_nibble((LCD_SWM | auto_none) >> 4, chip, cmd_flag ); write_nibble((LCD_SWM | auto_none) , chip, cmd_flag ); delayMicroseconds(4); while(!digitalRead(_busy_pin)) { } digitalWrite(_select_pin, HIGH); */ //write the data digitalWrite(_select_pin, LOW); write_nibble(aByte >> 4, chip, data_flag ); write_nibble(aByte, chip, data_flag ); delayMicroseconds(4); while(!digitalRead(_busy_pin)) { } digitalWrite(_select_pin, HIGH); }