SparkFun Forums 

Where electronics enthusiasts find answers.

Have questions about a SparkFun product or board? This is the place to be.
By Mee_n_Mac
#142169
StaticDet5 wrote:OK, so now the hard part.

We've been able to examine graphs and figure out where the peaks are. How do I write the computer code to figure that part out, while still keeping it speedy? It looks like we should be able to reliably identify the gearbox cycles. Hell, we can eyeball it ourselves, even with the moving baseline during the initial two shots.

Did you folks already work this out? I ask, because this is stuff I haven't done before.
This is the tricky part. And the reason I was recommending the BB detector as the means to do it. What I've done in Excel was implement a crude negative slope detector. It looks for the slope to be more negative than 1 ADC count/msec and do so for at least 4 consecutive msecs (a steep enough slope for long enough was the general idea). When that happens it declares the gearbox to be "offspring" (see my prior plots). The slope was computed from the 8 point moving average of the raw ADC readings. So pseudo-code would be something like this ...

- Compute 8 point moving average of the raw ADC readings
- Compute the slope of the average from the last 2 available averages
- If that slope is more negative than a threshold (= -1 ATM) then increment a counter but cap that counter to N max (= 9 ATM)
- When the slope is less negative than the threshold decrement the counter but cap that at 0 min
- If the counter >= threshold2 (= 4 ATM) then declare the gearbox to be "offsrping"

The above works fairly well with the M16 but produced false "detections" and missed some cycles with the other AEGs. Also the inrush current can vary in it's nature quite a bit and trip up the above. I limit the above to only work when time since trigger > time threshold, presently 100 msecs. I found that slight differences in the aforementioned thresholds and limits made differences in the "offspring" detections, sometimes out of all proportion to what I'd thought the changes would do. In other words it's tricky and too sensitive (IMO) to be ready for prime time. For the M16 it works OK, for the others ... not so much.

I choose 8 for the moving average to be "speedy" on an MCU. You can subtract out the last of the 8 ADC readings, add in the new ADC reading to make a moving sum and then right shift that by 3 to get a divide by 8. But this is silly now that I think about it. What you should code for is a moving sum, compute the slopes from the moving sum (not the average), and scale the new slope threshold to be 8 times greater than my slope threshold. An average is just an extra (and unneeded) divide. I may try that in Excel just to be sure.

I also tried a linear "best fit" estimation to get the slope, using 3 and 5 points (not something I'd want to do in an MCU), and that helped with the other AEGs as they were a lot noisier than the present M16. The 2 point slope did NOT work reliably with them, it seems to with the M16.

My guess is that the above will work for the times when the motor has been running for more than 500 msecs (past the inrush period) fairly well. The M16 is very repeatable past 500 msec and not that noisy (in the raw current readings). For other AEGs it was semi-working but required so much fine tuning I thought it not workable in real life. Having the slope detector try to detect the 1'st cycle or the first 3 or 5 (burst mode) is iffy. But I didn'tspend a lot of time trying to improve it after you went to the BB detector. Maybe I can re-look at it and see if the mental break time will result in some new and better ideas. The good thing is we have a wealth of data we can use as input to any algorithm. We can see if it works or not with Excel (or whatever) as a simulation tool and be able to compare those outputs to the actual BB detections to know the truth. No need to code up stuff for the Arduino and 10 people can be running 10 different sims to see what works.
Last edited by Mee_n_Mac on Sat Mar 31, 2012 11:33 am, edited 3 times in total.
By Mee_n_Mac
#142170
I think I've posted the 1'st plot before but just to make an easy comparison ...
The first plot is a manually time aligned set of inrush currents from the M16, taken from the Basic Interrupt data sets. The second is similar from the latest M16 data sets (new cabling). The thing to note is the differences and similarites in general nature of the inrush current.
Slide1.JPG
Note the negative slope early on before the 1'st cycle ...
Slide2.JPG
You do not have the required permissions to view the files attached to this post.
By Mee_n_Mac
#142171
And just to show what the last state of the slope detector is ...

Using the 3'rd set of the recent data (New Cabling set 3) I ran the slope detector described above and compared it with the "known good" BB detections for the 1'st 500 msec. Note the slope detector sometimes does an on/off/on/off oscillation and it's timing relative to the shot detection. I've plotted the slope of the smoothed running average as well. Note how noisy it becomes compared to the current (ADC) readings. Taking the slope is differentiation and that process always enhances any noise (whereas integration reduces noise).
CompareM16DetectionNCset3.jpg
(BTW the keen reader should note something odd in the above. Extra points if you can tell me how it happened)
You do not have the required permissions to view the files attached to this post.
By StaticDet5
#142185
My apologies, on two counts.
First, I got dragged out to see "The Hunger Games"... If you can, go out and see it. It's a good movie, and the first preview is pretty incredible.

Second, I'm going to flub the math problem. It looks like the BB detections coincide with negative slopes except at the 350msec detection. I'm guessing that the noise was sufficient in nature to trigger the negative slope detector.

This looks to be a pattern that we can easily see. It's a pattern that is based off of numbers. There should be a way to get this thing to detect it the way we can detect it. However, I'll be the first to admit that just the moving averages is going to slow me down.

I have some reading to do...
By StaticDet5
#142197
...annnnd still reading.

I'm looking at the graphs right now (I'm having to relearn graphing. There were significant changes since Office 2001 or whatever it is I used to use).

