Spark-controlled gamelamp

From Electriki
Jump to: navigation, search
Fig.1: cheap piezo-lighter. These things cost EUR1.65 or something here; they look very nice, and are easily moddable.
Fig.2: lighter, modified. Used Dremel(tm)-tool to cut away the top of the cylindrical protection and the tip; result is a nice 1cm-long spark alongside the top.
Fig.3: reset-line when sparked. This was with a small (approx 5cm diameter) antenna, and holding the lighter quite close.
Fig.4: zoom of sparked reset-line activity. The whole influence is about 1ms; I am guessing the 1st dip there does the actual reset. (My scope is not perfect either.) There was no real visible effect on the power-line, and sparking without antenna attached to reset-line had no effect at all.
Fig.5: very sucky picture of flame-emulation. Yes, my Gimp(tm)-skillz suck. Basically, when reaching a setpoint-intensity, new random intensity and time are chosen, and a timer-interrupt ramps from current to new setpoint over the given time.
Fig.6: schematics. Very simple; the cap with resistors and ADC on pin PB4 acts as timer.
Fig.7: near-linear charging of capacitor at start of curve. Charging a 100uF cap usign 3.3V and 100k is slow. Shown here is a 2.5 second period, charging the cap to about 20% of Vcc. As long as we don't really use things taking (much) longer than say 2 seconds, let's assume the cap charges at about 250mV/s. Combine this with an ADC, and sito presto, we have a clock.
Fig.8: discharging the cap. Before each time-measurement, the cap is discharged through a 100 Ohm resistor to (near) ground. The resistor is enough to limit sink-current to a nice 30mA or so. After 50ms, the cap is drained enough to begin a new measurement.
Fig.9: game-mode: game over when imitating T2. T1 and T2 are target-times, and T'1 and T'2 are the user's best effort. The user presses too late: delta_V between target and user (the 2 marked points) is about 112mV, corresponding to about 440ms (which is < 500ms, which is actually the threshold, but ok, scope display resolution + ADC jitter = I believe it :-). The blue part is where the MCU emits the target-pattern, the green area is where it waits for the user, and the red area is where the user tries to mimic the pattern.
Fig.10: done soldering. Used a can of paint to wind a nice 10cm diameter antenna, soldered it to reset, and tested with single LED on a string. The battery is a lithium 1200mAh 3.6V cell, good for many hours of fun.
Fig.11: LED-nest. Soldered 8 sandpapered LEDs and 8 resistors together to form a omnidirectional(tm) lightsource.
Fig.12: LED-nest lit up. One of my lamps is very translucent; the sandpapering of LEDs helped, but still, individual LEDs could still be seen through the lamp shade.
Fig.13: diffused using bread-bag. It won't help much for intensity, but this bag was nice to diffuse the light a bit more.
Fig.14: inserting board into lamp. Lowering LEDs into lamp; the antenna goes at the top, which worked fine (when 'lighting' the lamp from the bottom), to my surprise.
Fig.15: action. So this is what it looks like. Not very bright; not very useful during daytime, but very cosy whilst watching DVD :-)

Introduction, or: Do we really need this?

(Well yes we do!) I happen to have 3 Chinese-style lanterns hanging in my living-room. They are red, and very nice, but there's no light in them. I also happen to have one very nice wood-and-glass lamp, with translucent windows, but again, without a lamp.

I Considered...

  • putting an actual candle in them (nice effect, bad idea, not doign that again soon)
  • pulling a wire over the ceiling to each lamp (much work)
  • shining some fixed light through the (red) lamp from behind it, giving nice effect (cumbersome)
  • doing nothing (note that this is the most CBA-compliant option)

I basically like a flame-like light, remote-controlled.

El-cheapo remote

To be interesting here, a remote-control mechanism would have to be

  • cheap
  • simple
  • power-friendly, receiver-wise

An electrical spark is a nice simple event; if one could trigger something with it, the mere maximum distance to the receiver might be enough to control multiple targets in the same room.

Transmitter

They sell cheap piezo-lighters everywhere; I know there are ones that emit a 'sequence' of sparks, and those that emit a single spark when pressed/activated. Some have a built-in gas-container, some have not, and are used for lighting stoves and such.

Picked up a cheap 'one-spark-only' lighter (fig.1), emptied the gas-container, and behold, a spark-generator.

When working with the 'receiver' (next), the metal protective cylinder had to be shortened to expose the spark and cause any effect at all; modified version can be seen in fig.2. Dremel(tm) to the rescue, once again.

Receiver

