You are on page 1of 6

Programowanie

Programowanie procesorów
w języku C część 17
Po początkowych próbach nadążania za zmia- wszystkie zmiany na podstawie dokumenta- Najważniejsze zmiany w wersji
nami w WinAVR, trzymaliśmy się cały czas cji. Zwykle więc posługujemy się drogą na 20070122
wersji 20060125. Obecnie wersja ma ponad skróty. Below is just a sample of what’s new.

półtora roku. Ponieważ ta część jest częścią Spośród napisanych programów, niewiele • Major version change for GCC. Now at version
4.1.1.
przedostatnią, chcę pokazać Ci prostą metodę jest takich, które nie pozwolą skompilować się • Support for the ATmega2560, and ATmega2561 de-
postępowania przy dostosowywaniu kodu do bez żadnych zmian. Weźmy na warsztat pro- vices.
ewentualnych zmian, jakie pojawiają się gram testujący funkcję printf – drugi program • Change the DWARF2 debug information from 16-bit
w WinAVR. z części 7. Problem, który tu występuje, już addresses to 32-bit addresses. This now allows de-
bugging of code above 64K, including bootloaders
przerabialiśmy, jednak jego kod umożliwia
Jak samemu dostosować przedstawienie metody postępowania. Ponadto
for the ATmega128 and ATmega1281, and debug-

program do nowej wersji


ging of the ATmega2560 and ATmega2561 devices.
otrzymałem pytania, dlaczego nie da się pro- This requires a version of AVR Studio that has a new
narzędzia WinAVR gramu skompilować z nowym kompilatorem. ELF/DWARF2 parser (> 4.12).
Proponuję, abyś teraz przeszedł do strony Sprawdźmy, czy kompilator „przełknie” • New avr-libc version with new examples, updated
examples, many bugs fixed, and new APIs for Power
http://winavr.sourceforge.net i pobrał najnow- program bez żadnych zmian. Przed pierwszą Reduction and Clock Prescaler.
szą wersję WinAVR. W chwili pisania tekstu kompilacją wykonujemy czyszczenie pro- • New avrdude version with libusb support, which al-
była to wersja 20070122. jektu – zapobiegnie to sytuacji, gdy część pli- lows connections to the AVR ISP mkII.
Po instalacji, pierwszy plik, do którego po- ków kompilowana jest nową wersją kompila- • New avarice version with libusb support, which al-
winniśmy zajrzeć, to WinAVR-user-manual tora, a część pozostaje w starej wersji. Otrzy- lows connections to the AVR JTAG ICE mkII.
(wersja txt albo html). Najbardziej interesuje mamy błędy widoczne na listingu 223. • New versions for Binutils and SRecord.
• Updates to the WinAVR Makefile Template and
nas sekcja 1.0 What’s New. Tutaj znajdziemy Gdy klikniemy fioletową linię błędu, zosta- MFile.
najważniejsze informacje o zmianach. Odpo-
wiedni fragment przedstawia ramka obok. Listing 223 Błędy podczas kompilacji programu z części 7
Zmiana w GCC ma dla nas znaczenie od Compiling: main.c
strony optymalizacji kodu. Składnia C się avr-gcc -c -mmcu=atmega162 -I. -gdwarf-2 -DF_CPU=8000000UL -Os -funsigned-char
-funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,
przecież nie zmienia, a sam kompilator nie za- -adhlns=main.lst -std=gnu99 -MD -MP -MF .dep/main.o.d main.c -o main.o
wiera dodatkowych bibliotek. main.c: In function ‘main’:
main.c:28: warning: passing argument 1 of ‘fdevopen’ from incompatible pointer type
Wsparcie dla nowych procesorów, które main.c:28: warning: passing argument 2 of ‘fdevopen’ from incompatible pointer type
mają 256kB pamięci, to przede wszystkim main.c:28: error: too many arguments to function ‘fdevopen’
make.exe: *** [main.o] Error 1
miła niespodzianka. Wymusza jednak kolej-
ną, tym razem ważną dla nas zmianę: zmienia
Listing 224 Funkcja fdevopen
się format pliku używanego do symulacji pro-
gramu. Jeśli korzystamy z AVRStudio, wy- #if defined(__STDIO_FDEVOPEN_COMPAT_12)
/*
maga to posiadania programu w wersji przy- * Declare prototype for the discontinued version of fdevopen() that
najmniej 4.13. We wcześniejszej wersji, pod- * has been in use up to avr-libc 1.2.x. The new implementation has
* some backwards compatibility with the old version.
czas wczytywania pliku, otrzymamy komuni- */
extern FILE *fdevopen(int (*__put)(char), int (*__get)(void),
kat o błędzie. int __opts __attribute__((unused)));
Nowa biblioteka avr-libc to bardzo ważny #else /* !defined(__STDIO_FDEVOPEN_COMPAT_12) */
/* New prototype for avr-libc 1.4 and above. */
element naszego środowiska. Zmiany w tym extern FILE *fdevopen(int (*__put)(char, FILE*), int (*__get)(FILE*));
miejscu dotyczą funkcji standardowych, #endif /* defined(__STDIO_FDEVOPEN_COMPAT_12) */
z których cały czas korzystamy. Musimy mieć
więc świadomość, że w ich wywołaniach mo- niemy przeniesieni do linii, w której błąd się Listing 225 Zmiana w programie z części 7
gły pojawić się zmiany. pojawił. Nie zmieniajmy jej teraz pochopnie. #define __STDIO_FDEVOPEN_COMPAT_12
Reszta wypisanych tutaj zmian nie ma dla Przekonamy się, że błąd tkwi w innym miej- #include <stdio.h>
nas wielkiego znaczenia. Wiemy teraz, że scu. Po pierwsze, znajdujemy opis funkcji
szczególną uwagę musimy zwrócić na funk- fdevopen z biblioteki stdio.h. gramie, przed dołączeniem nagłówka stdio.h
cje z biblioteki avr-libc oraz na posiadanie Informacje znajdziemy zarówno w odpo- umieszczamy linię, jak na listingu 225.
nowej wersji AVRStudio. wiednim pliku nagłówkowym, jak i w doku- To najprostsze rozwiązanie, aby skompilo-
mentacji avr-libc. Okazuje się, że funkcja wać program. Oczywiście lepszym wyjściem
Dwa podejścia... zmieniła swoją składnię i teraz funkcje rs_put byłoby odpowiednie zmienienie wywołania
Do zagadnienia możemy teraz podejść na dwa i rs_get powinny mieć inną formę. Ponadto fdevopen oraz zmiana funkcji rs_put i rs_get.
sposoby. Pierwszy, który nazwałbym właści- usunięty został trzeci, nieużywany parametr. Później, gdy wczytamy się w dokumentację,
wym, to zajrzenie do dokumentacji avr-libc Linie opisujące funkcje fdevopen, znajdu- dojdziemy do wniosku, że inicjacji strumieni
i przeczytanie o dostępnych funkcjach oraz jące się w pliku <stdio. h>, pokazuje listing można dokonać, rezygnując w ogóle z pamię-
zmianach. Zazwyczaj jednak chcemy uzyskać 224. Widzimy tutaj furtkę, jaką pozostawili ciożernej funkcji fdevopen. Teraz zależy nam
efekt szybko, ponadto trudno wychwycić twórcy nowej wersji WinAVR. W naszym pro- jedynie na poznaniu sposobu postępowania.

