Zachciało mi się zrobić mrugające oczka do halloweenowego nietoperza. Jako że miałem pod ręką chińską podróbkę Arduino Mini Pro (taką bez USB), parę diód, fotoopornik i pudełko na baterię, to szybko skleciłem takie coś jak powyżej, napisałem kilka linii prostego kodu, polutowałem i gotowe. Kod sprawdzał co 10 sekund czy jest jasno czy ciemno i jak było ciemno to mrugał LED’ami. Niestety…. Tak, pewnie eksperci od Arduino śmieją się w głos patrząc na to co tu zrobiłem. Dla niezorientowanych wyjaśnienie – baterie wytrzymały 48 godzin… Zacząłem przyglądać się mojemu projektowi i okazało się że trzeba dokonać kilku drobnych zmian. Przede wszystkim nie ma potrzeby zasilać dzielnika napięcia sprawdzającego jasność jak się jej nie mierzy. Nie jest to jakaś wielka strata energii, ale jak już robimy coś porządnie, to róbmy porządnie.
Jednak większość zmian jakie zrobiłem były w kodzie:
#include <avr/interrupt.h>
#include <avr/power.h>
#include <avr/sleep.h>
#include <avr/io.h>
#include <avr/wdt.h>
volatile int f_wdt = 1;
// startujemy
void setup() {
// porty 2-7 jako input, nie dotykamy 0 i 1 jako że one są do serial. Hmm, może je też wyłączyć?
DDRD &= B00000011;
// porty 8 i 9 jako input, 10-13 output (jak się zrobi 13 jako input to się LED żarzy)
DDRB = B00111100;
// włączamy pull-up na portach 2-7
PORTD |= B11111100;
// i na 8-9. Podobno dyndający input w Arduino zużywa sporo prądu
PORTB |= B11000011;
// magiczne zaklęcia z http://donalmorrissey.blogspot.com/2010/04/sleeping-arduino-part-5-wake-up-via.html
// po to żeby budzić Arduino za pomocą watchdog timer – normalnie by nastąpił reset więc trzeba to wyłączyć
// i trzeba przestawić go żeby kopał co 8 sekund
MCUSR &= ~(1 << WDRF);
WDTCSR |= (1 << WDCE) | (1 << WDE);
WDTCSR = 1 << WDP0 | 1 << WDP3;
WDTCSR |= _BV(WDIE);
delay(100);
}
// obsługa przerwania jakie daje watchdog timer
ISR(WDT_vect)
{
if (f_wdt == 0)
{
f_wdt = 1;
}
}
// funkcja usypiania Arduino
void enterSleep(void)
{
// włączamy maksymalną oszczędność prądu
set_sleep_mode(SLEEP_MODE_PWR_DOWN);
// resetujemy wszelkie czekające przerwania
cli();
// włączamy tryb spania
sleep_enable();
// wyłączamy wszystko co się da – http://www.nongnu.org/avr-libc/user-manual/group__avr__power.html
power_all_disable();
// może to etż da się wyłączyć? http://www.nongnu.org/avr-libc/user-manual/group__avr__sleep.html
sleep_bod_disable();
// włączamy przerwania żeby dalo się nam wybudzić Arduino
sei();
// usypiamy
sleep_cpu();
// ta linia wykona się jak się Arduino obudzi, trzeba szybko wyłączyć mod spania
sleep_disable();
// włączamy wszystko spowrotem
power_all_enable();
}
// płynne rozświetlanie LED’ów
void lightUp( int pin1, int pin2, int tm)
{
for ( int i = 0; i < 255; i++ )
{
analogWrite( pin1, i );
analogWrite( pin2, i );
delay( tm );
}
}
// płynne gaszenie LED’ów
void lightDown( int pin1, int pin2, int tm)
{
for ( int i = 255; i >= 0; i– )
{
analogWrite( pin1, i );
analogWrite( pin2, i );
delay( tm );
}
}
// sprawdzanie czy jest jasno – uznałem że wartość 250 (mniej-więcej 1/4 ~= 1.25V to wystarczająco jasno żeby nie mrugać)
bool isBright()
{
digitalWrite( 12, true);
lpDelay(1);
bool res = analogRead(0) > 250;
digitalWrite( 12, false);
return res;
}
// to fajna funkcja jaką znalazłem na internecie – oszczędza prąd gdy Arduino czeka przez zmniejszenie częstotliwości zegara 250 razy. Oznacza to że 1 ms trwa 250 ms.
int lpDelay(int quarterSeconds) {
int oldClkPr = CLKPR;
CLKPR = 0x80;
CLKPR = 0x08;
delay(quarterSeconds);
CLKPR = 0x80;
CLKPR = oldClkPr;
}
// no i sama pętla
void loop()
{
if (f_wdt == 1)
{
if ( !isBright() ) // mrugamy tylko jak jest ciemno
{
int mode = random(1, 10);
if ( mode < 8 )
{
lightUp( 10, 11, 20 );
int repeat = random(1, 5);
for ( int i = 0; i < repeat; i++ )
{
lightDown( 10, 11, 1 );
lpDelay(2);
lightUp( 10, 11, 1 );
lpDelay(2);
lightDown( 10, 11, 1 );
lpDelay(2);
lightUp( 10, 11, 1 );
lpDelay(2);
}
lightDown( 10, 11, 5 );
}
else if ( mode == 8 )
{
digitalWrite( 10, true);
lpDelay(3);
digitalWrite( 10, false);
}
else if ( mode == 9 )
{
digitalWrite( 11, true);
lpDelay(2);
digitalWrite( 11, false);
lpDelay(2);
digitalWrite( 11, true);
lpDelay(2);
digitalWrite( 11, false);
}
}
wdt_reset(); // resetujemy licznik watchdoga
f_wdt = 0; // zaznaczamy że obsłużyliśmy ostatnią pobudkę
enterSleep(); // idziemy spać
}
}
Jak widać powyżej z 10 linii kodu mrugających lampkami, zrobił się całkiem poważny projekt. Mam nadzieję że te wszelkie magiczne zaklęcia (zmniejszanie częstotliwości zegara w czasie czekania, głęboki sen w ciągu dnia) spowodują że baterie wytrzymają z tydzień. Wystawiłem nietoperza z nową wersją na zewnątrz wczoraj, zobaczymy jak długo wytrzyma 🙂 Jak to wszystko nie pomoże, to mam kilka dodatkowych pomysłów – po pierwsze wydłubię mu LED’a który pokazuje że jest zasilanie – świeci się non-stop bez sensu i marnuje prąd. Inną opcją jest zastosowanie bardziej oszczędnego regulatora napięcia – ten z Pro Mini jest bardzo rozrzutny. W sumie można też zaryzykować i podpiąć baterie bezpośrednio pod baterie bez regulatora napięcia – ten scalak podobno dobrze znosi takie eksperymenty. Jeszcze inną opcją jest zamówienie energooszczędnej wersji Pro Mini z Chin – zamiast 5V 16 MHz, można kupić 3.3V 8 MHz. Podobno ciągnie ona mniej-więcej sześć razy mniej prądu od wersji którą używam.