Bullet Time - Aka The Matrix Effect

How to control the shutter of 30 DSLR cameras?

Please read Liability Disclaimer and License Agreement CAREFULLY

"Bullet time" is a visual effect that was popularized by the 1999 movie "The Matrix." It is also known as "time-slice photography" or the "Matrix effect."

The effect is created by using an array of still cameras or a single camera moving extremely fast around the subject, capturing images from different angles simultaneously.

The images are then played back in slow motion or in a freeze-frame style, giving the appearance of time standing still or the ability to see action from all angles at once.

In the movie "The Matrix," the effect was used to show the protagonist dodging bullets in slow motion.

Since then, the technique has been used in numerous movies, TV shows, and commercials to create dramatic and visually stunning sequences.

The Matrix project is based on ATmega2560 microcontroller to provide the required number of GPIO to control up to 30 DSLR cameras.

The Matrix device set-up is done using a 5 way joystick switch (Arduino Joystick Switch) , all settings are displayed on a Siemens S65 display 

(check this link for how to set it up).

I have included also the option to remote trigger the Matrix using the SM0038 IR sensor 

 

Matrix PCB Top

Matrix PCB Bottom

The Eagle files can be downloaded using right click + Save Link As Matrix.sch and Matrix.brd

Video showing the fist test

The C code for Arduino IDE

#include <S65Display.h>
#include <avr/eeprom.h>
#include <avr/pgmspace.h>
S65Display lcd;
#define IR_On          PORTB |=  (1<<7) //Power ON IR SM0038  (PB0)
#define IR_Off         PORTB &= ~(1<<7) //Power OFF IR SM0038  (PB0)
#define BkLight_On          PORTD |=  (1<<5)
#define BkLight_Off         PORTD &= ~(1<<5)
const byte ROM_Cameras		= 0; //No of cameras 1-30
const byte ROM_Delay		= 1; //No of ms between shots
const byte ROM_Cycles		= 2; //No of cycles
const byte ROM_DStart		= 3; //Time in [s] to delay the start
const byte ROM_BDelay		= 4; //Time in [ms] to hold the shutter pressed
const byte ROM_BackLight	= 5; //Time in [s] to keep the back light on 0 = always
const byte ROM_RemTrigger	= 6; //1=Use/0=NoUse remote trigger
const byte Items	        = 7; 
const byte mX=8;
const byte tX=12;
const char Str00[] PROGMEM = "No. of Cams";
const char Str01[] PROGMEM = "Shots Delay";
const char Str02[] PROGMEM = "NoOf Cycles";
const char Str03[] PROGMEM = "Start Delay";
const char Str04[] PROGMEM = "Button Time";
const char Str05[] PROGMEM = "Back  Light";
const char Str06[] PROGMEM = "RemoteTrigg";
const char Str07[] PROGMEM = "StartMATRIX";
const char Str08[] PROGMEM = "Matrix v0.1";
const char Str09[] PROGMEM = "Yes";
const char Str10[] PROGMEM = "No";
const char * const Strings[] PROGMEM = {Str00, Str01, Str02, Str03, Str04, Str05, Str06, Str07, Str08, Str09, Str10};
#define WHITE        0xFFFFFFFF//RGB(255,255,255) 
#define BLACK        0x00000000//RGB(0,0,0) 
#define GREY         0xFFFFC618//RGB(195,195,195)
#define RED          0xFFFFF800//RGB(255,0,0) 
#define GREEN        0x7E0//RGB(0,255,0) 
#define BLUE         0x1F//RGB(0,0,255) 
#define CYAN         0x7FF//RGB(0,255,255) 
#define MAGENTA      0xFFFFF81F//RGB(255,0,255) 
#define YELLOW       0xFFFFFFE0//RGB(255,255,0) 
#define BROWN        0xFFFFA145//RGB(165,42,42) 
#define ORANGE       0xFFFFFD20//RGB(255,165,0) 
#define PINK         0xFFFFFE19//RGB(255,192,203)
#define txtBGColor   0x00000000//RGB(0,0,0)//Black
#define nText        0x1F//RGB(0,0,255)//Blue
#define hText        0xFFFFFFFF//RGB(255,255,255)//White
#define mText        0x7E0//RGB(0,255,0)//Green
enum {Key_None, Key_R, Key_L, Key_U, Key_D, Key_C};
byte ii, CrtItem, OldItem, ROM_VAL[Items], MAX_VAL[Items];
byte currentpulse = 0;
boolean GetOut;//Used to exit from the running menu
char StrBuffer[12];
unsigned long Bk_Light = 0;
/////////////////////////////////////////////////////////
//Description:Loads from PROGMEM a string
//Parameters: String array to load
//Returns: the string
/////////////////////////////////////////////////////////
char* GetPrgStr(byte Idx){
	StrBuffer[0] = '\0';
	strcpy_P(StrBuffer, (char*)pgm_read_word(&(Strings[Idx])));
	return StrBuffer;
}