I think we're pretty fortunate that these four tests show some different data trends, particularly during the inrush cycle. I'm looking for a good rule to describe my mental amblings in identifying when the gearbox has cycled.

You're right. We're probably going to need to institute a "learning behavior" in the system, unless Dan's idea of a PWM ramp-up works.

OK, I'm getting my head around moving averages. I'm talking out loud here, because I'm very weak in this region (Damnit Jim, I'm a doctor not an engineer...)
The more points we use in computing the moving average, the less effect that noise has on the examination. Too much averaging and we lose resolution of the very peaks and valleys we're looking for, AND it takes up more computational time per cycle, right? I'm looking at a 10-point moving average graph, and it's beautiful, curves in all the right places, no harsh lines (Hey! This is a record for me, over 150 posts without any innuendo!).

The issue is the inrush current AND the fact that the gearbox may not always stop in the same place AND the fact that the battery energy is going to change. These last two elements are the main reasons I don't want to use a "timing system" to accomplish the gearbox parking. That won't look at the correct parameters to determine when to park the gearbox.

However, we talked about "refractory periods", times when we just don't care what is going on. I think that may be the way to deal with the inrush current. It looks like if we ignore the first 75 samples during the sampling current, we eliminate a fair chunk of the inrush wonkiness. In fact, it really looks like we can't pull a shot off before 120msec.
This may be a part of gun-tuning. We have the "In-Rush Delay" documented and alterable. If the gun fires two rounds on single shot, decrease the IRD (If the IRD is too long, the system will miss the first drop in amperage, and trigger on the second drop).

With that, it looks like a moving average utilizing 10 points will do it. Is that too computationally intensive. even if that is the only thing it is doing? At the "summation" of the shot, we can do all the other processing and checking (look for the BB exit and speed, send serial information to the display if available, check the trigger and decide to continue firing).

I'll be back on later tonight. Repair guy is here.
By Mee_n_Mac
#142200
Musing out loud as well ...

If you were to look at the current readings out past 500 msec you'd see a very repeatable set of peaks and troughs. It would be fairly easy to have code learn the max and min values and set a threshold (say ... halfway between) such that whenever the ADC read below that threshold, the gearbox would be declared "offspring". Alas that threshold wouldn't work for the first 5 or so cycles due to the inrush current. Perhaps some threshold, that changes vs time and eventually decays into the "post 500 msec" threshold, might be possible. I stopped looking at that but ...

When you look at the current readings what is it that stands out ? Even with the inrush you see peaks and troughs and what the slope detector does is try to find a slope that's steep enough for long enough. This way you don't have to worry about the absolute values of the current, but only the relative changes in the current. Right now that seems to work ... for this AEG ... because it's so well behaved and repeatable and noise-free. The earlier test AEGs were not this way. I don't know what gun you're planning to alter into your smart AEG so I can't predict how well the slope algorithm will work on that AEG. So far it's been mixed results of good and bad.

As for a refractory period ... yup ... I would wait for at least 100 msec post trigger to use any results from any method. There's just no way a shot gets off sooner than that. And on that note ...

Some posts ago I was mentioning a 45 msec min period between BB detections from the photo-interrupter. The idea is that once you have a "known good" detection, there's no way another shot happens sooner than that. Anything sooner is a false detection. Of course it's always possible that a false will sneak in after this refractory period and be declared a good detection but as I said back then "what's the worst that could happen?" Well that's what happened in set#3 above. A false detection happened 46 msec after the 3'rd shot and fooled the system into declaring it to be good and the real BB detection, that happened another 13 msec later to be "false". What you can see in the plot above was that the BB was "detected" just before the spring had been released. Oh well ... you can't expect a perfect system. A 50 msec refractory period would have prevented this and resulted in the following plot (for comparison). See if you can see the difference in the timing of the BB detection (green plot) vs the slope of the current or when the slope algorithm declared the GB to be "offspring" (red plot).
M16NewCableSet3SlopeDetect.jpg
M16NewCableLog3.jpg
You do not have the required permissions to view the files attached to this post.
By StaticDet5
#142201
Yeah. I really think BB detection (versus gearbox cycling detection) needs to be incident to gearbox cycling detection and irrelevant to determining the cycling of the gearbox. I may have to look for local events to go to, just to get more information on gearbox activities. Right now, I think I can just drop everything into a gun, without any real modification.
By StaticDet5
#142203
OK, I'm playing with Excel. I made a sheet with the last four sets of data. I'm now trying to come up with a simple system to predict when the gearbox has cycled.

With Column A being the ADC readouts from the amperage and column B being the hits on the photo-interrupter, I set up column D with the following equation:

=AVERAGE(A11:A19)-AVERAGE(A3:A11)

I then dragged this formula down to the end of column D (D1515).

