aboutsummaryrefslogtreecommitdiff
path: root/software/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'software/main.c')
-rw-r--r--software/main.c291
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;
+}