I have two Arduinos with identical wiring, they will (hopefully) end up being laser guns for my younger cousins.
When they both have a sketch which simply toggles lights when the correct IR code is received, everything works as it should. see the code here:
// Start of sketch 1
#include <IRremote.h>
IRsend irsend; //Setup the IR transmitter
int receiver = 8; //initialize pin 13 as receiver pin.
IRrecv irrecv(receiver); //create a new instance of receiver
decode_results results;
// declare consants
const int trigger = 13;
const int greenLight = 7;
const int yellowLight = 2;
const int timeBetweenTransmittion = 800;
// declare variables
bool toggleGreen = true;
bool toggleYellow = true;
long int previousTransmittion = 0;
// put your setup code in here, to run once:
void setup()
{
pinMode(trigger, INPUT_PULLUP);
Serial.begin(9600); // send code at 9600 bits per second
irrecv.enableIRIn(); // start the receiver
}
// put your main code in here, to run repeatedly:
void loop() {
if (not digitalRead(trigger) && millis() > previousTransmittion + timeBetweenTransmittion) // if the button is pressed and it's been enought time since the last transmittion
{
Serial.println("send!");
irsend.sendNEC(1367857, 32); // send the code
irrecv.enableIRIn(); // sending ir stops the receiver so start the receiver again
previousTransmittion = millis(); // set the record for latest transmittion to the currnt time
// toggle the yellow light
digitalWrite(yellowLight, toggleYellow);
toggleYellow = not toggleYellow;
}
if (irrecv.decode(&results)) { // if we have received an IR signal
Serial.println (String(results.value, HEX)); //display HEX results
if (String(results.value, DEC) == "1367857")
{
Serial.println("correct code");
// toggle the green light
digitalWrite(greenLight, toggleGreen);
toggleGreen = not toggleGreen;
}
irrecv.resume(); // it stops the receiver when something's received so we muct resume the ir receiver
}
}
// End of sketch 1
The receiver is on pin 8 and, button on pin 13 and transmitting LED is always on pin 3.
No issues there.
However, when I use the same concepts on a larger-scale project, a strange problem occurs. Once one of the Arduinos has sent some IR, the receiver no longer works. After every line of code which sends IR, I follow it with irrecv.enableIRIn();
, and after every time it receives code I have the line irrecv.resume();
. I can't work out what the problem is. It's very long, but the full code is below:
// Start of code 2
// include the library code:
#include <LiquidCrystal.h> // For the LCD
#include <IRremote.h> // For the IR communication
#include <PCM.h> // For the audio output
const unsigned char gunShot[] PROGMEM = {}; // In the program this contains a huge array of notes, I removed them to save space
const unsigned char gunClick[] PROGMEM = {}; // In the program this contains a huge array of notes, I removed them to save space
// Declare consants for pins
const int rs = A0, en = A1, d4 = A2, d5 = A3, d6 = A4, d7 = A5; // State constants for all the pins used for the LCD
const int trigger = 13;
const int redLight = 7;
const int magReader1 = 9;
const int magReader2 = 10;
const int speaker = 11;
const int healButton = 12;
const int receiver = 8;
const int muzzle = 2;
// Declare constants for gameplay
const int timeBetweenShots = 800; //(in 1000ths of a second)
const int healAmount = 100;
const int medicAmmo = 16;
const int assultAmmo = 31;
const int sniperAmmo = 1;
const int medicDamage = 30;
const int assultDamage = 23;
const int sniperDamage = 75;
const int playerNumber = 4;
// Declare all valiables
int timesKilledByTeam1 = 0; // I would loved to have used an array here, but for some reason an array broke the program
int timesKilledByTeam2 = 0;
int timesKilledByTeam3 = 0;
int timesKilledByTeam4 = 0;
int timesKilledByTeam5 = 0;
int timesKilledByTeam6 = 0;
int timesKilledByTeam7 = 0;
int timesKilledByTeam8 = 0;
String receivedCode = ""; // Store the received code in a string variable
bool gameDone = false;
bool semiCanShoot = true;
bool gunShouldClick = true;
int health = 100;
int gameMode = 1; // 1=TDM, 2=FFA, 3=LTS, 4=LPS
bool respawn = true;
int teamOrPlayer = playerNumber;
int loadStatus; // 0 is no mag, 1 is mag1, 2 is mag2
int mag1Ammo; // Current ammo in mag1
int mag2Ammo; // Current ammo in mag2
int maxAmmo; // Ammo in one mag for the selected class
int damage; // Damage this player is inflicting per shot
long int timeOfFlash = 0;
long int timeOfMuzzle = 0;
long int timeOfLastShot = 0;
long int loopTimer = 0;
int gameStartTime; // In seconds so no need for long int
//int secondsRemaining;
int gameDuration;
int playerClass = 0; // 0=Medic, 1=Asult, 2=Snipr
int shootCode;
IRsend irsend; //Setup the IR transmitter
IRrecv irrecv(receiver); //create a new instance of receiver
decode_results results;
LiquidCrystal lcd(rs, en, d4, d5, d6, d7);
void SetupClass()
{
switch (playerClass)
{
case (0):
maxAmmo = medicAmmo;
damage = medicDamage;
break;
case (1):
maxAmmo = assultAmmo;
damage = assultDamage;
break;
case (2):
maxAmmo = sniperAmmo;
damage = sniperDamage;
break;
}
mag1Ammo = maxAmmo;
mag2Ammo = maxAmmo;
}
// Define subroutines
//void QuickDisplayUpdate(int ammo, int secondsRemaining, int loadStatus, int health) // During a game, to save unnecessary computation, only update the things that might have changed during the game
void QuickDisplayUpdate()
{
lcd.setCursor(11,0);
if((gameDuration - (millis() / 1000 - gameStartTime)) / 60 < 10)
{
lcd.print("0");
lcd.setCursor(12,0);
}
lcd.print(String((gameDuration - (millis() / 1000 - gameStartTime)) / 60));
lcd.setCursor(14,0);
if((gameDuration - (millis() / 1000 - gameStartTime)) % 60 < 10)
{
lcd.print("0");
lcd.setCursor(15,0);
}
lcd.print(String((gameDuration - (millis() / 1000 - gameStartTime)) % 60));
lcd.setCursor(7,1); // Put the / in as it might have been removed from a previous "Reload!"
lcd.print("/");
lcd.setCursor(0,1);
if (loadStatus == 0) lcd.print("Reload!");
else if (loadStatus == 1)
{
lcd.print("Mag" + String(loadStatus) + ":");
lcd.setCursor(5,1);
if (mag1Ammo < 10)
{
lcd.print("0");
lcd.setCursor(6,1);
}
lcd.print(String(mag1Ammo));
}
else
{
lcd.print("Mag" + String(loadStatus) + ":");
lcd.setCursor(5,1);
if (mag2Ammo < 10)
{
lcd.print("0");
lcd.setCursor(6,1);
}
lcd.print(String(mag2Ammo));
}
lcd.setCursor(13,1);
if(health < 100)
{
lcd.print(" ");
lcd.setCursor(14,1);
if(health < 10)
{
lcd.print(" ");
lcd.setCursor(15,1);
}
}
lcd.print(String(health));
}
void FullDisplayUpdate(int teamOrPlayer)
{
lcd.clear();
lcd.setCursor(0,0);
switch (gameMode)
{
case (1):
lcd.print("TDM");
break;
case (2):
lcd.print("FFA");
break;
case (3):
lcd.print("LTS");
break;
case (4):
lcd.print("LPS");
break;
}
lcd.setCursor(3,0);
lcd.print(String(teamOrPlayer));
lcd.setCursor(5,0);
switch (playerClass)
{
case (0):
lcd.print("Medic");
break;
case (1):
lcd.print("Asult");
break;
case (2):
lcd.print("Snipr");
break;
}
lcd.setCursor(11,0);
if((gameDuration - (millis() / 1000 - gameStartTime)) / 60 < 10)
{
lcd.print("0");
lcd.setCursor(12,0);
}
lcd.print(String((gameDuration - (millis() / 1000 - gameStartTime)) / 60) + ":");
lcd.setCursor(14,0);
if((gameDuration - (millis() / 1000 - gameStartTime)) % 60 < 10)
{
lcd.print("0");
lcd.setCursor(15,0);
}
lcd.print(String((gameDuration - (millis() / 1000 - gameStartTime)) % 60));
lcd.setCursor(0,1);
if (loadStatus == 0) lcd.print("Reload!");
else if (loadStatus == 1)
{
lcd.print("Mag" + String(loadStatus) + ":");
lcd.setCursor(5,1);
if (mag1Ammo < 10)
{
lcd.print("0");
lcd.setCursor(6,1);
}
lcd.print(String(mag1Ammo));
}
else
{
lcd.print("Mag" + String(loadStatus) + ":");
lcd.setCursor(5,1);
if (mag2Ammo < 10)
{
lcd.print("0");
lcd.setCursor(6,1);
}
lcd.print(String(mag2Ammo));
}
lcd.setCursor(8,1);
if (maxAmmo < 10)
{
lcd.print("0");
lcd.setCursor(9,1);
}
lcd.print(String(maxAmmo));
lcd.setCursor(11,1);
lcd.print("H:" + String(health));
}
void Play()
{
gameDone = false; // When the game starts, the game isn't done
FullDisplayUpdate(teamOrPlayer); // Fully update the display before the game starts
// Setup the shoot code
if(damage > 9) shootCode = (String(teamOrPlayer) + String(damage) ).toInt();
else shootCode = (String(teamOrPlayer) + "0" + String(damage)).toInt();
// Wait until either a start or cancel code is recieved
Serial.println("wait");
receivedCode = "";
//irrecv.enableIRIn(); // sending ir stops the receiver so start the receiver again
while (receivedCode.length() != 2 && receivedCode.length() != 4)
{
if (irrecv.decode(&results))
{
receivedCode = String(results.value, DEC); // Store the received code in a variable when we have received an IR signal
irrecv.resume(); // Remove the code
}
}
Serial.println("code: " + receivedCode);
gameStartTime = millis() / 1000; // Set the game start time in seconds
if (String(receivedCode) == "50") // If the received code was starting code (50) then play
{
Serial.println("strt, dur: " + String(gameDuration) + "team: " + String(teamOrPlayer));
while (gameDone == false) // Repeat until the game is done
{
//Serial.println("Game loop");
CheckForIR(); // Check for an IR code as often as possible
if (playerClass == 0) // Only check for heal button if they are a medic
{
if (not digitalRead(healButton) && millis() > timeOfLastShot + timeBetweenShots) // If they are pressing the heal button and it's been enought time since last shot
{
Serial.println("heal bttn");
if (loadStatus == 1 && mag1Ammo == maxAmmo) // If mag1 is in and it's full
{
Serial.println("heal 1");
irsend.sendNEC((String(teamOrPlayer) + "00").toInt(), 32); // Send the heal code
//irrecv.enableIRIn(); // sending ir stops the receiver so start the receiver again
digitalWrite(muzzle, 1); // Turn on the muzzle flash
timeOfMuzzle = millis(); // Set the time of the muzzle turning on
//startPlayback(healShot, sizeof(healShot)); // Play heal sound
mag1Ammo = 0; // Empty the current mag
}
else if (loadStatus == 2 && mag2Ammo == maxAmmo) // If mag2 is in and it's full
{
Serial.println("heal 1");
irsend.sendNEC((String(teamOrPlayer) + "00").toInt(), 32); // Send the heal code
//irrecv.enableIRIn(); // sending ir stops the receiver so start the receiver again
digitalWrite(muzzle, 1); // Turn on the muzzle flash
timeOfMuzzle = millis(); // Set the time of the muzzle turning on
//startPlayback(healShot, sizeof(healShot)); // Play heal sound
mag2Ammo = 0; // Empty the current mag
}
}
}
CheckForIR();
if (not digitalRead(trigger) && (millis() > timeOfLastShot + timeBetweenShots)) // If the trigger is pulled and it's been long enough since last shot
{
Serial.println("sht bttn");
if (loadStatus == 1 && mag1Ammo > 0) // If mag1 is in and it has ammo
{
if (playerClass != 0 || semiCanShoot == true) // They can shoot if they are on full auto or if semi can shoot
{
Serial.println("1 sht");
irsend.sendNEC(shootCode, 32); // Send the shoot code
irrecv.enableIRIn(); // Start the receiver
digitalWrite(muzzle, 1); // Turn on the muzzle flash
timeOfMuzzle = millis(); // Set the time of the muzzle turning on
mag1Ammo = mag1Ammo - 1; // Reduce ammo of current mag by one
semiCanShoot = false; // Once they have shot, they cannot shoot again if on semi mode
startPlayback(gunShot, sizeof(gunShot)); // Play shot
Serial.println("1 sht done");
}
}
else if (loadStatus == 2 && mag2Ammo > 0) // If mag2 is in and it has ammo
{
if (playerClass != 0 || semiCanShoot == true) // They can shoot if they are on full auto or if semi can shoot
{
Serial.println("2 sht");
irsend.sendNEC(shootCode, 32); // Send the shoot code
irrecv.enableIRIn(); // Start the receiver
digitalWrite(muzzle, 1); // Turn on the muzzle flash
timeOfMuzzle = millis(); // Set the time of the muzzle turning on
mag2Ammo = mag2Ammo - 1; // Reduce ammo of current mag by one
semiCanShoot = false; // Once they have shot, they cannot shoot again if on semi mode
startPlayback(gunShot, sizeof(gunShot)); // Play shot
Serial.println("2 sht done");
}
}
else if (gunShouldClick) // (If no mag in OR no ammo in the mag that's in) AND gun should click
{
Serial.println("clik");
startPlayback(gunClick, sizeof(gunClick)); // Play click sound
gunShouldClick = false; // Gun shouldn't click again until they releace trigger
}
timeOfLastShot = millis(); // Update the time of last shot
Serial.println("shot done");
}
CheckForIR();
// Check for if the gun should click again and if player can shoot again if on semi mode
if (digitalRead(trigger)) semiCanShoot = true; // If they are not holding the trigger, they can shoot again if on semi mode
if (digitalRead(healButton) && digitalRead(trigger)) gunShouldClick = true; // If they are not holding the trigger or the heal button, the gun should click again when they do hold it again
CheckForIR();
// If the LEDs have been on for long enough, turn them off
if (millis() > timeOfFlash + 50) digitalWrite(redLight, 0); // Turn off the red LEDs if they have been on for enough time
if (millis() > timeOfMuzzle + 50) digitalWrite(muzzle, 0); // Turn off the muzzle if it's been on for enough time
CheckForIR();
if (millis() > loopTimer + 250) // Some things need to be done less often, so do that to save time so IR receive checks can be done more frequently
{
loopTimer = millis();
QuickDisplayUpdate(); // Quick update the display after every loop
CheckForIR();
// Update the loadStatus variable depending on the mag detector inputs
if (!digitalRead(magReader1) && digitalRead(magReader2))
{
loadStatus = 1;
mag2Ammo = maxAmmo; // Refil mag2 if mag one is in
}
else if (!digitalRead(magReader2) && digitalRead(magReader1))
{
loadStatus = 2;
mag1Ammo = maxAmmo; // Refil mag1 if mag two is in
}
else loadStatus = 0;
CheckForIR();
// Check to see if the game has reached the time limit
if (gameDuration - (millis() / 1000 - gameStartTime) < 1)
{
Serial.println("tm up");
gameDone = true;
}
}
} // Game is done
} // If the received code wasn't the starting code then cancel the game and return to waiting menu
}
void CheckForIR()
{
//Serial.println("check");
//irrecv.enableIRIn(); // sending ir stops the receiver so start the receiver again
if (irrecv.decode(&results)) // If there is a code
{
receivedCode = String(results.value, DEC); // Store the received code in a string variable
Serial.println(receivedCode);
irrecv.resume(); // Remove the code
if (receivedCode.length() == 3) // If the received code has length 3, we have been shot
{
Serial.println ("shot"); //display shot code
if (receivedCode.substring(0,1) != String(teamOrPlayer)) // Only continue if the shooter is on another team
{
Serial.println ("another team");
digitalWrite(redLight, 1); // Turn on the red lights
health = health - (receivedCode.substring(1, 3).toInt()); // Reduce health by incoming damage, if it's an enemy heal it won't have an effect
}
else if (receivedCode.substring(1, 3).toInt() == 0)
{
Serial.println("heal");
health = health + healAmount; // Heal if they are a teammate and they are doing 0 damage
}
}
if (health > 100) health = 100; // Set back to 100, cannot be on over 100 health
else if (health < 1)
{
Serial.println ("died");
switch(receivedCode.substring(0,1).toInt()) // Increment's the killer's kills by one
{
case (1):
timesKilledByTeam1++;
break;
case (2):
timesKilledByTeam2++;
break;
case (3):
timesKilledByTeam3++;
break;
case (4):
timesKilledByTeam4++;
break;
case (5):
timesKilledByTeam5++;
break;
case (6):
timesKilledByTeam6++;
break;
case (7):
timesKilledByTeam7++;
break;
case (8):
timesKilledByTeam8++;
break;
}
health = 100; // Reset the health
mag1Ammo = maxAmmo; // Refil the mags
mag2Ammo = maxAmmo;
// Update the LCD
lcd.clear();
lcd.setCursor(0,0);
lcd.print("You died!");
lcd.setCursor(0,1);
lcd.print("Return to base");
if (respawn == false) gameDone = true; // If playing one life gamemode, end game
else // Otherwise wait until respawn code is recieved
{
//irrecv.enableIRIn(); // sending ir stops the receiver so start the receiver again
while ((receivedCode != "50") && (gameDuration - (millis() / 1000 - gameStartTime) > 1)) // Keep looking for IR siginal until it's 50 or the time's up
{
//irrecv.enableIRIn(); // sending ir stops the receiver so start the receiver again
if (irrecv.decode(&results)) // if we have received an IR signal
{
receivedCode = String(results.value, DEC); // Store the received code in received code variable
irrecv.resume();
}
}
}
FullDisplayUpdate(teamOrPlayer); // Reset the LCD to gameplay mode
}
}
}
void setup()
{
lcd.begin(16, 2); // Set up the LCD's number of columns and rows
Serial.begin(9600); // Send code at 9600 bits per second
irrecv.enableIRIn(); // Start the receiver
pinMode(trigger, INPUT_PULLUP);
pinMode(magReader1, INPUT_PULLUP);
pinMode(magReader2, INPUT_PULLUP);
pinMode(healButton, INPUT_PULLUP);
}
void loop() // Repeat while looking for game (between games)
{
Serial.println("\nlp");
digitalWrite(redLight, 1); // Turn on the lights to show player isn't playing at the moment
// Print waiting screen to LCD
lcd.clear();
lcd.setCursor(0,0);
lcd.print("Awaiting start");
lcd.setCursor(0,1);
lcd.print("Player: " + String(playerNumber));
//irrecv.resume();// NOT YET TESTED
//irrecv.enableIRIn(); // sending ir stops the receiver so start the receiver again
while (not irrecv.decode(&results)) // wait until we have received an IR signal
{
// Transmit previous game data while we wait
//Serial.println("wait");
}
receivedCode = String(results.value, DEC); // Store the received code in a string variable
Serial.println(receivedCode); //display results
irrecv.resume();
if (receivedCode.length() == 4) // It was a sucessfull transmission if the recieved code was 4 characters long
{
playerClass = receivedCode.substring(3, 4).toInt(); // Set the class to the recieved class
Serial.println("clss: " + String(playerClass));
SetupClass();
gameDuration = 180 * (receivedCode.substring(1,2).toInt() + 1); // Set the time remaining and game duration to the received time
Serial.println("tm lft" + String((gameDuration - (millis() / 1000 - gameStartTime))) + ", gameDuration: " + String(gameDuration));
// Reset the death counter as a new game is starting
int timesKilledByTeam1 = 0;
int timesKilledByTeam2 = 0;
int timesKilledByTeam3 = 0;
int timesKilledByTeam4 = 0;
int timesKilledByTeam5 = 0;
int timesKilledByTeam6 = 0;
int timesKilledByTeam7 = 0;
int timesKilledByTeam8 = 0;
health = 100;
switch (receivedCode.substring(0, 1).toInt()) // Do operations based on the received game mode
{
case (1): // TDM
Serial.println("TDM");
gameMode = 1;
teamOrPlayer = receivedCode.substring(2, 3).toInt();
respawn = true;
Play(); // Call play with received team and respawn on
break;
case (2): //FFA
gameMode = 2;
Serial.println("FFA");
respawn = true;
teamOrPlayer = playerNumber;
Play(); // Call play with player number and respawn on
break;
case (3): // LTS
gameMode = 3;
Serial.println("LTS");
respawn = false;
teamOrPlayer = receivedCode.substring(2, 3).toInt();
//Play(); // Call play with received team and respawn off
break;
case (4): // LPS
gameMode = 4;
Serial.println("LPS");
respawn = false;
teamOrPlayer = playerNumber;
//Play(); // Call play with player numebr and respawn off
break;
default:
Serial.println("Unknown Gamemode");
break;
}
}
}
// End of code 2
I know this code is a mess, I should not be using global variables and there should be more subroutines, but I wrote most of it before remembering about them :/
If I run the sketch and don't "shoot" on that reset, the code works fully as intended, but the "times killed by player x" variables section I haven't got to yet.
Extra information which I don't think is relevant:
There is a third Arduino which allows you to choose a code and send it, or send the code "50". This is the "base" which someone must return to in order to respawn, as it's the only thing which can send the code "50". The codes it can send are explained below:
It sends a 4-digit number, but it's read as 4 separate numbers, for example, consider the code "rspq". The r is the game mode (1=team respawn, 2=free for all, 3=last team standing, 4=last player standing), s is the duration of the game (time(s) = 3 * (s + 1), resulting in possible times of 3,6,9...30), p is the team that that player is on (when playing a team game)(when not playing a team game it just uses the player ID stored on that particular gun) and q is the class (1=medic, 2=assault, 3=sniper).
There is a lot to say so if you have any questions just ask.
Any help/ideas would be appreciated, thanks in advance.