I then conditionally formatted the D column to light up every time the resulting number is less than -7. In only two cases would this analysis result in a parking of the gearbox AFTER the shot had passed the sensor. In both of those cases, the gearbox would have parked within two msecs. In every case, this examination indicated the end of the gearbox cycle, including the inrush period.

Now, we're looking at almost 20 datapoints. This may be too computationally intensive. I'm going to continue playing with the data to see if I can get good predictors with less data.
By StaticDet5
#142204
Dataset three is KILLING ME!

I can get pretty good predictors with 11 datapoints. Pretty much 100%. I'm trying to get it down to 8 datapoints, and I'm getting bad results.

When I say "Bad", I mean "Stop the gearbox during the middle of the compression cycle, bad".

Whew. This is an uphill battle. I think I'm going to have to try programming the examination using 11 datapoints. It's making nice, crisp datablocks, with very little noise and zero false identification of gearbox cycles.
By StaticDet5
#142206
I just started looking at arrays, and I realized I haven't used arrays yet in Arduino-code. Python, yes, but there are some huge differences here. I'm really going to have to make this code as lean as possible.

I need to go take a break. I'm starting to get crispy around the edges. I think I've got this down to eight datapoints:
=AVERAGE(A7:A10)-AVERAGE(A3:A6)
with a threshold of -4.7
By StaticDet5
#142237
New code, included here to be complete. I'm going to put a function snippet in a bit, with the really relevant parts.
Code: Select all
/*
    GearboxParkingUsingADC
    4-1-2012
    previous working code BasicAirsoftBarrelSensor

    The circuit:
    * LED on board at pin 13
    * Motor control on pin 3 (PWM pin)
    * pushbutton attached to pin 7 from +5V
    * 10K resistor attached to pin 7 from ground
    * ADC on analog pin 0
    * The photo-interrupter on pin 2

    Serial settings:
    Bits per second 115200

    */

#include <TimerOne.h>

const int TriggerPin = 7; //Trigger switch attached to digital pin 7
const int ledPin = 13; //onboard LED on pin 13
const int MotorPin = 3; //Motor drive pin (on MOSFET) connected to PWM pin at pin 3
const int CurrentSensor = 0; //Current flow sensor attached to Analog 0
const int MotorSpeed = 255; //PWM value for motor, max 255
volatile int BBdetect = 0; //BB seen or not
const int SelectorPin = 8;  //Selector Pin
const float CurveThresh = -4.7;  //negative slope detection threshold
const int IRD = 100;  //In Rush Delay in milliseconds to deal with in-rush motor startup current
const int Debounce = 50;  //Standard Debounce delay

int PowerCurve[7];  //initialize the power curve array size 8
float CurvePoint;  //CurvePoint will be compared to CurveThresh to determine if the spring is in free-fall
int fireTime = 0;
int TriggerState = 0; // Trigger button state
volatile int IMain = 0; // Direct ADC reading from current sensor
long ReadAmpsInterval = 1000; // This will provide 10 readings per AEG cycle

void setup()
{
  Serial.begin(115200);  //High output serial for testing
  pinMode(MotorPin, OUTPUT);
  analogWrite(MotorPin, 0);   //make sure the motorpin is off
  pinMode(ledPin, OUTPUT);
  pinMode(TriggerPin, INPUT);
  digitalWrite(2, HIGH);  //Turn on the internal pull-up on digital 2
  
  Timer1.initialize(ReadAmpsInterval);  //set the interrupt timer to read at this interval
  attachInterrupt(0, ReadBB, FALLING);  //The BB ISR
}

void loop()
{
  //code pending
}


void SINGLESHOT()
{
  analogWrite(MotorPin, MotorSpeed);  //turn on Motor at MotorSpeed
  digitalWrite(ledPin,HIGH);  //turn on indicator LED
  delay(IRD);  //Allow the motor to run for IRD milliseconds before examining current
  for (int i = 0; i < 7; i++)
  {
    IMain = analogRead(CurrentSensor);
    PowerCurve[i] = IMain;
  }
  do
  {
    PowerCurve[0] = PowerCurve[1];
    PowerCurve[1] = PowerCurve[2];
    PowerCurve[2] = PowerCurve[3];
    PowerCurve[3] = PowerCurve[4];
    PowerCurve[4] = PowerCurve[5];
    PowerCurve[5] = PowerCurve[6];
    PowerCurve[6] = PowerCurve[7];
    IMain = analogRead(CurrentSensor);
    PowerCurve[7] = IMain;
    CurvePoint = ((float)(PowerCurve[4]+PowerCurve[5]+PowerCurve[6]+PowerCurve[7])/4)-((float)(PowerCurve[0]+PowerCurve[1]+PowerCurve[2]+PowerCurve[3]+PowerCurve[4])/4);
  }  while (CurvePoint > CurveThresh)
  analogWrite(MotorPin, 0);  // Turn off Motor after a single shot
  digitalWrite(ledPin, HIGH);
  do
  {
    delay(1);
    TriggerState = digitalRead(TriggerPin);  //trigger latch
  }  while (TriggerState == HIGH);   //the program will not continue until the trigger is released
  delay(debounce);  //this debounce is critical.  The SINGLESHOT function exits here.  If the trigger is still bouncing, it will trigger another shot
}
This isn't tested code (in fact, there's no populated "Loop"). I wanted to get a SingleShot fire control procedure down.