40 E l e k t ro n i k a d l a Ws z y s t k i c h
Programowanie
Zbierzmy wszystko w punkty: %s – wskaźnik na łańcuch w pamięci RAM, go zabierzemy.
Postępując według schematu, być może wypisanie łańcucha. Resztę wyjaśnia ramka o otwartej liście ar-
czasem podpierając się słownikiem, bę- %S – wskaźnik na łańcuch w pamięci progra- gumentów oraz listingi 229 i 230.
mu, wypisanie łańcucha. Naszą funkcję piszącą możemy teraz wy-
1. Czyszczenie projektu Program napiszemy na podstawie naszej korzystać, aby na pokazywanym obrazku wy-
2. Kompilacja przeglądarki z poprzedniego odcinka. Ponie- pisać dodatkowe informację – dodamy linijkę
3. Sprawdzenie błędów i zajrzenie do waż nie wypisywaliśmy jeszcze tekstu na na- do funkcji app_DrawImg – listing 231.
dokumentacji albo pliku nagłówkowego, szym kolorowym wyświetlaczu, w dodatko- Jak widzisz, obsługa tego tworu okazuje
co się zmieniło w „spornej” funkcji? wej ramce wyjaśniam, w jaki sposób się do te- się stosunkowo prosta.
4. Dostosowanie funkcji do wymagań no-
wej biblioteki i powrót do punktu 2. va_end (va_list) – czyści wszystko, co jest do wy-
ABC... C czyszczenia i pozwala na powrót z funkcji o zmiennej li-
dziesz w stanie samodzielnie obejść pro- Otwarta (niepełna) lista argumentów ście argumentów, tak jakby to była zwyczajna funkcja.
blem przejścia do nowej wersji WinAVR albo
gdy tylko taki się pojawi. Zmienna długość linii argumentów Ograniczenia
Makro va_end powinno być wywoływane wyłącznie po

Pozostały zakres materiału Oto właściwa deklaracja funkcji z niepełną listą argu- odczytaniu wszystkich parametrów przesłanych do funk-
cji (va_list musi wskazywać na miejsce za ostatnim argu-
mentów:
Z założonego materiału zostało nam do omó- void funkcja (char *str, ,...); mentem). Niespełnienie tego warunku może spowodo-
wienia kilka przydatnych drobiazgów. Do tej Trzy kropki oznaczają, że w tym miejscu może wy- wać nieokreślone zachowanie programu.
pory pisaliśmy o tym, jak korzystać z funkcji stąpić nieokreślona liczba argumentów. Symbol trzech Wszystkie parametry przekazywane przez zmienną
listę argumentów, są przesyłane z użyciem „starej promo-
o otwartej liście argumentów, takiej jak printf. kropek umieszczamy zawsze jako ostatni argument.
cji argumentów języka C”. Promocja ta mówi, że typy
Teraz dowiesz się, jak wykorzystać trzy krop- char, short int są automatycznie promowane do typu int.
ki we własnych konstrukcjach. Obsługa
Sztuka polega na tym, aby odszukać w pamięci przesłane Typ float jest promowany do typu double. W związku
Później opowiem o kilku rzadziej używa- argumenty. W przypadku C służą do tego makra oraz spe- z tym błędem jest ustawienie char, short int albo float
nych, ale czasem bardzo przydatnych funkcjach. cjalny typ zmiennej deklarowane w pliku <stdarg.h>. w miejscu drugiego parametru funkcji va_arg.
Przedrostek va_ pochodzi od słowa varargs. Trzeba zdawać sobie sprawę z faktu, że w AVR-GCC