/////////////////////////////////////////////////////////
//Description: General function asociated with menu item. If an menu item has an value this function will change that value
//Parameters: None
//Returns: None
/////////////////////////////////////////////////////////
void ChangeValue(){
	GetOut = false;
	while(!GetOut){
		uint8_t OldParamVal = ROM_VAL[CrtItem];
		switch(GetKey()){
		case Key_R:
			ROM_VAL[CrtItem] = ROM_VAL[CrtItem] + 20;
			if(ROM_VAL[CrtItem] > MAX_VAL[CrtItem])
				ROM_VAL[CrtItem] = 0;
			break;
		case Key_U:
			ROM_VAL[CrtItem]++;
			if(ROM_VAL[CrtItem] > MAX_VAL[CrtItem]) ROM_VAL[CrtItem] = 0;
			break;
		case Key_D:
			ROM_VAL[CrtItem]--;
			if(ROM_VAL[CrtItem] > MAX_VAL[CrtItem]) ROM_VAL[CrtItem] = MAX_VAL[CrtItem];
			break;
		case Key_L:
			ROM_VAL[CrtItem] = (ROM_VAL[CrtItem] < 20) ? MAX_VAL[CrtItem] + ROM_VAL[CrtItem] - 20 : ROM_VAL[CrtItem] - 20;
			break;
		case Key_C:
			GetOut = true;
			break;
		}
        delay(10);             
		if((OldParamVal != ROM_VAL[CrtItem]) && (CrtItem < Items)){
			lcd.drawBG(144, 7 + CrtItem * 15, 16, 0);
			lcd.drawTextT(tX, 8 + CrtItem * 15, GetPrgStr(CrtItem), hText);//Label
			lcd.drawTextT(144, 8 + CrtItem * 15, itoa(ROM_VAL[CrtItem], StrBuffer, 10), GREEN);//Value
            if(CrtItem == ROM_BackLight) {
                  if(ROM_VAL[ROM_BackLight]) {
                          //if(ROM_VAL[ROM_BackLight] > 2) ROM_VAL[ROM_BackLight] = 2;
                          BkLight_On;
                          Bk_Light = millis();
                  } else
                     BkLight_Off;
            }
            //lcd.contrast((byte)ROM_VAL[ROM_BackLight].Value); 
            if(CrtItem == ROM_RemTrigger) {
                  if(ROM_VAL[ROM_RemTrigger])
                        IR_On;
                  else
                        IR_Off;
            }
            if(CrtItem != ROM_RemTrigger) eeprom_write_byte((uint8_t*)(CrtItem), ROM_VAL[CrtItem]);       
		}
	}
	lcd.drawTextT(144, 8 + CrtItem * 15, itoa(ROM_VAL[CrtItem], StrBuffer, 10), hText);//Value
}

