piątek, 2 grudnia 2022

STM32H7 - FreeRtos - Odczyt Wiegand

W tym poście chciałbym opisać implementację interfejsu Wiegand dla mikrokontrolera STM32H7, pracującego pod kontrolą systemu FreeRtos.

[Ź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]

Opis interfejsu:


Wiegand jest protokołem komunikacyjnym bardzo często stosowanym w systemach kontroli dostępu. Głównie do podłączenia modułu odczytującego karty (głowicy) z kontrolerem.

Wiegand wymaga podłączenia minimum dwóch linii czyli Data0 (Data Low) oraz Data1 (Data High). Często potrzeba zastosować dodatkowe podłączenie wspólnej masy w celu uzyskania poprawnej komunikacji. Jeśli wykorzystywana jest np. skrętka to dobrym rozwiązaniem jest wykorzystanie np. dwóch par przewodów dla każdej linii danych.

Głównymi wadami Wieganda jest jednokierunkowa transmisja, brak szyfrowania (chyba, że zaszyfrujemy przesyłane dane), ograniczenie wielkości danych. 

Z wykorzystaniem tego interfejsu przesyłane są numery kart w różnych formatach. Najbardziej popularne są standardy 26 bit oraz 37 bitów. Natomiast występują też karty przesyłające inną długość danych jak 32, 34, 35, 40 czy 4 w przypadku przesłania danych z klawiatury.

Standardowo linie Data0 oraz Data1 generują następujące sygnały:


