Simple Scoreboard with a 4x20 LCD and BIG numerals

I had some more antique hardware laying around so I made a simple scoreboard. If it can Blynk - use it! :wink:

It’s very basic, but still useful for my needs:

  • Track 2 different scores
  • Input 2 player names
  • Large numerals (they are 3 lines high)
  • Reset and start over

Hardware:

  • Arduino UNO R3
  • 4x20 LCD with HD44780 compatible controller (mine is actually a SPLC780 from Sunplus)
  • ENC28J60 ethernet :nauseated_face:

Not added yet:

  • 5 physical buttons

Pics from app and the LCD itself:

Scoreboard_LCD

Code:

/*

2018-01-10 00:00:00

A SIMPLE 4x20 LCD SCOREBOARD 

Using Arduino UNO, ENC28J60 ethernet, HD44780 compatible controller and custom glyphs for displaying larger numbers.

The very clever way to display BIG numbers with the HD44780 controller was stolen from:
http://woodsgood.ca/projects/2015/01/16/large-numbers-on-small-displays/

ENC28J60 wiring:

CS -  Pin 10
SI -  Pin 11  (= ST)
SO -  Pin 12
SCK - Pin 13
INT - Pin 2   (= LNT)

*/

//============

#include <LiquidCrystal.h>
#include <UIPEthernet.h>
#include <BlynkSimpleUIPEthernet.h>

//============ For splitting numbers to digits

byte  num100s;
byte   num10s;
byte    num1s;

//============ LCD setup and custom glyphs

const int rs = 7, en = 6, d4 = 5, d5 = 4, d6 = 3, d7 = 8;   // NOTE! d7 is not the standard pin 2 since UIP needs that for INT.

LiquidCrystal lcd(rs, en, d4, d5, d6, d7);

byte x10[8] = {0x00,0x00,0x00,0x00,0x00,0x07,0x07,0x07};
byte x11[8] = {0x00,0x00,0x00,0x00,0x00,0x1C,0x1C,0x1C};
byte x12[8] = {0x00,0x00,0x00,0x00,0x00,0x1F,0x1F,0x1F};
byte x13[8] = {0x07,0x07,0x07,0x07,0x07,0x1F,0x1F,0x1F};
byte x14[8] = {0x1C,0x1C,0x1C,0x1C,0x1C,0x1F,0x1F,0x1F};
byte x15[8] = {0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C,0x1C};
byte x16[8] = {0x07,0x07,0x07,0x07,0x07,0x07,0x07,0x07};
byte x17[8] = {0x00,0x00,0x0E,0x0E,0x0E,0x00,0x00,0x00};

byte row = 0,col = 0;

//============ Network

char auth[] = "";

byte arduino_mac[] = { 0xAE, 0xED, 0xBA, 0xFE, 0xFE, 0xCA };

IPAddress arduino_ip (192, 168,   1,  24);
IPAddress dns_ip     (192, 168,   1,   1);
IPAddress gateway_ip (192, 168,   1,   1);
IPAddress subnet_mask(255, 255, 255,   0);

//============ Misc

WidgetTerminal terminal(V0);
byte playerNo = 0;

//============ Setup

void setup() {

	Blynk.begin(auth, "", 8442, arduino_ip, dns_ip, gateway_ip, subnet_mask, arduino_mac);

	while (Blynk.connect() == false) {
		// Wait until connected
	}

	lcd.begin(20, 4);

	lcd.createChar(0, x10);
	lcd.createChar(1, x11);
	lcd.createChar(2, x12);
	lcd.createChar(3, x13);
	lcd.createChar(4, x14);
	lcd.createChar(5, x15);
	lcd.createChar(6, x16);
	lcd.createChar(7, x17);			// colon

	terminal.println(F("Blynk v" BLYNK_VERSION ": Device started"));
	terminal.println(F("-------------"));
	terminal.println("");
	terminal.flush();

	newGame(); 
}

//============ Get player names from terminal

BLYNK_WRITE(V0) {
	
	if (playerNo == 0) {
		String playerName = param.asStr();
		playerName.remove(9);		// Trim the Strings to 9 chars so they won't screw up the numbers on the next row.
		lcd.setCursor(0,0);
		lcd.print(playerName);
		terminal.println("");
		terminal.println("Enter player 2's name");
		terminal.flush();
		playerNo++;
	}
	
	else if (playerNo == 1) {
		String playerName = param.asStr();
		playerName.remove(9);     
		lcd.setCursor(11,0);
		lcd.print(playerName);
		terminal.println("");
		terminal.println("Rack 'em up!");
		terminal.flush();
		playerNo++;					// Now we ignore input from terminal
	}
}

//============ PLAYER 1 score button

BLYNK_WRITE(V1) {
	
	int valP1 = param.asInt();
	Blynk.virtualWrite(V11, valP1);
	updateNumberP1(valP1);
}

//============ PLAYER 2 score button

BLYNK_WRITE(V2) {
	
	int valP2 = param.asInt();
	Blynk.virtualWrite(V12, valP2);
	updateNumberP2(valP2);
}

//============ PLAYER 1 dispaly score on LCD

