309 lines
7.1 KiB
C
309 lines
7.1 KiB
C
/**
|
||
* @file main.c
|
||
* @brief Bike rear light implementation for ATTINY202 with low-power standby.
|
||
*/
|
||
|
||
#include <stdint.h>
|
||
#include <stdbool.h>
|
||
#include <avr/io.h>
|
||
#include <util/delay.h>
|
||
#include <avr/sleep.h>
|
||
#include <avr/interrupt.h>
|
||
|
||
#define BUTTON_PIN_MASK 0x01 // PA0
|
||
#define PA1_SET_MASK 0x02 // LED 1–2
|
||
#define PA2_SET_MASK 0x04 // LED 3–6
|
||
#define PA3_SET_MASK 0x08 // LED 7–8
|
||
#define PA6_SET_MASK 0x40 // Green LED
|
||
#define PA7_SET_MASK 0x80 // Red LED
|
||
|
||
#define MAIN_LOOP_SLEEP 50U // Loop period in ms
|
||
#define BUTTON_LONG_PRESS_DURATION_MS 1000U // Long press threshold
|
||
#define BUTTON_IGNORE_DURATION_MS 1000U // Ignore after long press
|
||
|
||
#define MS_TO_TICKS(ms) ((ms) / MAIN_LOOP_SLEEP)
|
||
|
||
volatile bool bLedEnabled = false;
|
||
volatile bool bBtnPressed = false;
|
||
|
||
static inline void leds_off(void);
|
||
static inline void leds_on(void);
|
||
static void battery_level_indicator(void);
|
||
static void handleSwitch(void);
|
||
void blinkLed(bool resetCounters);
|
||
|
||
/* --- Timer init: Use RTC PIT for periodic wake-up --- */
|
||
void init_timer(void)
|
||
{
|
||
RTC.CLKSEL = RTC_CLKSEL_INT1K_gc; // 1 kHz ULP clock for RTC
|
||
while (RTC.STATUS > 0)
|
||
{
|
||
} // Wait for sync
|
||
|
||
RTC.PITINTCTRL = RTC_PI_bm; // Enable PIT interrupt
|
||
RTC.PITCTRLA = RTC_PERIOD_CYC64_gc // ≈64 ms wake-up (~50 ms)
|
||
| RTC_PITEN_bm; // Enable PIT
|
||
}
|
||
|
||
ISR(RTC_PIT_vect)
|
||
{
|
||
RTC.PITINTFLAGS = RTC_PI_bm; // Clear interrupt flag
|
||
}
|
||
|
||
/* --- MAIN --- */
|
||
int main(void)
|
||
{
|
||
// Configure LED pins as outputs
|
||
VPORTA.DIR = (PA1_SET_MASK | PA2_SET_MASK | PA3_SET_MASK | PA6_SET_MASK | PA7_SET_MASK);
|
||
|
||
// Configure PA0 as input with pull-up
|
||
VPORTA.DIR &= ~BUTTON_PIN_MASK; // Input
|
||
PORTA.PIN0CTRL = PORT_PULLUPEN_bm; // Pull-up enabled
|
||
|
||
// Ensure all LEDs off at startup
|
||
leds_off();
|
||
battery_level_indicator(); // TODO: Implement
|
||
|
||
bool bLedEnabledOld = bLedEnabled;
|
||
|
||
cli();
|
||
init_timer();
|
||
sei();
|
||
|
||
set_sleep_mode(SLEEP_MODE_STANDBY);
|
||
|
||
while (1)
|
||
{
|
||
handleSwitch(); // Check switch state
|
||
|
||
// Light LEDs while button is pressed
|
||
if (bBtnPressed)
|
||
{
|
||
leds_on();
|
||
}
|
||
else
|
||
{
|
||
leds_off();
|
||
}
|
||
|
||
// Long press detected → show confirmation blink
|
||
if (bLedEnabledOld != bLedEnabled)
|
||
{
|
||
bLedEnabledOld = bLedEnabled;
|
||
|
||
leds_off();
|
||
_delay_ms(BUTTON_IGNORE_DURATION_MS / 10);
|
||
leds_on();
|
||
_delay_ms(BUTTON_IGNORE_DURATION_MS / 10);
|
||
leds_off();
|
||
_delay_ms(BUTTON_IGNORE_DURATION_MS);
|
||
|
||
blinkLed(true); // reset blink state machine
|
||
}
|
||
else
|
||
{
|
||
if (bLedEnabled && !bBtnPressed)
|
||
{
|
||
blinkLed(false); // run normal blink
|
||
}
|
||
}
|
||
|
||
sleep_enable();
|
||
sleep_cpu(); // Sleep until PIT wakes
|
||
sleep_disable();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Turn off all controlled LEDs (PA1, PA2, PA3)
|
||
*/
|
||
static inline void leds_off(void)
|
||
{
|
||
VPORTA.OUT &= (uint8_t) ~(PA1_SET_MASK | PA2_SET_MASK | PA3_SET_MASK);
|
||
}
|
||
|
||
/**
|
||
* @brief Turn on all controlled LEDs (PA1, PA2, PA3)
|
||
*/
|
||
static inline void leds_on(void)
|
||
{
|
||
VPORTA.OUT |= (PA1_SET_MASK | PA2_SET_MASK | PA3_SET_MASK);
|
||
}
|
||
|
||
/**
|
||
* @brief Battery monitoring
|
||
*/
|
||
static void battery_level_indicator(void)
|
||
{
|
||
// TODO: Implement
|
||
VPORTA.OUT |= (PA6_SET_MASK | PA7_SET_MASK); // green + red OFF
|
||
}
|
||
|
||
/**
|
||
* @brief Handle momentary switch input on PA0
|
||
*
|
||
* A long press toggles ::bLedEnabled.
|
||
*/
|
||
static void handleSwitch(void)
|
||
{
|
||
static uint16_t pressTicks = 0; ///< Press duration counter
|
||
static bool prevPressed = false; ///< Previous button state
|
||
|
||
bool pressed = !(VPORTA.IN & BUTTON_PIN_MASK); // Active-low
|
||
|
||
if (pressed)
|
||
{
|
||
bBtnPressed = true;
|
||
if (pressTicks < 0xFFFF)
|
||
pressTicks++; // Prevent overflow
|
||
}
|
||
else
|
||
{
|
||
bBtnPressed = false;
|
||
pressTicks = 0;
|
||
}
|
||
|
||
if (prevPressed && pressTicks >= MS_TO_TICKS(BUTTON_LONG_PRESS_DURATION_MS))
|
||
{
|
||
bLedEnabled = !bLedEnabled; // Toggle LED blinking
|
||
}
|
||
|
||
prevPressed = pressed;
|
||
}
|
||
|
||
/**
|
||
* @brief LED blink state machine (bike rear light style)
|
||
*
|
||
* Normal cycle:
|
||
* 0: all off, wait 250 ms
|
||
* 2: LED 1–2 + 7–8 ON, wait 50 ms
|
||
* 5: all off, wait 100 ms
|
||
* 7: LED 1–2 + 7–8 ON, wait 50 ms
|
||
* 10: all off, wait 250 ms
|
||
* 12: LED 3–6 ON, wait 50 ms
|
||
* 14: all off, wait 100 ms
|
||
* 16: LED 3–6 ON, wait 50 ms
|
||
* 18: all off, wait 250 ms → restart
|
||
* Special: every 3rd cycle, all LEDs ON for 250 ms
|
||
*/
|
||
void blinkLed(bool resetCounters)
|
||
{
|
||
const uint8_t T50 = MS_TO_TICKS(50);
|
||
const uint8_t T100 = MS_TO_TICKS(100);
|
||
const uint8_t T250 = MS_TO_TICKS(250);
|
||
|
||
static uint16_t counter = 0;
|
||
static uint8_t state = 2; // start with LEDs-on state
|
||
static uint8_t cycle = 0;
|
||
|
||
if (resetCounters)
|
||
{
|
||
counter = 0;
|
||
state = 12; // start with LEDs on
|
||
cycle = 0;
|
||
}
|
||
|
||
counter++;
|
||
|
||
switch (state)
|
||
{
|
||
case 0: // all LEDs off, wait 250 ms
|
||
leds_off();
|
||
if (counter >= T250)
|
||
{
|
||
counter = 0;
|
||
state = 2;
|
||
}
|
||
break;
|
||
|
||
case 2: // LED 1–2 + 7–8 on, wait 50 ms
|
||
VPORTA.OUT |= (PA1_SET_MASK | PA3_SET_MASK);
|
||
if (counter >= T50)
|
||
{
|
||
counter = 0;
|
||
state = 5;
|
||
}
|
||
break;
|
||
|
||
case 5: // all LEDs off, wait 100 ms
|
||
leds_off();
|
||
if (counter >= T100)
|
||
{
|
||
counter = 0;
|
||
state = 7;
|
||
}
|
||
break;
|
||
|
||
case 7: // LED 1–2 + 7–8 on, wait 50 ms
|
||
VPORTA.OUT |= (PA1_SET_MASK | PA3_SET_MASK);
|
||
if (counter >= T50)
|
||
{
|
||
counter = 0;
|
||
state = 10;
|
||
}
|
||
break;
|
||
|
||
case 10: // all LEDs off, wait 250 ms
|
||
leds_off();
|
||
if (counter >= T250)
|
||
{
|
||
counter = 0;
|
||
state = 12;
|
||
}
|
||
break;
|
||
|
||
case 12: // LED 3–6 on, wait 50 ms
|
||
VPORTA.OUT |= PA2_SET_MASK;
|
||
if (counter >= T50)
|
||
{
|
||
counter = 0;
|
||
state = 14;
|
||
}
|
||
break;
|
||
|
||
case 14: // all LEDs off, wait 100 ms
|
||
leds_off();
|
||
if (counter >= T100)
|
||
{
|
||
counter = 0;
|
||
state = 16;
|
||
}
|
||
break;
|
||
|
||
case 16: // LED 3–6 on, wait 50 ms
|
||
VPORTA.OUT |= PA2_SET_MASK;
|
||
if (counter >= T50)
|
||
{
|
||
counter = 0;
|
||
state = 18;
|
||
}
|
||
break;
|
||
|
||
case 18: // all LEDs off, wait 250 ms
|
||
leds_off();
|
||
if (counter >= T250)
|
||
{
|
||
counter = 0;
|
||
if (++cycle >= 3)
|
||
{
|
||
cycle = 0;
|
||
state = 20; // special all-LEDs-on state
|
||
}
|
||
else
|
||
{
|
||
state = 0; // restart normal cycle
|
||
}
|
||
}
|
||
break;
|
||
|
||
case 20: // special: all LEDs on for 250 ms
|
||
VPORTA.OUT |= (PA1_SET_MASK | PA2_SET_MASK | PA3_SET_MASK);
|
||
if (counter >= T250)
|
||
{
|
||
counter = 0;
|
||
state = 0;
|
||
}
|
||
break;
|
||
}
|
||
}
|