Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@CharlieASVZ
Last active February 11, 2019 18:36
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save CharlieASVZ/729b30dbed9a3e37372475c86961c948 to your computer and use it in GitHub Desktop.
Save CharlieASVZ/729b30dbed9a3e37372475c86961c948 to your computer and use it in GitHub Desktop.
EFCS_Arduin_DigiSpark_for_Airsoft
// This is free software released under: GNU General Public License (GPL), Version 3
// for more information about GNU GPLv3 look here: https://www.gnu.org/licenses/gpl.html
// so in principle you can use and modify it for anything you want, as long as it remains free software.
//* helpful links:
//* http://digistump.com/products/1 // Board
//* https://digistump.com/wiki/digispark/tutorials/connecting // Start programming
//* https://digistump.com/wiki/digispark/quickref // which PIN to use
//* https://www.arduino.cc/reference/en/#page-title // help for programming
//* originally build for DigiSpark ATTiny85 and Cyma CM0.30 AEP on 2s Lipo, for ohter setups you may have to adjust the settings.
//* WARNING: Will only work with a lot of mechanical manipulations for installing the microswitches and Mosfets in your AEP, or other gearboxes.
//* external safety switch recommend. if you want to disconnecting from battery for safety, you have to deal with 5s delay or modify the bootloader
//* Be aware that _PIN 3 and _PIN 4 are used for USB connection(only on Digispark Bord), can be buggy. and not optimal for a I/O switch.
//* _PIN 3 only have ca. +3.1V(working surprisingly well on my Board) to GND, _PIN 4 ca. 2V -so dont use for MosfetOutput - maybee with external Pullup resistor.
//* _PIN 4 here is used with 2.2KOhm to _PIN1 for using both I/O switch and USB programming capability - this is maybee not a recommend solution(it just works by chance)
//* if you don't need active breaking change the BREAKFET_PIN number witch CUTOFF_PIN number to avaoid extra resistor(only for Digispark because specific Pin restrictions)
//* _PIN 5 on some Digispark boards is used as reset- don`t use forI/O switch via GND... *using for voltagereading with highOhm resistors works fine
//* !!!!!!!!!!DISCONNECT _PIN 3 and _PIN 4 from GND for Programming via USB *simply use normal microswitch, which is open on default (only necessary for DigiSpark)
//* for battery voltage check you need to build a voltage divider, so PIN_5 never see more than 5v. PS: you need different values for R1/R2 for using 3s 11.1v Lipo.
//* WARNING: be careful with precocking and use it only at YOUR OWN RISK, because more stress on Gearbox and parts. (in the course of this i broke one AEP shell by the ARL mounting)
//* step by step instruction for installation:
//* - you need Arduino IDE with downloaded Bounce2.h and EEPROM.h files added to your libraries folder
//* - choose Your Arduino board (and check the Pinouts, if your board is different) this variation only fits DigiSpark
//* - DigiSpark Board with 2added resistors for voltageDivider in this case for 2s Lipo: GND--2.2MOhm--P5--1.5MOhm--VIN
//* - Connect Lipo via GND to+ VIN_PIN and switches from GND to _PIN XYZ. // VIN pin takes 7-12v,without extra heatsink. I used 2extra wires from battery+/- to Digispark GND/VIN
//* - Multimeter for general checking and improving Measured5vReference (recommend, if you want any precise voltage)
//* - Use IRLB-3034PBF MOSFET with 2.2 KOhm between Gain and Source, suppressor Diode, and 100 Ohm resistor to _PIN 1 recommend.
//* - if you don't use activebreaking be sure to change CUTOFF switch to _PIN 0, else (look above why) and connect_PIN 4 with 2.2KOhm to _PIN1
//* - Use with Microswitches on Trigger and Cutofflever, for Fireselector use normal switches, for AEP there are a few little difficult modifications:
//* for CutOff on AEP something like DF-P1L-0P-Z(Conrad)(tested) or maby something like ESE11(DigiKey),ESE-31(DigiKey) for AEGs you can also look what BTC and ASCU are using
//* - check and adapt the following values in the Code, recommend in(): R1, R2, Measured5vReference(5.0), CellCount(2.0 or 3.0), LOW_VOLTAGE(3.4), preCockingTime(0), aktiveBreakingTime(0),
//* modeVoltageProtectionEnabled (true), modePreCockingEnabled (false), modeAktiveBreakingEnabled (false).
//* - throw everything together and hope it works
//*
//* these steps are without installing P-channel Fet for activebreaking: IRF4905PBF MOSFET
//* - for 7.4Lipo you can connect the P-Channel Fet the following: Source==Motor(+)==Vcc+(7,4v), Drain==Motor(-) , Gate==OutputPin. (+resistors same as N-ChannelFet+ TVS diode)
//* - If set OutputPin:LOW (to GND), P-Fet see -7,4v and opens, if set to HIGH P-Fet see -7,4v+5v == -2,4v and hopefully does not yet opens(look datasheet)
//* - maybe there is a better way around and i don't think this works fine for 11.1vLipo (you should look on datasheet and modify the right voltages to switch the fet via the Gate)
//* for trigger programming options please look direct into the Code behind Setup
//* you have to hold trigger down while connecting battery and choose option by fireselector and still holding or resetting trigger while first ten beeps(look in code down there)
//-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
#include <avr/power.h>
#include <avr/sleep.h>
#include <Bounce2.h> // no standard library - eliminates oscillation while switch contacts are at closing procedure
#include <EEPROM.h> // https://github.com/thomasfredericks/Bounce2 - Download - adding to Arduino libraries folder
// these Pin numbers refer to DigiSpark P0 - P5 , for other Boards this changes of course
#define BREAKFET_PIN 0 // active breaking, need extra P-Channel-FET *if not used, it is easier to change pin numbers with CUTOFF_PIN so you finally get "#define CUTOFF_PIN 0" ,alternative look below and solder extra resistor
#define MOSFET_PIN 1 // Mosfet Gate * LED is coupled to _PIN 1
#define TRIGGER_PIN 2 // Switch Unit
#define FIRESELECTOR_PIN 3 // Fire Selector * if switch open == SEMI
#define CUTOFF_PIN 4 // CutOff Switch * if using DigiSpark and don't have another free Pin to use: 22kOhm between _PIN4 and _PIN1 for external pullup(because _PIN4 used for USB causes low HIGH level),
// or use pullup resistor via Vcc... but then you have to remove it for every programming
//Voltage input on _PIN 5 // Vin from voltage divider * please note, for DigiSpark the analogue read is different named -(command:"analogRead(0)" will read this _PIN 5 /P5),
#define R1 2.10 // R1 in Mohm * necessary for voltage divider
#define R2 1.48 // R2 in Mohm * for 7.4Lipo, for 11.1 please calculate new
#define Measured5vReference 4.70 // measure your exact internal 5v voltage supply with multimeter for more accuracy, or try numbers until it works accurate
#define LOW_VOLTAGE 3.7 // Shutdown Voltage for one cell * please use floating numbers, like 3.3 , 3.4 , 3.5 .... 3.6
#define preCockingTime 30 // time in ms * for testing use fully charged battery
#define activeBreakingTime 0 // time in ms * if wanted, you can decrease intensity by setting delay time before breaking.
// 0ms(recommend) means instant, most intense breaking. please don`t use to high numbers, because this delay isn`t interrupted by next trigger input and can slow down complete cycle
boolean firstSwitchOn =true; // for voltage measuring direct after battery connection * maybee turn off for less initial delay
boolean modeVoltageProtectionEnabled =true; // true = activated; false = deactivated //dont sure if works reliable!
float CellCount = 2.0; // you can choose later via trigger programming between 2.0 and 3.0
boolean modePreCockingEnabled =false; // true == enabled - not recommend for AEPs
boolean modeActiveBreakingEnabled =false; // short circuit on Motor via BreakingFet directly on Motor - this will cause more stress on your Gearbox and motorbrushes, but helps against burst and doubles in SEMI
int eeAddressVoltageProtection = 0; // adress for EEPROM(permanent onboard memory) to safe the trigger programmed settings
int eeAddressCellCount = 1;
int eeAddressActiveBreaking = 2;
int eeAddressPreCock = 3;
/*struct ConfigurationSettings { // not implemented stuff for more complex trigger programming in future...
float CellCount
float LOW_VOLTAGE
int preCockingTime
activeBreakingTime
boolean modePreCockingEnabled
boolean modeVoltageProtectionEnabled
boolean modeAktiveBreakingEnabled
};
ConfigurationSettings settings = {
2.0,
3.4,
30,
0,
false,
true,
true
};
EEPROM.put(0,settings); uncomment this the first time to initialize the EEPROM to "settings", then comment it out
EEPROM.get(0, settings);
// void loop(void)
//{
// if(somethingInEprommChanged)
// {
// EEPROM.put(0, settings);
// }
//} */
int cutOff = 0;
int trigger = 0;
int fireselector = 0;
float digitalVoltageread = 0; // measured voltage at PIN_5 to digital 0-1023
float voltageDivider = 0;
float realVoltage = 0;
float voltagePerCell = 4.2; // only estimated, inreality one cell can be bad and below the ohters
unsigned long lastVoltageCheck = 0; // timestamp when voltage was read last
unsigned long lastshot = 0; // timestamp when last time fireing
unsigned long timeAfterTriggerInput = 0; // time mark for Cutofftimer if defect CUTOFF_PIN
Bounce debouncer0 = Bounce(); // Debouncer3 for the CUTOFF_PIN 0
Bounce debouncer2 = Bounce(); // Debouncer2 for the TRIGGER_PIN 2
Bounce debouncer3 = Bounce(); // Debouncer3 for the FIRESELECTOR_PIN 3
void setup() //-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{
pinMode(CUTOFF_PIN, INPUT_PULLUP); // Pin setup with internal Pullup resistor // evtl. zusammenfassen debouncer0.attach(CUTOFF_PIN,INPUT_PULLUP);
debouncer0.attach(CUTOFF_PIN); // Attach the debouncer0
debouncer0.interval(3); // Debounce interval in ms // maybe can try the recommend 5ms
pinMode(MOSFET_PIN, OUTPUT); // Mosfet gate pin set to output
pinMode(TRIGGER_PIN, INPUT_PULLUP); // Pin setup with internal Pullup resistor // Without resistor in case of an open switch the digital pin is "free" and can be randomly true or false
debouncer2.attach(TRIGGER_PIN); // Attach the debouncer2 // Pullup resistor connects to +5V, so the _PIN is HIGH open and closed LOW, becaue schort-circuit do GND
debouncer2.interval(3); // Debounce interval in ms - time for oscillations to swing out
pinMode(FIRESELECTOR_PIN, INPUT_PULLUP); // Pin setup with internal Pullup resistor // _PIN 3 connects also to USB+ and caus problems with lower voltage
debouncer3.attach(FIRESELECTOR_PIN); // Attach the debouncer3
debouncer3.interval(3); // Debounce interval in ms
pinMode(BREAKFET_PIN, OUTPUT); // Pin for Motorbreaking
voltageDivider = R1 / (R1 + R2);
set_sleep_mode(SLEEP_MODE_PWR_DOWN); // Set sleep mode for low voltage cutoff
EEPROM.get(eeAddressPreCock, modePreCockingEnabled); // get modePreCockingEnabled from EEPROM
EEPROM.get(eeAddressCellCount, CellCount);
EEPROM.get(eeAddressVoltageProtection, modeVoltageProtectionEnabled); // get modeVoltageProtection from EEPROM
EEPROM.get(eeAddressActiveBreaking, modeActiveBreakingEnabled);
digitalWrite(BREAKFET_PIN, HIGH); //remember:P-Channel Fet, HIGH means deactivate Fet * here only necessary to be sure breakfet don't do anything stupid without declaration
debouncer2.update(); // first normal trigger update
trigger = debouncer2.read(); // write status
if (trigger == LOW) // pull trigger, while connecting battery for START trigger programming.........................................................................
{
for (int i=0; i <= 20; i++)
{
for (int u=0; u <= 30; u++)
{
digitalWrite(MOSFET_PIN, HIGH); // 20 fast, high noise motor vibration
delayMicroseconds(100);
digitalWrite(MOSFET_PIN, LOW);
delayMicroseconds(500);
debouncer2.update(); // second trigger update, need updated while beeping important to detect if trigger was released in this (beeping) time period
trigger = debouncer2.read(); // write status
}
delay(100);
}
debouncer3.update(); // first normal fireselector update
fireselector = debouncer3.read();
if (fireselector == LOW) // Switch closed - Fireselector == Fullauto.....................................................................................................
{
if (trigger == LOW) // check if trigger was holded down
{
modeVoltageProtectionEnabled =true; // still holding trigger the full time to enable Voltageprotection
}
else
{
modeVoltageProtectionEnabled =false; // you should have released the trigger within fast motor beeps to disable
}
EEPROM.put(eeAddressVoltageProtection, modeVoltageProtectionEnabled); // safe modeVoltageProtection in EEPROM
}//end setup for fullauto switch - voltageprotection
else // Switch open - Fireselector == SEMI.....................................................................................................
{
if (trigger == LOW) // check if trigger was holded down
{
modePreCockingEnabled =true; // still holding trigger the full time to enable
}
else
{
modePreCockingEnabled =false; // you should have released the trigger within fast motor beeps to disable
}
EEPROM.put(eeAddressPreCock, modePreCockingEnabled); // safe modePreCockingEnabled in EEPROM
}//end setup for SEMI switch - preCocking
for (int i=0; (i <= 180) || (trigger == LOW); i++)
{
digitalWrite(MOSFET_PIN, HIGH); // lower noise motor vibration until trigger is released - Pleas release Trigger after hearing this noise
delayMicroseconds(100);
digitalWrite(MOSFET_PIN, LOW);
delayMicroseconds(2000);
debouncer2.update(); // check TRIGGER_PIN
trigger = debouncer2.read(); // write status
}
//........................................................................................................................................................................................
//first programming part done. you can skip now by waiting 5s and doing nothing, or pull and hold trigger again within the 5s ............................................................
for (int i=0; (i <= 50) && (trigger != LOW); i++) // needed for recognizing again the trigger pull
{ // second programming cycle starts now similar to first, but changing different modes now
debouncer2.update();
trigger = debouncer2.read();
delay(100);
}
if (trigger == LOW) // still hold trigger, or skip
{
for (int i=0; i <= 20; i++)
{
for (int u=0; u <= 30; u++)
{
digitalWrite(MOSFET_PIN, HIGH); // fast, high noise motor vibration
delayMicroseconds(100);
digitalWrite(MOSFET_PIN, LOW);
delayMicroseconds(500);
debouncer2.update(); // second trigger update, need updated while beeping important to detect if trigger was released in this (beeping) time period
trigger = debouncer2.read(); // write status
}
delay(100);
}
debouncer3.update(); // first normal fireselector update
fireselector = debouncer3.read();
if (fireselector == LOW) // Switch closed - Fireselector == Fullauto.....................................................................................................
{
if (trigger == LOW) // check if trigger was holded down
{
CellCount = 2.0; // still holding trigger the full time to enable CellCount == 2 LipoCells
}
else
{
CellCount = 3.0; // you should have released the trigger within fast motor beeps enable CellCount == 3 LipoCells
}
EEPROM.put(eeAddressCellCount, CellCount); // safe modeVoltageProtection in EEPROM
}//end setup for fullauto switch - CellCount
else // Switch open - Fireselector == SEMI.....................................................................................................
{
if (trigger == LOW) // check if trigger was holded down
{
modeActiveBreakingEnabled =true; // still holding trigger the full time to enable
}
else
{
modeActiveBreakingEnabled =false; // you should have released the trigger within fast motor beeps to disable
}
EEPROM.put(eeAddressActiveBreaking, modeActiveBreakingEnabled); // safe modeActiveBreakingEnabled in EEPROM
}//end setup for SEMI switch - activebreaking
for (int i=0; (i <= 180) || (trigger == LOW); i++)
{
digitalWrite(MOSFET_PIN, HIGH); // lower noise motor vibration until trigger is released - Pleas release Trigger after hearing this noise
delayMicroseconds(100);
digitalWrite(MOSFET_PIN, LOW);
delayMicroseconds(2000);
debouncer2.update(); // check TRIGGER_PIN
trigger = debouncer2.read(); // write status
}
}//end second cycle of trigger programming
}//end trigger programming completely
EEPROM.get(eeAddressVoltageProtection, modeVoltageProtectionEnabled); //get all safed configurations from eeprom to variables // also if nothing has changed
EEPROM.get(eeAddressPreCock, modePreCockingEnabled);
EEPROM.get(eeAddressCellCount, CellCount);
EEPROM.get(eeAddressActiveBreaking, modeActiveBreakingEnabled);
for (int u=0; (u <= 500); u++)
{
digitalWrite(MOSFET_PIN, HIGH); // higher noise motor vibration - ready to go on
delayMicroseconds(100);
digitalWrite(MOSFET_PIN, LOW);
delayMicroseconds(1000);
}
}//end setup
void loop() //---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
{ // start switch updating
debouncer3.update(); // Bounce objects don’t use interrupts, so you need to update the object to check for state changes once per loop().
// This method returns true (1) if the pin state just changed, and false (0) if no change was detected.
fireselector = debouncer3.read(); // write status to fireselector - .read Returns the updated/current pin state (1 for HIGH, 0 for LOW)
if (fireselector == LOW) // Switch closed - Fireselector == Fullauto........................................................................................................
{
debouncer2.update(); // check TRIGGER_PIN
trigger = debouncer2.read(); // write status
if (trigger == LOW) // Switch closed - Trigger pulled
{
if (modeActiveBreakingEnabled) //(only if activeBreaking activated)
{
digitalWrite(BREAKFET_PIN, HIGH); //remember:P-Channel Fet, HIGH means deactivate Fet (Fet stays activated since last shot)
}
digitalWrite(MOSFET_PIN, HIGH); //normal N-Channel Fet, HIGH means activate Fet
do {
debouncer2.update();
trigger = debouncer2.read(); // check if trigger is still pulled
}
while (trigger == LOW); // until its released
timeAfterTriggerInput = millis(); // set time mark after released Trigger
//do // to be sure, that the gears stop every time at same position afer fullauto burst
// { // don't cutoff randomly after trigger release/-> wait for next cutoff cycle before switching of
// debouncer0.update(); // check cutoff switch
// cutOff = debouncer0.fell(); // debouncer.fell Returns true if the pin state just changed from HIGH to LOW, else false
// }
//while (cutOff == false && (millis() - timeAfterTriggerInput) < 130); // cutoff switch and automatic timecutoff, if no cutoff signal received after 130ms
// time can can be shorter than in SEMI, because of faster rotation Speed
// this (do-while)part can be leaved out completely if not wanted
// without, you can play even without cutoff switch completely fine
digitalWrite(MOSFET_PIN, LOW); // maybee change this part direct after trigger released without waiting for cutOff ALREADY DISABLED NOW?????????????????????????????????????????????????????????????
if (modeActiveBreakingEnabled)
{
digitalWrite(BREAKFET_PIN, LOW); // P-Channel Fet: LOW means activated
} // activate Fet, remember P-Channel Fet will switch activated at negative Gate(LOW: PIN==GND) to Source(Vcc+) input,
lastshot = millis(); // start Timer for voltagecheck
}
}//endFullauto
else // switch open - fireselector == SEMI.................................................................................................................
{
debouncer2.update(); // check Trigger
trigger = debouncer2.read();
if (trigger == LOW) // Switch closed - Trigger pulled
{
if (modeActiveBreakingEnabled)
{
digitalWrite(BREAKFET_PIN, HIGH); //remember:P-Channel Fet, HIGH means close Fet (Fet stays open since last shot)
}
digitalWrite(MOSFET_PIN, HIGH); // normal N-Channel Fet, HIGH means open Fet
timeAfterTriggerInput = millis(); // set time mark after Trigger pressed
do
{
debouncer0.update(); // check cutoff switch
cutOff = debouncer0.fell(); // debouncer.fell Returns true if the pin state just changed from HIGH to LOW, else false
}
while (cutOff == false && (millis() - timeAfterTriggerInput) < 150); // cutoff switch and automatic timecutoff, if no cutoff signal received/ if switch defect it will stop after 150ms
// fine tuning for each setup
if (modePreCockingEnabled)
{ // preCocking / motor pull the spring again to almost full-power-piston-position in the same cycle - faster trigger response
delay(preCockingTime); // Please note that pre-cocking puts a lot more stress on your gearbox.
} // The time in ms is individual for every battery - to high numbers cause double shots, especially, if upgrading to chihai motor
digitalWrite(MOSFET_PIN, LOW); // N-Channel Fet: LOW means deactivated
if (modeActiveBreakingEnabled)
{
delay(activeBreakingTime); // zero or low numbers recommend or you slow down everything
digitalWrite(BREAKFET_PIN, LOW); // P-Channel Fet: connected to Vcc+(7,4v Lipo) LOW means activated
} // activate Fet, remember P-Channel Fet will switch activated at negative Gate(LOW: PIN==GND) to Source(Vcc+) input,
do {
debouncer2.update(); // check if trigger is still pulled // is this relevant????????????????????
trigger = debouncer2.read();
}
while (trigger == LOW); // wait until trigger released
lastshot = millis(); // start Timer for voltagecheck
}
}//end SEMI
//start voltage check-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
if (((millis()-lastVoltageCheck > 5000) && (millis()-lastshot > 2000) && modeVoltageProtectionEnabled) || (firstSwitchOn && modeVoltageProtectionEnabled))
{ // Voltage check every 5 seconds and wait additional 2s after each shot
digitalVoltageread = analogRead(0); // analogue PIN Numbers are different from digital!
// this command "analogRead(0)" refers to _PIN 5 /P5 on DigiSpark board
realVoltage = (digitalVoltageread *Measured5vReference /1023.0) /voltageDivider; // calculate digital numbers to volts
voltagePerCell = realVoltage /CellCount ;
lastVoltageCheck = millis();
if (firstSwitchOn)
{
firstSwitchOn = false; // set Off, to run only once
for (int i=0; i <= 1; i++)
{
for (int u=0; u <= 30; u++)
{
digitalWrite(MOSFET_PIN, HIGH); // 2 high noise motor vibration * noise can be removed
delayMicroseconds(100);
digitalWrite(MOSFET_PIN, LOW);
delayMicroseconds(500);
}
delay(50);
}
}
if ((voltagePerCell < LOW_VOLTAGE) && modeVoltageProtectionEnabled)
{
for (int i=0; i <= 5; i++)
{
for (int u=0; u <= 150; u++)
{
digitalWrite(MOSFET_PIN, HIGH); // 5 not as high noise motor vibration
delayMicroseconds(100);
digitalWrite(MOSFET_PIN, LOW);
delayMicroseconds(1000);
}
delay(50);
}
sleep_enable();
sleep_cpu(); // BATTERY LOW -> GO SLEEP
}
}//end voltage check
}//end loop :)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment