Programowanie
Programowanie
Makro różni się od prostej definicji stałej tym, że dodatkowo zawiera parametry, które mogą być wykorzystane w jego wnętrzu. Przyjrzyjmy się prostemu makru MAX. Jego zadaniem jest wybranie oraz zwrócenie większego z podanych parametrów:
#define MAX(x,y) C(x)<(y) ? (y):
Zwracam uwagę na znajdującą się tutaj dużą ilość nawiasów. Takie pisanie makra pozwala uniknąć przykrych niespodzianek. Pamiętaj, że makro to nie funkcja. Makro zostanie przez preprocesor rozwinięte do tekstu dokładnie tak zapisanego jak przy jego wywołaniu. Na obrazku A w tej ramce możesz zobaczyć, jakie mogą być efekty pominięcia pojawiających się przy definiowaniu naszego makra nawiasów. Spróbuj przeprowadzić analogiczną analizę przy założeniu, że wszystkie wymienione nawiasy ist-
Używanie makr wymaga także wyczucia przy ich wywoływaniu. Ostrożności wymaga wprowadzanie jako parametrów zapisów zmieniających wartość zmiennej. Pułapkę taką pokazuje rysunek B. Podsumowując:
1. Przy tworzeniu makr zawsze obejmujemy parametry oraz cały wynik obliczeń w nawiasy.
2. Przy wywoływaniu makra dobrze
B
ABC... C Makra
Podczas naszego pierwszego spotkania ze słowem kluczowym Mefine wykorzystaliśmy go w celu nadawania wyprowadzeniom portów wygodnych dla nas nazw. Wspominałem wtedy, że słowo to służy także do tworzenia całych makr. Skorzystamy teraz właśnie z tej móżli-#define MAX(x,y) x<y ? y:X;
^ j Spodziewamy się wyniku 7.
Jednak w zmiennej a a = 4 + MAx(2, 3); znajdzie się wartość 2 Zobacz rozwinięcie p0 zmianie formatu na
a=4 + 2<3?3:2; bardziej czytelny, co dla C
iSłgijest większe od| wynik FAŁSE - wybranie -HfflB z elementów
#define MAX(x,y) (Cx)<(y) ? (y):U))
a = MAX(b++, c++)— -
a = ((b++)<(c++) ? (c++):(b++» Zauważ, źe w takim przypadku zmienna, która jest większa zostanie zwiększona dwa razy, podczas gdy zmienna mniejsza tylko raz.
jest wyobrazić sobie, w jaki sposób zostanie ono rozwinięte. Dla bezpieczeństwa lepiej nie korzystać z parametrów zmieniających wartość danej zmiennej.
Sklejanie parametrów
f . . Tematykę makr będziemy jeszcze rozwi-^ jać, teraz zajmiemy się potrzebną nam możliwością sklejania parametrów z... czymkolwiek.
Chcemy utworzyć makra, które umożliwią przetworzenie nazwy portu w postaci litery (A, B, C, D...) na nazwy odpowiednich rejestrów (PINA, DDRA, PORTA, PTNB...). Konieczne jest więc łączenie jednego z przedrostków z parametrem, który zawierał będzie literę portu. Działanie takie umożliwia operator podwójnego znaku hash (##).
Zobaczmy przykładową, wstępną wersję makra tworzącego z podanej litery nazwę portu: #defi ne PORT(x) (PORT##x)
Teraz rozwinięcie makra będzie przebiegać jak na rysunku C.
Widzisz niebezpieczeństwo? Makro będzie działało poprawnie, jeśli podamy mu bezpośrednio literę portu. Gdy zastosujemy nazwę portu w formie zdefiniowanej wcześniej stałej, to identyfikator stałej, a nie przypisana jej wartość, zostanie połączony z przedrostkiem PORT. Utworzony identyfikator nie zostanie dalej przetworzony. W praktyce, ze względu na kolejność przetwarzania danych przez preprocesor, problem rozwiąże dodanie makra drugiego poziomu. Pokazuje to rysunek D.
#define port(x) (port##x) port(d) |= l«swi | l«sw2;
y Rozwinięcie
(PORTD) |= 1«SW1 | 1«SW2;
PORT(SW_PORT) |= 1«SW1 | 1«SW2; g-i y Rozwinięcie
(PORTSW_PORT) |= 1«SW1 | 1«SW2;
Rozwinięcie SW_PORT n; skazanie identyfik^ora SW PORT^
#define portu) xport(x)
#define xport(x) (port##x)
port(sw_port) |= l«swi | 1«SW2;
■Wywołanie makra Do makra na drugim
poziomie podawana jest już y wartość stałej (D)j
U Prawidłowe złożenie nazwy: PORTD--
Listing 21 LCD: nagłówki i konfiguracja
#inc1ude <avr\io.h>
#include <inttypes.h>
// Makra upraszczające dostęp do portów (...)
// Definicje wyprowadzeń #define LCD_RS 2 #define ,LCD_RSPORT B #defi ne LCD_E 1 #define LCD_EPORT B #define lcd_dport b
#define LCD_D4 4 // D5,D6,D7 - kolejno
Wierzę więc, że nie będziesz miał żadnych problemów z moim sposobem opisu.
Aby móc przeprowadzić kompilację, jeszcze przed napisaniem całości, koniecznie wprowadź pustą funkcję main - zawierającą tylko instrukcję return.
Listing 21 przedstawia początek pliku z programem. Ze względu na oszczędność miejsca wykropkowane zostały makra PORT, PIN i DDR. Są one identyczne jak te z listingu 20. Nie dzieje się tutaj nic niezwykłego: dołączenie plików .nagłówkowych oraz definicja wyprowadzeń to coś, co robiliśmy już wielokrotnie. Istotną zmianą jest podawanie tylko litery portu zamiast pełnej nazwy odpowiednich rejestrów.
Następnie dla własnej wygody zwiększenia czytelności kodu umieszczone zostały definicje komend sterujących wyświetlaczem. Przedstawia to listing 22.
W dokumentacji stosowanego wyświetlacza powinna pojawić się tabelka ze wszystkimi możliwymi komendami. Przedstawione definicje to nic innego jak przeniesienie wspomnianej tabelki bezpośrednio na rząd odpowiednich stałych. Założenie jest takie, że wysyłając odpowiednią komendę, będziemy łączyć ją poprzez sumę logiczną z występującymi poniżej komendy parametrami. Zauważ, że część parametrów ma przypisaną wartość 0.
Elektronika dla Wszystkich Październik 2005 37