W pierwszej części tej serii omówiliśmy znaczenie ostrożnej obsługi przerwań i kilka ogólnych praktyk związanych z niezawodną strukturą ISR-ów. Teraz przedstawimy implikacje zbyt liberalnego użycia zmiennych globalnych i lokalnych w ISR.
Zmienne globalne — wiedz, kiedy są modyfikowane
Zmienne globalne muszą być ostrożnie obsługiwane, gdy są używane z przerwaniami, ponieważ te są generalnie asynchroniczne. I jeżeli zmienna globalna jest modyfikowana przez ISR, może zostać zmieniona w dowolnym momencie. Musimy mieć na uwadze następujące aspekty:
Odczyt/zapis zmiennych globalnych w wielu miejscach
Zmienne globalne powinny być modyfikowane tylko w kilku koniecznych obszarach wewnątrz programu. Gdyż, jeśli odbywa się to w oparciu o wiele wątków (tj. poprzez główny proces, ISR i inne funkcje sprzętowe, takie jak DMA w mikrokontrolerze), istnieje ryzyko uszkodzenia zmiennej w pamięci.
Odczytywanie zmiennej globalnej w wielu instancjach wewnątrz procesu głównego
Druga sytuacja jest równie niepokojąca, jak jej modyfikacja w różnych wątkach. Rozważmy następujący przykład:
Tutaj w case1, jeżeli ISR wystąpił podczas wykonywania instrukcji przez CPU:
I jeśli Command == 1, CPU wyśle do zmiennej dane równe 3, podczas gdy powinno być 1. Problem ten nie wydarzy się w case2, wspomnianym powyżej.
Uzyskiwanie dostępu do wielobajtowych zmiennych globalnych w systemie 8-bitowym
Korzystanie z wielobajtowych zmiennych globalnych w systemie 8-bitowym wymaga szczególnej uwagi. Gdyż zmienne wielobajtowe są odczytywane z pamięci bajt po bajcie. Należy uważać, aby ISR nie wystąpił i nie zmodyfikował zmiennej wielobajtowej, kiedy jeden lub więcej bajtów zostało już rozszyfrowanych, podczas gdy odczyt nie jest zakończony. Prowadziłoby to do uszkodzenia danych. Poniższy przykład ilustruje ten scenariusz:
Należy zauważyć, że powyższe dwa przypadki zakładają, że I2C_ISR jest przetwarzane natychmiast po wystąpieniu; tj. ISR jest obsługiwany, a dane są odczytywane z bufora, zanim I2C zacznie ponownie aktualizować I2C_BUF (tj. przed odebraniem kolejnych 8 bitów). Założenie to będzie w większości przypadków prawdziwe, biorąc pod uwagę, że I²C działa ze znacznie mniejszą prędkością (np. 100 kHz) w porównaniu do procesora (ogólnie od pojedynczych do setek MHz). I nie ma innych przerwań w systemie lub też całkowite opóźnienie spowodowane przez wszystkie jest mniejsze niż czas na odbieranie 8 bitów (= 8/100kHz tj. 80 µs). Jeśli tak nie jest, może dojść do uszkodzenia danych.
Dostęp do wielobajtowych rejestrów peryferyjnych w systemie 8-bitowym
Niektóre mikrokontrolery wyposażone są w 8-bitowy procesor, ale ich urządzenia peryferyjne mogą mieć rejestry o większym rozmiarze. Na przykład w mikrokontrolerze może być 12-bitowy ADC komunikujący się z 8-bitowym procesorem. W takich przypadkach ważne jest, aby zachować ostrożność podczas odczytywania wielobajtowych rejestrów peryferyjnych w przerwaniu. Dane mogą zostać uszkodzone, gdy w ISR próbuje się uzyskać dostęp do wielobajtowego rejestru w 8-bitowym MCU. Rozważmy następujący ISR, który próbuje odczytać bajty High i Low z konwersji ADC:
Trzeba zauważyć, że Case1 (niepoprawny) w Table3 jest podobny do Case2 (poprawny) w Table2. Jak można, wykorzystując tę samą metodę, uzyskać raz poprawny, a następnie niewłaściwy wynik? Odpowiedź jest prosta — Case1 w Table3, jak i Case2 w Table2, opisane powyżej, działałyby dobrze, tak długo, jak zagwarantowana będzie poprawna i kompletna obsługa ADC_ISR i I2C_ISR, zanim zostaną one wywołane ponownie.
Jak widać w Table3, w systemie są jeszcze dwa inne przerwania. W tym przypadku należy wziąć pod uwagę, że ADC działa w sposób ciągły i że ADC_ISR jest wyzwalany po zakończeniu konwersji ADC. Należy rozważyć, że ADC_MSB i ADC_LSB są sprzętowo zsynchronizowane, więc rejestry wyników konwersji ADC będą aktualizowane przez operację zatrzasku sprzętowego, gdy tylko konwersja ADC zostanie zakończona. Zanalizujmy teraz sytuację, w której ADC_ISR został wyzwolony po raz pierwszy, gdy ISR1 był już wykonywany. Zwiększa to opóźnienie w obsłudze ADC_ISR. Następnie pomyślmy, że kolejna konwersja ADC jest zakończona, gdy tylko MSB poprzedniej został odczytany w ISR. W tym przypadku jednako rejestr ADC_MSB, jak i ADC_LSB zostałby zaktualizowany nową wartością. Jednak poprzednie przerwanie nadal się wykonuje i ADC_MSB z minionej konwersji został już odczytany. Zweryfikowany teraz ADC_LSB będzie pochodził z bieżącej konwersji. Dane są uszkodzone i niepoprawne.
Właściwe dane to albo 0x1FF, albo 0x200. Data, jednakże przyjęła wartość 0x0100, która jest niewłaściwa. Istnieją dwa sposoby rozwiązania tego problemu: skontrolowanie, że ADC_ISR zostanie zwieńczone przed oczekiwanym zakończeniem kolejnej konwersji oraz odczytanie danych tak, jak pokazano to w Case2 w Table3 powyżej.
Trzeba zauważyć, że każde przerwanie ma na ogół coś podobnego do rejestru: „Interrupt_Clear”. Zapis do niego uniemożliwia wysłanie przerwania do kontrolera. A odczyt zwraca aktualny stan przerwania, tj. czy odpowiednie z nich zostało zarejestrowane przez urządzenie. Rejestr Interrupt_Clear może być użyty do zrozumienia, czy konwersja ADC została zakończona podczas odczytu MSB i LSB poprzedniej konwersji. Ponownie działać może odczyt wyniku konwersji, jeśli to samo miało miejsce.
Zmienne lokalne — poznaj swój kompilator
Kompilatory mogą alokować pamięć dla zmiennych lokalnych na dwa różne sposoby. Aby zrozumieć problemy, które mogą wystąpić w wyniku użycia zmiennych lokalnych w przerwaniach, a w tym metody, których można użyć, aby temu zapobiec, powinniśmy najpierw zrozumieć, w jaki sposób zmienne lokalne są przechowywane w pamięci. Poniżej opisano dwie różne metody:
Wykorzystywanie stosu do przechowywania zmiennych lokalnych
W tym przypadku zmienne lokalne są tworzone na stosie za każdym razem, gdy wywoływana jest funkcja. Jest to najczęstszy sposób ich przechowywania, ponieważ stos, a tym samym pamięć używana przez wywoływaną funkcję, zostanie zwolniona, gdy sterowanie będzie zwrócone do funkcji wywołującej. W odniesieniu do takich kompilatorów ważne jest, aby zdawać sobie sprawę z całkowitego wykorzystania stosu. Gdyż, jeśli program zawiera dużą liczbę wywołań funkcji w postaci drzewa — to jedno z nich uruchamia drugie, to skutkuje trzecim i tak dalej.
W przypadku przerwań analiza użycia stosu staje się jeszcze ważniejsza, ponieważ mogą one być wyzwalane w dowolnym momencie podczas realizacji programu. Całkowite użycie stosu, a tym samym wymaganą pamięć RAM, można obliczyć w następujący sposób:
Przykład I: Tylko jedno ISR obsługiwane naraz
Jeśli przerwania nie są zagnieżdżone, obsługiwane jest tylko jedno na raz. W tym przypadku maksymalne zużycie w dowolnym momencie jest obliczane jako całkowity stos wymagany dla największego drzewa wywołań (w tym miejsce na zmienne lokalne, licznik programu, argumenty funkcji itp.). Plus stos ISR, który zużywa największą ilość pamięci.
Przykład II: Zagnieżdżone przerwania
Jeśli przerwania są zagnieżdżone, ISR może wywłaszczyć inne, a nawet siebie. W takim przypadku całkowite użycie stosu może być nadmierne. Stąd należy unikać zagnieżdżonych przerwań, nawet jeśli nie ma w systemie zmiennych lokalnych. Dzieje się tak, ponieważ kiedy ISR jest wyzwalany wszystkie (lub prawie, w zależności od kompilatora) funkcje specjalne i rejestry ogólnego przeznaczenia używane przez ISR są również przechowywane na stosie.
Jeśli pozwoli się przerwaniu na wywłaszczenie, stos będzie cały czas zapełniał się (w przypadku, w którym przerwanie nadal występuje i wywłaszcza ISR) i łatwo przesyci. Należy unikać zagnieżdżania przerwań, chyba że jest to absolutnie konieczne, a w danym ujęciu trzeba się upewnić, że ISR nigdy nie będzie wywłaszczone. Można to zapewnić, analizując całkowity czas wymagany do wykonania ISR i minimalne możliwe opóźnienie między dwoma wystąpieniami przerwania. Jeśli maksymalne użycie stosu przekracza dostępną pamięć RAM, dane w pamięci mogą zostać uszkodzone, gdy mikrokontroler uruchomi program. Takie błędy mogą być krytyczne i trudne do wykrycia, dlatego należy eliminować potencjał ich wystąpienia.
Ustalone lokalizacje zmiennych lokalnych w pamięci i nakładanie się jej
Niektóre kompilatory nie zapisują zmiennych lokalnych na stosie, jak to zwykle robi się w C. Zamiast tego używają stałych lokalizacji pamięci do przechowywania zmiennych lokalnych i argumentów funkcji oraz dzielą te obszary między lokalne zmienne funkcji, które się nie wywołują. Taka forma nakładania się pamięci jest wykonywana w celu zapobiegania przepełnieniom stosu i umożliwienia efektywnego wykorzystania. Użycie zmiennych lokalnych w przerwaniach z takimi kompilatorami wymaga szczególnej uwagi. Aby zrozumieć dlaczego, przyjrzyjmy się najpierw, jak działa nakładanie się pamięci.
Nakładanie stałych lokalizacji pamięci w celu przechowywania zmiennych lokalnych i argumentów funkcji pod tym samym adresem uzyskuje się przy użyciu dobrze zdefiniowanej procedury. Po pierwsze, linker buduje drzewo wywołań programu, dzięki czemu może dowiedzieć się, które segmenty danych, dla jakich funkcji wzajemnie się wykluczają, i w ten sposób je nakładać. Załóżmy na przykład, że funkcja Main() wywołuje funkcję A, B i C. Funkcja A używa 10 bajtów, B 20 bajtów, a C 16 bajtów zmiennych lokalnych. Zakładając, że A, B i C nie wywołują się wzajemnie, pamięć, z której korzystają, może zostać na siebie nałożona. Tak więc zamiast 46 bajtów pamięci danych (10 dla A + 20 dla B + 16 dla C) zużywane jest tylko 20 (ilość zajmowana przez: „największą” z funkcji).
Problemy ze zmiennymi lokalnymi w ISR, gdy pamięć jest nakładana
Jeśli zmienne lokalne przerwania zajmują tę samą przestrzeń pamięci, co jedna z funkcji w głównej gałęzi programu. A dodatkowo przerwanie zostanie wyzwolone, gdy jedna z funkcji korzystających z tej samej przestrzeni pamięci jest wykonywana, dane w tych zmiennych lokalnych mogą być narażone na uszkodzenie.
Poniższe techniki są pomocne w zapobieganiu naruszeń przestrzeni danych przez przerwania:
* Użycie słowa kluczowego: „static” w zmiennych lokalnych stosowanych w przerwaniach. Przydziela to osobną przestrzeń danych dla tych zmiennych, która nie jest nałożona na żadną inną funkcję.
* Wykorzystanie niższego poziomu ustawień optymalizacji dla kompilatora, który wyłączy nakładanie zmiennych. Szczegóły poziomu optymalizacji można znaleźć w podręcznikach referencyjnych do eksploatowanego kompilatora/linkera.
* Zużytkowanie innej przestrzeni danych dla zmiennych normalnych i lokalnych przerwania. Na przykład, zastosowanie XDATA dla: „normalnych” zmiennych lokalnych i DATA dla zmiennych lokalnych przerwania. Można to zrobić, używając specyfikatorów pamięci w deklaracjach zmiennych lub posiłkując się różnymi modelami pamięci dla odmiennych plików.
* W zależności od ustawień optymalizacji kompilator może spożytkować banki rejestrów, zamiast pamięci RAM dla zmiennych lokalnych przerwania. W takim przypadku trzeba koniecznie upewnić się, że udział pamięci w przerwaniu nie jest większy niż bank rejestrów, z którego korzysta podczas jego obsługi.
Powyżej omówiliśmy część aspektów zarządzania pamięcią w ramach przetwarzania przerwań. W kolejnej odsłonie z tej serii pójdziemy trochę dalej. Przedstawione zostaną bufory danych i pamięć współdzielona. A więc to, na co należy uważać oraz jakie są dobre i złe praktyki podczas korzystania z buforów i przerwań.
Źródło: https://www.embedded.com/interrupts-short-simple-part-2-variables-buffers-latencies/
Zmienne globalne — wiedz, kiedy są modyfikowane
Zmienne globalne muszą być ostrożnie obsługiwane, gdy są używane z przerwaniami, ponieważ te są generalnie asynchroniczne. I jeżeli zmienna globalna jest modyfikowana przez ISR, może zostać zmieniona w dowolnym momencie. Musimy mieć na uwadze następujące aspekty:
Odczyt/zapis zmiennych globalnych w wielu miejscach
Zmienne globalne powinny być modyfikowane tylko w kilku koniecznych obszarach wewnątrz programu. Gdyż, jeśli odbywa się to w oparciu o wiele wątków (tj. poprzez główny proces, ISR i inne funkcje sprzętowe, takie jak DMA w mikrokontrolerze), istnieje ryzyko uszkodzenia zmiennej w pamięci.
Odczytywanie zmiennej globalnej w wielu instancjach wewnątrz procesu głównego
Druga sytuacja jest równie niepokojąca, jak jej modyfikacja w różnych wątkach. Rozważmy następujący przykład:
Tutaj w case1, jeżeli ISR wystąpił podczas wykonywania instrukcji przez CPU:
Kod: C / C++
I jeśli Command == 1, CPU wyśle do zmiennej dane równe 3, podczas gdy powinno być 1. Problem ten nie wydarzy się w case2, wspomnianym powyżej.
Uzyskiwanie dostępu do wielobajtowych zmiennych globalnych w systemie 8-bitowym
Korzystanie z wielobajtowych zmiennych globalnych w systemie 8-bitowym wymaga szczególnej uwagi. Gdyż zmienne wielobajtowe są odczytywane z pamięci bajt po bajcie. Należy uważać, aby ISR nie wystąpił i nie zmodyfikował zmiennej wielobajtowej, kiedy jeden lub więcej bajtów zostało już rozszyfrowanych, podczas gdy odczyt nie jest zakończony. Prowadziłoby to do uszkodzenia danych. Poniższy przykład ilustruje ten scenariusz:
Należy zauważyć, że powyższe dwa przypadki zakładają, że I2C_ISR jest przetwarzane natychmiast po wystąpieniu; tj. ISR jest obsługiwany, a dane są odczytywane z bufora, zanim I2C zacznie ponownie aktualizować I2C_BUF (tj. przed odebraniem kolejnych 8 bitów). Założenie to będzie w większości przypadków prawdziwe, biorąc pod uwagę, że I²C działa ze znacznie mniejszą prędkością (np. 100 kHz) w porównaniu do procesora (ogólnie od pojedynczych do setek MHz). I nie ma innych przerwań w systemie lub też całkowite opóźnienie spowodowane przez wszystkie jest mniejsze niż czas na odbieranie 8 bitów (= 8/100kHz tj. 80 µs). Jeśli tak nie jest, może dojść do uszkodzenia danych.
Dostęp do wielobajtowych rejestrów peryferyjnych w systemie 8-bitowym
Niektóre mikrokontrolery wyposażone są w 8-bitowy procesor, ale ich urządzenia peryferyjne mogą mieć rejestry o większym rozmiarze. Na przykład w mikrokontrolerze może być 12-bitowy ADC komunikujący się z 8-bitowym procesorem. W takich przypadkach ważne jest, aby zachować ostrożność podczas odczytywania wielobajtowych rejestrów peryferyjnych w przerwaniu. Dane mogą zostać uszkodzone, gdy w ISR próbuje się uzyskać dostęp do wielobajtowego rejestru w 8-bitowym MCU. Rozważmy następujący ISR, który próbuje odczytać bajty High i Low z konwersji ADC:
Trzeba zauważyć, że Case1 (niepoprawny) w Table3 jest podobny do Case2 (poprawny) w Table2. Jak można, wykorzystując tę samą metodę, uzyskać raz poprawny, a następnie niewłaściwy wynik? Odpowiedź jest prosta — Case1 w Table3, jak i Case2 w Table2, opisane powyżej, działałyby dobrze, tak długo, jak zagwarantowana będzie poprawna i kompletna obsługa ADC_ISR i I2C_ISR, zanim zostaną one wywołane ponownie.
Jak widać w Table3, w systemie są jeszcze dwa inne przerwania. W tym przypadku należy wziąć pod uwagę, że ADC działa w sposób ciągły i że ADC_ISR jest wyzwalany po zakończeniu konwersji ADC. Należy rozważyć, że ADC_MSB i ADC_LSB są sprzętowo zsynchronizowane, więc rejestry wyników konwersji ADC będą aktualizowane przez operację zatrzasku sprzętowego, gdy tylko konwersja ADC zostanie zakończona. Zanalizujmy teraz sytuację, w której ADC_ISR został wyzwolony po raz pierwszy, gdy ISR1 był już wykonywany. Zwiększa to opóźnienie w obsłudze ADC_ISR. Następnie pomyślmy, że kolejna konwersja ADC jest zakończona, gdy tylko MSB poprzedniej został odczytany w ISR. W tym przypadku jednako rejestr ADC_MSB, jak i ADC_LSB zostałby zaktualizowany nową wartością. Jednak poprzednie przerwanie nadal się wykonuje i ADC_MSB z minionej konwersji został już odczytany. Zweryfikowany teraz ADC_LSB będzie pochodził z bieżącej konwersji. Dane są uszkodzone i niepoprawne.
Kod: C / C++
Właściwe dane to albo 0x1FF, albo 0x200. Data, jednakże przyjęła wartość 0x0100, która jest niewłaściwa. Istnieją dwa sposoby rozwiązania tego problemu: skontrolowanie, że ADC_ISR zostanie zwieńczone przed oczekiwanym zakończeniem kolejnej konwersji oraz odczytanie danych tak, jak pokazano to w Case2 w Table3 powyżej.
Trzeba zauważyć, że każde przerwanie ma na ogół coś podobnego do rejestru: „Interrupt_Clear”. Zapis do niego uniemożliwia wysłanie przerwania do kontrolera. A odczyt zwraca aktualny stan przerwania, tj. czy odpowiednie z nich zostało zarejestrowane przez urządzenie. Rejestr Interrupt_Clear może być użyty do zrozumienia, czy konwersja ADC została zakończona podczas odczytu MSB i LSB poprzedniej konwersji. Ponownie działać może odczyt wyniku konwersji, jeśli to samo miało miejsce.
Zmienne lokalne — poznaj swój kompilator
Kompilatory mogą alokować pamięć dla zmiennych lokalnych na dwa różne sposoby. Aby zrozumieć problemy, które mogą wystąpić w wyniku użycia zmiennych lokalnych w przerwaniach, a w tym metody, których można użyć, aby temu zapobiec, powinniśmy najpierw zrozumieć, w jaki sposób zmienne lokalne są przechowywane w pamięci. Poniżej opisano dwie różne metody:
Wykorzystywanie stosu do przechowywania zmiennych lokalnych
W tym przypadku zmienne lokalne są tworzone na stosie za każdym razem, gdy wywoływana jest funkcja. Jest to najczęstszy sposób ich przechowywania, ponieważ stos, a tym samym pamięć używana przez wywoływaną funkcję, zostanie zwolniona, gdy sterowanie będzie zwrócone do funkcji wywołującej. W odniesieniu do takich kompilatorów ważne jest, aby zdawać sobie sprawę z całkowitego wykorzystania stosu. Gdyż, jeśli program zawiera dużą liczbę wywołań funkcji w postaci drzewa — to jedno z nich uruchamia drugie, to skutkuje trzecim i tak dalej.
W przypadku przerwań analiza użycia stosu staje się jeszcze ważniejsza, ponieważ mogą one być wyzwalane w dowolnym momencie podczas realizacji programu. Całkowite użycie stosu, a tym samym wymaganą pamięć RAM, można obliczyć w następujący sposób:
Przykład I: Tylko jedno ISR obsługiwane naraz
Jeśli przerwania nie są zagnieżdżone, obsługiwane jest tylko jedno na raz. W tym przypadku maksymalne zużycie w dowolnym momencie jest obliczane jako całkowity stos wymagany dla największego drzewa wywołań (w tym miejsce na zmienne lokalne, licznik programu, argumenty funkcji itp.). Plus stos ISR, który zużywa największą ilość pamięci.
Przykład II: Zagnieżdżone przerwania
Jeśli przerwania są zagnieżdżone, ISR może wywłaszczyć inne, a nawet siebie. W takim przypadku całkowite użycie stosu może być nadmierne. Stąd należy unikać zagnieżdżonych przerwań, nawet jeśli nie ma w systemie zmiennych lokalnych. Dzieje się tak, ponieważ kiedy ISR jest wyzwalany wszystkie (lub prawie, w zależności od kompilatora) funkcje specjalne i rejestry ogólnego przeznaczenia używane przez ISR są również przechowywane na stosie.
Jeśli pozwoli się przerwaniu na wywłaszczenie, stos będzie cały czas zapełniał się (w przypadku, w którym przerwanie nadal występuje i wywłaszcza ISR) i łatwo przesyci. Należy unikać zagnieżdżania przerwań, chyba że jest to absolutnie konieczne, a w danym ujęciu trzeba się upewnić, że ISR nigdy nie będzie wywłaszczone. Można to zapewnić, analizując całkowity czas wymagany do wykonania ISR i minimalne możliwe opóźnienie między dwoma wystąpieniami przerwania. Jeśli maksymalne użycie stosu przekracza dostępną pamięć RAM, dane w pamięci mogą zostać uszkodzone, gdy mikrokontroler uruchomi program. Takie błędy mogą być krytyczne i trudne do wykrycia, dlatego należy eliminować potencjał ich wystąpienia.
Ustalone lokalizacje zmiennych lokalnych w pamięci i nakładanie się jej
Niektóre kompilatory nie zapisują zmiennych lokalnych na stosie, jak to zwykle robi się w C. Zamiast tego używają stałych lokalizacji pamięci do przechowywania zmiennych lokalnych i argumentów funkcji oraz dzielą te obszary między lokalne zmienne funkcji, które się nie wywołują. Taka forma nakładania się pamięci jest wykonywana w celu zapobiegania przepełnieniom stosu i umożliwienia efektywnego wykorzystania. Użycie zmiennych lokalnych w przerwaniach z takimi kompilatorami wymaga szczególnej uwagi. Aby zrozumieć dlaczego, przyjrzyjmy się najpierw, jak działa nakładanie się pamięci.
Nakładanie stałych lokalizacji pamięci w celu przechowywania zmiennych lokalnych i argumentów funkcji pod tym samym adresem uzyskuje się przy użyciu dobrze zdefiniowanej procedury. Po pierwsze, linker buduje drzewo wywołań programu, dzięki czemu może dowiedzieć się, które segmenty danych, dla jakich funkcji wzajemnie się wykluczają, i w ten sposób je nakładać. Załóżmy na przykład, że funkcja Main() wywołuje funkcję A, B i C. Funkcja A używa 10 bajtów, B 20 bajtów, a C 16 bajtów zmiennych lokalnych. Zakładając, że A, B i C nie wywołują się wzajemnie, pamięć, z której korzystają, może zostać na siebie nałożona. Tak więc zamiast 46 bajtów pamięci danych (10 dla A + 20 dla B + 16 dla C) zużywane jest tylko 20 (ilość zajmowana przez: „największą” z funkcji).
Problemy ze zmiennymi lokalnymi w ISR, gdy pamięć jest nakładana
Jeśli zmienne lokalne przerwania zajmują tę samą przestrzeń pamięci, co jedna z funkcji w głównej gałęzi programu. A dodatkowo przerwanie zostanie wyzwolone, gdy jedna z funkcji korzystających z tej samej przestrzeni pamięci jest wykonywana, dane w tych zmiennych lokalnych mogą być narażone na uszkodzenie.
Poniższe techniki są pomocne w zapobieganiu naruszeń przestrzeni danych przez przerwania:
* Użycie słowa kluczowego: „static” w zmiennych lokalnych stosowanych w przerwaniach. Przydziela to osobną przestrzeń danych dla tych zmiennych, która nie jest nałożona na żadną inną funkcję.
* Wykorzystanie niższego poziomu ustawień optymalizacji dla kompilatora, który wyłączy nakładanie zmiennych. Szczegóły poziomu optymalizacji można znaleźć w podręcznikach referencyjnych do eksploatowanego kompilatora/linkera.
* Zużytkowanie innej przestrzeni danych dla zmiennych normalnych i lokalnych przerwania. Na przykład, zastosowanie XDATA dla: „normalnych” zmiennych lokalnych i DATA dla zmiennych lokalnych przerwania. Można to zrobić, używając specyfikatorów pamięci w deklaracjach zmiennych lub posiłkując się różnymi modelami pamięci dla odmiennych plików.
* W zależności od ustawień optymalizacji kompilator może spożytkować banki rejestrów, zamiast pamięci RAM dla zmiennych lokalnych przerwania. W takim przypadku trzeba koniecznie upewnić się, że udział pamięci w przerwaniu nie jest większy niż bank rejestrów, z którego korzysta podczas jego obsługi.
Powyżej omówiliśmy część aspektów zarządzania pamięcią w ramach przetwarzania przerwań. W kolejnej odsłonie z tej serii pójdziemy trochę dalej. Przedstawione zostaną bufory danych i pamięć współdzielona. A więc to, na co należy uważać oraz jakie są dobre i złe praktyki podczas korzystania z buforów i przerwań.
Źródło: https://www.embedded.com/interrupts-short-simple-part-2-variables-buffers-latencies/
Fajne? Ranking DIY