Here's just the SINGLESHOT function:
Code: Select all
void SINGLESHOT()
{
  analogWrite(MotorPin, MotorSpeed);  //turn on Motor at MotorSpeed
  digitalWrite(ledPin,HIGH);  //turn on indicator LED
  delay(IRD);  //Allow the motor to run for IRD milliseconds before examining current
  for (int i = 0; i < 7; i++)
  {
    IMain = analogRead(CurrentSensor);
    PowerCurve[i] = IMain;
  }
  do
  {
    PowerCurve[0] = PowerCurve[1];
    PowerCurve[1] = PowerCurve[2];
    PowerCurve[2] = PowerCurve[3];
    PowerCurve[3] = PowerCurve[4];
    PowerCurve[4] = PowerCurve[5];
    PowerCurve[5] = PowerCurve[6];
    PowerCurve[6] = PowerCurve[7];
    IMain = analogRead(CurrentSensor);
    PowerCurve[7] = IMain;
    CurvePoint = ((float)(PowerCurve[4]+PowerCurve[5]+PowerCurve[6]+PowerCurve[7])/4)-((float)(PowerCurve[0]+PowerCurve[1]+PowerCurve[2]+PowerCurve[3]+PowerCurve[4])/4);
  }  while (CurvePoint > CurveThresh)
  analogWrite(MotorPin, 0);  // Turn off Motor after a single shot
  digitalWrite(ledPin, HIGH);
  do
  {
    delay(1);
    TriggerState = digitalRead(TriggerPin);  //trigger latch
  }  while (TriggerState == HIGH);   //the program will not continue until the trigger is released
  delay(debounce);  //this debounce is critical.  The SINGLESHOT function exits here.  If the trigger is still bouncing, it will trigger another shot
}
The only thing that needs in depth explanation is the PowerCurve Array, CurvePoint, and CurveThresh.
The PowerCurve Array stores the last 8 amperage ADC readings. Every time it loops, the function shuffles the array down one step and adds the latest reading to the [7] point in the array (Eighth reading, because it is zero accessed).
CurvePoint takes the average of the first four readings (0:3) and subtracts it from the last four readings (4:7). There may be some confusion there as the array position is contrary to the temporal position. The first position in the array(position 0) is the OLDEST reading, while the last position in the array(position 7) is the NEWEST reading. When CurvePoint starts a negative trend, we're looking at a negative slope on a graph of amperage readings. We reduce the noise impact by averaging, and comparing two averaged points.
Once CurvePoint is less than CurveThresh (Curve Threshold), we're statistically sure that the gearbox has cycled and is in a relaxed state. In rare cases the round will have already left the barrel (according to the previous test data), but this has always been within 1-2 msecs.

I haven't tested the code yet. It's going to be another busy day so I may not get back to it today. However, feel free to comment or point out pitfalls that I'm stumbling towards.
By StaticDet5
#142285
Code: Select all
/*
    GearboxParkingUsingADC
    4-3-2012
    previous working code BasicAirsoftBarrelSensor

    The circuit:
    * LED on board at pin 13
    * Motor control on pin 3 (PWM pin)
    * pushbutton attached to pin 7 from +5V
    * 10K resistor attached to pin 7 from ground
    * ADC on analog pin 0
    * The photo-interrupter on pin 2

    Serial settings:
    Bits per second 115200

    */

//#include <TimerOne.h>

const int TriggerPin = 7; //Trigger switch attached to digital pin 7
const int ledPin = 13; //onboard LED on pin 13
const int MotorPin = 3; //Motor drive pin (on MOSFET) connected to PWM pin at pin 3
const int CurrentSensor = 0; //Current flow sensor attached to Analog 0
const int MotorSpeed = 255; //PWM value for motor, max 255
volatile int BBdetect = 0; //BB seen or not
const int SelectorPin = 8;  //Selector Pin
const float CurveThresh = -4;  //negative slope detection threshold
const int IRD = 110;  //In Rush Delay in milliseconds to deal with in-rush motor startup current
const int Debounce = 50;  //Standard Debounce delay

int PowerCurve[7];  //initialize the power curve array size 8
float CurvePoint;  //CurvePoint will be compared to CurveThresh to determine if the spring is in free-fall
int fireTime = 0;
int TriggerState = 0; // Trigger button state
volatile int IMain = 0; // Direct ADC reading from current sensor
long ReadAmpsInterval = 1000; // This will provide 10 readings per AEG cycle

void setup()
{
  Serial.begin(115200);  //High output serial for testing
  pinMode(MotorPin, OUTPUT);
  analogWrite(MotorPin, 0);   //make sure the motorpin is off
  pinMode(ledPin, OUTPUT);
  pinMode(TriggerPin, INPUT);
  digitalWrite(2, HIGH);  //Turn on the internal pull-up on digital 2
  
  //Timer1.initialize(ReadAmpsInterval);  //set the interrupt timer to read at this interval
  //attachInterrupt(0, ReadBB, FALLING);  //The BB ISR
}

