Vuurvliegjes
Een simpel project met de AVR ATtiny2313
Ik had nog een aantal oude tuinlampen met zonne-cellen
liggen; van die dingen met bovenin zonnecellen en een lichtdetector, onderin
een LED en van binnen twee oplaadbare NiCd cellen. Was ik op uitgekeken, kan ik
daar niet iets anders mee? Wat me leuk leek in de tuin waren 'vuurvliegjes',
subtiele oplichtende LEDjes die in een willekeurig patroon aan en uit gingen.
En dan niet hard knipperend, maar vloeiend aan en uit.
Gebaseerd op een van die tuinlampen is er toen dit mini-projectje ontstaan. Een
AVR ATtiny2313 microprocessor (zie mijn 'Starten
met de ATtiny2313 pagina) stuurt via de 4 PWM-uitgangen 4x 2 gele LEDs aan.
Het geheel (behalve de LEDs) is ingebouwd in de oude tuinlamp, wordt geladen
door de zonnecel, en wordt actief in het donker. De LEDs hangen in een
boom.
Hoe is dit geheel opgebouwd? Het bestaat uit een aantal delen:
- de zonnecellen uit de lamp laden de NiMH batterijen, die vervolgens 's
nachts de ATtiny voeden
- de ATtiny meet regelmatig of het al donker is, is het donker dan gaat het
LED programma draaien
- de PWM-uitgangen van de ATtiny regelen de helderheid van de LEDs
- en een voltage booster met een transistor en spoel
verhoogt de batterijspanning (2..3V) tot de waarde die voor de LEDs nodig is
(4V)
Zonne-energie
Hoeveel zonne-energie
krijgen we eigenlijk hier in Nederland? Ongeveer 1000 kWh/m² per jaar, in de
zomer ongeveer 10x zo veel als in de winter. Gemiddeld dus zo'n 2.7 kWh per
dag, bij 10 uur/dag dus 270 W/m². Voor een cel van 10 cm², een rendement
van 10% en een spanning van 3V is dat zo'n 100 mAh in de zomer, en maar 10
mAh in de winter...
Van belang is natuurlijk het energiegebruik. Zon is in Nederland schaars
(zie kader Zonne-energie), dus zeker in ruststand moet het ultra low power
zijn... In de hardware is er voor gezorgd dat in stand-by mode de onderdelen
buiten de ATtiny uitgeschakeld zijn, alleen incidenteel wordt het lichtmeetcircuit een keer per seconde zo'n 20 microseconden
van energie voorzien om te kijken of het al donker is. Totale energiegebruik is
in rust daardoor slechts 50 µA (of slechts 25 µA als de batterijspanning te ver
daalt, omdat de processor dan helemaal plat gaat via de 'brown-out detection op
1.8V).
Het
circuit
Demo-opstelling op
ElektorLive-dag 2010
Hieronder de beschrijving van de delen van het schema (download het hele
schema: vuurvlieg_schema.pdf):
processor, laadcircuit, voltage booster, LED control, lichtmeting.
De
processor
Schematic
entry en PCB
Een plezierig en gratis pakket om schema's en de bijhorende print-layouts te
maken is Eagle Light
(Easily Applicable Graphical Layout
Editor)
De processor is op de 'standaard' manier aangesloten. Het draait op de
low-power interne 128 kHz RC oscillator, niet erg nauwkeurig en snel maar dat
is voor deze toepassing ook niet nodig. De processor probeert zo veel mogelijk
in sleep/idle mode te zijn, om energie te besparen. Na iedere actie schakelt
hij naar idle-mode, om vervolgens bij een timer-interrupt weer wakker te
worden, bijvoorbeeld een keer per seconde.
De bron voor de klok en dergelijken worden op de ATtiny ingesteld door
'fuses', een soort
elektrisch programmeerbare zekeringen. Deze fuses zijn geprogrammeerd op 128
kHz interne low-power RC oscillator, en BOD aan (brown-out detection; het
resetten van de processor als de spanning te ver daalt) op 1.8 Volt. Dit
laatste heeft tot gevolg dat de processor in ultra low power mode gaat als de
spanning daalt (25 µA voor het hele circuit) en pas weer wakker wordt als de
spanning weer boven de 1.8 Volt komt; extra batterij-bescherming.
Het
laadcircuit
Het laadcircuit stuurt de stroom van de zonnecellen direct naar de NiMH
batterijen. Wel met een diode ertussen; in het donker zou anders de batterij
via een lekstroom door de zonnecellen weer ontladen. Door de beperkte
capaciteit van de zonnecel ben ik in Nederland niet bang voor overladen (zie
kader 'zonne-energie'), maar eventueel kan je in de zomer (tip: meet de lengte
van de dag) de LEDs overdag inschakelen om het laden te beperken...
De voltage booster
De voltage booster is een variant op een geschakelde voeding, waarbij de
schakelfrequentie door de ATtiny wordt verzorgt. Omdat de ATtiny op een lage
klok loopt, is de schakelfrequentie voor de voeding ook relatief laag (64 kHz,
hoogste waarde die ik uit de baudrate-generator kan krijgen met de CPU op 128
kHz). Als gevolg hiervan is er een relatief grote spoel nodig, die ik nog in
een rommelbakje had liggen (type-nummer in het schema slaat nergens op, maar
had de goede afmetingen voor de PCB, waarde was ongeveer 1 milli-Henry). Als de
transistor ingeschakeld wordt gaat er een stroom door de spoel lopen. Wordt nu
deze stroom onderbroken (transistor schakelt uit), dan ontstaat er een
spanningspuls, die door de diode wordt gelijkgericht.
En uiteraard: als er geen enkele LED voor langere periode aan is, dan
schakelt de ATtiny ook de oscillator van de geschakelde voeding af om energie
te besparen.
Pas op: onbelast kan deze spanning behoorlijk oplopen (tientallen
volts), en dus je circuit beschadigen! Daarom zit er een zenerdiode van 5.1
Volt op de uitgang. Is er geen enkele LED aan, dan beperkt deze de spanning.
Zodra een LED aangeschakeld is zakt deze spanning tot de spanningsval over de
LEDs (twee gele LEDs in serie geeft 2x2 = 4 Volt). Je moet de waarde van de
zener aanpassen aan het type (kleur) en aantal LEDs.
Pas op: als er geen LEDs aan zijn is de voeding onbelast, en laden de
condensatoren tot de maximum spanning. Als je te grote condensatoren plaatst
laden deze op tot 5.1 Volt, en bij het inschakelen van een LED ontladen ze zich
in een keer tot 4 Volt, dit kan grote stromen geven... Daarom beperkt tot 2x
0.1 µF.
Ik had oorspronkelijk geen 5.1V zener als bescherming, en een 33µF elco
--> spanning liep op tot >15 Volt; zodra je dan een LED activeert
brand'ie nog maar een keer... De elco op 15 Volt bevat een behoorlijke
hoeveelheid energie! Oeps... had ik kunnen bedenken. Onbelast kan de
piekspanning trouwens wel tot >60V oplopen!
LED-besturing
De LEDs worden via een transistor aangesloten op de PWM-uitgang van de
ATtiny geschakeld. De stroom door de LEDs is beperkt, en zou direct door de
ATtiny geschakeld kunnen worden. Maar... de spanning (max 5.1 V) is hoger dan
de voedingsspanning van de ATtiny (2..3V), en dat gaat niet samen (pinnen mogen
max Vcc+0.5V hebben). Vandaar deze extra transistoren!
De helderheid van de LEDs wordt door pulsbreedte modulatie aangestuurd (in
phase correct mode), de frequentie hiervan is 128 kHz/512 = 250 Hz. Deze
frequentie kan het menselijk oog niet meer als knipperen zien, we zien een mooi
gelijkmatig gloeiende LED. De ATtiny heeft 4 PWM uitgangen, op iedere uitgang
heb ik op het moment 2 LEDs in serie zitten.
Lichtmeting
De vuurvliegjes in de tuin
in het seringenboompje
De lichtmeting wordt gedaan met behulp van de analoge comparator in de
ATtiny. Ik wil alleen maar weten of het donker danwel licht is, dus ik bepaal
niet de waarde maar kijk alleen of het meer of minder is dan een ingestelde
waarde. De LDR (lichtgevoelige weerstand) vormt samen met een weerstand R5 een
spanningsdeler, de spanning hier wordt vergeleken met een vaste spanning uit de
deler R6 en R7. Door deze delers loopt normaal stroom, wat weer jammer is voor
het energiegebruik; daarom schakel ik dit circuit uit met Q6 (aangesloten op
uitgang PD6) als er niet gemeten wordt. Alleen tijdens een meting staat het
circuit enkele tientallen microseconden aan.
Ik maak geen gebruik van de ingebouwde spanningsreferentie; omdat de
batterijspanning varieert ook de spanning in de deler verandert; door
twee delers te gebruiken veranderen deze op dezelfde manier en wordt de invloed
van de batterijspanning weggewerkt.
Achteraf gezien had ik die transistor Q6 ook wel kunnen weglaten, en het
circuit direct aan de output-pin van de tiny kunnen hangen, om op die manier de
slaapstand te implementeren. Ach.
De bijhorende code is vrij simpel, maar leuk is hier het software filter: je
wilt niet de zaak verstoren omdat er een vogel overvliegt die de sensor
triggert. Daarom wordt er steeds een groepje van 8 metingen bekeken (bij een
meting per seconde) en pas als deze 8 keer hetzelfde is vind er een
statuswijziging plaats.
uint8_t light_samples = 1; // 8 latest light samples; all equal?
uint8_t light_on = 0; // assume dark to start with
uint8_t measure_light(void) // what is
current light level?
{
// 'filter' over 8 samples
ACSR = (0 << ACD);
// enable comp using external
inputs
SETBIT(PORTD, 1<<PD6); // enable power on LDR
_delay_us(10);
// 10 us
stabilise (<util/delay.h>)
light_samples <<= 1;
// shift one position, fill zero
if(!(ACSR & (1 << ACO))) light_samples |= 1;
// comparator result
ACSR = (1 << ACD) | (0 << ACBG); // switch off comparator etc
CLRBIT(PORTD, 1<<PD6); // disable power on LDR
// only change if 8 identical samples:
if(light_samples == 0xFF) { light_on =
1; }
else if(light_samples == 0) { light_on =
0; }
return light_on;
// 1=light,
0=dark
}
De montage
Het geheel is op een stukje ouderwets gaatjesprint (een gat per eiland)
gemonteerd. Het voordeel hiervan is dat je SMD onderdelen (met name de
weerstanden) direct op de onderkant kunt solderen, een '0805' smd component
(0.8 x 0.5 tiende inch, dus ongeveer 2 bij 1 mm) past netjes tussen twee
eilandjes in, en is nog goed hanteerbaar. Het geheel is met zelfstrippend draad
gesoldeerd.
Op de foto bovenaan deze pagina zit de print nog 'ondersteboven', normaal is
deze omgedraaid gemonteerd zodat de componenten in de ruimte tussen de
batterijen vallen. Ziet er mooi uit (en past dan in de gesloten behuizing),
alleen zit de programmeer-connector daarmee op een onhandige plaats... Had
beter een haaks type kunnen nemen.
Omdat het geheel buiten in een boom zit, moet de zaak tegen
weersomstandigheden kunnen: ik heb de print met een heldere plastic-lak
bespoten, om een beschermlaag aan te brengen. De eerste winter heeft het al
buiten overleeft.
De
software
Op hoog niveau bestaat de software uit een state machine met drie
toestanden: slapen, spelen en laden. In de slaap- en laad-stand is de processor
meestal in idle mode; slechts een keer per seconde wordt er gekeken of
het licht veranderd is. Hiermee wordt het stroomgebruik geminimaliseerd.
Tijdens het spelen is de processor vaker actief; maar wordt nog het meest
gedaan door de peripherals als de hardware pulsbreedte-modulatoren. Deze
draaien op 250 Hz; de frequentie waarmee ook de processor uit idle wordt
gehaald (timer overflow interrupt). Meestal valt deze vervolgens weer snel in
slaap, maar twee keer per seconde wordt er per LED gecheckt of de LED state
machine moet worden bijgewerkt. Op dit moment gaat'ie na drie uur spelen via
een time-out in nachtmode; dit zou in de zomer wat langer kunnen (meer zon, dus
meer lading). Iets voor de volgende software revisie...
Dit wordt misschien nog eens uitgewerkt: details LED state machine,
lichtmeting, PWM-gebruik, etc; maar bekijk anders maar de source file.
Alvast enkele fragmenten code: om de processor in idle mode te
zetten gebruik ik gedeeltelijk de avr_lib functies (met name
<util/atomic.h> ), en zet een timer na x seconde om
weer wakker te worden. Ook zorg ik dat de PWM outputs 0 zijn (power
saving):
void timer_xxx_sec(int secs) //
program timer to wake up from idle
{
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) // no spurious
timer IRQ's while programming
{
TIMSK = 0;
// disable all
timer interrupts
GTCCR |= 1;
// timer1: reset
prescaler
TCCR1A = 0;
// outputs normal digital
(no PWM), mode 0
TCCR1B = (SLEEP_PRESCALE << CS10); // prescaler: 0x5: /1024; mode: 0 (counting)
TCNT1 = 0xFFFF-secs*TICK2SEC;
// set up-counter @ x secs (16-bit write!!)
TCCR0A = 0; TCCR0B = 0; //
timer0: no PWM operation, no clock
TIMSK = (1 << TOIE1);
// IRQ @ timer 1 overflow (0xFFFF->0)
enable
}
}
void go_sleep()
// see
doc on <avr/sleep.h>
{
timer_xxx_sec(1); // program timer for wake-up after 1 second
sei();
// make sure interrupts enabled
set_sleep_mode(0);
// 0 == IDLE mode
sleep_mode();
// and return here on interrupt
}
En het fragment dat de helderheid van de LEDs regelt. Het 'echte werk'
gebeurt in de TIMER1 interrupt routine, die 250 keer per seconde wordt
aangeroepen. Omdat de processor slechts op 128 kHz loopt zijn er in totaal dus
maar 512 klokcycles per periode, waarin je 4 LEDs moet bijwerken: de routines
moeten dus wel efficiënt zijn! Een aantal trucks van mijn software pagina zijn dan ook toegepast; waarmee het aantal
cycli in de interrupt routine naar ongeveer 90 is teruggebracht, ongeveer 40%
van de beschikbare hoeveelheid. De rest is voor het hoofdprogramma.
Ik heb per LED twee vlaggen: up en down, die aangeven of de helderheid
omhoog gaat danwel naar beneden. Voor de 4 LEDS heb ik deze in een byte
gepackt, zie de definitie van IsrStruct . De ATtiny kan heel
efficiënt met dit soort gepackte bits omgaan, mits ze in een bit-addresseerbaar
register als GPIOR0 staan. Dit wordt gebruikt in de interrupt
service routine TIMER1_OVF_vect , waar in de macro's
COND_INC en COND_DEC getest wordt of de LED omhoog of
omlaag moet. Als dat het geval is wordt met een volgende truck met een
temp -register ook de code voor het wijzigen van de PWM met een
minimum aan instructies gedaan. Daarnaast wordt in de ISR ook de halve-seconde
teller bijgewerkt (deze wordt in het hoofdprogramma gebruikt voor het
tijdsgevoel).
// ######################### Interrupt
support #############################
typedef union isrstruct //
pack some bits indicating the running state
{
unsigned char state; // for ease of initialisation/erasure
struct
{
// bitfields start at LSB
unsigned char up_1: 1; // LSB: pwm_1 up?
unsigned char up_2: 1; // pwm_2 up?
unsigned char up_3: 1; // pwm_3 up?
unsigned char up_4: 1; // pwm_4 up?
unsigned char dn_1: 1; // pwm_1 down?
unsigned char dn_2: 1; // pwm_2 down?
unsigned char dn_3: 1; // pwm_3 down?
unsigned char dn_4: 1; // MSB: pwm_4 down?
};
} IsrStruct;
// IsrStruct isr_ctrl;
// formal but non-optimised declaration
#define isr_ctrl (*((IsrStruct *)&GPIOR0)) //
map on scratch register
// Interrupt service routine for 500 Hz PWM timer1
interrupt
// which at 128 kHz results in 256 cycles per period.
// ISR takes 8*7 + 30 = ~90 cycles = 40% (approx, worst case)
ISR(TIMER1_OVF_vect)
{
if(! --timer_tick_hs) // half second count down
{
timer_tick_hs = TICK2QSEC;
// restart next interval
missed_hsec++; // and indicate
one more passed
}
uint8_t temp;
// to create optimal code, some
tricks like temp
#define COND_INC(flag, pwm) if(isr_ctrl.flag) { temp=pwm; if(temp != 0xFF)
pwm=temp+1; }
#define COND_DEC(flag, pwm) if(isr_ctrl.flag) { temp=pwm;
if(temp) pwm=temp-1; }
COND_INC(up_1, PWM_1); // conditionally increment light level
COND_INC(up_2, PWM_2); // (using the correct polarity)
COND_INC(up_3, PWM_3); // don't increase above max level!
COND_INC(up_4, PWM_4);
COND_DEC(dn_1, PWM_1); // every macro expands into 7 instructions
COND_DEC(dn_2, PWM_2); // including the 2 if statements!
COND_DEC(dn_3, PWM_3);
COND_DEC(dn_4, PWM_4);
}
Stroomgebruik in de werkelijke schakeling
In mijn vuurvlieg-project, een batterij- en zonnecel-gevoed ontwerp, heb ik
de stroom gemeten. Wordt tenslotte gevoed door 2 NiMH batterijen en geladen
door een kleine zonnecel, en de datasheets zeggen ook niet altijd alles... Dat
viel dus reuze mee (stroomgebruik is van de hele schakeling):
Toestand
|
Stroom |
Beschrijving
|
BOD mode
|
25 µA @ 1.8V
|
Brown-Out Detected: spanning onder de 1.8 Volt gedaald en chip
in reset (om te voorkomen dat de batterijen helemaal leeggezogen worden). Start
automatisch weer op als de zon schijnt (en gaat dan naar idle mode)
|
Idle
|
50 µA @ 2.5V
|
Slaapmode, bijvoorbeeld overdag (cellen worden geladen, chip
in idle mode); wake-up elke seconde voor een lichtmeting (duurt enkele
10-tallen µS); draaiend op de 128 kHz interne RC oscillator
|
Active
|
400 µA @ 2.5V
|
Actief (maar zonder de LEDs aangesloten); wake-up op 250 Hz en
draaiend op de 128 kHz interne RC oscillator |
Active
|
20 mA @ 2.5V
30 mA @ 3.0V
|
Actief met brandende LEDs (100% aan). Dit is de voedingsstroom
uit de batterij, niet de stroom door de LEDs (nog eens meten; wordt met een
schakelend circuitje naar de juiste spanning gebracht).
|
Active
|
3 mA @ 2.5V
|
Actief gemiddeld (LEDs nu en dan aan)
|
Als de LEDs uit staan gaan de batterijen dus wel een tijdje mee (batterij
van 1000 mAh bij een stroom van 50 µA --> 20000 uur = 2¼ jaar; tegen die
tijd is de NiMH batterij al leeg door zelfontlading).
|