Skip to content

Instantly share code, notes, and snippets.

@paulbarber
Last active August 29, 2015 14:16
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save paulbarber/fd0467d912f3a2d644bd to your computer and use it in GitHub Desktop.
Save paulbarber/fd0467d912f3a2d644bd to your computer and use it in GitHub Desktop.
A full colour fading mood light based on the ATtiny85
/* moody.c ATtiny85_3pinPWM
P Barber
Jan 2014
Version 1.0
based on http://matt16060936.blogspot.co.uk/2012/04/attiny-pwm.html
Code for a mood light based on the ATtiny85
It uses 3 PWM outputs for smooth transitions between a theoretical 16 million colours!
This has two modes:
1: You can control and set the colour using two pots on pin 2 and pin 7. Pin 2 will control the hue and pin 7 the lightness in an HSL colour model.
2: If the lightness pot (pin 7) returns less than 5, it becomes a mood light that changes to random colours, the time between changes is controlled by the pot on pin 2.
To use it just as a rondom mood light connect pin 7 to GND, and pin 2 to GND for quick changes, Vcc for slow changes, or somewhere in between.
Pins 3, 5 and 6 are the RGB channels. Can't remember which is which but it does not really matter in the end.
Released under the GPL licence.
*/
//#define DO_NOT_FADE
//#define DO_NOT_RANDOMISE
#define F_CPU 8000000
#include <avr/io.h>
#include <util/delay.h>
// Convinience defines for the duty cycles
#define BLUE_DUTY OCR0A
#define GREEN_DUTY OCR0B
#define RED_DUTY OCR1B
#define MIN(X,Y) ((X) < (Y) ? (X) : (Y))
#define MAX(X,Y) ((X) > (Y) ? (X) : (Y))
typedef struct {
uint8_t r;
uint8_t g;
uint8_t b;
} rgb_colour;
// Global store of the current colour
rgb_colour gColour;
void setup_ATtiny85_3pinPWM()
{
/*
Setup PWM on io pins 0, 1, and 4
Use OCR0A, OCR0B and OCR1B to set the duty cycles
*/
DDRB = 1<<DDB4 | 1<<DDB1 | 1<<DDB0;
TCCR0A = 2<<COM0A0 | 2<<COM0B0 | 3<<WGM00;
TCCR0B = 0<<WGM02 | 1<<CS00;
TCCR1 = 0<<PWM1A | 0<<COM1A0 | 1<<CS10;
GTCCR = 1<<PWM1B | 2<<COM1B0;
}
void setColour(uint8_t red, uint8_t green, uint8_t blue)
{
// Expect 0-255 for each colour
RED_DUTY = red;
GREEN_DUTY = green;
BLUE_DUTY = blue;
// Store current values globally
gColour.r = red;
gColour.g = green;
gColour.b = blue;
}
void fadeColour(uint8_t red, uint8_t green, uint8_t blue, int time)
{
#ifndef DO_NOT_FADE
// Uses setColour, time is time in ms for complete fade
// This is general purpose which makes the code very big when compiled
int update_time = 20;
int steps = time/update_time;
float red_step = (float)(red - gColour.r)/(float)steps;
float green_step = (float)(green - gColour.g)/(float)steps;
float blue_step = (float)(blue - gColour.b)/(float)steps;
int i;
float r, g, b;
r = (float)gColour.r;
g = (float)gColour.g;
b = (float)gColour.b;
for (i=0; i<steps; i++){
r += red_step;
g += green_step;
b += blue_step;
setColour((uint8_t)r, (uint8_t)g, (uint8_t)b);
_delay_ms(update_time);
}
#else
setColour(red, green, blue);
_delay_ms(1000);
#endif
}
#ifndef DO_NOT_RANDOMISE
unsigned long m_w = 1;
unsigned long m_z = 2;
uint8_t getRandom255()
{
m_z = 36969L * (m_z & 65535L) + (m_z >> 16);
m_w = 18000L * (m_w & 65535L) + (m_w >> 16);
return (((m_z << 16) + m_w) % 255);
}
#endif
float RGB (float q1, float q2, float hue)
{
if (hue > 360.0) hue -= 360.0;
else if (hue < 0.0) hue += 360.0;
if (hue < 60.0)
return(q1+(q2-q1)*hue/60.0);
else if (hue < 180.0)
return(q2);
else if (hue < 240.0)
return(q1+(q2-q1)*(240.0-hue)/60.0);
else
return(q1);
}
int HLSToRGB (float H, float L, float S, float *R, float *G, float *B)
{
// adapted from
// http://astronomy.swin.edu.au/~pbourke/colour/conversion.html, Compiled by Paul Bourke, February 1994
float p1, p2;
if (L <= 0.5) p2 = L*(1+S);
else p2 = L+S-(L*S);
p1 = 2.0*L-p2;
if (S == 0.0)
{
*R = L;
*G = L;
*B = L;
}
else
{
*R = RGB(p1, p2, H+120.0);
*G = RGB(p1, p2, H);
*B = RGB(p1, p2, H-120.0);
}
return(0);
}
int main()
{
uint8_t r=0, g=85, b=10;
int l=0, h=0, count=0;
//int R, G, B;
float R, G, B, H, L, S;
setup_ATtiny85_3pinPWM();
// Setup ADC inputs 1 and 3 on pins 7 and 2
ADMUX = ( ( 1 << ADLAR ) | ( 1 << MUX1 ) | ( 1 << MUX0 ) ); // left adjust and select ADC3 to start with
ADCSRA |= ( ( 1 << ADEN ) | ( 1 << ADPS2 ) | ( 1 << ADPS1 ) ); // ADC enable and clock divide 8MHz by 64 for 125khz sample rate
DIDR0 |= ( 1 << ADC3D ) | ( 1 << ADC2D ); // disable digital input on analog input channel to conserve power
for (;;){
// setup ADC3 (pin 2)
ADMUX = ( ( 1 << ADLAR ) | ( 1 << MUX1 ) | ( 1 << MUX0 ) );
// read pot
ADCSRA |= ( 1 << ADSC ); // start the ADC Conversion
while( ADCSRA & ( 1 << ADSC )); // wait for the conversion to be complete
h = ADCH;
// setup ADC1 (pin 7)
ADMUX = ( ( 1 << ADLAR ) | ( 1 << MUX0 ) );
// read pot
ADCSRA |= ( 1 << ADSC ); // start the ADC Conversion
while( ADCSRA & ( 1 << ADSC )); // wait for the conversion to be complete
l = ADCH;
if (l>5){ // normal pot set mode
H = (float)(h*360.0/255.0);
S = 1.0;
L = (float)(l/255.0);
HLSToRGB (H, L, S, &R, &G, &B);
r = (uint8_t)(R*255.0);
g = (uint8_t)(G*255.0);
b = (uint8_t)(B*255.0);
setColour(r, g, b);
count = 0;
}
else {
if (count ==0){ // enter auto mode with rgb signal
setColour(255,0,0);
_delay_ms(100);
setColour(0,255,0);
_delay_ms(100);
setColour(0,0,255);
}
count++;
if (count > h*20){
fadeColour(getRandom255(), getRandom255(), getRandom255(), 1000);
count = 1; // reset counter, but set to 1 so we do not get rgb signal again
}
}
_delay_ms(50);
}
return (0);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment