#include <IRremote.h>
#include <Adafruit_LEDBackpack.h>
#include <LiquidCrystal_I2C.h>
#define WOKWI
// Function prototypes
void (*resetBoard)() = 0; // Allow to reset Arduino.
void updateClock();
void nextMessage();
// Structures, enum and classes
struct MessageLCD {
// This structure allows you to store a type of data
// that represents a message displayed on an LCD display.
// This structure contains two strings representing the
// top line and bottom line of the display.
String topLine;
String bottomLine;
};
class Task {
// Class used to any task that needs
// a non-blocking delay.
private:
unsigned int timeout;
bool toLoop;
void (*callbackFunction)();
public:
unsigned long t1, dt;
bool RUN = false;
// Set timeout and callback
Task(unsigned int t, void (*callback)()){
timeout = t;
callbackFunction = callback;
}
// Change timeout
void changeTimeout(unsigned int t){
timeout = t;
}
// Start the task
void start(){
t1 = millis();
RUN = true;
}
// Check if time is elapsed, then stop the task
void checkUpdate(){
dt = millis() - t1;
if ((dt >= timeout) && RUN){
stop();
if (toLoop){
start();
}
}
}
// Stop task and call callback if isn't NULL
void stop(){
RUN = false;
if (callbackFunction != NULL) {
callbackFunction();
}
}
// Allow to activate or deactivate the task loop
void loop(bool b){
toLoop = b;
}
};
// Timing consts
const int CLOCK_MS = 1000;
const int MESSAGES_MS = 1750;
// Tasks
const int numTasks = 2;
Task tasks[numTasks] = {
Task(CLOCK_MS, updateClock), // Clock Task: the clock of every second (1000ms)
Task(MESSAGES_MS, nextMessage)
};
// Display messages
const int numMessages = 4;
MessageLCD messages[numMessages] = {
{ " Arduino Bomb ", " Simulator "},
{ " Developed and ", " designed by: "},
{ " Loris ", " Accordino "},
{ " Andre' ", " Mauceri "}
};
// Displays
Adafruit_7segment led_display1 = Adafruit_7segment();
LiquidCrystal_I2C lcd_1(32, 16, 2);
// Digital pinout
const int pinIR = 5;
const int pinIRctrl = 6;
const int pinBuzzer = 7;
// IR buttons consts
const int POWER = 0;
// Tasks consts
const int CLOCK_TASK = 0;
const int MESSAGES_TASK = 1;
// Messages consts
const int INITIAL_TITLE = 0;
const int CREDITS_0 = 1;
const int CREDITS_1 = 2;
const int CREDITS_2 = 3;
// Generic consts
const int BUZZER_FREQUENCY = 200;
// Status flags
bool powerStatus = false;
bool bombStatus = false;
bool clockHighStatus = false;
// Counters
int currentMessageIndex = 0;
int messageIndexToStop = 0;
// Map the IR code to the corresponding remote button.
// The buttons are in this order on the remote:
// 0 1 2
// 4 5 6
// 8 9 10
// 12 13 14
// 16 17 18
// 20 21 22
// 24 25 26
//
// Return -1, if supplied code does not map to a key.
int mapCodeToButton(unsigned long code) {
// For the remote used in the Tinkercad simulator,
// the buttons are encoded such that the hex code
// received is of the format: 0xiivvBF00
// Where the vv is the button value, and ii is
// the bit-inverse of vv.
// For example, the power button is 0xFF00BF000
// Check for codes from this specific remote
if ((code & 0x0000FFFF) == 0x0000BF00) {
// No longer need the lower 16 bits. Shift the code by 16
// to make the rest easier.
code >>= 16;
// Check that the value and inverse bytes are complementary.
if (((code >> 8) ^ (code & 0x00FF)) == 0x00FF) {
return code & 0xFF;
}
}
return -1;
}
int readInfrared() {
int result = -1;
// Check if we've received a new code
if (IrReceiver.decode()) {
// Get the infrared code
unsigned long code = IrReceiver.decodedIRData.decodedRawData;
// Map it to a specific button on the remote*/
result = mapCodeToButton(code);
// Enable receiving of the next value
IrReceiver.resume();
}
return result;
}
void setup()
{
// IR setup
pinMode(pinIRctrl, OUTPUT);
digitalWrite(pinIRctrl, HIGH);
IrReceiver.begin(pinIR);
// PinMode setup
pinMode(pinBuzzer, OUTPUT);
// Displays setup
led_display1.begin(112);
lcd_1.begin(16,2);
lcd_1.init();
lcd_1.clear();
// Serial port setup
Serial.begin(9600);
}
// Generate a digital square wave (LOW and HIGH states)
// at a given frequency for a certain period.
void buzzer(int hz, int ms){
float bpms = 1000.0 / hz;
float time = 0.0;
digitalWrite(pinIRctrl, LOW);
while(time < ms){
// Generate one wave cycle
digitalWrite(pinBuzzer, HIGH);
delay(bpms);
digitalWrite(pinBuzzer, LOW);
// Adds one cycle time
time += bpms;
}
digitalWrite(pinIRctrl, HIGH);
}
void ledDisplay(int d1, int d2, int d3, int d4, bool colon){
led_display1.writeDigitNum(0, d1);
led_display1.writeDigitNum(1, d2);
led_display1.drawColon(colon);
led_display1.writeDigitNum(3, d3);
led_display1.writeDigitNum(4, d4);
led_display1.writeDisplay();
}
void lcdDisplay(struct MessageLCD msg){
lcd_1.clear();
lcd_1.backlight();
lcd_1.setCursor(0, 0);
lcd_1.print(msg.topLine);
lcd_1.setCursor(0, 1);
lcd_1.print(msg.bottomLine);
}
void clearLcdDisplay(){
lcd_1.clear();
lcd_1.noBacklight();
}
// Function called to turn on the entire bomb system.
void turnON(){
powerStatus = true;
buzzer(BUZZER_FREQUENCY, 750);
ledDisplay(0, 0, 0, 0, false);
tasks[CLOCK_TASK].loop(true);
tasks[CLOCK_TASK].start();
showMessagesInRange(INITIAL_TITLE, CREDITS_2);
}
// Function called to turn off the entire bomb system.
void turnOFF(){
buzzer(BUZZER_FREQUENCY, 750);
powerStatus = false;
resetBoard();
}
void updateClock(){
// Swap HIGH and LOW clock status
clockHighStatus = !clockHighStatus;
led_display1.drawColon(clockHighStatus);
led_display1.writeDisplay();
tasks[CLOCK_TASK].start();
}
// This function allows to show messages in a set range,
// through a timed loop task that manages the messages and
// which will stop when it reaches the upper limit
// of the range.
void showMessagesInRange(int from, int to){
currentMessageIndex = from;
messageIndexToStop = to;
tasks[MESSAGES_TASK].loop(true);
tasks[MESSAGES_TASK].start();
}
void nextMessage() {
if (currentMessageIndex <= messageIndexToStop){
// Show each message in range
lcdDisplay(messages[currentMessageIndex]);
}
else{
// After reaching the desired index, clear the display
clearLcdDisplay();
}
currentMessageIndex++;
// When the stop index is reached, stop the task
if (currentMessageIndex > messageIndexToStop + 1){
tasks[MESSAGES_TASK].loop(false);
//tasks[MESSAGES_TASK].stop();
return;
}
}
void updateTasks(){
// Check update for each task
for (int i = 0; i < numTasks; i++) {
tasks[i].checkUpdate();
}
}
void loop()
{
#ifdef WOKWI
int button = readSerial();
if (button != 256){
Serial.println("CODE: " + String(button));
}
#else
int button = readInfrared();
#endif
if (button == POWER){
if (!powerStatus){
turnON();
}
else{
turnOFF();
}
}
updateTasks();
}
int readSerial(){
if (Serial.available()){
return Serial.parseInt();
}
}