piątek, 1 kwietnia 2011

Pułapki AVR: Zapis do EEPROM vs usypianie vs prąd


Autor: Dondu

Artykuł jest fragmentem cyklu: Pułapki mikrokontrolerów AVR
To jedna z pułapek, która może sprawić sporo problemów w projektach używających EEPROM i korzystających z usypiania mikrokonrolera do trybów:
  • Power Down
  • Power Save
Można się niemile rozczarować, że mikrokontroler wchodzi w tryb uśpienia, lecz pobiera znacznie więcej prądu, niż wynika to z datasheet.

Na początku zobaczmy jaki powinien być pobór prądu przez mikrokontroler w trybie uśpienia Power Down, na przykładzie ATmega8A:




Zaznaczyłem napięcie zasilania około 4,85V ponieważ w poniższych testach (pomiarach) takim właśnie zasilałem mikrokontroler. Pomiary przeprowadzałem w temperaturze pokojowej około 21°C stąd biorę pod uwagę wykresy dla temperatury 25°C. Z wykresu wynika więc, że powinniśmy się spodziewać prądu około 1,1µA.


Układ pomiarowy

Zastosujemy możliwie najprostszy układ pomiarowy z niezbędnymi kondensatorami filtrującymi oraz rezystorem wymuszającym wysoki stan pinu RESET:

Rys. Schemat układu pomiarowego.

Warunki dot. pomiarów:
  • mikrokontroler taktowany jest z wewnętrznego generatora RC 1MHz,
  • wyłączone wszelkie wewnętrzne moduły,
  • napięcie zasilania w trakcie pomiarów wynosiło 4,85V.
  • multimetr METEX M-3610.

Do pomiarów zastosujemy także dobre praktyki polegające na wymuszeniu ustawienia wszystkich nieużywanych pinów, by nie stały się antenkami łapiącymi zakłócenia mogące mieć wpływ na pomiary. Będzie to zgodne z przyjętymi przez producenta mikrokontrolera warunkami pomiarowymi na podstawie których zamieścił wykresy w datasheet:




Pomiary

Załóżmy że mamy jakieś program, który na przykład:
  1. dokonuje co kilka godzin pomiaru za pomocą przetwornika analogowo-cyfrowego ADC,
  2. zapisuje pomiar w EEPROM mikrokontrolera,
  3. zostaje uśpiony w celu oszczędzania energii.


Pomiar 1

Najpierw sprawdzimy jak ile prądu pobiera mikrokontroler uśpiony do trybu Power Down:

#include <avr/io.h> 
#include <avr/interrupt.h> 
#include <avr/sleep.h>
#include <avr/eeprom.h>

EEMEM unsigned char pamiec_eeprom[512];

int main(void) 
{ 

 //--- Wyłącz zbędne moduły ---
 //W celu oszczędzania energii wyłączamy domyślnie włączony 
 //moduł komparatora analogowego
 ACSR |= (1<<ACD); //wyłącz komparator analogowy


 //--- Dobre praktyki ---
 //szczegóły: http://mikrokontrolery.blogspot.com/2011/04/zakocenia-w-pracy-mikrokontrolerow.html
 PORTB  = 0xff; //włącz rezystory pull-up
 PORTC  = 0xff; //włącz rezystory pull-up
 PORTD  = 0xff; //włącz rezystory pull-up

 //uśpij mikrokontroler do trybu Power Down
 set_sleep_mode(SLEEP_MODE_PWR_DOWN); 
 sleep_enable();
 cli();   //na wszelki wypadek wyłącz przerwania globalne
 sleep_cpu();  

 //pętla główna nigdy się nie wykona 
 while(1); 
}


Pomiar 1
Wynik pomiaru: 0,8µA

Różnica w stosunku do spodziewanych 1,1µA to 0,3µA, czyli około 30%, co porównując z parametrami multimetru oznacza, że trafiła mi się bardzo oszczędna ATmega8A :-)



Pomiar 2

Dokonajmy teraz zapis do pamięci EEPROM, po czym natychmiast uśpijmy mikrokontroler także do trybu Power Down:

#include <avr/io.h> 
#include <avr/interrupt.h> 
#include <avr/sleep.h>
#include <avr/eeprom.h>

EEMEM unsigned char pamiec_eeprom[512];

