Jump to content


Oryhara

Member Since 05 May 2008
Offline Last Active Nov 08 2013 02:20 PM

Posts I've Made

In Topic: Rifling in Nerf Guns

07 November 2013 - 01:05 PM

Actually it is possible to rifle a nerf barrel while maintaining seal. Using this approach
Using my 3d printer I made an octagonal press to form 17/32" brass tubing into a polygonal shape. It imparted spin in the dart and increased accuracy. However the increased friction required more air pressure behind the dart, and that along with the increased friction resulted in streamline darts peeling like an onion after only a few shots, or if any wear or imperfections were present in the dart. Also the polygonal press was very difficult to use precisely. After ruining a couple dozen darts in this manner I halted further work.

Currently I'm testing a flywheel-based approach to rifling, by 3d printing a new motor housing with angled flywheels to impart a spin on the dart. Initial efforts with 2 flywheels showed some promise, but the dart had a high chance to became unstable due to differing levels of contact with the flywheels if it entered at any position other that directly centered perpendicularly between the flywheels. Straight flywheels would not demonstrate any such instability because the contact point with the dart is a line perpendicular to the path of dart travel. With angled flywheels, the contact point is also angled, so if the dart is off to one side, it gets more contact with one wheel vs the other.

So I added a third flywheel, and angled the 3 of them at 120 degrees around the path the dart would follow. This should act to center the dart such that it receives the correct amount of force from each flywheel. Initial tests with this mechanism at a 30 degree angle of rifling caused severe spin in the dart, to the point of fishtailing uselessly. a little quick real-world math showed that a .50 BMG round with 1 in 15" twist rate has approximately 6 degrees of rifling angle, so I redesigned the printed flywheel housing to use a much shallower angle on the flywheels.

Expect a full writeup when I have finished removing support material from the printed part and tested it.

In Topic: [WIP] Stampede ACB, the Arduino-powered Stampede ECS

12 August 2013 - 03:42 PM

The only issue I have with that is that I would like my LED to be flashing even when firing. A delay wouldn't allow that. Now, adding a whole timing function is a lot more work, but I'd really rather not have the whole code stop for firing anyway.


It really wouldn't be hard to process LED blink code in there.

But its your project, I just wanted to share the lessons i learned while building remote-controlled stampedes.

In Topic: [WIP] Stampede ACB, the Arduino-powered Stampede ECS

11 August 2013 - 04:51 PM

Ok, here is the code for my pair of relay-controlled stampedes.
I'll apologize now because it is both horribly commented and way more than you need for what you want to do.
This project had an LCD display that showed ammo for both stampedes, had configurable burst fire, semi, and fully automatic fire modes, and maintained preferences when powering on and off.
here is the bit that managed the relay and stampede fire cycle switch.

void pullTrigger(int barrel)
{//0 neither, 1 upper, 2 lower
  if(barrel == 1)
  {
    while(digitalRead(UpperSensor) == HIGH)
    {//check and see if the firing cycle has taken over
      Serial.println("firing interval");
      digitalWrite(RelayUpper, LOW);//turn the relay on 
      delay(FIRING_INTERVAL);//wait a little bit
      digitalWrite(RelayUpper, HIGH);//turn the relay off
    }
  }
  else if(barrel == 2)
  {
    while(digitalRead(LowerSensor) == HIGH)
    {//check and see if the firing cycle has taken over
      Serial.println("firing interval");
      digitalWrite(RelayLower, LOW);//turn the relay on 
      delay(FIRING_INTERVAL);//wait a little bit
      digitalWrite(RelayLower, HIGH);//turn the relay off
    }    
  }
}

Like i said, i had two stampedes, but there you can see how it handled firing via relay.

the full code is below

 /*

*/

#include <LiquidCrystal.h>
#include <DFR_Key.h>
#include <stdio.h>
#include <avr/pgmspace.h>
#include <phi_big_font.h>
#include <EEPROM.h>

//storing preferences between power cycles
#define EEPCOUNTUPPER 0
#define EEPCOUNTLOWER 1
#define EEPBARREL 2
#define EEPROUNDS 3
#define EEPMODE 5


#define CAPACITY 18
#define DISPLAYCOUNT 19
char* capacityDisplay[] = {"00", "01", "02", "03", "04", "05", "06", "07", "08", "09", "10", "11", "12", "13", "14", "15", "16", "17", "18", "XX"};
static unsigned char CountUpper;
static unsigned char CountLower;

