Files
2025-09-06 11:21:38 +02:00

287 lines
6.6 KiB
C
Raw Permalink 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>
#define BUTTON_PIN_MASK 0x01 // PA0 TODO: using RESET/UPDI pin
#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 pin
#define PA7_SET_MASK 0x80 ///< Red LED
#define MAIN_LOOP_SLEEP 10U // Main loop delay in ms
#define BUTTON_LONG_PRESS_DURATION_MS 1000U // Long press threshold
#define BUTTON_IGNORE_DURATION_MS 1000U // Time button ignored after long press
/** Convert milliseconds to system ticks */
#define MS_TO_TICKS(ms) ((ms) / MAIN_LOOP_SLEEP)
/** Global flags */
volatile bool bLedEnabled = true;
volatile bool bBtnPressed = false;
// Forward declarations
void blinkLed(bool resetCounters);
static inline void leds_off(void);
static inline void leds_on(void);
static void battery_level_indicator(void);
static void handleSwitch(void);
/**
* @brief Main entry point
*/
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;
while (true)
{
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
}
}
_delay_ms(MAIN_LOOP_SLEEP);
}
}
/**
* @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; // restart normal sequence
}
break;
}
}