Dziękuję koledze LordBlick, poprosiłem go o odblokowanie tematu, bo wydaje mi się, że mimo pozornej prostoty problemu warto się nad nim chwilę zastanowić. Zanim przejdę do odpowiedzi na post #3, powiem dwa słowa o makrodefinicjach. Wiele osób z nich chętnie korzysta, w pokazanym wyżej przykładzie zdecydowanie lepiej posługiwać się makrami LED_ON i LED_OFF niż patrzeć na „goły” kod. Ale IMHO jeszcze lepiej wykorzystać wspomniane funkcje. Wtedy zamiast makra mamy zwykłą funkcję:
Zaloguj się, aby zobaczyć kod
Co nam to daje? Przede wszystkim mamy zwykłą funkcję, która wywołana w kodzie będzie się stosować do wszystkich reguł języka C, a kod wynikowy będzie identyczny jak w przypadku stosowania makr. IMHO jeśli preprocesor nie jest niezbędny to lepiej z niego nie korzystać.
Teraz kwestia zapisu PORTE_DIR=, czy PORTE.DIR=? Oba zapisy robią dokładnie to samo, zapisują nową wartość do rejestru DIR portu E. Dla kompilatora jest jednak różnica. Zapis PORTE_DIR to znane nam z innych AVRów makro, rozwijane do zapisu typu (volatile uint8_t*)(addr)=, powodujące zapis wartości pod wskazany adres IO. Zwykle kompilator tłumaczy to na jedną instrukcję ST(adres),rejestr. Sprawa prosta. Druga postać to zapis, ale z wykorzystaniem struktury o typie PORT_t, gdzie DIR to tylko jedno z kilku pól tej struktury. Wykorzystanie struktur wiąże się z adresowaniem pośrednim, gdzie mamy adres bazowy trzymany w rejestrze indeksowym, a pola struktury adresuje się jako przemieszczenie (offset) w stosunku do adresu bazowego. Zwykle jest to tłumaczone na instrukcję ST (Z+x), rejestr, co jest efektywne, ale wymaga wcześniejszego załadowania rejestru Z adresem struktury (kolejne dwie instrukcje). W efekcie nie zawsze warto ze struktur korzystać, chociaż optymalizator gcc odwala kawał dobrej roboty i trywialne odwołania do pól struktury czasami potrafi zamienić na pojedyncze instrukcje, w efekcie oba zapisy są równoważne. Jeśli adresujemy kilka pól tej samej struktury to przewaga struktury nad adresami IO staje się co raz bardziej oczywista. Struktury też można wygodnie przesyłać jako argumenty funkcji, co jest kłopotem w przypadku gołych rejestrów.
Na koniec kwestia bp, bm, gm itd. Generalnie literka p to position (pozycja), m to mask (maska), b to bit, a g to grupa (pole bitowe). W efekcie wszystkie definicje kończące się na _bp określają pozycję bitu, np. PIN6_bp to liczbowo 6 – pozycja bitu wynosi 6. Taką pozycję można zamienić na maskę bitową, przy pomocy znanego z innych AVR przesunięcia bitowego (1<<PIN6_bp), lub od razu skorzystać z gotowej maski PIN6_bm. Podobne zasady dotyczą pól bitowych (grup). Wygląda to trochę inaczej niż dla innych AVR, ale w efekcie zapis jest przejrzysty i trudniej o błąd (przypuszczam, że każdemu zdarzyło się zrobić zamiast 1<<cośtam, 1<cośtam).