High speed photography of smashing paintballs

Hi

I feel a little bit outdated in presenting my project, but it's my first major build on Arduino and constructed from sketch.

Searching for the topic 'high speed photography' reveals quite a lot of older posts and these refer mainly to some kind of triggering mechanism.

The difference in ths project can be mainly found in the way, how the timing of the interesting moment is realized.

In order to take images of different stages during the very moment, a paintball (shot from a gun) which hits the target and smashes the timing has to be exact to less than a 1/1000 sec (given a speed of about 85-100 m/sec).

I have build a solution around two spaced IR phoroelectric sensors that measure the speed just after the paintball has left the muzzle; in the next step the interval for igniting the photo-flash ist calculated (whith presets of different delays) an the photo can be taken just before the impact and at specific moments of collision.

This all comes qith a casing (which guarantees safety and shields ambient light), an automated trigger mechanism (the maximum capacity of the used revolver is 6 paintballs), an IR diode transferring the command for openening the camera's shutter well before the paintball is fired and two motors for turning a protective transparent screen between lens and target and the target itself.

A small control panel enables switching between programming and executing mode and changing the preset parameters (number of fired bulltes; distance to target; timing intervals for subsequent images relative to the calculated impact; relase button for every shot to be fired).

Any of your comments will be highly appreciated!

Georg

Images can be found here

And my sketch here

1 Like

Do you have questions?

If you want help here, post the code and images here. Read the forum guidelines to see how to properly post code and some good information on making a good post.
Use the IDE autoformat tool (ctrl-t or Tools, Auto format) before posting code in code tags.

Many members, myself included, will not download code. It is just too much hassle.

It's a showcase forum section

OOPS. I did not see that. OP, sorry, but you should still post the formatted code in code tags and post the images here.

I've put two links at the bottom; some photos of the project, some results and the code - unfortunaltely I#m not familiar in posting the code whithin my post (in tags). I'll have a try. cheers

OK, that's easy.

 // verwendete Bibliotheken
#include <Wire.h>;
#include <LCD.h>;
#include <LiquidCrystal_I2C.h>;
#include <EEPROM.h>;
#include <Servo.h>
#include <LedControl.h>
#include <IRremote.h>
#include <Stepper.h>

// definierte Konstanten
#define prell     2   // Prellzeit [ms]
#define laenge    190 // Abstand der Lichtschranken [mm]
#define anzeigezeit 3000 // 3 sekunden Standzeit der Anzeige

//Drehwähler
#define pin_clk   0   // Drehgeber CLK Violett
#define pin_dre   1   // Drehgeber DRE Grau
#define pin_but   2   // Zentralknopf SW Gelb

//Auslöser
#define pin_irs   9   // IR Auslösesendediode
#define als       0xB4B8F  // Auslösekommando

//Servo
#define pin_sst   4   // Revolverabzug; Strom
#define pin_spo   5   // Signale für servo
#define se0       0   // Positionen
#define se1       180
#define sew       2000 // Setzzeit  [ms]

//Lichtschranke
#define pin_ls0   6   // pin der Meßlichtschranke 0
#define pin_ls1   7   // pin der Meßlichtschranke 1

//Blitz
#define pin_bli   8   // Blitzauslöserpin, 2 Kabel schwarz+gelb
#define laz       6000 //  und Ladezeit[ms]

//Starttaster
#define pin_wei   10    // Taster 'weiter' gelbes Kabel
#define pin_wli   3     // Licht für Weiter-Taster grünes Kabel (war 9; Tausch mit IR-Pin 3-Nano auf 9-MEGA)
#define pin_prw   30    // Kippschalter Vorlauf / Hauptlauf grünes Kabel

//Einblendeanzeige
#define lda       2000 //  [ms]
#define pin_ldi   11 // data in
#define pin_lcs   13 // CS
#define pin_lcl   12 // clock


//Schrittmotor
#define pin_sla   32    /*  Workaraound, sollte die Direktansteuerung nicht klappen für Mikro-Arduino
                            der dann die Ansteuerung des SM übernimmt
                            derzeit nicht belegt
                            */
#define pin_sm0   22    // Vierfachkabel zur H-Brücke (2 Stück); SM Strom getrennt, gelb
#define pin_sm1   24    // lilagrau
#define pin_sm2   26    // orange
#define pin_sm3   28    // lila‚
#define schritteProDrehung 2048
#define drehgeschwindigkeit 5 // rpm

