Powrót do strony głównej

Pytania dotyczące C dla mikrokontrolerów

Artykuł zawiera kluczowe pytania dotyczące programowania w C dla mikrokontrolerów, systemów budowania, przerwania i RTOS. Wybór oparty na rzeczywistych rozmowach kwalifikacyjnych dla specjalistów middle/senior. Przykłady kodu i wyjaśnienia terminologii.

C dla mikrokontrolerów: 60+ kluczowych pytań
Advertisement 728x90

Teoretyczne minimum: kluczowe pytania z programowania mikrokontrolerów

Programiści mikrokontrolerów muszą znać specyfikę języka C w warunkach ograniczonych zasobów. Poniżej — zestaw praktycznych pytań z naciskiem na kompilację, pamięć i optymalizację. Te tematy regularnie pojawiają się na rozmowach kwalifikacyjnych w firmach zajmujących się embedded.

Kluczowe słowa kluczowe i ich zastosowanie

Static ogranicza widoczność zmiennej do bieżącego pliku lub funkcji, zachowując wartość między wywołaniami dla lokalnych static. Volatile zapobiega optymalizacji przez kompilator dla zmiennych zmienianych z zewnątrz (rejestry, pamięć, przerwania).

Przykład problematycznego kodu:

Google AdInline article slot
int square(volatile int *ptr) {
    return *ptr * *ptr;
}

Tutaj volatile dotyczy tylko pierwszego dereferencjonowania, drugie może być zoptymalizowane. Poprawnie: (ptr) (*ptr).

Const volatile jest możliwe dla danych niezmienianych z zewnątrz, jak rejestry statusowe. Register sugeruje kompilatorowi przechowywanie zmiennej w rejestrze (przestarzałe w C11). Restrict wskazuje brak aliasingu wskaźników dla wektoryzacji.

Praca z pamięcią i strukturami

Globalna zmienna const ląduje w Flash. Dla stałej tablicy 32 KB o wartości 0xFF użyj skryptu linkera z sekcją fill(0xFF).

Google AdInline article slot

Operacje bitowe: sprawdzenie bitu — n & (1 << k), czyszczenie — n & ~(1 << k). Porównanie float: fabs(a - b) < EPSILON.

Rozmiar struktur zależy od wyrównania. Dla

struct Foo {
    int iiii;
    char c;
};

rozmiar 8 bajtów (4 bajty paddingu). Pakowanie w GCC: __attribute__((packed)).

Google AdInline article slot

Przykład wyświetlania stringów:

char str5[]={'s','t','r','i','n','g'};  // size:7 len:6
char *str1="string";                    // size:4 len:6 (wskaźnik)
char str3[]="string";                   // size:7 len:6
char str4[10]="string";                 // size:10 len:6

Systemy build i toolchain

System build automatyzuje kompilację, linkowanie, zależności. Make obsługuje zmiany plików, Ninja — szybszy dzięki paralelizowaniu i prostocie.

VPATH w GNU Make szuka źródeł w podanych katalogach.

Ścieżka kodu: .c → .i (preprocesor) → .s (asembler) → .o → .elf → .bin/.hex.

Wyłączyć ostrzeżenie GCC: #pragma GCC diagnostic ignored "-Wrestrict".

Skompresować firmware: -ffunction-sections -fdata-sections, ld --gc-sections, optymalizacja -Os.

Artefakty builda

  • .o — pliki obiektowe
  • .elf — plik wykonywalny z symbolami
  • .hex/.bin — do flashowania (hex z adresami)

Binutils: objdump (dizassembler), objcopy (konwerter), readelf (analiza ELF).

Sprawdzić build pliku: make V=1 lub -n (dry-run).

Struktury danych i algorytmy

Bufor cykliczny — pierścieniowa tablica z indeksami head/tail, różni się od FIFO brakiem przesunięć.

Usuwanie z listy powiązanej: zmieniamy prev->next = node->next (potrzebny prev) lub node->next->prev = node->prev (dwukierunkowa).

Polimorfizm w C: wskaźniki na funkcje w strukturach (podobnie jak vtable).

Zadanie bez if/switch:

void print_num(int n) {
    const char *nums[] = {"","Jeden","Dwa"};
    printf("%s", nums[n]);
}

do-while(0) dla makr — pojedyncza iteracja z wieloma statementami.

Przerwania i RTOS

Przerwanie — sprzętowy mechanizm przejścia do ISR po zdarzeniu. Tabela wektorów zawiera adresy ISR. Cortex-M4: 12–16 taktów na wejście (zapis kontekstu).

Algorytm: hardware zapisuje PC/LR, ładuje wektor, jump do ISR, return przez POP.

Reentrant funkcja — bezpieczna dla równoległych wywołań (brak static/lokalnych bez synchronizacji).

W RTOS:

  • Wątek — jednostka wykonania ze stosem
  • Mutex — wzajemne wykluczanie
  • Semafory — licznik zdarzeń
  • Spinlock — blokada busy-wait

Problem w kodzie: deadlock z powodu kolejności lock (TaskA: ma→mb, TaskB: mb→mc→ma).

Inwersja priorytetów: wysoki priorytet czeka na niski. Rozwiązanie: dziedziczenie priorytetu.

Operacje atomowe

Przykład: __atomic_exchange_n(&x, &y, __ATOMIC_SEQ_CST).

Bit-banding — bezpośredni dostęp do bitów bez masek (Cortex-M3/4).

Toolchain i debugowanie

ABI — interfejs binarnej kompatybilności (calling convention).

Segmenty: .text (Flash kod), .data (RAM inicjowane), .bss (RAM zera), .rodata (Flash stałe).

Rozłożyć preprocesor: gcc -E file.c > expanded.i.

Dokumenty: datasheet, reference manual, programming manual, schemat.

Co ważne

  • Volatile obowiązkowe dla rejestrów peryferii
  • Pakowane struktury oszczędzają RAM, ale spowalniają dostęp
  • RTOS wymaga kodu thread-safe i ochrony sekcji krytycznych
  • Optymalizacja builda: sections + gc-sections
  • Przerwania działają na stosie bieżącego wątku

Dla paddingu 0xFF w strukturach: skrypt linkera z fill lub __attribute__((packed)) + ręczne wypełnienie.

— Editorial Team

Advertisement 728x90

Czytaj dalej