Introduction: DIY Portable Video Light

The key for good Photos and Videos is lighting. However lighting is generally not very expensive, more complex, mobile solutions with long battery runtime can get expensive quickly.

This guide will show you how to build a cheap universal LED light that is portable and versatile and the best thing, you can make it so it fits your needs best. The completed build has the following features:

  • Powered by rechargeable lithium battery (4x18650)
  • High quality light output (Ra 80+)
  • High brightness (around 2000lm)
  • Adjustable color temperature (4000K - 6000K)
  • Micro controller inside (allows for different programs)

How it works

Changing the color temperature works by mixing the light of two different LED strips with different light colors. The range depends on the LED strips you use. Color temperatures under 3300K are generally considered as warm white, between 3300 and 5000 it's called neutral and above 5000 it's called cold white. Daylight is situated around 6000K and a tungsten light bulb around 4000K. The basic idea is that you can enter a color value into your light and by mixing the two lights you can fit your lighting to every situation. Because the light is approximately settable to a certain color you cold even use the same value to set the white balance of your camera. Or you could set other lights accordingly.

Step 1: Get the Parts!

Notice: I just provide purchase links to show what i personally used. There may be better or cheaper options.

The LEDs

In many ways good parts are the key for this build. You need good quality LED strips in order to make your build suitable for Video and Photography work. Don't even consider using the cheapest LED strips available its very likely that your Photos and Videos will not only end up with a green or magenta tint but also skin tones will render horrible. Also high quality LEDs tend to be much more effective, wich means that you get more light for the energy you are using up. Therefore getting more out of the batteries you are carrying around. Especially for this portable build this is nice to have.

When buying the LEDs you should search for terms like "CRI", "Ra 80+", "Ra 90+". Sometimes such LEDs can be hard to come by by a reasonable price. Also consider buying the LED strips directly from china to save some money. You really should not spend more then 30€ per 5 meter roll.Try looking at places like eBay, amazon or aliexpress. Reasonable prized ones can be found here.

The case

The case should of course be big enough to house all the components and small enough so it can be easily carried around. Another important thing to consider when it comes to enclosure is heat! You want to choose a case that is able to dissipate all the heat your LEDs are generating. So a metal enclosure would be great because it saves us an extra heat sink. The area on one of the sides of your compartment should be big enough to house all of the LEDs you want (obviously). I used this one, if you are not from Germany try to find something similar on Ebay.

The batteries

When buying 18650 Lithium-Ion-Batteries it is important to look for good building quality. Don't make the mistake to buy the cheapest options on eBay. Those Chinese no name batteries won't make you happy because most of them won't hardly come over 900mAh also they are claiming absurd capacities like 9Ah or similar - It's just a lie. You should get some decent branded 18650s instead. Just look for common manufacturers like Sony, Panasonic, Samsung or similar. Those batteries will at least have a data sheet and hold their specifications. I recommend these batteries.

The BMS

A Battery Management System can be extremely helpful to make your lithium-powered project safe. They are very cheap and can be ordered for different configurations (1 cell, 2cells etc.). A 4 cell one can be found here

The display

The display is very cheap and only costs about 4€. It's a dot matrix OLED one with integrated logic so that it can be driven by I2C. Its size is roughly 1" diagonally. Just search for the keywords "1" display", "oled", "i2c" etc. An example can be found here.

The rotary encoder

The rotary encoder really makes the feel of the user interface. Using a rotary encoder with an integrated click button feels very intuitive and looks professional. Also its just one hole in the enclosure. A cheap one can be found here.

The DC/DC converter

You will need a step down DC/DC converter that can handle 3 Amps (at least 1.5). We will get 16.8 Volts peak from the batteries. And need to drop it down to 12V for the LEDs. Also you need a second voltage regulator for 5V. A 12V one can be found here. An example for a 5V can be found here. Maybe you can also make it into a power bank if you add a USB plug with decent current capabilities.

Step 2: The Electronics

Mainboard

