wtorek, 9 listopada 2021

STM32H7 - FreeRtos - Semafor binarny

W tym poście chciałem opisać sposób działania semafora binarnego podłączonego do przerwania od pinu.
 
[Źródło: https://www.st.com/content/st_com/en/products/evaluation-tools/product-evaluation-tools/mcu-mpu-eval-tools/stm32-mcu-mpu-eval-tools/stm32-nucleo-boards/nucleo-h753zi.html#overview]

Semafor binarny:


W celu synchronizacji zadań wywoływanych z przerwań, zwłaszcza takich które wymagają większej ilości operacji, należy stosować semafory. W przypadku przerwania od pinu dobrze sprawdzi się semafor binarny. Może on znajdować się w jednym z dwóch stanów, podniesionym lub opuszczonym. Czyli przedstawia go liczba binarna 1 lub 0.


Program:


Zastosowanie semaforów binarnych zaprezentuję na podstawie czterech przerwań generowanych przez interfejs Wiegand. Dwa przerwania obsługują jedną z głowic. Sygnał W0 oraz W1. 

  1. SemaphoreHandle_t xSemaphore_bin_Wieg1_W0 = NULL;
  2. SemaphoreHandle_t xSemaphore_bin_Wieg1_W1 = NULL;
  3. SemaphoreHandle_t xSemaphore_bin_Wieg2_W0 = NULL;
  4. SemaphoreHandle_t xSemaphore_bin_Wieg2_W1 = NULL;

Po zdefiniowaniu zmiennych przechodzę do ich inicjalizacji:

  1. xSemaphore_bin_Wieg1_W0 = xSemaphoreCreateBinary();
  2. if( xSemaphore_bin_Wieg1_W0 == NULL ) { while(1); }
  3.  
  4. xSemaphore_bin_Wieg1_W1 = xSemaphoreCreateBinary();
  5. if( xSemaphore_bin_Wieg1_W1 == NULL ) { while(1); }
  6.  
  7. xSemaphore_bin_Wieg2_W0 = xSemaphoreCreateBinary();
  8. if( xSemaphore_bin_Wieg2_W0 == NULL ) { while(1); }
  9.  
  10. xSemaphore_bin_Wieg2_W1 = xSemaphoreCreateBinary();
  11. if( xSemaphore_bin_Wieg2_W1 == NULL ) { while(1); }
  12.  
  13. xTaskCreate( vInterrupt_Wieg1_W0, "Inter_Wieg1_W0", 100, NULL, 2, NULL );
  14. xTaskCreate( vInterrupt_Wieg1_W1, "Inter_Wieg1_W1", 100, NULL, 2, NULL );
  15. xTaskCreate( vInterrupt_Wieg2_W0, "Inter_Wieg2_W0", 100, NULL, 2, NULL );
  16. xTaskCreate( vInterrupt_Wieg2_W1, "Inter_Wieg2_W1", 100, NULL, 2, NULL );

Po zdefiniowaniu samoforów tworzę zadania przypisane do każdego z przerwań generowanych przez piny. Przerwania muszą mieć niższy priorytet niż maksymalny wykorzystywany przez FreeRtos. Pozwala to na poprawne działanie systemu, dla przerwań krytycznych wymaganych przez system.

Definicję priorytetów można znaleźć w pliku FreeRtosConfig.h:

  1. /* The lowest interrupt priority that can be used in a call to a "set priority"
  2. function. */
  3. #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY   15
  4.  
  5. /* The highest interrupt priority that can be used by any interrupt service
  6. routine that makes calls to interrupt safe FreeRTOS API functions.  DO NOT CALL
  7. INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
  8. PRIORITY THAN THIS! (higher priorities are lower numeric values. */
  9. #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
  10.  
  11. /* Interrupt priorities used by the kernel port layer itself.  These are generic
  12. to all Cortex-M ports, and do not rely on any particular library functions. */
  13. #define configKERNEL_INTERRUPT_PRIORITY         ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
  14. /* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
  15. See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
  16. #define configMAX_SYSCALL_INTERRUPT_PRIORITY    ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )

Poniżej uruchomienie i obsługa przerwań dla pinów GPIO:

  1. static void Wiegand1_2_InitInterrupts(void)
  2. {
  3.       HAL_NVIC_SetPriority(EXTI15_10_IRQn, 6, 0);
  4.       HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
  5. }
  6.  
  7. static void Wiegand1_Init(void)
  8. {
  9.       GPIO_InitTypeDef GPIO_InitStruct = {0};
  10.  
  11.       WIEGAND1_CLOCK_ENABLE();
  12.  
  13.       GPIO_InitStruct.Pin = WIEGAND1_DATA1_PIN;
  14.       GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  15.       GPIO_InitStruct.Pull = GPIO_NOPULL;
  16.       HAL_GPIO_Init(WIEGAND1_DATA1_PORT, &GPIO_InitStruct);
  17.  
  18.       GPIO_InitStruct.Pin = WIEGAND1_DATA0_PIN;
  19.       GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
  20.       GPIO_InitStruct.Pull = GPIO_NOPULL;
  21.       HAL_GPIO_Init(WIEGAND1_DATA0_PORT, &GPIO_InitStruct);
  22. }

  1. void EXTI15_10_IRQHandler(void)
  2. {
  3.   /* USER CODE BEGIN EXTI15_10_IRQn 0 */
  4.  
  5.   /* USER CODE END EXTI15_10_IRQn 0 */
  6.   HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
  7.   HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
  8.   HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
  9.   HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
  10.   /* USER CODE BEGIN EXTI15_10_IRQn 1 */
  11.  
  12.   /* USER CODE END EXTI15_10_IRQn 1 */
  13. }
  14.  
  15. void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
  16.     Wiegand1_ReadData(GPIO_Pin);
  17.     Wiegand2_ReadData(GPIO_Pin);
  18. }

  1. void Wiegand1_ReadData(const uint16_t gpio_pin) {
  2.     BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  3.     if(gpio_pin == GPIO_PIN_13) {
  4.         xSemaphoreGiveFromISR( xSemaphore_bin_Wieg1_W0, &xHigherPriorityTaskWoken );
  5.         portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  6.     }
  7.     if(gpio_pin == GPIO_PIN_14) {
  8.         xSemaphoreGiveFromISR( xSemaphore_bin_Wieg1_W1, &xHigherPriorityTaskWoken );
  9.         portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  10.     }
  11. }
  12.  
  13. void Wiegand2_ReadData(const uint16_t gpio_pin) {
  14.     BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  15.     if(gpio_pin == GPIO_PIN_12) {
  16.         xSemaphoreGiveFromISR( xSemaphore_bin_Wieg2_W0, &xHigherPriorityTaskWoken );
  17.         portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  18.     }
  19.  
  20.     if(gpio_pin == GPIO_PIN_11) {
  21.         xSemaphoreGiveFromISR( xSemaphore_bin_Wieg2_W1, &xHigherPriorityTaskWoken );
  22.         portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  23.     }
  24. }