The friendly people on #electronics mentioned some (older) AVR MCU's reset by itself when a spark was near - excellent :-) I had a ATtiny13 in mind for this thing, but unfortunately it seemed very robust to sparks, even when nearby.

Attaching a circle-shaped antenna to the reset-pin, combined with slow startup-time, made the device nicely reset once at every spark. Tested this with a simple board with ATtiny13 that would flash a LED when starting execution; worked like a charm. See fig.3 and fig.4 (zoomed) for the voltage on the reset-pin during an attack.

Flame-lamp

Now for the lamp-effect itself. I know there are some imitation candle-lights you can charge using induction and then put on a table; the ones I saw have a single LED, and alternate between 2 intensities using a randomish pattern. Such flickering would (I guess) be very irritating in a living-room, so let's not do that.

Imitating a flame

Instead, gradually changing intensity from random setpoint to another random setpoint sounded more like it. To do this, I generated a 53-byte list of random bytes, and stored it in EEPROM. The time in which to change between 2 setpoints was also chosen from this list. See fig.5 for an idea; it's all very simple.

A routine simply iterates through the values to get time- and setpoint-values, without much magic. A timer-interrupt then ramps from previous to new setpoint using ATtiny13's 'fast PWM' mode; when the new setpoint is reached, new intensity-/time-values are taken from the list, and a new ramp is started.

Without any obvious highs and lows in the displayed pattern, the repetition is not disturbing at all, and will (probably) not be noticed.

Different light-modes

By varying the ranges of setpoint-intensities and time, different effects can be made: for a 'calm' flame, one would use a relatively small intensity-range, and relatively long ramp-time. For a 'wild' flame, one would use shorter times and a wider range of intensities. Limiting intensities and times is done using division and adding offsets to values from the random list.

I also wanted 2 'steady' modes - low and high intensity. This is done by simply replacing obtained random values by constants; ramp-time is still variable, but harmless.

The user can iterate through different modes as follows (prepare for ASCII-art; yes, I am too lazy to install/use Graphviz):

     +---------------------------------------------------------------------+
     |                                                                     |
     V                                                                     |
  +-----+   +-------------+   +-------------+   +--------+   +-----------+ |
  | OFF |-->| CALM_FLAME  |-->| WILD_FLAME  |-->| ON_DIM |-->| ON_BRIGHT |-+ 
  +-----+   +-------------+   +-------------+   +--------+   +-----------+ 

In mode 'OFF', the MCU enters deep sleep (to be awoken by reset); in all other modes, different light-patterns are displayed as described above.

To iterate through the modes, ...

  1. user presses lighter
  2. MCU resets and restarts execution
  3. current mode is read from EEPROM
  4. next mode is written to EEPROM
  5. mode-dependent behaviour until reset (except in 'OFF'-mode)

Bonus-feature: "repeat the pattern"

So, all of the above was about 550 bytes of flash, without any serious optimising. A bit of a waste; the ATtiny13 has 1k of flash. So why not make something fun! (eventually, everything fitted into 1022 bytes, with slight optimisation; nothing like the CRAFT ATtiny2313 demo, no... :-) You can take a look at all the code if you want.

MB, the WII and probably many others offer a "repeat the pattern" game - the computer emits a pattern of some sort, and the user has to repeat it (or be sent to Mordor).

So let's do that: a game mode, where the lamp emits randomly-timed light-flashes, and the user has to repeat it with the lighter: press the lighter exactly in the same pattern as in which the flashes appeared.

Keeping track of time

An interesting thing is, since all the user can do is press the lighter and effectively reset the MCU, how to keep track of time. Timer-counters and other things will be set to power-on defaults after each reset.

To have a resettable counter that is not influenced by resetting of the MCU itself, I chose a simple capacitor, being charged by a big resistor to Vcc (i.e. 'counting'), or drained through a small resistor to gnd (i.e. 'resetting'). The pin draining the cap is also one of the MCU's ADC-pins, and can thus serve to read the capacitor-voltage (i.e. 'reading the counter'). See the schematics for an idea.

Because charging a cap this way is not linear at all over the whole voltage-range, let's only look at the first bit of the curve (fig.7): charging from 0 to about 20% of Vcc (20% * 3.2V is about 632 mV) is near-linear, and takes about 2.5 seconds.

The ATtiny13 has a 10-bit ADC, basically meaning that resolution is Vcc/1024. The ADC-readout consists of 2 bytes, but since we only look at, say, the first 20% of the range, only 8 bits (i.e. LSB) are enough.

