Sekcje krytyczne na Cortex-M3

10

Zastanawiam się trochę nad implementacją krytycznych sekcji kodu na Cortex-M3, w których wyjątki nie są dozwolone z powodu ograniczeń czasowych lub problemów z współbieżnością.

W moim przypadku korzystam z LPC1758 i na pokładzie mam transceiver TI CC2500. CC2500 ma piny, które mogą być użyte jako linie przerwania dla danych w buforze RX i wolnej przestrzeni w buforze TX.

Na przykład chcę mieć bufor TX w pamięci SRAM mojego MCU, a kiedy w buforze TX transceivera jest wolne miejsce, chcę tam zapisać te dane. Ale procedura, która umieszcza dane w buforze SRAM, oczywiście nie może zostać przerwana przez przerwanie wolnego miejsca w TX. Tak więc chcę tymczasowo wyłączyć przerwania podczas wykonywania tej procedury wypełniania bufora, ale aby wszelkie przerwania występujące podczas tej procedury były wykonywane po jej zakończeniu.

Jak najlepiej to zrobić na Cortex-M3?

Emil Eriksson
źródło

Odpowiedzi:

11

Cortex M3 obsługuje użyteczną parę operacji operacyjnych (wspólnych również w wielu innych maszynach) o nazwie „Load-Exclusive” (LDREX) i „Store-Exclusive” (STREX). Koncepcyjnie operacja LDREX wykonuje ładowanie, ustawia także specjalny sprzęt, aby obserwować, czy załadowana lokalizacja może być zapisana przez coś innego. Wykonanie STREX na adres użyty przez ostatni LDREX spowoduje, że adres zostanie zapisany tylko wtedy, gdy nic innego nie zostanie napisane najpierw . Instrukcja STREX załaduje rejestr z wartością 0, jeśli sklep miał miejsce, lub 1, jeśli został przerwany.

Pamiętaj, że STREX jest często pesymistyczny. Istnieje wiele sytuacji, w których może zdecydować się nie prowadzić sklepu, nawet jeśli dane miejsce nie zostało dotknięte. Na przykład przerwanie między LDREX a STREX spowoduje, że STREX przyjmie, że obserwowana lokalizacja mogła zostać trafiona. Z tego powodu zwykle dobrym pomysłem jest zminimalizowanie ilości kodu między LDREX a STREX. Rozważmy na przykład coś takiego:

inline void safe_increment (uint32_t * addr)
{
  uint32_t nowa_wartość;
  zrobić
  {
    nowa_wartość = __ldrex (addr) + 1;
  } while (__ strex (nowa_wartość, adres));
}

który kompiluje się do czegoś takiego:

; Załóżmy, że R0 zawiera dany adres; r1 zniszczony
lp:
  ldrex r1, [r0]
  dodaj r1, r1, # 1
  strex r1, r1, [r0]
  cmp r1, # 0; Sprawdź, czy niezerowa
  bne lp
  .. kod trwa

Przez większość czasu wykonywania kodu między LDREX i STREX nic się nie stanie, aby je „zakłócić”, więc STREX odniesie sukces bez zbędnych ceregieli. Jeśli jednak nastąpi natychmiastowe przerwanie po instrukcji LDREX lub ADD, STREX nie wykona zapisu, ale zamiast tego kod wróci do odczytu (ewentualnie zaktualizowanej) wartości [r0] i wyliczy nową wartość przyrostową w oparciu o to.

Wykorzystanie LDREX / STREX do tworzenia operacji takich jak safe_increment pozwala nie tylko zarządzać krytycznymi sekcjami, ale także w wielu przypadkach, aby uniknąć ich potrzeby.