The mainboard is kept very simple. An ATmega328 as its commonly used in Arduino UNOs is used for the heart of the project. Two BUZ11 mosfets are controlling the Lights via PWM. (It's fast PWM so you won't see it in video.)

The schematic

  • VREG: The 5V voltage regulator. Pin 1 is the input pin 2 GND and pin 3 5V output.
  • ICSP: The In-Circuit-System-Programmer header. It's used to program the atmega328 with an external programmer. You could use your Arduino as a programmer by using the ArduinoISP sketch. However this caused problems in my case. I decided to build a usbtiny programmer using an attiny85. You could also just use your Arduino UNO as a programmer. Just remember that you don't have a crystal, so use the 8Mhz configuration.
  • TMP1, TMP2, TMP3: Place for 3 NTC 10k Thermistors for temperature monitoring.
  • PAD7, PAD2: Voltage sense. Hook up PAD7 to the negative terminal of your battery pack and PAD2 to the positive. This will meassure the battery voltage. But because we are using PWM the readings fluctuate a lot. So it's not very accurate.
  • LEDS: Hook up the plus pole to 1 and 4 and the minus poles to 2 and 3. If the warm/cold logic is flipped you can just reverse the plug.
  • JP3: 1: 5V; 2: GND; 3: Button1; 4: DT; 5: CLK; 6: SDA; 7: SCL.
  • The button to change modes is missing in the schematic because it was added later on!!! Dont forget to add it. It has to be added on Arduino pin 7 wich corresponds to the 13th hardware pin. And the other terminal of the button needs to go to ground. The Arduino sketch uses an internal pullup resistor, so you won't need a pull down. Depending on the button you are using, consider using a small capacitor in parrallel to debounce the signal.

Battery management

There are cheap finished battery protection circuits on eBay and on aliexpress. You can get them for 3 or 4 cells. Be aware that its important wich version you buy, because also they might look the same and have the same circuit board some resistors and capacitors are rearranged for each configuration. Once you have such a BMS board you are pretty much safe. Just hook all the wires up to the battery including the balancing leads. Now the BMS will care about short circuit protection, over discharge, over charge, charging rate etc. You will get a battery pack with two leads coming out of it being the in- and outputs of the BMS at the same time. In order to charge it, just apply something under 25V and it will safely charge the batteries. To discharge just hook your source up to it. It's very easy and safe.

However if you care about extra security you can get a cheap NTC Thermistor (about 0,2€) and let your micro controller check the temperature. If the batteries are overheating you are able to shut down the system, just in case.

Voltage regulation

For voltage regulation, just get a drop down DC-DC converter off eBay that can handle 3A, so you have enough headroom and it won't run hot. Set it to 12V. If your case is running very hot every time, you should consider dropping the Voltage to maybe 11,5V or something.

For the 5V logic you can either use a small DC-DC converter as well or just use the normal power regulator, but keep in mind that you are throwing away energy with a normal regulator, the more current you are drawing the more wasteful it gets.

The LEDs

Use the self adhesive surface of the LED strips to stick them to the front of the device. Drill a hole to fade the cables thru and seal it with epoxy or hot glue.

The mainboard itself

For the mainboard i provide you with a pdf-schematic and eagle files to etch your own circuit board. Eagle is a free software and can be downloaded here. But its also very possible to just use a breadboard.

Putting everything together

If you have chosen a small enclosure to begin with this will be the hardest part. Generally speaking, you want a tight fit so nothing is loose or flies around in the case. Also it's a good idea to insulate sensitive parts against the case (if you are using a metal one). Just secure everything with a combination of hot glue, screws and tape.

Step 3: The Code

Configuring Arduino IDE

You can use the atmega328 in an 16Mhz or 8Mhz configuration. Read here how to set the micro controller to 8Mhz without using a crystal.

What the code does:

Normal mode

  • Adjust light color from 4000K - 6000K
  • Adjust brightness from 0% - 100% (not really full power)

