[Źródło: http://www.priority1design.com.au/em4095_rfid_ic.html]
Parametry układu:
- Modulacja AM OOK
- Zakres częstotliwości fali nośnej 100 do 150 kHz
- Pobór prądu w czasie uśpienia wynosi 1uA
- Czas potrzebny na wybudzenie to 25ms
- Napięcie zasilania od 4.1V do 5.5V
- Zakres temperatury pracy wynosi od -40 st. C do 85 st. C
Układ ten zawiera modulator oraz demodulator.
Wykorzystywał będę tylko dwa piny układu podłączane do F7:
SHD - pozwala na włączenie bądź wyłączenie układu bądź na wykonanie resetu. Procedury te wykonuje się poprzez wygenerowanie zbocza opadającego. Dla normalnej pracy należy go utrzymywać w stanie wysokim.
DMOD - jest to wyjście zawierające zdemodulowany sygnał odebrany z karty unique. To wyjście z układu zawiera sygnał
Pozostałe obsługują następujące funkcje:
ANT - podłączenie anteny do układu.
RDY/CLK - na nim dostępny jest sygnał w przebiegu prostokątnym jaki jest nadawany przez antenę.
MOD - jest to pin wyjściowy za pomocą którego można modulować nadawany przez antenę sygnał.
Pin RDY/CLK zostawiam niepodłączony, natomiast pin MOD zostaje zwarty do masy.
Układ EM pozwala na odczytanie takich kart jak:
Unique - jest to jeden z pierwszych standardów kart, obecnie najprostszy z nich. Częstotliwość odczytu wynosi 125 kHz. Numer przechowywany jest na karcie na 40 bitach. Jest on nadawany podczas jej produkcji.
Q5 - są to karty komunikujące się na tej samej zasadzie co Unique. Dodatkiem do nich jest pamięć EEPROM. Te karty można także programować i zmieniać ich numer wielokrotnie.
Kodowanie danych odbywa się za pomocą kodu Manchester. Sygnał przyjmuje stan odpowiadający jego wartość binarną. W środku czasu trwania transmisji następuje zmiana sygnału na przeciwny. Dla zera będzie to stan niski, dla jedynki będzie to stan wysoki. [Wikipedia].
Czyli jeśli będą nadawane te same bity to czas wystąpienia kolejnego zbocza będzie zawsze wynosił połowę okresu sygnału. Natomiast jeśli nastąpi zmiana stanu to ten czas zostanie wydłużony do pełnego okresu.
Ramka danych prezentuje się następująco:
[Źródło: elektroda]
Transmisja danych rozpoczyna się od 9 bitów startu oznaczonych jako 1. Potem od D00 do D07 wprowadzony jest kod producenta karty. Od D08 do D39 umieszczony jest numer karty. Bity P0 do P9 są to bity parzystości dla każdych 4 bitów. Jeśli zawierają one nieparzystą ilość jedynek to bit parzystości wynosi 1. Bity od CP0 do CP3 są to bity parzystości każdych 10 bitów umieszczonych w kolumnach. Transmisja jest kończona bitem S0, który zawsze wynosi 1.
CubeMx:
Aby można było tego dokonać to należy uruchomić licznik z obsługą trybu Input Capture. Ja wybrałem Timer 2. Dla jego poprawnej pracy należy go skonfigurować w następujący sposób:
Timer 2 jest taktowany z linii APB1 więc jego częstotliwość wynosi 1kHz. Ustawienia timera są wykonywane w funkcji EM4095_Init(). Więc ustawienia pobrane z CubeMx zostaną nadpisane. W przypadku korzystania z innego zegara lub mikrokontrolera należy zmodyfikować tą funkcję.
Ustawienie zegarów wygląda następująco:
Konfiguracje należy dokończyć przez dodanie pozostałych ustawień.
Program:
Osobna procedura inicjalizacyjna wygląda następująco:
- void EM4095_Init(void)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- TIM_ClockConfigTypeDef sClockSourceConfig;
- TIM_SlaveConfigTypeDef sSlaveConfig;
- TIM_MasterConfigTypeDef sMasterConfig;
- TIM_IC_InitTypeDef sConfigIC;
- /* Define PI2 pin as output with pullUp */
- __HAL_RCC_GPIOA_CLK_ENABLE();
- __HAL_RCC_GPIOI_CLK_ENABLE();
- GPIO_InitStruct.Pin = GPIO_PIN_2;
- GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
- GPIO_InitStruct.Pull = GPIO_PULLUP;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
- HAL_GPIO_Init(GPIOI, &GPIO_InitStruct);
- HAL_GPIO_WritePin(GPIOI, GPIO_PIN_2, GPIO_PIN_RESET); /* SHD set for LOW */
- HAL_Delay(1000); /* small delay */
- HAL_GPIO_WritePin(GPIOI, GPIO_PIN_2, GPIO_PIN_SET); /* SHD set for HIGH */
- TIM2MspInit(&htim2);
- /* Init Timer 2 in Capture mode*/
- htim2.Instance = TIM2;
- htim2.Init.Prescaler = 99;
- htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
- htim2.Init.Period = 99999;
- htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
- htim2.Init.AutoReloadPreload = TIM_AUTORELOAD_PRELOAD_DISABLE;
- if (HAL_TIM_Base_Init(&htim2) != HAL_OK)
- {
- _Error_Handler(__FILE__, __LINE__);
- }
- sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
- if (HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig) != HAL_OK)
- {
- _Error_Handler(__FILE__, __LINE__);
- }
- if (HAL_TIM_IC_Init(&htim2) != HAL_OK)
- {
- _Error_Handler(__FILE__, __LINE__);
- }
- sSlaveConfig.SlaveMode = TIM_SLAVEMODE_RESET;
- sSlaveConfig.InputTrigger = TIM_TS_TI1FP1;
- sSlaveConfig.TriggerPolarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE;
- sSlaveConfig.TriggerFilter = 0;
- if (HAL_TIM_SlaveConfigSynchronization(&htim2, &sSlaveConfig) != HAL_OK)
- {
- _Error_Handler(__FILE__, __LINE__);
- }
- sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
- sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
- if (HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig) != HAL_OK)
- {
- _Error_Handler(__FILE__, __LINE__);
- }
- sConfigIC.ICPolarity = TIM_INPUTCHANNELPOLARITY_BOTHEDGE;
- sConfigIC.ICSelection = TIM_ICSELECTION_DIRECTTI;
- sConfigIC.ICPrescaler = TIM_ICPSC_DIV1;
- sConfigIC.ICFilter = 0;
- if (HAL_TIM_IC_ConfigChannel(&htim2, &sConfigIC, TIM_CHANNEL_1) != HAL_OK)
- {
- _Error_Handler(__FILE__, __LINE__);
- }
- /* TIM2 interrupt ON */
- HAL_NVIC_SetPriority(TIM2_IRQn, 1, 0);
- HAL_NVIC_EnableIRQ(TIM2_IRQn);
- }
- static void TIM2MspInit(TIM_HandleTypeDef* htim_base)
- {
- GPIO_InitTypeDef GPIO_InitStruct;
- if(htim_base->Instance==TIM2)
- {
- __HAL_RCC_GPIOA_CLK_ENABLE();
- __HAL_RCC_TIM2_CLK_ENABLE();
- /**TIM2 GPIO Configuration
- PA0 ------> TIM2_CH1
- */
- GPIO_InitStruct.Pin = GPIO_PIN_15;
- GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
- GPIO_InitStruct.Pull = GPIO_NOPULL;
- GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
- GPIO_InitStruct.Alternate = GPIO_AF1_TIM2;
- HAL_GPIO_Init(GPIOA, &GPIO_InitStruct);
- /* TIM2 interrupt Init */
- HAL_NVIC_SetPriority(TIM2_IRQn, 0, 0);
- HAL_NVIC_EnableIRQ(TIM2_IRQn);
- }
- }
- static void TIM2MspDeInit(TIM_HandleTypeDef* htim_base)
- {
- if(htim_base->Instance==TIM2)
- {
- /* Peripheral clock disable */
- __HAL_RCC_TIM2_CLK_DISABLE();
- HAL_GPIO_DeInit(GPIOA, GPIO_PIN_15);
- /* TIM2 interrupt DeInit */
- HAL_NVIC_DisableIRQ(TIM2_IRQn);
- }
- }
Podczas niej uruchomiony zostaje pin SHD na którym do działania układu musi być wystawiony sygnał wysoki. Układ można wyłączać poprzez podanie na ten pin stanu niskiego.
Funkcja znajdująca nagłówek, czyli 9 jedynek:
- static uint8_t FindHeader(em4095_unique_t * unique_card) {
- uint8_t bitsToCheck;
- /* Check all bits */
- for (uint8_t i = 0; i < 64; i++)
- {
- /* if header is in the beginning of data frame */
- if ((unique_card->data_frame & START_HEADER_MASK) == START_HEADER_MASK) { return 1; /* OK */ }
- else
- {
- bitsToCheck = unique_card->data_frame & MSB_BIT_MASK ? 1 : 0;
- unique_card->data_frame <<= 1;
- if (bitsToCheck)
- {
- unique_card->data_frame |= LSB_BIT_MASK;
- }
- }
- }
- if ((unique_card->data_frame & START_HEADER_MASK) == START_HEADER_MASK) {
- return 1; /* OK */
- }
- return 0; /* error */
- }
Funkcje obliczające parzystości:
- static uint8_t RowParity(uint8_t dat)
- {
- uint8_t count = 0;
- for (uint8_t i = 0; i < 4; i++)
- {
- if(dat & (1 << i))
- {
- count++;
- }
- }
- return count % 2 ? 1 : 0;
- }
- //-------------------------------------------------------------------------
- static uint8_t HorizontalParity(uint64_t data_frame)
- {
- for (uint8_t i = 1; i < 11; i++)
- {
- uint8_t byte = (data_frame >> (i * 5 + 1)) & 0xF;
- if (RowParity(byte) != ((data_frame >> (i * 5)) & 0x1))
- {
- return 0; /* error */
- }
- }
- return 1; /* OK */
- }
- //-------------------------------------------------------------------------
- static uint8_t VerticalParity(uint64_t data_frame)
- {
- for (uint8_t i = 1; i < 5; i++)
- {
- uint8_t parity = 0;
- for (uint8_t j = 1; j <= 10; j++)
- {
- parity ^= (data_frame >> (j * 5 + i)) & 0x1;
- }
- if (parity != ((data_frame >> i) & 0x1))
- {
- return 0; /* error */
- }
- }
- return 1; /* OK */
- }
Pierwsza funkcja od góry sprawdza parzystość czterech bitów w jednym rzędzie po podaniu do niej czterech bitów. Kolejna pozwala na weryfikację 10 bitów w kolumnie. Ostatnia sprawdza bity parzystości dla pojedynczej linii. Obie funkcje operują na całej ramce odebranych danych.
Przygotowanie danych odbywa się w następującej funkcji:
- static void GetDataForCard(em4095_unique_t * unique_card, uint8_t * resultData) {
- uint64_t std_card_nr = 0;
- char value[10];
- for (uint8_t i = 0; i < 10; i++) {
- value[9 - i] = (unique_card->data_frame >> (5 * (i + 1) + 1)) & 0xf;
- }
- for (uint8_t i = 0; i < 10; i++) {
- std_card_nr += value[i] * FactorialCalculate(16, (9 - i));
- }
- std_card_nr &= 0xFFFFFF;
- for (uint8_t i = 0; i < 4; i++) {
- resultData[i] = (std_card_nr >> i * 8) & 0xFF;
- }
- }
W niej następuje wprowadzenie danych do głównego bufora zawierającego dane z odczytanym numerem karty.
Procedura obsługi przerwania wygląda następująco:
- void HAL_TIM_IC_CaptureCallback(TIM_HandleTypeDef *htim)
- {
- if((htim->Instance==TIM2) && (htim->Channel == HAL_TIM_ACTIVE_CHANNEL_1))
- {
- static uint8_t edgeCountCheck;
- static uint8_t readBitValue;
- static uint8_t countBits;
- if(!em4095_unique_data.is_processed) { return; }
- /* If decode proces reset */
- if (edgeCountCheck==0)
- {
- /* Reset all counters */
- countBits = 0;
- readBitValue = 1;
- em4095_unique_data.result = 0;
- }
- /* Get Capture value, then reset counter */
- pulse_length = HAL_TIM_ReadCapturedValue(&htim2, TIM_CHANNEL_1);
- /* Clear counter */
- __HAL_TIM_SetCounter(&htim2, 0);
- /* Check pulse length */
- if ((pulse_length < EM4095_SHORT_PULSE_MIN) || (pulse_length > EM4095_LONG_PULSE_MAX)) {
- edgeCountCheck = 0;
- }
- else if (pulse_length >= EM4095_SHORT_PULSE_MIN && pulse_length <= EM4095_SHORT_PULSE_MAX)
- {
- /* Check edge parity, if edge positive*/
- if ((edgeCountCheck % 2) == 0)
- {
- em4095_unique_data.result <<= 1;
- em4095_unique_data.result |= (uint64_t)readBitValue;
- countBits++;
- }
- /* Add next value to edge counter */
- edgeCountCheck++;
- }
- else
- {
- /* Change bit value */
- readBitValue ^= 0x01;
- em4095_unique_data.result <<= 1;
- em4095_unique_data.result |= (uint64_t)readBitValue;
- countBits++;
- edgeCountCheck += 2;
- }
- /* Check if receive all 64 bits of data */
- if (countBits > 64)
- {
- edgeCountCheck = 0;
- /* Set flag for data ready to process*/
- em4095_unique_data.is_frame_ready = true;
- /* If wrong data */
- if (!em4095_unique_data.data_frame)
- {
- /* Disable interrupts */
- TIM2MspDeInit(&htim2);
- em4095_unique_data.data_frame = em4095_unique_data.result;
- em4095_unique_data.is_processed = false;
- }
- }
- }
- }
W przerwaniu od timera sprawdzane są długości poszczególnych impulsów, tak aby można było zweryfikować jaki impuls został odebrany długi czy krótki. W przypadku zastosowania innej częstotliwości licznika należy zmodyfikować czas impulsu długiego, krótkiego oraz tolerancji.
Teraz funkcja główna, której wywołanie następuje w pętli while:
Aby uśpić układ to należy ściągnąć pin SHD do masy. Spowoduje to wyłączenie wszystkich jego peryferiów. W celu jego wybudzenia należy podać na pin stan wysoki.
Biblioteka dla tego układu jest do pobrania pod tym linkiem.
- void EM4095_Main_Function(void)
- {
- /* If data is ready */
- if (em4095_unique_data.is_frame_ready)
- {
- /* refresh counter so the card will not be read again when still present in field */
- card_reset_timer = CARD_RESET_TIMER;
- counter_started = true;
- if (FindHeader(&em4095_unique_data)) /* Check if header is ok */
- {
- /* Check parity */
- if (HorizontalParity(em4095_unique_data.data_frame) && VerticalParity(em4095_unique_data.data_frame))
- {
- memset(em4095_unique_data.card_number, 0, sizeof(em4095_unique_data.card_number));
- GetDataForCard(&em4095_unique_data, em4095_unique_data.card_number);
- /* Check if there is different number */
- if (!((em4095_unique_data.old_card_number[0] == em4095_unique_data.card_number[0]) &&
- (em4095_unique_data.old_card_number[1] == em4095_unique_data.card_number[1]) &&
- (em4095_unique_data.old_card_number[2] == em4095_unique_data.card_number[2]) &&
- (em4095_unique_data.old_card_number[3] == em4095_unique_data.card_number[3])))
- {
- for(uint8_t i = 0; i<4; i++){
- em4095_unique_data.old_card_number[i] = em4095_unique_data.card_number[i];
- }
- /* Print rejestration on the screen */
- DisplayRegistrationOnTFTDisplay(&em4095_unique_data.card_number);
- }
- }
- else
- {
- em4095_unique_data.data_frame = 0;
- em4095_unique_data.is_frame_ready = false;
- }
- }
- else
- {
- em4095_unique_data.data_frame = 0;
- em4095_unique_data.is_frame_ready = false;
- }
- TIM2MspInit(&htim2);
- em4095_unique_data.is_processed = true;
- em4095_unique_data.data_frame = 0;
- em4095_unique_data.is_frame_ready = false;
- }
- else if ((card_reset_timer == 0) && (counter_started == true))
- {
- /*
- * counter will reset both stored card numbers after set time, and after card
- * is taken from the field
- */
- counter_started = false;
- allow_send = true;
- for(uint8_t i = 0; i<4; i++)
- {
- em4095_unique_data.old_card_number[i] = 0;
- em4095_unique_data.card_number[i] = 0;
- }
- }
- }
Aby uśpić układ to należy ściągnąć pin SHD do masy. Spowoduje to wyłączenie wszystkich jego peryferiów. W celu jego wybudzenia należy podać na pin stan wysoki.
W celu wywołania programu w głównej funkcji programu należy umieścić dwie funkcje:
- int main(void)
- {
- //...
- //...
- EM4095_Init();
- //...
- //...
- while(1) {
- //...
- //...
- EM4095_Main_Function();
- //...
- //...
- }
- }
Biblioteka dla tego układu jest do pobrania pod tym linkiem.