437 lines
10 KiB
C
437 lines
10 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>
|
||
#include <avr/sleep.h>
|
||
#include <avr/interrupt.h>
|
||
|
||
#define BUTTON_PIN_MASK 0x01 // PA0 used as RESET/UPDI pin
|
||
#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 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_SHORT_PRESS_DURATION_MS 50U // Short press threshold
|
||
#define BUTTON_IGNORE_DURATION_MS 2000U // Time button ignored after long press
|
||
#define BUTTON_CONFIRMATION_BLINK_LOOPS 10U // Blink animation for confirmation
|
||
#define BUTTON_CONFIRMATION_BLINK_DURATION_MS 50U
|
||
|
||
/** Convert milliseconds to system ticks */
|
||
#define MS_TO_TICKS(ms) ((ms) / MAIN_LOOP_SLEEP)
|
||
|
||
typedef enum _Mode
|
||
{
|
||
ANIMATION_BLINK,
|
||
ANIMATION_GLOW,
|
||
STATIC_FULL,
|
||
MAX_COUNT,
|
||
} eMode;
|
||
|
||
/** Global flags */
|
||
volatile bool bLedEnabled = true;
|
||
volatile bool bBtnPressed = false;
|
||
volatile eMode eModeCurrent = ANIMATION_BLINK;
|
||
|
||
// Forward declarations
|
||
ISR(PORTA_PORT_vect);
|
||
static void software_reset(void);
|
||
|
||
void initPWM(void);
|
||
void setPWM_PA2(uint8_t duty);
|
||
|
||
static inline void leds_off(void);
|
||
static inline void leds_on(void);
|
||
static void battery_level_indicator(void);
|
||
static bool handleSwitch(void);
|
||
static inline void switchMode(void);
|
||
void ledAnimationBlink(bool resetCounters);
|
||
void ledAnimationGlow(void);
|
||
void ledStaticFull(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);
|
||
initPWM();
|
||
|
||
// Configure PA0 as input with pull-up
|
||
VPORTA.DIR &= ~BUTTON_PIN_MASK; // Input
|
||
PORTA.PIN0CTRL = PORT_PULLUPEN_bm; // Pull-up enabled
|
||
|
||
leds_off(); // Ensure all LEDs off at startup
|
||
battery_level_indicator(); // TODO: Implement
|
||
|
||
bool bLedEnabledOld = bLedEnabled;
|
||
eModeCurrent = ANIMATION_BLINK; // Set the mode to start with
|
||
|
||
while (true)
|
||
{
|
||
bBtnPressed = 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;
|
||
|
||
for (uint8_t i = 0U; i < BUTTON_CONFIRMATION_BLINK_LOOPS; i++)
|
||
{
|
||
leds_off();
|
||
_delay_ms(BUTTON_CONFIRMATION_BLINK_DURATION_MS);
|
||
leds_on();
|
||
_delay_ms(BUTTON_CONFIRMATION_BLINK_DURATION_MS);
|
||
leds_off();
|
||
}
|
||
|
||
// Give time until button is released
|
||
_delay_ms(BUTTON_IGNORE_DURATION_MS);
|
||
|
||
// Activate the interrupt for PA0
|
||
PORTA.PIN0CTRL = PORT_PULLUPEN_bm | PORT_ISC_FALLING_gc;
|
||
|
||
set_sleep_mode(SLEEP_MODE_STANDBY); // Deepest sleep mode (standby)
|
||
sleep_enable(); // Enable sleep
|
||
|
||
cli(); // Disable global interrupts
|
||
sei(); // Re-enable interrupts
|
||
sleep_cpu(); // MCU sleeps here
|
||
}
|
||
else
|
||
{
|
||
if (bLedEnabled && !bBtnPressed)
|
||
{
|
||
switch (eModeCurrent)
|
||
{
|
||
case ANIMATION_BLINK:
|
||
ledAnimationBlink(false); // run normal blink
|
||
break;
|
||
case ANIMATION_GLOW:
|
||
ledAnimationGlow();
|
||
break;
|
||
case STATIC_FULL:
|
||
ledStaticFull();
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
|
||
_delay_ms(MAIN_LOOP_SLEEP);
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief Move to next mode
|
||
*/
|
||
static inline void switchMode(void)
|
||
{
|
||
eModeCurrent = (eModeCurrent + 1) % MAX_COUNT;
|
||
}
|
||
|
||
void initPWM(void)
|
||
{
|
||
// No PORTMUX needed - PA2 is WO2 by default
|
||
|
||
// TCA0 in normal mode (single slope PWM)
|
||
TCA0.SINGLE.CTRLB = TCA_SINGLE_CMP2EN_bm | TCA_SINGLE_WGMODE_SINGLESLOPE_gc;
|
||
|
||
// Set period for ~19.5kHz PWM (5MHz / 256 = ~19.5kHz)
|
||
TCA0.SINGLE.PER = 0xFF;
|
||
|
||
// Start timer with DIV1 (no prescaler)
|
||
TCA0.SINGLE.CTRLA = TCA_SINGLE_CLKSEL_DIV1_gc | TCA_SINGLE_ENABLE_bm;
|
||
}
|
||
|
||
void setPWM_PA2(uint8_t duty)
|
||
{
|
||
TCA0.SINGLE.CMP2 = duty;
|
||
}
|
||
|
||
/**
|
||
* @brief Turn off all controlled LEDs (PA1, PA2, PA3)
|
||
*/
|
||
static inline void leds_off(void)
|
||
{
|
||
VPORTA.OUT &= (uint8_t) ~(PA1_SET_MASK | PA3_SET_MASK);
|
||
setPWM_PA2(0U);
|
||
}
|
||
|
||
/**
|
||
* @brief Turn on all controlled LEDs (PA1, PA2, PA3)
|
||
*/
|
||
static inline void leds_on(void)
|
||
{
|
||
VPORTA.OUT |= (PA1_SET_MASK | PA3_SET_MASK);
|
||
setPWM_PA2(255U);
|
||
}
|
||
|
||
/**
|
||
* @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.
|
||
* A short press swiches the mode.
|
||
*/
|
||
static bool 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)
|
||
{
|
||
if (pressTicks < 0xFFFF)
|
||
pressTicks++; // Prevent overflow
|
||
}
|
||
else
|
||
{
|
||
// Button released
|
||
if (prevPressed)
|
||
{
|
||
// Check if it was a short press (not a long press)
|
||
if (pressTicks >= MS_TO_TICKS(BUTTON_SHORT_PRESS_DURATION_MS) &&
|
||
pressTicks < MS_TO_TICKS(BUTTON_LONG_PRESS_DURATION_MS))
|
||
{
|
||
switchMode();
|
||
}
|
||
}
|
||
|
||
pressTicks = 0;
|
||
}
|
||
|
||
if (pressed && pressTicks >= MS_TO_TICKS(BUTTON_LONG_PRESS_DURATION_MS))
|
||
{
|
||
bLedEnabled = !bLedEnabled; // Toggle LED blinking
|
||
}
|
||
|
||
prevPressed = pressed;
|
||
return pressed;
|
||
}
|
||
|
||
/**
|
||
* @brief Perform software reset
|
||
*/
|
||
static void software_reset(void)
|
||
{
|
||
CCP = CCP_IOREG_gc; // unlock protected registers
|
||
RSTCTRL.SWRR = 1; // trigger software reset
|
||
while (1)
|
||
; // wait for reset
|
||
}
|
||
|
||
/**
|
||
* @brief Interrupt service routine
|
||
*/
|
||
ISR(PORTA_PORT_vect)
|
||
{
|
||
// Clear interrupt flags for PA0
|
||
PORTA.INTFLAGS = BUTTON_PIN_MASK;
|
||
|
||
if (!(VPORTA.IN & BUTTON_PIN_MASK)) // check PA0 low
|
||
{
|
||
// Turn off all LEDs
|
||
software_reset();
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @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 ledAnimationBlink(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
|
||
setPWM_PA2(255U);
|
||
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
|
||
setPWM_PA2(255U);
|
||
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
|
||
leds_on();
|
||
if (counter >= T250)
|
||
{
|
||
counter = 0;
|
||
state = 0; // restart normal sequence
|
||
}
|
||
break;
|
||
}
|
||
}
|
||
|
||
/**
|
||
* @brief All LEDs with static full power
|
||
*/
|
||
void ledStaticFull(void)
|
||
{
|
||
leds_on();
|
||
}
|
||
|
||
/**
|
||
* @brief Inner LEDs with glow animation
|
||
*/
|
||
void ledAnimationGlow(void)
|
||
{
|
||
static uint8_t brightness = 0;
|
||
static int8_t direction = 1;
|
||
|
||
// Update brightness level every call (10ms)
|
||
brightness += direction;
|
||
|
||
// Reverse direction at limits
|
||
if (brightness >= 100)
|
||
{
|
||
brightness = 100;
|
||
direction = -1;
|
||
}
|
||
else if (brightness == 0)
|
||
{
|
||
brightness = 0;
|
||
direction = 1;
|
||
}
|
||
|
||
// Apply PWM brightness to LED 3-6
|
||
setPWM_PA2(brightness * 255 / 100); // Scale 0-100 to 0-255
|
||
} |