//Anzeigen
#define lcd_aktiv true
#define prn_aktiv true
#define debug     false

// Initialisierung der Bibliotheken
LedControl        lc =  LedControl(pin_ldi, pin_lcs, pin_lcl, 1);
IRsend            kamera;
Servo             abzug;
Stepper           Drehscheibe(schritteProDrehung, pin_sm0, pin_sm1, pin_sm2, pin_sm3);
LiquidCrystal_I2C lcd (0x27, 2, 1, 0, 4, 5, 6, 7, 3, POSITIVE);

// Variablendefinition

char *Variable[]  = {
  // Label für Parametereinstellung
  "n Kugeln   : ",
  "mm Distanz : ",
  "t0 1.Foto  : ",
  "t Inkrement: ",
  // Label für Bildschleife
  "Kugel Nr.  : ",
  "V0 (dm/s)  : ",
  "tn-t0 (ms) : ",
  "delta s(mm): ",
};

int adresse = EEPROM.read(0) + 256 * EEPROM.read(1);

int Wert[] = {
  (int)EEPROM.read(adresse * 8 + 2) + 256 * (int)EEPROM.read(adresse * 8 + 3),
  (int)EEPROM.read(adresse * 8 + 4) + 256 * (int)EEPROM.read(adresse * 8 + 5),
  (int)EEPROM.read(adresse * 8 + 6) + 256 * (int)EEPROM.read(adresse * 8 + 7),
  (int)EEPROM.read(adresse * 8 + 8) + 256 * (int)EEPROM.read(adresse * 8 + 9),
  5, 6, 7, 8
};

int Min[] = {  1,  200,  -200,  0}; // n Kugel, Flugstrecke [mm], erster Auslösezeitpunkt  [us], Intervall  [us]
int Max[] = { 6, 2000,  500,  500};
int Inkrement[] = {  1,  5,  10,  5};

// Ablauf Variablen
const unsigned long len = laenge; // Länge der Lichtschranke [mm]
int aender = 0; // index des aktuell zu ändernden Parameters [0,3]

// Funktionsdefinitionen

void speichern() {
  EEPROM.write(adresse * 8 + 2, lowByte(Wert[0]));    EEPROM.write(adresse * 8 + 3, highByte(Wert[0]));
  EEPROM.write(adresse * 8 + 4, lowByte(Wert[1]));    EEPROM.write(adresse * 8 + 5, highByte(Wert[1]));
  EEPROM.write(adresse * 8 + 6, lowByte(Wert[2]));    EEPROM.write(adresse * 8 + 7, highByte(Wert[2]));
  EEPROM.write(adresse * 8 + 8, lowByte(Wert[3]));    EEPROM.write(adresse * 8 + 9, highByte(Wert[3]));
};

void Anzeige(bool lc, bool sp, bool pt) {

  int ptm = 0;
  if (pt) {
    ptm = 4;
  };

  if (sp) {
    Serial.println();
    for (int i = 0 + ptm; i < 4 + ptm; i++) {
      Serial.print(Variable[i]);
      Serial.print(Wert[i]);

      if ((!pt) and (aender == i)) {
        Serial.print("**");
      };
      Serial.println();
    };
  };

  if (lc) {
    lcd.clear();
    lcd.home();
  }
  for (int i = 0; i < 4; i++) {
    lcd.setCursor(0, i);
    lcd.print(Variable[i + ptm]);
    lcd.setCursor(13, i);
    lcd.print(Wert[i + ptm]);
    if ((aender == i) and (!pt)) {
      lcd.setCursor( 18, i);
      lcd.print("**");
    };
  };
};

void starttaster() {
  digitalWrite(pin_wli, HIGH); // leuchtet!
  while (digitalRead(pin_wei) and sicherheit()) {}; // wartet auf das drücken und prüft, dass der Programmschalter auf 'Lauf' steht
  delay(prell * 10);
  digitalWrite(pin_wli, LOW); // leuchtet nicht mehr...
  ;
};

int drehung() {
  //  Frage Drehsignalgeber I ab
  if (digitalRead(pin_clk) == true) {
    return 0;
  };
  // keine Drehung detektiert

  delay(prell);

  if (digitalRead(pin_clk) == true) {
    return 0;
  };
  // est ein positives Signal (LOW) aber dann Drehung nicht bestätigt

  if (digitalRead(pin_dre) == true) {
    delay (prell);
    while (!(digitalRead(pin_dre) and digitalRead(pin_clk))) {
      delay(prell);
    };
    return -1;
  };
  // Drehung bestätigt im Sinn -1

  if (digitalRead(pin_dre) == false) {
    delay (prell);
    while (!(digitalRead(pin_dre) and digitalRead(pin_clk))) {
      delay(prell);
    };
    return 1;
  };
  // Drehung bestätigt im Sinn +1
};