Funkcja z otwartą listą va_list – jest to typ zmiennej wskazującej na aktual- zmienna lista argumentów przesyłana jest przez stos (jest
to jednocześnie najczęstsze rozwiązanie stosowane
argumentów ną pozycję w liście argumentów nieustalonych.
va_start (va_list, str) – funkcję tę wywołujemy w praktyce). Nie ma możliwości umieszczenia takich pa-
Napiszemy teraz funkcję tworzącą na ekranie w celu zainicjowania zmiennej typu va_list. Drugi para- rametrów w rejestrach. Sprawia to, że wywołanie tego ty-
formatowane wyjście. Będzie to pewna okro- metr to mnemonik ostatniego argumentu ustalonego – pu funkcji zajmuje więcej zasobów niż w przypadku
jona forma funkcji printf. Jej składnia będzie przed trzema kropkami. Jeśli przejdziemy już przez całą funkcji o określonej liczbie parametrów.
Dodatkowym ograniczeniem jest też fakt, że kompi-
identyczna, jednak będziemy przyjmować je- listę argumentów, ponowny dostęp do pierwszego argu-
lator nie jest w stanie pilnować czy parametry przesłane
dynie następujące formaty: mentu jest możliwy po ponownym wywołaniu funkcji.
va_arg (va_list, typ) – powoduje pobranie argumen- do funkcji są prawidłowe – należy pilnować tego we wła-
%d – wartość typu int, wypisanie w formie tu podanego typu oraz odpowiednie przesunięcie wskaź- snym zakresie.
dziesiętnej. nika listy argumentów. Przykłady: listing 229 i 230.
%c – wartość znakowa, wypisanie znaku.

Idea rozwiązania Funkcja rysująca linie znaku: danych. W tym pierwszym przypadku należy ustawić fla-
Na listingu 226 pokazana jest funkcja, która zamienia li- gę LCD_TM_FLASH. Aby uprościć sobie dalszą pracę,
Proste pisanie tekstu na ekranie ko- nię znaku na dane wpisywane bezpośrednio do bufora. Za- znak z łańcucha będziemy pobierać za pomocą funkcji
lorowym uważ możliwość podania zarówno koloru tła, jak i koloru pomocniczej widocznej na listingu 228.
znaku. Ponadto, jeśli w parametrze mode
ustawiono flagę LCD_TM_TRANSPA- Listing 227 Wypisanie znaku
Pisanie na ekranie kolorowym wykonamy teraz w oparciu
RENT, tło będzie przezroczyste (nie będzie
o czcionkę statyczną, z której korzystaliśmy w części 13. uint8_t lcd_DrawChar(uint8_t x, uint8_t y, char znak,
wypisywane).
Wykorzystamy dokładnie tę samą tablicę z definicją wy- lcd_pixel charcolor, lcd_pixel bkcolor, uint8_t mode)
Funkcja oznaczona jako statyczna, po- {
glądu znaków. Jedyna różnica jest taka, że o ile poprzed-
nieważ w założeniu nie będzie wywoływana // Jeśli znak nie mieści się w tablicy...
nio bajt opisujący jedną linię znaku był wpisywany bezpo- if(znak < ‘ ‘ ||
spoza modułu LCD. Umożliwia to kompila-
średnio do bajtu w buforze obrazu, teraz będziemy musie- znak > ((char)’ ‘ + ELEMS(lcd_Font)))
torowi optymalizację jej wywołania. znak = ‘#’;
li rozłożyć go na pojedyncze piksele – w naszym koloro-
// Wysyłam 5 kolejnych bajtów z tablicy
wym wyświetlaczu jeden bajt odpowiada jednemu pikse-
Wypisanie znaku: uint8_t n;
lowi. prog_uint8_t* pData = lcd_Font[znak - ‘ ‘];
Mając funkcję wypisującą linię znaku, dalej
postępujemy już bardzo podobnie jak // wysyłanie danych
Listing 226 Wypisanie linii znaku przy pisaniu na wyświetlaczu czarno- for(n=0; n<5; n++)
-białym. Funkcję wypisującą znak poka- {
uint8_t data = pgm_read_byte(pData++);
static void lcd_DrawCharLine(uint8_t x, zuje listing 227. W razie wątpliwości zaj- lcd_DrawCharLine(x, y, data,
uint8_t y, uint8_t data, rzyj do części 13 – listing 227 to przerób- charcolor, bkcolor, mode);
lcd_pixel charcolor, lcd_pixel bkcolor, ++x;
uint8_t mode) ka listingu 169.
}
{
return x;
uint8_t n; Wypisanie całego }
for(n=0; n<8; n++)
{ łańcucha:
if(data & 0x01) Nie przedstawiam tutaj funkcji
lcd_Pixel(x, y, charcolor); Listing 228
wypisującej cały łańcuch zna-
else if(!(mode & LCD_TM_TRANSPARENT)) Pobieranie znaku z łańcucha umieszczonego w wybranej pamięci
lcd_Pixel(x, y, bkcolor); ków – podobne procedury pisa-
++y; liśmy już wielokrotnie. Nasza static uint8_t lcd_GetCharFromStr(char *str, uint8_t mode)
data >>= 1; funkcja różni się jednak o tyle, {
} return (mode & LCD_TM_FLASH) ? pgm_read_byte(str) : *str;
że może czytać łańcuch z pa- }
}
mięci programu albo pamięci

