Showing posts with label ATtiny. Show all posts
Showing posts with label ATtiny. Show all posts

Monday, May 17, 2010

Pixie-Dust Bottles




It occurred to me whilst making some RGB nightlights for the kids that it'd be fun if they could make the lights change to whatever colour they wanted; kinda like the Philips LivingColors lamp, but about $100 cheaper. Lin suggested that having a few of these (say 5 - 7) would give the kids more room for imaginative fun, so I set about making a prototype and then a few more when that was up and working.

The kids love them (which is always good) and have been claiming that the bottles contain pixie dust (hence the title of this post).

Can you guess what Ffion's favourite colour is? She claims that this is the 'right colour' for pixie dust.


The hardware is pretty straight forward. I used a potentiometer with an in-built on/off switch, like this one, to simplify things. I thought it'd look much nicer than having a separate switch. The rest of the parts are the same as for the RGB nightlights project. The trickiest part was the coding, mainly because I'm doing it late at night when my brain isn't working quite so well...

Ingredients:



1. Potentiometer with switch
2. ATtiny45
3. 8-pin IC socket
4. coin cell battery holder
5. RGB LED (common anode) - I bought mine from EMSL. These are 10mm diffused RGB LEDs.
6. hookup wire, solder etc.

Methods:

I soldered the coin cell holder onto the potentiometer first.

Then I hooked up the RGB LED to the DIP socket. I'm using common anode LEDs so the long LED lead is soldered onto pin 8 of the IC socket. I have the RGB channels/leads connected to pins 5, 6 and 7 and the potentiometer output (middle pin from the pot) connected to pin 3.




After that, all you have to do is solder on the potentiometer's middle terminal to the IC socket and finish off connecting the ground and power connections.




And there we have it. Some kind of strange Borg eye ready to be put somewhere.

We had some plastic kids drink bottles sitting in the recycling bin - they're made from white plastic so I thought they'd diffuse the light nicely. I drilled a hole in the bottle cap for the potentiometer shaft and fitted it all together. Finally, I added a knob (radioshack purchase) to make it a bit prettier and easier to use.



Coding:

I'd already worked out how to do the software PWM (pulse width modulation) and how to measure a variable voltage input using ADC (analogue to digital conversion) so all that needed to be figured out, for this project, was how to map the ADC input values to the desired range of colours.

I wanted to let the kids cycle through the entire spectrum in much the same way the previous night-lights cycled automatically.

So, in the RGB nightlights project there were 6 transitions/steps:
stepRed channel valueGreen channel valueBlue channel valuechanging channelcolour range
125500increase GreenRed to Yellow
22552550decrease RedYellow to Green
302550increase BlueGreen to Cyan
40255255decrease GreenCyan to Blue
500255increase RedBlue to Purple
62550255decrease BluePurple to Red


If we use a 10-bit ADC we have 1024 values (2^10) available for mapping to colours in the above steps. This means that each step can contain 170 values/colours (1024/6 = 170.666...).

So, to map the ADC value to a colour we first assign it to one of the 6 steps and then use it to determine the value of the varying channel (Red/Green/Blue) for that step.

There are 6 steps and each step can have 170 ADC values associated with it. So I binned the ADC values into steps like so:
stepADC value
1< 170
2< 342
3< 512
4< 683
5< 854
6>= 854


Here's an example to clarify: Say the potentiometer is set just over half way, then the ADC value should be between 512 and 683; this would put us in step 4 where the red channel is off, the blue channel is completely on (255) and the green channel is varying (in this step the green levels are decreasing from 255 -> 0 as the ADC value increases from 512 -> 683). We calculate the green level by translating the ADC range to 0 - 170 (in this case by subtracting 512) and then multiplying by a scaling factor (255/170) to transform the 0 -> 170 range to a 0 -> 255 range. If we're increasing the level of the channel (steps 1, 3 and 5) then we just use this scaled value as the channel value. If we're decreasing the level of the channel (as is the case for step 4) then we inverse the scaled value by subtracting it from 255.

Here's the bit of code that sets the RGB values (see the complete listing here: selectableColourLight.c):

#define SCALING_RANGE 170
#define SCALING_FACTOR 255/SCALING_RANGE

...

void setRgbLevels(uint16_t pValue)
{
 if(pValue < SCALING_RANGE)
 {
  mRgbValues[RED_INDEX]   = 255;
  mRgbValues[GREEN_INDEX] = pValue * SCALING_FACTOR;
  mRgbValues[BLUE_INDEX]  = 0;

 }
 else if(pValue < 342) //SCALING_RANGE * 2
 {
  mRgbValues[RED_INDEX]   = 255 - ((pValue - SCALING_RANGE) * SCALING_FACTOR);
  mRgbValues[GREEN_INDEX] = 255;
  mRgbValues[BLUE_INDEX]  = 0;
 }
 else if(pValue < 512) //SCALING_RANGE * 3
 {
  mRgbValues[RED_INDEX]   = 0;
  mRgbValues[GREEN_INDEX] = 255;
  mRgbValues[BLUE_INDEX]  = (pValue - 342) * SCALING_FACTOR;
 }
 else if(pValue < 683)//SCALING_RANGE * 4
 {
  mRgbValues[RED_INDEX]   = 0;
  mRgbValues[GREEN_INDEX] = 255 - ((pValue - 512) * SCALING_FACTOR);
  mRgbValues[BLUE_INDEX]  = 255;
 }
 else if(pValue < 854)//SCALING_RANGE * 5
 {
  mRgbValues[RED_INDEX]   = (pValue - 683) * SCALING_FACTOR;
  mRgbValues[GREEN_INDEX] = 0;
  mRgbValues[BLUE_INDEX]  = 255;
 }
 else
 {
  mRgbValues[RED_INDEX]   = 255;
  mRgbValues[GREEN_INDEX] = 0;
  mRgbValues[BLUE_INDEX]  = 255 - ((pValue - 854) * SCALING_FACTOR);
 }
}

