Light Analysis: Findings

I was recently annoyed by a "dimmable" LED bulb I bought, which had some nasty flickering to it at start-up. I also thought there was a weird flickering quality to it at stead-state, but wasn't sure. To test this, I built a quick little photocell sensor, hooked it up to an Arduino clone, and captured data in IPython. From there, a quick bit of signal analysis and we can see the truth!

While all this was hooked up, I took the opportunity to check out incandescent bulbs, a little flicker LED candle, and a few other things (the others were pretty dull, so they're not included here).

The capture hardware

The data was captured using a bog-standard "Arduino"*, using a quick voltage divider with a photocell. If you want to reproduce this, you'll want to experiment to get things just right for you. Photocells aren't the most precise things in the universe, and your lighting levels may vary from the range I wanted to measure.

For mine, I have:

+5V ---- [ photocell ] --- ADC0 ---- [ 1k ohm ] ---- GND

The code for the Arduino is pretty simple:

// -*- c++ -*-

void setup() {
  analogReference(INTERNAL);
  Serial.begin(9600);
  pinMode(13, OUTPUT);
}

void loop() {
  while(1) {
    digitalWrite(13, 1);
    uint8_t i = analogRead(0);
    Serial.write(i);
    digitalWrite(13, 0);
  }
}

If you want "precise" ** frequency measurements, you'll want to hook an oscilliscope up to D13 and measure your sampling interval. For my hardware, it was 962.1Hz, which I'm calling 962Hz for everything here. If your chip is clocked at 16MHz as well, it will likely run the same speed. This is convenient, as 9600 baud can't support much faster sample rates.

* Actually an Arduino clone I designed and used to sell as kits. ** "Precise" isn't quite the right word here, but, hey.

Data Acquisition Software

This is also pretty simple. There's another notebook adjacent to this one which contains the DAQ I used. But, here's the quick version:

RUN_NAME="foo"
import serial

s = serial.Serial("/dev/tty.usbserial-A4006Dx4", 9600)

N_SAMPLES=Fs*10

for i in range(200):
    s.read() # discard the head to discharge things

data = [ ord(s.read()) for _ in range(N_SAMPLES) ]

s.close()

np.save("%s.npy" % RUN_NAME, data)

figure(figsize=(20,5))
plot(data)
len(data)

It really is that simple, assuming you have the serial (aka 'pyserial') library installed.

Results

Preliminary code stuff

In [31]:
# Just ignore this block unless you want to see the gory details

%pylab inline
import scipy
import scipy.signal
import scipy.fftpack
import numpy as np

Fs=962 # Sample rate, Hz

def show_fft(Fs, y):
    """Show the FFT of a data sample, in the current figure"""
    # sample spacing
    T = 1.0 / Fs
    # Number of samplepoints
    N = len(y)

    w = scipy.signal.blackman(N)
    ywf = scipy.fftpack.fft(y*w)

    xf = np.linspace(0.0, 1.0/(2.0*T), N/2)

    axvline(60, color="#ffcccc")
    axvline(120, color="#ccccff")

    semilogy(xf[1:N/2], 2.0/N * np.abs(ywf[1:N/2]), '-r')
    xticks([30,60,120,180,240])
    xlim(0,250)

def show_fft_and_plot(Fs, y):
    """Convenience method for making an FFT/time-series plot pair"""
    figure(figsize=(20,5))

    subplot(1,2,1)
    show_fft(Fs,y)
    ylim(10**-3, 10**1.5)

    subplot(1,2,2)
    plot(y)
Populating the interactive namespace from numpy and matplotlib

Summary of Spectra

It seems appropriate to start off with the spectra from the data:

In [32]:
disps = [ ("incandescent.npy", "Incandescent bulb"), ("dimmable_led_steady.npy", "Dimmable LED, steady-state"), ("dimmable_led_steady_dimmed.npy", "Dimmable LED, stead-state, dimmed"), ("dimmable_led.npy", "Dimmable LED, annoying flickering state") ]
len(disps)

figure(figsize=(20,10))
for i, dpdn in enumerate(disps):
    d_path, d_name = dpdn
    subplot(len(disps)/2, 2, i+1)
    show_fft(Fs, np.load(d_path)[1000:1500])
    ylim(10**-4, 10**2)
    title(d_name)

A Proper Incandescent Bulb

Before we get into the weird stuff, let's start with a really boring incandescent bulb.

In [14]:
data = np.load("incandescent.npy")
figure(figsize=(20,5))
plot(data)
title("Raw intensity, incandescen bulb")

show_fft_and_plot(Fs, data[2000:2500])

The Annoying Dimmable LED Bulb, Steady-state

In [12]:
data = np.load("dimmable_led_steady.npy")
figure(figsize=(20,5))
plot(data)
title("Raw intensity, dimmable LED, steady-state")

show_fft_and_plot(Fs, data[2000:2500])
title("foo")
Out[12]:
<matplotlib.text.Text at 0x11229de10>

The Annoying Dimmable LED Bulb, Steady-state, Dimmed

In [16]:
data = np.load("dimmable_led_steady_dimmed.npy")
figure(figsize=(20,5))
plot(data)
title("Raw intensity, dimmable LED, steady-state (dimmed)")

show_fft_and_plot(Fs, data[2000:2500])
title("foo")
Out[16]:
<matplotlib.text.Text at 0x1129f5210>

The Annoying Dimmable LED Bulb, Flickering-state

In [11]:
data = np.load("dimmable_led.npy")
figure(figsize=(20,5))
plot(data)
title("Raw intensity, dimmable LED, annoying-state")

show_fft_and_plot(Fs, data[2000:2500])
title("foo")
Out[11]:
<matplotlib.text.Text at 0x11205d390>

The Crazy Flickering Candle LED

This is mostly for amusement, but: here's the crazy flickering candle LED

In [18]:
data = np.load("candle.npy")
figure(figsize=(20,5))
plot(data)
title("Raw intensity, candle LED")

show_fft_and_plot(Fs, data[10000:30000])
title("foo")
Out[18]:
<matplotlib.text.Text at 0x113070f50>