Programowanie
ABC... C static, volatile
Oprócz tego, że zmienna może być na przykład typu int czy char, może być jednocześnie zmienną, typu static lub voiatile (lub const). Określenie pierwsze odnosi się do tego, ile miejsca zmienna zajmuje w pamięci, jakie jest jej przeznaczenie - czy będzie to przechowywanie liczb, tablicy, łańcucha... Określenie drugie sprawia, że zmienna jest w pewien szczególny sposób traktowana. Aby wymusić to szczególne traktowanie, należy jedno z przedstawionych słów kluczowych umieścić na początku deklaracji zmiennej. Przykłady widać na listingu 15 w punktach O i © . Poniżej przedstawiam działanie poszczególnych typów.
static
Typ static może być łączony ze zmienną lokalną, globalną lub nawet funkcją. Dwa ostatnie przypadki mają nieco odmienne działanie od pierwszego. Aktualnie zajmiemy się jedynie zmiennymi lokalnymi.
Zmienne lokalne mają to do siebie, że ich zawartość jest bezpowrotnie tracona po zakończeniu działania funkcji. Czasami chcemy jednak stworzyć zmienną, która musi zachować swoją wartość z poprzedniego wywołania. Z różnych powodów niekiedy wygodnie jest, aby nie była to zmienna globalna - chcemy mieć pewność, że żadna inna funkcja nie będzie miała do niej dostępu. W takim przypadku pomoże nam napisanie słowa kluczowego static przed nazwą zmiennej. Zmienna statyczna tworzy w pamięci kopię swojej wartości. Jest wczytywana zawsze na początku funkcji, a zapisywana z powrotem, gdy funkcja się kończy. Jeśli nadasz takiej zmiennej wartość początkową zostanie ona zainicjowana tylko raz przy starcie całego programu.
Typowy przykład zastosowania widać na listingu 15 w punkcie O .
volatile
Konieczność zastosowania zmiennej typu vola-tile pojawia się wszędzie tam, gdzie dana zmienna może zmienić się w każdej chwili, na przykład za pomocą przerwania. Słowo volatile przed zmienną zakazuje kompilatorowi optymalizacji dostępu do niej. Zobacz na wykorzystanie zmiennej g_bDelav na listingu 15 w punktach O, © i ©. Gdy zostanie wpisana do niej jakaś wartość, w przerwaniu wyświetlacza będzie ona stopniowo zmniejszana do zera. Gdyby była to zwyczajna zmienna, program zawiesiłby się w pętli czekającej na wyzerowanie jej wartości. Jest to związane z tym, że normalną zmienną kompilator przed użyciem załaduje do rejestru (do którego dostęp jest dużo szybszy i wymaga mniejszej ilości kodu). Tak więc nasza pętla while nie dostałaby się do zmiennej g_bDelay umieszczonej w pamięci, lecz do jej kopii umieszczonej w rejestrze. Przerwanie zmniejsza natomiast zmienną umieszczoną w pamięci danych i nie ma nawet dostępu do wspomnianej kopii.
obsługi przycisku.
yclskCuint8 t maska, VOid(*proc)(VOid)) & maska))
anie drgań styków / = 6; //50ms _bDelay != 0) O PIN & maska))
O;
elay = 120; //ls
f(g_bBelay=0)
g_bD<?lay = 6; //50ms proc();
TeCiCsMPlH & maska));
temat sprawdzania stanu wyprowadzenia portu umieszczone są w ramce „ABC... C -odczyt portów”.
Pojawienie się stanu niskiego (logiczne 0) na odpowiedniej pozycji w rejestrze SWP1N (porównaj z definicją wyprowadzeń, na początku pliku) jest sygnałem, że przycisk został naciśnięty. W punkcie © odbywa się odczekanie kilkudziesięciu milisekund oraz ponowne badanie portu. Jest to typowy sposób radzenia sobie z drganiami styków. Jeśli drugi raz stwierdzono niski stan portu, wartość zmiennej licznikowej jest zwiększana, do zmiennej g_bDelay ładowana jest wartość odpowiadająca opóźnieniu rozpoczęcia automatycznego powtarzania naciśnięcia klawisza i rozpoczyna się pętla trwająca tak długo, aż klawisz zostanie puszczony. Zauważ, że nic złego się nie stanie, jeśli między zwiększeniem zawartości zmiennej g_Licznik a rozpoczęciem wspomnianej pętli przycisk zostanie puszczony. Program wyjdzie z pętli przycisku, a nowa wartość zmiennej zostanie wyświetlona w pętli głów-
Gdy zmienna g_bDelay dojdzie do wartości 0, wartość licznika ponownie jest zwiększana. Teraz jednak opóźnienie będzie znacz-
ABC... C odczyt portów
Standardowo w mikrokontrolerach AVR rzeczywisty stan wyprowadzeń danego portu jest dostępny w rejestrze o nazwie PINx (x = duża litera portu). Odczyt z tego właśnie rejestru jest jednoznaczny ze sprawdzeniem fizycznego stanu odpowiedniego portu. Często będziemy chcieli sprawdzić nie tyle cały port, ile tylko jedno wyprowadzenie. Znów podobnie jak to nie krótsze, co sprawi, że rozpocznie się szybka zmiana wartości.
Proponowane rozwiązanie działa prawidłowo, jednak w praktyce ma kilka wad, które za chwilę postaramy się wyeliminować.
Sprawa, która jako pierwsza rzuca się w oczy, to fakt, że mamy bardzo podobne procedury, które praktycznie się powtarzają.
Można spróbować połączyć je w jedną funkcję.
Drugim nieefektywnym elementem jest ciągłe wywoływanie funkcji Wy-swietlDEC. Co prawda procesor i tak nie ma z tym wiele pracy i użytkownik praktycznie tego nie zauważa, jednak nie zawsze możemy zagwarantować, że wyświetlanie będzie tak prostą funkcją. Elegancko byłoby wywoływać funkcję wyświetlania tylko miało miejsce ze zmianą stanu pojedynczego wyprowadzenia C nic posiada instrukcji bitowych. Zauważmy jednak, że wyrażenie:
PIND & I«?
będzie miało wartość 0 tylko wtedy, jeśli wyprowadzenie 7 portu D jest wyzerowane. Dla wszelkich instrukcji wykorzystujących warunki 0 oznacza fałsz, każda inna wartość prawdę. Możemy wykorzystać to do testowania, czy na wyprowadzeniu jest stan I: if(PIND & 1«7) (...)
Jest stan 0: wtedy, gdy wartość zmiennej g_Licznik ulegnie zmianie.
Utworzymy funkcję obsługi przycisków podobną do BASCOM-owego DEBOUNCE. Podobną - ponieważ nie aż tak uniwersalną, ale za to dającą możliwość automatycznego powtarzania przytrzymanego klawisza. Aby,
if(!(PIND & 1«7)) (...)
Podobnie możemy czekać, dopóki na porcie istnieje stan 1 lub 0: while(piND & 1«7) {} while(!(piND & 1«7)) {}
Co bardzo ważne, wszystkie przedstawione tutaj rozwiązania, pozornie wymagające wielu obliczeń, zostaną zamienione na pojedyncze instrukcje asemblera:
SBIS skok, jeśli bit w rejestrze IO ustawiony; SBIC - skok, jeśli bit w rejestrze IO wyzerowany.
Elektronika dla Wszystkich Wrzesień 2005 45