Saturday, February 27, 2010

CharliePlexed LEDs

There are quite a few things I've wanted to try out and post about as I've been learning them (it seems like a good way to remember them!). For example, I have a half-written post on AVR ADC (analogue to digital converter - for reading inputs like potentiometers and light dependent resistors), one on using pulse width modulation (PWM), one on using LEDs as light sensors...etc, etc. well, so I don't always get round to finishing what I've started. I'm not sure those posts would even be popular and I've concentrated on real/physical/entertaining projects instead. Of course now I have a load of half written posts for almost finished projects... aaaaannyway, onto the point.

I've heard a lot about charlieplexing since starting out on the electronics adventure & the idea of controlling lots of LEDs from only a few microcontroller pins is very appealing. I've been wanting to make some interesting ways to play with the glow-in-the-dark wall and this has lead me to my first practical use for charlieplexing. I thought that a controllable row of LEDs would open up a lot of possibilities: scan back and forth for a sine wave; all LEDs on for caligraphy; random flash for making star-scapes; POV style message writing; printing patterns - hearts, smileys etc.

So, the problem is that with an ATtiny, if you don't want to mess with the reset pin, you only have 5 I/O pins available. I want to use two pins as input - a potentiometer and a push button. This would leave me 3 pins for controllign LEDs... not much to play with really. That's where charlieplexing comes in. I can control 6 LEDs using these 3 uC pins.

The wikipedia article on charlieplexing is pretty good, so I won't repeat what's already been said. Take a look at the tri-state logic part to see what's going on here. There are a load of Instructables which cover charlieplexing - this one on the theory is worth a read.

In the code below the LEDs are numbered slightly differently than exactly the same as in the wikipedia diagram (I just soldered up a prototype and the wiring is much easier to route with the wikipedia numbering). The trick is that we switch two pins to output and one as a high-impedance input (this is the 'floating' pin that is effectively taken out of consideration). One input is set high (+Vcc) and one is low (0v). Current is sourced by the +Vcc output pin and sunk by the 0v output pin. So, for each LED we have different data direction (DDRxn) and port (PORTxn) register settings.

In order to control the 6 LEDs in the diagram above we use the following settings:

LEDPIN1PIN2PIN3
1+Vcc0vInput
20v+VccInput
3Input+Vcc0v
4Input0v+Vcc
50vInput+Vcc
6+VccInput0v

The code below is just an initial test set-up where we activate LEDs in a scanning pattern (classic 'cylon eye'). It has all the basics necessary to implement the more complex behaviours. Having the structure array hold the PORTB and DDRB values makes controlling individual LEDs very easy. I'm hoping this will scale up to allowing PWM, but I haven't put much thought into that yet.

Hopefully I'll get a chance to make the UV LED gizmo this week and post about the results.

You can download the source-code from github or use the cut and paste options in the top right of the code box below.

/*
* charlieplex_test.c
*
* Running on an ATtiny45.
*
* Here we control 6 LEDs through 3 pins (PB0:2).
*
* In order to illuminate each LED we do the following:
*
* LED1 - PB0 & 2 output PB2 input. PB0 sourcing, PB1 sinking. Pull-up on PB2
* LED2 - PB0 & 2 output PB2 input. PB1 sourcing, PB0 sinking. Pull-up on PB2
* LED3 - PB1 & 3 output PB0 input. PB1 sourcing, PB2 sinking. Pull-up on PB0
* LED4 - PB1 & 3 output PB0 input. PB2 sourcing, PB1 sinking. Pull-up on PB0
* LED5 - PB0 & 3 output PB1 input. PB0 sourcing, PB2 sinking. Pull-up on PB1
* LED6 - PB0 & 3 output PB1 input. PB2 sourcing, PB0 sinking. Pull-up on PB2
*
* This little ASCII diagram shows the wiring and orientation of the 6 LEDs.
* The hyphens ('-') identify the cathode pins of the LEDs.
*
* PB0 ----------------------
*        |   -      |      |
*        1   2      |      |
*        -   |      |      -
* PB1 --------      5      6
*        |   -      -      |
*        3   4      |      |
*        -   |      |      |
* PB2 ----------------------
*
* Only a single LED can be illuminated at any point in time.
*
* Distributed under Creative Commons 3.0 -- Attib & Share Alike
*
*  Created on: Feb 27, 2010
*      Author: PaulBo
*/
#include <avr/io.h>
#include <util/delay.h>

#ifndef F_CPU
#define F_CPU 1000000UL
#endif

#define DELAY_TIME 50
#define N_LED 6

// see class comments for pin setting explanation
// unused pins are set to input with pull-up resistors activated
struct leds {
uint8_t mDdrB;
uint8_t mPortB;
} ledData[] = {
{0b00011011, 0b11100101},
{0b00011011, 0b11100110},
{0b00011110, 0b11100011},
{0b00011110, 0b11100101},
{0b00011101, 0b11100011},
{0b00011101, 0b11100110}
};

int main()
{
uint8_t i;
for(;;)
{
for(i = 0; i < N_LED - 1; i++)
{
DDRB = ledData[i].mDdrB;
PORTB = ledData[i].mPortB;
_delay_ms(DELAY_TIME);
}
for(i = N_LED - 1;i > 0; i--)
{
DDRB = ledData[i].mDdrB;
PORTB = ledData[i].mPortB;
_delay_ms(DELAY_TIME);
}
}
}