int main(void) 
{ 

 //--- Wyłącz zbędne moduły ---
 //W celu oszczędzania energii wyłączamy domyślnie włączony 
 //moduł komparatora analogowego
 ACSR |= (1<<ACD); //wyłącz komparator analogowy


 //--- Dobre praktyki ---
 //szczegóły: http://mikrokontrolery.blogspot.com/2011/04/zakocenia-w-pracy-mikrokontrolerow.html
 PORTB  = 0xff; //włącz rezystory pull-up
 PORTC  = 0xff; //włącz rezystory pull-up
 PORTD  = 0xff; //włącz rezystory pull-up

 //zapisz liczbę 0xAA do pamięci EEPROM o adresie 511
 eeprom_write_byte(&pamiec_eeprom[511], 0xAA);

 //uśpij mikrokontroler do trybu Power Down
 set_sleep_mode(SLEEP_MODE_PWR_DOWN); 
 sleep_enable();
 cli();   //na wszelki wypadek wyłącz przerwania globalne
 sleep_cpu();  

 //pętla główna nigdy się nie wykona 
 while(1); 
}


Pomiar 2
Wynik pomiaru: 1,014mA !!! 

Miało być tyle samo co w poprzednim pomiarze, czyli 0,8µA, a otrzymaliśmy ponad 1mA, czyli 1267 razy więcej.

Jak to możliwe?
Przecież wykonaliśmy instrukcję usypiania?


Odpowiedź na to pytanie znajdziemy oczywiście w datasheet.

Po pierwsze należy wziąć pod uwagę czas trwania zapisu do pamięci EEPROM:


Jak widzisz czas zapisu wynosi 8448 cykli zegara. Moja ATmega8A pracowała z zegarze 1MHz co daje czas trwania zapisu wynoszący 8,5ms.

Nie oznacza to, że mikrokontroler w tym czasie nie może nic robić. Ależ może i właśnie to wykorzystaliśmy wykonując następne rozkazy, którymi były czynności prowadzące do uśpienia mikrokontolera. Innymi słowy:

Zażądaliśmy uśpienia mikrokontrolera, w trakcie trwania zapisu do pamięci EEPROM!

No dobrze, ale dana zapisała się poprawnie, czyli operacja zapisu trwała nadal.
Wniosek: Mikrokontroler był uśpiony, ale jakby nie do końca :-)

Zerkamy do datasheet i znajdujemy wyjaśnienie:


Czyli:
  • gdy w trakcie zapisu do EEPROM, uśpimy mikrokontroler do trybu Power Down, operacja zapisu będzie kontynuowana,
  • gdy operacja zapisu zostanie zakończona, mikrokontroler nie wyłącza generatora zegara mikrokontrolera, czego konsekwencją jest niekompletne przejście do trybu Power Down.

Pomiar 3

Dla porównania sprawdźmy ile prądu powinien pobierać ten mikrokontroler, w czasie normalnej pracy:




I do kompletu pomiar, gdy mikrokontroler "kręci się" w pętli głównej:

#include <avr/io.h> 
#include <avr/interrupt.h> 
#include <avr/sleep.h>
#include <avr/eeprom.h>

EEMEM unsigned char pamiec_eeprom[512];

int main(void) 
{ 

 //--- Wyłącz zbędne moduły ---
 //W celu oszczędzania energii wyłączamy domyślnie włączony 
 //moduł komparatora analogowego
 ACSR |= (1<<ACD); //wyłącz komparator analogowy


 //--- Dobre praktyki ---
 //szczegóły: http://mikrokontrolery.blogspot.com/2011/04/zakocenia-w-pracy-mikrokontrolerow.html
 PORTB  = 0xff; //włącz rezystory pull-up
 PORTC  = 0xff; //włącz rezystory pull-up
 PORTD  = 0xff; //włącz rezystory pull-up

 //pętla główna
 while(1); 
}



Pomiar 3
Jak widzisz pomiar 2 (1,014mA) jest pomiędzy pomiarem 1 (0,8µA) i pomiarem 3 (1,532mA).

Czyli zgodnie z fragmentem datasheet zacytowanym powyżej, w pomiarze 2 mikrokontroler faktycznie wyłączył część wewnętrznych "bebechów", a część nie.






Jak temu zaradzić?

