Programowanie
Programowanie
Przy tw |
orzeniu wyrażeń warunkowych dla dyrek- |
ratorów C. |
Możemy porównywać, mnożyć, dzielić. |
rować możi |
;my tylko na wartościach stałych - takich |
które znane |
są w czaśie kompilacji. |
ffifdcf, ffifndef |
W pewien sposób, oddzielnymi dyrektywami są tytułowe difdef, Sifndef. Mieliśmy okazją już dziśiaj z nich
ABC... C
Kompilacja warunkowa jako sposób na zwiększenie uniwersalności kodu
Na samym początku przypominam, aby wyjaśnić wszelkie wątpliwości, że wszystkie instrukcje zaczynające się znakiem hash (#) są dyrektywami preprocesora. Niektóre z nich mogą w szczególności odnosić się do kompilatora. Jednak w żądnym razie nie tworzą i one części programu wpisywanego w. procesor. Nie mijając się daleko z prawdą, można pówiedzieć, że dyrektywy te tworzą program dla naszego kompilatora - pozwalają nam sterować przebiegiem kompilacji.
#if, #elif, #else, Sendif
Wymienione powyżej dyrektywy są bardzo podobne w swojej składni do instrukcji warunkowej C. Podstawowy przykład wykorzystania pokazuje listing:
#if STALA<2
// Wersja 1 programu ■ #elif STALA<100
// wersja 2 programu
#else
// wersja 3 programu
#endif
Zestaw takich instrukcji umożliwia skompilowanie różnych wersji programu dla różnych wartości stałych. Można wyobrazić sobie takie napisanie funkcji obsługi wyświetlacza, aby możliwe było przełączenie -za pomocą jednej .Stałej między trybami: sterowania wyświetlaczem podpiętym, tak jak na naszej płytce, a podpiętym do Szyny -'danych. Konieczne byłoby; umieszczenie między dyrektywami -#if, ftelif, ffendif różnych wersji funkcji niskiego poziomu.
korzystać. Okazuje Się, że w instrukcjach kompilacji warunkowej mamy: możliwość sprawdzenia, czy dana stała została- zdefiniowana, podjęcia w zależności od tego odpowiednich stałych. Definicja w stylu:
#define NIC
jest jak najbardziej prawidłowa (wszystkie wystąpienia identyfikatora NIC w kodzie zostaną zamienione... na nic - zostaną po prostu usunięte). Pamiętaj ó tym -takie działanie czasami się przydaje.
Jednak to. na co cheę teraz zwróctć uwagę, to fakt, że dokonanie takiej (oraz każdej innej) definicji stałej może być wykryte za pomocą instrukcji #ifdef i #ifndef.
Przy tworzeniu wyrażeń można korzystać ze słowa kluczowego defined. Zwraca ono TRUE, jeśli dana stała jest już zdefiniowana. Tak więc #ifdef i ffifndef mogą być zastąpione przez, odpowiednio:
#if defined NIC #if !defined NIC
Słowa defined można używać także w złożonych warunkach.
Jak zwykłe - wszystko będzie stawać sięnoraz jaśniejsza przy wprowadzaniu tego W' życie.
Listing 41 - nagłówki w i2c.c
łinclude <avr\io.h> rinclude einttypes.fi>
#include „harddef.h,, #include „makra.h„ #incJude „i2c.h„
Listing 42 - opóźnienie połówkowe w i2c.c #defi ne lżc_nhalf CF_cpu/i2c_SPEED/2) // Funkcja dłuższych opóźnień
nop();
#eise
Wdefine l2c_delay1oops (l+(l2c_nha!f-8)/3)
#if !2c_deJayloops > 255
#error Przyspiesz - bo sie nie wyrabiam ;) ffendif
static void i2o_xdelay(void)
asm volatile( \
„delayus8_1oop%=: \n\t„\
„dec %[ticks] \n\t„\
„brnę deiayus8_1oop%= \n\t„\
: : [ticks]„r„fl2c_dełayioops) );
#endif //I2c_nhalf >= 3
// opóźnienia dla i2c
static inline void i2c_hdeiay(void)
{
#if I2C_nhałf < 1 return;
#eiif l2C_nha1f < 2 N0P();
^ selif I2C_nhaJf < 3 asm voiatile(
„rjmp exitX=\n\t„
S=:\n\t„:: i2c_xdeJayO;
„odklepanie sprawy i zapomnienie o problemie”. Spróbujemy stworzyć coś bardziej uniwersalnego.
Powiedzmy sobie najpierw o tym, jak od strony programu będzie wyglądała transmisja jednego baj tu. Sprawa jest prosta:
1. Między warunkami START i STOP, w przerwie między bajtami, linia zegara znajduje się w stanie niskim.
2. Zmieniamy stan linii danych i odczekujemy połowę okresu wynikającego z szybkości transmisji - odczekanie pewnego czasu jest konieczne przed wygenerowaniem narastającego zbocza zegara.
3. Generujemy impuls wysoki na linii zegara o czasie trwania równym pozostałej nam połowie okresu.
4. Możliwy jest natychmiastowy powrót do punktu 2 - na magistrali nie trzeba podtrzymywać danych.
Jak łatwo zauważyć, konieczne będzie obliczenie występującego wyżej połówkowego opóźnienia. W praktyce, aby transmisja mogła przebiegać z pełną szybkością, na potrzebę warunków START i STOP można stworzyć oddzielną funkcję o opóźnieniu „ćwiartkowym”, jednak w tym przykładzie spowolnimy odrobinę szybkość działania magistrali, uzyskując w zamian uproszczenie kodu. Konieczne opóźnienie możemy obliczyć ze wzoru:
n = ( F_CPU - X ) / I2C_SPEED / 2
Uzyskamy wynik bezpośrednio w cyklach maszynowych. F_CPU jest stałą dołączaną z poziomu pliku makefile, I2C_SPEED zdefiniowaliśmy na listingu 38. Zapytanie może wywołać X. W tym miejscu powinna pojawić się liczba cykli, jakie zajmuje procesorowa zmiana stanu linii danych. W najprostszym przypadku możemy ją pominąć. Wpływ tej wartości jest widoczny jedynie dla prędkości transmisji porównywalnych z częstotliwością pracy mikroprocesora. W takim przypadku nasze obliczenia i tak obarczone będą dużym biędem - zmiana stanu linii to nie wszystko czym musimy się zająć.
Stwórz i dodaj teraz do projektu plik i2c.c oraz i2c.h. Plik nagłówkowy pozostawimy chwilowo pusty i zajmiemy się plikiem źródłowym.
Na początku dołączamy wszystkie potrzebne nagłówki - patrz listing 41.
Po tej czynności obliczymy niezbędną wartość opóźnienia oraz stworzymy odpowiednią funkcję. Zależy nam na tym, aby obliczanie opóźnienia oraz wybór odpowiedniej wersji funkcji odbywał się automatycznie. Informacje przydatne do zrozumienia tej części kodu znajdują się w trzech kolejnych ramkach: „Kompilacja
warunkowa jako sposób zwiększenie uniwersalności kodu”, „Funkcje static i inline” oraz „Generowanie własnych błędów i ostrzeżeń”. Kod przedstawiam na listingu 42.
Listing ten może wyglądać na skomplikowany. Zatrzymajmy się na nim przez chwilę. Jeśli przeczytałeś wspomnianą na początku dokumentację (avr300), po krótkim przyjrzeniu kod wyda się znajomy. W istocie jest to przepisany na C umieszczony w dokumentacji opis generowania niezbędnego opóźnienia. Wprowadzone zostały jedynie niewielkie poprawki dostosowujące kod do poznanej do tej pory części języka C. Pierwsza linia nie powinna budzić wątpliwości - odpowiedni wzór pojawił się w poprzednim śródtytule. Dalszą część łatwiej jest zrozumieć, analizując kod od końca. Funkcja i2cjidelay będzie wywoływana wszędzie tam w programie, gdzie potrzeba opóźnienia połówkowego. Jest to funkcja rozwijalna w miejscu wywołania. W pierwszym przypadku (opóźnienie mniejsze niż jeden cykl) kompilator powinien wszystkie jej wywołania pominąć. Zauważ, że reszta przypadków została zoptymalizowana w taki sposób, żc umieszczenie w kodzie programu odniesienia do funkcji spowoduje dodanie tylko
Elektronika dla Wszystkich Grudzień2005 45