/* * Traffic light * * Copyright (C) 2019-2024 Aurelien Jarno * * Authors: * Aurelien Jarno * * This work is licensed under the terms of the GNU GPL, version 2. * See the COPYING file in the same directory as this file. */ /********************************************************************** * Global defines * **********************************************************************/ /* CPU */ #define F_CPU 8000000UL /* 8 MHz */ /* I/O pins */ #define BUTTON PB0 /* Also DATA prog */ #define LED_DATA PB1 /* Also CLK prog */ #define LED_PWR PB2 #define RESET PB3 /********************************************************************** * Includes * **********************************************************************/ #include #include #include #include #include #include #include /********************************************************************** * State machine * **********************************************************************/ enum state { state_off, state_red, state_orange, state_orange_red, state_green, state_blinking_on, state_blinking_off, }; struct state_desc { const struct __attribute__((__packed__)) { uint8_t r; uint8_t g; uint8_t b; } leds[3]; uint16_t duration; enum state next; enum state next_button_short; enum state next_button_medium; enum state next_button_long; }; const struct state_desc states_desc[] = { [state_off] = { .leds = { { .r = 0x00, .g = 0x00, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, }, .duration = 1, .next = state_off, .next_button_short = state_off, .next_button_medium = state_off, .next_button_long = state_off, }, [state_green] = { .leds = { { .r = 0x00, .g = 0x00, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, { .r = 0x00, .g = 0x7f, .b = 0x00, }, }, .duration = 13000, .next = state_orange, .next_button_short = state_orange, .next_button_medium = state_blinking_on, .next_button_long = state_off, }, [state_orange] = { .leds = { { .r = 0x00, .g = 0x00, .b = 0x00, }, { .r = 0x7f, .g = 0x2f, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, }, .duration = 1500, .next = state_red, .next_button_short = state_red, .next_button_medium = state_blinking_on, .next_button_long = state_off, }, /* Define to 1 for a four main states traffic light, as used in some countries, * i.e. going to orange + red in between red and green. */ #if 0 [state_red] = { .leds = { { .r = 0x7f, .g = 0x00, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, }, .duration = 13500, .next = state_orange_red, .next_button_short = state_orange_red, .next_button_medium = state_blinking_on, .next_button_long = state_off, }, [state_orange_red] = { .leds = { { .r = 0x7f, .g = 0x00, .b = 0x00, }, { .r = 0x7f, .g = 0x2f, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, }, .duration = 1500, .next = state_green, .next_button_short = state_green, .next_button_medium = state_blinking_on, .next_button_long = state_off, }, #else [state_red] = { .leds = { { .r = 0x7f, .g = 0x00, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, }, .duration = 15000, .next = state_green, .next_button_short = state_green, .next_button_medium = state_blinking_on, .next_button_long = state_off, }, #endif [state_blinking_on] = { .leds = { { .r = 0x00, .g = 0x00, .b = 0x00, }, { .r = 0x7f, .g = 0x2f, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, }, .duration = 500, .next = state_blinking_off, .next_button_short = state_blinking_off, .next_button_medium = state_red, .next_button_long = state_off, }, [state_blinking_off] = { .leds = { { .r = 0x00, .g = 0x00, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, { .r = 0x00, .g = 0x00, .b = 0x00, }, }, .duration = 500, .next = state_blinking_on, .next_button_short = state_blinking_on, .next_button_medium = state_red, .next_button_long = state_off, }, }; /********************************************************************** * Interrupt handlers * **********************************************************************/ /* Do nothing, this is only used to wake-up the MCU */ EMPTY_INTERRUPT(PCINT0_vect); /********************************************************************** * Functions * **********************************************************************/ static void set_leds(const uint8_t *data, uint8_t len) { uint8_t outlo, outhi; outlo = PORTB & ~(1 << LED_DATA); outhi = PORTB | (1 << LED_DATA); /* Reset signal */ PORTB = outlo; _delay_us(280); /* Send all bits */ for (uint8_t i = 0; i < len; i++) { asm volatile( " ldi __tmp_reg__, 8 \n\t" "loop: \n\t" " out %[port], %[outhi] \n\t" " nop \n\t" " sbrs %[data], 7 \n\t" " out %[port], %[outlo] \n\t" " lsl %[data] \n\t" " nop \n\t" " nop \n\t" " out %[port], %[outlo] \n\t" " dec __tmp_reg__ \n\t" " brne loop \n\t" : : [data] "r" (data[i]), [port] "I" (_SFR_IO_ADDR(PORTB)), [outhi] "r" (outhi), [outlo] "r" (outlo) : "cc" ); } } /********************************************************************** * Main code * **********************************************************************/ int main(void) { /* Initialization: disable interrupts, clear watchdog */ cli(); wdt_reset(); /* Switch to calibrated internal 8 MHz oscillator without prescaler, corresponding to a 8 MHz CPU clock */ CCP = 0xD8; CLKMSR = 0x00; CCP = 0xD8; CLKPSR = 0x00; /* Disable unused peripherals: ANA_COMP, TIM0, VLM, WDT */ ACSR = (1 << ACD); PRR = (1 << PRADC) | (1 << PRTIM0); VLMCSR = 0x00; CCP = 0xD8; WDTCSR = 0x00; /* Setup LED pins as low output, BUTTON and RESET pins as input with * pull-up */ PORTCR = 0x00; PORTB = 0x00; PUEB = (1 << BUTTON) | (1 << RESET); DDRB = (1 << LED_PWR) | (1 << LED_DATA); /* Setup interrupt on button change */ PCMSK = (1 << PCINT0); PCICR = (1 << PCIE0); for (;;) { /* Reset power-on duration and start with the red color */ uint32_t on_duration = 0; uint16_t button_duration = 0; enum state state = state_red; /* Power-on the LED and wait for it to be ready */ PORTB |= (1 << LED_PWR); _delay_ms(10); /* Loop through all the states until reaching the off state */ while (state != state_off) { set_leds((const uint8_t *)states_desc[state].leds, sizeof(states_desc[state].leds)); for (uint16_t d = states_desc[state].duration; d > 0; d--) { _delay_ms(1); on_duration++; /* Count the button push time, and debounce the button by * ignoring short pulses. */ if ((PINB & (1 << BUTTON)) == 0) { if (button_duration != 65535) { button_duration++; } } else if (button_duration > 10) { break; } else { button_duration = 0; } } if (((PINB & (1 << BUTTON)) != 0) && button_duration > 10) { if (button_duration < 500) { state = states_desc[state].next_button_short; } else if (button_duration < 1500) { state = states_desc[state].next_button_medium; } else { state = states_desc[state].next_button_long; } on_duration = 0; button_duration = 0; } else if (on_duration > 1800000) { state = state_off; } else { state = states_desc[state].next; } } /* Power-off the LED */ PORTB &= ~(1 << LED_PWR); /* Clear and allow interrupts to wake-up the MCU and go to power down mode */ PCIFR |= (1 << PCIF0); set_sleep_mode(SLEEP_MODE_PWR_DOWN); sei(); sleep_mode(); /* Returned from interrupt, disable interrupts */ cli(); /* Wait for the button to be released */ while ((PINB & (1 << BUTTON)) == 0) { ;; } } /* Never reached */ return 0; }