/////////////////////////////////////////////////////////
//Description: Draws curent menu
//Parameters: None
//Returns: None
/////////////////////////////////////////////////////////
void DrawMenu(){
	for(ii = 0; ii < 8; ii++){
		if(ii == CrtItem)//Highlight
		{
			lcd.drawBG(mX, 7 + ii * 15, 160, 0); 
		}
		else//Normal
		{
			lcd.drawBG(mX, 7 + ii * 15, 160, 1); 
		}
		lcd.drawTextT(tX, 8 + ii * 15, GetPrgStr(ii), hText);//Label
		if(ii < Items) lcd.drawTextT(144, 8 + ii * 15, itoa(ROM_VAL[ii], StrBuffer, 10), hText);//Draw value
	}
}

void setup(){
//InitFastAnalogRead();
//	Serial.begin(9600);
	DDRF &= ~(1<<0); //A0 Input - Read Switch
	// Turn on 20K pullup on analog 1 - keys pin
	PORTF |= B00000001;
    //DDRB &= ~(1<<6);//B1 Input - Read IR
    DDRB  |= B10000000;//Set digital pin 7 as output
    DDRD  |= B00100000;//Set digital pin 5 as outputv - back light
    DDRC  |= B11111111;//Set digital pins as output
    DDRK  |= B11111111;//Set digital pins as output
    DDRA  |= B11111111;//Set digital pins as output
    DDRL  |= B00111111;//Set digital pins as output
    BkLight_On;
	//init LCD
	lcd.init();
	//clear screen
	lcd.clear(BLACK);//RGB(255,0,0)
//	ResetEEPROM();
	SetMaxVal();
	LoadEEPROM();
	CrtItem = 0;
    DrawMenu();
    ROM_VAL[ROM_RemTrigger] = 0;
    DoTheMatrix();
//	for(ii = 0; ii < Items; ii++)
//	Serial.println(MAX_VAL[ii]);
}

void loop(){
    if((ROM_VAL[ROM_BackLight] == 2) && (millis() > Bk_Light))
      BkLight_Off;
    
    //If we enabled the remote trigger and the IR signal is ok
    if(ROM_VAL[ROM_RemTrigger] && !bitRead(PINB, 6))//if we received a comlete burst
        DoTheMatrix();
    else {    
        switch(GetKey()) {
    	//    case Key_R://
    	//    case Key_L://
    	case Key_D://
    		CrtItem++;
    		if(CrtItem > Items) CrtItem = 0;
    		break;
    	case Key_U://
    		CrtItem--;
    		if(CrtItem > Items) CrtItem = Items;
    		break;
    	case Key_C://Key_C
    		if(CrtItem == Items) {
    			DoTheMatrix();  
    		} else {
    			lcd.drawTextT(144, 8 + CrtItem * 15, itoa(ROM_VAL[CrtItem], StrBuffer, 10), GREEN);//Draw value
    			ChangeValue();
    		}
    		break;
    	}
    }
	if(CrtItem != OldItem) {
		DrawMenu();
		OldItem = CrtItem;
	}
}

/////////////////////////////////////////////////////////
//Description: This is where the magic happens
//Parameters: None
//Returns: None
/////////////////////////////////////////////////////////
void DoTheMatrix(){
	//unsigned long wait = ROM_VAL[ROM_DStart] * 1000 + millis();
	//while (millis() < wait) {;} //We wait
	for(byte jj =0; jj < ROM_VAL[ROM_Cycles]; jj++)//repeat n times
	{
		for(ii = 0; ii < ROM_VAL[ROM_Cameras]; ii++)//activate each camera
		{
			doShutter(ii, 1);//Shutter ON
			delay(ROM_VAL[ROM_BDelay]);
			doShutter(ii, 0);//Shutter OFF
			delay(ROM_VAL[ROM_BDelay]);
		}
	}
}