void updateNumberP1(int numP1) {

	row = 1;

	if (numP1 >= 100) {				// Need to split the current score (number) to its individual digits
		num100s = numP1 / 100;		
		num10s = numP1 % 100 / 10;
		num1s = numP1 % 10;

		doNumber( num100s,row,0);
		doNumber( num10s,row,3);
		doNumber( num1s,row,6);
	}
	else if (numP1 >= 10) {
		num10s = numP1 / 10;
		num1s = numP1 % 10;

		doNumber( num10s,row,3);
		doNumber( num1s,row,6);
	}
	else {
		doNumber( numP1,row,6);
	}
}

//============ PLAYER 2 dispaly score on LCD

void updateNumberP2(int numP2) {

	row = 1;

	if (numP2 >= 100) {
		num100s = numP2 / 100;
		num10s = numP2 % 100 / 10;
		num1s = numP2 % 10;

		doNumber( num100s,row,11);
		doNumber( num10s,row,14);
		doNumber( num1s,row,17);
	}
	else if (numP2 >= 10) {
		num10s = numP2 / 10;
		num1s = numP2 % 10;

		doNumber( num10s,row,14);
		doNumber( num1s,row,17);
	}
	else {
		doNumber( numP2,row,17);
	}
}

//============ Button to start a new game

BLYNK_WRITE(V3) {
	
	if (param.asInt()) {
		newGame();
	}
}

//============ Start a new game

void newGame() {

	playerNo = 0;               	// Start reading input from terminal again
	resetValues();

	terminal.println("Enter player 1's name");
	terminal.flush();
  
	// Display 0 : 0
	row = 1;
	doNumber( 0,row,6);
	doNumber(11,row,9);         	
	doNumber( 0,row,17);
}

//============ Reset values to 0 in app and clear LCD and terminal

void resetValues() {

	for (int i = 0; i <= 24; i++) {
	terminal.println("");			// "clear screen" in app.
	}
	terminal.flush();

	lcd.clear();

	Blynk.virtualWrite(V1, 0);
	Blynk.virtualWrite(V2, 0);
	Blynk.virtualWrite(V11, 0);
	Blynk.virtualWrite(V12, 0);

	}

//*****************************************************************************************//
// doNumber: routine to position number 'num' starting top left at row 'r', column 'c'
//*****************************************************************************************//

void doNumber(byte num, byte r, byte c) {
	lcd.setCursor(c,r);
	switch(num) {
		case 0: lcd.write(byte(2)); lcd.write(byte(2));
			lcd.setCursor(c,r+1); lcd.write(byte(5)); lcd.write(byte(6));
			lcd.setCursor(c,r+2); lcd.write(byte(4)); lcd.write(byte(3)); break;

		case 1: lcd.write(byte(0)); lcd.write(byte(1));
			lcd.setCursor(c,r+1); lcd.print(" "); lcd.write(byte(5));
			lcd.setCursor(c,r+2); lcd.write(byte(0)); lcd.write(byte(4)); break;

		case 2: lcd.write(byte(2)); lcd.write(byte(2));
			lcd.setCursor(c,r+1); lcd.write(byte(2)); lcd.write(byte(3));
			lcd.setCursor(c,r+2); lcd.write(byte(4)); lcd.write(byte(2)); break;

		case 3: lcd.write(byte(2)); lcd.write(byte(2));
			lcd.setCursor(c,r+1); lcd.write(byte(0)); lcd.write(byte(3));
			lcd.setCursor(c,r+2); lcd.write(byte(2)); lcd.write(byte(3)); break;

		case 4: lcd.write(byte(1)); lcd.write(byte(0));
			lcd.setCursor(c,r+1); lcd.write(byte(4)); lcd.write(byte(3));
			lcd.setCursor(c,r+2); lcd.print(" "); lcd.write(byte(6)); break;

		case 5: lcd.write(byte(2)); lcd.write(byte(2));
			lcd.setCursor(c,r+1); lcd.write(byte(4)); lcd.write(byte(2));
			lcd.setCursor(c,r+2); lcd.write(byte(2)); lcd.write(byte(3)); break;
	
		case 6: lcd.write(byte(1)); lcd.print(" ");
			lcd.setCursor(c,r+1); lcd.write(byte(4)); lcd.write(byte(2));
			lcd.setCursor(c,r+2); lcd.write(byte(4)); lcd.write(byte(3)); break;

		case 7: lcd.write(byte(2)); lcd.write(byte(2));
			lcd.setCursor(c,r+1); lcd.print(" "); lcd.write(byte(6));
			lcd.setCursor(c,r+2); lcd.print(" "); lcd.write(byte(6)); break;

		case 8: lcd.write(byte(2)); lcd.write(byte(2));
			lcd.setCursor(c,r+1); lcd.write(byte(4)); lcd.write(byte(3));
			lcd.setCursor(c,r+2); lcd.write(byte(4)); lcd.write(byte(3)); break;

		case 9: lcd.write(byte(2)); lcd.write(byte(2));
			lcd.setCursor(c,r+1); lcd.write(byte(4)); lcd.write(byte(3));
			lcd.setCursor(c,r+2); lcd.print(" "); lcd.write(byte(6)); break;

		case 11: lcd.setCursor(c,r+1); lcd.write(byte(7)); lcd.setCursor(c,r+2); lcd.write(byte(7)); break;
	}
}

//============ Main loop

void loop() {
	Blynk.run();
}

Feedback, suggestions, improvements and cookies are most welcome! :smiley:

3 Likes