void loop()
{
  TriggerState = digitalRead(TriggerPin);
  if (TriggerState == HIGH)
  {
    SINGLESHOT();
  }
  else
  {
    analogWrite(MotorPin, 0);
  }
}


void SINGLESHOT()
{
  int PowerCurve[7];  //Initialize the PowerCurve Array
  analogWrite(MotorPin, MotorSpeed);  //turn on Motor at MotorSpeed
  digitalWrite(ledPin,HIGH);  //turn on indicator LED
  delay(IRD);  //Allow the motor to run for IRD milliseconds before examining current
  for (int i = 0; i < 7; i++)  //Get 7 of the 8 PowerCurve readings
  {
    IMain = analogRead(CurrentSensor);
    PowerCurve[i] = IMain;
  }
  do                          //shuffle the array readings down one step and add a new reading at the top of the array stack
  {
    PowerCurve[0] = PowerCurve[1];
    PowerCurve[1] = PowerCurve[2];
    PowerCurve[2] = PowerCurve[3];
    PowerCurve[3] = PowerCurve[4];
    PowerCurve[4] = PowerCurve[5];
    PowerCurve[5] = PowerCurve[6];
    PowerCurve[6] = PowerCurve[7];
    IMain = analogRead(CurrentSensor);
    PowerCurve[7] = IMain;
    CurvePoint = ((float)(PowerCurve[4]+PowerCurve[5]+PowerCurve[6]+PowerCurve[7])/4)-((float)(PowerCurve[0]+PowerCurve[1]+PowerCurve[2]+PowerCurve[3]+PowerCurve[4])/4);
  }  while (CurvePoint > CurveThresh);   //keep running until the CurvePoint is below the Curve Threshold
  analogWrite(MotorPin, 0);  // Turn off Motor after a single shot
  digitalWrite(ledPin, HIGH);
  do
  {
    delay(1);
    TriggerState = digitalRead(TriggerPin);  //trigger latch
  }  while (TriggerState == HIGH);   //the program will not continue until the trigger is released
  delay(Debounce);  //this debounce is critical.  The SINGLESHOT function exits here.  If the trigger is still bouncing, it will trigger another shot
}

  
  
Alright!!!
I'm getting good, consistent singleshots with this code.
An added benefit of this system is that the system sounds very crisp. In other AEG's, you still hear some gearbox noise after the round has fired off. All of that noise is buried in the noise of the round leaving and the cylinder slamming forward.

I'm going to work on adding additional fire mode code, selector and indicator code (I'd like an LED to blink while this thing is on the test bench).

Right now, this is performing beautifully!
By StaticDet5
#142287
Code: Select all
/*
    GearboxParkingUsingADC
    4-3-2012
    previous working code BasicAirsoftBarrelSensor

    The circuit:
    * LED on board at pin 13
    * Motor control on pin 3 (PWM pin)
    * pushbutton attached to pin 7 from +5V
    * 10K resistor attached to pin 7 from ground
    * ADC on analog pin 0
    * The photo-interrupter on pin 2

    Serial settings:
    Bits per second 115200

    */

//#include <TimerOne.h>

const int TriggerPin = 7; //Trigger switch attached to digital pin 7
const int ledPin = 13; //onboard LED on pin 13
const int MotorPin = 3; //Motor drive pin (on MOSFET) connected to PWM pin at pin 3
const int CurrentSensor = 0; //Current flow sensor attached to Analog 0
const int MotorSpeed = 255; //PWM value for motor, max 255
volatile int BBdetect = 0; //BB seen or not
const int SelectorPin = 8;  //Selector Pin
const float CurveThresh = -4;  //negative slope detection threshold
const int IRD = 110;  //In Rush Delay in milliseconds to deal with in-rush motor startup current
const int Debounce = 50;  //Standard Debounce delay
const float SpringSense = 4; //spring compression slope detection threshold

int PowerCurve[7];  //initialize the power curve array size 8
float CurvePoint;  //CurvePoint will be compared to CurveThresh to determine if the spring is in free-fall
int fireTime = 0;
int TriggerState = 0; // Trigger button state
int FireMode = 0; //Firing mode of the gearbox.  0 is always safe, 1 is always singleshot
volatile int IMain = 0; // Direct ADC reading from current sensor
long ReadAmpsInterval = 1000; // This will provide 10 readings per AEG cycle
int BurstSetting = 3;  //The number of rounds in a burst

void setup()
{
  Serial.begin(115200);  //High output serial for testing
  pinMode(MotorPin, OUTPUT);
  analogWrite(MotorPin, 0);   //make sure the motorpin is off
  pinMode(ledPin, OUTPUT);
  pinMode(TriggerPin, INPUT);
  digitalWrite(2, HIGH);  //Turn on the internal pull-up on digital 2
  
  //Timer1.initialize(ReadAmpsInterval);  //set the interrupt timer to read at this interval
  //attachInterrupt(0, ReadBB, FALLING);  //The BB ISR
}

void loop()
{
  TriggerState = digitalRead(TriggerPin);
  if (TriggerState == HIGH)
  {
    //SINGLESHOT();
    BURST(BurstSetting);
  }
  else
  {
    analogWrite(MotorPin, 0);
  }
}


