/* * lcdtest1.ino * Arduino Uno ATMEGA328 sketch * G. Forrest Cook, W0RIO * Started: May 10, 2024 * Version: 5 June, 2024 * Released under the GPLv3 license. * * This sketch creates a variety of different tests for a modular LCD. * A parallel input 2x20 line Newhaven NHD-0220DZ-FL-YBW LCD * (HD44780 compatible) is connected via a 74HC164 shift register * on the SPI port. * Change LCDCOL for different display widths. * R/W is wired low, RS goes to A4 (PortC-4), EN goes to A5 (PortC-5) * R/W is 0 for write, 1 for read, hard-wired to 0. * RS is 0 for command, 1 for data * E is 0 for idle 1 to 0 for falling edge trigger * Upper row addresses range: 0x80 to 0x93, bit 7 high is passed thru * Lower row addresses range: 0xC0 to 0xD3, bit 7 high is passed thru * * The serial output can be used for debugging purposes. * */ #include const int ledPin = 13; // onboard LED (also used by the SPI Clock) const int RSPin = A4; // LCD Regester Select (RS) const int ENPin = A5; // LCD Enable (EN) #define LCD_Cursor 2 // underline cursor on bit #define LCD_Blink 1 // cursor blink bit #define LCD_Incr 2 // shift direction bit (only R->L works) #define LCD_Shift 1 // shift enable bit (both rows shift together) #define LCDCOL 20 // LCD Display columns #define LCDROW 2 // LCD Display rows #define LCD_T0 1 // LCD shift register delay, uSecs #define LCD_T1 40 // fast LCD command delay, uSecs #define LCD_T2 2 // delay after slow commands, mSecs #define LCD_T3 20 // delay after power up, mSecs char scrollstr1[LCDCOL+1]; char scrollstr2[LCDCOL+1]; void setup() { SPI.setBitOrder(MSBFIRST); SPI.begin(); // SPI initializes the port B DDR register. Serial.begin(19200); // Initialize serial port, set baud rate pinMode(RSPin, OUTPUT); // define the two LCD control pins pinMode(ENPin, OUTPUT); digitalWrite(RSPin, LOW); // EN and RS low digitalWrite(ENPin, LOW); lcdsetup(); // Initialize the LCD lcd_clear(); Serial.println (F("LCD Test Version 1")); } void loop() // loop through the various tests { lcdbarchars(); lcdstring(0x80, "Upper String \1\2\3 \3\4\5"); lcdstring(0xC0, " "); delay(2000); lcdanimchars(); lcdstring(0x8C, " "); // erase just the user chars lcdstring(0xC0, "Lower String \1\2\3\4\5\6\7"); delay(2000); lcdstring(0x80, " "); lcdstring(0xC0, "Integer Counter "); integercount(); delay(1000); lcdstring(0xC0, " "); lcdstring(0x80, " "); lcdstring(0xC0, "Hexadecimal Counter "); hexwordcount(); delay(1000); lcdstring(0xC0, " "); lcdstring(0x80, "Dual Bar Graph: "); lcdstring(0xC0, " "); lcdbarchars(); dualbargraph(); delay(1000); lcdstring(0x80, " "); lcdstring(0xC0, "Simple Animation "); lcdanimchars(); lcdsquare(0x86); lcdstring(0x80, " "); lcdstring(0xC0, "Bouncing Ball "); lcdanimchars(); lcdbounceball(0x85, 2); // multi-cell bouncing ball delay(1000); clearscrollstr(); scrollstring(250, "Scroll Test:one two three four five six."); delay(1000); clearscrollstr(); lcdstring(0x80, "Enter Serial String "); lcdstring(0xC0, " "); scrollserial(250, 7); delay(1500); } // Create two opposite direction bar graphs and a decimal counter void dualbargraph() { int i; for (i=0; i<25; i++) // count up { lcdinteger(0x91, i); // addr is for the right digit lcdLRbargraph(0xC0, i); // write the left bargraph lcdRLbargraph(0xD3, i); // write the right bargraph delay(150); } for (i=23; i>=0; i--) // count down { if (i==9) lcdchar(0x90, ' '); // blank the left count digit lcdinteger(0x91, i); // addr is for the right digit lcdLRbargraph(0xC0, i); // write the left bargraph lcdRLbargraph(0xD3, i); // write the right bargraph delay(150); } } // simulate an expanding square pattern void lcdsquare(byte addr) { byte i; for (i=0; i<5; i++) { lcdchar(addr, '\0'); delay(175); lcdchar(addr, '\1'); delay(175); lcdchar(addr, '\2'); delay(175); lcdchar(addr, '\1'); delay(175); } lcdchar(addr, '\0'); delay(175); lcdchar(addr, ' '); } // simulate a bouncing ball with gravity // use 6 character cells L->R and R->L void lcdbounceball(byte addr, byte num) { byte i; for (i=0; i0) // loop until newline or timeout { inchar = Serial.read(); // this returns 0 until chars are received if (inchar > 0) // wait for serial input { tc=tout; // reset timout if (inchar != '\n' && inchar != '\r') // scroll chars until newline { schar = scrollline (0xC0, scrollstr1, (char)inchar); // lower line scrollline(0x80, scrollstr2, schar); // upper line } } delay(dtime); tc--; } if (tc==0) { lcdstring(0x80, "Serial Input Timout "); lcdstring(0xC0, " "); } } // scroll a string across 2 lines of the LCD, bottom to top void scrollstring(int dtime, char *str) { char schar; while (*str) { schar = scrollline (0xC0, scrollstr1, *str++); // fill lower line scrollline(0x80, scrollstr2, schar); // fill upper line delay(dtime); } } // Scroll chars R->L on one line, output the line. char scrollline(byte addr, char *scrollstr, char inchr) { char retchr; byte i; retchr = scrollstr[0]; // save left char for the next line for (i=0; i>8)); } } // Display an integer counter void integercount() { int count; for (count=-110; count<111; count++) { lcdnum_clear(0x8A, 4); // clear the digits and - sign lcdinteger(0x8A, count); delay(50); } } // Write an integer to the LCD with variable character width. // addr is for the right digit // this doesn't overwrite the higher digits, // call lcdnum_clear first to clear the previous digits. void lcdinteger(byte addr, int val) { if (val >= 0) // check for negative numbers lcdintegerdigs(addr, val, 0); else lcdintegerdigs(addr, 0-val, 1); } // recursively display integer digits void lcdintegerdigs(byte addr, int val, byte neg) { int dividend; lcdchar(addr, val%10 + '0'); dividend = val / 10; if (dividend > 0) lcdintegerdigs(addr-1, dividend, neg); // recurse else if (neg) // deal with the minus sign lcdchar(--addr, '-'); } // clear a specified number of counter digits (integer or hex) // starting with the right-most character and moving left void lcdnum_clear(byte addr, byte digits) { do { lcdchar(addr--, 0x20); digits--; } while (digits); } // Write a 2 digit hexadecimal value to the LCD. void lcdhexbyte(byte addr, byte val) // addr is MSD position { byte nibble; nibble = val >> 4; if (nibble < 10) nibble += '0'; else nibble += '7'; lcdchar(addr, nibble); nibble = val & 0x0F; if (nibble < 10) nibble += '0'; else nibble += '7'; lcdchar(addr+1, nibble); } // output an 8 character wide L->R bargraph // addr is left char address, bars range from 0-24 void lcdLRbargraph(byte addr, int bars) { if (bars > 24) bars = 24; // LCD bargraph data, 0x20 is blank, // 1 is 1 L-bar, 2 is 2 L-bars, 3 is 3 bars, 4 is 2 R-bars, 5 is 1 R-bar. static byte bgdata1[25] = {0x20,1,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3}; static byte bgdata2[25] = {0x20,0x20,0x20,0x20,1,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3}; static byte bgdata3[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,1,2,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3}; static byte bgdata4[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,1,2,3,3,3,3,3,3,3,3,3,3,3,3,3}; static byte bgdata5[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,1,2,3,3,3,3,3,3,3,3,3,3}; static byte bgdata6[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,1,2,3,3,3,3,3,3,3}; static byte bgdata7[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,1,2,3,3,3,3}; static byte bgdata8[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,1,2,3}; lcdchar(addr++, bgdata1[bars]); lcdchar(addr++, bgdata2[bars]); lcdchar(addr++, bgdata3[bars]); lcdchar(addr++, bgdata4[bars]); lcdchar(addr++, bgdata5[bars]); lcdchar(addr++, bgdata6[bars]); lcdchar(addr++, bgdata7[bars]); lcdchar(addr++, bgdata8[bars]); } // output an 8 character wide R->L bargraph // addr is right char address, bars range from 0-24 void lcdRLbargraph(byte addr, int bars) { if (bars > 24) bars = 24; // LCD bargraph data, 0x20 is blank, // 1 is 1 L-bar, 2 is 2 L-bars, 3 is 3 bars, 4 is 2 R-bars, 5 is 1 R-bar. static byte bgdata1[25] = {0x20,5,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3}; static byte bgdata2[25] = {0x20,0x20,0x20,0x20,5,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3}; static byte bgdata3[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,5,4,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3,3}; static byte bgdata4[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,5,4,3,3,3,3,3,3,3,3,3,3,3,3,3}; static byte bgdata5[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,5,4,3,3,3,3,3,3,3,3,3,3}; static byte bgdata6[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,5,4,3,3,3,3,3,3,3}; static byte bgdata7[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,5,4,3,3,3,3}; static byte bgdata8[25] = {0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,0x20,5,4,3}; lcdchar(addr--, bgdata1[bars]); lcdchar(addr--, bgdata2[bars]); lcdchar(addr--, bgdata3[bars]); lcdchar(addr--, bgdata4[bars]); lcdchar(addr--, bgdata5[bars]); lcdchar(addr--, bgdata6[bars]); lcdchar(addr--, bgdata7[bars]); lcdchar(addr--, bgdata8[bars]); } void lcdstring(byte addr, char *str) // put a string at LCD addr { while (*str) lcdchar(addr++, *str++); } void lcd_clear() // Blank the LCD { lcdcmd(0x01); // clear display (1.52ms) delay(2); lcdcmd(0x02); // return home (1.52mS) delay(2); } // write a char to the specified LCD address // inline version of lcdcmd(); and lcddat(); for speed void lcdchar(byte addr, byte chr) { digitalWrite(ENPin, HIGH); // EN high digitalWrite(RSPin, LOW); // RS low SPI.transfer(addr); // Shift out the command byte delayMicroseconds(LCD_T0); digitalWrite(ENPin, LOW); // EN low delayMicroseconds(LCD_T1); digitalWrite(ENPin, HIGH); // EN high digitalWrite(RSPin, HIGH); // RS high SPI.transfer(chr); // Shift out the data byte delayMicroseconds(LCD_T0); digitalWrite(ENPin, LOW); // EN low delayMicroseconds(LCD_T1); } void lcdcmd(byte command) // Send a command byte to the LCD { digitalWrite(ENPin, HIGH); // EN high digitalWrite(RSPin, LOW); // RS low SPI.transfer(command); // Shift out the command byte delayMicroseconds(LCD_T0); digitalWrite(ENPin, LOW); // EN low delayMicroseconds(LCD_T1); } void lcddat(byte data) // Send a data byte to the LCD { digitalWrite(ENPin, HIGH); // EN high digitalWrite(RSPin, HIGH); // RS high SPI.transfer(data); // Shift out the data byte delayMicroseconds(LCD_T0); digitalWrite(ENPin, LOW); // EN low delayMicroseconds(LCD_T1); } void lcdsetup() // Initialize the LCD { delay(LCD_T3); // delay at power-up lcdcmd(0x38); // 8 bit i/o, 2 lines, 5x8 font (38uS) delayMicroseconds(LCD_T1); lcdcmd(0x01); // clear display (1.52ms) delay(LCD_T2); lcdcmd(0x02); // return home (1.52mS) delay(LCD_T2); lcdcmd(0x04); // cursor dir and display shift (38uS) delayMicroseconds(LCD_T1); lcdcmd(0x0C); // Display On, Cursor Off, Blink off } void lcdcgram(byte addr, byte *chardata) // Set one User-defined LCD char { byte cgaddr = (addr & 7) << 3; int i; for (i=0; i<8; i++) // count through the rows { lcdcmd(0x40 | cgaddr++); // Set the 6 bit CGRAM addr (top->bot) lcddat(chardata[i] & 0x1F); // send one line of char data } } // Define several sets of user-defined LCD characters (up to 8). // Note that \1 through \7 characters work in strings but \0 doesn't // because that is the end of string character. // Characters already on the screen will change with new cgram data // Bar graph chars for L to R and R to L bargraphs void lcdbarchars() { static byte cgchar1[8]={0x10,0x10,0x10,0x10,0x10,0x10,0x10,0x00}; // 1 bar L static byte cgchar2[8]={0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x00}; // 2 bars L static byte cgchar3[8]={0x15,0x15,0x15,0x15,0x15,0x15,0x15,0x00}; // 3 bars static byte cgchar4[8]={0x05,0x05,0x05,0x05,0x05,0x05,0x05,0x00}; // 2 bars R static byte cgchar5[8]={0x01,0x01,0x01,0x01,0x01,0x01,0x01,0x00}; // 1 bar R lcdcgram(1, cgchar1); // Write the chars to LCD character generator RAM lcdcgram(2, cgchar2); lcdcgram(3, cgchar3); lcdcgram(4, cgchar4); lcdcgram(5, cgchar5); } // Animation characters at different heights void lcdanimchars() { static byte cgchar0[8]={0x00,0x00,0x00,0x04,0x00,0x00,0x00,0x00}; // dot static byte cgchar1[8]={0x00,0x00,0x0E,0x0A,0x0E,0x00,0x00,0x00}; // sm sq static byte cgchar2[8]={0x00,0x1F,0x11,0x11,0x11,0x1F,0x00,0x00}; // big sq static byte cgchar3[8]={0x0C,0x12,0x12,0x0C,0x00,0x00,0x00,0x00}; // degree static byte cgchar4[8]={0x00,0x0C,0x12,0x12,0x0c,0x00,0x00,0x00}; // deg2 static byte cgchar5[8]={0x00,0x00,0x0C,0x12,0x12,0x0C,0x00,0x00}; // deg3 static byte cgchar6[8]={0x00,0x00,0x00,0x0C,0x12,0x12,0x0C,0x00}; // deg4 static byte cgchar7[8]={0x00,0x00,0x00,0x00,0x0C,0x12,0x12,0x0C}; // deg5 // Write the chars to LCD character generator RAM lcdcgram(0, cgchar0); lcdcgram(1, cgchar1); lcdcgram(2, cgchar2); lcdcgram(3, cgchar3); lcdcgram(4, cgchar4); lcdcgram(5, cgchar5); lcdcgram(6, cgchar6); lcdcgram(7, cgchar7); }