In this mode you won't be able to archive full brightness because 4000K will be LED strip 1 turned on full and 6000K will be LED strip 2 turned on full. The middle will be 50/50. So of possible "200%" you are just getting 100%.

Insane mode

  • Adjust brightness from 0% - 100% (really full power)
  • Adjust light color but not with constant brightness from 4000K - 6000K

This mode is specially made to get the full brightness out of the lamp.

Portion mode (w.i.p.)

for photography work in long exposures. A fixed light amount will be settable and a time in wich the light will dispense. According to this a special brightness will result and the Light will turn off after the desired amount of light is emitted. Also the display will turn off so it won't be visible on camera.

Halogen mode

Just one brightness control. When the light is darker it will be warmer, when it gets bright the light will get colder. Like a real halogen bulb.

Things that may be added in the future:

  • Flicker mode, the lamp will flicker like a broken fluorescent light when its turned on. Maybe a little bit of flickering during continuous light output to emulate one broken light in an array.
  • Torch mode, the lamp will give its best to emulate the light of a torch
  • Strobe mode, with settable on and off time (kind of pointless)
  • Morse mode, the light will just light up when you press the button.

The Arduino Code:

get the latest release or checkout the code and contribute at github.

Stable version but not the latest:

you need to replace the brackets in the #include statement with < and >.

For this code to run you need the following libraries installed:

  • U8glib by oliver
  • PinChangeInterrupt by NicoHood
  • Encoder by Paul Stoffregen
  • Wire (built-in)