void SINGLESHOT()
{
  int PowerCurve[7];  //Initialize the PowerCurve Array
  analogWrite(MotorPin, MotorSpeed);  //turn on Motor at MotorSpeed
  digitalWrite(ledPin,HIGH);  //turn on indicator LED
  delay(IRD);  //Allow the motor to run for IRD milliseconds before examining current
  for (int i = 0; i < 7; i++)  //Get 7 of the 8 PowerCurve readings
  {
    IMain = analogRead(CurrentSensor);
    PowerCurve[i] = IMain;
  }
  do                          //shuffle the array readings down one step and add a new reading at the top of the array stack
  {
    PowerCurve[0] = PowerCurve[1];
    PowerCurve[1] = PowerCurve[2];
    PowerCurve[2] = PowerCurve[3];
    PowerCurve[3] = PowerCurve[4];
    PowerCurve[4] = PowerCurve[5];
    PowerCurve[5] = PowerCurve[6];
    PowerCurve[6] = PowerCurve[7];
    IMain = analogRead(CurrentSensor);
    PowerCurve[7] = IMain;
    CurvePoint = ((float)(PowerCurve[4]+PowerCurve[5]+PowerCurve[6]+PowerCurve[7])/4)-((float)(PowerCurve[0]+PowerCurve[1]+PowerCurve[2]+PowerCurve[3]+PowerCurve[4])/4);
  }  while (CurvePoint > CurveThresh);   //keep running until the CurvePoint is below the Curve Threshold
  analogWrite(MotorPin, 0);  // Turn off Motor after a single shot
  digitalWrite(ledPin, HIGH);
  do
  {
    delay(1);
    TriggerState = digitalRead(TriggerPin);  //trigger latch
  }  while (TriggerState == HIGH);   //the program will not continue until the trigger is released
  delay(Debounce);  //this debounce is critical.  The SINGLESHOT function exits here.  If the trigger is still bouncing, it will trigger another shot
}

void BURST(int BurstCount)
{
  int ShotNum = 0;
  int PowerCurve[7];  //Initialize the PowerCurve Array
  analogWrite(MotorPin, MotorSpeed);  //turn on Motor at MotorSpeed
  digitalWrite(ledPin,HIGH);  //turn on indicator LED
  delay(IRD);  //Allow the motor to run for IRD milliseconds before examining current
  for (int i = 0; i < 7; i++)  //Get 7 of the 8 PowerCurve readings
  {
    IMain = analogRead(CurrentSensor);
    PowerCurve[i] = IMain;
  }
  do
  {
    if (ShotNum > 1)  //This does not run during the first spring compression cycle!
    {
      do
      {
        PowerCurve[0] = PowerCurve[1];
        PowerCurve[1] = PowerCurve[2];
        PowerCurve[2] = PowerCurve[3];
        PowerCurve[3] = PowerCurve[4];
        PowerCurve[4] = PowerCurve[5];
        PowerCurve[5] = PowerCurve[6];
        PowerCurve[6] = PowerCurve[7];
        IMain = analogRead(CurrentSensor);
        PowerCurve[7] = IMain;
        CurvePoint = ((float)(PowerCurve[4]+PowerCurve[5]+PowerCurve[6]+PowerCurve[7])/4)-((float)(PowerCurve[0]+PowerCurve[1]+PowerCurve[2]+PowerCurve[3]+PowerCurve[4])/4);
      }  while (CurvePoint < SpringSense);  //Sense the start of the spring compression cycle
    }
    do
    {
      PowerCurve[0] = PowerCurve[1];
      PowerCurve[1] = PowerCurve[2];
      PowerCurve[2] = PowerCurve[3];
      PowerCurve[3] = PowerCurve[4];
      PowerCurve[4] = PowerCurve[5];
      PowerCurve[5] = PowerCurve[6];
      PowerCurve[6] = PowerCurve[7];
      IMain = analogRead(CurrentSensor);
      PowerCurve[7] = IMain;
      CurvePoint = ((float)(PowerCurve[4]+PowerCurve[5]+PowerCurve[6]+PowerCurve[7])/4)-((float)(PowerCurve[0]+PowerCurve[1]+PowerCurve[2]+PowerCurve[3]+PowerCurve[4])/4);
    }  while (CurvePoint > CurveThresh);   //keep running until the CurvePoint is below the Curve Threshold
    ShotNum = ShotNum + 1;
  } while (ShotNum < BurstCount);
  do
  {
    delay(1);
    TriggerState = digitalRead(TriggerPin);  //trigger latch
  }  while (TriggerState == HIGH);   //the program will not continue until the trigger is released
  delay(Debounce);  //this debounce is critical.  The SINGLESHOT function exits here.  If the trigger is still bouncing, it will trigger another shot
}

    
  
