Intelligent xmas decoration

From Electriki
Jump to: navigation, search
Fig.1: schematics. A boost-converter generates 3.3 V for the MCU (V_OUT), LED-currents are sinked (sunk?) by the MCU using a shared series-resistor, and light-intensity is obtained by measuring how long it takes to charge a cap (V_LDR).
Fig.2: PCB-layout, as seen in Kicad. Red traces are on the front; the back only has a battery-holder. The blue circle on the upper left is the LDR; the big IC is the MCU, and the SOT23-5 is the DC/DC converter. The 3 pads on either side are ISP-signals for programming and debugging.
Fig.3: the Octopus. Programming happens through this adapter. The programmer I use here is a serial Atmel AVRISP dongle.
Fig.4: measuring light-itensity. Blue indicates the voltage over the cap, charged through the LDR, while yellow is merely for debugging. The yellow positive spike indicates charging to 2/3 Vcc is complete.
Fig.5: front of PCB. 9V battery and 1 EUR coin are shown for reference. The yellow led is currently PWM'ing.
Fig.6: back of PCB. Battery holder shown is actually not for LR44, but for a thinner battery, because proper holders hadn't arrived yet.
Fig.7: copy-paste. All of these but one (which one?) have LR44-sized battery holders. I didn't bother removing excess flux.


Fact: LEDs are nice, PWM is nice, and coin-cell batteries are cheap (1 EUR for 6x LR44). Last year, I wanted to make an xmas-decoration using many small 'xmas star' type lights, where LEDs of different colour were PWM'd in a random pattern. Sadly, the CBA prevented that.

The idea then was to use two 1.5 V coin-cell batteries pressed against each other, a piece of veroboard, and a PDIP-8 MCU. Since then, I have been enlightened, and have started to use proper layouts for stuff like this.

General operation

In short, the device toggles between sleep- and run-mode depending on changes in ambient light. In particular, it responds to 'light turned on' and 'light turned off' events, defined as changes in light-intensity above a certain magnitude within a certain time.

From the moment the battery is inserted, the device would do the following:

  1. wake up every second using watchdog timeout, and measure ambient light intensity
  2. if a well-timed 'light off' / 'light on' / 'light off' sequence is seen, enter run-mode, else go back to sleep
  3. in run-mode, PWM the LEDs on/off in random pattern until ambient light intensity rises more than a certain threshold, then go back to sleep

In addition, while in sleep-mode, the battery-voltage is periodically checked; if it drops below a threshold (0.9 V - this is about the minimum for the boost-converter to make 3.3 V out of), blink the red LED every second.


(See fig.1 for schematic.)


To save size and experiment with new stuff, instead of 2 coin-cells to make 3 V (enough to drive a LED and feed a >1.8 V MCU), a DC/DC-converter was used. I chose the TI TPS61097-33DBVT (datasheet at Farnell) boost-converter, which has a quiescent current of about 10 uA, and can deliver 3.3 V from an input ranging from at least 0.9 V to 5.5 V.

The datasheet suggests two 10 uF caps and one 10 uH inductor as external components, so let's do that as well. I fried at least 1 inductor in the process. This thing just worked, and worked very nicely, I was impressed.

Measuring light-intensity

Ambient light intensity is measured using a constant voltage (Vcc) charging a cap through an LDR, and measuring the time it takes to charge to a certain voltage. As LDR-resistance decreases with light-intensity, shorter charge-times indicate brighter ambient-light. The cap is used instead of a resistor voltage-divider to minimise current.

Measurements are taken periodically, using the following sequence:

  1. drain the cap by discharging it through a MCU-pin
  2. let it charge through the LDR, and measure charged voltage about every millisecond (arbitrary time)
  3. if 3 consecutive samples indicate > 2/3 Vcc (2.2 V), measurement is complete
  4. total measurement-time from draining to charging the cap is converted to a percentage of 'darkness'.

In fig.4, the yellow low-time to the left of the spike is N * (1 ms + sampletime), and the low-time to the right of the spike is N * 1 ms, just indicated for debugging (i.e. to read the actually calculated percentage). The delay between the 2nd cursor and the spike is the verification that 3 consecutive samples indeed read > 2/3 Vcc.

The kind people on #electronics mentioned it was probably not such a bright idea to discharge the cap without resistor through an I/O pin, and that's true, but let it be a wise lesson for the next time, and let it Be So.

Resistance of an LDR probably doesn't vary linear with light-intensity, and what's more, I don't know whether what my eye percieves as 'twice as bright' is actually twice the intensity anyway. I simply chose the 0..100% scale to be somewhat sane:

  //   TL-lit hobbyroom, uncovered             :  <5
  //   TL-lit hobbyroom, hand 10cm over LDR    :   13
  //   TL-lit hobbyroom, hand 1cm over LDR     :   75
  //   2nd light hobbyroom, uncovered          :   18
  //   2nd light hobbyroom, hand 10cm over LDR :   80
  //   2nd light hobbyroom, hand 1cm over LDR  : >100