[Źródło: http://electronicsgate.com/projects/rfid.html]

Linie podciągnięte się do stanu wysokiego (najczęściej 5V). Gdy przesyłana jest karta stan na liniach zmienia się w zależności od tego czy wysyłany jest bit 0 lub 1. Po przesłaniu pełnego kompletu danych następuje zakończenie transmisji. W związku z tym trzeba ustawić timeout po odczycie danych, który zagwarantuje brak kolejnych bitów w ciągu.

Można także spotkać rozwiązania w których dane z Wieganda przesyłane są w odwrotny sposób tzn. domyślny stan na linii jest niski i ustawienie stanu wysokiego poinformuje o otrzymaniu danych.

W związku z tym, że Wiegand przesyła dane jednokierunkowo można zastosować mechanizm przesyłania określonego kodu (tzw. Hearbeat) np. co 15-30 minut gdy nie nastąpiła żadna aktywność na czytniku. Dzięki temu rejestrator będzie wiedział, że głowica nie uległa uszkodzeniu. 

Ponieważ Wiegand w większości przypadku przesyła czysty numer karty w formacie binarnym, to w przypadku podłączenia snifferów na liniach. Można w łatwy sposób określić numer karty. Ponieważ ciąg przesyłanych danych będzie identyczny po każdym przyłożeniu karty. Jedynym zabezpieczeniem jest stosowanie mechanizmu szyfrowania danych z dołożeniem losowych bajtów, co oczywiście zwiększy ilość przesyłanych danych.

Format kart:


Dla przykładu opiszę tutaj opis standardowego formatu 26 bitowego. 


Należy tutaj pamiętać, że o ile jest zdefiniowanych kilka ogólnych standardów/typów przesyłanych danych. To tak naprawdę sposób ich zapisu, przesyłania jest ograniczony jedynie protokołem i naszą wyobraźnią. 

Na powyższym obrazku jest 16 bitów danych dla karty (0 - 65535), 8 bitów dla Facility Code (można tłumaczyć jako kod strefy/placówki/budynku. Czyli karta o tym samym numerze, ale różnym kodzie może mieć dostęp do budynku A ale do budynku B już nie) oraz bity parzystości (Even) oraz nieparzystości (Odd). 

Implementacja w programie:


W celu odbierania danych trzeba przygotować dwa piny mikrokontrolera, i ustawić je w przerwaniu od zbocza opadającego (narastającego też może być, natomiast opadające do nam nieco więcej czasu, zanim nastąpi przesłanie kolejnej informacji z bitem danych).

  1. #define WIEGAND1_CLOCK_ENABLE() __HAL_RCC_GPIOD_CLK_ENABLE()
  2. #define WIEGAND1_DATA1_PORT     GPIOD
  3. #define WIEGAND1_DATA1_PIN      GPIO_PIN_13
  4. #define WIEGAND1_DATA0_PORT     GPIOD
  5. #define WIEGAND1_DATA0_PIN      GPIO_PIN_14
  6.  
  7. static void Wiegand1_2_InitInterrupts(void)
  8. {
  9.       HAL_NVIC_SetPriority(EXTI15_10_IRQn, 6, 0);
  10.       HAL_NVIC_EnableIRQ(EXTI15_10_IRQn);
  11. }
  12.  
  13. static void Wiegand1_Init(void)
  14. {
  15.       GPIO_InitTypeDef GPIO_InitStruct = {0};
  16.  
  17.       WIEGAND1_CLOCK_ENABLE();
  18.  
  19.       GPIO_InitStruct.Pin = WIEGAND1_DATA1_PIN;
  20.       GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  21.       GPIO_InitStruct.Pull = GPIO_NOPULL;
  22.       HAL_GPIO_Init(WIEGAND1_DATA1_PORT, &GPIO_InitStruct);
  23.  
  24.       GPIO_InitStruct.Pin = WIEGAND1_DATA0_PIN;
  25.       GPIO_InitStruct.Mode = GPIO_MODE_IT_FALLING;
  26.       GPIO_InitStruct.Pull = GPIO_NOPULL;
  27.       HAL_GPIO_Init(WIEGAND1_DATA0_PORT, &GPIO_InitStruct);
  28. }

Podłączenie funkcji odczytujących pod przerwanie:

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

Struktura danych przechowująca informacje o odczytanej karcie:

  1. typedef struct Wiegand_CardData_Typdef_Struct{
  2.     volatile uint64_t fullData;
  3.     volatile uint64_t fullData_BigCards;
  4.     uint16_t facilityCode;
  5.     uint64_t cardNumber;
  6.     volatile uint8_t bitCounter;
  7.     volatile uint8_t thereIsCardToDecode;
  8.     volatile uint8_t flag_startReceivingData;
  9. }Wiegand_CardDataTypeDef;
  10.  
  11. typedef struct Wiegand_Typedef_Struct{
  12.     Wiegand_CardDataTypeDef readCardData;
  13.     uint8_t headerAddress;
  14.     volatile uint32_t timeoutCounter;
  15. }Wiegand_TypeDef;

Odczytanie odebranych wykonuje w osobnych wątkach:

  1. osThreadId taskWiegand1_W0;
  2. osThreadId taskWiegand1_W1;
  3. void TaskWiegand1ListenW0(void const *argument);
  4. void TaskWiegand1ListenW1(void const *argument);
  5.  
  6. osThreadDef(wiegand1TaskW0, TaskWiegand1ListenW0, osPriorityNormal, 0, 128);
  7. taskWiegand1_W0 = osThreadCreate(osThread(wiegand1TaskW0), NULL);
  8. osThreadDef(wiegand1TaskW1, TaskWiegand1ListenW1, osPriorityNormal, 0, 128);
  9. taskWiegand1_W1 = osThreadCreate(osThread(wiegand1TaskW1), NULL);

Odbierane dane:

  1. void Wiegand1_ReadData(const uint16_t gpio_pin) {
  2.       BaseType_t xHigherPriorityTaskWoken = pdFALSE;
  3.       if(gpio_pin == GPIO_PIN_13) {
  4.           configASSERT( xTaskToNotifyat_Wieg1_W0 != NULL );
  5.           vTaskNotifyGiveFromISR( xTaskToNotifyat_Wieg1_W0, &xHigherPriorityTaskWoken );
  6.           portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  7.       }
  8.       if(gpio_pin == GPIO_PIN_14) {
  9.           configASSERT( xTaskToNotifyat_Wieg1_W1 != NULL );
  10.           vTaskNotifyGiveFromISR( xTaskToNotifyat_Wieg1_W1, &xHigherPriorityTaskWoken );
  11.           portYIELD_FROM_ISR( xHigherPriorityTaskWoken );
  12.       }
  13. }
  14.  
  15. void TaskWiegand1ListenW0(void const *argument) {
  16.   configASSERT(xTaskToNotifyat_Wieg1_W0 == NULL);
  17.   xTaskToNotifyat_Wieg1_W0 = xTaskGetCurrentTaskHandle();
  18.  
  19.   for(;;) {
  20.     uint32_t ulNotificationValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
  21.     if(ulNotificationValue == 1) {
  22.         Wiegand_GetData(&wiegandCardData_Head1, 0);
  23.     }
  24.     else { }
  25.   }
  26. }
  27. void TaskWiegand1ListenW1(void const *argument) {
  28.   configASSERT(xTaskToNotifyat_Wieg1_W1 == NULL);
  29.   xTaskToNotifyat_Wieg1_W1 = xTaskGetCurrentTaskHandle();
  30.  
  31.   for(;;) {
  32.     uint32_t ulNotificationValue = ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
  33.     if(ulNotificationValue == 1) {
  34.         Wiegand_GetData(&wiegandCardData_Head1, 1);
  35.     }
  36.     else { }
  37.   }
  38. }

Powyżej przerwanie wywołuje wątek do obsługi dodawania danych.

Dopisywanie odebranych bitów do zmiennej:

  1. void Wiegand_GetData(Wiegand_TypeDef *wieg_ptr, uint8_t dataToPass) {
  2.     if(wieg_ptr->readCardData.bitCounter < 64) {
  3.         wieg_ptr->readCardData.fullData = (wieg_ptr->readCardData.fullData << 1);
  4.         wieg_ptr->readCardData.fullData |= dataToPass;
  5.     }
  6.     else {
  7.         wieg_ptr->readCardData.fullData_BigCards = (wieg_ptr->readCardData.fullData_BigCards << 1);
  8.         wieg_ptr->readCardData.fullData_BigCards |= dataToPass;
  9.     }
  10.     wieg_ptr->readCardData.bitCounter++;
  11.     wieg_ptr->readCardData.flag_startReceivingData = 1;
  12.  
  13.     if(wieg_ptr->readCardData.bitCounter == 1) {
  14.         wieg_ptr->timeoutCounter = kWiegandTimeoutMs;
  15.     }
  16. }

Odliczanie timera:

  1. void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
  2. {
  3.   /* USER CODE BEGIN Callback 0 */
  4.  
  5.   /* USER CODE END Callback 0 */
  6.   if (htim->Instance == TIM6) {
  7.     HAL_IncTick();
  8.   }
  9.   /* USER CODE BEGIN Callback 1 */
  10.   wiegand_timeoutChange(&wiegandCardData_Head1);
  11.   /* USER CODE END Callback 1 */
  12. }
  13.  

Sprawdzanie odebranych danych:

  1. void StartReaderTask(void const * argument)
  2. {
  3.     for(;;)
  4.     {
  5.         if(wiegand_checkReceivedData(&wiegandCardData_Head1, settingsData.wiegSett_Data_Flags) == 1) {
  6.             //Do some stuff
  7.         }
  8.         osDelay(1000);
  9.     }
  10. }

Sprawdzenie odebranego formatu danych:

  1. uint8_t wiegand_checkReceivedData(Wiegand_TypeDef* wiegandData, const WiegandSettings_t wiegSettings)
  2. {
  3.     if(wiegand_checkIfCardReceive((Wiegand_TypeDef*)wiegandData)) {
  4.         if(wiegandData->readCardData.bitCounter == 26)
  5.         {
  6.             //...
  7.         }
  8.         else if(wiegandData->readCardData.bitCounter == 32)
  9.         {
  10.             //...
  11.         }
  12.         else if(wiegandData->readCardData.bitCounter == 37)
  13.         {
  14.             //...
  15.         }
  16.         else if(wiegandData->readCardData.bitCounter == 40)
  17.         {
  18.             //...
  19.         }
  20.         else
  21.         {
  22.             //...
  23.         }
  24.         return 1u;
  25.     }
  26.     return 0u;
  27. }