<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>drone colony</title>
	<atom:link href="http://dronecolony.com/feed/" rel="self" type="application/rss+xml" />
	<link>http://dronecolony.com</link>
	<description></description>
	<lastBuildDate>Fri, 17 May 2013 12:39:12 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.5.1</generator>
		<item>
		<title>Park Path</title>
		<link>http://dronecolony.com/2013/05/16/park-path/</link>
		<comments>http://dronecolony.com/2013/05/16/park-path/#comments</comments>
		<pubDate>Fri, 17 May 2013 02:29:58 +0000</pubDate>
		<dc:creator>Church</dc:creator>
				<category><![CDATA[Black and White]]></category>
		<category><![CDATA[Infrared]]></category>
		<category><![CDATA[K7]]></category>
		<category><![CDATA[Photography]]></category>

		<guid isPermaLink="false">http://dronecolony.com/?p=234</guid>
		<description><![CDATA[Pentax K7 &#8211; 590nM IR &#8211; FA 50/1.4]]></description>
				<content:encoded><![CDATA[<p><img class="aligncenter size-full wp-image-237" alt="arbtrail1-sm" src="http://dronecolony.com/wp-content/uploads/2013/05/arbtrail1-sm.png" width="432" height="650" /></p>
<p style="text-align: center;">Pentax K7 &#8211; 590nM IR &#8211; FA 50/1.4</p>
]]></content:encoded>
			<wfw:commentRss>http://dronecolony.com/2013/05/16/park-path/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Customized QDial with QSS Support</title>
		<link>http://dronecolony.com/2012/12/11/customized-qdial-with-qss-support/</link>
		<comments>http://dronecolony.com/2012/12/11/customized-qdial-with-qss-support/#comments</comments>
		<pubDate>Tue, 11 Dec 2012 18:05:18 +0000</pubDate>
		<dc:creator>Church</dc:creator>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Projects]]></category>

		<guid isPermaLink="false">http://dronecolony.com/?p=229</guid>
		<description><![CDATA[Googling &#8220;customize QDial&#8221; will generate a lot of results telling you to sub-class QDial and create your own paintEvent.  Of course, there are precious few examples floating around of exactly how to do that, namely rotating and scaling images.  As I had to create a customized QDial recently, I thought I&#8217;d share the class I [...]]]></description>
				<content:encoded><![CDATA[<p><a  href="http://dronecolony.com/wp-content/uploads/2012/12/Screen-Shot-2012-12-11-at-12.16.24-PM.png" class="thickbox no_icon" rel="gallery-229" title="custom dial"><img class="alignright size-full wp-image-232" title="custom dial" alt="custom dial" src="http://dronecolony.com/wp-content/uploads/2012/12/Screen-Shot-2012-12-11-at-12.16.24-PM.png" width="150" /></a>Googling &#8220;customize QDial&#8221; will generate a lot of results telling you to sub-class QDial and create your own paintEvent.  Of course, there are precious few examples floating around of exactly how to do that, namely rotating and scaling images.  As I had to create a customized QDial recently, I thought I&#8217;d share the class I wrote for doing it, which allows each dial to be skinned via QSS, making it play nicely with Qt Designer (hint: promote your QDial to this class).  The QDial to the right here serves as an excellent example of what you can do.</p>
<p>So, without further ado, the code is behind the cut:</p>
<p><span id="more-229"></span></p>
<p>Note: I wrote this code as part of the <span style="text-decoration: underline;"><a  href="https://github.com/DynamicPerception/Graffik/" target="_blank">Graffik</a></span> application.  The latest version of this code can always be found on Github, here: <span style="text-decoration: underline;"><a  href="https://github.com/DynamicPerception/Graffik/blob/master/Graffik/gui/core/Widgets/skinneddial.h" target="_blank">skinneddial.h</a></span>, <span style="text-decoration: underline;"><a  href="https://github.com/DynamicPerception/Graffik/blob/master/Graffik/gui/core/Widgets/skinneddial.cpp" target="_blank">skinneddial.cpp</a></span></p>
<p>skinneddial.h:</p>
<pre class="brush: plain; title: ; notranslate">
/*

Copyright (c) 2012 Dynamic Perception

  This software is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This software is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this software.  If not, see &lt;http://www.gnu.org/licenses/&gt;.

  */

#ifndef SKINNEDDIAL_H
#define SKINNEDDIAL_H

#include &lt;QDial&gt;
#include &lt;QPixmap&gt;
#include &lt;QPainter&gt;
#include &lt;QPaintEvent&gt;
#include &lt;QTransform&gt;

/** Custom-Skinned QDial

  This class allows one to create a custom-skinned QDial by supplying your own image
  for the background and needle, both through Qt Style Sheets and through normal C++
  usage.

  Example Usage:
  @code
  #include &quot;skinneddial.h&quot;

  ...

  SkinnedDial* myDial = new SkinnedDial();
  @endcode

  All look and feel is provided through images, you cannot have hash marks drawn programmatically, if you
  want them, put them in your image.

  @section dialvalues Acceptable QDial Limits

  The SkinnedDial class makes an assumption that the center of the dial is at the value of 0, and
  that negative values are on the left side of the dial, and the positive values are on the right
  side.  SkinnedDial requires that the minimum be &lt; 0 and the maximum be &gt; 0 and that there be
  an equivalent distance on both side of the dial. For example:

  @code
  myDial-&gt;setMaximum(100);
  myDial-&gt;setMinimum(-100);
  @endcode

  You must also specify where the maximum travel of the dial is, in degrees.
  See the \ref qsscustom &quot;Customing the look and feel via QSS&quot; section for more information.

  @section qsscustom Customizing the look and feel via QSS

  You can control the look and feel of the SkinnedDial via Qt Stylesheets by setting values for the custom
  properties associated with the skinned dial.

  It is expected that the backgroundImage and needleImage are of the exact same dimensions, and the needleImage
  is transparent everywhere the needle isn't.

  The maxAngle property determines the extent that the needle may travel on either side of the circle.  140' would
  imply that the needle may not move more than 140' either clockwise or counter-clockwise.

  @code
  SkinnedDial {
    qproperty-backgroundImage: url(:dial-back.png);
    qproperty-needleImage: url(:dial-needle.png);
    qproperty-maxAngle: 140;
   }
   @endcode

   @section scaling Scaling of Images

   Your images are automatically scaled relative to the container, up and to the full size of the input
   image.  The input image will not be scaled larger than its original size.

   @author
   C. A. Church

   Copyright &amp;copy; 2012 Dynamic Perception LLC
   */

class SkinnedDial : public QDial
{
    Q_OBJECT
        /** Background Image Property

          Sets the background image for the dial (the dial its self, and any surrounding decoration).
          */
    Q_PROPERTY(QPixmap backgroundImage READ backgroundImage WRITE setBackgroundImage DESIGNABLE true)
        /** Needle Image Property

          Sets the needle (pointer) image, to be drawn over the dial. Must be the same width and height
          as the background image, and transparent everywhere the needle isn't.
          */
    Q_PROPERTY(QPixmap needleImage READ needleImage WRITE setNeedleImage DESIGNABLE true)
        /** Maximum Rotation Angle Property

          Sets the maximum angle at which the needle may be rotated on either side of the dial
          */
    Q_PROPERTY(float maxAngle READ maxAngle WRITE setMaxAngle DESIGNABLE true)

public:
    explicit SkinnedDial(QWidget *parent = 0);
    SkinnedDial(QPixmap* c_back, QPixmap* c_needle, float c_angle, QWidget *parent = 0);

    ~SkinnedDial();

    QPixmap backgroundImage();
    void setBackgroundImage(QPixmap p_img);

    QPixmap needleImage();
    void setNeedleImage(QPixmap p_img);

    float maxAngle();
    void setMaxAngle(float p_angle);

protected:

    void paintEvent(QPaintEvent *pe);

signals:

public slots:

private:

    QPixmap* m_background;
    QPixmap* m_needle;
    QPainter::RenderHint m_paintFlags;
    float m_maxDeg;

    QPixmap* m_cacheBackground;
    QPixmap* m_cacheNeedle;

    int m_cacheVal;
    int m_cacheWidth;
    int m_cacheHeight;

    QPixmap _rotatePix(QPixmap* p_pix, float p_deg, bool p_dir);

};

#endif // SKINNEDDIAL_H
</pre>
<p>skinneddial.cpp:</p>
<pre class="brush: plain; title: ; notranslate">
#include &quot;skinneddial.h&quot;

 /** Constructor

   Create a new instance of SkinnedDial, with no images pre-defined.

   You must set the images and values required for display via the property setters before use.

   @param parent
   The parent widget
   */
SkinnedDial::SkinnedDial(QWidget *parent) : QDial(parent) {

    m_background      = new QPixmap;
    m_needle          = new QPixmap;
    m_maxDeg          = 180.0;
    m_paintFlags      = QPainter::RenderHint(QPainter::Antialiasing | QPainter::SmoothPixmapTransform || QPainter::HighQualityAntialiasing);
    m_cacheBackground = new QPixmap;
    m_cacheNeedle     = new QPixmap;
    m_cacheVal        = 0;
    m_cacheWidth      = 0;
    m_cacheHeight     = 0;

}

 /** Constructor

   Create a new instance of SkinnedDial, specifying all images and maximum angle.

   @param c_back
   The backgroundImage for the dial

   @param c_needle
   The needleImage for the dial

   @param c_angle
   The maxAngle for the dial

   @param parent
   The parent widget
   */
SkinnedDial::SkinnedDial(QPixmap* c_back, QPixmap* c_needle, float c_angle, QWidget *parent) : QDial(parent) {

    *m_background      = *c_back;
    *m_needle          = *c_needle;
    m_maxDeg           = c_angle;
    m_paintFlags       = QPainter::RenderHint(QPainter::Antialiasing | QPainter::SmoothPixmapTransform || QPainter::HighQualityAntialiasing);
    *m_cacheBackground = *m_background;
    *m_cacheNeedle     = *m_needle;
    m_cacheVal         = 0;
    m_cacheWidth       = 0;
    m_cacheHeight      = 0;
}

SkinnedDial::~SkinnedDial() {
    delete m_background;
    delete m_needle;
    delete m_cacheBackground;
    delete m_cacheNeedle;
}

void SkinnedDial::setBackgroundImage(QPixmap p_img) {
    *m_background = p_img;
}

QPixmap SkinnedDial::backgroundImage() {
    return *m_background;
}

void SkinnedDial::setNeedleImage(QPixmap p_img) {
    *m_needle = p_img;
}

QPixmap SkinnedDial::needleImage() {
    return *m_needle;
}

float SkinnedDial::maxAngle() {
    return m_maxDeg;
}

void SkinnedDial::setMaxAngle(float p_angle) {
    m_maxDeg = p_angle;
}

/** Overloaded paintEvent
  */

void SkinnedDial::paintEvent(QPaintEvent *pe) {

    QPainter painter(this);
    QRect eventRect = pe-&gt;rect();

    bool cacheHit = true;

        // scale pixmap so that it fits within the
        // boundaries of the event

    int height = eventRect.height();
    int  width = eventRect.width();

        // only perform scaling again if our event rectangle has changed
    if( height != m_cacheHeight || width != m_cacheWidth ) {
        if( width &gt;= height ) {
            if( height &lt;= m_background-&gt;height() ) {
                *m_cacheBackground = m_background-&gt;scaledToHeight(height, Qt::SmoothTransformation);
                *m_cacheNeedle     = m_needle-&gt;scaledToHeight(height, Qt::SmoothTransformation);
            }
        }
        else {
            if( width &lt;= m_background-&gt;width() ) {
                *m_cacheBackground = m_background-&gt;scaledToWidth(width, Qt::SmoothTransformation);
                *m_cacheNeedle     = m_needle-&gt;scaledToWidth(width, Qt::SmoothTransformation);
            }
        }
        m_cacheHeight = height;
        m_cacheWidth  = width;
        cacheHit      = false;
    }

     // find top-left corner to start placing the pixmap for the background,
     // centering it in the total event area

    int leftPoint = (width - m_cacheBackground-&gt;width()) / 2;
    int topPoint  = (height - m_cacheBackground-&gt;height()) / 2;

    QPoint startPix(leftPoint, topPoint);

    painter.setRenderHints(m_paintFlags);
    painter.drawPixmap(startPix, *m_cacheBackground);

        // rotate the needle image and display it

    int curVal = this-&gt;value();
    QPixmap rotNeedle = *m_cacheNeedle;

        // only re-process the needle image if the position
        // changes, or we had a size cache miss

    if( curVal != m_cacheVal || cacheHit == false ) {

       if( curVal &lt; 0 ) {
            float pct = (float) abs(curVal) / (float) abs(this-&gt;minimum());
            rotNeedle = _rotatePix(&amp;rotNeedle, m_maxDeg * pct, false);
        }
       else if( curVal &gt; 0 ) {
            float pct =(float)  abs(curVal) / (float) abs(this-&gt;maximum());
            rotNeedle = _rotatePix(&amp;rotNeedle, m_maxDeg * pct, true);
        }

        m_cacheVal = curVal;
    }

    painter.drawPixmap(startPix, rotNeedle);

}

QPixmap SkinnedDial::_rotatePix(QPixmap *p_pix, float p_deg, bool p_dir) {

        // all rotation is CCW, so calculate for the &quot;right&quot; side of the knob
    if( p_dir == false )
        p_deg = 360.0 - p_deg;

        // perform rotation, transforming around the center of the
        // image

    QTransform trans;
    trans.translate(p_pix-&gt;width()/2.0 , p_pix-&gt;height()/2.0);
    trans.rotate(p_deg);
    trans.translate(-p_pix-&gt;width()/2.0 , -p_pix-&gt;height()/2.0);
    QPixmap outPix = p_pix-&gt;transformed(trans, Qt::SmoothTransformation);

        // re-crop to original size
    int xOffset = (outPix.width() - p_pix-&gt;width()) / 2;
    int yOffset = (outPix.height() - p_pix-&gt;height()) / 2;
    outPix = outPix.copy(xOffset, yOffset, p_pix-&gt;width(), p_pix-&gt;height());

    return outPix;

}
</pre>
]]></content:encoded>
			<wfw:commentRss>http://dronecolony.com/2012/12/11/customized-qdial-with-qss-support/feed/</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>An Intelligent, Auxiliary Controller for Offroad Vehicles</title>
		<link>http://dronecolony.com/2012/07/14/auxplus-an-intelligent-auxiliary-controller-for-offroad-vehicles/</link>
		<comments>http://dronecolony.com/2012/07/14/auxplus-an-intelligent-auxiliary-controller-for-offroad-vehicles/#comments</comments>
		<pubDate>Sat, 14 Jul 2012 15:18:12 +0000</pubDate>
		<dc:creator>Church</dc:creator>
				<category><![CDATA[Jeep Accessories]]></category>
		<category><![CDATA[Projects]]></category>

		<guid isPermaLink="false">http://droneone.wpengine.com/?p=221</guid>
		<description><![CDATA[Control up to five auxiliary accessories in the vehicle (up to 15A each, or 45A total) using standard, replaceable Bosch-style relays.  Control via remote control interface (Linux) over RS485 or via standard switches.  Atmega328p processor and custom bootloader makes it possible to program via Arduino IDE over RS-485 bus.  Uses the MoCoBus libraries I developed [...]]]></description>
				<content:encoded><![CDATA[<p>Control up to five auxiliary accessories in the vehicle (up to 15A each, or 45A total) using standard, replaceable Bosch-style relays.  Control via remote control interface (Linux) over RS485 or via standard switches.  Atmega328p processor and custom bootloader makes it possible to program via Arduino IDE over RS-485 bus.  Uses the MoCoBus libraries I developed for Dynamic Perception for rapid protocol development.  GUI written in Qt.  Will release all files and code open-source when done.</p>
<p>Additional Features:</p>
<p><a  href="http://droneone.wpengine.com/wp-content/uploads/2012/07/apgui.png" class="thickbox no_icon" rel="gallery-221" title="GUI Starting Point, Base Display"><img class=" wp-image-223 alignright" title="GUI Starting Point, Base Display" src="http://droneone.wpengine.com/wp-content/uploads/2012/07/apgui.png" alt="" width="319" height="147" /></a><a  href="http://droneone.wpengine.com/wp-content/uploads/2012/07/auxlux2.png" class="thickbox no_icon" rel="gallery-221" title="3D AuxPlus Render"><img class=" wp-image-222 alignright" title="3D AuxPlus Render" src="http://droneone.wpengine.com/wp-content/uploads/2012/07/auxlux2.png" alt="" width="336" height="148" /></a></p>
<ul>
<li>Battery Voltage Monitoring</li>
<li>Self-Regulating Temperature with DC fan control</li>
<li>Optical isolation between relays and CPU</li>
<li>Fail-safe aux control with mechanical switches</li>
<li>All relays in off-state during power-up</li>
</ul>
]]></content:encoded>
			<wfw:commentRss>http://dronecolony.com/2012/07/14/auxplus-an-intelligent-auxiliary-controller-for-offroad-vehicles/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Automating Qt Help Compilation from Project Files</title>
		<link>http://dronecolony.com/2012/06/20/automating-qt-help-compilation-from-project-files/</link>
		<comments>http://dronecolony.com/2012/06/20/automating-qt-help-compilation-from-project-files/#comments</comments>
		<pubDate>Wed, 20 Jun 2012 15:54:53 +0000</pubDate>
		<dc:creator>Church</dc:creator>
				<category><![CDATA[Coding]]></category>
		<category><![CDATA[Projects]]></category>

		<guid isPermaLink="false">http://droneone.wpengine.com/?p=219</guid>
		<description><![CDATA[It always bugs me that while the UI and moc generation is automatic and doesn&#8217;t require much effort to make happen in Qt Creator, the help file generation has no such automation that I can find built-in to the IDE.  Here&#8217;s what I add to my project file to compile the different help files and [...]]]></description>
				<content:encoded><![CDATA[<p>It always bugs me that while the UI and moc generation is automatic and doesn&#8217;t require much effort to make happen in Qt Creator, the help file generation has no such automation that I can find built-in to the IDE.  Here&#8217;s what I add to my project file to compile the different help files and put them where they need to be for the built program to access.</p>
<p><span id="more-219"></span></p>
<p>Example<em> docs.pri</em></p>
<pre class="brush: plain; title: ; notranslate">

# Documentation

OTHER_FILES += \
    docs/docs.html \
    docs/docs1.png \
    docs/docs2.png \
    docs/docs.qhp \
    docs/docs.qhcp

HEADERS += \
    docs/helpwindow.h \
    docs/helpbrowser.h

SOURCES += \
    docs/helpwindow.cpp \
    docs/helpbrowser.cpp

FORMS += \
    docs/helpwindow.ui

defineTest(helpCopyCommands) {
    files = $$1
    for(FILE, files) {

              # get rid of trailing 'docs/'

        FILE ~= s,docs/,,

        FILE = $$PWD/$$FILE

              # Replace slashes in paths with backslashes for Windows
        win32:FILE ~= s,/,\\,g

        MYFILECOPY += @echo &quot;Copying $$FILE&quot; $$escape_expand(\\n\\t)
        MYFILECOPY += $$QMAKE_COPY $$quote($$FILE) $$quote($$DDIR) $$escape_expand(\\n\\t)
    }
    export(MYFILECOPY)
}

help_copy.target = dox
help_copy.commands = @echo &quot;Building Help Files in $$DDIR&quot;  $$escape_expand(\\n\\t)
QMAKE_EXTRA_TARGETS += help_copy
POST_TARGETDEPS += dox

win32 {

  CONFIG += help
  CONFIG(debug, debug|release) {
        DDIR = $$OUT_PWD/debug/docs
  }
  CONFIG(release, debug|release) {
        DDIR = $$OUT_PWD/release/docs
  }

!exists($$DDIR) {
    help_copy.commands += @echo &quot;Creating Docs Directory: $$DDIR ($$EDIR)&quot; $$escape_expand(\\n\\t)
    help_copy.commands += md $$DDIR $$escape_expand(\\n\\t)
  }
# replace forward-slashes

  DDIR ~= s,/,\\,g

  helpCopyCommands($$OTHER_FILES)

  help_copy.commands += $$MYFILECOPY
  help_copy.commands += @echo &quot;Running qhelpgenerator&quot; $$escape_expand(\\n\\t)
  help_copy.commands += $$[QT_INSTALL_BINS]\\qhelpgenerator $$DDIR\\docs.qhp -o $$DDIR\\docs.qch $$escape_expand(\\n\\t)
  help_copy.commands += @echo &quot;Running qcollectiongenerator&quot; $$escape_expand(\\n\\t)
  help_copy.commands += $$[QT_INSTALL_BINS]\\qcollectiongenerator $$DDIR\\docs.qhcp -o $$DDIR\\docs.qhc $$escape_expand(\\n\\t)
}

# OSX Instructions
macx {

 LIBS += -framework QtHelp

 DDIR = $$OUT_PWD/$$TARGET.app/Contents/MacOS/docs

 !exists($$DDIR) {
    help_copy.commands += @echo &quot;Creating Docs Directory: $$DDIR&quot; $$escape_expand(\\n\\t)
    help_copy.commands += mkdir -p $$DDIR $$escape_expand(\\n\\t)
  }

 helpCopyCommands($$OTHER_FILES)
 help_copy.commands += $$MYFILECOPY
 help_copy.commands += @echo &quot;Running qhelpgenerator&quot; $$escape_expand(\\n\\t)
 help_copy.commands += $$[QT_INSTALL_BINS]/qhelpgenerator $$DDIR/docs.qhp -o $$DDIR/docs.qch $$escape_expand(\\n\\t)
 help_copy.commands += @echo &quot;Running qcollectiongenerator&quot; $$escape_expand(\\n\\t)
 help_copy.commands += $$[QT_INSTALL_BINS]/qcollectiongenerator $$DDIR/docs.qhcp -o $$DDIR/docs.qhc $$escape_expand(\\n\\t)
}

</pre>
]]></content:encoded>
			<wfw:commentRss>http://dronecolony.com/2012/06/20/automating-qt-help-compilation-from-project-files/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Backyard Gardening</title>
		<link>http://dronecolony.com/2012/05/20/backyard-gardening/</link>
		<comments>http://dronecolony.com/2012/05/20/backyard-gardening/#comments</comments>
		<pubDate>Sun, 20 May 2012 19:38:40 +0000</pubDate>
		<dc:creator>Church</dc:creator>
				<category><![CDATA[Gardening]]></category>
		<category><![CDATA[Projects]]></category>

		<guid isPermaLink="false">http://droneone.wpengine.com/?p=178</guid>
		<description><![CDATA[What available space on our lot the bungalow and the requisite set-back in the front doesn&#8217;t fill, our separate garage in the rear and its associated large parking pad (an effect of having the garage at a 90-degree angle to the driveway) consumes.  Of course, we do have a small grass area, only about 15&#8242;x16&#8242;.  [...]]]></description>
				<content:encoded><![CDATA[<p>What available space on our lot the bungalow and the requisite set-back in the front doesn&#8217;t fill, our separate garage in the rear and its associated large parking pad (an effect of having the garage at a 90-degree angle to the driveway) consumes.  Of course, we do have a small grass area, only about 15&#8242;x16&#8242;.  Having been in the house for nearly three years without having made any appreciable changes to the outside, we&#8217;ve set about converting this small patch of dirt into both a garden and an entertainment/lounging area.  <a  href="http://droneone.wpengine.com/wp-content/uploads/2012/05/garden.jpg" class="thickbox no_icon" rel="gallery-178" title="Main Garden Box"><img class="alignright size-medium wp-image-179" title="Main Garden Box" src="http://droneone.wpengine.com/wp-content/uploads/2012/05/garden-300x194.jpg" alt="" width="300" height="194" /></a></p>
<p>The first step in the process has been to create two raised bed areas, one along the fenceline as a primary garden, and another along the house with a trellis for berries and climbing vines.  Both boxes have been completed and the main garden box is getting well-established after only a few weeks.  The vines box still needs its trellis made and to have vines planted.  When completed the backyard will also have a water garden, one potted fruit tree, a paved seating area and a center firebox for entertaining.</p>
<p>All boxes were designed with both looks and simplicity in mind.  Whitewood 2x4s are utilized, and the only cuts required are for the end-pieces to cut them to shape.  For the main garden box, the height chosen was 5 2x4s and the width approximately 2 3/4 feet.  Resulting in approximately 25 cubic feet of useful contained soil.  No fastening hardware is used, instead the panels are assembled using expanding glue and pipe clamps.  Having irregular jointing from the standard edges on the 2&#215;4 studs, and a rough application of custom-mixed stain with a single coat gives the boxes a bit of a rustic edge, while still having a clearly decorative intent.  Three heavy coats of spar urethane inside and out provide a lot of protection for facing the weather.  Due to the gaps in the rough joints, an interior liner of weed fabric is used to keep the soil from spilling out of the larger gaps.</p>
<p>The main box follows a basic square-foot style gardening tactic, with 14 divided sections &#8211; 12 at 12&#8243;x16&#8243;, and two at 18&#8243;x16&#8243;.  This high-density method takes a little more work, but can produce a lot more, and a larger variety of, crop in the same amount of area.  The two larger sections are currently used for the biggest plants: our heirloom tomatoes and purple pole beans.  Other plants include: <a  href="http://droneone.wpengine.com/wp-content/uploads/2012/05/edge.jpg" class="thickbox no_icon" rel="gallery-178" title="Edging"><img class="alignright size-medium wp-image-180" title="Edging" src="http://droneone.wpengine.com/wp-content/uploads/2012/05/edge-300x194.jpg" alt="" width="300" height="194" /></a>jalapeno peppers, three kinds of basil, sage, rosemary, carrots, beets, arugula, soybeans, lavender, thyme, chives, bell peppers, and micro greens.   Three areas are reserved for micro greens and as this is a thing we love to constantly eat, they are planted to ensure a nearly-constant harvest once they take off.  At 25 days to harvest, each plot is planted one week after the next, and re-planted when harvested.  Our first batch of seeds didn&#8217;t perform well, so we just re-planted with a new batch of seeds this weekend.  We&#8217;re hoping the timing on this will keep us in fresh bits of salad well into the winter. The pole beans have been our most aggressive grower, in just two and a half weeks since planting seeds, they have grown up to several feet above their 3&#8242; poles.</p>
<p>Of course, urban gardening is not without its pests: our basil and thyme have been hit the hardest.  The basil by large caterpillars and small grasshoppers, and the thyme by snails.  We have a large rue plant to attract the black swallowtail caterpillars, but as of late there haven&#8217;t been any.  We can&#8217;t identify this caterpillar eating our basil, so we&#8217;re presuming it to be a large moth. Lids of beer set out are starting to do their job with the snails, but I doubt we&#8217;ll make much of a dent.</p>
<p>We love mint too, but it has no place in a container garden with other plants &#8211; it tends to grow wild and out of control.  So, our peppermint is happily getting along in a large pot.</p>
<p>As we get more done in the backyard, we&#8217;ll post photos and how we did it. In the mean-time, here are a few garden photos:</p>
<p><a  href="http://droneone.wpengine.com/wp-content/uploads/2012/05/cat1.jpg" class="thickbox no_icon" rel="gallery-178" title="Large Caterpillar"><img class="alignright size-medium wp-image-181" title="Large Caterpillar" src="http://droneone.wpengine.com/wp-content/uploads/2012/05/cat1-300x194.jpg" alt="" width="300" height="194" /></a><a  href="http://droneone.wpengine.com/wp-content/uploads/2012/05/snail1.jpg" class="thickbox no_icon" rel="gallery-178" title="Drunken Snail"><img class="alignright size-medium wp-image-182" title="Drunken Snail" src="http://droneone.wpengine.com/wp-content/uploads/2012/05/snail1-300x194.jpg" alt="" width="300" height="194" /></a><a  href="http://droneone.wpengine.com/wp-content/uploads/2012/05/pb.jpg" class="thickbox no_icon" rel="gallery-178" title="pole beans"><img class="alignright  wp-image-183" title="pole beans" src="http://droneone.wpengine.com/wp-content/uploads/2012/05/pb-300x194.jpg" alt="" width="300" height="194" /></a><a  href="http://droneone.wpengine.com/wp-content/uploads/2012/05/pepmint.jpg" class="thickbox no_icon" rel="gallery-178" title="Peppermint"><img class="alignright size-medium wp-image-184" title="Peppermint" src="http://droneone.wpengine.com/wp-content/uploads/2012/05/pepmint-300x194.jpg" alt="" width="300" height="194" /></a></p>
]]></content:encoded>
			<wfw:commentRss>http://dronecolony.com/2012/05/20/backyard-gardening/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Welcome Back</title>
		<link>http://dronecolony.com/2012/05/20/welcome-back/</link>
		<comments>http://dronecolony.com/2012/05/20/welcome-back/#comments</comments>
		<pubDate>Sun, 20 May 2012 18:23:07 +0000</pubDate>
		<dc:creator>Church</dc:creator>
				<category><![CDATA[Uncategorized]]></category>

		<guid isPermaLink="false">http://droneone.wpengine.com/?p=177</guid>
		<description><![CDATA[After a long hiatus from blogging and managing my personal website in general, I&#8217;ve decided to start again.  Previously, I had two blogs (roamingdrone.wordpress.com, shutterdrone.wordpress.com) and one website (dronecolony.com).  Here, you&#8217;ll find both blogs combined into one, and the new blog now replaces the old website.  I hope that you&#8217;ll find the content here engaging, [...]]]></description>
				<content:encoded><![CDATA[<p>After a long hiatus from blogging and managing my personal website in general, I&#8217;ve decided to start again.  Previously, I had two blogs (roamingdrone.wordpress.com, shutterdrone.wordpress.com) and one website (dronecolony.com).  Here, you&#8217;ll find both blogs combined into one, and the new blog now replaces the old website.  I hope that you&#8217;ll find the content here engaging, or at least a worthwhile distraction. Thanks for coming by!</p>
]]></content:encoded>
			<wfw:commentRss>http://dronecolony.com/2012/05/20/welcome-back/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Desert Oasis</title>
		<link>http://dronecolony.com/2010/04/10/desert-oasis/</link>
		<comments>http://dronecolony.com/2010/04/10/desert-oasis/#comments</comments>
		<pubDate>Sat, 10 Apr 2010 17:55:49 +0000</pubDate>
		<dc:creator>c.a. church</dc:creator>
				<category><![CDATA[Black and White]]></category>
		<category><![CDATA[Inland Locations]]></category>
		<category><![CDATA[K7]]></category>
		<category><![CDATA[Photography]]></category>
		<category><![CDATA[big bend ranch]]></category>
		<category><![CDATA[creek]]></category>
		<category><![CDATA[desert]]></category>
		<category><![CDATA[fresno canyon]]></category>
		<category><![CDATA[rocks]]></category>
		<category><![CDATA[spring]]></category>
		<category><![CDATA[water]]></category>

		<guid isPermaLink="false">http://shutterdrone.wordpress.com/?p=93</guid>
		<description><![CDATA[Pentax K7+CPol+ND8]]></description>
				<content:encoded><![CDATA[<p style="text-align:center;"><img class="aligncenter" title="Desert Oasis" src="http://dronecolony.com/2008/new_img/blog/shutter/IMGP2533-sm.jpg" alt="" width="600" height="388" /></p>
<p style="text-align:center;">Pentax K7+CPol+ND8</p>
]]></content:encoded>
			<wfw:commentRss>http://dronecolony.com/2010/04/10/desert-oasis/feed/</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Remnants</title>
		<link>http://dronecolony.com/2010/03/24/remnants/</link>
		<comments>http://dronecolony.com/2010/03/24/remnants/#comments</comments>
		<pubDate>Wed, 24 Mar 2010 23:50:38 +0000</pubDate>
		<dc:creator>c.a. church</dc:creator>
				<category><![CDATA[Black and White]]></category>
		<category><![CDATA[Inland Locations]]></category>
		<category><![CDATA[K7]]></category>
		<category><![CDATA[Photography]]></category>
		<category><![CDATA[b&w]]></category>
		<category><![CDATA[big bend ranch]]></category>
		<category><![CDATA[jar]]></category>

		<guid isPermaLink="false">http://shutterdrone.wordpress.com/?p=90</guid>
		<description><![CDATA[Pentax K7, Sigma 17-70 + CPol]]></description>
				<content:encoded><![CDATA[<p><img class="aligncenter" title="Remnants" src="http://dronecolony.com/2008/new_img/blog/shutter/IMGP2482-sm.jpg" alt="" width="600" height="388" /></p>
<p style="text-align:center;">Pentax K7, Sigma 17-70 + CPol</p>
]]></content:encoded>
			<wfw:commentRss>http://dronecolony.com/2010/03/24/remnants/feed/</wfw:commentRss>
		<slash:comments>3</slash:comments>
		</item>
		<item>
		<title>A Detailed Guide on Implementing Arduino Intervalometers</title>
		<link>http://dronecolony.com/2009/08/04/arduino_intervalometer/</link>
		<comments>http://dronecolony.com/2009/08/04/arduino_intervalometer/#comments</comments>
		<pubDate>Tue, 04 Aug 2009 15:37:15 +0000</pubDate>
		<dc:creator>Church</dc:creator>
				<category><![CDATA[Projects]]></category>
		<category><![CDATA[Time-lapse Video]]></category>
		<category><![CDATA[arduino]]></category>
		<category><![CDATA[blocking]]></category>
		<category><![CDATA[camera]]></category>
		<category><![CDATA[canon]]></category>
		<category><![CDATA[interrupts]]></category>
		<category><![CDATA[intervalometer]]></category>
		<category><![CDATA[non-blocking]]></category>
		<category><![CDATA[pentax]]></category>
		<category><![CDATA[Photography]]></category>
		<category><![CDATA[time-lapse]]></category>
		<category><![CDATA[timelapse]]></category>
		<category><![CDATA[timing]]></category>

		<guid isPermaLink="false">http://roamingdrone.wordpress.com/?p=151</guid>
		<description><![CDATA[I&#8217;ve begun a series of tutorials on the basics of developing your own time-lapse electronics. To make sense of all of it, I&#8217;m working in a forward-manner: starting with the most basic elements, and providing tips and tricks that will be built upon in future tutorials. While I understand that most of the DIY builders [...]]]></description>
				<content:encoded><![CDATA[<p>I&#8217;ve begun a series of tutorials on the basics of developing your own time-lapse electronics. To make sense of all of it, I&#8217;m working in a forward-manner: starting with the most basic elements, and providing tips and tricks that will be built upon in future tutorials.</p>
<p>While I understand that most of the DIY builders reading this post are long past the point of building just a simple intervalometer, for those just getting started, this should be highly informative. Unlike many other tutorials, I&#8217;m not just giving you some schematics and a block of code. Instead, my goal is to explain why things are done a certain way, and teaching foundations for better system design.</p>
<p>This tutorial covers building a simple test circuit that will allow you to validate your software and hardware without putting wear on your camera, the importance of protecting your equipment from mistakes, three different timing options: blocking, non-blocking, and non-blocking using simple interrupts, and finally controlling a Canon or Pentax camera. (Nikon, etc. I don&#8217;t have much info on &#8211; perhaps one of the Nikon-having contributors can expand in a later tutorial.)</p>
<p>The tutorial can be accessed here: <a  href="http://openmoco.org/node/88">http://openmoco.org/node/88</a></p>
]]></content:encoded>
			<wfw:commentRss>http://dronecolony.com/2009/08/04/arduino_intervalometer/feed/</wfw:commentRss>
		<slash:comments>4</slash:comments>
		</item>
		<item>
		<title>LightRails &#8211; Dynamic External Exposure Control</title>
		<link>http://dronecolony.com/2009/03/25/lightrails-dynamic-external-exposure-control-for-time-lapse/</link>
		<comments>http://dronecolony.com/2009/03/25/lightrails-dynamic-external-exposure-control-for-time-lapse/#comments</comments>
		<pubDate>Wed, 25 Mar 2009 15:30:01 +0000</pubDate>
		<dc:creator>Church</dc:creator>
				<category><![CDATA[Projects]]></category>
		<category><![CDATA[Robots]]></category>
		<category><![CDATA[Time-lapse Video]]></category>
		<category><![CDATA[arduino]]></category>
		<category><![CDATA[bulb mode]]></category>
		<category><![CDATA[calculate]]></category>
		<category><![CDATA[camera]]></category>
		<category><![CDATA[digital]]></category>
		<category><![CDATA[diy]]></category>
		<category><![CDATA[exposure time]]></category>
		<category><![CDATA[Exposure Value]]></category>
		<category><![CDATA[f/stop]]></category>
		<category><![CDATA[intervalometer]]></category>
		<category><![CDATA[light meter]]></category>
		<category><![CDATA[light sensor]]></category>
		<category><![CDATA[lightrails]]></category>
		<category><![CDATA[open source]]></category>
		<category><![CDATA[robot]]></category>
		<category><![CDATA[time-lapse]]></category>
		<category><![CDATA[timelapse]]></category>
		<category><![CDATA[TSL230R]]></category>
		<category><![CDATA[video]]></category>

		<guid isPermaLink="false">http://roamingdrone.wordpress.com/?p=140</guid>
		<description><![CDATA[I&#8217;ve been sitting on this a little while, and I feel a bit remiss in not sharing it sooner.  I had intended to make it &#8220;perfect&#8221; before sharing, but feel I&#8217;ve reached a point where I&#8217;m not ready to spend all of my effort on this project, and instead wish to work more on OpenMoco [...]]]></description>
				<content:encoded><![CDATA[<p><em>I&#8217;ve been sitting on this a little while, and I feel a bit remiss in not sharing it sooner.  I had intended to make it &#8220;perfect&#8221; before sharing, but feel I&#8217;ve reached a point where I&#8217;m not ready to spend all of my effort on this project, and instead wish to work more on OpenMoco (but we&#8217;ll talk more about that soon!).  So, I&#8217;ll share this as a more &#8220;rough&#8221; project.</em></p>
<p>Some time ago, I started out with the <a  href="http://roamingdrone.wordpress.com/2008/11/13/arduino-and-the-taos-tsl230r-light-sensor-getting-started/" target="_blank">TSL230R chip</a> from Taos with the intent of producing a (a) my own digital light meter and (b) a dynamic external control system for time-lapse photography.  Certainly, the act of creating my own digital light meter was a smashing success, also having it control my dSLR via a remote cable in bulb mode was also a great success.  Enough so that it should prove an invaluable tool to the DIY pinhole photographer.  (How do you time that 2:35:20 exposure manually?)</p>
<p>The problem I wanted to solve was a difficult, but common one: how do you effectively manage exposure changes across sunrise and sunset time-lapses with a dSLR?  It sounds very easy, just go into Av or Tv mode!  Of course, life isn&#8217;t that easy.  The standard dSLRs generally only meter and adjust exposure in the very rough terms of 1/6, 1/4, 1/3, or even 1/2 EV steps!  This means it has a sledge hammer where the problem calls for a gentle tapping of a finger.  I set out to create a system that would control the camera externally, metering and adjusting exposure in 1/100 EV steps&#8230;</p>
<p><span id="more-140"></span></p>
<p>Some more information about how this all got started, and some color commentary along the way can be found in these two forum postings over at timescapes.org:</p>
<p><a  href="http://www.timescapes.org/phpBB3/viewtopic.php?f=7&#038;t=588" target="_blank">Testing Dynamic External Exposure Control</a></p>
<p><a  href="http://www.timescapes.org/phpBB3/viewtopic.php?f=4&#038;t=577" target="_blank">Huge Dynamic Range Change</a></p>
<p><a  href="http://timescapes.org/phpBB3/viewtopic.php?f=4&#038;t=326" target="_blank">Sunset Time-lapses</a></p>
<p>Unfortunately, the system is still a bit rough for time-lapse photography in controlling exposure in all conditions, but it can be made to succeed in certain conditions, given practice and patience.  I&#8217;m not going to go into great detail on instructions, as it should be largely evident based on the information below, but you will need the following parts at a minimum to use this code:</p>
<ul>
<li>(1x) <a  href="http://www.arduino.cc" target="_blank">Arduino</a> board with Atmega168P chip (will probably work with 328p)</li>
<li>(1x) <a  href="http://www.sparkfun.com/commerce/product_info.php?products_id=8940" target="_blank">Taos TSL230R</a> light sensor IC</li>
<li>(1x) 16&#215;2 Parallel LCD screen</li>
<li>(1x) 4N28 opto-isolator (isolating camera trigger circuitry)</li>
<li>(5x) SPST mometary switches</li>
<li>(1x) 0.1uF Ceramic Capacitor</li>
</ul>
<p>Effectively, the sensor should be placed remote from the control unit (but within specs of the TSL230R), and the control unit uses an LCD display to display information, and five momentary switches to control operation and input data.  The best configuration I&#8217;ve found for using this for time-lapse is to create a reflector, placing the sensor and the capacitor in a separate enclosure from the control, and place the sensor inside of the reflector.  For a reflector, I simply used three pieces of wood connected like a &#8216;C&#8217;, painted the inside flat white, and mounted the sensor housing inside of the &#8216;C&#8217;, facing downwards.  The reflector could then either be placed in complete shade, or pointed towards the scene.  Either way, the unit should be protected from over-head light changes &#8211; it should either meter the scene on average, or the amount of light read in shade.  Which is better depends on the scene in question &#8212; shooting sunny sky-scenes with dim foregrounds generally require scene metering, whereas equally-lit sky and foregrounds prefer shade-readings.  It is also capable of metering through your eye-piece, but resolution suffers greatly (set TTL F/stop in the UI to enable this mode).</p>
<p>The system is configured with ISO, f/stop, minimum and maximum exposure times, number of EV divisions per step (best left at 100), and shot interval.  The light reading is calculated once per second, and exposure time calculated from the settings and light reading.  EV adjust is set to achieve the proper exposure vs. the metered exposure, and the system is left to run.</p>
<p>Random fluctuations and massive jumps from physical interference (say a dog sniffing at your meter while you&#8217;re napping) are prevented through the simple application of a &#8216;ev change ceiling&#8217; setting, which prevents any change in calculated EV greater than the ceiling (in x/y EV steps &#8212; e.g.: no change greater than 3/100&#8242;s EV.) &#8212; this method has proven its self as both the most simple and effective means through empirical testing, beating out averaging, weighting, and all other sorts of complicated methods.</p>
<p>If you find this useful, come up with a novel application, or have any questions &#8211; please don&#8217;t hesitate to drop me a comment here.  I&#8217;d love to know what you do with it, or help you if you need it.</p>
<p>The following videos should give you some idea how to assemble and use it:</p>
<p><span class='embed-youtube' style='text-align:center; display: block;'><iframe class='youtube-player' type='text/html' width='960' height='570' src='http://www.youtube.com/embed/_QQmGMQMAzw?version=3&#038;rel=1&#038;fs=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;wmode=transparent' frameborder='0'></iframe></span></p>
<p><span class='embed-youtube' style='text-align:center; display: block;'><iframe class='youtube-player' type='text/html' width='960' height='570' src='http://www.youtube.com/embed/UK05LFcsx-c?version=3&#038;rel=1&#038;fs=1&#038;showsearch=0&#038;showinfo=1&#038;iv_load_policy=1&#038;wmode=transparent' frameborder='0'></iframe></span></p>
<p><em>note: both videos show older versions of the UI and the device, where the sensor was integrated into the controller. This is not recommended. </em></p>
<p>And, here&#8217;s an example of its performance:</p>
<p><iframe src="http://player.vimeo.com/video/2988868" width="960" height="540" frameborder="0" webkitAllowFullScreen mozallowfullscreen allowFullScreen></iframe></p>
<p>And, without further ado, here&#8217;s the code.  Each section should be pasted into a seperate tab in the Arduino UI, with the first section of code in the main tab.</p>
<p><em>lightrails.pde:</em></p>
<pre class="brush: cpp; title: ; notranslate">

/*

LightRails 1.0
A dynamic external exposure control system with
integrated intervalometer for time-lapse
photography

Copyright (c) 2008-2009 C. A. Church drone&lt; a_t &gt;dronecolony.com

This program is free software: you can redistribute it
and/or modify it under the terms of the GNU General
Public License as published by the Free Software
Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License
for more details.

*/

#include &lt;LiquidCrystal.h&gt;
#include &lt;avr/pgmspace.h&gt;
#include &lt;MsTimer2.h&gt;

#define TSL_FREQ_PIN 2
#define TSL_S0         6
#define TSL_S1         5
#define TSL_S2         3
#define TSL_S3       4

#define CAMERA_PIN   13

// buttons

#define B_LT   9
#define B_RT   10
#define B_UP   7
#define B_DN   8
#define B_CT   11

// max number of setup steps available

#define MAX_SETUP_STEPS   12

// read frequency for how many milliseconds
// note: this code only works for 1000 any other value
// and you need to adjust your frequency calculation

#define READ_TM      1000

// low and high thresholds for adjusting sensitivity
// of the TSL230R.  If freq is below LO thresh, it will
// automatically increase sensitivity.  If it increases
// beyond the HI thresh, it will decrease sensitivity

#define SENS_THRESH_LO 101
#define SENS_THRESH_HI 7999

// LCD print buffer
// set to LCD width + 1

#define SIZE_OF_LCD_BUF 17

// threshold to accept a frequency change
// the frequency read off the chip must change by
// at least this many Hz to accept the change
// this prevents fluttering.  However, it can cause
// problems with low-light level changes.  You may need
// to adjust according to types of shooting you
// typically do (higher for more day shooting
// and lower for more night shooting)

#define FREQ_CHG_THRESH  20

// the minimum exposure gap is the minimum time
// between triggering exposures (for the cases where
// exposure_time exceeds interval_time).  At a bare
// minimum, it should be the minimum amount of time
// required by your camera to consistently register
// the completion of one exposure and execution of
// another.  this time is in milliseconds

#define MIN_EXP_GAP 1000

// some info about exposure data
// need iso and f/stop to calculate
// exposure time.  These are just
// defaults - they are adjusted via the UI

int   iso_rating = 100;
float f_stop     = 8.0;

// Maximum Diff % in EV Value calculated from reading to reading
byte ev_diff_ceiling  = 0;

// for through-a-lens metering - f-stop of the lens
// (light read must be calculated)

float ttl_stop = 0.0;

// default times
unsigned int camera_delay       = 5;
unsigned int min_exp_tm         = 10;
byte         max_exp_tm         = 20;

// actual delay time in mS

unsigned long real_camera_delay = camera_delay * 1000;

// running variables

float         uwcm2          = 0; // uw/cm2 calculated
float         lux            = 0; // Lux caclulated
float         ev             = 0; // EV value calculated
float         exp_tm         = 0; // camera exposure time calculated in 1/x value
float         pre_exp_tm     = 0; // for latching, need previous reading
unsigned long frequency      = 0; // frequency read
unsigned long cur_exp_tm     = 0; // current exposure time in mS
unsigned int  shots_fired    = 0; // how many exposures taken since interval started

bool camera_engaged          = false; // camera currently exposing?
bool light_type              = false; // which spd graph to use - d65 [0] or ilA [1]

// this is used by the pulse counter.

volatile unsigned long pulse_cnt = 0;

// for calculating time differences

unsigned long pre_tm         = millis();

unsigned int  freq_tm_diff   = 0; // how much time has passed since last frequency reading
// -- note the int size limit
unsigned long camera_tm_diff = 0; // how much time has passed since last exposure

// this is used by the sensitivity adjustment
// to record the amount to divide the freq by
// to get the uW/cm2 value at 420nm

unsigned int  calc_sensitivity   = 10;

// this is the frequency calculation multiplier
// based on what scaling factor is chosen.
// set_scaling() will adjust it for you.

byte freq_mult = 1;

// our previous frequency count
// -- we set it to fifty so it doesn't swing
// auto-sensitivity adjustment either way on the first
// reading

unsigned long last_cnt  = 50;

// some EV tracking variables

float prev_ev = 0;
float set_ev  = 0;

// our wavelengths (nm) we're willing to calculate illuminance for (lambda)
int wavelengths[18] = { 380, 400, 420, 440, 460, 480, 500, 520, 540, 560, 580, 600, 620, 640, 660, 680, 700, 720 };
// the CIE V(l) for photopic vision - CIE Vm(l) 1978 - mapping to the same (l) above
float v_lambda[18]  = { 0.0002, 0.0028, 0.0175, 0.0379, 0.06, 0.13902, 0.323, 0.71, 0.954, 0.995, 0.87, 0.631, 0.381, 0.175, 0.061, 0.017, 0.004102, 0.001047 };

// CIE SPD graphs for D65 and Illuminant A light sources, again mapping to same lambda as included in wavelengths
float spd_graphs[2][18] = {
{ 49.975500, 82.754900, 93.431800, 104.865000, 117.812000, 115.923000, 109.354000, 104.790000, 104.405000, 100.000000, 95.788000, 90.006200, 87.698700, 83.699200, 80.214600, 78.284200, 71.609100, 61.604000 },
{ 9.795100, 14.708000, 20.995000, 28.702700, 37.812100, 48.242300, 59.861100, 72.495900, 85.947000, 100.000000, 114.436000, 129.043000, 143.618000, 157.979000, 171.963000, 185.429000, 198.261000, 210.365000 }
};

/*

Let's setup all of our display strings for the LCD

These are stored as PROGMEM so they don't take up valuable
SRAM

*/
// main display strings

// setup strings

const prog_char setup_str1[] PROGMEM  = &quot;ISO&quot;;
const prog_char setup_str2[] PROGMEM  = &quot;Target F/Stop&quot;;
const prog_char setup_str3[] PROGMEM  = &quot;EV Adjust&quot;;
const prog_char setup_str4[] PROGMEM  = &quot;EV Steps&quot;;
const prog_char setup_str5[] PROGMEM  = &quot;Max Exp. - Sec&quot;;
const prog_char setup_str6[] PROGMEM  = &quot;Exposure Cycle&quot;;
const prog_char setup_str7[] PROGMEM  = &quot;Latch&quot;;
const prog_char setup_str8[] PROGMEM  = &quot;Min Exp. - mSec&quot;;
const prog_char setup_str9[] PROGMEM  = &quot;Scale Read&quot;;
const prog_char setup_str10[] PROGMEM = &quot;TTL F/Stop&quot;;
const prog_char setup_str11[] PROGMEM = &quot;Light Source&quot;;
const prog_char setup_str12[] PROGMEM = &quot;EV Change Max&quot;;

PGM_P setup_strings[MAX_SETUP_STEPS] PROGMEM =
{
setup_str1,
setup_str2,
setup_str3,
setup_str4,
setup_str6,
setup_str7,
setup_str8,
setup_str5,
setup_str9,
setup_str10,
setup_str11,
setup_str12
};

// buffer for above setup strings
char lcd_print_buffer[SIZE_OF_LCD_BUF];

// ev adjust can be negative
//
// how many EV steps to add or subtract
// to each EV calculation - UI moves in 1/4EV
// increments.

float ev_adjust     = 0.0;

// how many steps per EV to deal with -
// calculations round to the nearest step
// and adjustments happen in these steps,
// e.g.: 10 steps = .1 EV per step,
// 1 step = 1 EV per step

byte ev_steps     = 100;

// status flags:
// B0 = intervalometer enable
// B1 = latch up
// B2 = latch down
// B3 = in setup
// B4 = latched (exp tm not moved due to latch)
// B5 = latched to min/max exp tm setting
// B6 = ev adjust enabled
// B7 = update setup display

byte status = B00000000;

// current setup step

byte setup_step  = 0;

// display type flags
// 0 = 1/x shutter speed/time (calculated)
// 1 = ms shutter speed (calculated)
// 2 = frequency
// 3 = EV (calculated)
// 4 = W/m2 (calculated)
// 5 = sensitivity (10, 100, 1000)
// 6 = lux (calculated)
// 7 = scale

byte disp_enable = B10000000;

// button hit flags (plus a few others)
// B0 = up
// B1 = dn
// B2 = lt
// B3 = rt
// B4 = ct
// B5
// B6 =
// B7 = camera exposing (use this to save a var elsewhere)

byte buttons = B00000000;

// time last button was hit
unsigned long button_hit = 0;

// init lcd display

LiquidCrystal lcd(14, 14, 15, 16, 17, 18, 19);

void setup() {

lcd.clear();
lcd.print(&quot;LightRails/1.0&quot;);

// attach interrupt to pin8, send output pin of TSL230R to arduino 8
// call handler on each rising pulse

attachInterrupt(0, add_pulse, RISING);

pinMode(TSL_FREQ_PIN, INPUT);

pinMode(TSL_S0, OUTPUT);
pinMode(TSL_S1, OUTPUT);
pinMode(TSL_S2, OUTPUT);
pinMode(TSL_S3, OUTPUT);

pinMode(CAMERA_PIN, OUTPUT);

// set input button pins

pinMode(B_LT, INPUT);
pinMode(B_RT, INPUT);
pinMode(B_UP, INPUT);
pinMode(B_DN, INPUT);
pinMode(B_CT, INPUT);

// enable pullup resistors
digitalWrite(B_LT, HIGH);
digitalWrite(B_RT, HIGH);
digitalWrite(B_UP, HIGH);
digitalWrite(B_DN, HIGH);
digitalWrite(B_CT, HIGH);

// write TSL sensitivity 1x

digitalWrite(TSL_S0, HIGH);
digitalWrite(TSL_S1, LOW);

// set frequency scaling

tsl_set_scaling(2);

}

void loop() {

// operate camera
// handle user input
// update light sensor data

// calculate how much time has passed
// both for camera firing and light reading

// this code requires arduino 012 or any version with
// compatible millis() overflow and behavior

freq_tm_diff   += millis() - pre_tm;
camera_tm_diff += millis() - pre_tm;

pre_tm          = millis();

// if intervalometer enabled, enough time has passed,
// and the camera isn't already engaged
// - fire camera (camera delay is in seconds)

if( status &amp; B10000000 &amp;&amp; camera_tm_diff &gt;= real_camera_delay &amp;&amp;  camera_engaged == false ) {

// re-set camera difference timer
camera_tm_diff = 0;

// trigger camera remote
fire_camera();

// convert sec to msec for next exposure check
// always try and use the configured value first
// (see next operation)

real_camera_delay = camera_delay * 1000;

// assure MIN_EXP_GAP threshold is enforced
// between exposure triggers
//
// if the exposure time leaves less than the
// minimum gap between exposures (MIN_EXP_GAP
// must be &lt;= real_camera_delay), add the minimum gap
// to the current exposure time, and make that the
// interval time before the next shot

if( cur_exp_tm &gt;= ( real_camera_delay - MIN_EXP_GAP ) )
real_camera_delay = cur_exp_tm + MIN_EXP_GAP;

// update count of shots fired
shots_fired++;

} // end if( status...

// see if the user has pressed any buttons, and handle
// them if need be

check_input();

// if a second has passed, we need to
// update our calculations

if( freq_tm_diff &gt;= READ_TM ) {

// reset time counter

freq_tm_diff = 0;

// get current frequency

frequency = tsl_get_freq();

// ignore changes in frequency below FREQ_CHG_THRESH

if( last_cnt &gt; 0 &amp;&amp; abs( (long) ( frequency - last_cnt ) ) &lt; FREQ_CHG_THRESH )
frequency = last_cnt;

// set other needed values

if ( frequency &gt; 0 ) {

// chip has given us a positive reading

// calculate power
uwcm2     = tsl_calc_uwatt_cm2(frequency);

if( uwcm2 &lt;= 0 ) {
// handle no actual power reading available
uwcm2 = 0;
lux   = 0;
}
else {

// there was a positive power reading...

// calculate lx value using gaussian formula

lux = calc_lux_gauss(uwcm2);
}
}
else {
// 0 frequency read from chip - set all values to zero
uwcm2 = 0;
lux   = 0;
}

if ( lux &lt;= 0 ) {
// don't try calculating nonsense - we cant calculate
// an EV with no light.
lux = 0;
ev = -6.0;
}
else {
// we have positive lux reading
// calculate EV from lux reading
ev = calc_ev(lux);
}

// calculate exposure time value

exp_tm     = calc_exp_tm( ev, f_stop );

// determine if we need to latch on to a particular exposure
// as lowest or highest allowed

if( status &amp; B10000000 &amp;&amp; status &amp; B01100000) {
// latch high or low enabled - and intervalometer on

if( status &amp; B01000000 ) {
// latch low is enabled (don't increase exposure time)
// remember that exp_tm is a divisor - so a higher number == faster exposure
if( exp_tm &gt; pre_exp_tm) {
exp_tm = pre_exp_tm;
// indicate that exposure is latched
status |= B00001000;
}
else {
// reset latch indicator
status &amp;= B11110111;
// set new exposure value to keep exp from
// getting longer than current exp. (new
// ceiling)
pre_exp_tm = exp_tm;
}
}
else if( status &amp; B00100000 ) {
// latch high enabled (don't decrease exposure time)
if( exp_tm &lt; pre_exp_tm ) {
exp_tm = pre_exp_tm;
status |= B00001000;
}
else {
status &amp;= B11110111;
// new floor
pre_exp_tm = exp_tm;
}
}
} // end if latch high or low enabled

// calculate exposure ms
// we can only go as short as 1ms, so don't
// try and calculate below that.  Check for
// 1/1000 ceiling

if( exp_tm &gt;= 1000 ) {
cur_exp_tm = 1;
}
else {
cur_exp_tm = calc_exp_ms( exp_tm );
}

// check for minimum/max exposure time

if( cur_exp_tm &lt; min_exp_tm ) {
// bring time up to min. exposure time
cur_exp_tm = min_exp_tm;
// set exposure time ceiling/floor engaged
status |= B00000100;
}
// max exposure time is in seconds
else if( max_exp_tm &gt; 0 &amp;&amp; cur_exp_tm &gt; ( max_exp_tm * 1000 ) ) {
cur_exp_tm = max_exp_tm * 1000;
// set exposure time ceiling/floor engaged
status |= B00000100;
}
else {
// at neither min nor max exp. time
// reset exposure time ceiling/floor flag
status &amp;= B11111011;
}

// determine if we need to change sensitivity --
// two readings in a row must pass our thresholds

if( frequency &lt; SENS_THRESH_LO &amp;&amp; last_cnt &lt; SENS_THRESH_LO &amp;&amp; calc_sensitivity &lt; 1000 ) {
tsl_sensitivity( HIGH );
}
else if( frequency &gt; SENS_THRESH_HI &amp;&amp; last_cnt &gt; SENS_THRESH_HI &amp;&amp; calc_sensitivity &gt; 10) {
tsl_sensitivity( LOW );
}

// save off current reading so we can see if we need to adjust sensitivity
// on the next pass

last_cnt = frequency;

// set display to update

status |= B00000001;

} // end if(freq_tm_diff &gt; READ_TM)

//
// update user interface as the last step in the main
// loop...
//

if(  status &amp; B00010000 &amp;&amp; status &amp; B00000001 ) {
// in setup and screen needs updating

// clear update flag
status &amp;= B11111110;

// display setup info

print_setup_display();
}
else if( status &amp; B00000001 ) {
// on main screen and need to update display

// clear update flag
status &amp;= B11111110;

print_info();

}

} // end main loop

// interrupt handler to function as a pulse-counter

void add_pulse() {

// increase pulse count
pulse_cnt++;
return;
}

float float_abs_diff( float f1, float f2 ) {

float diff = f1 &gt; f2 ? f1 - f2 : f2 - f1;

if( diff &lt; 0 )
diff *= 1;

return(diff);
}

</pre>
<p><em>tsl230r_functions.pde:</em></p>
<pre class="brush: cpp; title: ; notranslate">

/*

LightRails - TSL230R Control and Conversion Functions

Copyright (c) 2008-2009 C. A. Church drone&lt; a_t &gt;dronecolony.com

unsigned long tsl_get_freq()
float tsl_calc_uwatt_cm2(unsigned long freq)
void tsl_sensitivity( bool dir )
void tsl_set_scaling( int what )

This program is free software: you can redistribute it
and/or modify it under the terms of the GNU General
Public License as published by the Free Software
Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License
for more details.

*/

unsigned long tsl_get_freq() {

// we have to scale out the frequency - the only
// 1:1 frequency pulse we get is 100x scale.  Smaller
// scaling on the TSL230R requires us to multiply by a factor
// to get actual frequency

unsigned long freq = pulse_cnt * freq_mult;
pulse_cnt = 0;
return(freq);
}

float tsl_calc_uwatt_cm2(unsigned long freq) {

// get uW observed - assume 640nm wavelength
// calc_sensitivity is our divide-by to map to a given signal strength
// for a given sensitivity (each level of greater sensitivity reduces the signal
// (uW) by a factor of 10)
//
// note: this function does not take sensor size into account
//       other functions handle this

float uw_cm2 = (float) freq / (float) calc_sensitivity;

return(uw_cm2);

}

void tsl_sensitivity( bool dir ) {

// adjust sensitivity of TSL230R in 3 steps of 10x either direction

int pin_0 = false;
int pin_1 = false;

if( dir == true ) {

// increasing sensitivity

// -- already as high as we can get
if( calc_sensitivity == 1000 )
return;

if( calc_sensitivity == 100 ) {
// move up to max sensitivity
pin_0 = true;
pin_1 = true;
}
else {
// move up to med. sesitivity
pin_1 = true;
}

// increase sensitivity divider
calc_sensitivity *= 10;
}
else {
// reducing sensitivity

// already at lowest setting

if( calc_sensitivity == 10 )
return;

if( calc_sensitivity == 100 ) {
// move to lowest setting
pin_0 = true;
}
else {
// move to medium sensitivity
pin_1 = true;
}

// reduce sensitivity divider
calc_sensitivity = calc_sensitivity / 10;
}

// make any necessary changes to pin states

digitalWrite(TSL_S0, pin_0);
digitalWrite(TSL_S1, pin_1);

return;
}

void tsl_set_scaling ( int what ) {

// set output frequency scaling for TSL230R
// when increasing the scaling for divide-by-output, you reduce the
// freq multiplier by a factor of ten.
// e.g.:
// scale = 2 == freq_mult = 100
// scale = 10 == freq_mult = 10
// scale = 100 == freq_mult = 1

byte pin_2 = HIGH;
byte pin_3 = HIGH;

switch( what ) {
case 2:
pin_3     = LOW;
freq_mult = 2;
break;
case 10:
pin_2     = LOW;
freq_mult = 10;
break;
case 100:
freq_mult = 100;
break;
default:
return;
}

digitalWrite(TSL_S2, pin_2);
digitalWrite(TSL_S3, pin_3);

return;
}
</pre>
<p><em>camera_controls.pde</em>:</p>
<pre class="brush: cpp; title: ; notranslate">

/*

-- Camera Control Functions

LightRails 1.0
A dynamic external exposure control system with
integrated intervalometer for time-lapse
photography

Copyright (c) 2008-2009 C. A. Church drone&lt; a_t &gt;dronecolony.com

This program is free software: you can redistribute it
and/or modify it under the terms of the GNU General
Public License as published by the Free Software
Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License
for more details.

*/

void fire_camera() {

digitalWrite(CAMERA_PIN, HIGH);
// start timer to stop camera exposure
MsTimer2::set(cur_exp_tm, stop_camera);
MsTimer2::start();

// update camera currently enaged
camera_engaged = true;

return;
}

void stop_camera() {

digitalWrite(CAMERA_PIN, LOW);
// turn off timer
MsTimer2::stop();

// update camera currently enaged
camera_engaged = false;
}
</pre>
<p><em>lcd_display.pde:</em></p>
<pre class="brush: cpp; title: ; notranslate">

/*

-- LCD Display Functions

LightRails 1.0
A dynamic external exposure control system with
integrated intervalometer for time-lapse
photography

Copyright (c) 2008-2009 C. A. Church drone&lt; a_t &gt;dronecolony.com

This program is free software: you can redistribute it
and/or modify it under the terms of the GNU General
Public License as published by the Free Software
Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License
for more details.

*/

void print_setup_display() {

// clear out lcd print buffer

memset(lcd_print_buffer, 0, SIZE_OF_LCD_BUF);

// get setup string from hash

strcpy_P(lcd_print_buffer, (char*)pgm_read_word( &amp;(setup_strings[setup_step])) );

lcd.clear();
lcd.setCursor(0, 0);

lcd.print(lcd_print_buffer);

// go to second line
lcd.setCursor(0, 1);

// determine which setup step to display

switch(setup_step) {
case 0:
lcd.print(iso_rating, DEC);
break;

case 1:
// handle rounding for higher f-stops

if( f_stop &gt; 7.1 ) {
print_float((float) int(f_stop));
}
else {
print_float(f_stop);
}

break;

case 2:
print_float(ev_adjust);
break;

case 3:
lcd.print(ev_steps, DEC);
break;

case 4:
lcd.print(camera_delay, DEC);
break;

case 5:
if( status &amp; B01000000 ) {
lcd.print('-');
}
else if( status &amp; B00100000 ) {
lcd.print('+');
}
else {
lcd.print('X');
}
break;

case 6:

lcd.print(min_exp_tm, DEC);
break;

case 7:

lcd.print(max_exp_tm, DEC);
break;

case 8:

lcd.print(freq_mult, DEC);
break;

case 9:

print_float(ttl_stop);
break;

case 10:

lcd.print(light_type, DEC);
break;

case 11:

lcd.print(ev_diff_ceiling, DEC);
break;

default:
break;
}

return;
}

void print_info() {

lcd.clear();

lcd.setCursor(0, 0);

// display set iso and f-stop
lcd.print(iso_rating);
lcd.print(' ');

lcd.print('f');

// handle rounding for higher f-stops
if( f_stop &gt; 7.1 ) {
print_float((float) int(f_stop));
}
else {
print_float(f_stop);
}

// if intervalometer on, show 'I' indicator
// and count of shots fired

if( status &amp; B10000000 ) {
lcd.setCursor(15,0);
lcd.print('I');

if( shots_fired &gt; 999 ) {
lcd.setCursor(11, 0);
}
else if( shots_fired &gt; 99 ) {
lcd.setCursor(12,0);
}
else if( shots_fired &gt; 9 ) {
lcd.setCursor(13,0);
}
else {
lcd.setCursor(14,0);
}

lcd.print(shots_fired, DEC);

}

// move to second line
lcd.setCursor(0,1);

if( disp_enable &amp; B10000000 ) {

// show exposure time in 1/x notation

if( exp_tm &gt;= 2 ) {
float disp_v = 0;

if( exp_tm &gt;= (float) int(exp_tm) + (float) 0.5 ) {
disp_v = int(exp_tm) + 1;
}
else {
disp_v = int(exp_tm);
}

lcd.print(&quot;1/&quot;);
lcd.print(exp_tm, DEC);
}
else if( exp_tm &gt;= 1 ) {
// deal with times larger than 1/2 second

float disp_v = 1 / exp_tm;
// get first significant digit
disp_v       = int( disp_v * 10 );

lcd.print('.');
lcd.print(disp_v, DEC);
lcd.print('&quot;');
}
else {
int disp_v = int( (float) 1 / exp_tm);

lcd.print(disp_v, DEC);
lcd.print('&quot;');

}
}

else if( disp_enable &amp; B01000000 ) {
// display actual exposure mS
print_ul(cur_exp_tm);
lcd.print(&quot;mS&quot;);
}

else if( disp_enable &amp; B00100000 ) {
// display frequency reading
lcd.print(&quot;Fq &quot;);
lcd.print(frequency, DEC);

}

else if( disp_enable &amp; B00010000 ) {

// display EV
lcd.print(&quot;EV &quot;);
print_float(ev);

if( ev_adjust &gt; 0 || ev_adjust &lt; 0 ) {
// show any adjustment made
lcd.print('[');
print_float(ev_adjust);
lcd.print(']');
}

}

else if( disp_enable &amp; B00001000 ) {
// uW/m2
print_float(uwcm2);
lcd.print(&quot;uW&quot;);

}
else if( disp_enable &amp; B00000100 ) {
// sensitivity level
lcd.print('S');
lcd.print(calc_sensitivity, DEC);

}

else if( disp_enable &amp; B00000010 ) {
// display lux
lcd.print(&quot;Lx &quot;);
print_float(lux);

}

else if( disp_enable &amp; B00000001 ) {
// divide-by-factor display
lcd.print('R');
lcd.print(freq_mult, DEC);
}

lcd.setCursor(14,1);

// minimum/maximum exposure time enabled?
if( status &amp; B00000100 )
lcd.print('M');

// exposure was latched?
if( status &amp; B00001000 )
lcd.print('L');

}

void print_float( float val ){

if ( val &lt; 0 ) {
lcd.print('-');
val *= -1;
}

print_ul( (unsigned long int) int(val) );
lcd.print('.');

// add 1 digit for hundreths precision
if( (val * 100) - (int(val) * 100)  &lt; 10 )
lcd.print('0');

print_ul( (val * 100) - (int(val) * 100) );

}

void print_ul( unsigned long val ) {

// print unsigned long to lcd

// clear out lcd print buffer

memset(lcd_print_buffer, 0, SIZE_OF_LCD_BUF);

ultoa(val, lcd_print_buffer, 10);
lcd.print(lcd_print_buffer);

return;
}
</pre>
<p><em>photo_calculations.pde:</em></p>
<pre class="brush: cpp; title: ; notranslate">

/*

LightRails 1.0
A dynamic external exposure control system with
integrated intervalometer for time-lapse
photography

Copyright (c) 2008-2009 C. A. Church drone&lt; a_t &gt;dronecolony.com

This program is free software: you can redistribute it
and/or modify it under the terms of the GNU General
Public License as published by the Free Software
Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License
for more details.

*/

/*

Functions to calculate photographic values
from light readings, the following functions are
found here:

calc_lux_single*
calc_lux_gauss
calc_ev
calc_exp_tm
calc_exp_ms

Last Modified: 2/29/2009, c.a. church
Original:      10/15/2008, c.a. church
*/

/*

We always use multiple wavelengths.  This function is
if you happen to be erm, you know, filming something illuminated
by a single wavelength light source.  Given the rarity of this
situation, you'll have to provide the luminous efficiency function
value yourself.

float calc_lux_single(float uw_cm2, float efficiency) {

// calculate lux (lm/m^2) for single wavelength, using standard formula:
// Xv = Xl * V(l) * Km
// Xl is W/m^2 (calculate actual receied uW/cm^2, extrapolate from sensor size (0.0136cm^2)
// to whole cm size, then convert uW to W)
// V(l) = efficiency function (provided via argument)
// Km = constant, L/W @ 555nm = 683 (555nm has efficiency function of nearly 1.0)
//
// Only a single wavelength is calculated - you'd better make sure that your
// source is of a single wavelength...  Otherwise, you should be using
// calc_lux_gauss() for multiple wavelengths

return( ( ( uw_cm2 * ( (float) 1 / (float) 0.0136) ) / (float) 1000000 ) * (float) 100   * efficiency * (float) 683 );
}

*/

float calc_lux_gauss( float uw_cm2 ) {

// # of wavelengths mapped to V(l) values - better have
// enough V(l) values!

int nm_cnt = sizeof(wavelengths) / sizeof(int);

// watts/m2

float w_m2 =  ( uw_cm2 * ( (float) 1 / (float) 0.0136 ) / (float) 1000000 ) * (float) 100;

float result = 0;

// integrate XlV(l) dl
// Xl = uW-m2-nm caclulation weighted by the CIE lookup for the given light
//   temp
// V(l) = standard luminous efficiency function

for( int i = 0; i &lt; nm_cnt; i++) {

if( i &gt; 0) {
result +=  ( spd_graphs[light_type][i] / (float) 1000000)  * (wavelengths[i] - wavelengths[i - 1]) * w_m2  * v_lambda[i];
}
else {
result += ( spd_graphs[light_type][i] / (float) 1000000) * wavelengths[i] * w_m2 * v_lambda[i];
}

}

// multiply by constant Km and return

return(result * (float) 683);
}

float calc_ev( float lux ) {

// calculate EV using APEX method:
// Ev = Av + Tv = Bv + Sv
// Bv = log2( B/NK )
// Sv = log2( NSx )
// K  = Meter Calibration Constant
//      14 = Pentax standard
// N  = constant relationship between Sx and Sv

float ev = ( log( (float)  0.3 * (float) iso_rating ) / log(2) ) + ( log( lux / ( (float) 0.3 * (float) 14.0 ) ) / log(2) );

// round down if EV step
// value is set to whole steps

if( ev_steps == 1 )
ev = (float) int(ev);

// convert to positive value temporarily, if needed
bool neg_ev = false;

if( ev &lt; 0 ) {
neg_ev = true;
ev *= -1;
}

if( ev &gt; int(ev) ) {

// if ev has a decimal value, determine nearest
// fraction of EV to round to, based on ev step
// setting

// handle rounding to nearest step

int rem = ( ( ev - int(ev) ) * 100 );
int step = 100 / ev_steps;

for (int i = ev_steps; i &gt; 0; i-- ) {

if( rem &gt;= step * i) {
rem = step * i;
break;
}
}

ev = (float) int(ev) + ((float) rem / 100);

}

// reset back to negative EV value
if( neg_ev == true )
ev *= -1.0;

// is there a ceiling set for maximum EV change?
// if so, handle only moving a maximum amount of &lt;ceiling&gt;
// steps between readings

if( ev_diff_ceiling &gt; 0 ) {

float diff = float_abs_diff( ev, prev_ev );
float ceiling = (float) ev_diff_ceiling / (float) ev_steps;

if( diff &gt; ceiling ) {

if( ev &lt; prev_ev ) {
ev = prev_ev - ceiling;
}
else {
ev = prev_ev + ceiling;
}

}

}

prev_ev = ev;

// if ev adjust enabled - apply it now

ev += ev_adjust;

// deal with TTL compensation
// each full aperture step results in one less
// EV read in, so adjust output
// up by one EV per stop

if( ttl_stop &gt; 0 )
ev += log( ttl_stop ) / log(sqrt(2));

return(ev);

}

float calc_exp_tm ( float ev, float aperture  ) {

// Ev = Av + Tv = Bv + Sv
// need to determine Tv value, so Ev - Av = Tv
// Av = log2(Aperture^2)
// Tv = log2( 1/T ) = log2(T) = 2 ^^ (Ev - Av)

float exp_tm = ev - ( log( pow(aperture, 2) ) / log(2) );

float exp_log = pow(2, exp_tm);

return( exp_log  );
}

unsigned long calc_exp_ms( float exp_tm ) {

return( (unsigned long) ( 1000 / exp_tm ) );

// if you wish to round actual mS for exposure to nearest
// exposure step (the actual exposure step displayed in 1/x
// format), un-comment the following code and comment the
// line above out.
//
// Mind you, that doing so will make the device
// no more accurate than your standard camera's meter.

/*
// deal with times less than or equal to half a second
if( exp_tm &gt;= 2 ) {

if( exp_tm &gt;= (float) int(exp_tm) + (float) 0.5 ) {
exp_tm = int(exp_tm) + 1;
}
else {
exp_tm = int(exp_tm);
}

return(1000 / exp_tm);

}
else if( exp_tm &gt;= 1 ) {
// deal with times larger than 1/2 second

float disp_v = 1 / exp_tm;
// get first significant digit
disp_v       = int( disp_v * 10 );
return( ( 1000 * disp_v ) / 10 );

}
else {
// times larger
int disp_v = int( (float) 1 / exp_tm);
return((unsigned long) 1000 * (unsigned long) disp_v);

}
*/

}

</pre>
<p><em>user_input.pde:</em></p>
<pre class="brush: cpp; title: ; notranslate">

/*

-- user input functions

LightRails 1.0
A dynamic external exposure control system with
integrated intervalometer for time-lapse
photography

Copyright (c) 2008-2009 C. A. Church drone&lt; a_t &gt;dronecolony.com

This program is free software: you can redistribute it
and/or modify it under the terms of the GNU General
Public License as published by the Free Software
Foundation, either version 3 of the License, or (at
your option) any later version.

This program is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License
for more details.

*/

void check_input() {

// this function determines if any button has been hit,
// and what to do about it

// up and down do not require a HIGH (unpressed) reading
// so that you can hold them down and quickly cycle through
// values (about 3 changes per second)

if( digitalRead(B_UP) == LOW &amp;&amp; millis() - button_hit &gt; 300) {

button_hit = millis();
take_button_action(0);
return;
}

if( digitalRead(B_DN) == LOW &amp;&amp; millis() - button_hit &gt; 300 ) {
//pre_dn = true;
take_button_action(1);
button_hit = millis();
return;
}

if( digitalRead(B_LT) == LOW) {
if(buttons &amp; B00100000 &amp;&amp; millis() - button_hit &gt; 300) {
buttons &amp;= B11011111;

take_button_action(2);
button_hit = millis();
return;
}

buttons &amp;= B11011111;

}
else {
buttons  |= B00100000;
}

if( digitalRead(B_RT) == LOW ) {
if( buttons &amp; B00010000 &amp;&amp; millis() - button_hit &gt; 300) {
buttons &amp;= B11101111;
take_button_action(3);
button_hit = millis();
return;
}
buttons &amp;= B11101111;
}
else {
buttons |= B00010000;
}

if( digitalRead(B_CT) == LOW ) {
if( buttons &amp; B00001000 &amp;&amp; millis() - button_hit &gt; 300) {
buttons &amp;= B11110111;
take_button_action(4);
button_hit = millis();
return;
}
buttons &amp;= B11110111;
}
else {
buttons |= B00001000;
}

}

void take_button_action( byte button ) {

// this function operates whatever action is desired when
// a button is pressed.  called by check_input()

switch(button) {
case 0:
// up hit

if( status &amp; B00010000 ) {

// we're in a setup screen...

// increasing value at current cursor position

change_setup_value(setup_step, 1);

// update flag to show change in display value
status |= B00000001;

return;
}
else {
// on main screen - up turns intervalometer on

// if intervalometer on already, do nothing

if( status &amp; B10000000 )
return;

// turn on intervalometer
status |= B10000000;
// save current exposure reading for latch
pre_exp_tm = exp_tm;

}

break;

case 1:
// down hit

if( status &amp; B00010000 ) {
// we're in a setup screen
// decreasing value at current cursor position

change_setup_value(setup_step, -1);
status |= B00000001;

return;
}
else {

// main menu - turn off intervalometer
// re-set latch matched (if set)
status &amp;= B01110111;
shots_fired = 0;
}

break;

case 2:
// left button was hit

// do nothing if in setup menu
if( status &amp; B00010000 )
return;

// main display, cycle through display values

if( disp_enable &amp; B10000000 ) {
// we were already left, move to the furthest right
// value
disp_enable = B00000001;
// set screen to update
status |= B00000001;
}
else {
// shift current display value left
disp_enable = disp_enable &lt;&lt; 1;
status |= B00000001;
}

break;

case 3:
// right button was hit

// do nothing if in setup menu
if( status &amp; B00010000 )
return;

// main display, cycle through display values

if( disp_enable &amp; B00000001 ) {
// we were already at furthest right value,
// need to move to furthest left value
disp_enable = B10000000;

// set screen to update
status |= B00000001;
}
else {
// shift current display right
disp_enable = disp_enable &gt;&gt; 1;
// set screen to update
status |= B00000001;
}

break;

case 4:

// center button hit

if( status &amp; B00010000 ) {
// in setup mode - we're going to change which
// setup value we're displaying

// set display to update
status |= B00000001;

if( setup_step == MAX_SETUP_STEPS - 1 ) {
// no more steps to go, exit menu
setup_step = 0;
// set that we're no longer in setup
status &amp;= B11101111;
return;
}

setup_step++;
return;

}
else {
// not in setup or input -
// go into setup mode, tell display
// to refresh
status |= B00010001;
return;
}

break;
}

}

void change_setup_value( byte which, int what ) {

switch( which ) {
case 0:

{

// change iso value, convert to logarithmic
// scale first, then add or subtract one step
// and convert back to arithmetic scale

float foo = log(iso_rating) / log(10);

int din = ( 10 * foo ) + 1;

din += what;

if( din &lt; 1 )
din = 1;

float bar = pow(10, (float) ( (float) din - 1) / (float) 10 );

if( bar &gt; int(bar) ) {
iso_rating = int(bar) + 1;
}
else {
iso_rating = int(bar);
}

}

break;

case 1:
{
// move f_stop value - move one third step
// either direction

float steps = log( f_stop ) / log( sqrt(2) );

if( what &gt; 0 ) {
steps += 0.3333;
}
else {
steps -= 0.3333;
}

if( steps &lt; 0 )
steps = 0;

f_stop = pow( sqrt(2), steps );

}
break;

case 2:
// ev adjust moes in 1/4 EV increments

ev_adjust += (float) what * 0.25;
break;

case 3:
// ev steps determines how many divisions of an EV
// there are when calculating automatic exposure
// values.  more steps = better granularity in exposure
// timing.

ev_steps += what;
if( ev_steps &lt; 0 )
ev_steps = 0;

break;

case 4:

camera_delay += what;

if( camera_delay &lt; 1 )
camera_delay = 1;

// ms delay time
real_camera_delay = camera_delay * 1000;

break;

case 5:

// cycle between latch settings

if( status &amp; B01000000 ) {
status &amp;= B10111111;
status |= B00100000;
}
else if( status &amp; B00100000 ) {
status &amp;= B10011111;
}
else {
status |= B01000000;
}

break;

case 6:

min_exp_tm += what;

// don't allow min time to be less than 0
if( min_exp_tm &lt;= 0 )
min_exp_tm = 0;

break;

case 7:

max_exp_tm += what;

// don't allow max time to be less than 0
if( max_exp_tm &lt;= 0 )
max_exp_tm = 0;

break;

case 8:

// change scaling:
// scaling value can be 2, 10, or 100
// allow wrap around from highest to lowest
// and vice-versa
freq_mult += what;

// we only get to 11 by going up from 10 and
// we only 1 by going down from 2 - next step
// either way is 100
if( freq_mult == 11 || freq_mult == 1 ) {
tsl_set_scaling(100);
}
else if( freq_mult == 9 || freq_mult == 101 ) {
// if we're moving down from 10, or up from 100
// move to lowest setting
tsl_set_scaling(2);
}
else {
// if neither of the above cases is true, then our only
// destination is 10
tsl_set_scaling(10);
}

break;

case 9:
{
// move ttl_stop value - move one third step
// either direction

float steps = log( ttl_stop ) / log(sqrt(2));

if( steps &lt; 0 )
steps = 0;

if( what &gt; 0 ) {
steps += 0.3333;
}
else {
steps -= 0.3333;
}

// for ttl stop, we want to get to zero, if possible
// for non-ttl readings.  We go ahead and skip every stop below
// 1.0 for the heck of it. (Very few lenses go below 1.0)

if( steps &lt; 0 ) {
ttl_stop = 0.0;
}
else {
ttl_stop = pow( sqrt(2), steps );

// handle rounding for higher f-stops
if( ttl_stop &gt; 7.1 )
ttl_stop = int(ttl_stop);
}

}
break;

case 10:

light_type = light_type == 1 ? 0 : 1;
break;

case 11:

ev_diff_ceiling += what;
break;

default:
break;
}

return;
}

</pre>
]]></content:encoded>
			<wfw:commentRss>http://dronecolony.com/2009/03/25/lightrails-dynamic-external-exposure-control-for-time-lapse/feed/</wfw:commentRss>
		<slash:comments>15</slash:comments>
		</item>
	</channel>
</rss>
