PlipBox a obsługa modułów ethernet

Okazuje się, że PlipBox z założenia miał działać nie tylko na module opartym o ENC28j60, lecz także innych. Obsługa wyboru modułu została zrobiona w sposób dość ciekawy, ale nie będę psuł niespodzianki. Przeanalizujmy sobie fragmenty kodu.

Załóżmy, że natknęliśmy się w kodzie na funkcję sprawdzania, czy przyszła do sieciówki nowa ramka Ethernet:

  while(run_mode == RUN_MODE_PIO_TEST) {
    // handle commands
    // NOTE: UART cmd_worker() processing here, reset by loop break

    // incoming packet?
    if(pio_has_recv()) {
      // do sth
    }
  }

Ciekawe jak działa funkcja pio_has_recv() ? Spójrzmy na jej kod:

uint8_t pio_has_recv(void)
{
  return pio_dev_has_recv(cur_dev);
}

Okej, mamy wrapper. Co znajdziemy dalej?

inline uint8_t pio_dev_has_recv(pio_dev_ptr_t pd)
{
  pio_dev_has_recv_t has_recv_f = (pio_dev_has_recv_t)pgm_read_word(&pd->has_recv_f);
  return has_recv_f();
}

I tu się zaczynaja spaghetti. Wczytywany jest wskaźnik na właściwą funkcję has_recv_f() , której adres został zapisany w ROMie pod adresem pd->has_recv_f . Struktura pd  jest podawana w argumencie, a jest to nic innego jak zmienna globalna cur_dev:

// pio_dev.h
/* device structure */
typedef struct {
  const uint8_t         *name;
  pio_dev_init_t      init_f;
  pio_dev_exit_t      exit_f;
  pio_dev_send_t      send_f;
  pio_dev_recv_t      recv_f;
  pio_dev_has_recv_t  has_recv_f;
  pio_dev_status_t    status_f;
  pio_dev_control_t   control_f;
} pio_dev_t;

typedef const pio_dev_t *pio_dev_ptr_t;

// pio.c
static pio_dev_ptr_t cur_dev;

No dobra, ale gdzie jest wartość tej zmiennej? Cierpliwości:

uint8_t pio_init(const uint8_t mac[6],uint8_t flags)
 {
 // get current device
 cur_dev = (pio_dev_ptr_t)pgm_read_word(devices + dev_id);
 // do sth
 }

Czyli sama jej zawartość również jest wczytywana z ROMu. Powoli zaczyna się klarować sens takiego działania:

static const pio_dev_ptr_t PROGMEM devices[] = {
#ifdef DEV_ENC28J60
  &pio_dev_enc28j60,
#endif
};
#define NUM_DEVICES (sizeof(devices) / sizeof(pio_dev_ptr_t))

Gdzie pio_dev_enc28j60  jest inicjalizowaną strukturą w ROMie zawierającą właściwie wskaźniki na funkcje. Czyli mamy tablicę wskaźników na struktury opisujące interfejs z danym urządzeniem peryferyjnym, wypchane po brzegi kolejnymi wskaźnikami na funkcję. Hura!

Co o tym myśleć?

Cały system miał na celu dodanie do firmware’u supportu dodatkowych modułów sieciowych. Wyszedł potworek, który wprowadza niepotrzebny narzut do prędkości działania kodu, zaśmieca ROM i skutecznie unieczytelnia kod.

Taką decyzję projektową można tłumaczyć chęcią pisania kodu multiplatformowego, ale przecież istnieje wiele innych rozwiązań – chociażby nieśmiertelne #ifdef , które inkludują odpowiedni kod w zależności od flag kompilacji. Oszczędzimy przy tym również cenne miejsce pamięci mikrokontrolera, bo we wsadzie będzie znajdować się tylko to, co niezbędne. Pojawi się wtedy jeden minus, bowiem na każdą konfigurację wsad AVRa trzeba zbudować oddzielnie. Pytanie – czy jest to aż tak straszne, zwłaszcza że sam autor zamieszcza różne wsady dla różnych wersji płytki bazowej z mikrokontrolerem?

Sam system wyboru modułu chyba jednak warto zapamiętać – może się przydać w jakichś przyszłych, bardziej rozbudowanych projektach. A tymczasem z kodu go wywaliłem – oprócz poprawy czytelności zyskałem na tym również ćwierć kilo flasha i 3 bajty RAMu – to ostatnie najcenniejsze przy obecnym zapełnieniu około 80%.

This entry was posted in AVR, PlipUltimate and tagged , , , , . Bookmark the permalink.

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Witryna wykorzystuje Akismet, aby ograniczyć spam. Dowiedz się więcej jak przetwarzane są dane komentarzy.