diff options
Diffstat (limited to 'software/main.c')
-rw-r--r-- | software/main.c | 291 |
1 files changed, 291 insertions, 0 deletions
diff --git a/software/main.c b/software/main.c new file mode 100644 index 0000000..5261d34 --- /dev/null +++ b/software/main.c @@ -0,0 +1,291 @@ +/* + * Traffic light + * + * Copyright (C) 2019-2022 Aurelien Jarno + * + * Authors: + * Aurelien Jarno <aurelien@aurel32.net> + * + * 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 <avr/interrupt.h> +#include <avr/io.h> +#include <avr/pgmspace.h> +#include <avr/sleep.h> +#include <avr/wdt.h> +#include <stdint.h> +#include <util/delay.h> + + +/********************************************************************** + * State machine * + **********************************************************************/ +enum state { + state_off, + state_red, + state_orange, + 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, + }, + [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, + }, + + [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; +} |