#define BARRELS 3 

static boolean Shooting = true;
static boolean menuBarrel = true;
static int Mode; //0: Both, 3 Empty uppersecond; 4 Lower emptysecond; 5 Both empty; 6 menuRounds; 7 menuBarrel
//0 > 5 > 0
// 1 > 3 > 2 > 4 > 5
// 2 > 4 > 1 > 3 > 5

static char Rounds; //0: semi-auto, 1: Full auto, 2+ bursts 
static unsigned char Barrel; //0 both, 1 upper, 2 lower

const static int RelayUpper = 11;
static const int RelayLower = 12;
static const int Trigger = 13;
#define UpperSensor  A2
#define LowerSensor  A3
#define FIRING_INTERVAL 10

//A1 unused
#define UPPERMAGSENSOR A4
#define LOWERMAGSENSOR A5
#define UPPEROVERRIDE 2
#define LOWEROVERRIDE 3
#define BLINKRATE 500
#define DISPLAYRATE 3000
//Pin assignments for SainSmart LCD Keypad Shield
LiquidCrystal lcd(8, 9, 4, 5, 6, 7); 
//---------------------------------------------
DFR_Key keypad;

int localKey = 0;
String keyString = "";

unsigned long time;
unsigned long lastDisplay;
unsigned long lastBlink;

void setup() 
{ 
  Serial.begin(9600);
  
  digitalWrite(RelayUpper, HIGH);
  digitalWrite(RelayLower, HIGH);
  pinMode(RelayUpper,OUTPUT);
  pinMode(RelayLower,OUTPUT);
  pinMode(Trigger,INPUT_PULLUP);
  pinMode(UpperSensor,INPUT_PULLUP);
  pinMode(LowerSensor,INPUT_PULLUP);
  pinMode(UPPERMAGSENSOR, INPUT_PULLUP);
  pinMode(LOWERMAGSENSOR, INPUT_PULLUP);
  pinMode(UPPEROVERRIDE, INPUT_PULLUP);
  pinMode(LOWEROVERRIDE, INPUT_PULLUP);
  init_big_font(&lcd);
  lcd.begin(16, 2);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("SPARKYBFG ONLINE");
  lcd.setCursor(0,1);
  lcd.print("INITIALIZING NOW");
  waitTrig(2000);
  lcd.clear();

  Mode = 1;

  keypad.setRate(2500);
  char check = EEPROM.read(0);
  if(check == 255)
  {//no preferences saved
    CountUpper = CAPACITY;
    CountLower = CAPACITY;
    Rounds = 1;
    Barrel = 0;
  }
  else
  {
    CountUpper = EEPROM.read(EEPCOUNTUPPER);
    CountLower = EEPROM.read(EEPCOUNTLOWER);
    Rounds = EEPROM.read(EEPROUNDS);
    Barrel = EEPROM.read(EEPBARREL);
  }
  displayCount();
}