void parameter() {
  // Konopf gedrückt um nächste Eingabeabfrage zu setzen
  if (!digitalRead(pin_but)) {
    delay(prell);
    if (!digitalRead(pin_but)) {
      if (debug) {
        Serial.println("Knopf gedrückt");
      };
      while (!digitalRead(pin_but)) {};
      delay(prell);
      if (aender < 3) {
        aender++;
      } else {
        aender = 0;
      };
      if (debug) {
        Serial.println("Knopf losgelassen");
      };
      Anzeige(lcd_aktiv, prn_aktiv, false);
    };
  };

  // Frage Drehgeber ab
  int drehpuls = drehung();
  if (drehpuls != 0) {
    Wert[aender] = Wert[aender] + drehpuls * Inkrement[aender];
    if (Wert[aender] < Min[aender]) {
      Wert[aender] = Min[aender];
    };
    if (Wert[aender] > Max[aender]) {
      Wert[aender] = Max[aender];
    };
    Anzeige(lcd_aktiv, prn_aktiv, false);
  };
};


// servo setzten; 0/1 Ruhe / Abzug und Stellzeit in ms warten
// Achtung: die Zeit ab Kommando '1" und bis Schuss ist niicht definiert, für Schuß daher Stellzeit 0!
void servo_setzen(int abzugpos, int stellzeit) {
  if (abzugpos == 0) {
    abzug.write(se0);
    delay(stellzeit);
  };
  if (abzugpos == 1) {
    abzug.write(se1);
    delay(stellzeit);
  };
  if (debug) {
    Serial.print("SP: ");
    Serial.print(abzugpos);
    Serial.print('\t');
  };
  return;
}

// Auslösung Kamera
void kamera_ausloesen() {
  for (int i = 0; i < 3; i++) {
    kamera.sendSony(als, 20); // Sony TV power code
    delay(50);
  };
};


// Blitzauslösung
void blitz_ausloesen (unsigned long ausloesezeitpunkt) {
  while (ausloesezeitpunkt >  micros()) {}; // wartet bis Auslösezeitpunkt eingetreten ist
  digitalWrite(pin_bli, LOW);
  delay(2);                // 2 ms
  digitalWrite(pin_bli, HIGH);
  return;
};


//  Bildschirme
void startbildschirm() {
  lcd.setBacklight(HIGH);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Vorbereitung:");
  lcd.setCursor(0, 1);
  lcd.print("=======");
  lcd.setCursor(0, 2);
  lcd.print("Eingabe");
  lcd.setCursor(0, 3);
  lcd.print("der Startwerte.");
  delay(anzeigezeit);
};

void zwischenbildschirm() {
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Es geht los!");
  Serial.print("Es geht los! ");
  delay(anzeigezeit);
  lcd.clear ();
};

void abschlussbildschirm() {
  lcd.setBacklight(HIGH);
  lcd.clear();
  lcd.setCursor(0, 0);
  lcd.print("Ende der Serie:");
  delay(anzeigezeit);
};


// Ausgabe 8-stellige 7-Sergment-LED
void led_setzen ( long versatz, float geschwindigkeit, int durchgang, int leuchtdauer)
{
  // Einzelziffern für Anzeige berechnen
  int v100 = 0;
  int v010 = 0;
  int v001 = 0;
  bool plu = false;
  int ve100 = 0;
  int ve010 = 0;
  int ve001 = 0;

  v100 = (int(geschwindigkeit) / 10) % 10;
  v010 = int(geschwindigkeit) % 10;
  v001 = int((geschwindigkeit * 10)) % 10;

  ve100 = abs(versatz) / 100 % 10;
  ve010 = abs(versatz) / 10 % 10;
  ve001 = abs(versatz) % 10;
  if (versatz < 0) {
    plu = false;
  } else {
    plu = true;
  };


  // LED Setzen
  // plus/Minus
  if (!plu) {
    lc.setChar(0, 7, "c", false);
  } else {
    lc.setChar(0, 7, " ", false);
  };

  // versatz
  lc.setDigit(0, 6, ve100, false);
  lc.setDigit(0, 5, ve010, false);
  lc.setDigit(0, 4, ve001, true);

  // durchgang
  lc.setDigit(0, 3, durchgang, true);

  // V0
  lc.setDigit(0, 2, v100, false);
  lc.setDigit(0, 1, v010, true);
  lc.setDigit(0, 0, v001, false);

  delay(leuchtdauer); // Zeitdauer in msec für Ziffern an
  lc.clearDisplay(0); // löschen

};


