Files
lezyne-rear-light-firmware/main.c
2025-09-13 17:51:19 +02:00

309 lines
7.1 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* @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 12
#define PA2_SET_MASK 0x04 // LED 36
#define PA3_SET_MASK 0x08 // LED 78
#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 12 + 78 ON, wait 50 ms
* 5: all off, wait 100 ms
* 7: LED 12 + 78 ON, wait 50 ms
* 10: all off, wait 250 ms
* 12: LED 36 ON, wait 50 ms
* 14: all off, wait 100 ms
* 16: LED 36 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 12 + 78 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 12 + 78 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 36 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 36 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;
}
}