W tym poście chciałbym opisać zastosowanie biblioteki CException w układzie STM32H7.
Biblioteka CException pozwala na wprowadzenie obsługi wyjątków w języku C. Można ją pobrać pod tym linkiem. Wykorzystuje ona funkcje setjmp oraz longjmp.
Opis, Przykładowe użycie:
Do działa potrzebujemy dwóch plików, które dołączamy do projektu:
- CException.c
- CException.h
Pliki zostają po prostu skopiowane do projektu, który został wygenerowany przez program CubeMx. Natomiast biblioteka jest łatwa w implementacji i nie będzie żadnego problemu z jej wdrożeniem na innych układach. Warunkiem jest jedynie zaimplementowana obsługa funkcji setjmp oraz longjmp.
Biblioteka wprowadza dodatkowe bloki Try{ } Catch { }, które pozawalają na obsługę wyjątków.
Bloki Try oraz Catch są zdefiniowane jako define. Zdefiniowano je w pliku .h:
- //Try (see C file for explanation)
- #define Try \
- { \
- jmp_buf *PrevFrame, NewFrame; \
- unsigned int MY_ID = CEXCEPTION_GET_ID; \
- PrevFrame = CExceptionFrames[MY_ID].pFrame; \
- CExceptionFrames[MY_ID].pFrame = (jmp_buf*)(&NewFrame); \
- CExceptionFrames[MY_ID].Exception = CEXCEPTION_NONE; \
- CEXCEPTION_HOOK_START_TRY; \
- if (setjmp(NewFrame) == 0) { \
- if (1)
- //Catch (see C file for explanation)
- #define Catch(e) \
- else { } \
- CExceptionFrames[MY_ID].Exception = CEXCEPTION_NONE; \
- CEXCEPTION_HOOK_HAPPY_TRY; \
- } \
- else \
- { \
- e = CExceptionFrames[MY_ID].Exception; \
- (void)e; \
- CEXCEPTION_HOOK_START_CATCH; \
- } \
- CExceptionFrames[MY_ID].pFrame = PrevFrame; \
- CEXCEPTION_HOOK_AFTER_TRY; \
- } \
- if (CExceptionFrames[CEXCEPTION_GET_ID].Exception != CEXCEPTION_NONE)
Ich dokładne działanie zostało opisane w pliku CException.c w komentarzu.
Poniżej jakaś prosta funkcja prezentująca zastosowanie biblioteki:
- void CheckThrow(uint8_t a, uint8_t b) {
- uint8_t sum = a + b;
- if(sum > 150 && sum < 200)
- {
- printf("Throw 0x54\r\n");
- fflush(stdout);
- Throw(0x54);
- }
- else if(sum > 200)
- {
- printf("Throw 0x88\r\n");
- fflush(stdout);
- Throw(0x88);
- }
- else
- {
- //do some stuff
- }
- }
- void Worker_Test(void)
- {
- volatile CEXCEPTION_T e;
- Try {
- CheckThrow(100, 46);
- CheckThrow(200, 1);
- CheckThrow(100, 59);
- }
- Catch(e) {
- if(e == 0x54)
- {
- printf("catch code 0x54\r\n");
- fflush(stdout);
- }
- else if(e == 0x88)
- {
- printf("catch code 0x88\r\n");
- fflush(stdout);
- }
- else{
- printf("catch diff code");
- }
- }
- }
Blok Catch zostanie wywołany po pierwszej instukcji Throw, co oznacza, że ostatnia funkcja CheckThrow nie zostanie wykonana. Dodatkowo wszystkie zadania w funkcji, które są umieszczone za blokiem Throw() nie zostaną wykonane. Wynika to z faktu, że w bloku Throw() znajduje się wykonanie skoku (longjmp) do wcześniej ustawionego skoku przez blok Try().
W pliku CException.h znajdują się cztery wartości na jakie warto zwrócić uwagę:
- //This is the value to assign when there isn't an exception
- #ifndef CEXCEPTION_NONE
- #define CEXCEPTION_NONE (0x5A5A5A5A)
- #endif
- //This is number of exception stacks to keep track of (one per task)
- #ifndef CEXCEPTION_NUM_ID
- #define CEXCEPTION_NUM_ID (1) //there is only the one stack by default
- #endif
- //This is the method of getting the current exception stack index (0 if only one stack)
- #ifndef CEXCEPTION_GET_ID
- #define CEXCEPTION_GET_ID (0) //use the first index always because there is only one anyway
- #endif
- //The type to use to store the exception values.
- #ifndef CEXCEPTION_T
- #define CEXCEPTION_T unsigned int
- #endif
CEXCEPTION_NONE jest wartością jaka jest domyślnie ustawiana dla wyjątku, gdy nie został wywołany żaden wyjątek. Jest to wartości czterobajtowa, ponieważ zmienna CEXCEPTION_T została ustawiona jako unsigned int. Zmienna w formacie unsigned int jest w większości przypadków 4 bajtowa (dla STM też jest to wartość 4 bajtowa), natomiast nie musi tak być. Dlatego warto zweryfikować jakie wartości możemy wprowadzić do zmiennej zanim zaczniemy pracę z biblioteką.
CEXCEPTION_NUM_ID jest to ilość wyjątków jakie należy sprawdzać. Według autora należy zastosować jeden numer dla jednego procesu.
CEXCEPTION_GET_ID definiuje z ilu indeksów tablicy, ze zdefiniowanymi wyjątkami możemy pobierać dane.
Ważne:
Na co należy zwrócić uwagę podczas stosowania tej biblioteki.
W przypadku biblioteki CException należy pamiętać aby nie używać instrukcji return w bloku Try. Dotyczy to także instrukcji goto. Wynika to z tego, że bloki te alokują pamięć, która zostaje wyczyszczona i zwolniona dopiero po zakończeniu bloku Catch. Wspomniane instrukcje doprowadzą do wycieku pamięci w układzie bądź do innego nie przewidywalnego zachowania.
Dodatkowo w przypadku gdy chcemy aktualizować dane w bloku Try{ } oraz uzyskać do nich dostęp w po Throw(), należy użyć modyfikatora volatile do zmiennej. Pozwoli to na poprawne ustawienie danych w zmiennej. np. funkcja:
- #define ERROR_THROW_CODE 0x34
- void UpdateTest(uint8_t a)
- {
- volatile CEXCEPTION_T e;
- uint8_t b = 0;
- Try {
- a = 7 + 5;
- b = a;
- Throw(ERROR_THROW_CODE);
- }
- Catch(e) {
- if(e == ERROR_THROW_CODE)
- {
- printf("ERROR_THROW_CODE Catch\r\n");
- }
- else
- {
- printf("Diff Code Catch\r\n");
- }
- }
- printf("a = %d, b = %d", a, b);
- fflush(stdout);
- }
Z włączoną optymalizacją -O3 zwróci następujące informacje:
- ERROR_THROW_CODE Catch
- a = 12, b = 0
Teraz jeśli zostawimy -O3 oraz dołożymy modyfikator volatile to otrzymamy:
- ERROR_THROW_CODE Catch
- a = 12, b = 12
W obu przypadkach gdy zamienimy optymalizację na -O0 to wszystko będzie działało poprawnie. Wynika z tego, że zawsze w takich przypadkach należy umieszczać modyfikator volatile, aby uniknąć późniejszych błędów.