The counter is reset (i.e. cap is drained) through a 100 Ohm resistor to the low-driven ADC-pin. This ensures not more than the maximum 40mA sink-current flows. (It's only for a short time but ok - we're that nice.) As can be seen in fig.8, a fully charged cap is drained in about 50ms.

Game-FSM

Basically, 'gameplay' will consists of iterations

  1. show pattern to user
  2. read pattern (presses A, B, and C, with time T'1 between A and B, and time T'2 between B' and C) from user
  3. 'game over' (shut down) as soon as failure is detected (i.e. T'1 or T'2 not as expected)
  4. congratulate user on getting it right

After each succesful iteration, a new game can be started right away.

For the above to fit into the already existing lamp-modes, an extra 'mode' is introduced:

   +---------------------------------------------------------+
   |                                                         |
   V                                                         |
+-----+   +-------------+           +-----------+   +======+ |
| OFF |-->| CALM_FLAME  |--> ... -->| ON_BRIGHT |-->| GAME |-+ 
+-----+   +-------------+           +-----------+   +======+ 

The 'GAME' mode itself consists of a state machine as well:

             |    
             |    
.............|.............................................................
:            |                                                            :    
: GAME       |                             (win)                          :    
:            |  +-------------------------------------------------+       :    
:            |  |                                                 |       :    
:            V  V                                                 |       :    
:          +------+   +------+   +---------+   +---------+   +---------+  :    
:          | WAIT |-->| SHOW |-->| CHECK_A |-->| CHECK_B |-->| CHECK_C |  :    
:          |      |   |      |   | _WAIT_B |   | _WAIT_C |   |         |  :    
:          +------+   +------+   +---------+   +---------+   +---------+  :    
:            |            |           |             |             |       :    
:  (timeout) |      (key) |    (lose) |      (lose) |      (lose) |       :    
:            |            V           V             V             |       :    
:            |<---------------------------------------------------+       :    
:            |                                                            :
:............|............................................................:
             |    
             |    
             V

Functionality of each game-state is shortly described below. When 'save state XXX' is mentioned, what is meant is that state 'XXX' is written to EEPROM, so that on next reset, execution resumes at 'XXX'. This is used to enter a certain state when the lighter is pressed within some time, and to fall back to another state (most likely 'OFF') on timeout.

WAIT

This is where the MCU waits to see whether the user wants to play a game or not:

  1. reset timer
  2. save state SHOW
  3. blink LED for 2 seconds
  4. if timeout, game over (where 'game over' means blink LED in sad pattern, save state 'OFF', and reset MCU from software

When the user presses the lighter within the blink-period, SHOW is entered after the following reset; if not, the lamp shuts down.

SHOW

The MCU displays the target-pattern:

  1. get random random-list position by using time spent in WAIT
  2. get random values for T1 and T2
  3. save state OFF
  4. wait 1 second; user waits in suspense...
  5. blink LED once and reset timer
  6. wait T1
  7. store ADC-value corresponding to current cap-voltage (thus, corresponding to T1)
  8. blink LED once and reset timer
  9. wait T2
  10. store ADC-value corresponding to current cap-voltage (thus, corresponding to T2)
  11. blink LED once and reset timer
  12. save state CHECK_A_WAIT_B
  13. if timeout, game over

The timer is reset in between LED-flashes A, B and C, the MCU reads its own timer-value just before resetting the timer to learn T1 and T2.

CHECK_A_WAIT_B

User pressed to imitate flash A;

  1. reset timer and blink LED for acknowledgement
  2. save state CHECK_B_WAIT_C
  3. if timeout, game over

Since A (the 1st of 3 target-flashes as given in state SHOW) is just an indication that the user has started to mimic the MCU's pattern, no time-checking can be done yet. However, the timer is reset to measure the user's T'1, next.

CHECK_B_WAIT_C

Time T'1 is verified, and MCU waits for C:

  1. verify measured time T'1 against stored T1 in EEPROM; game over on failure
  2. reset timer and blink LED for acknowledgement
  3. save state CHECK_C
  4. if timeout, game over

Since the user pressed to mimic flash B, time T'1 between A and B can be measured and verified. Likewise, the timer is reset to prepare for measurement of T'2.

CHECK_C

Time T'2 is verified:

  1. verify measured time T'2 against stored T2 in EEPROM; game over on failure
  2. blink LED for acknowledgement
  3. save state WAIT
  4. display 'congratulations' light-effect (it's really nice! ;-)
  5. wait 1 second
  6. reset MCU from software

When the last mimiced time T'2 matches, the game is won; the user is treated to a superb show, and is given the option to play again (by re-entering WAIT).

A demo

Behold, demo of all modes and playing a nice game:


Have fun -- Michai