bool sicherheit() {
  if (digitalRead(pin_prw)) {
    return true;
  }
  else {
    return false;
  };
};

void zieldrehung () {
  Drehscheibe.step(schritteProDrehung / 6);
  digitalWrite(pin_sla, LOW);
  delay(20);
  digitalWrite(pin_sla, HIGH);
};

void schritt() {
  while (digitalRead(pin_wei)) {};
  delay(50);
};

void bildlauf(int f_kug, int f_dis, int f_ve0, int f_dti, unsigned long f_len ) {
  digitalWrite(pin_spo, LOW); // servo Power on
  for (int dug = 0; dug < f_kug; dug++) {
    // Hauptschleife für die Kugeln
    // 0. schalter immer noch offen?  wenn nicht dug=kug und break
    // 1. Kamera offen
    // 2. Servo hopp
    // 3. warte auf Schuss
    // 4. Berechne V und Aufschlagzeit
    // 5. löse Blitz aus
    // 6. Gib V0 und Versatzzeit aus
    // 7. Servo zurück
    // 8. warte

    //0.
    starttaster();

    if (!sicherheit()) {
      dug = f_kug;
      break;
    }; // Schalter nicht mehr umgelegt LOW bricht ab


    // 1.
    // sende IR-Signal an die Kamera
    kamera_ausloesen();

    // 2.
    // Setze Servo auf Auslösewinkel
    servo_setzen(1, 0);

    // 3.
    // Messung
    while (digitalRead(pin_ls0)) {}; // warte bis LS0 auf LOW geht
    unsigned long st0 = micros();
    // erster Messpunkt gespeichert
    if (debug) {
      Serial.println();
      Serial.print("LS0: "); Serial.println(st0);
    };

    while (digitalRead(pin_ls1)) {}; // warte auf die 2. Lichtschranke LOW FLanke
    unsigned long st1 = micros();
    // zweiter Messpunkt gespeichert
    if (debug) {
      Serial.println();
      Serial.print("LS1: "); Serial.println(st1);
    };
    unsigned long stl = st1 - st0;
    // Zeit über Lichtschranke [us]

    float vku = float(f_len) * float(1000) / float(stl); // [[mm*1000]=um] / [us]
    // Mündungs-V0 in um/us^=m/s

    // 4.
    // Aufschlagzeit mit Latenz und aktuellem Versatz
    unsigned long auf = st1 + ((f_dis * stl) / f_len) ; // Aufschlagzeit in micros()-Einheit
    long ver =  f_ve0 + f_dti * dug; // aktueller Zeitversatz aus Basisversatz und Schritt / Durchgang
    unsigned long aus = (unsigned long) ((long)auf + (long)ver); // wann soll blitz auslösen

    // 5.
    // löse den Blitz aus
    blitz_ausloesen(aus);

    // 6. Ausgaben
    if (debug) {
      Serial.print("stl: "); Serial.println(stl);
      Serial.print("vk0: "); Serial.println(vku);
      Serial.print("f-dis: "); Serial.println(f_dis);
      Serial.print("f-len: "); Serial.println(f_len);
      Serial.print("auf: "); Serial.println(auf);
      Serial.print("aus: "); Serial.println(aus);
      Serial.print("ver: "); Serial.println(ver);
    };

    Wert[4] = dug + 1;
    Wert[5] = (int) (vku * 10.0);
    Wert[6] = (int) ver ;// 1000;
    Wert[7] = (int) (vku * ver / 1000);

    // gib Vo, Runde und Zeitversatz aus; Versatz, V0, Durchgang, Leuchtdauer
    led_setzen (ver, vku, dug, lda);
    // led_setzen (-90, 80.5, 3, 2000);
    Anzeige(lcd_aktiv , prn_aktiv, true);

    // 7.
    // Servo zurück
    servo_setzen(0, sew);
    zieldrehung();

    // 8.
    // warte laz(Latenzzeit) msec (Kameraverschluß, Blitzladazyklus etc)
    delay(laz);
  };

  // halt!
  servo_setzen(0, sew); // Nullposition
  digitalWrite(pin_spo, HIGH); // Servo stromlos
};