('hobbyroom' is some arbitrary room, 'TL' is fluorescent tube light, and '2nd light' is some halogen light further away)


The trustful ATtiny13 (1 kb flash, 63 bytes RAM if I'm not mistaken) would be more than enough for this. In the future, perhaps ATtiny22 or similar, or PIC10f200 (both SOT23) are nice for such stuff.

Clock is selected to be internal 128 kHz resonator for minimum power-consumption. While not active, power-down mode is entered, to be woken up by (dummy) watchdog timeout interrupt each second. Power-down mode consumes about 6 uA; active mode about 200 uA (which is much less than an active LED, which is in the > 2 mA range).


The software is implemented using a classic 'main loop' and 2 coroutines, inspired by Adam Dunkels' Contiki coroutines. Main loop is as follows:

for ( ;; ) {
  TASK_monitor_darkness( &enable );
  if ( enable ) {
  else {
    if ( !batt_is_ok() ) {
      blink_red( 1, 0 );

In words: an 'enable' flag is set/cleared from 'TASK_monitor_darkness', and indicates whether to display the xmas-effect or not. Another task does the actual xmas-effect ('TASK_pwm_leds'). These 'tasks' each implement a state-machine using coroutines.

In short, a coroutine is a procedure that can be re-entered at a previous exit-point. Some ways to implement them are generic C 'switch'-constructs with a static variable indicating the label to jump to, or GCC 'labels as values' C-extension, where you can dereference a label. I chose the latter, because it produces smaller code. See the C header for definition and example.

The PWM-task is simple, and basically reads EEPROM values, 4 bits at a time, and selects LED to use - or none - for this iteration (2 bits), and delay between 2 PWM-ramps (2 bits). Each device is programmed with unique randomness-data.

Ambient-light monitoring task

The monitor-task keeps track of ambient intensity percentage history (current intensity, intensity of last measurement, and intensity of measurement before that), and continuously verifies it against a valid start-sequence for the xmas-show.

We are mainly interested in 'light goes on' and 'light goes off' events. These are defined as follows:

  • light goes off == darkness_percent[ N ] - darkness_percent[ N - 2 ] > threshold
  • light goes on == darkness_percent[ N ] - darkness_percent[ N - 2 ] < -threshold

(index being the number of the measurement, i.e. once every second)

Furthermore, 'light is stable' is defined as absence of either event.

Comparing darkness_percent[ N ] to darkness_percent[ N - 1 ] instead of [ N - 2 ] does not work in border-cases where the light goes off/on exactly during a measurement - an event could go by unnoticed. 'Threshold' is determined experimentally (and is 20).

After a 'light goes on' or 'light goes off' event, one additional measurement is skipped to deal with border-cases where light goes off (or on) exactly in the middle of a measurement, and the total increase (or decrease) in ambient darkness is > 2 * threshold. If this delay wasn't there, the sequence could be broken because an additional unexpected 'light goes off' (or 'on') event would be seen, after which sleep-mode would be re-entered.

When in sleep-mode, run-mode is entered if and only if:

  1. light goes off
  2. light stays off (or at least stable) for 1 to 3 seconds
  3. light goes on
  4. light stays on (or at least stable) for 1 to 3 seconds
  5. light goes off

When run-mode is entered, the darkness-percentage of that moment is latched. Run-mode is canceled when a measured darkness-percent is at least 'threshold' lower than the latched value.


Description from the youtube-site:

ATtiny13 @ 128 kHz, DC/DC converter, LR44 battery, LDR and 3 LEDs. Operation is as follows (shown in the video in this order): turn off the light; if light stays off for longer than 3 seconds, go back to idle mode. If light stays off for 1 to 3 seconds, then goes on again for 1 to 3 seconds, then goes off permanently, LEDs start to PWM on and off, from randomness-table in EEPROM (until the light goes on again). At all times, the light-intensity is measured about every second, and fed into state-machine controlling operation. When idle, a battery-check blinks the red LED every second if battery-voltage drops below 0.9V, which is about the minimum the DC/DC-converter can still turn into 3.3V. Power-consumption when sleeping is about 16 uA (6 for the MCU, and 10 for the DC/DC-converter).


20121215 - I plopped in new batteries, after removing the old ones (which had started leaking), removing corrosion + solder on contact pad, and applying new solder.

wiki.productivity *= 10000;

But of course:

< pit> one shall remove batteries BEFORE he lies down a device that would be unused for long period of time
< pit> you note this down
<@michai> I note this down at once.

(please note, I have OPS, and young Pit does not.)

Have fun -- Michai