ACR122U(Linux)를 사용하여 C/C++ 응용 프로그램 만들기

ACR122U NFC 카드 리더기는 PC에 연결된 무접촉 스마트 카드 리더기다.이 자습서에서는 ACR122U와 통신할 수 있는 Linux에서 실행되는 간단한 C/C++ 프로그램을 만드는 방법을 학습합니다.이 응용 프로그램은 MIFARE Ultralight 및 MIFARE Classic 1K 태그에서 데이터를 전송하고 검색하는 데 사용됩니다.
이 강좌를 배우려면 다음이 필요합니다.
  • A ACR122U.
  • 은 ISO/IEC 14443 Class A 규격에 맞는 무접촉 스마트 카드(예: MIFARE Ultralight 및 MIFARE Classic 1K 레이블)입니다.
  • Linux 릴리스(이 자습서는 Fedora와 Ubuntu에서 테스트되었으나 다른 릴리스에는 적용되어야 함)
  • C.
  • 의 기본 사항

    1. 드라이버 설치
    첫 번째 단계는 ACR122U의 드라이버를 설치하는 것입니다.우리가 만들 프로그램을 개발하고 실행하려면 이 절차가 필요합니다.
    다운로드 부분의 official website을 살펴보겠습니다.
    Linux는 두 가지 다운로드가 있습니다.

  • PC/SC Driver Package에는 다양한 릴리즈(Debian, Fedora, Ubuntu, Raspbian...)에 사용할 수 있는 패키지가 포함되어 있습니다.만약 이 발행판 중 하나를 사용한다면, 이것은 드라이버를 설치하는 가장 간단한 방법입니다.

  • PC/SC Drivers 드라이버의 소스 코드를 포함합니다.설치 전에 드라이버를 구축해야 하기 때문에, 이 드라이버는 어떤 리눅스 버전에서도 작동할 수 있기 때문에 좀 복잡하다.
  • PC/SC Drivers을 구축하고 설치하는 방법을 보여 드리겠습니다.먼저 소스 코드를 다운로드하고 압축을 풉니다.
    wget https://www.acs.com.hk/download-driver-unified/12030/ACS-Unified-Driver-Lnx-Mac-118-P.zip
    unzip ACS-Unified-Driver-Lnx-Mac-118-P.zip
    cd ACS-Unified-Driver-Lnx-Mac-118-P/
    
    README 파일은 소스를 구축하는 데 필요한 내용을 나타냅니다.
  • pcsclite 1.8.3 이상
  • libusb1.0.9 이상
  • flex
  • perl
  • 패키지 구성
  • 또한 다음 두 소프트웨어 패키지를 설치해야 합니다.
  • pcsclite devel
  • libusb-devel
  • 그래서 너는 반드시 먼저 이 소프트웨어 패키지들을 설치해야 한다.Fedora 및 Ubuntu에서 명령은 다음과 같습니다.
    # Fedora
    sudo dnf install pcsc-lite libusb flex perl pkg-config pcsc-lite-devel libusb-devel
    
    # Ubuntu
    sudo apt-get install pcscd libpcsclite1 libusb-1.0-0 flex perl pkg-config libpcsclite-dev libusb-1.0-0-dev
    
    그런 다음 소스 코드의 압축을 풀고 구성, 구축 및 설치를 수행할 수 있습니다.
    tar -xf acsccid-1.1.8.tar.bz2
    cd acsccid-1.1.8/
    ./configure
    ./configure --disable-dependency-tracking # for Ubuntu
    make
    sudo make install
    
    두 단계가 더 필요합니다.먼저 pcscd 서비스가 실행 중인지 확인해야 합니다.
    sudo systemctl start pcscd
    sudo systemctl enable pcscd
    
    그런 다음 다음 다음 명령을 실행해야 합니다.
    echo "blacklist pn533_usb" | sudo tee -a /etc/modprobe.d/blacklist-libnfc.conf
    
    이것은 드라이버와 기본 핵 모듈 사이의 충돌을 피하는 데 사용됩니다.
    이제 컴퓨터를 다시 시작할 수 있다.
    만약 당신이 Yocto를 사용한다면, 나는 식단 here을 만들었다.

    2. SDK 설치
    다음 단계는 응용 프로그램 개발에 필요한 SDK를 설치하는 것이지만, 응용 프로그램을 실행하는 데 필요한 것은 아니다.
    공식 웹사이트에서 제안한 SDK는 사용하지 않습니다.소스가 아닌 i386 및 x86 64 CPU 아키텍처에서만 사용할 수 있으므로 Raspberry Pi에서 ARM CPU 아키텍처를 사용할 수 없습니다.
    대신 PCSC lite을 사용하겠습니다.systemd-devel 패키지와 Ubuntu용 libudev-dev 패키지가 필요합니다.
    # Fedora
    sudo dnf install systemd-devel
    
    # Ubuntu
    sudo apt-get install libsystemd-dev libudev-dev
    
    그런 다음 소스 코드를 다운로드, 압축 해제, 구성, 구축 및 설치할 수 있습니다.
    wget https://pcsclite.apdu.fr/files/pcsc-lite-1.9.3.tar.bz2
    tar -xf pcsc-lite-1.9.3.tar.bz2
    cd pcsc-lite-1.9.3/
    ./configure
    make
    sudo make install
    

    3. 컴퓨터에서 ACR122U가 감지되었는지 확인(옵션)
    응용 프로그램을 만들기 전에 컴퓨터에서 카드 리더기를 인식할 수 있는지 확인하는 것이 좋습니다. 카드 리더기를 삽입하고 pcsc-tools 패키지를 설치한 다음 pscs_scan 명령을 실행하십시오.
    카드 리더기가 감지되지 않으면 다음 메시지가 표시됩니다.
    Waiting for the first reader... 
    
    솔루션은 pcscd 서비스를 다시 시작하는 것입니다.
    sudo systemctl restart pcscd
    
    계속 작동하지 않으면 다음 명령을 실행할 수 있습니다.
    systemctl status pcscd
    
    이것은 왜 카드 리더가 검출되지 않았는지 알려줄 수 있다.

    4. 응용 프로그램 만들기
    이제 응용 프로그램을 만듭니다.나는 gcc 컴파일러에서 C 언어를 사용할 것이지만, 그것은 C++와 g++에서도 호환된다.독자와의 교류를 위해 우리는 PC/SC Lite API (WinSCard)을 사용할 것이다.모든 코드는 main.c 파일로 GitHub에서 얻을 수 있습니다.
    이 자습서에서는 MIFARE Ultralight 및 MIFARE Classic 1K 태그를 사용합니다.
    먼저 WinS카드 라이브러리를 포함할 수 있는지 살펴보겠습니다.
    #include <winscard.h>
    
    int main() {
        return 0;
    }
    
    WinS카드 라이브러리의 헤더 파일은 /usr/local/include/PCSC에 있습니다.따라서 이 프로그램은 다음과 같은 방법으로 컴파일하고 실행할 수 있습니다.
    gcc main.c -lpcsclite -I/usr/local/include/PCSC
    ./a.out
    
    현재 우리는 WinSCard API의 문서를 사용하여 독자와 통신할 것이다.첫 번째 단계는 컨텍스트를 설정하는 것입니다(응용 프로그램이 끝날 때 게시해야 함).
    #include <stdio.h>
    #include <stdlib.h>
    #include <winscard.h>
    
    SCARDCONTEXT applicationContext;
    
    void establishContext() {
        SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &applicationContext);
    }
    void releaseContext() {
        SCardReleaseContext(applicationContext);
    }
    
    int main() {
        establishContext();
    
        releaseContext();
        return 0;
    }
    
    우리는 미래의 함수에 그것을 필요로 하기 때문에 전역 변수 applicationContext을 사용합니다.이 코드는 작업이 성공했는지 확인하지 않습니다.그래서 우리는 다음과 같이 덧붙였다.
    #include <stdio.h>
    #include <stdlib.h>
    #include <winscard.h>
    
    SCARDCONTEXT applicationContext;
    
    void establishContext() {
        LONG status = SCardEstablishContext(SCARD_SCOPE_SYSTEM, NULL, NULL, &applicationContext);
        if (status == SCARD_S_SUCCESS) {
            printf("Context established\n");
        } else {
            printf("Establish context error: %s\n", pcsc_stringify_error(status));
            exit(1);
        }
    }
    void releaseContext() {
        LONG status = SCardReleaseContext(applicationContext);
        if (status == SCARD_S_SUCCESS) {
            printf("Context released\n");
        } else {
            printf("Release context error: %s\n", pcsc_stringify_error(status));
            exit(1);
        }
    }
    
    int main() {
        establishContext();
    
        releaseContext();
        return 0;
    }
    
    이것은 모든 조작의 성공에 대한 정보를 제공하고 고장이 나면 프로그램을 종료합니다.
    그런 다음 사용 가능한 독자를 나열해야 합니다.
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <winscard.h>
    
    SCARDCONTEXT applicationContext;
    LPSTR reader = NULL;
    
    void establishContext() {}
    void releaseContext() {}
    
    void listReaders() {
        DWORD readers = SCARD_AUTOALLOCATE;
        LONG status = SCardListReaders(applicationContext, NULL, (LPSTR)&reader, &readers);
    
        if (status == SCARD_S_SUCCESS) {
            char *p = reader;
            while (*p) {
                printf("Reader found: %s\n", p);
                p += strlen(p) +1;
            }
        } else {
            printf("List reader error: %s\n", pcsc_stringify_error(status));
            exit(1);
        }
    }
    void freeListReader() {
        LONG status = SCardFreeMemory(applicationContext, reader);
        if (status == SCARD_S_SUCCESS) {
            printf("Reader list free\n");
        } else {
            printf("Free reader list error: %s\n", pcsc_stringify_error(status));
            exit(1);
        }
    }
    
    int main() {
        establishContext();
        listReaders();
    
        freeListReader();
        releaseContext();
        return 0;
    }
    
    글로벌 변수 reader을 추가했습니다. 이 변수는 카드 리더기를 식별하여 사용할 수 있도록 합니다.함수 SCardListReadersreader에 메모리를 분배하고 프로그램이 끝나기 전에 SCardFreeMemory 함수로 메모리를 방출해야 한다.카드 리더기를 삽입하는 동안 오류가 발생하면 컴퓨터가 ACR122U를 감지했는지 확인하십시오.SCardListReaders 함수가 실패하면 프로그램은 상하문을 방출하지 않고 종료됩니다.내가 이런 행동을 유지하는 것은 이 강좌를 가능한 한 간단하게 하기 위해서이지만, 이것은 네가 실제 응용 프로그램에서 하고 싶은 일이 아니다.
    다음 단계는 탭과 연결합니다.카드 리더기에 이름표(예: MIFARE Ultralight)를 배치하고 다음 코드를 실행합니다.
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <winscard.h>
    
    SCARDCONTEXT applicationContext;
    LPSTR reader = NULL;
    SCARDHANDLE connectionHandler;
    DWORD activeProtocol;
    
    void establishContext() {}
    void releaseContext() {}
    void listReaders() {}
    void freeListReader() {}
    
    void connectToCard() {
        activeProtocol = -1;
    
        LONG status = SCardConnect(applicationContext, reader, SCARD_SHARE_SHARED, SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1, &connectionHandler, &activeProtocol);
        if (status == SCARD_S_SUCCESS) {
            printf("Connected to card\n");
        } else {
            printf("Card connection error: %s\n", pcsc_stringify_error(status));
            exit(1);
        }
    }
    void disconnectFromCard() {
        LONG status = SCardDisconnect(connectionHandler, SCARD_LEAVE_CARD);
        if (status == SCARD_S_SUCCESS) {
            printf("Disconnected from card\n");
        } else {
            printf("Card deconnection error: %s\n", pcsc_stringify_error(status));
            exit(1);
        }
    }
    
    
    int main() {
        establishContext();
        listReaders();
        connectToCard();
    
        disconnectFromCard();
        freeListReader();
        releaseContext();
        return 0;
    }
    
    여기에는 두 개의 글로벌 변수가 있습니다.
  • connectionHandler: 라벨과 통신하는 데 사용됩니다.
  • activeProtocol: 이것은 이 연결의 이미 정해진 협의입니다.WinS카드가 사용할 프로토콜을 선택하도록 하기 때문에 추적 프로토콜이 필요합니다. 왜냐하면 우리는 SCARD_PROTOCOL_T0 | SCARD_PROTOCOL_T1SCardConnect 함수의 네 번째 매개 변수로 사용하기 때문입니다.예를 들어 SCARD_PROTOCOL_T0만 제공하여 사용할 프로토콜을 지정할 수 있습니다.탭에 사용자 정의 명령을 보내려면 프로토콜이 필요합니다.
  • 이전과 마찬가지로 연결은 프로그램이 끝나기 전에 닫아야 합니다.
    현재 우리는 탭과 연결되어 있으며, 예를 들어 탭의 ATR을 얻을 수 있다.ATR은 카드의 유형을 식별하는 데 사용할 수 있습니다.전체 목록은 here에서 얻을 수 있습니다.
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <winscard.h>
    
    SCARDCONTEXT applicationContext;
    LPSTR reader = NULL;
    SCARDHANDLE connectionHandler;
    DWORD activeProtocol;
    
    void establishContext() {}
    void releaseContext() {}
    void listReaders() {}
    void freeListReader() {}
    void connectToCard() {}
    void disconnectFromCard() {}
    
    void getCardInformation() {
        BYTE ATR[MAX_ATR_SIZE] = "";
        DWORD ATRLength = sizeof(ATR);
        char readerName[MAX_READERNAME] = "";
        DWORD readerLength = sizeof(readerName);
        DWORD readerState;
        DWORD readerProtocol;
    
        LONG status = SCardStatus(connectionHandler, readerName, &readerLength, &readerState, &readerProtocol, ATR, &ATRLength);
        if (status == SCARD_S_SUCCESS) {
            printf("\n");
            printf("Name of the reader: %s\n", readerName);
            printf("ATR: ");
            for (int i=0; i<ATRLength; i++) {
                printf("%02X ", ATR[i]);
            }
            printf("\n\n");
        } else {
            printf("Get card information error: %s\n", pcsc_stringify_error(status));
            exit(1);
        }
    }
    
    int main() {
        establishContext();
        listReaders();
        connectToCard();
    
        getCardInformation();
    
        disconnectFromCard();
        freeListReader();
        releaseContext();
        return 0;
    }
    
    마지막으로, 우리는 리더에게 사용자 정의 명령을 보낼 수 있다.ACR122U webpage으로 돌아가면 API Driver Manual of ACR122U NFC Contactless Smart Card Reader을 다운로드할 수 있습니다.이 문서는 우리가 독자에게 보낼 수 있는 명령 목록을 보여 줍니다.예를 들어, 카드 리더기의 펌웨어 버전(24페이지)을 읽어들입니다.관련 명령은 FFh 00h 48h 00h 00h이다.
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdint.h>
    #include <winscard.h>
    
    SCARDCONTEXT applicationContext;
    LPSTR reader = NULL;
    SCARDHANDLE connectionHandler;
    DWORD activeProtocol;
    
    void establishContext() {}
    void releaseContext() {}
    void listReaders() {}
    void freeListReader() {}
    void connectToCard() {}
    void getCardInformation() {}
    
    void sendCommand(uint8_t command[], unsigned short commandLength) {
        const SCARD_IO_REQUEST *pioSendPci;
        SCARD_IO_REQUEST pioRecvPci;
        uint8_t response[300];
        unsigned long responseLength = sizeof(response);
    
        switch(activeProtocol) {
            case SCARD_PROTOCOL_T0:
                pioSendPci = SCARD_PCI_T0;
                break;
            case SCARD_PROTOCOL_T1:
                pioSendPci = SCARD_PCI_T1;
                break;
            default:
                printf("Protocol not found\n");
                exit(1);
        }
    
        LONG status = SCardTransmit(connectionHandler, pioSendPci, command, commandLength, &pioRecvPci, response, &responseLength);
        if (status == SCARD_S_SUCCESS) {
            printf("Command sent: \n");
            for (int i=0; i<commandLength; i++) {
                printf("%02X ", command[i]);
            }
            printf("\nResponse: \n");
            for (int i=0; i<responseLength; i++) {
                printf("%02X ", response[i]);
            }
            printf("\n\n");
        } else {
            printf("Send command error: %s\n", pcsc_stringify_error(status));
            exit(1);
        }
    }
    
    int main() {
        establishContext();
        listReaders();
        connectToCard();
    
        getCardInformation();
    
        printf("Firmware command:\n");
        uint8_t firmwareCommand[] = { 0xFF, 0x00, 0x48, 0x00, 0x00 };
        unsigned short firmwareCommandLength = sizeof(firmwareCommand);
        sendCommand(firmwareCommand, firmwareCommandLength);
    
        disconnectFromCard();
        freeListReader();
        releaseContext();
    
        return 0;
    }
    
    연결된 프로토콜이 실행될 때 결정되기 때문에 switch 문구가 필요합니다.만약 우리가 SCARD_PROTOCOL_T0 함수에서 SCardConnect만 사용한다면, 우리는 switch 문장이 아닌 pioSendPci = SCARD_PCI_T0;을 직접 작성할 수 있다.
    리더와 탭에 사용자 정의 명령을 보낼 수 있는 프로그램이 실행 중입니다.MIFARE Ultralight 및 MIFARE Classic 1k에서 데이터를 전송하고 읽어들이는 데 사용합니다.MIFARE Ultralight부터 시작하겠습니다.
    MIFARE Ultralight는 4바이트씩 16페이지로 구성됩니다.사용자 메모리는 5페이지부터 보호되지 않습니다. (인증이 없는 상황에서 접근할 수 있습니다.)자세한 내용은 태그의 data sheet에서 확인할 수 있습니다.
    API Driver Manual of ACR122U의 16페이지와 17페이지에서 읽기와 쓰기 명령을 볼 수 있습니다.5페이지(04h페이지)에서 16바이트를 읽고 5페이지에 4바이트를 쓰는 함수를 작성합니다.
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdint.h>
    #include <winscard.h>
    
    SCARDCONTEXT applicationContext;
    LPSTR reader = NULL;
    SCARDHANDLE connectionHandler;
    DWORD activeProtocol;
    
    void establishContext() {}
    void releaseContext() {}
    void listReaders() {}
    void freeListReader() {}
    void connectToCard() {}
    void disconnectFromCard() {}
    void getCardInformation() {}
    void sendCommand(uint8_t command[], unsigned short commandLength) {}
    
    void mifareUltralight() {
        printf("### MIFARE Ultralight ###\n");
        uint8_t pageNumber = 0x04;
    
        // Read 4 blocks (16 bytes) starting from pageNumber
        uint8_t readCommand[] = { 0xFF, 0xB0, 0x00, pageNumber, 0x10 };
        unsigned short readCommandLength = sizeof(readCommand);
        sendCommand(readCommand, readCommandLength);
    
        // Write 1 block (4 bytes) to pageNumber
        uint8_t data[] = { 0x00, 0x01, 0x02, 0x03 };
        uint8_t writeCommand[9] = { 0xFF, 0xD6, 0x00, pageNumber, 0x04 };
        unsigned short writeCommandLength = sizeof(writeCommand);
        for (int i=0; i<4; i++) {
            writeCommand[i+5] = data[i];
        }
        sendCommand(writeCommand, writeCommandLength);
    }
    
    int main() {
        establishContext();
        listReaders();
        connectToCard();
    
        getCardInformation();
    
        printf("Firmware command:\n");
        uint8_t firmwareCommand[] = { 0xFF, 0x00, 0x48, 0x00, 0x00 };
        unsigned short firmwareCommandLength = sizeof(firmwareCommand);
        sendCommand(firmwareCommand, firmwareCommandLength);
    
        mifareUltralight();
    
        disconnectFromCard();
        freeListReader();
        releaseContext();
        return 0;
    }
    
    마지막으로 MIFARE Classic 1k에 대해 동일한 작업을 수행합니다.레이블은 4개의 블록에 16개의 섹터로 구성되며 각 블록은 16바이트로 구성됩니다.데이터는 6바이트 키로 보호되며 기본 키는 FFh FFh FFh FFh FFh FFh입니다.자세한 내용은 태그의 data sheet에서 확인할 수 있습니다.
    MIFARE Classic 1k를 사용하려면 API Driver Manual of ACR122U의 12페이지와 13페이지와 같이 섹터에 액세스하기 전에 인증해야 합니다.
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <stdint.h>
    #include <winscard.h>
    
    SCARDCONTEXT applicationContext;
    LPSTR reader = NULL;
    SCARDHANDLE connectionHandler;
    DWORD activeProtocol;
    
    void establishContext() {}
    void releaseContext() {}
    void listReaders() {}
    void freeListReader() {}
    void connectToCard() {}
    void disconnectFromCard() {}
    void getCardInformation() {}
    void sendCommand(uint8_t command[], unsigned short commandLength) {}
    void mifareUltralight() {}
    
    void mifareClassic() {
        printf("### MIFARE Classic ###\n");
        uint8_t blockNumber = 0x04;
    
        // Load Authentication Keys
        uint8_t key[6] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };
        uint8_t authenticationKeysCommand[11] = { 0xFF, 0x82, 0x00, 0x00, 0x06 };
        unsigned short authenticationKeysCommandLength = sizeof(authenticationKeysCommand);
        for (int i=0; i<6; i++) {
            authenticationKeysCommand[i+5] = key[i];
        }
        sendCommand(authenticationKeysCommand, authenticationKeysCommandLength);
    
        // Authenticate
        uint8_t authenticateCommand[] = { 0xFF, 0x86, 0x00, 0x00, 0x05, 0x01, 0x00, blockNumber, 0x60, 0x00 };
        unsigned short authenticateCommandLength = sizeof(authenticateCommand);
        sendCommand(authenticateCommand, authenticateCommandLength);
    
        // Read 1 block (16 bytes) at blockNumber
        uint8_t readCommand[] = { 0xFF, 0xB0, 0x00, blockNumber, 0x10 };
        unsigned short readCommandLength = sizeof(readCommand);
        sendCommand(readCommand, readCommandLength);
    
        // Write 1 block (16 bytes) at blockNumber
        uint8_t data[] = { 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F };
        uint8_t writeCommand[21] = { 0xFF, 0xD6, 0x00, blockNumber, 0x10 };
        unsigned short writeCommandLength = sizeof(writeCommand);
        for (int i=0; i<16; i++) {
            writeCommand[i+5] = data[i];
        }
        sendCommand(writeCommand, writeCommandLength);
    }
    
    int main() {
        establishContext();
        listReaders();
        connectToCard();
    
        getCardInformation();
    
        printf("Firmware command:\n");
        uint8_t firmwareCommand[] = { 0xFF, 0x00, 0x48, 0x00, 0x00 };
        unsigned short firmwareCommandLength = sizeof(firmwareCommand);
        sendCommand(firmwareCommand, firmwareCommandLength);
    
        //mifareUltralight();
        mifareClassic();
    
        disconnectFromCard();
        freeListReader();
        releaseContext();
        return 0;
    }
    

    5. 결론
    이제 ACR122U를 사용하여 MIFARE Ultralight 및 Classic 1k에서 데이터를 전송하고 검색하는 방법을 알 수 있습니다.대부분의 응용 프로그램에 있어서 이 정도면 충분할 것이다.그렇지 않으면 다음을 볼 수 있습니다.
  • PC/SC Lite API (WinSCard) 설명서를 참조하십시오.
  • API Driver Manual of ACR122U NFC Contactless Smart Card Reader.
  • 자체 레이블의 데이터 시트입니다.
  • 좋은 웹페이지 즐겨찾기