Tropfenfotografie mit Raspberry und Lichtschranke

17. Januar 2014 at 20:15
Print Friendly, PDF & Email

In der c’t Hardware Hacks 01/13 wurde beschrieben, wie man Tropfen fotografiert. Der Aufbau basierte auf einem Arduino.

Dieser Artikel beschreibt die Umsetzung mit einem Raspberry Pi.
Weiterhin habe ich Funkauslöser für Kamera und Blitz eingesetzt.

(Eine Zeitmessung mit zwei Lichtschranken findet man hier: https://hoeser-medien.de/?p=1212)

Für Einstellungen der Kamera gibt es hier Ratschläge: http://www.tropfenfotografie.de/

KONICA MINOLTA DIGITAL CAMERA
Weitere Bilder gibt es hier: klick

Aufbau

KONICA MINOLTA DIGITAL CAMERA

 

Beim ersten Versuch habe ich eine Spritze genommen. Später soll an dem Galgen eine Flasche hängen.

Die Schraube dient dazu, die Kamera zu fokussieren.

Hinter der mattierten Scheibe befindet sich ein zweiter Blitz.

 

Die Gartenschlauch-Kupplung dient als Lichtschranke. Unten wurde ein entsprechendes Loch gebohrt, um die IR-Diode bzw. den Fototransistor einzuführen.

KONICA MINOLTA DIGITAL CAMERA

 

Was wird benötigt ?

  • Digitalkamera
  • Funkauslöser (optional) Funktioniert nicht: Siehe Validation ganz unten
  • Studioblitz (optional)
  • Raspberrry PI
  • Elektronik-Interface (s.u.)
  • Software:
  • flash.c (s.u.)
  • wiringPI

Systemberechnung

flash-system-1 flash-system-2

Hardware Lichtschranke

Die Lichtschranke besteht aus einer IR-LED CQY37N und einem Fototransistor BPW17N.

flash-ecad-0

 

Gekauft bei ebay “NPN PHOTOTRANSISTO R + INFRAROT EMITTING DIODE 10 PAAR” für knapp 3 EUR.

flash-ecad-1a

Die Schaltung  basiert auf der Anleitung bei http://www.strippenstrolch.de/1-2-12-der-reflexkoppler-cny70.html Der Fototransistor steuert ein Schmitt-Trigger, um ein sauberes Ausgangssignal zu erhalten.

 

Funkauslöser

Ein Funkauslöser ist nicht notwendig, erlaubt jedoch eine flexibel Aufbau. Den Funkauslöser von Yongnuo setze ich für Portraitfotos bereits ein. Hier kann man dies sehen: klick.

Die Schaltung ist relativ und basiert auf einem 2N7000.

flash-ecad-2

Man kann die Treiberstufe mit einem Transistor oder Mos-FET realisieren. Ich hatte den 2N7000 verfügbar und habe daher diesen verwendet.

 

Software

Die Auslösung der Lichtschranke wird über einen Interrupt erkannt. WiringPi bietet eine einfache Lösung hierfür. In der Interrupt-Service-Routine (ISR) wird eine bestimmte Zeit gewartet, und dann der Ausgang geschaltet. Nach jedem Interrupt, wird die Wartezeit etwas erhöht, um den Tropfen in verschiedenen Phasen zu fotografieren.

 

Installation wiringPi

cd 
git clone git://git.drogon.net/wiringPi 
cd wiringPi 
./build

Installation argtable2

sudo apt-get install libargtable2-dev
cd git clone git://git.drogon.net/wiringPi cd wiringPi ./build
Makefile und Program liegen auf GitHUB:
cd; mkdir -p git; cd git
git clone git://github.com/ThomasH-W/Tropfenfotografie

Makefile

 

# -----------------------------------------
# makefile V0.2 2014-01-12 Thomas Hoeser
# do not use space at the beginning of a line – use tab only !
# -----------------------------------------
 
CC=gcc
# CFLAGS =I. -I/usr/local/include -Wall -ansi -O0
CFLAGS=-I. -lwiringPi -largtable2
 
LDFLAGS = -L/usr/lib
LDLIBS = -largtable2
 
PROJ=flash
RELEASEDIR=.
RASP-IP=192.168.178.61
 
all: prepare $(PROJ)
    @echo "------------------------ Fertig"
 
debug: prepare flash_ debug
 
# w/o : no echo of the command
prepare:
    @echo "Ich fang jetzt an!"
 
# w/o error message : - at the beginning
clean:
    -rm -f $(PROJ) $(PROJ).o
 
release:
    @echo “copy file $(PROJ) via ftp to raspberry $(RASP-IP)”
 
$(PROJ): $(PROJ).c
    $(CC) $(CFLAGS) -o $(PROJ) $(PROJ).c
 
flash_debug: flash.c
    gcc gcc –DMYDEBUG flash.c …..
 
help:
    @echo
    @echo "Aufruf   : make target"
    @echo
    @echo "Targets  : help    - Zeigt diesen Text an."
    @echo "           all     - Erstellt das Ziel $(PROJ)"
    @echo "           release - Kopiert $(PROJ) nach $(RELEASEDIR)"
    @echo "           clean   - Löscht die Ergebnisdateien (*.o, usw.)."
    @echo
    @echo "Beispiele: make all"
    @echo "           make clean"
    @echo

 

Flash.c

 

/*
 * flash.c:
 *    This progam is used to trigger a digital camera utilizing a light barrier
 *
 * git clone git://git.drogon.net/wiringPi
 * cd wiringPi
 * ./build
 *
 * sudo apt-get install libargtable2-dev
 *
 * gcc -lwiringPi -o flash flash.c
 *
 */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/time.h>
#include <wiringPi.h>
#include <argtable2.h>  // argopt
#include <errno.h>
 
#define PROGNAME         flash
#define MYDEBUG_NO
 
// Which GPIO pin we're using
#define PIN_IN         3    // GPIO 22 - header PIN 15
#define PIN_RELAYS     4    // GPIO 23 - header PIN 16
#define PIN_LED     5    // GPIO 24 - header PIN 18
 
/* --------------------------------------------------------
 * There are 8 available GPIO Pins on Raspberry Pi
 *                         wirPi        PIN  |  PIN   wirPi
 *                     ------------------|---------------
 * 3.3V,Vdd,Vcc              1         2     |                    5V
 * SDA                           8         3     |     4                5V
 * SCL                          9         5     |     6                  GND, Vss, Vee
 * 1-Wire    GPIO  4         7         7     |     8        15        UART TXD
 *                                        9     |    10        16         UART RXD
 *             GPIO 17          0         11  |    12         1        GPIO 18    PCM CLK
 *             GPIO 21            2         13     |    14                GND, Vss, Vee
 *             GPIO 22           3         15  |    16         4        GPIO 23
 * 3.3V,Vdd,Vcc                      17  |    18         5        GPIO 24
 * MOSI                     12         19  |    20                 GND, Vss, Vee
 * MISO                           13         20  |    22         6        GPIO 25
 * SCLK                         14         23     |    24        10        CE0
 * GND, Vss, Vee                        25     |    26        11        CE1
 */
 
struct timeval last_change       ;  // Time of last change
 
static volatile int globalDropCnt = 0 ;    // Count drops recognized
static volatile int t_wait_start  = 0 ;    // Count drops recognized
static volatile int t_wait_incr   = 0 ;    // Count drops recognized
static volatile int t_sysdelay    = 0 ;    // Count drops recognized
 
// How much time a change must be since the last in order to count as a change
#define IGNORE_CHANGE_BELOW_USEC 2000000 // 1 s
// s =    4,91 [m/s]    *    t2 [s]
// t 2 =    s    /    4,91 [m/s]
#define FLIGHT_MSEC         140 // 140ms    // time between input signal and trigger output
#define SYSDELAY_MSEC        20 //  20ms    // system (camara, remote) delay
#define FLIGHT_INCR_MSEC   40    //  40ms    // offset to FLIGHT_MSEC
#define FLASH_MSEC          320 // 320ms    // lenght of output signal for camera
 
// 1 Sekunde = 1000 Millisekunden = 1000000 Mikrosekunden = 10-6 Mikrosekunden
// 1 Millisekunden = 1000 Mikrosekunden
// 1 Mikrosekunde (µs) = 1000 Nanosekunden = 0,000 001 Sekunden = 10-6 Sekunden
 
// -------------------------------------------------------------------------------------
// toggle state for given time
void output_blink(int OUTPUT_PIN, int BLINK_TIME) {
 
    static volatile int state;       // define variable for state of the pin
    state = digitalRead(OUTPUT_PIN); // Get current state of pin
 
    digitalWrite (OUTPUT_PIN, !state) ;    // set pin to inverse state
    delay (BLINK_TIME) ;                // wait for given time [milliseconds]
    digitalWrite (OUTPUT_PIN, state) ;    // set pin to original state
}
// -------------------------------------------------------------------------------------
void myStandardFunc(void)
{ printf ("STD  - Global Counter = %d\n",++t_wait_start); } // end myStandardFunc()
// -------------------------------------------------------------------------------------
// Handler for interrupt
void myInterrupt(void) {
    static struct timeval t_begin,t_end;
    static unsigned long diff;
    static int t_delay,seconds,useconds;
 
    gettimeofday(&t_begin, NULL); // get current time and save in variable t_begin
 
    // Time difference in usec
    diff = (t_begin.tv_sec * 1000000 + t_begin.tv_usec) - (last_change.tv_sec * 1000000 + last_change.tv_usec);
 
    if (diff > IGNORE_CHANGE_BELOW_USEC) {  // Filter jitter
    t_delay = (t_wait_start + globalDropCnt* t_wait_incr - t_sysdelay );
 
    #ifdef MYDEBUG
    printf("flight start    :  %d \n",t_wait_start);
    printf("system delay    : -%d \n",t_sysdelay);
    printf("flight increment:  %d \n",t_wait_incr );
    printf("net delay       :  %d \n",t_delay );
    #endif  
 
      globalDropCnt ++;
    delay(t_delay) ;                         // wait for flight from light barrier to water surface
 
    gettimeofday(&t_end, NULL);            // get current time and save in variable t_end
      output_blink(PIN_RELAYS, FLASH_MSEC ); // Trigger Flash
 
      seconds  = t_end.tv_sec  - t_begin.tv_sec;
      useconds = t_end.tv_usec - t_begin.tv_usec;
      if(useconds < 0) {
        useconds += 1000000;
        seconds--;
        }
      printf("Interrupt #%d -> delay: %d -> var start/end: %d sec %d msec\n\n",globalDropCnt, t_delay, seconds, useconds/1000);
    // flight_start =   useconds; // dummy code
      output_blink(PIN_LED, 200 );
 
    } // if - Filter jitter
 
    last_change = t_begin;
} // myInterrupt()
 
// -------------------------------------------------------------------------------------
int mymain(int flight_start, int flight_incr, int sysdelay)
  {
  t_wait_start = flight_start;
  t_wait_incr  = flight_incr;
  t_sysdelay   = sysdelay;
 
  printf("flight_start   : %d \n",t_wait_start);
  printf("sysdelay       : %d \n",t_sysdelay);
  printf("flight_incr    : %d \n",t_wait_incr );
 
    globalDropCnt=0;                     // init counters
    gettimeofday(&last_change, NULL);     // Time now
 
    wiringPiSetup();                    // Init
    pinMode (PIN_RELAYS, OUTPUT) ;  // Set pin to output in case it's not
    pinMode (PIN_LED,    OUTPUT) ;  // Set pin to output in case it's not
    digitalWrite (PIN_LED,HIGH);        // Set pin to low
    wiringPiISR(PIN_IN, INT_EDGE_RISING, &myInterrupt);  // Bind to interrupt
 
  printf("flash - wait for drop ....\n\n");
  output_blink(PIN_LED, 500);
    // Waste time but not CPU
    for (;;) {
        sleep(1000);
    } // for
 
  return 0;
} // mymain()
// -------------------------------------------------------------------------------------
// http://argtable.sourceforge.net/example/myprog.c
//
int main(int argc, char **argv) {
 
    struct arg_int* aflight_start = arg_int0("s","start",NULL,       "start     (default is 100)");
    struct arg_int* aflight_incr  = arg_int0("i","increment",NULL,   "increment (default is 10)");
    struct arg_int* asysdelay     = arg_int0("d","delay",NULL,       "delay     (default is 20)");
    struct arg_lit* help         = arg_lit0(NULL,"help",            "print this help and exit");
    struct arg_lit* version      = arg_lit0(NULL,"version",         "print version information and exit");
    struct arg_end* end          = arg_end(20);
    void* argtable[] = {aflight_start,aflight_incr,asysdelay,help,version,end};
    const char* progname = "flash";
    int nerrors;
    int exitcode=0;
 
    /* verify the argtable[] entries were allocated sucessfully */
    if (arg_nullcheck(argtable) != 0)
        {
        /* NULL entries were detected, some allocations must have failed */
        printf("%s: insufficient memory\n",progname);
        exitcode=1;
        goto exit;
        }
 
    /* set any command line default values prior to parsing */
    aflight_start->ival[0] = FLIGHT_MSEC;
    aflight_incr ->ival[0] = FLIGHT_INCR_MSEC;
    asysdelay    ->ival[0] = SYSDELAY_MSEC;
 
    /* Parse the command line as defined by argtable[] */
    nerrors = arg_parse(argc,argv,argtable);
 
    /* special case: '--help' takes precedence over error reporting */
    if (help->count > 0)
        {
        printf("Usage: %s", progname);
        arg_print_syntax(stdout,argtable,"\n");
        printf("This program demonstrates the use of the argtable2 library\n");
        arg_print_glossary(stdout,argtable,"  %-25s %s\n");
        exitcode=0;
        goto exit;
        }
 
    /* special case: '--version' takes precedence error reporting */
    if (version->count > 0)
        {
        printf("'%s' trigger camera based on light gate\n",progname);
        printf("January 2014, Thomas Hoeser\n");
        exitcode=0;
        goto exit;
        }
 
    /* If the parser returned any errors then display them and exit */
    if (nerrors > 0)
        {
        /* Display the error details contained in the arg_end struct.*/
        arg_print_errors(stdout,end,progname);
        printf("Try '%s --help' for more information.\n",progname);
        exitcode=1;
        goto exit;
        }
 
/* normal case: take the command line options at face value */
exitcode = mymain(aflight_start->ival[0],aflight_incr->ival[0], asysdelay->ival[0]);
 
exit:
    /* deallocate each non-null entry in argtable[] */
    arg_freetable(argtable,sizeof(argtable)/sizeof(argtable[0]));
 
    return exitcode;
} // main()

Programm compilieren + starten

 

make
sudo ./flash

 

Anpassung für den Testaufbau:

sudo ./flash -s 100 -i 10 -d 20

Parameter

  • -s 100  # Fallzeit erechnet auf Fallhöhe
  • -i 10     # Inkrement für den nächsten Durchlauf
  • -d 20  # Auslöseverzögerung  (Funkauslöser + Kamera)

 

Validation der Schaltung / Software

Am Anfang hat sich der Aufbau nicht wie erwartet verhalten. Mit einem  LogicAnalyzer habe ich dies dann nachgeprüft. Die Verzögerung des Funkauslösers war größer, als ich erwartet hatte.

flash-2

 

Lichtschranke -> GPIO-Funksender: 80,2 ms ; Also fast genau der Zeitvorgabe des Raspberry.

flash-logik-0

 

GPIO-Funksender -> Kamera -> Empfänger Studioblitz: 0,41 s = 410 ms d.h. eine Verzögerung durch die Kamera von 410ms -> dies entsprichet einer Fallhöhe 82 cm

flash-logik-2

 

 

2. Versuch GPIO-Funksender -> Kamera -> Empfänger Studioblitz: 0,317 s = 317 ms

Noch schlimmer: Die Verzögerung durch die Kamera ist nicht konstant!

Ergo: die Aufnahmen mit Funkauslösung sind nicht zuverlässig. Die Kamera muss mit einem Kabel verbunden werden.

flash-logik-3