Alright, the BURST code isn't working yet. It's locking on a FA fire rate. I need to troubleshoot it later. Right now I need to run. I got a family member deploying, and we need to go party.
By StaticDet5
#142302
The code needs some tweaking, but the basic fire control coding is done. Safe, Semi, Burst, Full is all working, though there are some occasional "extra-fires". I'll post some code in the morning.
By StaticDet5
#142335
Code: Select all
/*
    GearboxParkingUsingADC
    4-3-2012
    previous working code BasicAirsoftBarrelSensor

    The circuit:
    * LED on board at pin 13
    * Motor control on pin 3 (PWM pin)
    * pushbutton attached to pin 7 from +5V
    * 10K resistor attached to pin 7 from ground
    * ADC on analog pin 0
    * The photo-interrupter on pin 2

    Serial settings:
    Bits per second 115200

    */

//#include <TimerOne.h>

const int TriggerPin = 7; //Trigger switch attached to digital pin 7
const int ledPin = 13; //onboard LED on pin 13
const int MotorPin = 3; //Motor drive pin (on MOSFET) connected to PWM pin at pin 3
const int CurrentSensor = 0; //Current flow sensor attached to Analog 0
const int MotorSpeed = 255; //PWM value for motor, max 255
volatile int BBdetect = 0; //BB seen or not
const int SelectorPin = 8;  //Selector Pin
const float CurveThresh = -4;  //negative slope detection threshold
const int IRD = 110;  //In Rush Delay in milliseconds to deal with in-rush motor startup current
const int Debounce = 50;  //Standard Debounce delay
const float SpringSense = 4; //spring compression slope detection threshold

int PowerCurve[7];  //initialize the power curve array size 8
float CurvePoint;  //CurvePoint will be compared to CurveThresh to determine if the spring is in free-fall
int fireTime = 0;
int TriggerState = 0; // Trigger button state
int FireMode = 0; //Firing mode of the gearbox.  0 is always safe, 1 is always singleshot
volatile int IMain = 0; // Direct ADC reading from current sensor
long ReadAmpsInterval = 1000; // This will provide 10 readings per AEG cycle
int BurstSetting = 3;  //The number of rounds in a burst

void setup()
{
  Serial.begin(115200);  //High output serial for testing
  pinMode(MotorPin, OUTPUT);
  analogWrite(MotorPin, 0);   //make sure the motorpin is off
  pinMode(ledPin, OUTPUT);
  pinMode(TriggerPin, INPUT);
  digitalWrite(2, HIGH);  //Turn on the internal pull-up on digital 2
  FireMode = 3;
  
  //Timer1.initialize(ReadAmpsInterval);  //set the interrupt timer to read at this interval
  //attachInterrupt(0, ReadBB, FALLING);  //The BB ISR
}

void loop()
{
  TriggerState = digitalRead(TriggerPin);
  if (TriggerState == HIGH)
  {
    if (FireMode == 0)
    {
      digitalWrite(ledPin, HIGH);
      delay(100);
      digitalWrite(ledPin, LOW);
      delay(100);
    }
    else if (FireMode == 1)
    {
      SINGLESHOT();
    }
    else if (FireMode == 2)
    {
      BURST(BurstSetting);
    }
    else if (FireMode == 3)
    {
      FULLAUTO();
    }
  }
  else
  {
    analogWrite(MotorPin, 0);
  }
}


void SINGLESHOT()
{
  int PowerCurve[7];  //Initialize the PowerCurve Array
  analogWrite(MotorPin, MotorSpeed);  //turn on Motor at MotorSpeed
  digitalWrite(ledPin,HIGH);  //turn on indicator LED
  delay(IRD);  //Allow the motor to run for IRD milliseconds before examining current
  for (int i = 0; i < 7; i++)  //Get 7 of the 8 PowerCurve readings
  {
    IMain = analogRead(CurrentSensor);
    PowerCurve[i] = IMain;
  }
  do                          //shuffle the array readings down one step and add a new reading at the top of the array stack
  {
    PowerCurve[0] = PowerCurve[1];
    PowerCurve[1] = PowerCurve[2];
    PowerCurve[2] = PowerCurve[3];
    PowerCurve[3] = PowerCurve[4];
    PowerCurve[4] = PowerCurve[5];
    PowerCurve[5] = PowerCurve[6];
    PowerCurve[6] = PowerCurve[7];
    IMain = analogRead(CurrentSensor);
    PowerCurve[7] = IMain;
    CurvePoint = ((float)(PowerCurve[4]+PowerCurve[5]+PowerCurve[6]+PowerCurve[7])/4)-((float)(PowerCurve[0]+PowerCurve[1]+PowerCurve[2]+PowerCurve[3]+PowerCurve[4])/4);
  }  while (CurvePoint > CurveThresh);   //keep running until the CurvePoint is below the Curve Threshold
  analogWrite(MotorPin, 0);  // Turn off Motor after a single shot
  digitalWrite(ledPin, HIGH);
  do
  {
    delay(1);
    TriggerState = digitalRead(TriggerPin);  //trigger latch
  }  while (TriggerState == HIGH);   //the program will not continue until the trigger is released
  delay(Debounce);  //this debounce is critical.  The SINGLESHOT function exits here.  If the trigger is still bouncing, it will trigger another shot
}