W powyższych przykładach wykorzystywana jest funkcja xSemaphoreGiveFromISR, która pozwala na wykonanie zadania z przerwania. Jako argumenty podaje handler do obsługi wywołania semafora oraz zmienną która zostanie ustawiona na wartość True jeśli zadanie zostanie odblokowane i posiada wyższy priorytet niż aktualnie wykonywane zadanie.

Kolejna funkcja spowoduje wykonanie zadania. Musi ona być wywołana na końcu przerwania. W innym przypadku przerwanie może zostanie chwilowo wstrzymane i powrót do niego nastąpi po zakończeniu zadania. Wszystko zależy od priorytetów przerwania oraz wykonywanego zadania.

Po wygenerowaniu przerwania z pinu następuje wejście do zadania przypisanego pod przerwanie z pinu:

  1. void vInterrupt_Wieg1_W0(void *pvParameters) {
  2.     while (1) {
  3.         if ( xSemaphoreTake( xSemaphore_bin_Wieg1_W0, 1000 / portTICK_RATE_MS ) == pdTRUE) {
  4.             Wiegand1_GetData_W0();
  5.         }
  6.         else { }
  7.     }
  8. }
  9.  
  10. void vInterrupt_Wieg1_W1(void *pvParameters) {
  11.     while (1) {
  12.         if ( xSemaphoreTake( xSemaphore_bin_Wieg1_W1, 1000 / portTICK_RATE_MS ) == pdTRUE) {
  13.             Wiegand1_GetData_W1();
  14.         }
  15.         else { }
  16.     }
  17. }
  18.  
  19. void vInterrupt_Wieg2_W0(void *pvParameters) {
  20.     while (1) {
  21.         if ( xSemaphoreTake( xSemaphore_bin_Wieg2_W0, 1000 / portTICK_RATE_MS ) == pdTRUE) {
  22.             Wiegand2_GetData_W0();
  23.         }
  24.         else { }
  25.     }
  26. }
  27.  
  28. void vInterrupt_Wieg2_W1(void *pvParameters) {
  29.     while (1) {
  30.         if ( xSemaphoreTake( xSemaphore_bin_Wieg2_W1, 1000 / portTICK_RATE_MS ) == pdTRUE) {
  31.             Wiegand2_GetData_W1();
  32.         }
  33.         else { }
  34.     }
  35. }