void loop()
{ 
  waitTrig(100);
  Serial.print("Barrel: ");
  Serial.println( Barrel);
  Serial.print("Rounds: ");
  Serial.println(Rounds, DEC);
  HandleKeys();

  //reload detection
  if(digitalRead(UPPERMAGSENSOR) == HIGH)
  {
    Serial.println("Upper Reloaded");
    CountUpper = CAPACITY;
  }
  if(digitalRead(LOWERMAGSENSOR) == HIGH)
  {
    Serial.println("Lower Reloaded");
     CountLower = CAPACITY;
  }
  
//  Serial.println(analogRead(UPPEROVERRIDE));
//  Serial.println(analogRead(LOWEROVERRIDE));
  if(digitalRead(UPPEROVERRIDE) == LOW)
  {
    Serial.println("Upper Disabled");
    Barrel = 1;
    CountUpper = 0;
  }
  if(digitalRead(LOWEROVERRIDE) == LOW)
  {
    Serial.println("Lower Disabled"); 
    Barrel = 2;
    CountLower = 0;
  }
//firing control -- Blocking
  while(digitalRead(Trigger) == LOW)
  {
    Serial.println("shooting");
    Shooting = true;
    
    switch(Barrel){
      case 0://fire both barrels
      {
        FireBoth(Rounds);
      }    
      break;
      case 1://fire upper 
      {
        FireUpper(Rounds);
      }
      break;
      case 2://fire lower
      {
        FireLower(Rounds);
      }
      break;
      default://not a valid firing mode
      {
        
      }
      break;
      
    }
  }
  time = millis();
  if(Shooting == false)
  {
    Serial.println("Not Shooting.  Display menu");
    if(time > lastDisplay + DISPLAYRATE)
    {
      selectCount();
      lastDisplay = millis();
    }
    if(lastBlink + BLINKRATE < time)
    {
      if(((time/BLINKRATE)%2) == 0)
      {
        blinkMenuCursor();
      }
      else
      {
        blankMenuCursor();
      }
    }
  }
  else
  {
    if(time > lastDisplay + DISPLAYRATE)
    {
      displayCount();
      lastDisplay = millis();
    }
  }
    
  switch(Mode){
    break;
    case 3:
    {
      if(CountLower == 0)
      {
        Mode = 5;
      }
      else
      {
        Mode = 2;
        Barrel = 2;
      }
    }
    break;
    case 4:
    {
      if(CountUpper == 0)
      {
        Mode = 5;
      }
      else
      {
        Mode = 1;
        Barrel = 1;
      }
    }
    break;
    case 5:
    {
      reload();
    }
    break;
    default:
    {
    }
    //don't need to do nothin
  }
  savePrefs();
}
void FireBoth(int rounds)//0 makes no sense in this mode, but is valid.  
{
  if(rounds == 0)
  {
    FireUpper(rounds);
    Barrel = 1;
  }
  else
  {
    short ready;
    while(rounds > 0)
    {
      ready = readyToFire();
      if(ready)
      {
        pullTrigger(ready);
        rounds--;
        shotFired(ready);
      } 
    }
  }
}

void shotFired(short barrel)
{
  if(barrel == 1)
  {
    CountUpper--;
    displayCount();
  }
  else
  {
    CountLower--;
    displayCount();
  }
}

short readyToFire()//checks upper first
{
  if(digitalRead(UpperSensor) == HIGH)
  {
    return 1;
  }
  else if(digitalRead(LowerSensor) == HIGH)
  {
    return 2;
  }
  else
  {
    return 0;
  }
}

void pullTrigger(int barrel)
{//0 neither, 1 upper, 2 lower
  if(barrel == 1)
  {
    while(digitalRead(UpperSensor) == HIGH)
    {//check and see if the firing cycle has taken over
      Serial.println("firing interval");
      digitalWrite(RelayUpper, LOW);//turn the relay on 
      delay(FIRING_INTERVAL);//wait a little bit
      digitalWrite(RelayUpper, HIGH);//turn the relay off
    }
  }
  else if(barrel == 2)
  {
    while(digitalRead(LowerSensor) == HIGH)
    {//check and see if the firing cycle has taken over
      Serial.println("firing interval");
      digitalWrite(RelayLower, LOW);//turn the relay on 
      delay(FIRING_INTERVAL);//wait a little bit
      digitalWrite(RelayLower, HIGH);//turn the relay off
    }    
  }
}
  
void FireUpper(int rounds)
{ 
  if(CountUpper != 0)
  {
    if(rounds == 0)
    {
      while(digitalRead(UpperSensor) == HIGH)
      {//check and see if the firing cycle has taken over
        Serial.println("firing interval");
        digitalWrite(RelayUpper, LOW);//turn the relay on 
        delay(FIRING_INTERVAL);//wait a little bit
        digitalWrite(RelayUpper, HIGH);//turn the relay off
      }
      while(digitalRead(UpperSensor) == LOW)
      {
        //firing in progress
      }//and finished
      shotFired(Barrel);
      if(CountUpper == 0)
      {
        Mode = 3;
      }
      while(digitalRead(Trigger) == LOW)
      {
      //wait because semi-auto means 1 shot per pull
      }
    }

    for(int i = rounds; i> 0; i--)
    {
      if(CountUpper == 0)
      {
        FireLower(i);
        i = 0;
      }
      while(digitalRead(UpperSensor) == HIGH){//check and see if the firing cycle has taken over
        Serial.println("firing interval");
        digitalWrite(RelayUpper, LOW);//turn the relay on 
        delay(FIRING_INTERVAL);//wait a little bit
        digitalWrite(RelayUpper, HIGH);//turn the relay off
      }
      while(digitalRead(UpperSensor) == LOW)
      {
        //firing in progress
      }//and finished
      shotFired(Barrel);
    }
  }
}