//Video Light 1.2<br>//Code by idexe
#include (U8glib.h)
#include (PinChangeInterrupt)
#include (avr/sleep.h)
#include (Wire.h)
#include (Encoder.h)
//Input and Output
#define BUTTON1 8
#define BUTTON_ON 7
#define DT 2
#define CLK 3
#define COLD 9
#define WARM 10
#define OLED_RESET 4
//U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);// for u8g2lib
class Button
{
  public:
    Button(uint8_t pin);
    void begin(bool pull);
    bool pressed();
    bool hold(int timer);
    bool status();
  private:
    bool _oldvalue;
    uint8_t _pin;
    unsigned long _time;
};
class Light
{
  public:
    Light(int pinWarm, int pinCold);
    void begin();
    void brightness(int Int);
    void color(int color);
    void insane(int Int);
  private:
    int _warm;
    int _cold;
    int _int;
    int _color;
    int _superInt;
};
class Screen
{
  public:
    Screen();
    void reset();
    void standby();
    void setBigText(String BigText, bool def);
    void setBattery(int Bat, bool def);
    void setBottom(String Bottom, bool def);
    void setCaption(String Caption, bool def);
    void update();
    void renderChanges();
  private:
    bool _valueChange;
    String _BigText;
    bool _showBigText;
    int _Bat;
    bool _showBat;
    String _Bottom;
    bool _showBottom;
    String _Caption;
    bool _showCaption;
};
Encoder knob(DT, CLK);
class RotEnc
{
  public:
    RotEnc();
    bool check(int lbound, int rbound);
    int getPos();
    void setPos(int lbound, int rbound, int pos);
  private:
    int _pos;
};
class Thermistor
{
  public:
    Thermistor(int analogPin);
    double read(int interval);
  private:
    int _analogPin;
    unsigned long _Time;
    double _Temp;
};
class Voltage
{
  public:
    Voltage(int analogPin);
    double read(int interval);
  private:
    int _analogPin;
    unsigned long _Time;
    double _Volt;
};
RotEnc rotenc;
U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST);
Screen screen;
Button button1(BUTTON1);
Button buttonOn(BUTTON_ON);
Light light(WARM, COLD);
Thermistor therm1(0);
Thermistor therm2(1);
//Thermistor therm3(2);
Voltage volt1(3);
void setup() {
  // put your setup code here, to run once:
  TCCR1B = (TCCR1B & 0b11111000) | 0x01;
  light.begin();
  Serial.begin(9600);
  button1.begin(true);
  buttonOn.begin(true);
}
enum prog_state {STANDBY, ON};
prog_state Prog = ON;
enum state {TMP, INT, SUPER_INT};
state State = TMP;
bool superint = false;
bool stateChange = true;
bool progChange = false;
long Tmp = 5000;
long Int = 50;
long Super_Int = 50;
void loop() {
  switch (Prog) {
    case ON: {
        switch (State) {
          case TMP: {//Farbtemperatur einstellen
              if (stateChange) {//Beim ersten ausführen
                screen.setBigText(String(Tmp) + "K", true);
                screen.setCaption("Temperatur", true);
                screen.update();
                rotenc.setPos(-10, 10, (Tmp - 5000) / 100);
                stateChange = false;
              }
              if (rotenc.check(-10, 10)) { //Wenn jemand dreht
                Tmp = rotenc.getPos() * 100 + 5000;
                screen.setBigText(String(Tmp) + "K", true);
                light.color((Tmp - 4000) / 100 * 255 / 20);
              }
              if (button1.pressed()) {//Wenn jemand drückt
                if (superint) {
                  State = SUPER_INT;
                } else {
                  State = INT;
                }
                stateChange = true;
              }
              break;
            }
          case INT: {//Helligkeit einstellen
              if (stateChange) {//Beim ersten ausführen
                screen.setBigText(String(Int) + "%", true);
                screen.setCaption("Helligkeit", true);
                screen.update();
                rotenc.setPos(-10, 10, (Int - 50) / 5);
                stateChange = false;
              }
              if (rotenc.check(-10, 10)) { //Wenn jemand dreht
                Int = rotenc.getPos() * 5 + 50;
                screen.setBigText(String(Int) + "%", true);
                light.brightness(Int / 5 * 255 / 20);
              }
              if (button1.pressed()) {//Wenn jemand drückt
                State = TMP;
                stateChange = true;
              }
              if (button1.hold(1000)) {
                State = SUPER_INT;
                superint = true;
                stateChange = true;
              }
              break;
            }
          case SUPER_INT: {
              if (stateChange) {//Beim ersten ausführen
                screen.setBigText(String(Super_Int) + "%", true);
                screen.setCaption("Insane", true);
                screen.update();
                rotenc.setPos(-10, 10, (Super_Int - 50) / 5);
                stateChange = false;
              }
              if (rotenc.check(-10, 10)) { //Wenn jemand dreht
                Super_Int = rotenc.getPos() * 5 + 50;
                screen.setBigText(String(Super_Int) + "%", true);
                light.insane(Super_Int / 5 * 255 / 20);
              }
              if (button1.pressed()) {//Wenn jemand drückt
                State = TMP;
                stateChange = true;
              }
              if (button1.hold(1000)) {
                State = INT;
                superint = false;
                stateChange = true;
              }
              break;
            }
        }
        //Allways running Code, like screen rendering, temperature and battery monitoring.
        //2 Temp sensor configuration
        char voltstr[5];
        dtostrf(volt1.read(1000) * 16.8 / 810,4,1,voltstr);
        screen.setBottom("LED:" + String(int(therm1.read(1000))) + (char)176 + "C BAT:" + String(int(therm2.read(1000))) + (char)176 + "C " + String(voltstr) + "V", true);
        //3 Temp sensor configuration
        //screen.setBottom("LED:" + String(int(therm1.read(1000))) + (char)176 + "C BAT:" + String(int(therm2.read(1000))) + (char)176 + "C " + String(int(therm3.read(1000))) + (char)176 + "C", true);
        screen.renderChanges();
        if (buttonOn.pressed() && millis() > 1000) {
          //Prog = STANDBY;
          //progChange = true;
          if (superint) {
            superint = false;
            State = INT;
          } else {
            superint = true;
            State = SUPER_INT;
          }
          stateChange = true;
        }
        /*
        if (buttonOn.hold(1000)) {
          Prog = STANDBY;
          progChange = true;
        }
        */
        break;
      }
    case STANDBY: {
        if (millis() <= 1500) {
          Prog = ON;
          stateChange = true;
          break;
        }
        if (progChange) {
          screen.standby();
          screen.renderChanges();
          progChange = false;
        } else {
          if (buttonOn.hold(2000)) {
            Prog = ON;
            stateChange = true;
          }
          //delay(1000);
          //sleepNow();
        }
      }
  }
}
//Button-methods
Button::Button(uint8_t pin) {
  _pin = pin;
  _oldvalue = true;
}
void Button::begin(bool pull) {
  if (pull) {
    pinMode(_pin, INPUT_PULLUP);
  } else {
    pinMode(_pin, INPUT);
  }
}
bool Button::pressed() {
  bool value = digitalRead(_pin);
  if (_oldvalue != value && value == false) {
    _oldvalue = value;
    return true;
  } else {
    _oldvalue = value;
    return false;
  }
}
bool Button::status() {
  return !digitalRead(_pin);
}
bool Button::hold(int timer) {
  if (pressed()) {
    _time = millis();
  }
  if (status()) {
    if (millis() - _time >= timer && millis() - _time <= timer + 100) {
      return true;
    }
  }
  return false;
}
//Light-methods
Light::Light(int pinCold, int pinWarm) {
  _cold = pinCold;
  _warm = pinWarm;
  _int = 128;
  _color = 128;
}
void Light::begin() {
  pinMode(_cold, OUTPUT);
  pinMode(_warm, OUTPUT);
  analogWrite(_warm, _int * _color / 255);
  analogWrite(_cold, _int * (255 - _color) / 255);
}
void Light::color(int color) {
  //Color mix between 0 (cold) and 255 (warm)
  _color = color;
  analogWrite(_warm, _int * _color / 255);
  analogWrite(_cold, _int * (255 - _color) / 255);
}
void Light::brightness(int Int) {
  //Brightness between 0 (dark) and 255 (bright)
  _int = Int;
  analogWrite(_warm, _int * _color / 255);
  analogWrite(_cold, _int * (255 - _color) / 255);
}
void Light::insane(int Int) {
  //Brightness between 0 (dark) and 510 (very bright)
  _superInt = Int;
  int warm = 2* _superInt * _color / 255;
  if (warm > 255) {
    warm = 255;
  }
  int cold = 2* _superInt * (255 - _color) / 255;
  if (cold > 255) {
    cold = 255;
  }
  analogWrite(_warm, warm);
  analogWrite(_cold, cold);
  
}
//Screen-methods
Screen::Screen() {
#ifndef u8glib_h
#define u8glib_h
#include 
#endif
  //U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);// for u8g2lib
  //U8GLIB_SSD1306_128X64 u8g(U8G_I2C_OPT_DEV_0 | U8G_I2C_OPT_NO_ACK | U8G_I2C_OPT_FAST);
  _valueChange = true;
  _showBigText = false;
  _showBat = false;
  _showBottom = false;
  _showCaption = false;
}
void Screen::reset() {
  _valueChange = false;
}
void Screen::standby() {
  _valueChange = true;
  _showBigText = false;
  _showBat = false;
  _showBottom = false;
  _showCaption = false;
}
void Screen::setBigText(String BigText, bool def) {
  _showBigText = def;
  if (_BigText != BigText) {
    _BigText = BigText;
    _valueChange = true;
  }
}
void Screen::setBattery(int Bat, bool def) {
  _showBat = def;
  if (_Bat != Bat) {
    _Bat = Bat;
    _valueChange = true;
  }
}
void Screen::setBottom(String Bottom, bool def) {
  _showBottom = def;
  if (_Bottom != Bottom) {
    _Bottom = Bottom;
    _valueChange = true;
  }
}
void Screen::setCaption(String Caption, bool def) {
  _showCaption = def;
  if (_Caption != Caption) {
    _Caption = Caption;
    _valueChange = true;
  }
}
void Screen::update() {
  _valueChange = true;
}
void Screen::renderChanges() {
  if (_valueChange == true) {
    u8g.firstPage();
    do {
      //BigText
      if (_showBigText) {
        u8g.setFont(u8g_font_helvB24r);
        u8g.setFontPosCenter();
        char BigText[_BigText.length() + 1];
        _BigText.toCharArray(BigText, _BigText.length() + 1);
        u8g.setPrintPos((128 - u8g.getStrWidth(BigText)) / 2, 35);
        u8g.print(_BigText);
      }
      //Battery
      //Bottom
      if (_showBottom) {
        u8g.setFont(u8g_font_helvB08);
        u8g.setFontPosBaseline();
        u8g.setPrintPos(0, 64);
        u8g.print(_Bottom);
      }
      //Caption
      if (_showCaption) {
        u8g.setFont(u8g_font_helvB08);
        u8g.setFontPosTop();
        char Caption[_Caption.length() + 1];
        _Caption.toCharArray(Caption, _Caption.length() + 1);
        u8g.setPrintPos((128 - u8g.getStrWidth(Caption)) / 2, 0);
        u8g.print(_Caption);
      }
    } while ( u8g.nextPage() );
    reset();
  }
}
//RotEnc-Methods
RotEnc::RotEnc() {
  _pos = -999;
}
bool RotEnc::check(int lbound, int rbound) {
  int pos = knob.read() / 4;
  if (pos != _pos) {
    if (pos < lbound) {
      pos = lbound;
      knob.write(pos * 4);
    }
    if (pos > rbound) {
      pos = rbound;
      knob.write(pos * 4);
    }
    if (pos != _pos) {
      _pos = pos;
      return true;
    } else {
      return false;
    }
  }
}
int RotEnc::getPos() {
  return _pos;
}
void RotEnc::setPos(int lbound, int rbound, int pos) {
  if (pos < lbound) {
    pos = lbound;
  }
  if (pos > rbound) {
    pos = rbound;
  }
  _pos = pos;
  knob.write(_pos * 4);
}
//Thermistor-Methods
Thermistor::Thermistor(int analogPin) {
  _analogPin = analogPin;
  _Time = 0;
}
double Thermistor::read(int interval) {
  unsigned long Time = millis();
  if (_Time + interval < Time) {
    _Temp = analogRead(_analogPin);
    _Temp = log(10000.0 * ((1024.0 / _Temp - 1)));
    _Temp = 1 / (0.001129148 + (0.000234125 + (0.0000000876741 * _Temp * _Temp )) * _Temp );
    _Temp = _Temp - 273.15;            // Convert Kelvin to Celcius
    _Time = Time;
    return _Temp;
  }
  return _Temp;
}
//Voltage-Methods
Voltage::Voltage(int analogPin) {
  _analogPin = analogPin;
  _Time = 0;
}
double Voltage::read(int interval) {
  unsigned long Time = millis();
  if (_Time + interval < Time) {
    _Volt = analogRead(_analogPin);
    _Time = Time;
    return _Volt;
  }
  return _Volt;
}
void sleepNow() {
  set_sleep_mode(SLEEP_MODE_PWR_DOWN);
  sleep_enable();
  //attachInterrupt(0,wakeUpNow,LOW);
  attachPinChangeInterrupt(digitalPinToPCINT(BUTTON_ON), wakeUpNow, FALLING);
  sleep_mode();
  sleep_disable();
  //detachInterrupt(0);
  detachPinChangeInterrupt(digitalPinToPCINT(BUTTON_ON));
}
void wakeUpNow() {
<pre>}

Step 4: Finished!

Thats it. Now you now everything to build your own portable video light with endless possibilities. It's very bright with its 2000lm, energy efficient, intelligent, customizable and relatively cheap. You can order all of the parts for under 100€ and you will have plenty of LEDs left if you order 5m rolls of each color.

Sample video and pictures are coming soon.

LED Contest

Participated in the
LED Contest

Circuits Contest 2016

Participated in the
Circuits Contest 2016