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.
- SemaphoreHandle_t xSemaphore_bin_Wieg1_W0 = NULL;
- SemaphoreHandle_t xSemaphore_bin_Wieg1_W1 = NULL;
- SemaphoreHandle_t xSemaphore_bin_Wieg2_W0 = NULL;
- SemaphoreHandle_t xSemaphore_bin_Wieg2_W1 = NULL;
Po zdefiniowaniu zmiennych przechodzę do ich inicjalizacji:
- xSemaphore_bin_Wieg1_W0 = xSemaphoreCreateBinary();
- if( xSemaphore_bin_Wieg1_W0 == NULL ) { while(1); }
- xSemaphore_bin_Wieg1_W1 = xSemaphoreCreateBinary();
- if( xSemaphore_bin_Wieg1_W1 == NULL ) { while(1); }
- xSemaphore_bin_Wieg2_W0 = xSemaphoreCreateBinary();
- if( xSemaphore_bin_Wieg2_W0 == NULL ) { while(1); }
- xSemaphore_bin_Wieg2_W1 = xSemaphoreCreateBinary();
- if( xSemaphore_bin_Wieg2_W1 == NULL ) { while(1); }
- xTaskCreate( vInterrupt_Wieg1_W0, "Inter_Wieg1_W0", 100, NULL, 2, NULL );
- xTaskCreate( vInterrupt_Wieg1_W1, "Inter_Wieg1_W1", 100, NULL, 2, NULL );
- xTaskCreate( vInterrupt_Wieg2_W0, "Inter_Wieg2_W0", 100, NULL, 2, NULL );
- 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:
- /* The lowest interrupt priority that can be used in a call to a "set priority"
- function. */
- #define configLIBRARY_LOWEST_INTERRUPT_PRIORITY 15
- /* The highest interrupt priority that can be used by any interrupt service
- routine that makes calls to interrupt safe FreeRTOS API functions. DO NOT CALL
- INTERRUPT SAFE FREERTOS API FUNCTIONS FROM ANY INTERRUPT THAT HAS A HIGHER
- PRIORITY THAN THIS! (higher priorities are lower numeric values. */
- #define configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY 5
- /* Interrupt priorities used by the kernel port layer itself. These are generic
- to all Cortex-M ports, and do not rely on any particular library functions. */
- #define configKERNEL_INTERRUPT_PRIORITY ( configLIBRARY_LOWEST_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
- /* !!!! configMAX_SYSCALL_INTERRUPT_PRIORITY must not be set to zero !!!!
- See http://www.FreeRTOS.org/RTOS-Cortex-M3-M4.html. */
- #define configMAX_SYSCALL_INTERRUPT_PRIORITY ( configLIBRARY_MAX_SYSCALL_INTERRUPT_PRIORITY << (8 - configPRIO_BITS) )
Poniżej uruchomienie i obsługa przerwań dla pinów GPIO:
- static void Wiegand1_2_InitInterrupts(void)
- {
- HAL_NVIC_SetPriority(EXTI15_10_IRQn, 6, 0);
- HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
- }
- static void Wiegand1_Init(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct = {0};
- WIEGAND1_CLOCK_ENABLE();
- GPIO_InitStruct.Pin = WIEGAND1_DATA1_PIN;
- GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- HAL_GPIO_Init(WIEGAND1_DATA1_PORT, &GPIO_InitStruct);
- GPIO_InitStruct.Pin = WIEGAND1_DATA0_PIN;
- GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- HAL_GPIO_Init(WIEGAND1_DATA0_PORT, &GPIO_InitStruct);
- }
- void EXTI15_10_IRQHandler(void)
- {
- /* USER CODE BEGIN EXTI15_10_IRQn 0 */
- /* USER CODE END EXTI15_10_IRQn 0 */
- HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_11);
- HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_12);
- HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_13);
- HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
- /* USER CODE BEGIN EXTI15_10_IRQn 1 */
- /* USER CODE END EXTI15_10_IRQn 1 */
- }
- void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
- Wiegand1_ReadData(GPIO_Pin);
- Wiegand2_ReadData(GPIO_Pin);
- }
- void Wiegand1_ReadData(const uint16_t gpio_pin) {
- BaseType_t xHigherPriorityTaskWoken = pdFALSE;
- if(gpio_pin == GPIO_PIN_13) {
- xSemaphoreGiveFromISR( xSemaphore_bin_Wieg1_W0, &xHigherPriorityTaskWoken );
- portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
- }
- if(gpio_pin == GPIO_PIN_14) {
- xSemaphoreGiveFromISR( xSemaphore_bin_Wieg1_W1, &xHigherPriorityTaskWoken );
- portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
- }
- }
- void Wiegand2_ReadData(const uint16_t gpio_pin) {
- BaseType_t xHigherPriorityTaskWoken = pdFALSE;
- if(gpio_pin == GPIO_PIN_12) {
- xSemaphoreGiveFromISR( xSemaphore_bin_Wieg2_W0, &xHigherPriorityTaskWoken );
- portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
- }
- if(gpio_pin == GPIO_PIN_11) {
- xSemaphoreGiveFromISR( xSemaphore_bin_Wieg2_W1, &xHigherPriorityTaskWoken );
- portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
- }
- }
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:
- void vInterrupt_Wieg1_W0(void *pvParameters) {
- while (1) {
- if ( xSemaphoreTake( xSemaphore_bin_Wieg1_W0, 1000 / portTICK_RATE_MS ) == pdTRUE) {
- Wiegand1_GetData_W0();
- }
- else { }
- }
- }
- void vInterrupt_Wieg1_W1(void *pvParameters) {
- while (1) {
- if ( xSemaphoreTake( xSemaphore_bin_Wieg1_W1, 1000 / portTICK_RATE_MS ) == pdTRUE) {
- Wiegand1_GetData_W1();
- }
- else { }
- }
- }
- void vInterrupt_Wieg2_W0(void *pvParameters) {
- while (1) {
- if ( xSemaphoreTake( xSemaphore_bin_Wieg2_W0, 1000 / portTICK_RATE_MS ) == pdTRUE) {
- Wiegand2_GetData_W0();
- }
- else { }
- }
- }
- void vInterrupt_Wieg2_W1(void *pvParameters) {
- while (1) {
- if ( xSemaphoreTake( xSemaphore_bin_Wieg2_W1, 1000 / portTICK_RATE_MS ) == pdTRUE) {
- Wiegand2_GetData_W1();
- }
- else { }
- }
- }
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.
- static void Wiegand1_GetData_W0(void)
- {
- if(wiegandCardData_Head1.readCardData.bitCounter < 64) {
- wiegandCardData_Head1.readCardData.fullData = (wiegandCardData_Head1.readCardData.fullData << 1);
- wiegandCardData_Head1.readCardData.fullData |= 1;
- }
- else {
- wiegandCardData_Head1.readCardData.fullData_BigCards = (wiegandCardData_Head1.readCardData.fullData_BigCards << 1);
- wiegandCardData_Head1.readCardData.fullData_BigCards |= 1;
- }
- wiegandCardData_Head1.readCardData.bitCounter++;
- wiegandCardData_Head1.readCardData.flag_startReceivingData = 1;
- if(wiegandCardData_Head1.readCardData.bitCounter == 1) {
- wiegandCardData_Head1.timeoutCounter = kWiegandTimeoutMs;
- }
- }
- static void Wiegand1_GetData_W1(void)
- {
- if(wiegandCardData_Head1.readCardData.bitCounter < 64) {
- wiegandCardData_Head1.readCardData.fullData = (wiegandCardData_Head1.readCardData.fullData << 1);
- wiegandCardData_Head1.readCardData.fullData |= 0;
- }
- else {
- wiegandCardData_Head1.readCardData.fullData_BigCards = (wiegandCardData_Head1.readCardData.fullData_BigCards << 1);
- wiegandCardData_Head1.readCardData.fullData_BigCards |= 0;
- }
- wiegandCardData_Head1.readCardData.bitCounter++;
- wiegandCardData_Head1.readCardData.flag_startReceivingData = 1;
- if(wiegandCardData_Head1.readCardData.bitCounter == 1) {
- wiegandCardData_Head1.timeoutCounter = kWiegandTimeoutMs;
- }
- }
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.