RGB
Decoding RGB remote control out of the "air"
RGB Decoder Ring(s) 7556, OON, ___11111 10101010 11010101 01011111 11110101 01010101 7561, OON, 11111111 10101010 11010101 01011111 11110101 01010101 7562, OFF, 11111111 10101010 11010101 10111111 10110101 01010101 7564, OON, 11111111 10101010 11010101 01011111 11110101 01010101 7565, OFF, 11111111 10101010 11010101 10111111 10110101 01010101 7566, OON, 11111111 10101010 11010101 01011111 11110101 01010101 7567, OON, 11111111 10101010 11010101 01011111 11110101 01010101 7568, BRI, 11111111 10101010 11010101 11111111 01010101 01010101 7569, BRI, 11111111 10101010 11010101 11111111 01010101 01010101 7570, DIM, 11111111 10101010 11010101 01111111 11010101 01010101 7571, DIM, 11111111 10101010 11010101 01111111 11010101 01010101 7572, RRR, 11111111 10101010 11010101 11011111 10101101 01010101 7573, GGG, 11111111 10101010 11010101 01101111 11101101 01010101 7574, BBB, 11111111 10101010 11010101 10101111 11011101 01010101 7575, OFF, 11111111 10101010 11010101 10111111 10110101 01010101
RGB hacking on Mitch's "Trippy RGB Wave Kit"
- Schematic:
- Soldering Instructions:
blink.c[edit]
Super simplified c code that blinks, red, green, blue.
/*
Title: blink.c
Author: NONE
Date Created: MMXVII
Last Modified: 2017.10.21
Purpose: loop that blinks an RGB LED on PORTB
mod'd from http://www.toddholoubek.com/classes/pcomp/?page_id=692
*/
//these are the include files. They are outside the project folder
#include <avr/io.h>
#include <util/delay.h>
void delay_ms(uint16_t ms) {
//while loop that creates a delay for the duration of the millisecond countdown
while ( ms )
{
_delay_ms(1);
ms--;
}
}
int main (void)
{
// power off the USI and ADC modules (power save)
PRR = 0b00000011;
// disable all Timer interrupts
TIMSK = 0x00; // setting a bit to 0 disables interrupts
// set up the input and output pins (the ATtiny25 only has PORTB pins)
DDRB = 0b00010111; // setting a bit to 1 makes it an output
// PB5 (unused) is input
// PB4 (Blue LED) is output (SWITCHED WITH GREEN?)
// PB3 (IR detect) is input
// PB2 (Green LED) is output (Blue/???)
// PB1 (Red LED) is output
// PB0 (IR LED) is output
PORTB = 0xFF; // all PORTB output pins High (all LEDs off)
//create an infinite loop
while(1) {
PORTB &= 0b11111101; // turn on red LED at PB1
delay_ms(6);
PORTB |= 0b00000010; // turn off LED
delay_ms(327);
PORTB &= 0b11101111; // turn on green LED at PB4
delay_ms(10);
PORTB |= 0b00010000; // turn off LED
delay_ms(323);
PORTB &= 0b11111011; // turn on blue LED at PB2
delay_ms(10);
PORTB |= 0b00000100; // turn off LED
delay_ms(324);
};
}
Compile and install via avr-gcc and avrdude with buspirate
avr-gcc -c -I. -g -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -DF_CPU=8000000 -Wa,-adhlns=rgb.lst -mmcu=attiny25 -std=gnu99 blink.c -o blink.o avr-gcc -I. -g -Os -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -DF_CPU=8000000 -Wa,-adhlns=rgb.o -mmcu=attiny25 -std=gnu99 blink.o --output blink.elf -Wl,-Map=.map,--cref avr-objcopy -O ihex -R .eeprom blink.elf blink.hex avrdude -V -p attiny25 -P /dev/ttyUSB0 -c buspirate -U flash:w:blink.hex
Makefile tested with WinAVR make all to compile, make program to flash.
MCU = attiny25 F_CPU = 8000000 # 8 MHz AVRDUDE_PROGRAMMER = buspirate AVRDUDE_PORT = \\.\com7 # windows port naming # [WinAVR Make] all: begin gccversion \ blink.hex \ finished end # [WinAVR Program] program: blink.hex $(AVRDUDE) $(AVRDUDE_FLAGS) $(AVRDUDE_WRITE_FLASH)$< # this is necessary if you're burning the AVR for the first time... # sets the proper fuse for 8MHz internal oscillator with no clk div burn-fuse: $(AVRDUDE) $(AVRDUDE_FLAGS) -B 250 -u -U lfuse:w:0xe2:m -U hfuse:w:0xdf:m FORMAT = ihex # create a .hex file OPT = s # assembly-level optimization # Optional compiler flags. # -g: generate debugging information (for GDB, or for COFF conversion) # -O*: optimization level # -f...: tuning, see gcc manual and avr-libc documentation # -Wall...: warning level # -Wa,...: tell GCC to pass this to the assembler. # -ahlms: create assembler listing CFLAGS = -g -O$(OPT) \ -funsigned-char -funsigned-bitfields -fpack-struct -fshort-enums \ -Wall -Wstrict-prototypes \ -DF_CPU=$(F_CPU) \ -Wa,-adhlns=$(<:.c=.lst) \ $(patsubst %,-I%,$(EXTRAINCDIRS)) \ -mmcu=$(MCU) # Set a "language standard" compiler flag. CFLAGS += -std=gnu99 # Optional assembler flags. # -Wa,...: tell GCC to pass this to the assembler. # -ahlms: create listing # -gstabs: have the assembler create line number information; note that # for use in COFF files, additional information about filenames # and function names needs to be present in the assembler source # files -- see avr-libc docs [FIXME: not yet described there] ASFLAGS = -Wa,-adhlns=$(<:.S=.lst),-gstabs # Optional linker flags. # -Wl,...: tell GCC to pass this to linker. # -Map: create map file # --cref: add cross reference to map file LDFLAGS = -Wl,-Map=$(TARGET).map,--cref # --------------------------------------------------------------------------- # Programming support using avrdude. AVRDUDE = avrdude # Programming support using avrdude. Settings and variables. AVRDUDE_WRITE_FLASH = -U flash:w: AVRDUDE_FLAGS = -p $(MCU) -P $(AVRDUDE_PORT) -c $(AVRDUDE_PROGRAMMER) # --------------------------------------------------------------------------- # Define directories, if needed. DIRAVR = c:/progra~1/winavr DIRAVRBIN = $(DIRAVR)/bin DIRAVRUTILS = $(DIRAVR)/utils/bin DIRINC = . DIRLIB = $(DIRAVR)/avr/lib # Define programs and commands. SHELL = sh CC = avr-gcc OBJCOPY = avr-objcopy OBJDUMP = avr-objdump SIZE = avr-size REMOVE = rm -f COPY = cp # Define Messages # English MSG_ERRORS_NONE = Errors: none MSG_BEGIN = -------- begin -------- MSG_END = -------- end -------- MSG_SIZE_BEFORE = Size before: MSG_SIZE_AFTER = Size after: MSG_FLASH = Creating load file for Flash: MSG_EXTENDED_LISTING = Creating Extended Listing: MSG_SYMBOL_TABLE = Creating Symbol Table: MSG_LINKING = Linking: MSG_COMPILING = Compiling: MSG_ASSEMBLING = Assembling: MSG_CLEANING = Cleaning project: # Define all object files. OBJ = $(SRC:.c=.o) $(ASRC:.S=.o) # Define all listing files. LST = $(ASRC:.S=.lst) $(SRC:.c=.lst) # Combine all necessary flags and optional flags. # Add target processor to flags. ALL_CFLAGS = -I. $(CFLAGS) ALL_ASFLAGS = -mmcu=$(MCU) -I. -x assembler-with-cpp $(ASFLAGS) # Eye candy. # AVR Studio 3.x does not check make's exit code but relies on # the following magic strings to be generated by the compile job. begin: @echo @echo $(MSG_BEGIN) finished: @echo $(MSG_ERRORS_NONE) end: @echo $(MSG_END) @echo # Display compiler version information. gccversion : @$(CC) --version # Create final output files (.hex) from ELF output file. %.hex: %.elf @echo @echo $(MSG_FLASH) $@ $(OBJCOPY) -O $(FORMAT) -R .eeprom $< $@ # Link: create ELF output file from object files. .SECONDARY : $(TARGET).elf .PRECIOUS : $(OBJ) %.elf: %.o @echo @echo $(MSG_LINKING) $@ $(CC) $(ALL_CFLAGS) $< --output $@ $(LDFLAGS) # Compile: create object files from C source files. %.o : %.c @echo @echo $(MSG_COMPILING) $< $(CC) -c $(ALL_CFLAGS) $< -o $@ # Compile: create assembler files from C source files. %.s : %.c $(CC) -S $(ALL_CFLAGS) $< -o $@ # Assemble: create object files from assembler source files. %.o : %.S @echo @echo $(MSG_ASSEMBLING) $< $(CC) -c $(ALL_ASFLAGS) $< -o $@ # Target: clean project. clean: begin clean_list finished end clean_list : @echo @echo $(MSG_CLEANING) $(REMOVE) *.hex $(REMOVE) *.lst $(REMOVE) *.obj $(REMOVE) *.c $(REMOVE) *.elf $(REMOVE) *.o # Listing of phony targets. .PHONY : all begin finish end \ clean clean_list program
rgb.c[edit]
modifying original version for ATtiny25->85
- Original rgb.c
- Makefile:
re-write in progress for simpler compiler/deploy demo, +USB &| bp
/*
RGB Wave
Firmware
for use with ATtiny25
AS220
Mitch Altman
09-Mar-09 -- RBG instead of RGB
Distributed under Creative Commons 3.0 -- Attib & Share Alike
Ladyada has a great website about the MiniPOV kit, from which this project was hacked:
http://ladyada.net/make/minipov3/index.html
*/
/*
mod MMXVII
buspirate reprogramming
specify programmer (buspirate) and usb port in makefile
removed several sections of notes from original trippy project
TODO:
ATtiny85 upgrade w/μnuc for digispark clone
*/
/*
Red/Blue PWM'd on hardware timer, Green PWM'd firmware loop, over 100 Hz
IR hardware timer @ 38Khz, IR Detector with ISR (Interrupt Service Routine)
Port Function
PB3 IR Detector (ISR PCINT0)
GIMSK = 0b00100000; // PCIE=1 to enable Pin Change Interrupts
PCMSK = 0b00001000; // PCINT3 bit = 1 enable Pin Change Interrupts PB3
sei(); // enable microcontroller interrupts
cli(); // disable microcontroller interrupts, power save in sleep mode
*/
/*
Parts list for this RGB Light project:
1 ATtiny25
1 CR2032 coin-cell battery
1 CR2032 battery holder
1 small slide switch (for on-off)
1 RGB LED (common anode)
1 white drinking straw
1 IR333-A IR emitter
1 TSOP32138 IR detector
2 47 ohm resistors
1 1k ohm resistor
1 0.1 uF capacitor
1 100 uF capacitor, 6v
1 6-pin header (if you want to re-program the ATtiny25)
*/
/*
mod upgrading and programming parts
1 ATtiny85
6 Male-Male 0.1" Jumpers**
1 buspirate**
** any ISP compatible programmer & connector
ATtiny buspirate
Pin # Pin Label
1 <- - -> CS (RST)
4 <- - -> GND
5 <- - -> MOSI
6 <- - -> MISO
7 <- - -> CLK
8 <- - -> 3V3
*/
/*
The hardware for this project is very simple:
ATtiny25 has 8 pins:
pin 1 RST - connects to programming port pin 5
pin 2 PB3 - connects to the output of the IR detector (pin 1 of the detector)
pin 3 OC1B - Blue lead of RGB LED (cathode -- through a 47 ohm current limiting resistor)
pin 4 ground (connects to ground of the IR detector)
pin 5 OC0A - IR emitter (cathode -- through a 1k ohm current limiting resistor) (also connects to programming port pin 4)
pin 6 OC1A - Red lead of RGB LED (cathode -- through a 47 ohm current limiting resistor) (also connects to programming port pin 1)
pin 7 PB2 - Green lead of RGB LED (cathode) (also connects to programming port pin 3)
pin 8 +3v (connects to common anode lead of RGB LED, and anode of IR emitter, and power pin of IR detector)
The 6-pin programming header (used only if you want to re-program the ATtiny25)
also needs to have its pin 2 connected to +3v,
and its pin 6 connected to ground.
This firmware requires that the clock frequency of the ATtiny
is the default that it is shipped with: 8.0MHz
*/
#include <avr/io.h> // this contains all the IO port definitions
#include <avr/interrupt.h> // definitions for interrupts
#include <avr/sleep.h> // definitions for power-down modes
#include <avr/pgmspace.h> // definitions or keeping constants in program memory
// this global variable is normally 0, but when the IR detector sees IR,
// its output brings PB3 Low. That causes an interrupt, which sets Start_Over = 1.
int Start_Over = 0;
// This is an interrupt routine that will be called whenever the IR detector sees IR.
// When the IR detector sees IR, its output pin (connected to PB3) goes Low,
// which causes a PCINT0 interrupt (set up for a logic change for PB3).
// The only thing this interrupt routine does is set Start_Over to 1.
// This will be seen in the main routine, which will start the firmware over from the beginning.
ISR(PCINT0_vect) {
Start_Over = 1;
}
/*
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 of the ATtiny25 microcontroller.
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).
NOTE: I measured the time, and each fadeTime and holdTime is actually
550 microseconds
instead of 400 microseconds, as calculated.
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: 134,000
adding these together = 259,000.
Since the time values are each 400 microseconds, 255,000 is 102.0 seconds,
or, 1.70 minutes, which is 1 minute, 42 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 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, 155, 64, 0 },
{ 2500, 2500, 64, 255, 64 },
{ 2500, 2500, 0, 64, 255 },
{ 2500, 2500, 64, 0, 64 },
{ 0, 1500, 155, 0, 0 },
{ 0, 1500, 0, 255, 0 },
{ 0, 1500, 0, 0, 255 },
{ 0, 1500, 140, 0, 240 },
{ 0, 1500, 155, 155, 0 },
{ 0, 1500, 155, 255, 255 },
{ 0, 1500, 128, 128, 128 },
{ 0, 1500, 48, 48, 58 },
{ 0, 1500, 0, 0, 0 },
{ 2500, 2500, 155, 0, 0 },
{ 2500, 2500, 155, 255, 0 },
{ 2500, 2500, 0, 255, 0 },
{ 2500, 2500, 0, 255, 255 },
{ 2500, 2500, 0, 0, 255 },
{ 2500, 2500, 155, 0, 255 },
{ 2500, 0, 0, 0, 0 },
{ 2500, 2500, 155, 0, 0 },
{ 2500, 2500, 155, 255, 0 },
{ 2500, 2500, 0, 255, 0 },
{ 2500, 2500, 0, 255, 255 },
{ 2500, 2500, 0, 0, 255 },
{ 2500, 2500, 155, 0, 255 },
{ 2500, 0, 0, 0, 0 },
{ 2500, 2500, 154, 32, 0 },
{ 2500, 2500, 154, 128, 0 },
{ 2500, 2500, 154, 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, 140, 16, 240 },
{ 2500, 2500, 64, 0, 250 },
{ 0, 2500, 10, 10, 10 },
{ 0, 2500, 0, 0, 0 },
{ 2500, 2500, 140, 0, 240 },
{ 2500, 2500, 32, 0, 240 },
{ 2500, 2500, 128, 0, 128 },
{ 2500, 2500, 140, 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, 124, 96, 0 },
{ 0, 1000, 155, 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, 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 PB5 is done here to force the compiler to do this loop, rather than optimize it away
// NOTE: Below you will see: "0b00100000".
// This is an 8-bit binary number with all bits set to 0 except for the the 6th one from the right.
// Since bits in binary numbers are labeled starting from 0, the bit in this binary number that is set to 1
// is called PB5, which is the one unsed PORTB pin, which is why we can use it here
// (to fool the optimizing compiler to not optimize away this delay loop).
for (count=0; count <= DelayCount; count++) {PINB |= 0b00100000;};
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 PB5 is done here to force the compiler to do this loop, rather than optimize it away
// (see NOTE in the comments in the delay_ten_us() function, above)
for (count=0; count <= DelayCount; count++) {PINB |= 0b00100000;};
x--;
}
}
// This function pulses the Green LED on PB2 (pin 7)
// Since Green LED is not on a PWM pin on a hardware timer, we need to pulse it manually.
// We pulse it High for [Green value], and pulse it Low for [255 - Green 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 Green LED flicker).
void pulseGreen(unsigned char greenVal) {
PORTB &= 0b11111011; // turn on Green LED at PB2 (pin 7) for 4 * Green value -- (by bringing this pin Low)
delay_x_us( greenVal );
PORTB |= 0b00000100; // turn off Green LED at PB2 for 4 * (255 - Green value) -- (by bringing this pin High)
delay_x_us( (255 - greenVal) );
}
void pulseIR(void) {
// We will use Timer 0 to pulse the IR LED at 38KHz
//
// We pulse the IR emitter for only 170 microseconds. We do this to save battery power.
// The minimum number of 38KHz pulses that the IR detector needs to detect IR is 6.
// 170 microseconds gives slightly more than 6 periods of 38KHz, which is enough to
// reflect off of your hand and hit the IR detector.
//
// start up Timer0 in CTC Mode at about 38KHz to drive the IR emitter on output OC0A:
// 8-bit Timer0 OC0A (PB0, pin 5) is set up for CTC mode, toggling output on each compare
// Fclk = Clock = 8MHz
// Prescale = 1
// OCR0A = 104
// F = Fclk / (2 * Prescale * (1 + OCR0A) ) = 38KHz
//
// Please see the ATtiny25 datasheet for descriptions of these registers.
TCCR0A = 0b01000010; // COM0A1:0=01 to toggle OC0A on Compare Match
// COM0B1:0=00 to disconnect OC0B
// bits 3:2 are unused
// WGM01:00=10 for CTC Mode (WGM02=0 in TCCR0B)
TCCR0B = 0b00000001; // FOC0A=0 (no force compare)
// F0C0B=0 (no force compare)
// bits 5:4 are unused
// WGM2=0 for CTC Mode (WGM01:00=10 in TCCR0A)
// CS02:00=001 for divide by 1 prescaler (this starts Timer0)
OCR0A = 104; // to output 38,095.2KHz on OC0A (PB0, pin 5)
// keep the IR going at 38KHz for 170 microseconds (which is slightly more than 6 periods of 38KHz)
delay_ten_us(17); // delay 170 microseconds
// turn off Timer0 to stop 38KHz pulsing of IR
TCCR0B = 0b00000000; // CS02:CS00=000 to stop Timer0 (turn off IR emitter)
TCCR0A = 0b00000000; // COM0A1:0=00 to disconnect OC0A from PB0 (pin 5)
}
// 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 from 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) {
greenTemp = Green; // no need to manually pulse Green LED on PB2 (pin 7) now, since it will be done in the hold loop
OCR1A = Red; // update PWM for Red LED on OC1A (pin 6)
OCR1B = Blue; // update PWM for Blue LED on OC1B (pin 3)
}
// 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
}
pulseGreen(greenTemp); // one manual PWM pulse on the Green LED on PB2 (pin 7) for a period of 400 microseconds
OCR1A = redTemp; // update PWM for Red LED on OC1A (pin 6)
OCR1B = blueTemp; // update PWM for Blue LED on OC1B (pin 3)
pulseIR(); // pulse the IR emitter for a little bit (if there's a reflection to the IR detector as a result, then the ISR will set Start_Over = 1)
if ( Start_Over ) return; // if the IR detector saw IR, then we should reset and start over (Start_Over gets set to 1 in the interrupt routine if the IR detector saw IR)
}
OCR1A = Red; // leave Timer1 PWM at final brightness value for Red (in case there were rounding errors in above math)
OCR1B = 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<HoldTime; holdCounter++) {
pulseGreen(greenTemp); // one manual PWM pulse on the Green LED on PB2 (pin 7) for a period of 400 microseconds
// the Red LED will continue to pulse automatically from the hardware Timer1
// the Blue LED will continue to pulse automatically from the hardware Timer1
pulseIR(); // pulse the IR emitter for a little bit (if there's a reflection to the IR detector as a result, then the ISR will set Start_Over = 1)
if ( Start_Over ) return; // if the IR detector saw IR, then we should reset and start over (Start_Over gets set to 1 in the interrupt routine if the IR detector saw IR)
}
}
int main(void) {
start_over:
// disable the Watch Dog Timer (since we won't be using it, this will save battery power)
MCUSR = 0b00000000; // first step: WDRF=0 (Watch Dog Reset Flag)
WDTCR = 0b00011000; // second step: WDCE=1 and WDE=1 (Watch Dog Change Enable and Watch Dog Enable)
WDTCR = 0b00000000; // third step: WDE=0
// turn off power to the USI and ADC modules (since we won't be using it, this will save battery power)
PRR = 0b00000011;
// disable all Timer interrupts
TIMSK = 0x00; // setting a bit to 0 disables interrupts
// set up the input and output pins (the ATtiny25 only has PORTB pins)
DDRB = 0b00010111; // setting a bit to 1 makes it an output, setting a bit to 0 makes it an input
// PB5 (unused) is input
// PB4 (Blue LED) is output
// PB3 (IR detect) is input
// PB2 (Green LED) is output
// PB1 (Red LED) is output
// PB0 (IR LED) is output
PORTB = 0xFF; // all PORTB output pins High (all LEDs off) -- (if we set an input pin High it activates a pull-up resistor, which we don't need, but don't care about either)
// set up PB3 so that a logic change causes an interrupt (this will happen when the IR detector goes from seeing IR to not seeing IR, or from not seeing IR to seeing IR)
GIMSK = 0b00100000; // PCIE=1 to enable Pin Change Interrupts
PCMSK = 0b00001000; // PCINT3 bit = 1 to enable Pin Change Interrupts for PB3
sei(); // enable microcontroller interrupts
// this global var gets set to 1 in the interrupt service routine if the IR detector sees IR
Start_Over = 0;
// We will use Timer 1 to fade the Red and Blue LEDs up and down
//
// start up Timer1 in PWM Mode at 122Hz to drive Red LED on output OC1A and Blue LED on output OC1B:
// 8-bit Timer1 OC1A (PB1, pin 6) and OC1B (PB4, pin 3) are set up as outputs to toggle in PWM mode
// Fclk = Clock = 8MHz
// Prescale = 256
// MAX = 255 (by setting OCR1C=255)
// OCR1A = 0 (as an initial value -- this value will increase to increase brightness of Red LED)
// OCR1B = 0 (as an initial value -- this value will increase to increase brightness of Blue LED)
// F = Fclk / (Prescale * (MAX+1) ) = 122Hz
// There is nothing too important about driving the Red and Blue LEDs at 122Hz, it is somewhat arbitrary,
// but it is fast enough to make it seem that the Red and Blue LEDs are not flickering.
// Later in the firmware, the OCR1A and OCR1B compare register values will change,
// but the period for Timer1 will always remain the same (with F = 122Hz, always) --
// with OCR1A = 0, the portion of the period with the Red LED on is a minimum
// so the Red LED is very dim,
// with OCR1A = 255, the portion of the period with the Red LED on is a maximum
// so the Red LED is very bright.
// with OCR1B = 0, the portion of the period with the Blue LED on is a minimum
// so the Blue LED is very dim,
// with OCR1B = 255, the portion of the period with the Blue LED on is a maximum
// so the Blue LED is very bright.
// the brightness of the Red LED can be any brightness between the min and max
// by varying the value of OCR1A between 0 and 255.
// the brightness of the Blue LED can be any brightness between the min and max
// by varying the value of OCR1B between 0 and 255.
//
// Please see the ATtiny25 datasheet for descriptions of these registers.
GTCCR = 0b01110000; // TSM=0 (we are not using synchronization mode)
// PWM1B=1 for PWM mode for compare register B
// COM1B1:0=11 for inverting PWM on OC1B (Blue LED output pin)
// FOC1B=0 (no Force Output Compare for compare register B)
// FOC1A=0 (no Force Output Compare for compare register A)
// PSR1=0 (do not reset the prescaler)
// PSR0=0 (do not reset the prescaler)
TCCR1 = 0b01111001; // CTC1=0 for PWM mode
// PWM1A=1 for PWM mode for compare register A
// COM1A1:0=11 for inverting PWM on OC1A (Red LED output pin)
// CS13:0=1001 for Prescale=256 (this starts Timer 1)
OCR1C = 255; // sets the MAX count for the PWM to 255 (to get PWM frequency of 122Hz)
OCR1A = 0; // start with minimum brightness for Red LED on OC1A (PB1, pin 6)
OCR1B = 0; // start with minimum brightness for Blue LED on OC1B (PB4, pin 3)
// Since we are only using hardware timers to drive the Red and Blue LEDs with PWM
// we will pulse the Green LED manually with the firmware (using the pulseGreen() function, which is called by the sendrgbElement() function)
// This loop goes through the lightTab, displaying each rgbElement in the table
// the loop knows that the last rgbElement has been displayed
// when it sees an rgbElement from lightTab that has both fadeTime=0 and holdTime=0
// This loop also starts the color sequence over from the beginning of lightTab if Start_Over is set
// (which happens when IR was detected by the IR detector)
int index = 0; // this points to the next rgbElement in the lightTab (initially pointing to the first rgbElement)
// send the entire 1.7-minute sequence 18 times so that it lasts about 1/2 hour.
// Actually, I measured the time, and the sequence lasts 2.33 minutes, so 13 times gives about 1/2 hour.
for (int count=0; count<180; count++) {
// send all rgbElements to LEDs (when both fadeTime = 0 and holdTime = 0 it signifies the last rgbElement in lightTab)
do {
sendrgbElement(index); // send one rgbElement to LEDs
index++; // increment to point to next rgbElement in lightTab
if ( Start_Over ) break; // if the IR detector saw IR, then we should reset and start over
} while ( !( (pgm_read_word(&lightTab[index].fadeTime) == 0 ) && (pgm_read_word(&lightTab[index].holdTime) == 0 ) ) );
if ( Start_Over ) break; // if the IR detector saw IR, then we should reset and start over
index = 0;
}
if ( Start_Over ) goto start_over; // if the IR detector saw IR, then we should reset and start over
// Shut down everything and put the CPU to sleep
cli(); // disable microcontroller interrupts
delay_ten_us(10000); // wait .1 second
TCCR0B &= 0b11111000; // CS02:CS00=000 to stop Timer0 (turn off IR emitter)
TCCR0A &= 0b00111111; // COM0A1:0=00 to disconnect OC0A from PB0 (pin 5)
TCCR1 &= 0b11110000; // CS13:CS10=0000 to stop Timer1 (turn off Red and Blue LEDs)
TCCR1 &= 0b11001111; // COM1A1:0=00 to disconnect OC1A from PB1 (pin 6)
GTCCR &= 0b11001111; // COM1B1:0=00 to disconnect OC1B from PB4 (pin 3)
DDRB = 0x00; // make PORTB all inputs (saves battery power)
PORTB = 0xFF; // enable pull-up resistors on all PORTB input pins (saves battery power)
MCUCR |= 0b00100000; // SE=1 (bit 5)
MCUCR |= 0b00010000; // SM1:0=10 to enable Power Down Sleep Mode (bits 4, 3)
sleep_cpu(); // put CPU into Power Down Sleep Mode
}