@@ -8,8 +8,10 @@
# 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 TODO: using RESET/UPDI pin
# 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
@@ -18,21 +20,40 @@
# 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 100 0U // Time button ignored after long press
# define BUTTON_SHORT_PRESS _DURATION_MS 5 0U // 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
void blinkLed ( bool resetCounters ) ;
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 void handleSwitch ( void ) ;
static bool handleSwitch ( void ) ;
static inline void switchMode ( void ) ;
void ledAnimationBlink ( bool resetCounters ) ;
void ledAnimationGlow ( void ) ;
void ledStaticFull ( void ) ;
/**
* @brief Main entry point
@@ -41,20 +62,21 @@ 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
// Ensure all LEDs off at startup
leds_off ( ) ;
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 )
{
handleSwitch ( ) ; // Check switch state
bBtnPressed = handleSwitch ( ) ; // Check switch state
// Light LEDs while button is pressed
if ( bBtnPressed )
@@ -66,25 +88,51 @@ int main(void)
leds_off ( ) ;
}
// Long press detected → show confirmation blink
// 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_IGNORE_DURATION_MS / 10 ) ;
_delay_ms ( BUTTON_CONFIRMATION_BLINK_DURATION_MS ) ;
leds_on ( ) ;
_delay_ms ( BUTTON_IGNORE_DURATION_MS / 10 ) ;
_delay_ms ( BUTTON_CONFIRMATION_BLINK_DURATION_MS ) ;
leds_off ( ) ;
}
// Give time until button is released
_delay_ms ( BUTTON_IGNORE_DURATION_MS ) ;
blinkLed ( true ) ; // reset blink state machine
// 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 )
{
blinkLed ( false ) ; // run normal blink
switch ( eModeCurrent )
{
case ANIMATION_BLINK :
ledAnimationBlink ( false ) ; // run normal blink
break ;
case ANIMATION_GLOW :
ledAnimationGlow ( ) ;
break ;
case STATIC_FULL :
ledStaticFull ( ) ;
break ;
default :
break ;
}
}
}
@@ -92,12 +140,46 @@ int main(void)
}
}
/**
* @brief Move to next mode
*/
static inline void switchMode ( void )
{
eModeCurrent = ( eModeCurrent + 1 ) % MAX_COUNT ;
}
/**
* @brief Init PWM for PA2
*/
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 ;
}
/**
* @brief Set PWM duty cycle for PA2
*/
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 | PA2_SET_MASK | PA3_SET_MASK ) ;
VPORTA . OUT & = ( uint8_t ) ~ ( PA1_SET_MASK | PA3_SET_MASK ) ;
setPWM_PA2 ( 0U ) ;
}
/**
@@ -105,7 +187,8 @@ static inline void leds_off(void)
*/
static inline void leds_on ( void )
{
VPORTA . OUT | = ( PA1_SET_MASK | PA2_SET_MASK | PA3_SET_MASK ) ;
VPORTA . OUT | = ( PA1_SET_MASK | PA3_SET_MASK ) ;
setPWM_PA2 ( 255U ) ;
}
/**
@@ -120,9 +203,10 @@ static void battery_level_indicator(void)
/**
* @brief Handle momentary switch input on PA0
*
* A long press toggles :: bLedEnabled.
* A long press toggles bLedEnabled.
* A short press swiches the mode.
*/
static void handleSwitch ( void )
static bool handleSwitch ( void )
{
static uint16_t pressTicks = 0 ; ///< Press duration counter
static bool prevPressed = false ; ///< Previous button state
@@ -131,22 +215,58 @@ static void handleSwitch(void)
if ( pressed )
{
bBtnPressed = true ;
if ( pressTicks < 0xFFFF )
pressTicks + + ; // Prevent overflow
}
else
{
bBtnPressed = false ;
// 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 ( prevPressed & & pressTicks > = MS_TO_TICKS ( BUTTON_LONG_PRESS_DURATION_MS ) )
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 ( ) ;
}
}
/**
@@ -164,7 +284,7 @@ static void handleSwitch(void)
* 18: all off, wait 250 ms → restart
* Special: every 3rd cycle, all LEDs ON for 250 ms
*/
void blinkLed ( bool resetCounters )
void ledAnimationBlink ( bool resetCounters )
{
const uint8_t T50 = MS_TO_TICKS ( 50 ) ;
const uint8_t T100 = MS_TO_TICKS ( 100 ) ;
@@ -231,7 +351,7 @@ void blinkLed(bool resetCounters)
break ;
case 12 : // LED 3– 6 on, wait 50 ms
VPORTA . OUT | = PA2_SET_MASK ;
setPWM_PA2 ( 255U ) ;
if ( counter > = T50 )
{
counter = 0 ;
@@ -249,7 +369,7 @@ void blinkLed(bool resetCounters)
break ;
case 16 : // LED 3– 6 on, wait 50 ms
VPORTA . OUT | = PA2_SET_MASK ;
setPWM_PA2 ( 255U ) ;
if ( counter > = T50 )
{
counter = 0 ;
@@ -275,7 +395,7 @@ void blinkLed(bool resetCounters)
break ;
case 20 : // special: all LEDs on for 250 ms
VPORTA . OUT | = ( PA1_SET_MASK | PA2_SET_MASK | PA3_SET_MASK ) ;
leds_on ( ) ;
if ( counter > = T250 )
{
counter = 0 ;
@@ -284,3 +404,38 @@ void blinkLed(bool resetCounters)
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 > = 100U )
{
brightness = 100U ;
direction = - 1 ;
}
else if ( brightness = = 10U )
{
brightness = 10U ;
direction = 1 ;
}
// Apply PWM brightness to LED 3-6
setPWM_PA2 ( brightness * 255 / 100 ) ; // Scale 0-100 to 0-255
}