/////////////////////////////////////////////////////////
//Description: This is where the magic happens
//Parameters: None
//Returns: None
/////////////////////////////////////////////////////////
void doShutter(byte x, byte y)
{  
	volatile uint8_t* thePort;
	switch (x>>3)// x>>3 is the same as x/8 only faster
	{   
	case 0:
		thePort = &PORTC;
		break;
	case 1:
		thePort = &PORTK;
		break;
	case 2:
		thePort = &PORTA;
		break;
	case 3:
		thePort = &PORTL;
		break;
	}
	if (y)
		*thePort |= (1<< (x&7));  // x&7 is the same as x%8 but faster
	else
		*thePort &= ~(1<< (x&7));   
}

/////////////////////////////////////////////////////////
//Description: Keys monitor function
//Parameters: None
//Returns: None
/////////////////////////////////////////////////////////
byte GetKey(){
	unsigned int KeyVal = analogRead(0);
	uint8_t btn = Key_None;
	if (KeyVal <= 720 && KeyVal > 700)
		btn = Key_C;
	else if (KeyVal <= 640 && KeyVal > 620)
		btn = Key_R;
	else if (KeyVal <= 550 && KeyVal > 530)
		btn = Key_U;
	else if (KeyVal <= 445 && KeyVal > 425)
		btn = Key_D;
	else if (KeyVal <= 277 && KeyVal > 257)
		btn = Key_L; 
	if(btn)	{
		while (analogRead(0) < 750) {delay(1);}//Serial.println(btn);}//debounce buttons
		if(ROM_VAL[ROM_BackLight] == 2) {
            BkLight_On;
            Bk_Light = millis() + 6000;
        }//BackLight On
	}
	return btn;
}

/////////////////////////////////////////////////////////
//Description: Set fast analog read
//in this mode 1000 calls of analog read takes 16ms (7 times faster than default)
//in default mode 1000 calls of analog read takes 111ms
//Parameters: None
//Returns: None
/////////////////////////////////////////////////////////
void InitFastAnalogRead(){
	//set prescale to 16
	ADCSRA |= (1<<ADPS2);
	ADCSRA &= ~(1<<ADPS1);
	ADCSRA &= ~(1<<ADPS0);
	// Set div factor to default of 128 for slower more accurate analog reads
	//ADCSRA |= (1<<ADPS2);
	//ADCSRA |= (1<<ADPS1);
	//ADCSRA |= (1<<ADPS0);
}

/////////////////////////////////////////////////////////
//Description:Loads from EEPROM settings values and store in to array
//Parameters: None
//Returns: None
/////////////////////////////////////////////////////////
void LoadEEPROM(){
	for(ii = 0; ii < 6; ii++)
		ROM_VAL[ii] = eeprom_read_byte((uint8_t*)(ii));
}

/////////////////////////////////////////////////////////
//Description: Set the limits of the settings parameters
//Parameters: None
//Returns: None
/////////////////////////////////////////////////////////
void SetMaxVal(){
	MAX_VAL[ROM_Cameras] = 30;
	MAX_VAL[ROM_Delay]   = 99;
	MAX_VAL[ROM_Cycles] = 99;
	MAX_VAL[ROM_DStart] = 99;
	MAX_VAL[ROM_BDelay] = 99;
	MAX_VAL[ROM_BackLight] = 2;
	MAX_VAL[ROM_RemTrigger] = 1;
}

/////////////////////////////////////////////////////////
//Description: Resets the values stored in EEPROM to the predefined ones
//Parameters: Profile to reset 0-2
//Returns: None
/////////////////////////////////////////////////////////
void ResetEEPROM(){
	eeprom_write_byte((uint8_t*)(0), 30);//ROM_Cameras
	eeprom_write_byte((uint8_t*)(1), 10);//ROM_Cameras
	eeprom_write_byte((uint8_t*)(2), 10);//ROM_Cycles
	eeprom_write_byte((uint8_t*)(3), 60);//ROM_DStart
	eeprom_write_byte((uint8_t*)(4), 2);//ROM_BDelay
	eeprom_write_byte((uint8_t*)(5), 0);//ROM_BackLight
    //eeprom_write_byte((uint8_t*)(6), 0);//ROM_BackLight
}

 

Comments powered by CComment