/*
This is working well and pretty easy to undertand how the code works.
7/29/25 CM:
I believe this is working code!!
1. 12 hour leds, one lights every hour
2. 12 5-minute leds, one lights every 5 minutes
3. RGB led cyles through color spectrum once every 5 minutes
a. 300 seconds / 3 = 100 second sections.
7/30/25 CM:
1. RGB LED tested on breadboard, works as expected!
*** USE THIS FINISHED VERSION ***
*** IF NOT USING SET TIME BUTTONS ***
*/
#define F_CPU 8000000UL
#include <avr/io.h>
#include <avr/interrupt.h>
#include <util/delay.h>
// --- Hour LED Pins (PORTC: PC0–PC3) ---
#define P0 PC0
#define P1 PC1
#define P2 PC2
#define P3 PC3
// --- Minute LED Pins (PD6, PD7, PB0, PB1) ---
#define Q0 PD6
#define Q1 PD7
#define Q2 PB0
#define Q3 PB1
// +++ Step 2
// RGB brightness levels (0–255)
uint8_t red = 255;
uint8_t green = 0;
uint8_t blue = 0;
// --- LED Mappings: 12 LEDs per group, high_pin, low_pin ---
const uint8_t hour_leds[12][2] = {
{P0, P1}, {P1, P0}, {P0, P2}, {P2, P0},
{P0, P3}, {P3, P0}, {P1, P2}, {P2, P1},
{P1, P3}, {P3, P1}, {P2, P3}, {P3, P2}
};
const uint8_t minute_leds[12][2] = {
{Q0, Q1}, {Q1, Q0}, {Q0, Q2}, {Q2, Q0},
{Q0, Q3}, {Q3, Q0}, {Q1, Q2}, {Q2, Q1},
{Q1, Q3}, {Q3, Q1}, {Q2, Q3}, {Q3, Q2}
};
volatile uint32_t seconds = 0;
uint8_t hour = 0;
uint8_t five_minute_index = 0;
// === Timer1 Initialization ===
void timer1_init() {
TCCR1A = 0; // Normal mode
TCCR1B = (1 << WGM12); // CTC mode 4, OCR1A = TOP
TCCR1B |= (1 << CS12); // Prescaler 256
OCR1A = 31250; // Compare match at 1 second
TIMSK1 |= (1 << OCIE1A); // Compare match interrupt
sei(); // Enable global interrupts
}
/*
Fast PWM mode on:
PB3 / OC2A for Red (Timer2)
PD3 / OC2B for Green (Timer2)
PD5 / OC0B for Blue (Timer0)
*/
// === Timer0 and Timer2 Init for RGB PWM ===
void rgb_pwm_init(void) {
// --- RED + GREEN on Timer2 (8-bit) ---
// Fast PWM, non-inverting on OC2A (PB3), OC2B (PD3)
// This sets both channels oF Timer2
TCCR2A = (1 << COM2A1) | (1 << COM2B1) | (1 << WGM21) | (1 << WGM20);
TCCR2B = (1 << CS21); // Prescaler 8 -> ~3.9kHz PWM
OCR2A = 0; // RED PWM (PB3)
OCR2B = 0; // GREEN PWM (PD3)
// --- BLUE on Timer0 (8-bit) ---
// Fast PWM, non-inverting on OC0B (PD5)
TCCR0A = (1 << COM0B1) | (1 << WGM01) | (1 << WGM00);
TCCR0B = (1 << CS01); // Prescaler 8 → ~3.9kHz PWM
OCR0B = 0; // BLUE PWM (PD5)
// Set RGB pins as output
DDRB |= (1 << PB3); // RED
DDRD |= (1 << PD3) | (1 << PD5); // GREEN and BLUE
}
// === Cycle colors function ===
void update_rgb_color(uint16_t seconds) {
// To adjust timing interval to one minute:
// 1. change 100 to 20 (3 places)
// 2. change ISR to 60 (one place)
uint8_t phase = seconds / 20; // 20 = 1 min total, 100 = 5 min total
uint8_t step = (seconds % 20) * 255 / 20; // this maps seconds 1 to 60, to value 1 to 255
switch (phase) {
case 0: // Red → Green
red = 255 - step;
green = step;
blue = 0;
break;
case 1: // Green → Blue
red = 0;
green = 255 - step;
blue = step;
break;
case 2: // Blue → Red
red = step;
green = 0;
blue = 255 - step;
break;
}
OCR2A = red; // Red: PB3
OCR2B = green; // Green: PD3
OCR0B = blue; // Blue: PD5
/*
Serial.print("seconds:"); Serial.print(seconds);
Serial.print(" phase:"); Serial.print(phase);
Serial.print(" step:"); Serial.println(step);
Serial.print("red: "); Serial.println(red);
Serial.print("green: "); Serial.println(green);
Serial.print("blue: "); Serial.println(blue);
*/
}
// === Reset all pins to Hi-Z ===
void reset_hour_pins() {
// Set as Inputs
DDRC &= ~((1 << P0) | (1 << P1) | (1 << P2) | (1 << P3));
// Set as 0, no pullup
PORTC &= ~((1 << P0) | (1 << P1) | (1 << P2) | (1 << P3));
}
void reset_minute_pins() {
DDRD &= ~((1 << Q0) | (1 << Q1));
PORTD &= ~((1 << Q0) | (1 << Q1));
DDRB &= ~((1 << Q2) | (1 << Q3));
PORTB &= ~((1 << Q2) | (1 << Q3));
}
// === Light 1 of 12 Hour LEDs ===
void light_hour_led(uint8_t index) {
reset_hour_pins(); // All pins Hi-Z
uint8_t high = hour_leds[index][0];
uint8_t low = hour_leds[index][1];
DDRC |= (1 << high) | (1 << low);
PORTC |= (1 << high);
PORTC &= ~(1 << low);
}
// === Light 1 of 12 Minute LEDs ===
void light_minute_led(uint8_t index) {
reset_minute_pins(); // All pins Hi-Z
uint8_t high = minute_leds[index][0];
uint8_t low = minute_leds[index][1];
// === Set HIGH pin ===
if (high == PB0 || high == PB1) {
DDRB |= (1 << high);
PORTB |= (1 << high);
}
else {
DDRD |= (1 << high);
PORTD |= (1 << high);
}
// === Set LOW pin ===
if (low == PB0 || low == PB1) {
DDRB |= (1 << low);
PORTB &= ~(1 << low);
}
else {
DDRD |= (1 << low);
PORTD &= ~(1 << low);
}
}
// === Timer ISR: Fire every second ===
ISR(TIMER1_COMPA_vect) {
seconds++;
// Every hour
if (seconds % 60 == 0) { // 3600, use 60 for testing
hour++;
if (hour >= 12) hour = 0;
}
// Every 5 minutes
if (seconds % 5 == 0) { // 300, use 5 for testing
five_minute_index++;
if (five_minute_index >= 12) five_minute_index = 0;
}
update_rgb_color(seconds % 60); // Use 60 for 1 min, 300 for 5 min
}
// === Main Loop ===
int main(void) {
Serial.begin(115200);
timer1_init();
rgb_pwm_init();
reset_hour_pins();
reset_minute_pins();
while (1) {
light_hour_led(hour);
_delay_ms(3);
light_minute_led(five_minute_index);
_delay_ms(3);
}
}