Programowanie
Funkcje statlc i inllne
Funkcja statyczna
Umieszczenie przed nazwą jiunkcji słowa kluczowego statlc powoduje utworzenie tak zwanej funkcji statycznej. Nie ma to wiele wspólnego z poznanymi już statycznymi zmiennymi lokalnymi. Tworząc funkcją statyczną, informujemy kompilator, że będziemy korzystać z niej tylko w bieżącym module - co praktycznie oznacza bieżący plik źródłowy. Informacja o danej funkcji nie zostanie umieszczona przez kompilator w pliku obiektowym (*.o), przez co linkier nie będzie miał do takiej funkcji dostępu. Sam kompilator uzyskuje za to możliwość lepszej optymalizacji kodu. W skrajnym przypadku, jeśli funkcja nie zostanie wykorzystana, nic znajdzie się ona w kodzie wynikowym programu (jednak zostanie w tym przypadku wygenerowane ostrzeżenie).
Funkcji static użyjemy wszędzie tam, gdzie pewnych elementów modułu nie chcemy udostępniać na zewnątrz. W naszym module wyświetlacza można by
zastosować to do wszystkich funkcji niskiego pozio-
Funkcju rozwijana w miejscu wywołania
Pod tą niezwykle brzmiącą nazwą kryje się mechanizm zbliżony do makr. Ma on jednak kilka zalet. Przede wszystkim, wygodniej się pisze funkcje niż makro. Drugą sprawą jest to, że kompilator może zbadać, czy bardziej optymalne jest rozwinięcie funkcji na wzór makra, czy też jej zwykłe wywołanie.
Dodatkowo jeśli tworzymy funkcję, kompilator pilnuje, czy nasze argumenty są prawidłowe i jeśli to konieczne, dokonuje przekształcenia ich typów. Zauważmy, że z jednej strony zmniejsza to możliwość popełnienia błędu, z drugiej jednak sprawia, że w niektórych zastosowaniach sprawdzają się tylko makra. Weźmy jako przykład makro obliczające ilość elementów w tablicy: #define ELEMS(p)\
(sizeof (p)/s1zeof (p[0])) Twór taki zupełnie nie ma sensu, jeśli p miałoby określony typ - tak jak ma to miejsce w funkcji. Nale
ży zdawać sobie sprawę także, że nawet tak banalne makro, jak obliczenie sumy dwóch argumentów: #define DODA](a, b) (a+b) zachowa się inaczej jako funkcja inlinc: makro operuje na argumentach o oryginalnych typach; Funkcja inline przekształci je najpierw do typu identycznego ze swoimi argumentami.
statlc inline - czemu?
Jeśli chcesz zastosować funkcję inline, spotka Cię jeszcze jedna niespodzianka. Kompilator musi założyć, że poza jej wykorzystaniem w bieżącym pliku, mogą pojawić się zewnętrzne do niej odwołania. Jeśli nie zaznaczysz funkcji inline jako statycznej, oprócz tego, że jej kod będzie rozwinięty w każdym miejscu wywołania, dodatkowo zostanie w pamięci utworzona jej „zwykła" wersja. W naszym przypadku byłoby to marnowanie zasobów - słówko static rozwiązuje ten problem.
Dzięki optymalizacji kodu, funkcja static inllne składająca się tylko z rozkazu powrotu (return) nic zostanie w ogóle umieszczona w kodzie.
Na tym etapie pracy mamy gotowe wszystkie potrzebne nam moduły. Pozostaje połączyć je w działający program. Ponieważ zależy nam na szybkim uruchomieniu programu, teraz wszystkie niezbędne funkcje wywołamy z poziomu funkcji main - tylko po to, aby wreszcie coś się zaczęło dziać. Utwórz plik main.c.
Listing 46 - plik main.c
#inc1ude <avr\io.h> #inc1ude <stdio.h> łlnclude <inttypes.h> #1nclude <avr\pgmspace.h>
łinclude „harddef.h,, ♦tnclude ..delay.h„ #include „makra.h„ #1nclude „12c.h„
#inctude „lcd.h,,
// Inicjacja
PORTO - 1«I2C_SDA 1 i«l2C_SCL; DORD = 1«I2C_SCL;
DDRB - 1«LCD_E | 1«LCD_RS | 0x0F«l_CD_D4; łcCLinitO;
II Koniec Inicjacji
// Tekst informacyjny w górnej li 1cd_str_PC(prog_char *)PSTR C „Dane z 10:,,)) i lcd_command(LCDC_DDA 1 64);
II bajt adresowy, i2c_send(Cx'K));
// bajt kontrolny
i2c_send{OxOO);
12c_stopO;
zapis
// Pobieranie danych
for(;;j
ł
lcd_command(LCDC_DDA | 64);
// Odczyt danych
i2c_start() i
// bajt adresowy, odczyt i2c_sendC0x9l);
// Pobranie i wyświetlenie danej lcd_dec(i 2c_getĆi2c_NACK));
// Przysłonięcie reszty napisu
1cd_str_PC(prog_char*)PSTR(, „));
i2c_stopO;
return 0;
Powinien pojawić się listing 46. Większa część zawartego tutaj kodu powinna być już Ci znana. Nowością jest jedynie wysyłanie oraz odbiór danych z układu przetwornika. Na rysunkach 32 i 33 zamieściłem fragmenty wyjęte z dokumentacji firmy Philips. Pokazują one, jak powinna przebiegać komunikacja z naszym układem. Jak widać na rysunku 32, zgodnie z opisanymi wcześniej ramkami transmisji, po podaniu warunku start, konieczne jest zaadresowanie układu. Adres PCF8591 składa się z czterech starszych bitów ustawionych na stale (1001 b = 9h) oraz trzech młodszych, które możemy wybrać poprzez odpowiednie zwarcie wyprowadzeń adresowych. Płytka AVT3500 ustawia na stałe w tym miejscu 0. Najmłodszy bit słowa adresowego służy do ustalenia czy odbywa się zapis, czy odczyt. Przy zapisie, w pierwszej kolejności wysyłany jest bajt konfiguracyjny. Jego znaczenie pokazuje rysunek 33.
Przyjrzyjmy się programowi. Po starcie transmisji wysyłamy bajt 90h - adres układu, zapis. Następnie bajt konfiguracyjny (0) - wyłączone wyjście przetwornika CA, konfiguracja wejść na cztery niezależne obwody, wyłączone automatyczne przełączanie kanałów dla każdego odczytu, kanał 0.
W pętli głównej włączamy transmisję, pobieramy bajt danych i wyłączamy transmisję. Usunięcie z pętli warunku STOP nie
spowoduje błędnego działania układu. Później, gdy uda nam się skompilować program, spróbuj przenieść inicjację odczytu poza pętlę a w pętli pozostawić samą instrukcję odczytu danych. Zgodnie z rysunkiem 32 takie działanie jest możliwe.
Wyświetlanie danych wykorzystuje utworzoną dziś funkcję lcd_dec. Zauważ, że zaraz po niej wpisywane są dodatkowo dwie spacje. Poprawia to czytelność wyniku, jeśli wcześniej wyświetlana była liczba o większej ilości cyfr - w takim przypadku na wyświetlaczu pozostaną stare cyfry i trzeba je usunąć. Przedstawiony sposób jest najprostszym z możliwych i nie wszędzie może być zastosowany. Dobrze się spisuje jeśli nie zamierzamy pisać nic więcej w danej linii.
W pliku main.c napisaliśmy prościutki program, którego zadaniem jest wyświetlanie danej odpowiadającej napięciu podanemu na wejście 10 naszej płytki. Zanim przejdziemy dalej, podłącz na to wejście źródło regulowanego napięcia. Może być to zasilacz albo -najwygodniej - zwykły potencjometr (l-100k) z przeciwległymi wyprowadzeniami wpiętymi między masę i zasilanie a środkowym do wejścia 10.
“ t *“7"" ~ * | ||
I oj. | ranom | . |
| . 1 ». | | |
________ |
.....m.. 1- | |
Z7SS. tra: | ||
I H —• , 11 * |
»»•« I . | |
| Ru-17 Bur protoool for re«l |
ode, A/O comofmton |
Gdybyś chciał skompilować teraz program, czeka Cię przykra niespodzianka - w tej chwili nic jest to jeszcze możliwe. Jeśli zmieniłeś, tak jak zwykle, pole TARGET pliku makefile, otrzymasz komunikat w stylu:
make.exe: *** No rule to make target PCF8591.o', neededby PCF8591.elf. Stop.
Nic dziwnego - nie istnieje przecież plik PCF8591.c. Skompilowanie programu składającego się z
większej liczby plików wymagać będzie niewielkiej modyfikacji
Elektronika dla Wszystkich Grudzień2005 47