CAN od zera część 3: Jak zarządzać danymi CAN w RTOS? Porównanie mechanizmów i wybór najlepszego
Dlaczego w systemie CAN gubią się ramki, mimo że magistrala działa poprawnie?
To pytanie pojawia się bardzo często.
Analizator CAN pokazuje, że wszystkie ramki zostały wysłane. Transceiver działa poprawnie. CRC się zgadza. A mimo to mikrokontroler nie widzi części wiadomości.
Problem w większości przypadków nie leży w protokole CAN, lecz w:
- sposobie zarządzania pamięcią,
- komunikacji między ISR a zadaniami RTOS,
- nieoptymalnym przekazywaniu danych.

W tym artykule pokażę:
- dlaczego proste podejście ISR + statyczny bufor prowadzi do utraty ramek,
- jak wygląda profesjonalna implementacja w systemie RTOS,
- dlaczego Memory Pool + kolejki z przekazywaniem wskaźników to wzorzec stosowany w systemach automotive i przemysłowych.
Przykłady bazują na mikrokontrolerze STM32.
Schemat podłączenia:
Połączenie układów jest identyczne jak w poprzednim wpisie, czyli mamy dwa mikrokontrolery połączone ze sobą transceiverami protokołem CAN, co przedstawia poniższy schemat blokowy.

Metody zarządzania pamięcią we wbudowanych systemach operacyjnych:
1. Statyczny bufor + ISR + FIFO (najprostsza metoda)
To najprostszy model odbioru danych: przerwanie odbioru CAN zapisuje dane do statycznego bufora (np. tablica struktur), a następnie dane są pobierane w kolejności FIFO.
Przykład bufora:

Zarządzanie wymaga ręcznego obsłużenia indeksów, flag zajętości i kolejkowania.
Ta metoda jest:
- deterministyczna (czas zawsze taki sam — brak dynamicznej alokacji),
- bezpieczna pamięciowo (statycznie zaalokowane tablice),
- mało skalowalna, ponieważ: (ISR wykonuje dużo logiki, przekazywanie danych między zadaniami nie jest izolowane, zmiana rozmiaru bufora wymaga rekompilacji)
Użycie: To dobra metoda na małe systemy i prosty ruch CAN.
2. Statyczny bufor + ISR + Semafor (usprawniona metoda)
To rozwinięcie metody nr 1.
ISR zapisuje ramkę do bufora (FIFO), ale zamiast aktywnie odpytywać bufor, zadanie budzi się dzięki semaforowi:
- ISR → daje semafor
- Zadanie → czeka na semafor (blokująco)
Zalety:
- nadal deterministyczne i statyczne,
- ISR jest krótsze (tylko zapis + semafor),
- zadanie CAN nie pracuje gdy nie ma ramek,
- dobre dla systemów z: (stałym rozmiarem ramek, niewielką liczbą typów wiadomości, umiarkowanym obciążeniem.)
Użycie: To najczęściej spotykana metoda w prostych systemach RTOS.
3. Dynamiczna alokacja (malloc/free)
ISR lub zadanie przydziela pamięć każdej ramce osobno:

Zalety:
- pełna elastyczność — każdy obiekt może mieć inny rozmiar.
Wady:
- ryzyko fragmentacji, szczególnie po wielu godzinach/dniach,
- konieczność ręcznego zwalniania pamięci,
- potencjalne wycieki i zawieszenia systemu,
- czas nie jest deterministyczny,
- niedopuszczalne w systemach wymagających wysokiej niezawodności.
Użycie: Tę metodę stosuje się rzadko — głównie w systemach niekrytycznych lub eksperymentalnych.
4. Kolejki RTOS (Queue) – pełne struktury vs wskaźniki
Zadania mogą komunikować się poprzez:
a) Przesyłanie całych struktur
Łatwe, czytelne, ale:
- RTOS musi kopiować nawet duże struktury,
- powoduje duże obciążenie CPU,
- kolejki mają mały maksymalny rozmiar (kilkadziesiąt wiadomości).
b) Przesyłanie wskaźników w kolejce (zalecane)
Kolejka zawiera tylko pointer, np. do bufora statycznego lub z memory pool.
Zalety:
- minimalny czas ISR,
- mało kopiowania danych,
- skalowalne i wydajne.
Użycie: To najlepsza forma użycia kolejek RTOS w CAN.
5. Memory Pool (najlepsza metoda w RTOS)
To wcześniej przygotowana pula identycznych bloków pamięci, z których system może szybko „wypożyczać” i „oddawać” elementy. Wyobraź sobie, że zamiast dynamicznie budować pamięć za każdym razem (malloc), przygotowujesz na starcie. Potem tylko: bierzesz jeden wolny blok, używasz go, oddajesz go do puli. Bez dzielenia sterty, bez szukania miejsca w pamięci, bez fragmentacji.
Prosta analogia:
Malloc to jak:
- Szukasz miejsca parkingowego w mieście.
- Czasem znajdziesz od razu.
- Czasem krążysz 10 minut.
Memory Pool to jak:
- Masz prywatny parking z 32 miejscami.
- Jeśli jest wolne miejsce – wjeżdżasz natychmiast.
- Jeśli nie ma – od razu wiesz, że parking jest pełny.
ISR odbiera ramkę i rezerwuje blok o stałym rozmiarze z memory pool:
ISR:
- pobiera blok z poola (stały czas)
- zapisuje ramkę do bloku
- wrzuca wskaźnik do kolejki
Zadanie:
- odbiera wskaźnik z kolejki
- dekoduje ramkę
- zwalnia blok do poola
Zalety:
- zero fragmentacji
- deterministyczne czasy alokacji i zwalniania
- idealne do stałych rozmiarów wiadomości CAN
- ISR bardzo szybkie
- zadanie dostaje gotowy wskaźnik — zero kopiowania
Użycie: To zdecydowanie najbardziej profesjonalna metoda w systemach pracujących latami.

