I have a few bad habits. One is using digital tech where analogue would do.
This might be a classic example.
I wanted to create something to give a bit of decoration and atmosphere for a microfestival thrown by some friends. I thought I could put some LEDs into normal latex party balloons, figuring it would diffuse the light to give a pleasant all-over glow. To make it more interesting, I decided it should fade in and out, and to add further variation amongst the fleet/flock/flashmob of balloons, the rate of fade should be randomised.
I didn’t have long to make them, and I needed quite a few, so my first thought was to ease of manufacture and assembly. I’d thought about LED “throwies”. If you don’t know, you can’t normally connect an LED directly to a power supply- either the voltage is too low, and it doesn’t conduct, doesn’t turn on- or it is enough, and suddenly is has very low resistance, too much current passes, and it kills itself. So usually, you need either a constant current power supply (limits itself to a safe amount of current), or to put a resistor in the way.
LED throwies don’t bother with a resistor. Or rather they don’t use an external, discrete resistor, instead relying on the comparatively large internal resistance of the power source, a 3V lithium coin cell- something like a CR2032. Resistors are very very cheap, but they’re an extra component, so the throwie concept is ideal for rapid manufacturing.
And if the LED was happy, then my chosen control chip- an Atmel ATtiny45, would be happy- they can handle 40mA per output, way more than the little 5mm LED would need or draw.
Why A Microcontroller?
Firstly, LEDs don’t work well by varying the voltage to try to dim them. Instead, we use PWM. Pulse Width Modulation involves rapidly turning the output on and off- fully on and fully off at least thousands of times per second. When the LED is switched on, it is working at maximum efficiency, and when off, it uses no power. By varying the ratio of on to off, you trick the human eye into thinking it is dimmer or brighter. To do this without a microcontroller, with just resistors, capacitors and transistors you would need quite a bit of circuitry. And you’d need even more components to add in the random dimming rate behaviour. Instead, I’d use the AT45 like a glorified transistor, interposed between the LED and the battery, using the built-in PWM capability to set the light level, and iterating over all the PWM duty cycle values, pausing between PWM duty cycles to create the different time from off to brightest.
Note that you can’t go linearly from dim to bright, from 0% duty to 100% duty, because the human eye doesn’t perceive light that way. So in the code below, lines 51 and 71, you can see the value is scaled for apparent brightness.
Randomness
For a source of randomness on a device that has no time-of-day clock (something that would be hopeless for crypto purposes, but fine for a toy), I sampled a disconnected ADC (analogue to digital convertor). With no direct connection, it is said to be “floating”, which should give me very minor fluctuations in the reading. Because it’s only minor, only the lower order bits will be changing randomly, so I repeated sampled the pin, using each reading to build a 7 bit number.
Another plan was to sample the on-board thermometer, also connected to an ADC. Again, it’s not a good thermometer for anything other than getting a rough suggestion of thermal jeopardy the chip is under, but I didn’t want accurate, I wanted jumpy and unpredictable. But it simply wasn’t necessary, in the end.
Construction
I was able to solder the thing together using straightened out paperclips to make contact with the coin cell and to raise the device up out of the balloon’s neck, and I could then inflate the balloon, twist the neck, and use a mini pony clamp (like a big bulldog clip) to hold the balloon closed in a reversible way, in case I needed to swap the battery out. I then duct taped the clip to a bamboo cane, and stuck them in the ground. Note the bit of heatshrink around one leg of the paperclip terminals, to avoid shorting the cell.
Battery life was fairly decent, and easily lasted 12 hours, way more than I needed- in daylight, the effect was invisible.
Doing the same for green LEDs was more complicated. One cell wasn’t enough, so I needed to connect two cells in series. Fine, but then that 6V was too much for the microcontroller. So I have to have a centre-tap for the battery pack, so I had a dual-supply power source! Then, to power the LED, I needed to use the AT45 to switch on a transistor that fed the full 6V into the LED. Definitely more fiddly to construct, and not really worth it.
Improvements? Well, more brightness would have made them more adaptable to indoor use, where they would need to compete against light reflecting off the white latex surface. I didn’t have any of the units fail in use, but I was probably lucky. I could have encased them in epoxy and made them a lot more durable, albeit making them permanently single-purpose (I later used some of the ATTINY45s for other projects).
Here’s the code, compiled in Atmel Studio:
[code lang=”c”]
#define PWMSTEPS 2048
#define MINB 75
// IllumBalloon
// by Phil Bambridge
// v1.0 (2010/08/03)
// This is for an ATTINY25/45/85
// with an LED connected from GND to pin 5 (PB0)
// assuming the power source is current-limited enough
// to not need a resistor- such as a lithium coin cell.
#include <avr/io.h>
int main(void)
{
unsigned long int i; // Current brightness setting
unsigned int scaledi; // Power-law scaled value of i
unsigned int x; // PWM counter
unsigned int randseed; // ADC noise derived entropy seed
randseed = 0;
unsigned int d; // Delay counter
unsigned int delay; // Delay amount
// Setting clock to 4MHz
CLKPR = 0|(1<<CLKPCE); // Enable clock divisor change
CLKPR = 0|(1<<CLKPS0); // Write new divisor bits (divisor is factor of 2).
DDRB = 1; // Output on PB0/OC0A
// Sample an ADC port to get entropy. Might we do better to use the thermometer?
ADMUX = (1<<MUX1) | (1<<MUX0);
ADCSRA = (1<<ADEN) | (0<<ADATE) | (0<<ADPS2) | (1<<ADPS1) | (1<<ADPS0);
DIDR0 = (0<<ADC0D) | (0<<ADC1D) | (0<<ADC2D ) | (1<<ADC3D);
// Value of 0 to 127 should be PLENTY
for (x = 0; x < 7; x++) {
ADCSRA = ADCSRA | (1<<ADSC); // Say we would like to perform a conversion
while( ADCSRA & (1<<ADSC) ) {} // Poll the register for availability of bits
randseed += ((ADCL & 1) << x); // Take only least significant bit of the least significant byte, hopefully getting random noise.
}
ADCSRA = (0<<ADEN); // Switch off the ADC
delay = 1 + (randseed & 011111); // Ergo delay is 1 to a maximum of 17. At 4MHz this is approx 170 seconds.
while (1) { // The main loop, runs until power disconnected
for (i = MINB; i < PWMSTEPS; i++) { // This outer loop iterates over possible perceived brightness values.
if (i == PWMSTEPS – 1) { delay = 1000;} // Ideally, randomise the max brightness hold time here
scaledi = (i * i) / PWMSTEPS; // TODO: Try a logarithmic scaling, or multi-point envelope.
for (d = 1; d <= delay; d++) { // This middle loop is the delay- how many times do we use the same PWM setting.
for (x = 0; x < PWMSTEPS; x++) { // This inner loop is the PWM control- mark/space ratio.
if (scaledi > x) { // This is where we test for if the light should be on or off, given the current brightness
// setting. We could change the test here to provide different scalings to better match the
// eye’s response to light.
PORTB = 1;
} else {
PORTB = 0;
}
}
}
}
delay = 1 + (randseed & 011111); // reset that delay
// Now we start to decrease brightness
for (i = PWMSTEPS; i >= MINB; i–) { // Note the loop running in the opposite direction this time.
if (i == MINB) { delay = 1000;}
scaledi = (i * i) / PWMSTEPS;
for (d = 1; d <= delay; d++) {
for (x = 0; x < PWMSTEPS; x++) {
PORTB = (scaledi > x) ? 1 : 0; // Why not use the ternary operator for compactness?
}
}
}
delay = 1 + (randseed & 0x1F); // reset that delay
[/code]