//==============================================================================================
// programmteile
void setup () {
  //Drehwahlschalter
  pinMode(pin_clk, INPUT_PULLUP);
  pinMode(pin_dre, INPUT_PULLUP);
  pinMode(pin_but, INPUT_PULLUP);

  // programmteilwahlschalter
  pinMode(pin_prw, INPUT); // der Pin ist mit 100 k auf LOW gelegt und geht HIGH für 'Lauf'

  // Lichtschranke
  pinMode(pin_ls0, INPUT);
  pinMode(pin_ls1, INPUT);

  // BLitz
  pinMode(pin_bli, OUTPUT);
  digitalWrite(pin_bli, HIGH);

  //Seriell
  if (debug) {
    Serial.begin(9600);
    delay(300);
  };
  //LCD
  lcd.begin(20, 4);
  /*
    lcd.clear();
    lcd.setBacklight(HIGH);
    delay(1000);
    lcd.setBacklight(LOW);
    delay(1000);
  */
  lcd.clear();

  Anzeige(lcd_aktiv, prn_aktiv, false);

  //Schrittmotor
  Drehscheibe.setSpeed(drehgeschwindigkeit);


  // IR Diode --> Bibliothek PIN 3 festgelegt; schon initialisiert

  // Servo
  pinMode (pin_spo, OUTPUT);
  digitalWrite(pin_spo, LOW);
  abzug.attach(pin_sst); // Steuerungsausgang Servo zugeordnet
  servo_setzen(se0, sew); // Neutralposition Abzug, 2 Sek. warten

  // weiter-Knopf
  pinMode(pin_wei, INPUT_PULLUP); // geht auf LOW für Schussfreigabe
  pinMode(pin_wli, OUTPUT);

  // 8LED
  lc.shutdown(0, false);
  lc.setIntensity(0, 8);
  lc.clearDisplay(0);


  // slave-Arduino
  pinMode(pin_sla, OUTPUT);
  digitalWrite(pin_sla, HIGH);

}


void loop() {
// led_setzen (-90, 80.5, 3, 2000);
  //Startbildschirm
  startbildschirm();

  // frage solange die Parameter ab, wie der Wahlschalter off ist
  while (!digitalRead(pin_prw)) {
    parameter ();
  };

  zwischenbildschirm();

  // speichere die neuen Parameter im EEPROM
  speichern ();

  // beginne die Bilderstellung
  bildlauf(Wert[0], Wert[1], Wert[2], Wert[3], len);

  // Abschluß
  abschlussbildschirm();
  while (true) {};
}

Nice project, and nice photographic results!

I'm curious, which camera did you use? I assume you're triggering it via a remote triggering functionality of some sort?

Sony A7 II; Open shutter (shutter opens before shot; flash is triggered for impact, flash burning time 0.0001-0.0002 sec

1 Like

You've got an awful lot of semicolons where semicolons aren't needed, and your EEPROM handling could be simplified by using EEPROM.get and EEPROM.put.

Code like

  int v100 = 0;
  int v010 = 0;
  int v001 = 0;
  bool plu = false;
  int ve100 = 0;
  int ve010 = 0;
  int ve001 = 0;

  v100 = (int(geschwindigkeit) / 10) % 10;
  v010 = int(geschwindigkeit) % 10;
  v001 = int((geschwindigkeit * 10)) % 10;

  ve100 = abs(versatz) / 100 % 10;
  ve010 = abs(versatz) / 10 % 10;
  ve001 = abs(versatz) % 10;
  if (versatz < 0) {
    plu = false;
  } else {
    plu = true;
  };

could be simplified - there's no point initialising the variables to zero if you're going to put other values in them

  int v100 = (int(geschwindigkeit) / 10) % 10;
  int v010 = int(geschwindigkeit) % 10;
  int v001 = int((geschwindigkeit * 10)) % 10;

  int ve100 = abs(versatz) / 100 % 10;
  int ve010 = abs(versatz) / 10 % 10;
  int ve001 = abs(versatz) % 10;
  bool plu = versatz >= 0;

Thank's for your hints; I'm at war with semicolons since the old days of Pascal :wink: and you're absolutely right; no need for double assignent - I must admit, when finishing I never again had a closer look to the first part.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.