Okay, brand new deluxe version of the Ghostbusters G-Meter with SOUND!!!! I added a piezo speaker to the rig, and now it beeps and buzzes and plays tunes.
I've also added new on-screen effects to the different modes.
Here's the new, updated version of the code.
/*
Arduino 2x16 LCD - SPENGLER G-METER
October 14, 2021 Version 4 with animated scanner and SOUNDS!!
by Bruce Wright
Free use for fans. Change whatever you want. Share for free only.
*/
//at the top of the program, I'm setting up some variables. A list of messages to print
//and some values I'll need deeper in the program
int spea = 12;
float tempo =.5;
// make some custom characters:
byte ball0[8] = {
0b00000,
0b11111,
0b01110,
0b00100,
0b00100,
0b01110,
0b11111,
0b00000
};
byte ball1[8] = {
0b00000,
0b00100,
0b01100,
0b11100,
0b01101,
0b00110,
0b00100,
0b00000
};
byte ball2[8] = {
0b00000,
0b00000,
0b10001,
0b11011,
0b11011,
0b10001,
0b00000,
0b00000
};
byte ball3[8] = {
0b00000,
0b00100,
0b00110,
0b00111,
0b10110,
0b01100,
0b00100,
0b00000
};
//This is a string array. It's a list of all the bottom line text that each mode prints.;
//There are 4 sets of 10 messages. message 0-9 for the bottom button, 10-19 right, 20-29 top, 30-39 left;
char *myString1[] = {" 212-555-2368 ", "ghostbusters.com", " no job too big ", " no FEE too big ",
"?STrAnGe NoIsEs?", "??CoLd SpOtS?!??", "?FeAr or dREaD?!", " Ready2BelieveU ",
"WHO U GONNA CALL", " 24 HOURS CALL: ",
"EMF: nominal", "EVP LvL: quiet", "EVP LvL: quiet", "EVP LvL: BUMP",
"ECTO COUNT: low", "ECTO COUNT: low", "ECTO COUNT: med", "ECTO COUNT: med",
"ECTO COUNT: HIGH", "ECTO COUNT: med",
"CYCLOTRON: 13khz", "NUTRONAWAND: 73%", "POWER CELL: 85%", "SLIME CHARGE:3++",
"SLIME Lvl:RISING", "CONTAINMENT: 30%", "TWINKIE: BIG", "INJECTORS: CLEAR",
"SLO-BLO: OFFLINE", "N-FILTER: VENTED",
"Apparition 2", "----DETECTING---", "Full Torso 5", "----DETECTING---",
"Free-Floating 6", "----DETECTING---", "Spectral mist 1", "UNKNOWN UNK",
"NonCorporeal 3", "----DETECTING---"
};
char *myString2[] = {"----SCANNING----", "--->SCANNING<---", "-->>SCANNING<<--", "->>>SCANNING<<<-",
">>>>SCANNING<<<<", ">>>-SCANNING-<<<", ">>--SCANNING--<<", ">---SCANNING---<"
};
char *myString3[] = {"--GHOSTBUSTERS--", "--GHOSTBUSTERS--", "-<GHOSTBUSTERS>-", "-<GHOSTBUSTERS>-",
"<<GHOSTBUSTERS>>", "<<GHOSTBUSTERS>>", "<-GHOSTBUSTERS->", "<-GHOSTBUSTERS->"
};
//each mode allows you to change the speed setting. That's the speed it prints at. Here we start at speed 2
//smaller numbers are faster. I guess I should have called it myDelay instead!!!
int mySpeed =2;
// first cycling mode will be printing pke numbers. So I turn that mode on with this variable
int numbers =1;
//initialize top animated text to be on
int scanning = 1;
//initialize typing animated text to be off
int typing = 0;
int pkeInt = 0;
#include "pitches.h" // must include open source pitches.h found online in libraries folder or make a new tab => https://www.arduino.cc/en/Tutorial/toneMelody
int playGb1(float tempo){
int quarter = 2000 * tempo;
int eighth = 1000 * tempo;
int sixteenth = 500 * tempo;
int thirtysecond = 250* tempo;
int rest1 = 260* tempo;
int rest2 = 400 * tempo;
int rest3 = 800 * tempo;
tone(spea,NOTE_B5,thirtysecond);
delay(rest1);
tone(spea,NOTE_B5,thirtysecond);
delay(rest1);
tone(spea,NOTE_DS6,sixteenth);
delay(rest2);
tone(spea,NOTE_B5,sixteenth);
delay(rest2);
tone(spea,NOTE_CS6,sixteenth);
delay(rest2);
tone(spea,NOTE_A4,sixteenth);
delay(rest2);
delay(rest2);
delay(rest2);
delay(rest2);
delay(rest1);
delay(rest1);
tone(spea,NOTE_B5,thirtysecond);
delay(rest1);
tone(spea,NOTE_B5,thirtysecond);
delay(rest1);
tone(spea,NOTE_B5,thirtysecond);
delay(rest1);
tone(spea,NOTE_B5,thirtysecond);
delay(rest1);
tone(spea,NOTE_A4,eighth);
delay(rest1);
delay(rest1);
tone(spea,NOTE_B5,sixteenth);
delay(rest2);
}
int playGb2(float tempo){
int quarter = 2000 * tempo;
int eighth = 1000 * tempo;
int sixteenth = 500 * tempo;
int thirtysecond = 250* tempo;
int rest1 = 260* tempo;
int rest2 = 400 * tempo;
int rest3 = 800 * tempo;
tone(spea,NOTE_B4,eighth);
delay(rest2);
tone(spea,NOTE_A5,eighth);
delay(rest2);
tone(spea,NOTE_GS5,eighth);
delay(rest2);
tone(spea,NOTE_F5,eighth);
delay(rest2);
}
int playGb3(float tempo){
int quarter = 2000 * tempo;
int eighth = 1000 * tempo;
int sixteenth = 500 * tempo;
int thirtysecond = 250* tempo;
int rest1 = 260* tempo;
int rest2 = 400 * tempo;
int rest3 = 800 * tempo;
tone(spea,NOTE_E5,quarter);
delay(rest2);
tone(spea,NOTE_F6,quarter);
delay(rest2);
tone(spea,NOTE_CS6,eighth);
delay(rest2);
}
int playGb4(float tempo){
int half = 4000 * tempo;
int quarter = 2000 * tempo;
int eighth = 1000 * tempo;
int sixteenth = 500 * tempo;
int thirtysecond = 250* tempo;
int rest1 = 260* tempo;
int rest2 = 400 * tempo;
int rest3 = 800 * tempo;
tone(spea,NOTE_C5,sixteenth);
delay(rest3);
//delay(rest1);
tone(spea,NOTE_C5,eighth);
delay(rest3);
tone(spea,NOTE_DS5,eighth);
delay(rest2);
tone(spea,NOTE_E5,eighth);
delay(rest2);
tone(spea,NOTE_F5,half);
delay(rest2);
delay(rest2);
tone(spea,NOTE_GS5,eighth);
delay(rest3);
delay(rest3);
tone(spea,NOTE_G5,eighth);
delay(rest3);
delay(tempo*900);
}
int playGb5(float tempo){
int half = 4000 * tempo;
int quarter = 2000 * tempo;
int eighth = 1000 * tempo;
int sixteenth = 500 * tempo;
int thirtysecond = 250* tempo;
int rest1 = 260* tempo;
int rest2 = 400 * tempo;
int rest3 = 800 * tempo;
tone(spea,NOTE_C5,sixteenth);
delay(rest3);
tone(spea,NOTE_C5,eighth);
delay(rest3);
tone(spea,NOTE_DS5,eighth);
delay(rest2);
tone(spea,NOTE_E5,eighth);
delay(rest2);
tone(spea,NOTE_F5,half);
delay(rest2);
delay(rest2);
tone(spea,NOTE_GS5,sixteenth);
delay(rest3);
tone(spea,NOTE_GS5,sixteenth);
delay(rest3);
tone(spea,NOTE_G5,eighth);
delay(rest3);
delay(tempo*900);
}
int scannerNote(int input){
int note = 932;
if (input>33){
note = 988;
}
if (input>66){
note = 1047;
}
if (input>70){
note = 1109;
}
if (input>75){
note = 1175;
}
if (input>80){
note = 1319;
}
if (input>85){
note = 1397;
}
if (input>90){
note = 1760;
}
return note;
}
//in order to drive the LCD shield, you'll need to include the LiquidCrystal library
// THIS SHOULD ALREADY BE PART OF YOUR ARDUINO LIBRARIES
// if not, go to Tools>ManageLibraries and search and install LiquidCrystal
#include <LiquidCrystal.h>
//LCD pin to Arduino
const int pin_RS = 8;
const int pin_EN = 9;
const int pin_d4 = 4;
const int pin_d5 = 5;
const int pin_d6 = 6;
const int pin_d7 = 7;
const int pin_BL = 10;
LiquidCrystal lcd( pin_RS, pin_EN, pin_d4, pin_d5, pin_d6, pin_d7);
//the void setup here runs ONCE on startup and then the rest of the program loops forever.
//first things I'm doing in this are just animating the boot up messages
//all these messages and the "boot up" process is just for fun. the system is all booted up already!
void setup() {
// create a new character
lcd.createChar(0, ball0);
lcd.createChar(1, ball1);
lcd.createChar(2, ball2);
lcd.createChar(3, ball3);
//Serial.begin(9600);
pinMode(spea, OUTPUT);
lcd.begin(16, 2);
// Turn off the backlight:
lcd.noDisplay();
digitalWrite(pin_BL, LOW);
pinMode(pin_BL, OUTPUT);
delay(200);
// Turn on the backlight:
lcd.display();
digitalWrite(pin_BL, LOW);
pinMode(pin_BL, INPUT);
delay(100);
lcd.setCursor(0,0);
lcd.print(" ");
delay(1100);
lcd.setCursor(0,0);
lcd.print(" speng_v3.35b");
delay(1900);
lcd.setCursor(0,1);
lcd.print("booting wait");
delay(500);
lcd.setCursor(0,1);
lcd.print("booting>");
delay(500);
lcd.setCursor(0,1);
lcd.print("booting->");
delay(500);
lcd.setCursor(0,1);
lcd.print("booting-->");
delay(500);
lcd.setCursor(0,1);
lcd.print("booting--->");
delay(500);
lcd.setCursor(0,1);
lcd.print("booting---->");
delay(500);
lcd.setCursor(0,1);
lcd.print("booting----->");
delay(500);
lcd.setCursor(0,1);
lcd.print("booting------>");
delay(500);
lcd.setCursor(0,1);
lcd.print("booting------->");
delay(500);
lcd.setCursor(0,1);
lcd.print("booting-------->");
delay(500);
lcd.setCursor(0,1);
int a;
a = analogRead (0);
//check if the right button was pushed if so, do this
if (a < 1000) {
tone(spea,NOTE_D5,50);
lcd.setCursor(0,1);
lcd.print("SSHHHHHHHH!!!!!!");
spea = 11;
if (a < 800 && a>600) {
delay(500);
lcd.setCursor(0,0);
lcd.print("G-Meter Made By ");
lcd.setCursor(0,1);
lcd.print(" Bruce Wright ");
spea = 12;
playGb4(.3);
playGb4(.3);
playGb4(.3);
playGb5(.3);
delay(1000);
spea = 11;
}
}
Serial.print(a);
Serial.println();
lcd.print(" starting up ");
delay(500);
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,0);
lcd.print(" ");
// Turn off the backlight:
lcd.noDisplay();
digitalWrite(pin_BL, LOW);
pinMode(pin_BL, OUTPUT);
delay(1200);
// Turn on the backlight:
lcd.display();
digitalWrite(pin_BL, LOW);
pinMode(pin_BL, INPUT);
delay(900);
lcd.setCursor(0,0);
lcd.print("SPENGLER G-METER");
// Play startup song
playGb2(1.3);
delay(100);
lcd.setCursor(0,1);
lcd.print("c2016 jHoltzmann");
// Play startup song
playGb3(1.3);
delay(3900);
lcd.setCursor(0,1);
lcd.print(" ");
lcd.setCursor(0,0);
lcd.print(" ");
// Turn off the display:
lcd.noDisplay();
delay(600);
// Turn on the display:
lcd.display();
lcd.setCursor(0,1);
lcd.print("PKE LEVEL:");
}
//okay, that's the whole "startup sequence" When it's done, it's printing PKE numbers.
//that way, when you turn it on, it's aready "doing something".
//here I'm initializing three counters I'll need later for the printed array messages.
// b is which group of messages, i is which message in that group
// for instance b=0 i=9 finds the value "WHO U GONNA CALL" from the string array at the top
// whereas b=20 i=0 finds "CYCLOTRON: OK"
//in computer programs, when we count, we start with zero. You probably already knew that.
int i = 0;
int b = 0;
int j=0;
int echo = 0;
int myC=0;
//This is the always running part of the program. Once the startup sequence is done this next part loops forever
void loop() {
//This big part is all about reading the button presses and what to do when a button is pressed.
// first, just read the botton press, and put that value into the variable x
int x;
x = analogRead (0);
lcd.setCursor(0,0);
//check if the right button was pushed if so, do this
if (x < 60) {
tone(spea,NOTE_D5,50);
b=10;
i=0;
numbers =0;
scanning=1;
typing = 0;
lcd.print (" ");
lcd.setCursor(0,1);
lcd.print (" ");
// Turn off the display:
lcd.noDisplay();
delay(500);
// Turn on the display:
lcd.display();
lcd.setCursor(0,0);
lcd.print (" MULTISCAN ");
delay(600);
lcd.setCursor(0,0);
lcd.print (" ");
delay(250);
lcd.setCursor(0,0);
lcd.print ("CALIBRATING ");
delay(400);
lcd.setCursor(0,1);
lcd.print ("<-------------->");
delay(150);
for (int f = 1; f<=14; f++){
lcd.setCursor(f,1);
lcd.print ("=");
int sound = max(0,map(f,1,14,631,3000));
tone(spea,sound,20);
delay(200);
}
//set the print speed of the multiscanner mode here
mySpeed =7;
}
//check if the top button was pushed if so, do this
else if (x < 400) {
tone(spea,NOTE_D5,50);
b=20;
i=0;
numbers =0;
scanning=0;
typing = 1;
lcd.print (" ");
lcd.setCursor(0,1);
lcd.print (" ");
// Turn off the display:
lcd.noDisplay();
delay(500);
// Turn on the display:
lcd.display();
lcd.setCursor(0,0);
lcd.print (" RUN DIAGNOSTIC ");
delay(200);
for(int p =0; p<10; p++){
lcd.setCursor(0,0);
lcd.write(byte(p%4)); // when calling lcd.write() '0' must be cast as a byte
lcd.setCursor(15,0);
lcd.write(byte(p%4)); // when calling lcd.write() '0' must be cast as a byte
tone(spea,random(60,2000),35);
delay(100);
tone(spea,random(60,2000),35);
delay(100);
tone(spea,random(60,2000),35);
delay(100);
}
lcd.setCursor(0,0);
lcd.print (" ");
lcd.setCursor(15,0);
lcd.print (" ");
tone(spea,NOTE_C7,235);
lcd.setCursor(0,1);
lcd.print (" CHECK COMPLETE ");
delay(3000);
lcd.setCursor(0,0);
lcd.print (" ");
lcd.setCursor(0,1);
lcd.print (" ");
delay(500);
lcd.setCursor(0,0);
tone(spea,NOTE_F6,100);
lcd.print (" SYSTEMS ");
lcd.setCursor(0,1);
lcd.print ("________________");
//here I set the speed of the bottom text for the dignostic messages. Don't like it? Change it!
mySpeed =24;
}
//check if the bottom button was pushed. If so, do this
else if (x < 600){
tone(spea,NOTE_D5,50);
//playGb4(.3);
scanning=2;
typing = 0;
b=0;
i=0;
numbers =0;
lcd.print (" ");
lcd.setCursor(0,1);
lcd.print (" ");
// Turn off the display:
lcd.noDisplay();
delay(500);
// Turn on the display:
lcd.display();
lcd.setCursor(0,0);
lcd.print (" GHOSTBUSTERS ");
delay(700);
playGb4(.3);
lcd.setCursor(0,1);
lcd.print (" WHO ");
delay(350);
lcd.setCursor(0,1);
lcd.print (" YOU ");
delay(350);
lcd.setCursor(0,1);
lcd.print (" GONNA ");
delay(350);
lcd.setCursor(0,1);
lcd.print (" CALL? ");
delay(350);
playGb1(.5);
// here I set the speed of the bottom text for the advertising messages. Don't like it? Change it!
mySpeed =30;
}
//check if the left button was pushed. If so, do this
else if (x < 800){
tone(spea,NOTE_D5,50);
scanning=0;
typing = 1;
b=30;
i=0;
numbers =0;
lcd.print (" ");
lcd.setCursor(0,1);
lcd.print (" ");
// Turn off the display:
lcd.noDisplay();
delay(500);
// Turn on the display:
lcd.display();
String topLine = (" TOBIN'S SPIRIT ");
String bottomLine = (" DATABASE ");
for (int d = 0; d<=15; d++){
lcd.cursor();
lcd.setCursor(d,0);
delay(100);
lcd.print((topLine.substring(d,d+1)));
tone(spea,NOTE_B6,7);
//tone(spea,random(60,3000),7);
lcd.noCursor();
}
for (int d = 0; d<=15; d++){
lcd.cursor();
lcd.setCursor(d,1);
delay(100);
lcd.print((bottomLine.substring(d,d+1)));
tone(spea,NOTE_B6,7);
//tone(spea,random(60,3000),7);
lcd.noCursor();
}
playGb2(1.0);
delay(3000);
lcd.print (" ");
lcd.setCursor(0,0);
lcd.print ("ENTITY CLASS");
lcd.setCursor(0,1);
lcd.print ("----DETECTING---");
// here I set the speed of the bottom text for the detecting messages. Don't like it? Change it!
// WARNING, don't set mySpeed much higher than 120. Because of a rounding error, higher settings may never print
mySpeed =90;
}
//check if the "select" button was pushed. If so, do this
else if (x < 1000){
tone(spea,NOTE_D5,50);
scanning=1;
typing = 0;
b=0;
numbers =1;
lcd.setCursor(0,0);
lcd.print (" ");
lcd.setCursor(0,1);
lcd.print (" ");
// Turn off the display:
lcd.noDisplay();
delay(500);
// Turn on the display:
lcd.display();
lcd.setCursor(0,1);
lcd.print ("PKE LEVEL:");
mySpeed =2;
}
//OKAY THAT'S DONE WE CHECKED ALL 5 BUTTONS!!!
//this next part prints the animated scanning top line graphic, if the mode calls for it
//check and see if it's time to print a TOP line
if ((millis()) % (204) <=0){
// if so, check and see if we're printing the scanning graphic and print it
if (scanning==1){
lcd.setCursor(0,0);
lcd.print((myString2)[j]);
if(j==2){
if(numbers==1){
tone(spea,scannerNote(pkeInt),(scannerNote(pkeInt)/30));
}
else if (numbers==0){
tone(spea,1047,20);
}
}
j++;
if(j>=8){
j=0;
}
}
if (scanning==2){
lcd.setCursor(0,0);
lcd.print((myString3)[j]);
j++;
if(j>=8){
j=0;
}
}
}
//The last part of the program is just printing the bottom line text in each mode
//check and see if it's time to print a bottom line
if (millis() % (200 *mySpeed) ==0){
// if so, check and see if we're printing pke numbers, and if so, print them!
if(numbers==1){
lcd.setCursor(15,1);
lcd.print("%");
lcd.setCursor(10,1);
//this part generates the numbers for the pke meter
float pke= (millis()*.00007);
pke = sin(pke)*100;
pke = map(pke, -99, 100, 6802, 9834);
pke = pke*.01;
float pkeMult= (millis()*.00003);
pkeMult = sin(pkeMult+60)*100;
pkeMult = map(pkeMult, -99, 100, 20, 100);
pkeMult = pkeMult*.01;
lcd.print(pke*pkeMult);
pkeInt = (pke*pkeMult);
}
//if we're not printing numbers, get the appropriate message from the string array at the top, and print it
else if (numbers==0){
if(typing==0){
lcd.setCursor(0,1);
lcd.print((myString1)[i+b]);
i++;
if(i>=10){
i=0;
}
}
else if (typing==1){
String thisLine = ((myString1)[i+b]);
i++;
if(i>=10){
i=0;
}
lcd.setCursor(0,1);
lcd.print(" ");
for (int d = 0; d<=15; d++){
lcd.cursor();
lcd.setCursor(d,1);
//tone(spea,NOTE_B6,50);
delay(100);
lcd.print((thisLine.substring(d,d+1)));
tone(spea,NOTE_B6,7);
lcd.noCursor();
}
}
}
}
}