/** * @file main.c * @brief Bike rear light implementation for ATTINY202. * */ #include #include #include #include #include #include #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 #define GLOW_BRIGHTNESS_MIN 10U #define GLOW_BRIGHTNESS_MAX 100U /** 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); 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 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 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 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; } } } // 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) { // 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 | 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 >= 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 }