Obsługa przerwań od danych wygląda następująco:

Tutaj w zależności od linii jaka wygeneruje przerwanie do danych opisujących kartę dodawana jest wartość 0 lub 1.

  1. static void Wiegand1_GetData_W0(void)
  2. {
  3.     if(wiegandCardData_Head1.readCardData.bitCounter < 64) {
  4.         wiegandCardData_Head1.readCardData.fullData = (wiegandCardData_Head1.readCardData.fullData << 1);
  5.         wiegandCardData_Head1.readCardData.fullData |= 1;
  6.     }
  7.     else {
  8.         wiegandCardData_Head1.readCardData.fullData_BigCards = (wiegandCardData_Head1.readCardData.fullData_BigCards << 1);
  9.         wiegandCardData_Head1.readCardData.fullData_BigCards |= 1;
  10.     }
  11.     wiegandCardData_Head1.readCardData.bitCounter++;
  12.     wiegandCardData_Head1.readCardData.flag_startReceivingData = 1;
  13.  
  14.     if(wiegandCardData_Head1.readCardData.bitCounter == 1) {
  15.         wiegandCardData_Head1.timeoutCounter = kWiegandTimeoutMs;
  16.     }
  17. }
  18.  
  19. static void Wiegand1_GetData_W1(void)
  20. {
  21.     if(wiegandCardData_Head1.readCardData.bitCounter < 64) {
  22.         wiegandCardData_Head1.readCardData.fullData = (wiegandCardData_Head1.readCardData.fullData << 1);
  23.         wiegandCardData_Head1.readCardData.fullData |= 0;
  24.     }
  25.     else {
  26.         wiegandCardData_Head1.readCardData.fullData_BigCards = (wiegandCardData_Head1.readCardData.fullData_BigCards << 1);
  27.         wiegandCardData_Head1.readCardData.fullData_BigCards |= 0;
  28.     }
  29.  
  30.     wiegandCardData_Head1.readCardData.bitCounter++;
  31.     wiegandCardData_Head1.readCardData.flag_startReceivingData = 1;
  32.  
  33.     if(wiegandCardData_Head1.readCardData.bitCounter == 1) {
  34.         wiegandCardData_Head1.timeoutCounter = kWiegandTimeoutMs;
  35.     }
  36. }

Timeout od odebranych danych można wykonać dla całej długości lub odliczać krótszy czas pomiędzy odebraniem kolejnych bitów. 

Drugim przydatnym mechanizmem do przerwań jest TaskNotification, który działa znacznie szybciej i zużywa mniej pamięci RAM. Jego zastosowanie i działanie opiszę w kolejnym poście.