void BURST(int BurstCount)
{
  int PowerCurve[7];  //Initialize the PowerCurve Array
  analogWrite(MotorPin, MotorSpeed);  //turn on Motor at MotorSpeed
  digitalWrite(ledPin,HIGH);  //turn on indicator LED
  delay(IRD);  //Allow the motor to run for IRD milliseconds before examining current
  for (int i = 0; i < 7; i++)  //Get 7 of the 8 PowerCurve readings
  {
    IMain = analogRead(CurrentSensor);
    PowerCurve[i] = IMain;
  }
  for (int j = 0; j < BurstCount; j++)  //run the loop enough times to fire the burst
  {
    do                          //shuffle the array readings down one step and add a new reading at the top of the array stack
    {
      PowerCurve[0] = PowerCurve[1];
      PowerCurve[1] = PowerCurve[2];
      PowerCurve[2] = PowerCurve[3];
      PowerCurve[3] = PowerCurve[4];
      PowerCurve[4] = PowerCurve[5];
      PowerCurve[5] = PowerCurve[6];
      PowerCurve[6] = PowerCurve[7];
      IMain = analogRead(CurrentSensor);
      PowerCurve[7] = IMain;
      CurvePoint = ((float)(PowerCurve[4]+PowerCurve[5]+PowerCurve[6]+PowerCurve[7])/4)-((float)(PowerCurve[0]+PowerCurve[1]+PowerCurve[2]+PowerCurve[3]+PowerCurve[4])/4);
    }  while (CurvePoint > CurveThresh);   //keep running until the CurvePoint is below the Curve Threshold
    delay(60);
  }
  analogWrite(MotorPin, 0);  // Turn off Motor after a single shot
  digitalWrite(ledPin, HIGH);
  do
  {
    delay(1);
    TriggerState = digitalRead(TriggerPin);  //trigger latch
  }  while (TriggerState == HIGH);   //the program will not continue until the trigger is released
  delay(Debounce);  //this debounce is critical.  The SINGLESHOT function exits here.  If the trigger is still bouncing, it will trigger another shot
}

void FULLAUTO()
{
  int PowerCurve[7];  //Initialize the PowerCurve Array
  analogWrite(MotorPin, MotorSpeed);  //turn on Motor at MotorSpeed
  digitalWrite(ledPin,HIGH);  //turn on indicator LED
  delay(IRD);  //Allow the motor to run for IRD milliseconds before examining current
  for (int i = 0; i < 7; i++)  //Get 7 of the 8 PowerCurve readings
  {
    IMain = analogRead(CurrentSensor);
    PowerCurve[i] = IMain;
  }
  do
  {
    do                          //shuffle the array readings down one step and add a new reading at the top of the array stack
    {
      PowerCurve[0] = PowerCurve[1];
      PowerCurve[1] = PowerCurve[2];
      PowerCurve[2] = PowerCurve[3];
      PowerCurve[3] = PowerCurve[4];
      PowerCurve[4] = PowerCurve[5];
      PowerCurve[5] = PowerCurve[6];
      PowerCurve[6] = PowerCurve[7];
      IMain = analogRead(CurrentSensor);
      PowerCurve[7] = IMain;
      CurvePoint = ((float)(PowerCurve[4]+PowerCurve[5]+PowerCurve[6]+PowerCurve[7])/4)-((float)(PowerCurve[0]+PowerCurve[1]+PowerCurve[2]+PowerCurve[3]+PowerCurve[4])/4);
    }  while (CurvePoint > CurveThresh);   //keep running until the CurvePoint is below the Curve Threshold
    delay(60);
    TriggerState = digitalRead(TriggerPin);
  } while (TriggerState == HIGH);
  analogWrite(MotorPin, 0);  // Turn off Motor after a single shot
  digitalWrite(ledPin, HIGH);
  do
  {
    delay(1);
    TriggerState = digitalRead(TriggerPin);  //trigger latch
  }  while (TriggerState == HIGH);   //the program will not continue until the trigger is released
  delay(Debounce);  //this debounce is critical.  The SINGLESHOT function exits here.  If the trigger is still bouncing, it will trigger another shot
}
  
Here's the working code. As of right now, a couple of things need to be done. First, I need to program a method for changing the fire control mode while the code is running (right now you have to "toggle bits" and re-load the code). That's going to involve adding another button to the circuit (no big deal). Then I need to develop the "Data Returns" after each shot, reporting what has happened in the AEG while the trigger has been pressed. I want to be able to send the data over the USB port, as well as formatted for reporting over a software serial port for the eventual display.
I'm also going to need to figure out how to break out all of these pins on the Arduino Nano backpack, while still having room for the MOSFET (I may make the MOSFET a "remote" component, allowing it to be mounted somewhere on the AEG that can sink further heat).

I sat and played with the MOSFET a little, last night, trying to get it to fire without being commanded. I couldn't get it to happen. I think the original MOSFET breakout was damaged when I ran too much current through it. That's the only explanation I have for it. For now, I'm going to look at the thermal protection circuit, and interrupt the firing cycle if the temp gets too high. I think there is enough processor oomph to do that.
  • 1
  • 23
  • 24
  • 25
  • 26
  • 27
  • 32