294 lines
7.1 KiB
C
294 lines
7.1 KiB
C
/**
|
||
* @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 1–2
|
||
#define PA2_SET_MASK 0x04 ///< LED 3–6
|
||
#define PA3_SET_MASK 0x08 ///< LED 7–8
|
||
#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_LONG_PRESS_DURATION_MS 1000U // Long press detection threshold
|
||
#define BUTTON_IGNORE_DURATION_MS 1000U // Time that the button is ignored after a long press
|
||
|
||
/** @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(bool resetCounters);
|
||
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)
|
||
{
|
||
// A long press detected --> confirm with a blink
|
||
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 the persistent counters
|
||
}
|
||
else
|
||
{
|
||
if ((bLedEnabled) && (!bBtnPressed))
|
||
{
|
||
blinkLed(false);
|
||
}
|
||
}
|
||
|
||
_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_LONG_PRESS_DURATION_MS))
|
||
{
|
||
// long press detected → toggle blinking
|
||
bLedEnabled = !bLedEnabled;
|
||
}
|
||
|
||
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)
|
||
{
|
||
// --- precomputed constants ---
|
||
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; ///< ticks for current state
|
||
static uint8_t state = 2; ///< start with first LEDs-on state
|
||
static uint8_t cycle = 0; ///< cycle counter
|
||
|
||
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; // restart normal sequence
|
||
}
|
||
break;
|
||
}
|
||
}
|