// Servo control library
#include <Servo.h>
// Declaring servo variables > 0 = left barrel, 1 = right barrel, 2 = left core, 3 = right core
Servo shots[4];
// Trigger status
byte trigPress = 0;
// Declaring digital pins that will control the servo signals and the buttons
const byte shotPin1 = 3; // Digital output pin dedicated to the left barrel
const byte shotPin2 = 5; // Digital output pin dedicated to the right barrel
const byte corePin1 = 6; // Digital output pin dedicated to the left core
const byte corePin2 = 9; // Digital output pin dedicated to the right core
const byte triggerPin = 10; // Digital input pin dedicated to the trigger
const byte variantPin = 11; // Digital input pin dedicated to the variant selection button
const byte pumpPin = 12; // Digital input pin dedicated to the pump endstop
const byte reloadPin = 8; // Digital input pin dedicated to the reload button
// Temporary allocation of the pins for the RGB LED in place of the LED strip
const byte redPin = 13;
const byte grnPin = 4;
const byte bluPin = 2;
// Digital output pin for the DIN on the NeoPixel LED strip
// const byte ledPin = 13;
// This probably could have been done a bit more visually appealing with arrays, but I'd rather have
// separate variables with a clear and distinct name for each pin so that other users know what they're
// tampering with in the future. Makes it a bit easier to just modify the pins at leisure, IMO.
// Declaring time related variables for checking how long the trigger has been pressed
unsigned long time; // Time variable, determines the time the trigger has been pressed
bool reset = false; // Reset variable, used to reset the timer
const unsigned long coreTime = 2000; // Minimal time required holding the trigger in order for the
// shotgun to fire a core instead of a normal shot (in milliseconds)
// Can be adjusted by the user if you don't like the wait
// Declaring variables related to the state of each shot in an array
// (false when the shot hasn't been fired, true when it has been)
bool hasBeenShot[4] = {false,false,false,false};
// ↗ ↑ ↑ ↖
// left barrel right barrel left core right core
// Variables related to the variant type (0 = Core Eject, 1 = Pump Charge, 2 = Sawed-On)
byte variant = 0; // Selected variant
byte prevVariant = 0; // Previous variant selected
int varState = 0; // Current state of the button used to switch between variants
int lastVarState = 0; // Previous state of the button used to switch between variants
// Variable related to whether the shot from the Sawed-On variant was fired
bool sawShotFired = false;
// Variables related to the pump charges (0 - 3)
byte pumps = 1; // Current pumps
int pumpState = 0; // Current state of the button used to add pumps
int lastPumpState = 0; // Previous state of the button used to add pumps
// Variables that determine if the trigger is loose (0), pressed (1) or held down (2)
byte press = 0; // Current state of the trigger
byte prevPress = 0; // Previous state of the trigger
// Variables that determine the servo's angles to either stay retracted or fire a shot
const byte safe = 70; // Inactive servo angle ("safe" position)
const byte shoot = 0; // Active servo angle ("shoot" position)
void setup()
{
// Initializing serial
Serial.begin(9600);
// Initializing the pushbuttons
pinMode(triggerPin, INPUT);
pinMode(variantPin, INPUT);
pinMode(pumpPin, INPUT);
pinMode(reloadPin, INPUT);
//Initializing the RGB LED
pinMode(redPin, OUTPUT);
pinMode(grnPin, OUTPUT);
pinMode(bluPin, OUTPUT);
// Attaching each servo with their corresponding pin
shots[0].attach(shotPin1); // Left barrel, pin 1
shots[1].attach(shotPin2); // Right barrel, pin 2
shots[2].attach(corePin1); // Left core, pin 3
shots[3].attach(corePin2); // Right core, pin 4
// Place all servos in the default "safe" position
for(int i = 0; i < 4; i++)
shots[i].write(safe);
colour(0,0,255);
}
void loop()
{
// Check if the user has reloaded
if(digitalRead(reloadPin))
reload();
else
{
// Update the previous variant state
prevVariant = variant;
// Update the current and past state of the trigger for later calculations
prevPress = press;
press = parsePress();
// Check for which variant has been chosen
parseVariant();
// If the variant has been changed after pressing the trigger, reset the trigger values to avoid a misfire
if(variant != prevVariant && press > 0)
{
press = 0;
prevPress = 0;
}
// Execute the corresponding function depending on the variant selected
switch(variant)
{
case 0:
coreEject(press,prevPress);
break;
case 1:
pumpCharge(press,prevPress);
break;
case 2:
sawedOn(press,prevPress);
break;
default:
break;
// Do nothing
}
}
// Slight delay to avoid overlap
delay(50);
}
// Function modifies the value of the current variant depending on how many times the button was pressed
void parseVariant()
{
varState = digitalRead(variantPin); // Update the current state of the variant button
if((varState != lastVarState) && varState)
{
variant = (variant + 4) % 3; // Cycle between the 3 different variants using modulus
lastVarState = varState; // Update the last state of the variant button
pumps = 1; // Reset the amount of pumps upon changing variants
switch(variant) // Delete or comment the entire clause for the final version, only for debug purposes
{
case 0:
colour(0,0,255); // Blue
break;
case 1:
colour(0,255,0); // Green
break;
default:
colour(255,0,0); // Red
}
}
else
lastVarState = varState; // Update the last state of the button for the next check
}
// Function modifies the value of the current amount of accumulated pumps depending on how many
// times the associated button was pressed
void parsePump()
{
pumpState = digitalRead(pumpPin); // Update the current state of the pump button
if((pumpState != lastPumpState) && pumpState)
{
if(pumpState < 4)
{
pumps++; // Update the current pumps
lastPumpState = pumpState; // Update the last state of the pump button
}
/*
switch(pumps) // Delete or comment the entire clause for the final version, only for debug purposes
{
case 2:
Serial.println("Pumped once!");
break;
case 3:
Serial.println("Pumped twice!");
break;
default:
Serial.println("Pumped thrice!");
}
*/
}
else
lastPumpState = pumpState; // Update the last state of the button for the next check
}
// Function returns 0 if the button isn't pressed, 1 if it was just pressed and 2 if it was held > 2 seconds
byte parsePress()
{
// Check if trigger is pressed and reset is false
if(digitalRead(triggerPin) && !reset)
{
time = millis(); // Take a sample of the current time elapsed since the board started
reset = true; // Update the reset state for the next check
}
// Check if trigger is released and reset is true
if(!digitalRead(triggerPin) && reset)
reset = false; // Update the reset state for the next check
// If trigger is pressed and 2 seconds has passed, return 2
if(digitalRead(triggerPin) && (millis() - time > coreTime))
return 2;
// If trigger is pressed for less than 2 seconds, return 1
else if(digitalRead(triggerPin) && (millis() - time <= coreTime))
return 1;
// If trigger wasn't pressed, return 0
else
return 0;
}
// Function activates the servos according to the requirements of the Core Eject variant
// - If the trigger is pressed for a short time, fire a single main shot
// - If the trigger is held and released after 2 seconds, fire both core shots
void coreEject(byte trigPress, byte prevTrigPress)
{
// If trigger is released and 2 seconds have passed, fire a core.
switch(prevTrigPress)
{
case 2:
if(!hasBeenShot[2] && !trigPress) // Check if the left core has been fired and if the trigger was released
{
shots[2].write(shoot); // Turn the servo to the fire configuration
hasBeenShot[2] = true; // Update the state of the fired core
}
if(!hasBeenShot[3] && !trigPress) // Check if the right core has been fired and if the trigger was released
{
shots[3].write(shoot); // Turn the servo to the fire configuration
hasBeenShot[3] = true; // Update the state of the fired core
}
break;
// If trigger is pressed for less than 2 seconds, fire a normal shot upon release
case 1:
if(!hasBeenShot[0] && !trigPress) // Check if the left shell has been fired and if the trigger was released
{
shots[0].write(shoot); // Turn the servo to the fire configuration
hasBeenShot[0] = true; // Update the state of the fired shot
}
else if(!hasBeenShot[1] && !trigPress) // Check if the right shell has been fired and if the trigger was released
{
shots[1].write(shoot); // Turn the servo to the fire configuration
hasBeenShot[1] = true; // Update the state of the fired shot
}
break;
// If trigger isn't pressed, do nothing
default:
break;
}
}
// Function activates the servos according to the requirements of the Pump Charge variant
// - If the trigger is pressed without pumping the gun, fire 1 main shot
// - If the trigger is pressed after pumping once, fire both main shots
// - If the trigger is pressed after pumping twice, fire both shots and a core
// - If the trigger is pressed after pumping thrice, fire all 4 shells at once
void pumpCharge(byte trigPress, byte prevTrigPress)
{
parsePump(); // Check how many times we have pumped the shotgun
if(prevTrigPress == 0 && trigPress) // If trigger has been pressed, fire the correct amount of shots upon release
{
for(byte i = 0; i < 4; i++) // We run through every barrel checking if they have been shot
{
if(!hasBeenShot[i] && pumps > 0) // If there are pump charges left and the barrel hasn't been shot...
{
shots[i].write(shoot); // ... then shoot the current shell...
hasBeenShot[i] = true; // ... update the shell state...
pumps--; // ... and subtract one pump charge from the total
}
}
pumps = 1; // Always reset the pumps after finishing
}
}
// Function shoots each shot individually when the trigger is pressed
void sawedOn(byte trigPress, byte prevTrigPress)
{
if(prevTrigPress == 0 && trigPress) // If the trigger has been pressed, execute the rest of the code
{
sawShotFired = false; // This variable will avoid shooting more than one shot per trigger press
for(byte i = 0; i < 4; i++) // Run through each barrel to see if they contain a loaded shell
{
if(!hasBeenShot[i] && !sawShotFired) // If they contain a loaded shell and we still haven't fired...
{
shots[i].write(shoot); // ... then fire a shot...
hasBeenShot[i] = true; // ... make sure we update this variable so we know it's unloaded...
sawShotFired = true; // ... and update this variable so that we don't fire any more shots
}
}
}
}
// Function resets all relevant variables when the shotgun is opened for reload
void reload()
{
for(byte i = 0; i < 4; i++)
{
hasBeenShot[i] = false; // Reset the state of all the shots to show that the shells have been reloaded
shots[i].write(safe); // Reset the orientation of the servos
pumps = 1; // Reset the amount of stored pumps
}
}
void colour(int red, int grn, int blu)
{
digitalWrite(redPin,red);
digitalWrite(grnPin,grn);
digitalWrite(bluPin,blu);
}