supercat
źródło
Więc nie ma sposobu na „blokowanie” przerwań, aby można je było ponownie obsłużyć po ich odblokowaniu? Zdaję sobie sprawę, że jest to prawdopodobnie nieeleganckie rozwiązanie, nawet jeśli jest to możliwe, ale chcę tylko dowiedzieć się więcej na temat obsługi przerwań ARM.
Emil Eriksson
3
Możliwe jest wyłączenie przerwań, a na Cortex-M0 często nie ma praktycznej alternatywy. Uważam, że podejście LDREX / STREX jest czystsze niż wyłączanie przerwań, choć wprawdzie w wielu przypadkach nie ma to większego znaczenia (myślę, że włączanie i wyłączanie kończy się na jednym cyklu, a wyłączanie przerwań na pięć cykli prawdopodobnie nie jest wielkim problemem) . Zauważ, że podejście ldrex / strex będzie działać, jeśli kod zostanie zmigrowany do wielordzeniowego procesora, podczas gdy podejście wyłączające przerwania nie. Ponadto niektóre kody uruchamiania RTOS mają ograniczone uprawnienia, które nie mogą wyłączać przerwań.
supercat
Prawdopodobnie i tak skończę z FreeRTOS, więc nie będę tego robić sam, ale i tak chciałbym się uczyć. Jakiej metody wyłączania przerwań należy użyć do blokowania przerwań, jak opisano, w przeciwieństwie do odrzucania przerwań pojawiających się podczas procedury? Jak mam to zrobić, jeśli chcę je odrzucić?
Emil Eriksson
Nie można ufać jednej powyższej odpowiedzi, ponieważ w powiązanym kodzie brakuje nawiasu: while(STREXW(new_value, addr); Jak możemy uważać, że to, co uważasz za poprawne, jeśli Twój kod nawet się nie skompiluje?
@Tim: Przepraszam, że moje pisanie nie jest idealne; Nie mam faktycznego kodu, który napisałem do porównania, więc nie pamiętam, czy używany system używał STREXW czy __STREXW, ale referencje kompilatora podają __strex jako wartość wewnętrzną (w przeciwieństwie do STREXW, która jest ograniczona do 32-bitowy STREX, wewnętrzne zastosowania __strex generują STREXB, STREXH lub STREX w zależności od dostarczonego rozmiaru wskaźnika)
supercat
4

Wygląda na to, że potrzebujesz oprogramowania okrągłych buforów lub FIFO w oprogramowaniu MCU. Śledząc dwa wskaźniki lub wskaźniki do tablicy w celu odczytu i zapisu, możesz mieć dostęp do tego samego bufora zarówno na pierwszym planie, jak iw tle bez zakłóceń.

Kod pierwszego planu można w dowolnym momencie zapisać w buforze cyklicznym. Wstawia dane we wskaźniku zapisu, a następnie zwiększa wskaźnik zapisu.

Kod w tle (obsługa przerwań) zużywa dane ze wskaźnika odczytu i zwiększa wskaźnik odczytu.

Gdy wskaźniki odczytu i zapisu są równe, bufor jest pusty, a proces w tle nie wysyła danych. Gdy bufor jest pełny, proces pierwszego planu odmawia zapisania (lub może zastąpić stare dane, w zależności od potrzeb).

Używanie okrągłych buforów do oddzielania czytników i programów piszących powinno wyeliminować potrzebę wyłączania przerwań.

Toby Jaffey
źródło
Tak, oczywiście użyję buforów okrągłych, ale przyrost i spadek nie są operacjami atomowymi.
Emil Eriksson
3
@Emil: Nie muszą tak być. W przypadku klasycznego bufora okrągłego z dwoma wskaźnikami i jednym „nieużywalnym” gniazdem wszystko, co jest konieczne, to zapisy w pamięci atomowej i wymuszane w kolejności. Czytelnik posiada jeden wskaźnik, pisarz jest właścicielem drugiego i chociaż oba mogą odczytać dowolny wskaźnik, tylko właściciel wskaźnika pisze jego wskaźnik. W tym momencie wszystko, czego potrzebujesz, to zapisy w kolejności atomowej.
John R. Strohm
2

Nie pamiętam dokładnej lokalizacji, ale w bibliotekach pochodzących z ARM (nie TI, ARM, powinien być pod CMSIS lub coś w tym rodzaju, używam ST, ale pamiętam gdzieś, że ten plik pochodzi z ARM, więc powinieneś go również mieć ) dostępna jest opcja globalnego wyłączenia przerwań. Jest to wywołanie funkcji. (Nie jestem w pracy, ale jutro sprawdzę dokładną funkcję). Chciałbym zakończyć to ładną nazwą w twoim systemie i wyłączyć przerwania, zrób swoje i włącz ponownie. Powiedziawszy to, lepszym rozwiązaniem byłoby zaimplementowanie semafora lub struktury kolejki zamiast globalnego wyłączenia przerwań.

Ktc
źródło