Porównanie metod:

Przypadek 1 – Dlaczego gubimy ramki?
Założenia testu:
Mikrokontroler 1:
- wysyła watchdog ID 0x11 co 100 ms,
- wysyła ID 0x12 po naciśnięciu przycisku.
Mikrokontroler 2:
- używa statycznego bufora + ISR,
- przetwarza dane w pętli głównej.
Efekt:
Analizator CAN pokazuje 20 wysłanych ramek ID 0x12.
Mikrokontroler odbiera tylko 5.
Dlaczego?
Bo:
- ISR ustawia tylko flagę,
- przetwarzanie w pętli głównej trwa zbyt długo,
- nowe ramki nadpisują poprzednie dane.
Reasumując:
Magistrala działa poprawnie.
Zarządzanie pamięcią – nie.
Tak wygląda kod do obsługi przerwania:

Główna pętla:

Wysyłam z Mikrokontrolera 1 ramkę o ID 0x11 jako watchdog do Mikrokontrolera 2 co obserwujemy za pomocą konwertera USB CAN w programie USBCAN. Ramki wysyłane są co 100ms co widać na poniższym rysunku:

Po naciśnięciu przycisku wysyłane są ramki o ID 0x12, aby podsłuchiwać tylko te ramki wprowadziłem do programu USBCAN filtr na to ID.

Jak widać na powyższym rysunku rysunku Mikrokotroler 1 wysłał 20 ramek o ID 0x12, a Mikrokontroler 2 odebrał tylko 5 ramek o ID 0x12.

Przypadek 2 – RTOS + Memory Pool (rozwiązanie profesjonalne)
W tym wariancie w Mikrokontrolerze 2:
- używam dwóch filtrów (FIFO0 i FIFO1),
- tworzę dwie kolejki,
- tworzę dwa memory poole,
- każde FIFO obsługuje osobne zadanie.
ISR trwa bardzo krótko. Zadania przetwarzają dane niezależnie.
Efekt testu:
- 20 wysłanych ramek ID 0x12.
- 20 odebranych ramek ID 0x12.
Reasumując: Bez strat.
Poniżej umieściłem diagram pokazujący przepływ danych odbieranych po CAN:

W porównaniu do poprzednich wpisów w tym przypadku, użyłem dwóch filtrów:

Dalej włączyłem dwa przerwania od CAN:

Uruchomiłem RTOS i dodałem następujące dwa zadania:


Umieściłem następujące elementy w kodzie programu, handlery do kolejek i puli pamięci:

Struktury do przechowywania odebranych ramek CAN:

Funkcja, w której tworzone są kolejki:

Funkcja, w której tworzone są banki pamięci:

Przerwanie, w którym odbierane są normalne ramki komunikacyjne:

Przerwanie, w którym odbierane są ramki typu watchdog:

Funkcja odbioru normalnych ramek komunikacyjnych, która jest umieszczona w zadaniu:

Funkcja odbioru ramek typu watchdog, która jest umieszczona w zadaniu:

W funkcji main() umieściłem tylko start CAN, aktywację przerwań dla pierwszego i drugiego FIFO, nagłówek ramki nadawczej, funkcje tworzące kolejki oraz banki pamięci:

Dalej skonfigurowałem następujące filtry dla CAN:

Na końcu umieściłem w osobnych zadaniach funkcje odbierające ramki CAN:

Po skonfigurowaniu Mikrokontrolera 2 zgodnie z powyższym opisem wykonałem taki sam test. Mikrokontroler 1 wysyła periodycznie ramki watchdog o ID 0x11, a po naciśnięciu przycisku wysyłane są ramki o ID 0x12. Tak samo jak w Przypadku 1 wprowadziłem do programu USBCAN filtr na ID 0x12.

Poniżej znajduje się terminal, który informuje o ramkach odebranych przez Mikrokontroler 2. Jak widać Mikrokontroler 2 odebrał wszystkie 20 ramek o ID 0x12.

Podsumowanie:
W tym artykule pokazałem, że problem utraty ramek CAN rzadko wynika z samej magistrali. Najczęściej jest to konsekwencja nieoptymalnego zarządzania pamięcią między ISR a zadaniami RTOS.
W systemach pracujących latami – jak automotive czy przemysł – deterministyczna alokacja pamięci i brak fragmentacji nie są opcją, lecz koniecznością.
Memory Pool + kolejki z przekazywaniem wskaźników to wzorzec projektowy, który pozwala:
- utrzymać minimalny czas ISR,
- uniknąć kopiowania danych,
- zagwarantować brak fragmentacji,
- skalować system bez utraty stabilności.
Źródło: DMBP

CAN od zera część 1: Jak przesłać dane między CAN1, a CAN2 w STM32 bez użycia transceivera?
CAN od zera część 2: Jak połączyć dwa mikrokontrolery i przesłać pierwszą ramkę?
Sterownik PWM: Definicja timerów w STM32 do programowych PWM oraz odczyt ADC STM32 