E l e k t ro n i k a d l a Ws z y s t k i c h 41
Programowanie
Oszczędzanie energii
Do tej pory zupełnie nie przejmowaliśmy się Listing 229 Funkcja lcd_Printf

zużyciem energii przez układ. Nie ma w tym // Zwraca: pozycję x za ostatnio napisanym znakiem.
uint8_t lcd_Printf(uint8_t x, uint8_t y, char *str,
nic dziwnego – nasza płytka nie została utwo- lcd_pixel charcolor, lcd_pixel bkcolor, uint8_t mode, ...)
rzona do wielkiego oszczędzania mocy. Jed- {
// Inicjacja
nak pomyślmy teraz, co zrobić, gdy procesor va_list arg;
kręci się w pętli, czekając na jakieś zdarzenie? va_start (arg, mode);
Możemy przecież wprowadzić go w jeden ze // Pętla wypisywania łańcucha
stanów obniżonego poboru mocy. Pokażę Ci for(;;str++)
{
za moment, że dodając do naszej przeglądarki
zaledwie kilka linii, można uzyskać zaskaku- char znak;
// Wczytanie danej łańcucha
jące oszczędności. znak = lcd_GetCharFromStr(str, mode);
Wynik pomiaru prądu, pobieranego przez // Zakończenie funkcji, jeśli koniec łańcucha
if(znak == 0)
układ, w przypadku wyświetlania obrazka, break;
przy odłączonych wszelkich przewodach // Obsługa znaków specjalnych
if(znak == ‘%’)
oprócz zasilającego, pokazuje fotografia 12. {
(Listing 230 !!!)
Napięcie zasilania ograniczono do 4,1V, aby }
zminimalizować prąd płynący przez zabez- // Wypisanie zwyczajnego znaku
else
pieczającą diodę Zenera. {
W naszym programie są dwa miejsca, // Wypisanie znaku
x = lcd_DrawChar(x, y, znak, charcolor, bkcolor, mode);
gdzie procesor czeka na jakieś zdarzenia. Oba }
znajdują się w pliku system.c. Są to funkcje // Wypisanie przerwy
lcd_DrawCharLine(x, y, 0, charcolor, bkcolor, mode);
system_delayMake oraz system_msgWaitFor. ++x;
Co bardzo ważne, zdarzenie, na jakie czekają }
// Wyczyszczenie wszystkiego, co wymaga wyczyszczenia
funkcje, będzie ustawione w obsłudze prze- va_end (arg);
rwania. Możemy więc spokojnie przełączyć
return x;
procesor w tryb IDLE, w którym pobiera on }
2x mniej prądu.
W obu wspomnianych funkcjach
Listing 230 Pobieranie argumentów w funkcji lcd_Printf Listing 231 Wypisanie informacji o obrazku
znajdują się pętle z pustym ciałem.
W obu przypadkach w ciało pętli znak = lcd_GetCharFromStr(++str, mode);
switch(znak)
(...)
wpiszemy zawartość listingu 232. { lcd_Printf(0, 0, PSTR(„Obrazek %d z %d“),
0x1c, 0, LCD_TM_FLASH, index+1, imgl_GetSize());
Konieczne będzie dodanie pliku na- case{ ‘d’: // Odmalowanie ekranu
lcd_Update();
główkowego <avr/sleep.h>. int d = va_arg(arg, int);
char bufor[6];
Już taka prosta modyfikacja, któ-
ra zajęła nam może minutę czasu, x = lcd_DrawText(x, y, Listing 232 Usypianie procesora w czasie czekania na przerwanie
itoa(d, bufor, 10), charcolor,
pozwoli na widoczne wydłużenie bkcolor, mode & ~LCD_TM_FLASH); while(...)
czasu pracy urządzenia na bateriach. } {
break; set_sleep_mode(SLEEP_MODE_IDLE);
Fotografia 13 pokazuje nowy wy- case ‘c’: sleep_mode();
nik pomiaru zużycia prądu. // Uwaga - dajemy int nie char!!! }
znak = va_arg (arg, int);
Ale może taka oszczędność nam x = lcd_DrawChar(x, y, znak,
Listing 233 Makra włączające i wyłączające pamięć RAM
nie wystarczy? charcolor, bkcolor, mode);
break;
Co prawda nie dotyczy to już sa- case ‘s’: #define ram_Enable() PORTC &= ~(1<<7);
mego WinAVR, a raczej metodolo- case ‘S’: #define ram_Disable() PORTC |= (1<<7);
{
gii postępowania... sporo mocy po- char *str;
biera aktywna cały czas, zewnętrzna uint8_t mode_s = mode & ~LCD_TM_FLASH;
if(znak == ‘S’)
Listing 234 Wyłączanie pamięci w funkcji app_run
pamięć RAM. A przecież przez mode_s = mode | LCD_TM_FLASH;
for(;;)
str = va_arg (arg, char*);
większość czasu w ogóle się do niej x = lcd_DrawText(x, y, str,
{
// Aktywacja interpretera komend
nie odwołujemy. Dla prostoty jednak charcolor, bkcolor, mode_s);
system_enableUARTCommand();
}
ustawiliśmy na sztywno stan ‘0’ na break;
uint8_t msg;
wyprowadzeniu A15, połączonym case ‘%’: ram_Disable();
x = lcd_DrawChar(x, y, znak,
z wejściem /CE pamięci. Dodajmy charcolor, bkcolor, mode);
msg = system_msgWaitFor();
ram_Enable();
do pliku harddef. h dwa proste ma- break;
(...)
}
kra, widoczne na listingu 233.

Fot. 13 Pobór prądu, usypianie Fot. 14 Pobór prądu, usypianie proce-


Fot. 12 Pobór prądu, oryginalnie procesora, gdy nie pracuje sora oraz wyłączanie pamięci RAM

42 E l e k t ro n i k a d l a Ws z y s t k i c h
Nie będziemy teraz zastanawiać się spe- na pozostawieniu zawartości kluczowych dla Ponadto, co warto wiedzieć:
cjalnie, gdzie dostęp do pamięci jest ko- aplikacji zmiennych – możemy sprawdzić je- Od startu procesora do pierwszej instrukcji
nieczny, a gdzie nie. Założymy, że pamięć dynie, czy ich wartości są prawidłowe (przy- w main (): 395us. Tutaj uwaga – czas ten za-
RAM jest domyślnie uruchomiona w funk- pomnij sobie, co pisaliśmy o sekcji .noinit). leży także od tego, ile zmiennych globalnych
cji main – tak jak to było do tej pory. Wyłą- Wróćmy do naszego programu generowa- i statycznych należy wcześniej przygotować
czać ją będziemy na czas czekania na zda- nia płomienia. Zabezpieczmy jego działanie (wyzerować albo nadać im wartość początko-
rzenie w module aplikacji. Zmienimy więc watchdogiem. Prosta kontynuacja pracy bę- wą). W dużych aplikacjach, czas ten może
fragment funkcji app_run widoczny na dzie polegała na tym, że pamięć obrazu nie znacząco się zwiększyć.
listingu 234. będzie czyszczona w przypadku wystąpienia Od startu procesora do pierwszej instrukcji
Obie opisane zmiany, chociaż bardzo „awaryjnego” zerowania. w .init3: Niecałe 2us.
proste, umożliwiły zejście z poborem prądu Sprawdźmy, jaki czas powinniśmy dać so- Widzimy, że minimalny, sensowny okres
do wartości widocznej na fotografii 14. My- bie na zerowanie watchdoga. Eksperymental- WDT wynosi w naszym przypadku 250ms.
ślę, że zestawienie fotografii mówi samo za nie, za pomocą symulacji w AVRStudio, mo- Można oczywiście go jeszcze skrócić, zerując
siebie, że warto czasem, po napisaniu pro- żemy sprawdzić, że przy 8MHz zegarze czasy watchdog w większej ilości miejsc niż tylko raz
gramu, przyjrzeć się, gdzie można oszczę- wykonania poszczególnych fragmentów kodu w pętli głównej, liczbie w naszym przypadku
dzić trochę mocy. Tym bardziej, jeśli spore kształtują się następująco: takie podejście nie wydaje się uzasadnione. Dla
oszczędności uzyskamy praktycznie za Od uruchomienia do zakończenia inicjacji: bezpieczeństwa nawet wybierzmy konfigura-
darmo. 67ms/67ms (sprzętowy SPI/programowy SPI). cję o 2x dłuższym czasie niż nasze minimum.
Pojedynczy przebieg pętli głównej: Listing 235 pokazuje wczesną konfigura-
Timer watchdog – bezpie- 146ms/187ms. cję watchdoga. Nie ma znaczenia, czy timer
czeństwo aplikacji
Poza oszczędzaniem mocy, powinniśmy SLEEP_MODE_PWR_SAVE
zwrócić także uwagę na bezpieczeństwo pra- ABC... GCC SLEEP_MODE_STANDBY
cy naszego programu. Trzeba zdawać sobie Dostępne funkcje:
z tego sprawę, że każdy, nawet najlepiej napi- Sterowanie trybami obniżonego po- set_sleep_mode (mode) – ustawia tryb uśpienia. Ustawie-
boru mocy nia wystarczy dokonać tylko raz – każde późniejsze wy-
sany program, może ulec zawieszeniu. Nieko- wołanie funkcji sleep_cpu () albo sleep_mode () spowo-
niecznie musi być to związane z błędem pro- duje przejście do wybranego trybu.
W przypadku procesorów AVR powstało kilka różnych
gramu – czasem wystarczy zakłócenie na li- zestawów rejestrów służących do przełączania ich w tryb sleep_cpu () – powoduje przejście procesora w wybrany
niach zasilania albo silny impuls EM. obniżonego poboru mocy. Wszystko zależy od modelu. tryb uśpienia. W praktyce wykonuje instrukcję asemblera
Jeśli urządzenie się zawiesi, możemy za- Jednak korzystając z WinAVR nie musimy się tym przej- „sleep”. Przed wywołaniem tej funkcji bit zezwalający na
mować – wszystkie wersje obsługiwane są przez iden- uśpienie powinien być ustawiony. Zaleca się jego wyzero-
działać tak, aby użytkownik tego nie odczuł al- wanie zaraz po wyjściu ze stanu uśpienia.
tyczne funkcje.
bo przynajmniej zdarzenie takie nie było zbyt sleep_enable () – ustawia bit zezwalający na uśpienie.
Aby wymienione niżej funkcje były dostępne, ko-
uciążliwe (na przykład wyjście do menu głów- nieczne jest dołączenie do programu pliku <avr/sleep.h>. sleep_disable () – zeruje bit zezwalający na uśpienie.
nego, ale zachowanie właściwego odliczania Definiowane stałe oznaczające poszczególne tryby: sleep_mode () – powoduje przejście procesora w wybra-
czasu i działania ustawionych alarmów). SLEEP_MODE_ADC ny tryb uśpienia. W odróżnieniu od sleep_cpu, sam prze-
prowadza aktywację uśpienia oraz późniejsze jego wyłą-
W tym przypadku pomoże nam watchdog ti- SLEEP_MODE_EXT_STANDBY
SLEEP_MODE_IDLE czanie.
mer i odpowiednia reakcja na występujące Przykłady: listing 232.
SLEEP_MODE_PWR_DOWN
z niego zerowanie. Zwykle będzie to polegało

Pobór prądu przy 5V, 8MHz


Szczegóły techniczne Tryb Pobór prądu Skrócony opis
Active 16mA Normalny tryb pracy
Stany obniżonego poboru mocy Idle 8mA CPU i pamięć FLASH zatrzymane, reszta peryferii pracuje
procesorów AVR Power Down <2µA CPU i wszystkie peryferie zatrzymane. Rezonator nie pracuje.
Możliwe wyjście tylko przez reset albo przerwanie asynchroniczne.
Power Save <25µA Jak Power Save, ale Timer2 pracuje, jeśli taktowany asynchronicznie
Procesory AVR mają spore możliwości oszczędzania
Standby <180µA Jak Power Down, ale rezonator pracuje.
energii. Sam procesor może być przełączony w kilka try- Skraca to czas reakcji na zerowanie albo przerwanie.
bów pracy, a najbardziej energochłonne peryferia mogą Extended Standby <200µA Jak Standby, ale Timer2 pracuje, jeśli taktowany asynchronicznie
być wyłączone. Nie będziemy zajmować się dokładnym
opisem sposobu przełączenia w tryb energooszczędny, po-
Dodatkowy pobór mocy przez włączone peryferia
nieważ będziemy korzystać z wygodnych funkcji avr-libc. ście będzie aktywne. I co bardzo ciekawe, włączone wej-
Element Pobór prądu
Ważna natomiast jest wiedza na temat tego co w danym ścia objęte przerwaniem na zmianę stanu też mogą obu-
BOD ok. 25µA
stanie procesor może i ile potrzebuje prądu. Na drugie py- dzić procesor. TOSC 32kHz (Timer2) ok. 23µA
tanie odpowiadają tabelki w ramce. Na pierwsze odpowia- W każdym przypadku jednak wymagane jest, aby zda- WDT ok. 15µA
da opis niżej: rzenie (reset, poziom niski, zmiana stanu) trwało przez pe- Komparator analogowy ok. 60µA
Idle – Tryb pracy, w którym zatrzymany jest CPU wien czas. To czyni procesor odporniejszym na zakłócenia
oraz pamięć programu. Działają natomiast wszystkie pe- – wejścia są próbkowane dwa razy w takt oscylatora
ryferie i procesor może być obudzony przez dowolne WDT, którego nominalny okres wynosi 1µs. Extended Standby – Tryb Standby rozszerzony
przerwanie (także gotowości pamięci FLASH do instruk- Od chwili wykrycia zdarzenia do uruchomienia proce- o możliwość pracy w trybie asynchronicznym zegara
cji SPM). Pobór prądu w tym trybie jest dwa razy mniej- sora mija pewien czas, zależny od bitów konfiguracji, nie- – licznika 2.
szy niż w trybie pełnej aktywności. zbędny do uruchomienia i stabilizacji drgań oscylatora. ADC Noise Reduction – Tryb dostępny w proceso-
Power Down – Najbardziej oszczędny tryb pracy. Power Save – Tryb Power Down rozwinięty o możli- rach wyposażonych we wbudowany przetwornik AC.
Wydawałoby się, że w tym trybie procesor praktycznie nic wość pracy w trybie asynchronicznym zegara – licznika 2. Umożliwia on osiągnięcie dokładniejszych pomiarów,
nie może. Nie jest to jednak do końca prawda. Procesor Procesor może być budzony dodatkowo przez przerwania przez zmniejszenie generowanych zakłóceń. Po wejściu
może być wybudzony za pomocą dowolnego źródła rese- pochodzące z tego elementu. w tryb, przetwarzanie rozpoczyna się automatycznie.
tu, jeśli tylko jest ono aktywne (Reset zewnętrzny, WDT, Standby – Tryb identyczny z trybem Power Down, Poza zakończeniem przetwarzania, inne przerwania
BOD). Ponadto można obudzić go przerwaniem wyzwala- z tym wyjątkiem, że oscylator główny cały czas pracuje. (ale niekoniecznie wszystkie) mogą obudzić procesor.
nym poziomem na wejściach INT0 i INT1. Jeśli urucho- Dzięki temu czas, jaki mija od wykrycia zdarzenia do pod- Szczegółowe informacje wymagają sprawdzenia doku-
miliśmy przerwanie asynchroniczne INT2, także to wej- jęcia pracy przez procesor, wynosi sześć taktów zegara. mentacji wybranego procesora.

E l e k t ro n i k a d l a Ws z y s t k i c h 43
Programowanie
ten włączymy na sztywno z poziomu progra- jeszcze wyświetlacz zostanie uruchomiony,
matora, czy będziemy zezwalali na jego za- ktoś w łazience włącza starą suszarkę. Gene- ABC... GCC
trzymanie. Tak czy inaczej dobrze jest prze- ruje to impuls na liniach zasilania i procesor ABC... C
prowadzić jego inicjację możliwie wcześnie. wpada w martwą pętlę. Po 0,5s zostaje on wy-
zerowany przez watchdoga. Ponownie rozpo- Dyrektywa #undef
Listing 235 Wczesna inicjacja WatchDog’a czyna się inicjacja, która tym razem kończy Poniższa linia:
void before_main(void)
się pomyślnie. Dochodzimy do decyzji, czy #undef EXTRF
{ należy czyścić bufor obrazu. W rejestrze powoduje unieważnienie wcześniejszej definicji stałej /
// Jak najwcześniejsze ustawienie wdt makra EXTRF. Unieważnienie obowiązuje od chwili
wdt_enable(WDTO_500MS); MCUCSR ustawiony jest bit WDFR – wyda-
wystąpienia dyrektywy. Dane makro lub stała są ważne
(...) wałoby się więc, że ponieważ wystąpiło awa-
wcześniej. Po dyrektywie #undef, można nadać określo-
ryjne zerowanie, nie powinniśmy nemu mnemonikowi nową wartość przez ponowną defi-
Listing 236 Warunkowa inicjacja części zmiennych (czyszczenie bufora) wykonywać tej funkcji. Jednak nicję.
if(MCUCSR & (1<<PORF | 1<<EXTRF | 1<<BORF | 1<<JTRF))
tak naprawdę bufor od czasu włą- Przykład: listing 237
lcd_Cls(0); czenia zasilania nie był jeszcze
MCUCSR = 0;

Do obsługi WDT służą w sumie trzy makra:


ABC... GCC
Spójrz teraz na listing 236. Nie jest to po- wdt_reset () – zerowanie licznika. Makro jest zamie-
czątkowa faza inicjacji – pojawia się już po Obsługa watchdoga niane na pojedynczą instrukcję asemblera: wdr. Jeśli ti-
mer pracuje, ta instrukcja powinna być wywoływana
uruchomieniu samego wyświetlacza i wczyta-
W bibliotece avr-libc specjalne funkcje do obsługi timera okresowo, aby nie dopuścić do zerowania procesora. Do-
niu palety. Na tym listingu na dwie rzeczy po- brym miejscem na jej umieszczenie jest pętla główna
WatchDog wymagają dołączenia pliku <avr/wdt. h>.
winny zwrócić uwagę: Definiowane stałe oznaczające poszczególne opóź- i wszystkie miejsca, gdzie procesor może dłużej „kręcić
1. Wcale nie zerujemy rejestru MCUCSR nienia: się” w pętli.
bardzo wcześnie. Do czasu zakończenia ini- WDTO_15MS wdt_disable () – wyłączenie WDT, jeśli tylko jest to
WDTO_30MS możliwe (jeśli zezwala na to wybrany tryb bezpieczeń-
cjacji wyświetlacza mija czas liczony już
WDTO_60MS stwa procesora). Automatycznie wykonuje sekwencję
w dziesiątkach milisekund. wymaganą do zmiany rejestru WDTCR oraz blokuje
WDTO_120MS
2. Czyszczenie ekranu nie jest uzależnione WDTO_250MS przerwania na czas wprowadzanej zmiany.
od flagi zerowania z WDT, lecz od tego, czy WDTO_500MS wdt_enable (timeout) – powoduje włączenie WDT al-
nie są ustawione flagi pozostałe. WDTO_1S bo, jeśli licznik już pracuje, zmianę czasu jego opóźnie-
WDTO_2S nia. Podobnie jak wdt_disable, także generuje odpowied-
Wyjaśnienie zacznę od końca:
WDTO_4S – tylko niektóre procesory nią sekwencję i chwilowo blokuje przerwania. Ponadto
Załóżmy taką teoretyczną sytuację: włą- zawiera także instrukcję wdr.
WDTO_8S – tylko niektóre procesory
czamy zasilanie. W tej chwili zaczyna się Przykłady: listing 235 i 236.
inicjacja. I dokładnie w tym momencie, zanim

stępującą sekwencję:
Szczegóły techniczne 1. W pojedynczej operacji
wpisz 1 do bitów WDCE i WDE
watchdog timer rejestru WDTCR (niezależnie od
tego, czy WDE jest aktualnie usta-
Nie ma wielkich różnic w obsłudze timera WDT w po- wiony, czy wyzerowany).
szczególnych procesorach, chociaż pewne występują. Po 2. W czasie czterech cykli
szczegółowe informacje należy zawsze zajrzeć do doku- wpisz wybrane ustawienia. Tym
mentacji danego procesora. Dalej skupiam się na proceso- razem bit WDCE powinien być
rze atmega162. wyzerowany.
Timer watchdog jest taktowany z oddzielnego genera- Sekwencja zmiany WDTCR wymaga odpowiednio WDP2..0 Liczba cykli Przybliżony czas
tora o częstotliwości 1MHz. Nie należy jednak liczyć na krótkiego czasu między poszczególnymi instrukcjami. 3V 5V
dużą stabilność jej wartości. Jeśli pozwolimy doliczyć te- Ważne jest więc by w czasie jej wykonywania żadne prze- 0 16K 17ms 16ms
mu timerowi do końca, procesor zostanie wyzerowany. rwanie nie rozpoczęło wykonywania swojego kodu. Naj- 1 32K 34ms 33ms
Możliwe do ustawienia czasy, między ostatnim zerowa- bezpieczniej jest chwilowo wyłączyć globalne zezwolenie 2 64K 69ms 65ms
na przerwania. 3 128K 0,14s 0,13s
niem timera a zerowaniem procesora, pokazuje tabela
4 256K 0,27s 0,26s
w ramce. Duża rozpiętość czasów umożliwia dostosowa- Flagi źródła zerowania
5 512K 0,55s 0,52s
nie działania WDT do wymagań aplikacji. Przydatność timera WDT została znacznie zwiększona 6 1024K 1,1s 1,0s
W procesorze Atmega162 watchdog może pracować przez specjalny rejestr MCUCSR, który zawiera informa- 7 2048K 2,2s 2,1s
w 3 poziomach bezpieczeństwa, zależnych od ustawień cję o źródle zerowania.
bezpieczników w czasie programowania: Rejestr ten ma to do siebie, że zerowanie przy włącze-
Safety Level 0 – WDT wyłączony po zerowaniu pro- niu zasilania (flaga PORF) powoduje wyzerowanie pozo-
cesora. Jego włączenie i zmiana okresu są możliwe, bez stałych flag. Flaga PORF z kolei może być wyzerowana
konieczności wykonywania niżej opisanej sekwencji tylko przez wpisanie do niej zera.
zmiany WDTCR. Wyłączenie wymaga odpowiedniej se- Teraz, co ważne, jeśli nie wyzeru-
kwencji. jemy naszego rejestru i gdy po
Safety Level 1 – Podobnie jak poziom 0, ale wymaga włączeniu zasilania wystąpi zero-
sekwencji także do zmiany okresu pracy WDT. wanie z zewnętrznego wyprowa-
Safety Level 2 – WDT domyślnie włączony po zero- dzenia, a później, na przykład
waniu procesora. Timer ten nie może być programowo z WDT – wszystkie trzy flagi
wyłączony, a jedynie okresowo zerowany. Bit WDE pod- (PORF, EXTRF, WDRF) będą
czas odczytu przyjmuje zawsze wartość 1. ustawione. Gdy już odpowiednio
Okresowego zerowania watchdoga dokonujemy jedną zareagujemy na zawartość flag, re-
instrukcją asemblera: wdr. jestr MCUCSR powinniśmy pro-
Sekwencja zmiany rejestru WDTCR gramowo wyzerować. Wiele źró-
W poziomach bezpieczeństwa, które tego wymagają, aby deł zaleca zrobić to jak najszyb-
zmienić zawartość rejestru WDTCR, należy wykonać na- ciej, ale przekonamy się, że nie za-
wsze jest to dobre wyjście.

44 E l e k t ro n i k a d l a Ws z y s t k i c h
Programowanie
czyszczony. W rejestrze MCUCSR cały czas rozwiązanie. Zasymulujemy zerowanie
ustawiona jest także flaga PORF. Dlatego watchdoga przyciskiem RESET na płytce.
też bezpieczniej jest sprawdzić, czy nie wy- Nie odpowiada to na wszystkie pytania, ale
stąpiło zdarzenie wymagające czyszczenia umożliwia bardzo wygodne testy, które dużo
zmiennych, niż czy nie wystąpiło zdarzenie, mówią już o napisanym programie.
które umożliwia ominięcie tej części kodu. Aby nie zmieniać nic w naszym kodzie,
Rejestr zawierający flagi zerowania zmienimy raczej przypisanie bitów w reje-
czyścimy dopiero po zainicjowaniu zmien- strze. Ponieważ do każdego praktycznie pli-
nych, nie wcześniej. Łatwo wyobrazić sobie ku dołączamy nagłówek „harddef.h”, może-
sytuację, gdy awaria zdarzy się w chwili my zrobić to właśnie tutaj. Dodane linijki
czyszczenia bufora – jeśli wyzerujemy przedstawia listing 237.

A
MCUCSR przed skończeniem jego czysz- Listing 237 Zmiana przypisania flag zerowania
czenia, nie wyłapiemy takiego zdarzenia.

M
Przedstawione powyżej przypadki to #undef EXTRF

A
#define EXTRF WDRF
przypadki skrajne. Jednak ich uwzględnienie

L
sprawi, że nasze urządzenie będzie miało I to wszystko. Teraz, gdy po skompilow-
mniejszą szansę, aby paść po wielu miesią- niu i wgraniu programu naciśniemy przycisk

K
cach/latach pracy bez przerwy. RESET, wyświetlacz mignie, ale sam pło-

E
mień nie zacznie się od początku – jego ge-

R
Jak sprawdzić, nerowanie będzie kontynuowane – czyli
czy program prawidłowo wszystko działa tak, jak chcieliśmy.
reaguje na reset WDT? Po skończeniu testów pamiętaj, aby usu-
Teraz powinno pojawić się pytanie, jak prze- nąć (wykomentować) dodane linie z listingu
testować reakcję urządzenia na awaryjne ze- 237.
rowanie? Raczej nie możemy liczyć na testy Radosław Koppel
za pomocą starej suszarki. Jest na to inne radoslaw.koppel@elportal.pl

R E K L A M A

You might also like