Files
lezyne-rear-light-firmware/main.c
2025-09-06 00:05:39 +02:00

282 lines
6.7 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.
*
*/
#include <stdint.h>
#include <stdbool.h>
#include <avr/io.h>
#include <util/delay.h>
/** @defgroup LED_Masks LED bitmasks for PORTA
* @{
*/
#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 ///< Switch input (was green LED pin) TODO: Switch to PA0
#define PA7_SET_MASK 0x80 ///< Red LED
/** @} */
#define MAIN_LOOP_SLEEP 10U // Main loop delay in ms (system tick)
#define BUTTON_PRESS_DURATION 1000U // Button needs to be pressed for at least 1000ms
/** @brief Convert milliseconds to system ticks (integer division). */
#define MS_TO_TICKS(ms) ((ms) / MAIN_LOOP_SLEEP)
/** @brief Global flags */
volatile bool bLedEnabled = true;
volatile bool bBtnPressed = false;
// Function forward declarations
void blinkLed(void);
static inline void leds_off(void);
static inline void leds_on(void);
static void handleSwitch(void);
/**
* @brief Main entry point.
*
* Initializes I/O ports and runs the main loop, periodically calling @ref blinkLed().
*
* @return never returns
*/
int main(void)
{
// --- configure LED pins as outputs ---
VPORTA.DIR = (PA1_SET_MASK | PA2_SET_MASK | PA3_SET_MASK |
/*PA6_SET_MASK |*/ // PA6 now input (switch)
PA7_SET_MASK);
// Configure PA6 as input with pull-up TODO: Switch to PA0
VPORTA.DIR &= ~PA6_SET_MASK; // Input
PORTA.PIN6CTRL = PORT_PULLUPEN_bm; // Pull-up enabled
// --- ensure all LEDs off at startup ---
leds_off();
VPORTA.OUT &= (uint8_t) ~(PA7_SET_MASK);
bool bLedEnabledOld = bLedEnabled;
while (true)
{
handleSwitch(); // check switch state
if (bBtnPressed)
{
leds_on();
}
else
{
leds_off();
}
if (bLedEnabledOld != bLedEnabled)
{
bLedEnabledOld = bLedEnabled;
leds_off();
_delay_ms(1000);
}
else
{
if ((bLedEnabled) && (!bBtnPressed))
{
blinkLed();
}
}
_delay_ms(MAIN_LOOP_SLEEP);
}
}
/**
* @brief Switch off all controlled LEDs (PA1, PA2, PA3).
*
* @note Declared inline for speed (single instruction sequence).
*/
static inline void leds_off(void)
{
VPORTA.OUT &= (uint8_t) ~(PA1_SET_MASK | PA2_SET_MASK | PA3_SET_MASK);
}
/**
* @brief Switch on all controlled LEDs (PA1, PA2, PA3).
*
* @note Declared inline for speed (single instruction sequence).
*/
static inline void leds_on(void)
{
VPORTA.OUT |= (PA1_SET_MASK | PA2_SET_MASK | PA3_SET_MASK);
}
/**
* @brief Handle momentary switch input on PA6. TODO: Switch to PA0
*
* A press longer than 2 seconds toggles ::bLedEnabled.
* Uses simple state and counters for debouncing and long-press detection.
*/
static void handleSwitch(void)
{
static uint16_t pressTicks = 0; ///< press duration counter
static bool prevPressed = false; ///< previous switch state
bool pressed = !(VPORTA.IN & PA6_SET_MASK); // active-low
if (pressed)
{
bBtnPressed = true;
if (pressTicks < 0xFFFF)
{
// prevent overflow
pressTicks++;
}
}
else
{
bBtnPressed = false;
pressTicks = 0;
}
if (prevPressed && pressTicks >= MS_TO_TICKS(BUTTON_PRESS_DURATION))
{
// long press detected → toggle blinking
bLedEnabled = !bLedEnabled;
}
prevPressed = pressed;
}
/**
* @brief LED blink state machine (bike rear light style).
*
* Implements a "rounded" pulsing blink sequence:
*
* Normal cycle (~1 s total):
* - Step 0: all off, wait 100 ms
* - Step 1: LED 12 + 78 ON, wait 100 ms
* - Step 2: all LEDs ON, wait 200 ms
* - Step 3: only LED 12 ON, wait 100 ms
* - Step 4: all off, wait 400 ms → restart
*
* Special behavior:
* - Every 3rd cycle, after step 4, all LEDs are switched on for 300 ms.
*
* Timing is based on @ref MAIN_LOOP_SLEEP ticks.
*/
void blinkLed(void)
{
// --- precomputed constants (compile-time) ---
const uint8_t T50 = MS_TO_TICKS(50); ///< 50 ms in ticks
const uint8_t T100 = MS_TO_TICKS(100); ///< 100 ms in ticks
const uint8_t T250 = MS_TO_TICKS(250); ///< 250 ms in ticks
// --- persistent state ---
static uint16_t counter = 0; ///< counts ticks for current state
static uint8_t state = 0; ///< current step in the sequence
static uint8_t cycle = 0; ///< cycle counter (02)
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; // restart normal sequence
}
break;
}
}