void FireLower(int rounds)
{ 
  if(CountLower != 0)
  {
    if(rounds == 0)
    {
      while(digitalRead(LowerSensor) == HIGH)
      {//check and see if the firing cycle has taken over
        Serial.println("firing interval");
        digitalWrite(RelayLower, LOW);//turn the relay on 
        delay(FIRING_INTERVAL);//wait a little bit
        digitalWrite(RelayLower, HIGH);//turn the relay off
      }
      while(digitalRead(LowerSensor) == LOW)
      {
        //firing in progress
      }//and finished
      shotFired(Barrel);
      if(CountLower == 0)
      {
        Mode = 3;
      }
      while(digitalRead(Trigger) == LOW)
      {
      //wait because semi-auto means 1 shot per pull
      }
    }

    for(int i = rounds; i> 0; i--)
    {
      if(CountLower == 0)
      {
        FireUpper(i);
        i = 0;
        Mode = 3;
      }
      else
      {
        while(digitalRead(LowerSensor) == HIGH){//check and see if the firing cycle has taken over
          Serial.println("firing interval");
          digitalWrite(RelayLower, LOW);//turn the relay on 
          delay(FIRING_INTERVAL);//wait a little bit
          digitalWrite(RelayLower, HIGH);//turn the relay off
        }
        while(digitalRead(LowerSensor) == LOW)
        {
          //firing in progress
        }//and finished
        shotFired(Barrel);
      }
    }
  }
}
void Select_Button()
{
  Serial.println("Select Pressed");
  Shooting = !Shooting;
    Serial.print("Shooting value : ");
      Serial.println(Shooting);
  if(Shooting)
  {
    displayCount();
  }
  else
  {
    lastDisplay = 0;
  }
}  

void Left_Button()
{
  Serial.println("leftbutton pressed");
  if(Shooting)
  {
    reload();
  }
  else if(menuBarrel)
  {
    Barrel = (Barrel +(BARRELS-1))%BARRELS;
    lastDisplay = 0;
  }
  else
  {
    
    if(Rounds == 0)
    {
      Rounds = 1;
    }
    else
    {
      Rounds--;
    }
    lastDisplay = 0;
  }
}  

void Right_Button()
{
  Serial.println("Right Pressed");
  if(Shooting)
  {
    reload();
  }
  else if(menuBarrel)
  {
    Barrel = (Barrel + 1)%BARRELS;
    lastDisplay = 0;
  }
  else
  {
    Rounds++;
    lastDisplay = 0;
  }
}
void Up_Button()
{
  Serial.println("Up Pressed");
  if(Shooting)
  {
    reload();
  }
  else 
  {
    menuBarrel = !menuBarrel;
    lastDisplay = 0;
    lastBlink = 0;
  }
}

void Down_Button()
{
  Serial.println("Down Pressed");
  if(Shooting)
  {
    reload();
  }
  else 
  {
    menuBarrel = !menuBarrel;
    lastDisplay = 0;
    lastBlink = 0;
  }
}

void HandleKeys()
{
  localKey = keypad.getKey();
  
  if (localKey != -1)//TODO confirm that -1 is correct and not 0
  {
    //Serial.println(localKey);
  //Key Codes (in left-to-right order):
  //None   - 0
  //Select - 1
  //Left   - 2
  //Up     - 3
  //Down   - 4
  //Right  - 5
    switch(localKey){
      case 1:
      {
        Select_Button();
      }
      break;
  
      case 2:
      {
        Left_Button();
      }
      break;
      
      case 3:
      {
        Up_Button();
      }
      break;
      
      case 4:
      {
        Down_Button();
      }
      break;
      
      case 5:
      {
        Right_Button();
      }
      break;
      
      default: //should never happen
      break;
    }

  }
}

void blinkMenuCursor()
{
  if(menuBarrel)
  {
    lcd.setCursor(5,1);
    lcd.print("::");
  }
  else
  {
    lcd.setCursor(5,0);
    lcd.print("::");
  }
}
void blankMenuCursor()
{
  if(menuBarrel)
  {
    lcd.setCursor(5,1);
    lcd.print("  ");
  }
  else
  {
    lcd.setCursor(5,0);
    lcd.print("  ");
  }
}

