/* Trippy RGB Light Firmware for use with ATtiny2313 Chaos Communications Camp 2007 Mitch Altman 29-Aug-08 Distributed under Creative Commons 2.5 -- Attib & Share Alike */ /* The Trippy RGB Light uses 3 LEDs, one Red, one Green, one Blue, each fading at different rates so that when the light from the three LEDs add together, you get a lot of changing colors (you can get any color a computer monitor can get using different brightnesses of Red, Green, and Blue light -- this is the way computer monitors create color). Trippy! */ /* The Trippy RGB Light was hacked from Ladyada's MiniPOV3 kit. Ladyada has a great website about the MiniPOV kit, from which this project was hacked: http://ladyada.net/make/minipov3/index.html Her website also has a user forum, where people can ask and answer questions by people building various projects, including this one: http://www.ladyada.net/forums/ (Click on the link for MiniPOV). */ #include // this contains all the IO port definitions #include // definitions for interrupts #include // definitions for power-down modes #include // definitions or keeping constants in program memory /* This project has 3 LEDs: Red, Green, Blue. The light from these three LEDs should be diffused so that the light from them mix together (short sections of white drinking straws work well if you cover these with a translucent plastic salsa container). The firmware goes through a sequence, mixing various amounts of brightness of R, G, and B to create various different colors, and blinking and fading these colors to create a trippy effect. You can easily change this firmware to create your own sequences of colors! */ /* This project provides a good example of how to use Pulse Width Modulation (PWM) to fade a voltage up or down (in this case, the changing voltage is used to control the brightness of the Red, Green, and Blue LEDs). We will use the two hardware timers that are embedded inside of the ATtiny2313 set up to control PWM for the Green LED and the Blue LED. The two timers have 4 independent PWM outputs, but we'll only be using one output from each (for no particular reason). We will create PWM for the Red LED by manually pulsing it in a firmware loop. With PWM, we vary the amount of time an LED is on versus how long it is off. We repeatedly turn an LED on and off very quickly (so quickly you can't see it blink). If the LED is on 50% of the time (and off for 50% of the time), it will be half as bright as if it were on all of the time. If the LED is on 25% of the time (and off for 75% of the time), it will be one quarter as bright as if it were on all of the time. Each time we turn the LED on and off, the amount of time it is on, added to the amount of time it is off, always equals the same length of time (this length of time is known as the "PWM period"). It doesn't really matter what the PWM period is, as long as it is short enough that we can't perceive the the LED flicker. We can perceive flickering if an LED blinks slower than about 1/30 of a second, or about 0.033 seconds (or 30Hz). In this firmware, I keep the PWM period to less than 0.01 seconds (faster than 100Hz) so there is no perceieved flicker (though it is interesting to wave the unit back and forth while it is running, and you can actually see the PWM pusle widths widen and shrink over time). */ /* Parts list for this RGB Light project: 1 MiniPOV kit 2 AA batteries 1 super bright Green LED 1 super bright Blue LED 2 10 ohm resistor (to limit the current and brightness of the Red and Blue LEDs -- since Green LEDs are often brighter we'll use one of the 47 ohm resistors that come with the MiniPOV for the Green) 1 translucent plastic salsa container 3 short sections of white drinking straw */ /* The hardware for this project is very simple: ATtiny2313 has 20 pins: pin 1 connects to serial port programming circuitry pin 10 ground pin 13 PB1 - Red LED pin 14 OC0A - Green LED pin 15 OC1A - Blue LED pin 17 connects to serial port programming circuitry pin 18 connects to serial port programming circuitry pin 19 connects to serial port programming circuitry pin 20 +3v All other pins are unused This firmware requires that the clock frequency of the ATtiny is the default that it is shipped with: 8.0MHz */ /* The C compiler creates code that will transfer all constants into RAM when the microcontroller resets. Since this firmware has a table (lightTab) that is too large to transfer into RAM (and since we don't need it in RAM) the C compiler needs to be told to keep it in program memory space. This is accomplished by the macro PROGMEM (this is used, below, in the definition for the lightTab). Since the C compiler assumes that constants are in RAM, rather than in program memory, when accessing the lightTab, we need to use the pgm_read_byte() macro, and we need to use the lightTab as an address, i.e., precede it with "&". For example, to access lightTab[3].red, which is a byte, this is how to do it: pgm_read_byte( &lightTab[3].red ); And to access lightTab[3].fadeTime, which is a word, this is how to do it: pgm_read_word( &lightTab[3].fadeTime ); */ /* The following Light Table consists of any number of rgbElements that will fit into the 2k flash ROM. Each rgbElement consists of: fadeTime -- how long to take to fade from previous values of RGB to the ones specified in this rgbElement (0 or between 1,000 and 65,535) holdTime -- how long to keep the RGB values once they are faded in (0 or between 1,000 and 65,535) Red -- brightness value for Red (between 0 to 255) Green -- brightness value for Green (between 0 to 255) Blue -- brightness value for Blue (between 0 to 255) Both of the time values, fadeTime and holdTime, are expressed as the number of 400 microseconds -- for example, 2 seconds would be entered as 5000. To signify the last rgbElement in the lightTab, its fadeTime and hold time must both be 0 (all other values of this last rgbElement are ignored). The values for fadeTime and holdTime must either be 0, or between 1,000 and 65,535 (i.e., 0, or between 0.4 sec and 26.2 sec). */ /* The Light Sequences and the notions of fadeTime and holdTime are taken from Pete Griffiths, downloaded from: http://www.petesworld.demon.co.uk/homebrew/PIC/rgb/index.htm I modified it to fit my purposes. The sequence takes about 2 minutes. More precisely: adding all of the fadeTime values together: 121,000 adding all of the holdTime values together: 138,000 adding these together = 259,000. Since the time values are each 400 microseconds, 259,000 is 103.6 seconds, or, 1.727 minutes, which is 1 minute, 44 seconds. The Main function repeats the sequence several times. */ // table of Light Sequences struct rgbElement { int fadeTime; // how long to fade from previous values of RGB to the ones specified in this rgbElement (0 or 1000 to 65,535) int holdTime; // how long to keep the RGB values once they are faded in (0 or 1000 to 65,535) unsigned char red; // brightness value for Red LED (0 to 255) unsigned char green; // brightness value for Green LED (0 to 255) unsigned char blue; // brightness value for Blue LED (0 to 255) } const lightTab[] PROGMEM = { { 0, 500, 255, 0, 0 }, { 500, 0, 0, 0, 0 }, { 0, 500, 0, 255, 0 }, { 500, 0, 0, 0, 0 }, { 0, 500, 0, 0, 255 }, { 500, 0, 0, 0, 0 }, { 2500, 2500, 255, 0, 0 }, { 2500, 2500, 0, 255, 0 }, { 2500, 2500, 0, 0, 255 }, { 2500, 2500, 255, 64, 0 }, { 2500, 2500, 64, 255, 64 }, { 2500, 2500, 0, 64, 255 }, { 2500, 2500, 64, 0, 64 }, { 0, 1500, 255, 0, 0 }, { 0, 1500, 0, 255, 0 }, { 0, 1500, 0, 0, 255 }, { 0, 1500, 240, 0, 240 }, { 0, 1500, 255, 155, 0 }, { 0, 1500, 255, 255, 255 }, { 0, 1500, 128, 128, 128 }, { 0, 1500, 48, 48, 58 }, { 0, 1500, 0, 0, 0 }, { 2500, 2500, 255, 0, 0 }, { 2500, 2500, 255, 255, 0 }, { 2500, 2500, 0, 255, 0 }, { 2500, 2500, 0, 255, 255 }, { 2500, 2500, 0, 0, 255 }, { 2500, 2500, 255, 0, 255 }, { 2500, 0, 0, 0, 0 }, { 2500, 2500, 255, 0, 0 }, { 2500, 2500, 255, 255, 0 }, { 2500, 2500, 0, 255, 0 }, { 2500, 2500, 0, 255, 255 }, { 2500, 2500, 0, 0, 255 }, { 2500, 2500, 255, 0, 255 }, { 2500, 0, 0, 0, 0 }, { 2500, 2500, 254, 32, 0 }, { 2500, 2500, 254, 128, 0 }, { 2500, 2500, 254, 240, 0 }, { 2500, 2500, 128, 240, 0 }, { 0, 2500, 0, 0, 0 }, { 2500, 2500, 0, 16, 255 }, { 2500, 2500, 0, 128, 255 }, { 2500, 2500, 0, 240, 128 }, { 2500, 2500, 16, 16, 240 }, { 2500, 2500, 240, 16, 240 }, { 2500, 2500, 64, 0, 250 }, { 0, 2500, 10, 10, 10 }, { 0, 2500, 0, 0, 0 }, { 2500, 2500, 240, 0, 240 }, { 2500, 2500, 32, 0, 240 }, { 2500, 2500, 128, 0, 128 }, { 2500, 2500, 240, 0, 32 }, { 2500, 0, 0, 0, 10 }, { 2500, 0, 0, 0, 0 }, { 1000, 1000, 0, 0, 0 }, { 1000, 1000, 32, 0, 0 }, { 1000, 1000, 64, 0, 0 }, { 0, 1000, 96, 0, 0 }, { 1000, 0, 128, 0, 0 }, { 1000, 0, 160, 32, 0 }, { 1000, 0, 192, 64, 0 }, { 1000, 0, 224, 96, 0 }, { 0, 1000, 255, 128, 0 }, { 1000, 1000, 0, 160, 0 }, { 0, 1000, 0, 192, 0 }, { 1000, 1000, 0, 224, 32 }, { 1000, 0, 0, 255, 64 }, { 1000, 0, 0, 0, 96 }, { 1000, 0, 0, 0, 128 }, { 1000, 0, 0, 0, 160 }, { 1000, 0, 0, 0, 192 }, { 1000, 0, 0, 0, 224 }, { 1000, 1000, 0, 0, 255 }, { 1000, 0, 0, 0, 0 }, { 0, 1000, 0, 0, 255 }, { 1000, 1000, 32, 0, 0 }, { 1000, 1000, 96, 0, 0 }, { 1000, 1000, 160, 0, 0 }, { 1000, 0, 255, 0, 0 }, { 1000, 1000, 0, 96, 0 }, { 1000, 1000, 0, 160, 32 }, { 1000, 1000, 0, 224, 64 }, { 1000, 1000, 0, 255, 96 }, { 1000, 1000, 0, 0, 128 }, { 1000, 1000, 0, 0, 160 }, { 0, 1000, 0, 32, 192 }, { 0, 1000, 0, 64, 224 }, { 0, 1000, 0, 96, 225 }, { 0, 1000, 0, 128, 0 }, { 0, 1000, 0, 160, 0 }, { 0, 1000, 0, 192, 32 }, { 0, 1000, 0, 224, 64 }, { 0, 1000, 0, 255, 96 }, { 0, 1000, 0, 0, 128 }, { 0, 1000, 0, 0, 160 }, { 0, 1000, 0, 0, 192 }, { 0, 1000, 0, 0, 224 }, { 0, 1000, 0, 0, 255 }, { 0, 1000, 0, 0, 0 }, { 0, 0, 0, 0, 0 } }; // This function delays the specified number of 10 microseconds void delay_ten_us(unsigned long int us) { unsigned long int count; const unsigned long int DelayCount=6; // this value was determined by trial and error while (us != 0) { // Toggling PD0 is done here to force the compiler to do this loop, rather than optimize it away for (count=0; count <= DelayCount; count++) {PIND |= 0b0000001;}; us--; } } // This function delays (1.56 microseconds * x) + 2 microseconds // (determined empirically) // e.g. if x = 1, the delay is (1 * 1.56) + 2 = 5.1 microseconds // if x = 255, the delay is (255 * 1.56) + 2 = 399.8 microseconds void delay_x_us(unsigned long int x) { unsigned long int count; const unsigned long int DelayCount=0; // the shortest delay while (x != 0) { // Toggling PD0 is done here to force the compiler to do this loop, rather than optimize it away for (count=0; count <= DelayCount; count++) {PIND |= 0b0000001;}; x--; } } // This function pulses the Red LED on PB1 (pin 13) // Since Red LED is not on a PWM pin on a hardware timer, we need to pulse it manually. // We pulse it High for [Red value], and pulse it Low for [255 - Red value]. // Since the delay_x_us function delays 1.56x+2 microseconds, // the total period is about 400 microseconds, which is 2500Hz (if we repeat it) // (and that is way fast enough so that we don't perceive the Red LED flicker). void pulseRed(unsigned char redVal) { PORTB |= 0b00000010; // turn on Red LED at PB1 (pin 13) for 4 * Red value delay_x_us( redVal ); PORTB &= 0b11111101; // turn off Red LED at PB1 for 4 * (255 - Red value) delay_x_us( (255 - redVal) ); } // This function sends one rgbElement of lightTab to the LEDs, // given index to the codeElement in lightTab // If both fadeTime = 0 and holdTime = 0 that signifies the last rgbElement of the lightTab // // There are several variables used in this function: // index: the input argument for this function -- it is the index to lightTab, pointing to the rgbElement in lightTab // // FadeTime: gotten from rgbElement in lightTab // HoldTime: gotten from rgbElement in lightTab // Red: gotten from rgbElement in lightTab // Green: gotten from rgbElement in lightTab // Blue: gotten from rgbElement in lightTab // // redPrev: gotten from previous rgbElement in lightTab (set to 0 if index to lightTab = 0) // greenPrev: gotten from previous rgbElement in lightTab (set to 0 if index to lightTab = 0) // bluePrev: gotten from previous rgbElement in lightTab (set to 0 if index to lightTab = 0) // // redTime: when the fadeCounter in the fade loop reaches this value, we will update the Red LED brightness value // greenTime: when the fadeCounter in the fade loop reaches this value, we will update the Green LED brightness value // blueTime: when the fadeCounter in the fade loop reaches this value, we will update the Blue LED brightness value // // redTemp: keep track of current Red LED brightness value as we're fading up or down in the fade loop // greenTemp: keep track of current Green LED brightness value as we're fading up or down in the fade loop // blueTemp: keep track of current Blue LED brightness value as we're fading up or down in the fade loop // // redDelta: the total amount of brightness we need to change to get from where the Red LED was to where we want it // greenDelta: the total amount of brightness we need to change to get from where the Green LED was to where we want it // blueDelta: the total amount of brightness we need to change to get from where the Blue LED was to where we want it // // redTimeInc: in the fade loop we will update the Red LED every time the fadeCounter increments by this amount // greenTimeInc: in the fade loop we will update the Green LED every time the fadeCounter increments by this amount // blueTimeInc: in the fade loop we will update the Blue LED every time the fadeCounter increments by this amount // // fadeCounter: for counting through the steps (each of which is 400us long) in the fade loop // // holdCounter: for counting through the steps (each of which is 400us long) in the hold loop void sendrgbElement( int index ) { // get values of rgbElement from lightTab int FadeTime = pgm_read_word(&lightTab[index].fadeTime); int HoldTime = pgm_read_word(&lightTab[index].holdTime); unsigned char Red = pgm_read_byte(&lightTab[index].red); unsigned char Green = pgm_read_byte(&lightTab[index].green); unsigned char Blue = pgm_read_byte(&lightTab[index].blue); // get previous RGB brightness values from lightTab // (these values are set to 0 if index to lightTab = 0) unsigned char redPrev = 0; // keep track of previous Red brightness value unsigned char greenPrev = 0; // keep track of previous Green brightness value unsigned char bluePrev = 0; // keep track of previous Blue brightness value if (index != 0) { redPrev = pgm_read_byte(&lightTab[index-1].red); greenPrev = pgm_read_byte(&lightTab[index-1].green); bluePrev = pgm_read_byte(&lightTab[index-1].blue); } // set color timing values // everytime the fadeCounter reaches this timing value in the fade loop // we will update the color value for the color (default value of 0 for no updating) int redTime = 0; int greenTime = 0; int blueTime = 0; // set values of temp colors // starting from the previous color values, // these will change to the color values just gotten from rgbElement over fadeTime unsigned char redTemp = redPrev; unsigned char greenTemp = greenPrev; unsigned char blueTemp = bluePrev; // fade LEDs up or down, from previous values to current values int redDelta = Red - redPrev; // total amount to fade red value (up or down) during fadeTime int greenDelta = Green - greenPrev; // total amount to fade green value (up or down) during fadeTime int blueDelta = Blue - bluePrev; // total amount to fade blue value (up or down) during fadeTime if (redDelta != 0) { redTime = (FadeTime / redDelta); // increment Red value every time we reach this fade value in the fade loop if (redTime < 0) redTime = -redTime; // absolute value redTime = redTime + 1; // adjust for truncation of integer division } // int redTimeInc = redTime; // increment Red value every time the fadeCounter increments this amount if (greenDelta != 0) { greenTime = (FadeTime / greenDelta); // increment Green value every time we reach this fade value in the fade loop if (greenTime < 0) greenTime = -greenTime; // absolute value greenTime = greenTime + 1; // adjust for truncation of integer division } // int greenTimeInc = greenTime; // increment Green value every time the fadeCounter increments this amount if (blueDelta != 0) { blueTime = (FadeTime / blueDelta); // increment Blue value every time we reach this fade value in the fade loop if (blueTime < 0) blueTime = -blueTime; // absolute value blueTime = blueTime + 1; // adjust for truncation of integer division } // int blueTimeInc = blueTime; // increment Blue value every time the fade value increments this amount // set color increment values // the amount to increment color value each time we update it in the fade loop // (default value of 1, to slowly increase brightness each time through the fade loop) unsigned char redInc = 1; unsigned char greenInc = 1; unsigned char blueInc = 1; // if we need to fade down the brightness, then make the increment values negative if (redDelta < 0) redInc = -1; if (greenDelta < 0) greenInc = -1; if (blueDelta < 0) blueInc = -1; // if FadeTime = 0, then just set the LEDs blinking at the RGB values (the fade loop will not be executed) if (FadeTime == 0) { redTemp = Red; // no need to manually pulse Red LED on PB1 (pin 13) now, since it will be done in the hold loop OCR0A = Green; // update PWM for Green LED on OC0A (pin 14) OCR1A = Blue; // update PWM for Blue LED on OC1A (pin 15) } // fade loop // this loop will independently fade each LED up or down according to all of the above variables // (it will take a length of time, FadeTime, to accomplish the task) // this loop is not executed if FadeTime = 0 (since 1 is not <= 0, in the "for" loop) for (int fadeCounter=1; fadeCounter<=FadeTime; fadeCounter++) { if ( fadeCounter == redTime ) { redTemp = redTemp + redInc; // increment to next red value redTime = redTime + redTimeInc; // we'll increment Red value again when FadeTime reaches new redTime } if ( fadeCounter == greenTime ) { greenTemp = greenTemp + greenInc; // increment to next green value greenTime = greenTime + greenTimeInc; // we'll increment Green value again when FadeTime reaches new greenTime } if ( fadeCounter == blueTime ) { blueTemp = blueTemp + blueInc; // increment to next blue value blueTime = blueTime + blueTimeInc; // we'll increment Blue value again when FadeTime reaches new blueTime } pulseRed(redTemp); // one manual PWM pulse on the Red LED on PB1 (pin 13) for a period of 400 microseconds OCR0A = greenTemp; // update PWM for Green LED on OC0A (pin 14) OCR1A = blueTemp; // update PWM for Blue LED on OC1A (pin 15) } OCR0A = Green; // leave Timer0 PWM at final brightness value for Green (in case there were rounding errors in above math) OCR1A = Blue; // leave Timer1 PWM at final brightness value for Blue (in case there were rounding errors in above math) // hold loop // hold all LEDs at current values for HoldTime for (int holdCounter=0; holdCounter