kursC_czesc017.pdf

(1126 KB) Pobierz
215235155 UNPDF
Programowanie
P r o g r a m o w a n i e p r o c e s o r ó w
w j ę ę ęz y k u C
część 17
Po początkowych próbach nadążania za zmia-
nami w WinAVR, trzymaliśmy się cały czas
wersji 20060125. Obecnie wersja ma ponad
półtora roku. Ponieważ ta część jest częścią
przedostatnią, chcę pokazać Ci prostą metodę
postępowania przy dostosowywaniu kodu do
ewentualnych zmian, jakie pojawiają się
w WinAVR.
Jak samemu dostosować
program do nowej wersji
narzędzia WinAVR
Proponuję, abyś teraz przeszedł do strony
http://winavr.sourceforge.net i pobrał najnow-
szą wersję WinAVR. W chwili pisania tekstu
była to wersja 20070122.
Po instalacji, pierwszy plik, do którego po-
winniśmy zajrzeć, to WinAVR-user-manual
(wersja txt albo html). Najbardziej interesuje
nas sekcja 1.0 What’s New. Tutaj znajdziemy
najważniejsze informacje o zmianach. Odpo-
wiedni fragment przedstawia ramka obok.
Zmiana w GCC ma dla nas znaczenie od
strony optymalizacji kodu. Składnia C się
przecież nie zmienia, a sam kompilator nie za-
wiera dodatkowych bibliotek.
Wsparcie dla nowych procesorów, które
mają 256kB pamięci, to przede wszystkim
miła niespodzianka. Wymusza jednak kolej-
ną, tym razem ważną dla nas zmianę: zmienia
się format pliku używanego do symulacji pro-
gramu. Jeśli korzystamy z AVRStudio, wy-
maga to posiadania programu w wersji przy-
najmniej 4.13. We wcześniejszej wersji, pod-
czas wczytywania pliku, otrzymamy komuni-
kat o błędzie.
Nowa biblioteka avr-libc to bardzo ważny
element naszego środowiska. Zmiany w tym
miejscu dotyczą funkcji standardowych,
z których cały czas korzystamy. Musimy mieć
więc świadomość, że w ich wywołaniach mo-
gły pojawić się zmiany.
Reszta wypisanych tutaj zmian nie ma dla
nas wielkiego znaczenia. Wiemy teraz, że
szczególną uwagę musimy zwrócić na funk-
cje z biblioteki avr-libc oraz na posiadanie
nowej wersji AVRStudio.
wszystkie zmiany na podstawie dokumenta-
cji. Zwykle więc posługujemy się drogą na
skróty.
Spośród napisanych programów, niewiele
jest takich, które nie pozwolą skompilować się
bez żadnych zmian. Weźmy na warsztat pro-
gram testujący funkcję printf drugi program
z części 7. Problem, który tu występuje, już
przerabialiśmy, jednak jego kod umożliwia
przedstawienie metody postępowania. Ponadto
otrzymałem pytania, dlaczego nie da się pro-
gramu skompilować z nowym kompilatorem.
Sprawdźmy, czy kompilator „przełknie”
program bez żadnych zmian. Przed pierwszą
kompilacją wykonujemy czyszczenie pro-
jektu – zapobiegnie to sytuacji, gdy część pli-
ków kompilowana jest nową wersją kompila-
tora, a część pozostaje w starej wersji. Otrzy-
mamy błędy widoczne na listingu 223.
Gdy klikniemy fioletową linię błędu, zosta-
Najważniejsze zmiany w wersji
20070122
Below is just a sample of what’s new.
Major version change for GCC. Now at version
4.1.1.
Support for the ATmega2560, and ATmega2561 de-
vices.
Change the DWARF2 debug information from 16-bit
addresses to 32-bit addresses. This now allows de-
bugging of code above 64K, including bootloaders
for the ATmega128 and ATmega1281, and debug-
ging of the ATmega2560 and ATmega2561 devices.
This requires a version of AVR Studio that has a new
ELF/DWARF2 parser (> 4.12).
New avr-libc version with new examples, updated
examples, many bugs fixed, and new APIs for Power
Reduction and Clock Prescaler.
New avrdude version with libusb support, which al-
lows connections to the AVR ISP mkII.
New avarice version with libusb support, which al-
lows connections to the AVR JTAG ICE mkII.
New versions for Binutils and SRecord.
Updates to the WinAVR Makefile Template and
MFile.
Listing 223 Błędy podczas kompilacji programu z części 7
Compiling: main.c
avr-gcc -c -mmcu=atmega162 -I. -gdwarf-2 -DF_CPU=8000000UL -Os -funsigned-char
-funsigned-bitfields -fpack-struct -fshort-enums -Wall -Wstrict-prototypes -Wa,
-adhlns=main.lst -std=gnu99 -MD -MP -MF .dep/main.o.d main.c -o main.o
main.c: In function ‘main’:
main.c:28: warning: passing argument 1 of ‘fdevopen’ from incompatible pointer type
main.c:28: warning: passing argument 2 of ‘fdevopen’ from incompatible pointer type
main.c:28: error: too many arguments to function ‘fdevopen’
make.exe: *** [main.o] Error 1
Listing 224 Funkcja fdevopen
#if defined(__STDIO_FDEVOPEN_COMPAT_12)
/*
* Declare prototype for the discontinued version of fdevopen() that
* has been in use up to avr-libc 1.2.x. The new implementation has
* some backwards compatibility with the old version.
*/
extern FILE *fdevopen( int (*__put)( char ), int (*__get)( void ),
int __opts __attribute__((unused)));
#else /* !defined(__STDIO_FDEVOPEN_COMPAT_12) */
/* New prototype for avr-libc 1.4 and above. */
extern FILE *fdevopen( int (*__put)( char , FILE*), int (*__get)(FILE*));
#endif /* defined(__STDIO_FDEVOPEN_COMPAT_12) */
niemy przeniesieni do linii, w której błąd się
pojawił. Nie zmieniajmy jej teraz pochopnie.
Przekonamy się, że błąd tkwi w innym miej-
scu. Po pierwsze, znajdujemy opis funkcji
fdevopen z biblioteki stdio.h.
Informacje znajdziemy zarówno w odpo-
wiednim pliku nagłówkowym, jak i w doku-
mentacji avr-libc. Okazuje się, że funkcja
zmieniła swoją składnię i teraz funkcje rs_put
i rs_get powinny mieć inną formę. Ponadto
usunięty został trzeci, nieużywany parametr.
Linie opisujące funkcje fdevopen, znajdu-
jące się w pliku <stdio. h>, pokazuje listing
224. Widzimy tutaj furtkę, jaką pozostawili
twórcy nowej wersji WinAVR. W naszym pro-
Listing 225 Zmiana w programie z części 7
#define __STDIO_FDEVOPEN_COMPAT_12
#include <stdio.h>
gramie, przed dołączeniem nagłówka stdio.h
umieszczamy linię, jak na listingu 225.
To najprostsze rozwiązanie, aby skompilo-
wać program. Oczywiście lepszym wyjściem
byłoby odpowiednie zmienienie wywołania
fdevopen oraz zmiana funkcji rs_put i rs_get.
Później, gdy wczytamy się w dokumentację,
dojdziemy do wniosku, że inicjacji strumieni
można dokonać, rezygnując w ogóle z pamię-
ciożernej funkcji fdevopen . Teraz zależy nam
jedynie na poznaniu sposobu postępowania.
Dwa podejścia...
Do zagadnienia możemy teraz podejść na dwa
sposoby. Pierwszy, który nazwałbym właści-
wym, to zajrzenie do dokumentacji avr-libc
i przeczytanie o dostępnych funkcjach oraz
zmianach. Zazwyczaj jednak chcemy uzyskać
efekt szybko, ponadto trudno wychwycić
40
Elektronika dla Wszystkich
215235155.028.png 215235155.029.png
Programowanie
Zbierzmy wszystko w punkty:
Postępując według schematu, być może
czasem podpierając się słownikiem, bę-
%s – wskaźnik na łańcuch w pamięci RAM,
wypisanie łańcucha.
%S – wskaźnik na łańcuch w pamięci progra-
mu, wypisanie łańcucha.
Program napiszemy na podstawie naszej
przeglądarki z poprzedniego odcinka. Ponie-
waż nie wypisywaliśmy jeszcze tekstu na na-
szym kolorowym wyświetlaczu, w dodatko-
wej ramce wyjaśniam, w jaki sposób się do te-
go zabierzemy.
Resztę wyjaśnia ramka o otwartej liście ar-
gumentów oraz listingi 229 i 230.
Naszą funkcję piszącą możemy teraz wy-
korzystać, aby na pokazywanym obrazku wy-
pisać dodatkowe informację – dodamy linijkę
do funkcji app_DrawImg listing 231.
Jak widzisz, obsługa tego tworu okazuje
się stosunkowo prosta.
1. Czyszczenie projektu
2. Kompilacja
3. Sprawdzenie błędów i zajrzenie do
dokumentacji albo pliku nagłówkowego,
co się zmieniło w „spornej” funkcji?
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-
czyszczenia i pozwala na powrót z funkcji o zmiennej li-
ście argumentów, tak jakby to była zwyczajna funkcja.
dziesz w stanie samodzielnie obejść pro-
blem przejścia do nowej wersji WinAVR
gdy tylko taki się pojawi.
ABC... C
Otwarta (niepełna) lista argumentów
albo
Zmienna długość linii argumentów
Ograniczenia
Makro va_end powinno być wywoływane wyłącznie po
odczytaniu wszystkich parametrów przesłanych do funk-
cji (va_list musi wskazywać na miejsce za ostatnim argu-
mentem). Niespełnienie tego warunku może spowodo-
wać nieokreślone zachowanie programu.
Wszystkie parametry przekazywane przez zmienną
listę argumentów, są przesyłane z użyciem „starej promo-
cji argumentów języka C”. Promocja ta mówi, że typy
char , short int są automatycznie promowane do typu int.
Typ float jest promowany do typu double. W związku
z tym błędem jest ustawienie char , short int albo float
w miejscu drugiego parametru funkcji va_arg .
Trzeba zdawać sobie sprawę z faktu, że w AVR-GCC
zmienna lista argumentów przesyłana jest przez stos (jest
to jednocześnie najczęstsze rozwiązanie stosowane
w praktyce). Nie ma możliwości umieszczenia takich pa-
rametrów w rejestrach. Sprawia to, że wywołanie tego ty-
pu funkcji zajmuje więcej zasobów niż w przypadku
funkcji o określonej liczbie parametrów.
Dodatkowym ograniczeniem jest też fakt, że kompi-
lator nie jest w stanie pilnować czy parametry przesłane
do funkcji są prawidłowe – należy pilnować tego we wła-
snym zakresie.
Pozostały zakres materiału
Z założonego materiału zostało nam do omó-
wienia kilka przydatnych drobiazgów. Do tej
pory pisaliśmy o tym, jak korzystać z funkcji
o otwartej liście argumentów, takiej jak printf .
Teraz dowiesz się, jak wykorzystać trzy krop-
ki we własnych konstrukcjach.
Później opowiem o kilku rzadziej używa-
nych, ale czasem bardzo przydatnych funkcjach.
Oto właściwa deklaracja funkcji z niepełną listą argu-
mentów:
void funkcja ( char *str,...);
Trzy kropki oznaczają, że w tym miejscu może wy-
stąpić nieokreślona liczba argumentów. Symbol trzech
kropek umieszczamy zawsze jako ostatni argument.
Obsługa
Sztuka polega na tym, aby odszukać w pamięci przesłane
argumenty. W przypadku C służą do tego makra oraz spe-
cjalny typ zmiennej deklarowane w pliku <stdarg.h>.
Przedrostek va_ pochodzi od słowa varargs.
va_list – jest to typ zmiennej wskazującej na aktual-
ną pozycję wliście argumentów nieustalonych.
va_start (va_list, str) – funkcję tę wywołujemy
w celu zainicjowania zmiennej typu va_list. Drugi para-
metr to mnemonik ostatniego argumentu ustalonego –
przed trzema kropkami. Jeśli przejdziemy już przez całą
listę argumentów, ponowny dostęp do pierwszego argu-
mentu jest możliwy po ponownym wywołaniu funkcji.
va_arg (va_list, typ) – powoduje pobranie argumen-
tu podanego typu oraz odpowiednie przesunięcie wskaź-
nika listy argumentów.
Funkcja z otwartą listą
argumentów
Napiszemy teraz funkcję tworzącą na ekranie
formatowane wyjście. Będzie to pewna okro-
jona forma funkcji printf. Jej składnia będzie
identyczna, jednak będziemy przyjmować je-
dynie następujące formaty:
%d – wartość typu int, wypisanie w formie
dziesiętnej.
%c – wartość znakowa, wypisanie znaku.
Przykłady: listing 229 i 230.
Idea rozwiązania
Funkcja rysująca linie znaku:
Na listingu 226 pokazana jest funkcja, która zamienia li-
nię znaku na dane wpisywane bezpośrednio do bufora. Za-
uważ możliwość podania zarówno koloru tła, jak i koloru
znaku. Ponadto, jeśli w parametrze mode
ustawiono flagę LCD_TM_TRANSPA-
RENT, tło będzie przezroczyste (nie będzie
wypisywane).
Funkcja oznaczona jako statyczna, po-
nieważ wzałożeniu nie będzie wywoływana
spoza modułu LCD. Umożliwia to kompila-
torowi optymalizację jej wywołania.
danych. W tym pierwszym przypadku należy ustawić fla-
gę LCD_TM_FLASH. Aby uprościć sobie dalszą pracę,
znak z łańcucha będziemy pobierać za pomocą funkcji
pomocniczej widocznej na listingu 228.
Proste pisanie tekstu na ekranie ko-
lorowym
Pisanie na ekranie kolorowym wykonamy teraz w oparciu
o czcionkę statyczną, z której korzystaliśmy w części 13.
Wykorzystamy dokładnie tę samą tablicę z definicją wy-
glądu znaków. Jedyna różnica jest taka, że o ile poprzed-
nio bajt opisujący jedną linię znaku był wpisywany bezpo-
średnio do bajtu w buforze obrazu, teraz będziemy musie-
li rozłożyć go na pojedyncze piksele – w naszym koloro-
wym wyświetlaczu jeden bajt odpowiada jednemu pikse-
lowi.
Listing 227 Wypisanie znaku
uint8_t lcd_DrawChar(uint8_t x, uint8_t y, char znak,
lcd_pixel charcolor, lcd_pixel bkcolor, uint8_t mode)
{
// Je ś li znak nie mie ś ci si ę w tablicy...
if (znak < ‘ ‘ ||
znak > (( char ) ’ ‘ + ELEMS(lcd_Font)))
znak = ‘#’ ;
// Wysy ł am 5 kolejnych bajtów z tablicy
uint8_t n;
prog_uint8_t* pData = lcd_Font[znak - ‘ ‘ ];
Wypisanie znaku:
Mając funkcję wypisującą linię znaku, dalej
postępujemy już bardzo podobnie jak
przy pisaniu na wyświetlaczu czarno-
-białym. Funkcję wypisującą znak poka-
zuje listing 227. W razie wątpliwości zaj-
rzyj do części 13 listing 227 to przerób-
ka listingu 169.
Listing 226 Wypisanie linii znaku
// wysy ł anie danych
for (n= 0 ; n< 5 ; n++)
{
uint8_t data = pgm_read_byte(pData++);
lcd_DrawCharLine(x, y, data,
charcolor, bkcolor, mode);
++x;
static void lcd_DrawCharLine(uint8_t x,
uint8_t y, uint8_t data,
lcd_pixel charcolor, lcd_pixel bkcolor,
uint8_t mode)
}
return x;
{
uint8_t n;
for (n= 0 ; n< 8 ; n++)
{
Wypisanie całego
łańcucha:
Nie przedstawiam tutaj funkcji
wypisującej cały łańcuch zna-
ków – podobne procedury pisa-
liśmy już wielokrotnie. Nasza
funkcja różni się jednak o tyle,
że może czytać łańcuch z pa-
mięci programu albo pamięci
}
if (data & 0x01 )
lcd_Pixel(x, y, charcolor);
else if (!(mode & LCD_TM_TRANSPARENT))
lcd_Pixel(x, y, bkcolor);
++y;
data >>= 1 ;
Listing 228
Pobieranie znaku z łańcucha umieszczonego w wybranej pamięci
static uint8_t lcd_GetCharFromStr ( char * str , uint8_t mode )
{
}
return ( mode & LCD_TM_FLASH ) ? pgm_read_byte ( str ) : * str ;
}
}
Elektronika dla Wszystkich
41
215235155.030.png 215235155.031.png
Programowanie
Oszczędzanie energii
Do tej pory zupełnie nie przejmowaliśmy się
zużyciem energii przez układ. Nie ma w tym
nic dziwnego – nasza płytka nie została utwo-
rzona do wielkiego oszczędzania mocy. Jed-
nak pomyślmy teraz, co zrobić, gdy procesor
kręci się wpętli, czekając na jakieś zdarzenie?
Możemy przecież wprowadzić go w jeden ze
stanów obniżonego poboru mocy. Pokażę Ci
za moment, że dodając do naszej przeglądarki
zaledwie kilka linii, można uzyskać zaskaku-
jące oszczędności.
Wynik pomiaru prądu, pobieranego przez
układ, w przypadku wyświetlania obrazka,
przy odłączonych wszelkich przewodach
oprócz zasilającego, pokazuje fotografia 12.
Napięcie zasilania ograniczono do 4,1V, aby
zminimalizować prąd płynący przez zabez-
pieczającą diodę Zenera.
W naszym programie są dwa miejsca,
gdzie procesor czeka na jakieś zdarzenia. Oba
znajdują się w pliku system.c . Są to funkcje
system_delayMake oraz system_msgWaitFor .
Co bardzo ważne, zdarzenie, na jakie czekają
funkcje, będzie ustawione w obsłudze prze-
rwania. Możemy więc spokojnie przełączyć
procesor w tryb IDLE, w którym pobiera on
2x mniej prądu.
W obu wspomnianych funkcjach
znajdują się pętle z pustym ciałem.
W obu przypadkach w ciało pętli
wpiszemy zawartość listingu 232.
Konieczne będzie dodanie pliku na-
główkowego <avr/sleep.h>.
Już taka prosta modyfikacja, któ-
ra zajęła nam może minutę czasu,
pozwoli na widoczne wydłużenie
czasu pracy urządzenia na bateriach.
Fotografia 13 pokazuje nowy wy-
nik pomiaru zużycia prądu.
Ale może taka oszczędność nam
nie wystarczy?
Co prawda nie dotyczy to już sa-
mego WinAVR, a raczej metodolo-
gii postępowania... sporo mocy po-
biera aktywna cały czas, zewnętrzna
pamięć RAM. A przecież przez
większość czasu w ogóle się do niej
nie odwołujemy. Dla prostoty jednak
ustawiliśmy na sztywno stan ‘0’ na
wyprowadzeniu A15, połączonym
z wejściem /CE pamięci. Dodajmy
do pliku harddef. h dwa proste ma-
kra, widoczne na listingu 233.
Listing 229 Funkcja lcd_Printf
// Zwraca: pozycj ę x za ostatnio napisanym znakiem.
uint8_t lcd_Printf ( uint8_t x , uint8_t y , char * str ,
lcd_pixel charcolor , lcd_pixel bkcolor , uint8_t mode , ...)
// Inicjacja
va_list arg ;
( arg , mode );
// P ę tla wypisywania ł a ń cucha
for (;; str ++)
{
char znak ;
// Wczytanie danej ł a ń cucha
znak = lcd_GetCharFromStr ( str , mode );
// Zako ń czenie funkcji, je ś li koniec ł a ń cucha
if ( znak == 0 )
break ;
// Obs ł uga znaków specjalnych
if ( znak == ‘%’ )
{
(Listing 230 !!!)
}
// Wypisanie zwyczajnego znaku
else
{
// Wypisanie znaku
x = lcd_DrawChar ( x , y , znak , charcolor , bkcolor , mode );
}
// Wypisanie przerwy
lcd_DrawCharLine ( x , y , 0 , charcolor , bkcolor , mode );
++ x ;
}
// Wyczyszczenie wszystkiego, co wymaga wyczyszczenia
( arg );
return x ;
va_end
}
Listing 230 Pobieranie argumentów w funkcji lcd_Printf
Listing 231 Wypisanie informacji o obrazku
(...)
lcd_Printf( 0 , 0 , PSTR( „Obrazek %d z %d“ ),
0x1c , 0 , LCD_TM_FLASH, index+ 1 , imgl_GetSize());
// Odmalowanie ekranu
lcd_Update();
znak = lcd_GetCharFromStr(++str, mode);
switch (znak)
{
case ‘d’ :
{
int d = (arg, int );
char bufor[ 6 ];
va_arg
x = lcd_DrawText(x, y,
itoa(d, bufor, 10 ), charcolor,
bkcolor, mode & ~LCD_TM_FLASH);
Listing 232 Usypianie procesora w czasie czekania na przerwanie
while (...)
{
}
break ;
case ‘c’ :
// Uwaga - dajemy int nie char!!!
znak = (arg, int );
x = lcd_DrawChar(x, y, znak,
charcolor, bkcolor, mode);
break ;
case ‘s’ :
case ‘S’ :
{
}
set_sleep_mode(SLEEP_MODE_IDLE);
sleep_mode();
va_arg
Listing 233 Makra włączające i wyłączające pamięć RAM
#define ram_Enable() PORTC &= ~(1<<7);
#define ram_Disable() PORTC |= (1<<7);
char *str;
uint8_t mode_s = mode & ~LCD_TM_FLASH;
if (znak == ‘S’ )
mode_s = mode | LCD_TM_FLASH;
str = (arg, char *);
x = lcd_DrawText(x, y, str,
charcolor, bkcolor, mode_s);
Listing 234 Wyłączanie pamięci w funkcji app_run
va_arg
for (;;)
{
// Aktywacja interpretera komend
system_enableUARTCommand();
}
break ;
case ‘%’ :
x = lcd_DrawChar(x, y, znak,
charcolor, bkcolor, mode);
break ;
uint8_t msg;
ram_Disable();
msg = system_msgWaitFor();
ram_Enable();
(...)
}
Fot. 12 Pobór prądu, oryginalnie
Fot. 13 Pobór prądu, usypianie
procesora, gdy nie pracuje
Fot. 14 Pobór prądu, usypianie proce-
sora oraz wyłączanie pamięci RAM
42
Elektronika dla Wszystkich
{
va_start
215235155.001.png 215235155.002.png 215235155.003.png 215235155.004.png 215235155.005.png
Nie będziemy teraz zastanawiać się spe-
cjalnie, gdzie dostęp do pamięci jest ko-
nieczny, a gdzie nie. Założymy, że pamięć
RAM jest domyślnie uruchomiona w funk-
cji main – tak jak to było do tej pory. Wyłą-
czać ją będziemy na czas czekania na zda-
rzenie w module aplikacji. Zmienimy więc
fragment funkcji app_run widoczny na
listingu 234.
Obie opisane zmiany, chociaż bardzo
proste, umożliwiły zejście z poborem prądu
do wartości widocznej na fotografii 14. My-
ślę, że zestawienie fotografii mówi samo za
siebie, że warto czasem, po napisaniu pro-
gramu, przyjrzeć się, gdzie można oszczę-
dzić trochę mocy. Tym bardziej, jeśli spore
oszczędności uzyskamy praktycznie za
darmo.
na pozostawieniu zawartości kluczowych dla
aplikacji zmiennych – możemy sprawdzić je-
dynie, czy ich wartości są prawidłowe (przy-
pomnij sobie, co pisaliśmy o sekcji .noinit ).
Wróćmy do naszego programu generowa-
nia płomienia. Zabezpieczmy jego działanie
watchdogiem. Prosta kontynuacja pracy bę-
dzie polegała na tym, że pamięć obrazu nie
będzie czyszczona w przypadku wystąpienia
„awaryjnego” zerowania.
Sprawdźmy, jaki czas powinniśmy dać so-
bie na zerowanie watchdoga. Eksperymental-
nie, za pomocą symulacji w AVRStudio, mo-
żemy sprawdzić, że przy 8MHz zegarze czasy
wykonania poszczególnych fragmentów kodu
kształtują się następująco:
Od uruchomienia do zakończenia inicjacji:
67ms/67ms (sprzętowy SPI/programowy SPI).
Pojedynczy przebieg pętli głównej:
146ms/187ms.
Ponadto, co warto wiedzieć:
Od startu procesora do pierwszej instrukcji
w main (): 395us. Tutaj uwaga – czas ten za-
leży także od tego, ile zmiennych globalnych
i statycznych należy wcześniej przygotować
(wyzerować albo nadać im wartość początko-
wą). W dużych aplikacjach, czas ten może
znacząco się zwiększyć.
Od startu procesora do pierwszej instrukcji
w .init3: Niecałe 2us.
Widzimy, że minimalny, sensowny okres
WDT wynosi w naszym przypadku 250ms.
Można oczywiście go jeszcze skrócić, zerując
watchdog w większej ilości miejsc niż tylko raz
wpętli głównej, liczbie w naszym przypadku
takie podejście nie wydaje się uzasadnione. Dla
bezpieczeństwa nawet wybierzmy konfigura-
cję o 2x dłuższym czasie niż nasze minimum.
Listing 235 pokazuje wczesną konfigura-
cję watchdoga. Nie ma znaczenia, czy timer
Timer watchdog – bezpie-
czeństwo aplikacji
Poza oszczędzaniem mocy, powinniśmy
zwrócić także uwagę na bezpieczeństwo pra-
cy naszego programu. Trzeba zdawać sobie
z tego sprawę, że każdy, nawet najlepiej napi-
sany program, może ulec zawieszeniu. Nieko-
niecznie musi być to związane z błędem pro-
gramu – czasem wystarczy zakłócenie na li-
niach zasilania albo silny impuls EM.
Jeśli urządzenie się zawiesi, możemy za-
działać tak, aby użytkownik tego nie odczuł al-
bo przynajmniej zdarzenie takie nie było zbyt
uciążliwe (na przykład wyjście do menu głów-
nego, ale zachowanie właściwego odliczania
czasu i działania ustawionych alarmów).
W tym przypadku pomoże nam watchdog ti-
mer i odpowiednia reakcja na występujące
z niego zerowanie. Zwykle będzie to polegało
ABC... GCC
SLEEP_MODE_PWR_SAVE
SLEEP_MODE_STANDBY
Dostępne funkcje:
set_sleep_mode (mode) – ustawia tryb uśpienia. Ustawie-
nia wystarczy dokonać tylko raz – każde późniejsze wy-
wołanie funkcji sleep_cpu () albo sleep_mode () spowo-
duje przejście do wybranego trybu.
sleep_cpu () – powoduje przejście procesora w wybrany
tryb uśpienia. W praktyce wykonuje instrukcję asemblera
„sleep”. Przed wywołaniem tej funkcji bit zezwalający na
uśpienie powinien być ustawiony. Zaleca się jego wyzero-
wanie zaraz po wyjściu ze stanu uśpienia.
sleep_enable () – ustawia bit zezwalający na uśpienie.
sleep_disable () – zeruje bit zezwalający na uśpienie.
sleep_mode () – powoduje przejście procesora w wybra-
ny tryb uśpienia. W odróżnieniu od sleep_cpu, sam prze-
prowadza aktywację uśpienia oraz późniejsze jego wyłą-
czanie.
Sterowanie trybami obniżonego po-
boru mocy
W przypadku procesorów AVR powstało kilka różnych
zestawów rejestrów służących do przełączania ich w tryb
obniżonego poboru mocy. Wszystko zależy od modelu.
Jednak korzystając z WinAVR nie musimy się tym przej-
mować – wszystkie wersje obsługiwane są przez iden-
tyczne funkcje.
Aby wymienione niżej funkcje były dostępne, ko-
nieczne jest dołączenie do programu pliku <avr/sleep.h>.
Definiowane stałe oznaczające poszczególne tryby:
SLEEP_MODE_ADC
SLEEP_MODE_EXT_STANDBY
SLEEP_MODE_IDLE
SLEEP_MODE_PWR_DOWN
Przykłady: listing 232.
Szczegóły techniczne
Pobór prądu przy 5V, 8MHz
Tryb
Pobór prądu
Skrócony opis
Active
16mA
Normalny tryb pracy
Stany obniżonego poboru mocy
procesorów AVR
Idle
8mA
CPU i pamięć FLASH zatrzymane, reszta peryferii pracuje
Power Down
<2 µ A
CPU i wszystkie peryferie zatrzymane. Rezonator nie pracuje.
Możliwe wyjście tylko przez reset albo przerwanie asynchroniczne.
Procesory AVR mają spore możliwości oszczędzania
energii. Sam procesor może być przełączony w kilka try-
bów pracy, a najbardziej energochłonne peryferia mogą
być wyłączone. Nie będziemy zajmować się dokładnym
opisem sposobu przełączenia w tryb energooszczędny, po-
nieważ będziemy korzystać z wygodnych funkcji avr-libc .
Ważna natomiast jest wiedza na temat tego co w danym
stanie procesor może i ile potrzebuje prądu. Na drugie py-
tanie odpowiadają tabelki w ramce. Na pierwsze odpowia-
da opis niżej:
Idle – Tryb pracy, w którym zatrzymany jest CPU
oraz pamięć programu. Działają natomiast wszystkie pe-
ryferie i procesor może być obudzony przez dowolne
przerwanie (także gotowości pamięci FLASH do instruk-
cji SPM). Pobór prądu w tym trybie jest dwa razy mniej-
szy niż w trybie pełnej aktywności.
Power Down – Najbardziej oszczędny tryb pracy.
Wydawałoby się, że w tym trybie procesor praktycznie nic
nie może. Nie jest to jednak do końca prawda. Procesor
może być wybudzony za pomocą dowolnego źródła rese-
tu, jeśli tylko jest ono aktywne (Reset zewnętrzny, WDT,
BOD). Ponadto można obudzić go przerwaniem wyzwala-
nym poziomem na wejściach INT0 i INT1. Jeśli urucho-
miliśmy przerwanie asynchroniczne INT2, także to wej-
Power Save
<25 µ A
Jak Power Save, ale Timer2 pracuje, jeśli taktowany asynchronicznie
Standby
<180 µ A
Jak Power Down, ale rezonator pracuje.
Skraca to czas reakcji na zerowanie albo przerwanie.
Extended Standby
<200 µ A
Jak Standby, ale Timer2 pracuje, jeśli taktowany asynchronicznie
ście będzie aktywne. I co bardzo ciekawe, włączone wej-
ścia objęte przerwaniem na zmianę stanu też mogą obu-
dzić procesor.
W każdym przypadku jednak wymagane jest, aby zda-
rzenie (reset, poziom niski, zmiana stanu) trwało przez pe-
wien czas. To czyni procesor odporniejszym na zakłócenia
– wejścia są próbkowane dwa razy w takt oscylatora
WDT, którego nominalny okres wynosi 1µs.
Od chwili wykrycia zdarzenia do uruchomienia proce-
sora mija pewien czas, zależny od bitów konfiguracji, nie-
zbędny do uruchomienia i stabilizacji drgań oscylatora.
Power Save – Tryb Power Down rozwinięty o możli-
wość pracy w trybie asynchronicznym zegara – licznika 2.
Procesor może być budzony dodatkowo przez przerwania
pochodzące z tego elementu.
Standby – Tryb identyczny z trybem Power Down,
z tym wyjątkiem, że oscylator główny cały czas pracuje.
Dzięki temu czas, jaki mija od wykrycia zdarzenia do pod-
jęcia pracy przez procesor, wynosi sześć taktów zegara.
Dodatkowy pobór mocy przez włączone peryferia
Element
Pobór prądu
BOD
ok. 25 µ A
TOSC 32kHz (Timer2)
ok. 23 µ A
WDT
ok. 15 µ A
Komparator analogowy
ok. 60 µ A
Extended Standby – Tryb Standby rozszerzony
omożliwość pracy w trybie asynchronicznym zegara
– licznika 2.
ADC Noise Reduction – Tryb dostępny w proceso-
rach wyposażonych we wbudowany przetwornik AC.
Umożliwia on osiągnięcie dokładniejszych pomiarów,
przez zmniejszenie generowanych zakłóceń. Po wejściu
w tryb, przetwarzanie rozpoczyna się automatycznie.
Poza zakończeniem przetwarzania, inne przerwania
(ale niekoniecznie wszystkie) mogą obudzić procesor.
Szczegółowe informacje wymagają sprawdzenia doku-
mentacji wybranego procesora.
Elektronika dla Wszystkich
43
215235155.006.png 215235155.007.png
Programowanie
ten włączymy na sztywno z poziomu progra-
matora, czy będziemy zezwalali na jego za-
trzymanie. Tak czy inaczej dobrze jest prze-
prowadzić jego inicjację możliwie wcześnie.
jeszcze wyświetlacz zostanie uruchomiony,
ktoś w łazience włącza starą suszarkę. Gene-
ruje to impuls na liniach zasilania i procesor
wpada w martwą pętlę. Po 0,5s zostaje on wy-
zerowany przez watchdoga. Ponownie rozpo-
czyna się inicjacja, która tym razem kończy
się pomyślnie. Dochodzimy do decyzji, czy
należy czyścić bufor obrazu. W rejestrze
MCUCSR ustawiony jest bit WDFR – wyda-
wałoby się więc, że ponieważ wystąpiło awa-
ryjne zerowanie, nie powinniśmy
wykonywać tej funkcji. Jednak
tak naprawdę bufor od czasu włą-
czenia zasilania nie był jeszcze
ABC... GCC
ABC... C
Listing 235 Wczesna inicjacja WatchDog’a
Dyrektywa #undef
Poniższa linia:
#undef EXTRF
powoduje unieważnienie wcześniejszej definicji stałej /
makra EXTRF. Unieważnienie obowiązuje od chwili
wystąpienia dyrektywy. Dane makro lub stała są ważne
wcześniej. Po dyrektywie #undef, można nadać określo-
nemu mnemonikowi nową wartość przez ponowną defi-
nicję.
void before_main( void )
{
// Jak najwcze ś niejsze ustawienie wdt
wdt_enable(WDTO_500MS);
(...)
Listing 236 Warunkowa inicjacja części zmiennych (czyszczenie bufora)
if ( MCUCSR & ( 1 << PORF | 1 << EXTRF | 1 << BORF | 1 << JTRF ))
lcd_Cls ( 0 );
MCUCSR = 0 ;
Przykład: listing 237
Spójrz teraz na listing 236. Nie jest to po-
czątkowa faza inicjacji – pojawia się już po
uruchomieniu samego wyświetlacza i wczyta-
niu palety. Na tym listingu na dwie rzeczy po-
winny zwrócić uwagę:
1. Wcale nie zerujemy rejestru MCUCSR
bardzo wcześnie. Do czasu zakończenia ini-
cjacji wyświetlacza mija czas liczony już
w dziesiątkach milisekund.
2. Czyszczenie ekranu nie jest uzależnione
od flagi zerowania z WDT, lecz od tego, czy
nie są ustawione flagi pozostałe.
Wyjaśnienie zacznę od końca:
Załóżmy taką teoretyczną sytuację: włą-
czamy zasilanie. W tej chwili zaczyna się
inicjacja. I dokładnie w tym momencie, zanim
ABC... GCC
Obsługa watchdoga
Do obsługi WDT służą w sumie trzy makra:
wdt_reset () – zerowanie licznika. Makro jest zamie-
niane na pojedynczą instrukcję asemblera: wdr. Jeśli ti-
mer pracuje, ta instrukcja powinna być wywoływana
okresowo, aby nie dopuścić do zerowania procesora. Do-
brym miejscem na jej umieszczenie jest pętla główna
i wszystkie miejsca, gdzie procesor może dłużej „kręcić
się” w pętli.
wdt_disable () – wyłączenie WDT, jeśli tylko jest to
możliwe (jeśli zezwala na to wybrany tryb bezpieczeń-
stwa procesora). Automatycznie wykonuje sekwencję
wymaganą do zmiany rejestru WDTCR oraz blokuje
przerwania na czas wprowadzanej zmiany.
wdt_enable (timeout) – powoduje włączenie WDT al-
bo, jeśli licznik już pracuje, zmianę czasu jego opóźnie-
nia. Podobnie jak wdt_disable, także generuje odpowied-
nią sekwencję i chwilowo blokuje przerwania. Ponadto
zawiera także instrukcję wdr.
Przykłady: listing 235 i 236.
W bibliotece avr-libc specjalne funkcje do obsługi timera
WatchDog wymagają dołączenia pliku <avr/wdt. h> .
Definiowane stałe oznaczające poszczególne opóź-
nienia:
WDTO_15MS
WDTO_30MS
WDTO_60MS
WDTO_120MS
WDTO_250MS
WDTO_500MS
WDTO_1S
WDTO_2S
WDTO_4S – tylko niektóre procesory
WDTO_8S – tylko niektóre procesory
Szczegóły techniczne
stępującą sekwencję:
1. W pojedynczej operacji
wpisz 1 do bitów WDCE i WDE
rejestru WDTCR (niezależnie od
tego, czy WDE jest aktualnie usta-
wiony, czy wyzerowany).
2. W czasie czterech cykli
wpisz wybrane ustawienia. Tym
razem bit WDCE powinien być
wyzerowany.
Sekwencja zmiany WDTCR wymaga odpowiednio
krótkiego czasu między poszczególnymi instrukcjami.
Ważne jest więc by w czasie jej wykonywania żadne prze-
rwanie nie rozpoczęło wykonywania swojego kodu. Naj-
bezpieczniej jest chwilowo wyłączyć globalne zezwolenie
na przerwania.
Flagi źródła zerowania
Przydatność timera WDT została znacznie zwiększona
przez specjalny rejestr MCUCSR, który zawiera informa-
cję o źródle zerowania.
Rejestr ten ma to do siebie, że zerowanie przy włącze-
niu zasilania (flaga PORF) powoduje wyzerowanie pozo-
stałych flag. Flaga PORF z kolei może być wyzerowana
tylko przez wpisanie do niej zera.
Teraz, co ważne, jeśli nie wyzeru-
jemy naszego rejestru i gdy po
włączeniu zasilania wystąpi zero-
wanie z zewnętrznego wyprowa-
dzenia, a później, na przykład
z WDT – wszystkie trzy flagi
(PORF, EXTRF, WDRF) będą
ustawione. Gdy już odpowiednio
zareagujemy na zawartość flag, re-
jestr MCUCSR powinniśmy pro-
gramowo wyzerować. Wiele źró-
deł zaleca zrobić to jak najszyb-
ciej, ale przekonamy się, że nie za-
wsze jest to dobre wyjście.
watchdog timer
Nie ma wielkich różnic w obsłudze timera WDT w po-
szczególnych procesorach, chociaż pewne występują. Po
szczegółowe informacje należy zawsze zajrzeć do doku-
mentacji danego procesora. Dalej skupiam się na proceso-
rze atmega162.
Timer watchdog jest taktowany z oddzielnego genera-
tora o częstotliwości 1MHz. Nie należy jednak liczyć na
dużą stabilność jej wartości. Jeśli pozwolimy doliczyć te-
mu timerowi do końca, procesor zostanie wyzerowany.
Możliwe do ustawienia czasy, między ostatnim zerowa-
niem timera a zerowaniem procesora, pokazuje tabela
w ramce. Duża rozpiętość czasów umożliwia dostosowa-
nie działania WDT do wymagań aplikacji.
W procesorze Atmega162 watchdog może pracować
w 3 poziomach bezpieczeństwa, zależnych od ustawień
bezpieczników w czasie programowania:
Safety Level 0 – WDT wyłączony po zerowaniu pro-
cesora. Jego włączenie i zmiana okresu są możliwe, bez
konieczności wykonywania niżej opisanej sekwencji
zmiany WDTCR. Wyłączenie wymaga odpowiedniej se-
kwencji.
Safety Level 1 – Podobnie jak poziom 0, ale wymaga
sekwencji także do zmiany okresu pracy WDT.
Safety Level 2 – WDT domyślnie włączony po zero-
waniu procesora. Timer ten nie może być programowo
wyłączony, a jedynie okresowo zerowany. Bit WDE pod-
czas odczytu przyjmuje zawsze wartość 1.
Okresowego zerowania watchdoga dokonujemy jedną
instrukcją asemblera: wdr.
Sekwencja zmiany rejestru WDTCR
W poziomach bezpieczeństwa, które tego wymagają, aby
zmienić zawartość rejestru WDTCR, należy wykonać na-
WDP2..0
Liczba cykli
Przybliżony czas
3V
5V
0
16K
17ms
16ms
1
32K
34ms
33ms
2
64K
69ms
65ms
3
128K
0,14s
0,13s
4
256K
0,27s
0,26s
5
512K
0,55s
0,52s
6
1024K
1,1s
1,0s
7
2048K
2,2s
2,1s
44
Elektronika dla Wszystkich
215235155.008.png 215235155.009.png 215235155.010.png 215235155.011.png 215235155.012.png 215235155.013.png 215235155.014.png 215235155.015.png 215235155.016.png 215235155.017.png 215235155.018.png 215235155.019.png 215235155.020.png 215235155.021.png 215235155.022.png 215235155.023.png 215235155.024.png 215235155.025.png 215235155.026.png 215235155.027.png
Zgłoś jeśli naruszono regulamin