RGB

From Noisebridge Wiki
Revision as of 04:56, 10 February 2026 by Maintenance script (talk | contribs) (Imported from Noisebridge wiki backup)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)
Jump to navigation Jump to search

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"

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


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
}