@@ -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,43 +20,71 @@
# 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
# define GLOW_BRIGHTNESS_MIN 10U
# define GLOW_BRIGHTNESS_MAX 100U
# define INTERNAL_VREF_MV 1024UL
/** 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 ) ;
static void configureLowPower ( 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
*/
int main ( void )
{
// Disable unused peripherals for power saving
configureLowPower ( ) ;
// 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 ( ) ;
battery_level_indicator ( ) ; // TODO: Implement
leds_off ( ) ; // Ensure all LEDs off at startup
bool bLedEnabledOld = bLedEnabled ;
eModeCurrent = ANIMATION_BLINK ; // Set the mode to start with
while ( true )
{
handleSwitch ( ) ; // Check switch state
battery_level_indicator ( ) ;
bBtnPressed = handleSwitch ( ) ; // Check switch state
// Light LEDs while button is pressed
if ( bBtnPressed )
@@ -66,38 +96,149 @@ 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
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 ;
}
}
}
// Sleep during delay instead of busy-wait
sleep_enable ( ) ;
_delay_ms ( MAIN_LOOP_SLEEP ) ;
sleep_disable ( ) ;
}
}
/**
* @brief Move to next mode
*/
static inline void switchMode ( void )
{
eModeCurrent = ( eModeCurrent + 1 ) % MAX_COUNT ;
}
/**
* @brief Configure for lowest power consumption
*/
static void configureLowPower ( void )
{
return ;
// Set unused pins as outputs LOW to prevent floating inputs
// Floating inputs can cause extra current consumption
PORTA . DIRSET = PIN4_bm | PIN5_bm ; // Set PA4, PA5 as outputs if unused
PORTA . OUTCLR = PIN4_bm | PIN5_bm ; // Drive them LOW
// Configure sleep mode
set_sleep_mode ( SLEEP_MODE_IDLE ) ; // Use IDLE when TCA0 PWM needs to run
// Use SLEEP_MODE_STANDBY for deep sleep when LEDs are off
// Disable unused timers
TCB0 . CTRLA = 0 ; // Disable TCB0 if not used
// TCA0 is used for PWM, keep it enabled
// Disable ADC (Analog-to-Digital Converter)
ADC0 . CTRLA & = ~ ADC_ENABLE_bm ;
// Disable AC (Analog Comparator)
AC0 . CTRLA & = ~ AC_ENABLE_bm ;
// Disable unused USART
USART0 . CTRLB = 0 ;
// Disable TWI (I2C) if not used
TWI0 . MCTRLA = 0 ;
TWI0 . SCTRLA = 0 ;
// Disable SPI
SPI0 . CTRLA = 0 ;
// Disable Watchdog Timer (if not needed)
// Note: WDT can only be disabled during first 4 clock cycles after reset
// CCP = CCP_IOREG_gc;
// WDT.CTRLA = 0;
// Disable BOD (Brown-Out Detection) in sleep modes for lower power
// This is done via fuses, not runtime configurable
// Disable digital input buffers on unused pins to save power
// Only needed if pins are truly unused (floating)
// PORTA.PIN4CTRL = PORT_ISC_INPUT_DISABLE_gc; // PA4 if unused
// PORTA.PIN5CTRL = PORT_ISC_INPUT_DISABLE_gc; // PA5 if unused
}
/**
* @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 +246,58 @@ 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 ) ;
}
/**
* @brief Read battery voltage using internal 1.1V reference
* @return Estimated battery voltage in millivolts
*/
uint16_t readBatteryVoltage ( void )
{
// Enable ADC with proper configuration
ADC0 . CTRLC = ADC_REFSEL_VDDREF_gc | // VCC as reference
ADC_PRESC_DIV4_gc ; // Prescaler DIV4
// 10-bit resolution (default)
ADC0 . CTRLA = ADC_RESSEL_10BIT_gc ;
// Select internal voltage reference as input to measure
ADC0 . MUXPOS = ADC_MUXPOS_INTREF_gc ;
// Enable ADC
ADC0 . CTRLA | = ADC_ENABLE_bm ;
// Wait for ADC to stabilize
_delay_us ( 100 ) ;
// Dummy conversion for stability
ADC0 . COMMAND = ADC_STCONV_bm ;
while ( ! ( ADC0 . INTFLAGS & ADC_RESRDY_bm ) )
;
ADC0 . INTFLAGS = ADC_RESRDY_bm ; // Clear flag
// Actual conversion
ADC0 . COMMAND = ADC_STCONV_bm ;
while ( ! ( ADC0 . INTFLAGS & ADC_RESRDY_bm ) )
;
uint16_t adcResult = ADC0 . RES ;
// Disable ADC to save power
ADC0 . CTRLA | = ! ADC_ENABLE_bm ;
// Check for valid reading
if ( adcResult = = 0 | | adcResult > = 1023 )
{
return 0 ; // Invalid reading
}
// Calculate VCC: V_VCC = VREF × 1023 / ADC_result
uint32_t voltage_mv = ( INTERNAL_VREF_MV * 1023 / adcResult ) ;
return ( uint16_t ) voltage_mv ;
}
/**
@@ -113,16 +305,47 @@ static inline void leds_on(void)
*/
static void battery_level_indicator ( void )
{
// TODO: Implement
VPORTA . OUT | = ( PA6_SET_MASK | PA7_SET_MASK ) ; // green + red OFF
uint16_t voltage = readBatteryVoltage ( ) ;
// 1S LiPo voltage ranges:
// Good: >=3700mV
// Low: >=3500mV -->
// VPORTA.OUT &= ~(PA6_SET_MASK | PA7_SET_MASK); // Turn off both LEDs first
VPORTA . OUT | = ( PA6_SET_MASK | PA7_SET_MASK ) ; // Red Debug ON
if ( voltage < = 65534 )
{
// Green OFF, Red ON - Low battery
//VPORTA.OUT &= ~PA7_SET_MASK; // Red Debug OFF
}
return ;
if ( voltage > = 3700 )
{
// Green ON, Red OFF - Good battery
VPORTA . OUT & = ~ PA6_SET_MASK ; // Green ON (active low)
}
else if ( voltage > = 3500 )
{
// Both ON (yellow/orange) - Medium battery
VPORTA . OUT & = ~ ( PA6_SET_MASK | PA7_SET_MASK ) ;
}
else
{
// Green OFF, Red ON - Low battery
VPORTA . OUT & = ~ PA7_SET_MASK ; // Red ON (active low)
}
}
/**
* @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 +354,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 +423,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 +490,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 +508,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 +534,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 +543,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 > = GLOW_BRIGHTNESS_MAX )
{
brightness = GLOW_BRIGHTNESS_MAX ;
direction = - 1 ;
}
else if ( brightness = = GLOW_BRIGHTNESS_MIN )
{
brightness = GLOW_BRIGHTNESS_MIN ;
direction = 1 ;
}
// Apply PWM brightness to LED 3-6
setPWM_PA2 ( brightness * 255 / 100 ) ; // Scale 0-100 to 0-255
}