Mamy zdiagnozowaną przyczynę, trzeba więc jakoś temu zaradzić. Ostatnie zdanie z zacytowanego powyżej fragmentu datasheet daje nam wskazówkę:




że jeżeli zależy nam na tym, by mkrokontroler poprawnie został uśpiony należy zaczekać do końca operacji zapisu danej do EEPROM.

Bitem, który o tym informuje jest EEWE w rejestrze ECCR (przy okazji znajdziesz tam opis całej procedury zapisu do EEPROM):


Innymi słowy:

Po rozpoczęciu operacji zapisu do pamięci EEPROM, mikrokontroler może wykonywać inne czynności, ale przed uśpieniem mikrokontrolera do trybu Power Down, należy upewnić się czy operacja zapisu została zakończona.

Pomiar 4

Można to zrobić sprawdzając bit EEWE pętlą:

//Czekaj na koniec zapisu do EEPROM
while(EECR & (1<<EEWE));

lub skorzystać z gotowej funkcji eeprom_busy_wait():

//zaczekaj do końca zapisu EEPROM
eeprom_busy_wait();

Sprawdźmy więc, czy teraz będzie prawidłowo:

#include <avr/io.h> 
#include <avr/interrupt.h> 
#include <avr/sleep.h>
#include <avr/eeprom.h>

EEMEM unsigned char pamiec_eeprom[512];

int main(void) 
{ 

 //--- Wyłącz zbędne moduły ---
 //W celu oszczędzania energii wyłączamy domyślnie włączony 
 //moduł komparatora analogowego
 ACSR |= (1<<ACD); //wyłącz komparator analogowy


 //--- Dobre praktyki ---
 //szczegóły: http://mikrokontrolery.blogspot.com/2011/04/zakocenia-w-pracy-mikrokontrolerow.html
 PORTB  = 0xff; //włącz rezystory pull-up
 PORTC  = 0xff; //włącz rezystory pull-up
 PORTD  = 0xff; //włącz rezystory pull-up

 //zapisz liczbę 0xAA do pamięci EEPROM o adresie 511
 eeprom_write_byte(&pamiec_eeprom[511], 0xAA);

 //zaczekaj do końca zapisu EEPROM
 eeprom_busy_wait();

 //uśpij mikrokontroler do trybu Power Down
 set_sleep_mode(SLEEP_MODE_PWR_DOWN); 
 sleep_enable();
 cli();   //na wszelki wypadek wyłącz przerwania globalne
 sleep_cpu();  

 //pętla główna nigdy się nie wykona 
 while(1); 
}


Testujemy powyższy program:

Pomiar 4

... i otrzymujemy tyle samo co w pierwszym pomiarze, czyli mikrokontroler prawidłowo wszedł w tryb uśpienia.


... a tryb Power Save?

W datasheet mowa jest o trybie Power Down, jednakże tryb Power Save podlega temu samemu problemowi (przynajmniej w ATmega8, którego sprawdziłem osobiście).


Zobacz pozostałe pułapki AVR

4 komentarze:

  1. Jeśli chodzi o EEPROM to na początku przygody z Attiny2313 wpadłem innego rodzaju pułapkę. Do zapisu używałem funkcji dostarczonej przez środowisko, ale każda próba zapisu nie dochodziła do skutku. Analizując asembler okazało się, iż kompilator między dwie kluczowe instrukcje dokładał 5 kolejnych, a wymaganiem zapisu do EEPROM było wykonanie tych instrukcji w odstępie 2-3 cykli zegara. Napisałem wstawkę w asm i rozwiązało to problem.

    OdpowiedzUsuń
    Odpowiedzi
    1. Możesz uściślić w jakiej wersji kompilatora i jaką optymalizację miałeś ustawioną?

      Usuń
  2. Ok, fajnie, ale czy eeprom_busy_wait() nie zatrzymuje działania całego programu (na ta 8,5ms)? Czy nie lepiej wykorzystać jakieś przerwanie gotowości EEprom-a?

    OdpowiedzUsuń
    Odpowiedzi
    1. Ta funkcja oczekuje na bit EEWE, nie opóźnia sztywno na 8,5ms, jeśli zapis zostanie dokonany bit ten zostaje zmieniony i funkcja to odczytuje ;) Przynajmniej ja to tak rozumiem :)

      Usuń