void selectCount(){
// ________________
// BURST: SEMI-AUTO
// BURST: FULL-AUTO
// BURST: XX ROUNDS
// ________________
// FIRE :  UPPER
// FIRE :  LOWER
// FIRE :  DOUBLE

  lcd.clear();
  lcd.setCursor(0, 1);
  lcd.print("FIRE   ");
  switch(Barrel)
  {
    case 0:
      lcd.print(" DOUBLE");
    break;
    case 1:
      lcd.print(" UPPER");
    break;
    case 2:
      lcd.print(" LOWER");
    break;
    default:
      lcd.print(" ERROR");
  }
  lcd.setCursor(0,0);
  lcd.print("BURST  ");
  switch(Rounds)
  {
    case 0:
      lcd.print("SEMI-AUTO");
    break;
    case 1:
      lcd.print("FULL-AUTO");
    break;
    default:
      lcd.print(Rounds, DEC);
      lcd.print(" ROUNDS");
  }

}


void displayCount()
{
  lcd.clear();
  render_big_msg(capacityDisplay[CountLower],0,0);
  render_big_msg(capacityDisplay[CountUpper],8,0);
  
}

void reload()
{
  CountUpper = CAPACITY;
  CountLower = CAPACITY;
  displayCount();
}

void waitTrig( int mseconds)
{
  unsigned long endTime = millis() + mseconds;
  while(endTime > time)
  {
    time = millis();
    if(digitalRead(Trigger) == LOW)
    {
      endTime = 0;
    }
  }
}
    
void savePrefs()
{
  char check;
  check = EEPROM.read(EEPCOUNTUPPER);
  if(check != CountUpper)
  {
    EEPROM.write(EEPCOUNTUPPER, CountUpper);
  }
  check = EEPROM.read(EEPCOUNTLOWER);
  if(check != CountLower)
  {
    EEPROM.write(EEPCOUNTLOWER, CountLower);
  }
  check = EEPROM.read(EEPBARREL);
  if(check != Barrel)
  {
    EEPROM.write(EEPBARREL, Barrel);
  }  
  check = EEPROM.read(EEPROUNDS);
  if(check != Rounds)
  {
    EEPROM.write(EEPROUNDS, Rounds);
  }
}






In Topic: [WIP] Stampede ACB, the Arduino-powered Stampede ECS

11 August 2013 - 04:46 PM

Completing the safety circuit as a means of firing the stampede is going to be unreliable. If the power to the motor is cut before the firing cycle is complete, you could have a dart in the breech, and one at the top of the magazine, which the breech attempts to chamber when you resume firing.
I did this once when installing a stampede in briefcase, and if you didnt fire the entire magazine, you had serious problems with jamming due to the firing cycle being partially completed.

The best way remotely(via arduino) actuate a stampede is to use the circuit that hasbro designed that mechanically ensures that the firing cycle is complete. Inside the stampede, there is a SPDT(single Pull Double Throw) switch that is pressed down by a lever and then held in that position by a part of the breech until the breech returns to the full rear position.

The two throws of this switch go to the motor and the firing circuit, while the center connects to the other lead of the motor. WHen this switch is in the 'off' position, it will form a closed loop with both leads of the motor, devoid of power, effectively providing a brake to the motor.
In the other position, this switch will complete a circuit with the motor and power source, making the motor spin and firing the stampede.

When I used a relay to drive a stampede, I kept this switch in the control circuit, upstream(logically) of the relay itself. THe relay directly powered the motor, and this switch actuated the relay. However, in order to know when the firing cycle was actually started, I had to wire a pin of the arduino to a part of this circuit downstream of this switch. The arduino then turn on power to the switch, which would in turn power the motor, wait a little bit(10ms or so) then turn off power and see if the switch was being held on. This would indicate that the breech had moved forward enough to engage the switch mechanically, and that kept the relay powered until the firing cycle was completed.

This ensured that the stampede fired a round precisely and reliably.

I'll post code once i go get some pictures of how i did this.

Edit:
Pictures of the switch im talking about above.
Posted Image
Posted Image

I was unsatisfied with the final product when i finished this project in march, and it has since been scrapped for parts(the arduino, LCD display, AR15 Grip), or I would provide a firing video.
It was a while ago that i designed this, but if memory serves, I wired the top (right as pictured) pole of that switch to the arduino, the middle goes to the relay control pin, and the lower pole to ground. My relay board was low active, so during the firing cycle, when the breech forces the middle and lower pins to connect(second picture), the relay is held active, regardless of the arduino's input. The line going to the relay also goes to another pin on the arduino, as the sensor for when the firing cycle is complete, but this is not strictly neccessary for your application.