ATtiny402-FireFly-(remake of ATtiny85 fireflys)

I completely re-designed and all new code for realism, my ATtiny85 Fire fly's. I did this with the new ATtiny402. This is a major upgrade. The ATtiny85 Fire fly did win an award. The 2022 #3 best ATtiny85 project.

This new design and code implement the LTC4054 for charging the battery from the solar panel, which only needs a little bit of charge (in my old version I used a TP4056 module). This LTC4054 module is one I designed and the schematics are below. This version doesn't care if its night or not. If it detects motion, it lights. Due to the PIR, it consumes about 14uA. I have other projects that use the AM312 PIR and they will last and run for 6 months without a recharge.. But since this does have a solar panel (only charging at 20-40ma) should run indefinitely . I made the fire fly's the same as before by 3d printing. Then I attached the LED's to the fire flies by using UV glue and also mixed in glow in the dark powder. It just make for a nice effect. I used Yellow LED's in this new version (mainly because the lower voltage of the LED compared to the green LED's from my ATtiny85 version).

Update 7/7/2023: Changed Lipo Battery Charger to 1.6v

Update 9/1/2023: Need to change for dark detection, if battery goes dead then gets charged, for some reason it would not restart.


Used PCB from my Meteor light project. Works Great, just can't use Pin 6 (#1 output) due to Timer B being used for fire fly flashing.

Battery management (also have DW01 set up on Battery for protection as well).

PCB available at

/* * ATtiny402-analogWrite-firefly-motion.c * * Compiled in Microchip Studio 7 * * Works fantastic, looks real * Change pins to work with ATtiny402-Meteor-lights-PCB * Pins Changed.. Works with PCB now. *  * To do: * Still cannot get it working with the PIR pin2 interrupt. Even tried pin6 no luck * try changing resistor (even though it works with other software). * See if interrupt is working with test points, Maybe lights just not lighting after sleep * (although they wake up after WDT and Sleep) * * UPDATE 5/31/2023: * Fixed Motion issue.. WDT was starting over, resetting the sleepCount variable..  * The motion did not. So added one line in and now the motion works great!!!! * * Created: 5/29/2023 11:05:50 * Author : Sherman Stebbins */ 
//Pins used PA1, PA2, PA7 (PA6 is attached to timer b)#define F_CPU 10000000UL#include <util/delay.h>#include <avr/io.h>#include <avr/interrupt.h>#include <stdlib.h> //rand()#include "analogWrite.h"
#define TCB_TIMEOUT_VALUE 0x000f //change timer speed less is more (0x000f is fast as you can go)#define DOWN 0#define UP 1#define HOLD 2#define PAUSE 3#define MAXSLEEPCOUNT 4#define MAXLEDTHATLIGHT 12
void gotosleep(void);void ledOn(uint8_t led, uint8_t value);void tcbInit(void);uint8_t adcResult(void);
volatile uint8_t upDown = UP;volatile uint8_t count = 0;volatile uint8_t pause = 0;volatile uint8_t sleepCount = 0;volatile uint16_t loopCount = 0;volatile uint8_t rampDownSpeed = 1;volatile uint8_t ledLight = 0;volatile int randLoopCount = 0;volatile int randNum = 0;volatile int randCount = 0;volatile int randSleepCount = 5;volatile unsigned char seedCount __attribute__((section(".noinit"))); // Not cleared on RESET
ISR(TCB0_INT_vect){ TCB0.INTFLAGS = TCB_CAPT_bm; /* Clear the interrupt flag */ //PORTA.OUTTGL = PIN6_bm; /* Toggle PB6 GPIO */ //for Scope (does slow it down a tad)
ISR(PORTA_PORT_vect){ // PORTA.INTFLAGS = 0x04; //clear int flag, from Ben Heck.. PORTA.INTFLAGS = PORT_INT2_bm; // got from iotn402.h}
int main(void){ randSleepCount++; CCP=0xD8; //unlock cpu clock CLKCTRL.MCLKCTRLB = 0; // go full speed seedCount++; uint8_t newSeedcount = adcResult(); srand(newSeedcount); if(seedCount>=200){ seedCount=0; } PORTA.DIR = 0b00000000; tcbInit(); //pull up pin 2 and set it to trigger int on falling edge: PORTA.PIN2CTRL = PORT_ISC_RISING_gc; //this was my fix for changing current draw sei(); TCA0_init(); //if you want motion detection: //pull up pin 2 and set it to trigger int on falling edge:// PORTA.PIN3CTRL = PORT_ISC_RISING_gc; //this was my fix for changing current draw SLPCTRL.CTRLA = SLPCTRL_SMODE_PDOWN_gc | SLPCTRL_SEN_bm; //Microchip studio showed this //Set random stuff: randLoopCount = (rand() % 800) +200; randNum = (rand() % 20) +1; randCount = (rand() % 200)+50; randSleepCount = (rand() % MAXLEDTHATLIGHT)+5; ledLight = (rand() % 6); uint8_t randHold =0; while (1) { if(loopCount>randLoopCount){ randLoopCount = (rand() % 800) +200; loopCount=0; switch(upDown){ case UP : // analogWrite(1,count); randNum = (rand() % 20) +1; randCount = (rand() % 200)+50; if(count % randNum && count<randCount){ pause=1; } count++; if(count>254){ upDown=HOLD; count--; } break; case HOLD : randHold = (rand() % 85)+1; for(uint8_t i=0;i<randHold;i++){ _delay_ms(1); } upDown=DOWN; case DOWN : count=count - rampDownSpeed; if(count<=0){ rampDownSpeed = (rand() % 3)+1; count = 0; upDown=PAUSE; sleepCount++; } break; case PAUSE : PORTA.DIR = 0b00000000; uint8_t randPause = (rand() % 150)+10; for(uint8_t i=0;i<randPause;i++){ _delay_ms(1); } ledLight = (rand() % 6); upDown=UP; break; } if(pause){ ledOn(ledLight,1); pause=0; }else{ ledOn(ledLight,count); } } if(sleepCount>randSleepCount){ sleepCount=0;  //this sovlved motion sensor... PORTA.DIR = 0b00000000; gotosleep(); } }}
uint8_t adcResult(void){ PORTA.DIR &= ~(PIN6_bm); uint8_t adcVal = 0; ADC0.CTRLA = 0b00000111; //0b00000100 is resolution at 8bit, 0b00000010 freerun, 0b00000001 enable ADC0.CTRLB = 0x0; //sample at 2 accumulation ADC0.CTRLC = 0b00010000; ADC0.MUXPOS = 0x6; //set to open pin six ADC0.COMMAND = 0x1; //start conversion while(!(ADC0.INTFLAGS & ADC_RESRDY_bm)){;} // _delay_us(10); adcVal = ADC0.RES; ADC0.CTRLA = 0; return adcVal;}void tcbInit(void){ /* Load the Compare or Capture register with the timeout value*/ TCB0.CCMP = TCB_TIMEOUT_VALUE; /* Enable TCB and set CLK_PER divider to 1 (No Prescaling) */ TCB0.CTRLA = TCB_CLKSEL_CLKDIV1_gc | TCB_ENABLE_bm; /* Enable Capture or Timeout interrupt */ TCB0.INTCTRL = TCB_CAPT_bm;
void ledOn(uint8_t led, uint8_t value){ PORTA.OUT = 0b00000000; PORTA.DIR = 0b00000000; switch (led){ case 0: PORTA.DIR |= (PIN1_bm|PIN3_bm); PORTA.OUTCLR = PIN3_bm; analogWrite(1,value); break; case 1: PORTA.DIR |= (PIN3_bm|PIN1_bm); PORTA.OUTCLR = PIN1_bm; analogWrite(3,value); break; case 2: PORTA.DIR |= (PIN1_bm|PIN7_bm); PORTA.OUTCLR =PIN7_bm; analogWrite(1,value); break; case 3: PORTA.DIR |= (PIN7_bm|PIN1_bm); PORTA.OUTCLR = PIN1_bm; analogWrite(7,value); break; case 4: PORTA.DIR |= (PIN3_bm|PIN7_bm); PORTA.OUTCLR = PIN7_bm; analogWrite(3,value); break; case 5: PORTA.DIR |= (PIN7_bm|PIN3_bm); PORTA.OUTCLR = PIN3_bm; analogWrite(7,value); break; default: break; }}

void gotosleep(void){ PORTA.DIR = 0;
// CCP = 0xd8;// WDT.CTRLA = 0xb; PORTA.PIN2CTRL = PORT_ISC_RISING_gc; //this was my fix for changing current draw sei(); SLPCTRL.CTRLA = 0b00000101; //POWERDOWN, SEN sleep enable
asm("sleep"); //sleep //turn off pull ups after sleep PORTA.PIN3CTRL &= ~(PORT_PULLUPEN_bm); PORTA.PIN1CTRL &= ~(PORT_PULLUPEN_bm); PORTA.PIN7CTRL &= ~(PORT_PULLUPEN_bm); PORTA.PIN6CTRL &= ~(PORT_PULLUPEN_bm);// uint8_t newSeed = adcResult(); // srand(newSeed);
//Put flashing light here test: Not tested yet// PORTA.DIR |= PIN6_bm;// PORTA.OUT |= PIN6_bm;// _delay_ms(1000);// PORTA.OUTCLR = PIN6_bm;// PORTA.DIR = 0b00000000; // CCP = 0xd8;// WDT.CTRLA = 0; TCA0_init(); tcbInit(); randSleepCount = (rand() % 6);}