#include <Keypad.h>
#include <LiquidCrystal_I2C.h>
#include <string.h>
#include <EEPROM.h>
#include <RTClib.h>
//NEW FEATURES!:
//added extension to keypad (now 5x5) - Aidan
//added negative numbers - Aidan
//added custom characters
//added '/', '%', 'sqrt' - needs proper flags for sqrt, % - Damiru
//added C-CE interrupt (creates a flag for the loop()) - Aidan
//Added start up menu - Damiru
//added memory functions, MR (memory recall) works in maths calculations - Aidan
//memory only uses returned answer values. - Aidan
//added start up text and EEPROM reading functionality - Aidan
//added menu and ability to change password and start text values - Aidan
//added additional text displays on LCD for UX - Aidan
//added sqrt symbol and function - Damiru
//sqrt symbol:
uint8_t square_root[8] = {
0b00111,
0b00100,
0b00100,
0b00100,
0b00100,
0b10100,
0b01000,
0b00000,
};
//set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27, 16, 2);
//need to be after definition lcd.createChar(3, square_root);
//KEYPAD SETUP
const uint8_t ROWS = 5;
const uint8_t COLS = 5;
char keys[ROWS][COLS] = {
{ 'A', 'B', 'C', 'D', 'O' },
{ '1', '2', '3', '/', 'C' }, //'C' is not being currently used
{ '4', '5', '6', '*', 'N' },
{ '7', '8', '9', '+', 'S' },
{ '.', '0', '=', '-', '%' }
};
uint8_t colPins[COLS] = { 5, 4, 3, 2, 11 }; // C1, C2, C3, C4, C5 pins
uint8_t rowPins[ROWS] = { 10, 9, 8, 7, 6 }; // R1, R2, R3, R4, R5 pins
Keypad keypad = Keypad(makeKeymap(keys), rowPins, colPins, ROWS, COLS);
//FUNCTIONS PREINITIALISED
bool isNumber(char);
void debugPrint(String, String); //for debug
void formalise(); //loop function - localise?
float calculate(); //loop function - localise?, returns final result
void LCD_clearRow(String); //used in loop and setup
void EEPROM_debug(); //setup function - for debug
String format_time (String, int); //setup function
int EEPROM_assign(String, int); //setup function
String EEPROM_read_string(int); //setup function
//GLOBAL VARIABLES
String tempString; //temporary stored keypresses. Becomes formatted later.
//Formatted string with values. Make local? <><
String numberString = "";
String operandString = "";
bool LCD_on; //State attached to whether screen is on or not
bool returnResult = false; //screen clearing behavour of device
float memory = 0; //value stored in memory
//ISR variables
//Button definitions and values
void C_CE_ISR(); //Clear everything ISR
#define C_CE 18 //C-CE pin
#define C_CE_DELAY 50 //debounce time, time button must be held to register
bool oldButton = false; //used for debouncing C_CE button
unsigned long buttonTime; //used for debouncing C_CE button
bool clear_calculator = false;//flag the calculator to clear screen
RTC_DS1307 rtc;//initialize real-time clock
DateTime currentTime;
void setup() {
Serial.begin(9600);
//Serial.println(EEPROM.read(0)); //prints first value in the memory address
//verifying that the real time clock works
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
abort();
}
//saves default values to EEPROM find default setup is set
if (EEPROM.read(0) == 255) { //first address is it default or not
Serial.println("EEPROM default values have been assigned!");
String defaultText = "Calculator by Damiru and Aidan";
int passwordAddress = EEPROM_assign(defaultText, 1);
//Assigning default password
String defaultPassword = "EEE20003";
EEPROM_assign(defaultPassword, passwordAddress);
}
//EEPROM_debug() //used to debug current EEPROM values
lcd.init();
String startUpString = EEPROM_read_string(1);
lcd.print(startUpString); //start up text
//if the string is too large the rest of the string is printed on the bottom LCD layer
if (startUpString.length() > 16) {
lcd.setCursor(0, 1); //go to bottom
lcd.print(startUpString.substring(16));
lcd.setCursor(0, 0); //return to top
}
lcd.display();
lcd.backlight();
LCD_on = true; //set LCD to be on by default
//accessing string values:
Serial.println(EEPROM_read_string(1));
Serial.println(EEPROM_read_string(EEPROM.read(1) + 2));
String EEPROM_password = EEPROM_read_string(EEPROM.read(1) + 2);
Serial.println("You are in the start up menu. Please a enter Password.");
Serial.print("Password: ");
//retrieves password
//String passwordInput = "EEE20003"; //for debugging
while (!Serial.available()) {
}
String passwordInput = Serial.readString();
passwordInput.trim(); //removes '\n' char
Serial.println(passwordInput);
if (passwordInput == EEPROM_password) {
Serial.println("Correct password. Please select an option.");
Serial.println("Options are:");
Serial.println("Option 1 (Press '1'): Change password.");
Serial.println("Option 2 (Press '2'): Change start up text.");
Serial.println("Option 3 (Press '3'): RTC clock.");
Serial.println("Option 4 (Press '4'): Continue to Calculator.");
//Get user option input:
Serial.print("Option: ");
while (!Serial.available()) {
}
int options = Serial.parseInt();
Serial.println(options);
//initalise variables outside of switch statement, otherwise switch breaks
String oldPassword, newStartUpText, newPassword, timeDisplay;
int newPasswordAddress;
switch (options) {
case 1:
Serial.println("Option: 1 - Change password.");
//Reset the Serial to clear any values currently stored.
Serial.end();
Serial.begin(9600);
while (!Serial.available()) {
}
newPassword = Serial.readString();
newPassword.trim(); //remove '\n'
EEPROM.write(0, 0);
EEPROM_assign(newPassword, EEPROM.read(1) + 2);
EEPROM_debug();
break;
case 2:
Serial.println("Option: 2 - Change start up text.");
//Reset the Serial to clear any values currently stored.
Serial.end();
Serial.begin(9600);
while (!Serial.available()) {
}
newStartUpText = Serial.readString();
newStartUpText.trim(); //remove '\n'
//newStartUpText = "CHEESECAKE";
EEPROM.write(0, 0);
oldPassword = EEPROM_read_string(EEPROM.read(1) + 2);
newPasswordAddress = EEPROM_assign(newStartUpText, 1);
EEPROM_assign(oldPassword, newPasswordAddress);
EEPROM_debug();
break;
case 3:
Serial.println("Option: 3 - Display RTC.");
lcd.clear();
do {
lcd.setCursor(0, 0);
//current time is read from the RTC
currentTime = rtc.now(); //hour, minute and second is concatenated
timeDisplay = "";
timeDisplay.concat(currentTime.hour()); //hours
timeDisplay = format_time(timeDisplay, currentTime.minute()); //minutes
timeDisplay = format_time(timeDisplay, currentTime.second()); //seconds
//print to display
lcd.print(timeDisplay);
lcd.display();
} while (keypad.getKey() == NO_KEY);
break;
case 4:
Serial.println("Option: 4 - Continue to Calculator.");
break;
}
} else {
Serial.println ("Incorrect password!");
}
//create text to display that the calculator is used
LCD_clearRow("bottom");
LCD_clearRow("top");
lcd.print("Calculator is on");
returnResult = true; //causes the top row to be result after a button is pressed
lcd.setCursor(0, 0);
//create square root character
lcd.createChar(1, square_root);
Serial.println("Setup has been completed");
attachInterrupt(digitalPinToInterrupt(C_CE), C_CE_ISR, CHANGE);
}
void loop() {
//interrupt clear value flag
if (clear_calculator) { //<>< issue with ISR crashing
lcd.clear();
lcd.display();
LCD_clearRow("bottom");
LCD_clearRow("top");
//Reset all global temporary storage variables
numberString = "";
operandString = "";
tempString = "";
clear_calculator = false;
}
volatile float result;
char key = keypad.getKey();
//clear top row of calculator
if (key != NO_KEY && key != 'O' && LCD_on == true) {
if (returnResult == true) {
LCD_clearRow("top");
returnResult = false;
}
//Displaying keypresses on to screen
//For special keypresses which need to display a different character to LCD
//(custom keys or those which use the high character encoding of the LCD)
//65 - 68 is ASCII for A, B, C, D
if (key == 'N' || key == '/' || key == 'S' || key >= 65 && key <= 68) {
switch (key) {
case 'N': //negative numbers
lcd.print(char(176));
break;
case '/': //division symbol
lcd.print(char(253));
break;
case 'A': //Memory reset (MC) symbol
memory = 0;
LCD_clearRow("top");
lcd.print("Memory cleared");
returnResult = true;
tempString = ""; //clear tempString
break;
case 'B': //Memory recall (MR) symbol
lcd.print(memory);
//handle mathematical operations in calculator
if (memory < 0) {
//for negative numbers, removes minus operand and creates negative float
tempString.concat('N');
tempString.concat(-1 * memory);
} else {
tempString.concat(memory); //store to the temporary string
}
break;
case 'C': //subtract from memory (M-)
memory = memory - result;
LCD_clearRow("top");
lcd.print("M is now ");
lcd.print(memory);
returnResult = true;
tempString = ""; //clear tempString
break;
case 'D': //add to memory (M+)
memory = memory + result;
LCD_clearRow("top");
lcd.print("M is now ");
lcd.print(memory);
returnResult = true;
tempString = ""; //clear tempString
break;
case 'S': //square root symbol
lcd.print("\x01");
break;
}
} else {
lcd.print(key);
}
lcd.display();
if (key == '=') {
debugPrint(tempString, "tempString");
formalise(); //format tempString raw input
result = calculate(); //calculate from formatted inputs
returnResult = true;
//Reset all global variables
numberString = "";
operandString = "";
tempString = "";
return;
}
if (!(key >= 65 && key <= 68)) { //doesn't save memory variables
tempString.concat(key); //concatenate character to end of tempString string
}
} else if (key == 'O') {
LCD_on = !LCD_on;
if (LCD_on == false) {
lcd.noBacklight();
lcd.noDisplay();
} else {
lcd.backlight();
lcd.display();
}
}
}
bool isNumber(char readChar) {
//Checks if char input is a number or not. 48 represents 0, 57 represents 9.
//46 represents period (.). Returns FALSE if not a number. ASCII code used.
//78 represents negative number
if (readChar >= 48 && readChar <= 57 || readChar == 46 || readChar == 78) {
return true;
} else {
return false;
}
}
void debugPrint(String printString, String stringName) {
//Prints of a string. Has option to have a custom string as a descriptor for
//readability and clarity
Serial.print(stringName); Serial.print(" is: ");
Serial.println(printString);
return;
}
void formalise() {
bool wasNum = false;
for (int i = 0; i < tempString.length(); i++) {
if (isNumber(tempString[i]) == true) {
//Stores numbers to temporary number string
numberString.concat(tempString[i]);
wasNum = true;
}
else if (isNumber(tempString[i]) == false && wasNum == true) {
//Stores and processes numbers to number string
//Stores and processes operand to operand string
wasNum = false;
operandString.concat(tempString[i]);
numberString.concat('|');
}
else {
Serial.println("Multiple buttons are pressed in a row.");
Serial.println("First operand will be used.");
//Error state when multiple operands are pressed in row.
//Last operand will be used.
}
}
numberString.concat('|'); //Add terminating character
//printing current results
debugPrint(operandString, "operandString");
debugPrint(numberString, "numberString");
}
float calculate() {
float result;
//Result variable - location of calculated result
float numFloat = 0;
//Read float from numberString
int countNum = 0;
//counts how many numbers are being found, used to set first float value
char pastOperand;
//remembers last operand used for operations
String calcNumString = "";
for (int i = 0; i < numberString.length(); i++) {
if (numberString[i] != '|') { //determines whether operand is solved for or not
if (numberString[i] == 'N') { //For negative number character
calcNumString.concat("-");
} else { //For every other number character
calcNumString.concat(numberString[i]);
}
} else { //when end of number is reached, format string and do maths.
debugPrint(calcNumString, "calcNumString");
numFloat = calcNumString.toFloat();
calcNumString = "";
//Following code calculates math results.
if (countNum == 0) {
result = numFloat; //first number is saved, skip operand skip
Serial.print("first no: "); Serial.println(result);
pastOperand = operandString[0]; //takes first operand
} else {
Serial.println(pastOperand);
switch (pastOperand) {
case '*': // Code for "*" (asterisk)
result = result * numFloat;
break;
case '+': // Code for "+" (plus)
result = result + numFloat;
break;
case '-': // Code for "-" (hyphen)
result = result - numFloat;
break;
case '/': // Code for "-" (hyphen)
result = result / numFloat;
break;
case 'S': // Code for "-" (hyphen)
result = sqrt(result);
break;
case '%': // Code for "-" (hyphen)
result = result / 100;
break;
case 'B': //MR (memory recall)
//do nothing;
break;
case 65:
case 67 ... 68: //ASCII A, C, D - all memory operations except memory recall
Serial.println("A memory operator has been selected.");
break;
}
pastOperand = operandString[countNum];
}
Serial.print("Result is "); Serial.println(result);
countNum++;
}
}
lcd.setCursor(0, 1); //bring cursor down to the bottom
LCD_clearRow("bottom");
lcd.print(result);
lcd.setCursor(0, 0); //reset to origin
return result; //can be used for something else
}
void LCD_clearRow(String row) {
//clears a row of screen, fills with space char
if (row == "top") {
lcd.setCursor(0, 0);
lcd.print(" ");
lcd.setCursor(0, 0);
} else if (row == "bottom") {
lcd.setCursor(0, 1);
lcd.print(" ");
lcd.setCursor(0, 1);
}
return;
}
void C_CE_ISR() { //ISR is debounced
bool readButton = digitalRead(C_CE);
//ISR functionality on falling edge and after debounce delay has occured
//ISR is actvated before the final debounce to falling. Acceptable but improvable.
if (readButton == 1 && oldButton == 0 && (millis() - buttonTime) > C_CE_DELAY) {
Serial.println("C_CE ISR has been called.");
clear_calculator = true;
}
buttonTime = millis();
oldButton = readButton;
}
void EEPROM_debug() {
for (int i = 0; i < 50; i++) {
Serial.print((char)EEPROM.read(i));
}
Serial.println();
for (int i = 0; i < 50; i++) {
Serial.print(EEPROM.read(i));
Serial.print(";");
}
return;
}
String format_time(String timeDisplay, int timeComponent) {
timeDisplay.concat(':');
if (timeComponent < 10) {
timeDisplay.concat('0');
}
timeDisplay.concat(timeComponent);
return timeDisplay;
}
int EEPROM_assign(String EString, int address) {
int textLength = EString.length();
//Serial.println(textLength);
EEPROM.write(address, textLength); //assign the length to the first address
for (int i = 1; i <= textLength; i++) {
EEPROM.write(i + address, EString[i - 1]); //offset by address + 1
}
int endAddress = textLength + address + 1; //new address - free memory location
return endAddress;
}
String EEPROM_read_string(int address) {
//reads the EEPROM string from a given address
//1st address is length of string
//string is stored as char array in EEPROM
int textLength = EEPROM.read(address);
String EEPROM_String = "";
for (int i = 1; i <= textLength; i++) {
char EEPROM_char = EEPROM.read(i + address);
EEPROM_String.concat(EEPROM_char);
//concat value to end of string
}
return EEPROM_String;
}