#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, currently not used in maths calculations - Aidan
//memory uses returned answer values. - Aidan
/*
uint8_t square_root[8] = {
0b00011,
0b00010,
0b00110,
0b00100,
0b00100,
0b10100,
0b01000,
0b00000,
//lcd.createChar(0, square_root);
};*/
//set the LCD address to 0x27 for a 16 chars and 2 line display
LiquidCrystal_I2C lcd(0x27, 16, 2);
//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);
void formalise(); //make more local?
float calculate(); //make more local?, returns the final result
//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);
lcd.init();
LCD_on = false; //set LCD to be off
//verifying that the real time clock works
if (! rtc.begin()) {
Serial.println("Couldn't find RTC");
Serial.flush();
abort();
}
Serial.println("Setup has been completed");
Serial.println("Start up menu");
Serial.println("Enter Password:");
//Serial.end();
//Serial.begin(9600);
String passwordInput;
char options;
bool passvalid = true;
while (!Serial.available()) {
}
passwordInput = Serial.readString();
Serial.print(passwordInput);
//Aidan does pass validation
if (passvalid == true) {
Serial.println("Select option");
while (!Serial.available()) {
}
options = Serial.read();
switch (options) {
case '1':
Serial.println("Option 1");
break;
case '2':
Serial.println("Option 2");
do {
//current time is read from the RTC
currentTime = rtc.now(); //hour, minute and second is concated
String timeDisplay = "";
int hour = currentTime.hour();
timeDisplay.concat(hour);
timeDisplay.concat(':');
int minute = currentTime.minute();
if (minute < 10) {
timeDisplay.concat('0');
}
timeDisplay.concat(minute);
timeDisplay.concat(':');
int second = currentTime.second();
if (second < 10) {
timeDisplay.concat('0');
}
timeDisplay.concat(second); //time displayed as a string
lcd.print(timeDisplay);
lcd.display();
delay(1000);
lcd.clear();
} while (keypad.getKey() == NO_KEY);
break;
case '3':
Serial.println("Option 3");
break;
}
}
}
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 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 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();
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 >= 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;
break;
case 'B': //Memory recall (MR) symbol
lcd.print(memory);
break;
case 'C': //subtract from memory (M-)
memory = memory - result;
LCD_clearRow("top");
lcd.print("M is now ");
lcd.print(memory);
returnResult = true;
break;
case 'D': //add to memory (M+)
memory = memory + result;
LCD_clearRow("top");
lcd.print("M is now ");
lcd.print(memory);
returnResult = true;
break;
//case 'S': //square root symbol
// lcd.print(char(253));
// 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;
}
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 65 ... 68: //ASCII A, B, C, D - all memory operations
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
}