Arc Grenade Code

Following up my previous posts breaking down some of the parts for the Arc Grenade and showing some of the wiring, I thought it would be appropriate to share the code running the Arc Grenade. Please note that I am in no way going to pretend like I’m a good programmer – there’s probably a lot of bad habits encapsulated in the lines below. That said, if it works, it works, right?

This was written with the Arduino Pro Mini 5v 16Mhz in mind. The reason I used this chip is twofold: First, it has plenty of onboard memory, which means I don’t have to fight to pare the code down to barebones. Second, it runs an ATMega 328P processor and is AVR-compatible, meaning that the Arduino library’s tone() function works without any issues. On something smaller like the ATTiny85 that you’d find in an Adafruit Trinket, the stock tone() function does not work, and you have to go to significant lengths for a workaround.

This code uses Adafruit’s NeoPixel library, because it is excellent and does what I am not smart enough to do. If you have not installed this library, make sure you do.

// This code was designed to run on an Arduino Pro Mini 5v. 


#define PIN 2 //Pin used to driving Neopixel Data
#define PIXELCOUNT  18 //Number of neopixels in the arc grenade (1 per plug, 18 plugs)
#define BUTTONPIN 13 //Input button for the 'arming' mechanism. 
#define TONEPIN 12 //Output for piezo buzzer to produce audio tones. 

//The following is just a few frequencies that correspond with particular musical notes. You can change these to suit whatever tones your particular piezo buzzer is good at procuding.  
#define G2 98
#define C3 131 
#define C5 523
#define C6 1047
#define E6 1319

long previousMillis = 0;
long previousToneMillis = 0; //separate counter for keeping track of Tone generation. 
unsigned long currentMillis = 0;
unsigned long currentToneMillis = 0;
unsigned long lastDebounceTime = 0;
unsigned long debounceDelay = 5;

bool toggleState = false;
const bool armed  = true;
const bool disarmed = false;
bool lastState = disarmed;
bool newState = disarmed;
bool justArmed = false;

long lightGenerationInterval = 3; //How often a new flicker effect is created. 
long toneGenerationInterval = 70; //How often a new passive audio sound is created. 
long fadeInterval = 300;
float fadeTimeValues[PIXELCOUNT];

Adafruit_NeoPixel strip = Adafruit_NeoPixel(PIXELCOUNT, PIN, NEO_GRB + NEO_KHZ800); //If you are getting weird results, check that your neopixels are GRB and not RGB. The discrete through-hole ones (individual) seem to be GRB.

void setup() {

void loop() {
  if(toggleState == disarmed){
    electricGenerator(); //Function call to create a new 'spark' on a random LED if enough time has passed.
    electricUpdate(); //Function call to calculate the decay of spark brightness over time. 
    playIdleTone(); //Function call to generate passive clicking audio to simulate sparks through Piezo buzzer.;
  } else { 
    redFade(50); //"Arming" animation; full red fade to zero.
    windUpGrenade(); //Function call to run the scanLine() function multiple times for the appropriate animation.
    explode(20); //Kaboom animation. 
    toggleState = disarmed;

void windUpGrenade(){
  for(int i = 7; i>0; i--){
    for(int k=5;k>0;k--){

void buttonRead(){ //Should be some rudimentary amount of debouncing going on here. 
  newState = digitalRead(BUTTONPIN);
  if( lastState != newState ){ //state changed
    delay( debounceDelay );
    lastState = newState;
    if( newState == armed && toggleState == false ){
      toggleState = armed;
    } else if( newState == armed && toggleState == true ){
      toggleState = disarmed;

void playIdleTone(){
  currentToneMillis = millis();
  if(currentToneMillis - previousToneMillis > toneGenerationInterval){
    previousToneMillis = currentToneMillis;
    int newTone = random(6);
    tone(TONEPIN, C5, 10);

void changeToneGenerationInterval(){
  toneGenerationInterval = toneGenerationInterval+random(-40,40);
  if(toneGenerationInterval > 100){
    toneGenerationInterval = 100-random(0,10);
  } else if (toneGenerationInterval < 20){ toneGenerationInterval = 20+random(0,10); } } void electricGenerator(){ currentMillis = millis(); if(currentMillis - previousMillis > lightGenerationInterval){
    previousMillis = currentMillis;
    int newLight = random(PIXELCOUNT);
    fadeTimeValues[newLight]=currentMillis; //records the time the new fade starts.

void electricUpdate(){
  for(int i = 0; i<PIXELCOUNT;i++){ int timeSinceNew = currentMillis - fadeTimeValues[i]; if(timeSinceNew>fadeInterval){
    } else {
    strip.setPixelColor(i,0,0,easeOutQuad(timeSinceNew, 128, -128, fadeInterval));

void scanLine(float delayValue){
  for(int i = 0;i<PIXELCOUNT;i++){
    tone(12, C6+(i*10), 8);
  for(int y = 0;y<PIXELCOUNT;y++){

void redFade(int steps){
    for (int i = 0; i<PIXELCOUNT; i++){
      tone(12, E6, 10);
    for(int s=0; s<steps; s++){
      for(int p=0; p<PIXELCOUNT;p++){
        strip.setPixelColor(p,easeOutQuad(s, 180, -180, steps),easeOutQuad(s, 255, -255, steps),0);
      tone(12, C5+(s*5), 10);;

void explode(int steps){ //Max white brightness out to zero over number of steps. Tone winds up as LEDs wind down. 
    for (int i = 0; i<PIXELCOUNT; i++){
    for(int s=0; s<steps; s++){
      for(int p=0; p<PIXELCOUNT;p++){
        strip.setPixelColor(p,easeOutQuad(s, 255, -255, steps),easeOutQuad(s, 255, -255, steps),easeOutQuad(s, 255, -255, steps));
      tone(12, C3+(s*50),10);;

float easeOutQuad (float currentTime, float base, float change, float duration) { //An easing function to make the animations look a little nicer. 
  currentTime /= duration;
  return -change * currentTime*(currentTime-2) + base;

Leave a comment