Learn C/C++ and assembly language with Arduino LCD display shield. Links to college courses in embedded programming.
Story
I bought the Artou version of this shield. My first move was to use an ohmmeter to buzz out the connections between the display unit and the IO pins to the Arduino board. Frabjous day! It matched the pins used by the DFRobot shield.
DFRobot LCD Shield has a wiki page with sample programs and documents. The buttons connect to analog input A0. Many IO pins from the Uno board are available as through-hole or headers.
Connections
We find out that RS Register Select is connected to Arduino pin 8 and Enable is on pin 9. Display pins D4, D5, D6 and D7 are wired to matching board pins 4, 5, 6 and 7. RW Ready/Wait is not connected and not used in software.
LiquidCrystal Library
Arduino comes, by default, with the LiquidCrystal library to use this display. We have an ATmega8 project using the same display and the same library but this time we need to change the default connection leads.
The examples in the library will work but we have to modify what is called a constructor.
Modify Constructor
In OOP object oriented programming we are instantiating a data object of type LiquidCrystal named lcd. The return value of lcd(variables) depends upon the bits transmitted on the various wires. All examples in the LiquidCrystal library expect 4 data leads and two control leads.
Replace the two lines that tell software which leads to use with this updated line. The DFrobot Shield and copies are wired this way. Compile the sketch immediately to check. It is during these edits that mistakes are made.
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
By including the LiquidCrystal library we add defined functions to exchange data with the display module. Overloading is a C++ feature where defined functions are ready for 8bit or 4bit operation.
Open Example
Menu File->Examples->LiquidCrystal->CustomCharacter locate the constructor line and update to correct wiring details. #include <LiquidCrystal.h> is an important command, it adds a library of code to control the display.
Upload to our Uno board and we should see a little animation. This updated sketch is attached to this project.
The Buttons
Look at the schematic and you see the reset that restarts the board and you see a ladder of resistances connecting to Analog Input 0. The voltage going to A0 depends upon which buttons are pressed.
Compile and upload the pacMan.ino sketch attached to this project. Goal is for PacMan to eat all the energy pills before the monster eats him. Move PacMan with your buttons and restart game with reset.
Next Steps
Go through all the examples in the LiquidCrystal library and change the constructor. Upload to board and watch results. Use the examples from DFRobot. Do Embedded Engineering project to compare C/C++, Arduino IDE and assembly language programming of this same display device.
DFRobotCustomChar.ino
// include the library code:
#include <LiquidCrystal.h>
// constructor modified for leads in DFRobot 1602 LCD Shield
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
// make some custom characters:
byte heart[8] = {
0b00000,
0b01010,
0b11111,
0b11111,
0b11111,
0b01110,
0b00100,
0b00000
};
byte smiley[8] = {
0b00000,
0b00000,
0b01010,
0b00000,
0b00000,
0b10001,
0b01110,
0b00000
};
byte frownie[8] = {
0b00000,
0b00000,
0b01010,
0b00000,
0b00000,
0b00000,
0b01110,
0b10001
};
byte armsDown[8] = {
0b00100,
0b01010,
0b00100,
0b00100,
0b01110,
0b10101,
0b00100,
0b01010
};
byte armsUp[8] = {
0b00100,
0b01010,
0b00100,
0b10101,
0b01110,
0b00100,
0b00100,
0b01010
};
void setup() {
// initialize LCD and set up the number of columns and rows:
lcd.begin(16, 2);
// create a new character
lcd.createChar(0, heart);
// create a new character
lcd.createChar(1, smiley);
// create a new character
lcd.createChar(2, frownie);
// create a new character
lcd.createChar(3, armsDown);
// create a new character
lcd.createChar(4, armsUp);
// set the cursor to the top left
lcd.setCursor(0, 0);
// Print a message to the lcd.
lcd.print("I ");
lcd.write(byte(0)); // when calling lcd.write() '0' must be cast as a byte
lcd.print(" Arduino! ");
lcd.write((byte)1);
}
void loop() {
// read the potentiometer on A0:
int sensorReading = analogRead(A0);
// map the result to 200 - 1000:
int delayTime = map(sensorReading, 0, 1023, 200, 1000);
// set the cursor to the bottom row, 5th position:
lcd.setCursor(4, 1);
// draw the little man, arms down:
lcd.write(3);
delay(delayTime);
lcd.setCursor(4, 1);
// draw him arms up:
lcd.write(4);
delay(delayTime);
}
pacMan.ino
//Pacman
//Written By: Jean Malha
//http://forum.snootlab.com/viewtopic.php?f=34&t=207
// https://github.com/dadecoza/arduino-lcd-keypad-shield-games/blob/5f6cf624a5ca7a7b83d6dccb28bbe4f933850f50/sketches/Pacman/Pacman.ino
//#include "Wire.h" // insertion de la librairie I2C (obligatoire)
//#include <Deuligne.h> // insertion de la librairie Deuligne (obligatoire)
#include <LiquidCrystal.h>
#define VITESSE_PAC 150
#define VITESSE_FANT 2000
#define MAXX 15
#define MAXY 1
#define btnRight 0
#define btnUp 1
#define btnDown 2
#define btnLeft 3
#define btnSelect 4
#define btnNone 5
void(* resetFunc) (void) = 0;
LiquidCrystal lcd(8, 9, 4, 5, 6, 7);
//Deuligne lcd; // Déclaration de l'objet lcd
// Charactère spécifique pacman
byte pacman[8] = {
B00000,
B00000,
B01110,
B11011,
B11100,
B01110,
B00000,
B00000
};
// Charactère spécifique fantome
byte fantome[8] = {
B00000,
B00000,
B01110,
B10101,
B11111,
B11111,
B10101,
B00000
};
byte point[8] = {
B00000,
B00000,
B00000,
B01110,
B01110,
B00000,
B00000,
B00000
};
// Tableau des points à manger
byte points[MAXX+1][MAXY+1];
int xpac=2; // Position de pacman en X (colone)
int ypac=1; //position de pacmanen y (ligne)
int xfant=15;// Position du fantome en X (colone)
int yfant=0;// Position du fantome en Y (ligne)
byte light=true; //Eclairage
long keystruck=0; //dernier appui sur un bouton
long poursuite=0; //dernier movement du fantome
byte partieEnCours=true; // pour eviter de boucler sur la fin
byte vide=false; // pour tester si tout est manger
byte level=0; // niveau
int score=0; // niveau
void bouge(int x,int y) // fonction pour bouger pacman
{
int oldx=xpac;
int oldy=ypac;
if (((xpac+x)>=0)&((xpac+x)<=MAXX)) xpac=xpac+x; //Si pas sorti d'ecran, on change x
if (((ypac+y)>=0)&((ypac+y)<=MAXY)) ypac=ypac+y;//Si pas sorti d'ecran, on change y
lcd.setCursor(xpac,ypac); // On se place en nouvelle position
lcd.write(byte(0)); // et on pose le caractere 0 (Pacman)
lcd.setCursor(oldx,oldy); // On se place en ancienne position
if ((xpac!=oldx)||(ypac!=oldy)) lcd.print(" "); // et on efface Pacman (s'il a bougé)
if(points[xpac][ypac]){
points[xpac][ypac]=false; // mange le truc
score++;
}
vide=true;
for (int i=0; i<=MAXX; i=i+1)
for (int j=0; j<=MAXY; j=j+1)
if (points[i][j]) vide=false;
if ((vide)&&(partieEnCours)) gagne();
}
void perdu(){
lcd.setCursor(0, 0); // on se place au point 0,0 (1ere ligne, 1er caractere)
lcd.print("***Game Over****"); // on écrit le début du texte de début
lcd.setCursor(0, 1); // on se place au point 0,1 (2eme ligne, 1er caractere)
lcd.print("***");
lcd.print(score);
lcd.print("***");
delay(2000);
resetFunc();
}
void gagne()
{
level++;
lcd.setCursor(0, 0); // on se place au point 0,0 (1ere ligne, 1er caractere)
lcd.print("*** Next level ***"); // on écrit le début du texte de début
lcd.setCursor(0, 1); // on se place au point 0,0 (1ere ligne, 1er caractere)
lcd.print("*** ");
lcd.print(level,DEC);
lcd.print(" ***"); // on écrit le début du texte de début
delay(2000); // 2 secondes de pause
initLevel(); //reinitialisation du tableau
}
void poursuis() // fonction pour bouger fantome
{
int oldx=xfant;
int oldy=yfant;
if (yfant<ypac) yfant=yfant+1;
else if (yfant>ypac) yfant=yfant-1;
else if (xfant<xpac) xfant=xfant+1;
else if (xfant>xpac) xfant=xfant-1;
lcd.setCursor(xfant,yfant); // On se place en nouvelle position
lcd.write(1); // et on pose le caractere 0 (Fantome)
lcd.setCursor(oldx,oldy); // On se place en ancienne position
if ((oldx!=xfant)||(oldy!=yfant)) // et on efface Fantome (s'il a bougé)
{
if (points[oldx][oldy]) lcd.write(2); // remplacé par un point si pas mangé
else lcd.print(" "); // remplacé par un espace si déja magé
}
}
//initialisation du tableau
void initLevel(){
for (int i=0; i<=MAXX; i=i+1)
for (int j=0; j<=MAXY; j=j+1){
points[i][j]=true; //initialisation du tableau des trucs à manger
lcd.setCursor(i-1, j-1); // on se place au point j,i
lcd.write(2); // on écrit les points
}
lcd.setCursor(xpac,ypac); // On se place en position de départ de pacman
lcd.write(byte(0)); // et on pose le caractere 0 (Pacman)
lcd.setCursor(xfant,yfant); // On se place en position de départ du fantome
lcd.write(1); // et on pose le caractere 1 (fantome)
poursuite=millis(); // On initialise le timer de poursuite (pour eviter un mouvement immédiat)
vide=false;
}
void setup() {
Serial.begin(9600);
//Wire.begin(); // initialisation I2C (obligatoire)
//lcd.init(); // initialisation LCD (obligatoire)
lcd.begin(16, 2);
lcd.createChar(0, pacman); // creation du caractere pacman et affectation au numéro 0
lcd.createChar(1, fantome); // creation du caractere de fantome et affectation au numéro 1
lcd.createChar(2, point); // creation du caractere de point et affectation au numéro 2
//lcd.backLight(true); // on allume le retro eclairage
lcd.setCursor(0, 0); // on se place au point 0,0 (1ere ligne, 1er caractere)
lcd.print("Pacman!"); // on écrit le début du texte de début
delay (5000); // Splash screen
initLevel(); // initialisation du tableau
}
void loop() {
int thisChar = Serial.read();
switch (thisChar)
{
case 'r':
lcd.scrollDisplayRight();
break;
case 'l':
lcd.scrollDisplayLeft();
break;
}
if ((thisChar>'a')&(thisChar<'z'))
{
lcd.setCursor(1,1);
lcd.write(thisChar);
}
if (millis()-keystruck>VITESSE_PAC) // Si plus de 200ms depuis le dernier mouvement de joystick
{
int joy=getKey();
switch (joy)
{
case btnNone:
break;
case btnLeft:
Serial.print("Pacman bouge à gauche.\n"); // envoi de controle sur liaison série
Serial.print(keystruck);
bouge(-1,0);// déplacement
keystruck=millis(); // remise à zero du timer de mouvement
break;
case btnRight:
Serial.print("Pacman bouge à droite\n");// envoi de controle sur liaison série
bouge(1,0);// déplacement
keystruck=millis(); // remise à zero du timer de mouvement
break;
case btnUp:
Serial.print("Pacman bouge en haut\n");// envoi de controle sur liaison série
bouge(0,-1);// déplacement
keystruck=millis(); // remise à zero du timer de mouvement
break;
case btnDown:
Serial.print("Pacman bouge en bas\n");
bouge(0,1);// déplacement
keystruck=millis(); // remise à zero du timer de mouvement
break;
/*case 4:
Serial.print("centre\n");
light=!light; //On inverse le statut d'allumage
lcd.backLight(light); // on applique
keystruck=millis(); // remise à zero du timer de mouvement
break;*/
default:
Serial.print(joy); //au cas ou...
keystruck=millis(); // remise à zero du timer de mouvement
};
};
if (millis()-poursuite>VITESSE_FANT/(level+1)+10)
{
poursuis();
poursuite=millis();
}
if ((xpac==xfant)&&(ypac==yfant)&&(partieEnCours))
{
perdu();
}
}
int getKey() {
int b = analogRead(A0);
if (b > 1000) return btnNone;
delay(8);
if (b < 50) return btnRight;
if (b < 180) return btnUp;
if (b < 330) return btnDown;
if (b < 520) return btnLeft;
if (b < 700) return btnSelect;
}