안경잡이개발자

728x90
반응형

  라즈베리파이(Raspberry Pi) Zero W를 이용하면 나만의 커스텀 USB HID 기기를 만들 수 있다. 이는 엄밀히 말하면 USB OTG 포트를 이용하여 라즈베리파이를 일종의 USB 디바이스(device)로 사용하는 것으로, 대용량 저장소(mass storage)부터 시작하여 키보드(keyboard)나 마우스 등의 HID 기기까지 다양한 USB 기능을 제공할 수 있다. 하지만 나만의 커스텀 HID 기기를 만드는 방법에 관해서는 소개하고 있는 글이 적다. 본 포스팅에서는 나만의 커스텀 HID 장치를 만드는 방법을 소개한다.

 

1. 커스텀 HID 기기의 Descriptor 명시 및 구현

 

※ Libcomposite 라이브러리 소개 ※

 

  최신 리눅스 커널(kernel)은 libcomposite 라이브러리를 제공한다. 이 라이브러리는 여러 개의 가젯(gadget)을 한꺼번에 활성화 할 수 있도록 해준다. 여기에서 가젯(gadget) 모드란, 리눅스 시스템을 호스트 컴퓨터의 입장에서 볼 때 USB 장치인 것처럼 보이게 하는 기능을 의미한다. 라즈베리파이 OS에서도 이를 지원하기 때문에 본 포스팅에서도 libcomposite을 이용하여 커스텀 HID 기능을 제공하도록 한다. 이는 현재 가장 정석적인 방법 중 하나이다.

 

※ USB 장치의 Descriptor 정의하기 ※

 

  USB 장치는 다양한 descriptor에 자신의 정보를 담아 호스트(host) PC에게 전송한다. 호스트 PC 입장에서는 자신에게 연결된 USB 장치가 무엇인지 모르기 때문에, 이는 당연한 절차이다.

  ▶ Device Descriptor: USB 장치의 전반적인 정보를 표현한다. 대표적으로 Vendor ID, Product ID를 통해 이 제품이 어떤 제품인지  고유한 ID를 알려주도록 하고, 어떠한 USB 버전을 따르는지 작성한다. 

 

  ▶ Configuration Descriptor: 각각의 configuration이 사용하는 소비 전력과 인터페이스(interface)의 개수를 명시한다. 일반적으로 USB 장치의 configuration은 1개이고, 인터페이스는 1개 혹은 여러 개를 사용한다.

  ▶ Interface Descriptor: USB 장치의 인터페이스(interface)에 대한 정보를 명시한다. 여기에서 하나의 인터페이스는 키보드와 같은 USB의 하나의 기능(function)을 담당한다. 여기에서 엔드포인트(endpoint)의 개수도 표시해야 한다. 예를 들어 HID가 USB 패킷을 받을 수도 있고, 내보낼 수도 있다면 엔드포인트는 IN, OUT 각각을 위해 2개를 사용하는 것이 일반적이다.

  ▶ String Descriptor: 문자열 형태의 추가적인 정보를 명시한다. 제조업체(manufacturer), 상품 이름(product), 시리얼 번호(serial number)가 여기에 포함된다.

 

  필자의 경우 Vendor ID로 0x1D6B, Product ID로 0x0104를 사용하겠다. 이는 각 Linux Foundation, Multifunction Composite Gadget을 의미한다. 사실 엄밀히 말하면 자기만의 Vendor ID와 Product ID를 정의해서 사용해도 되지만, 리눅스 기본 예제를 따라 이처럼 작성하겠다. 또한 USB의 버전은 2.0을 사용하고, 제조업체명은 "Dongbin Na", 상품 이름은 "My USB"로 설정하겠다. 실질적으로 바로 다음과 같이 스크립트를 작성하면 된다.

 

  필자의 경우 /usr/bin/my_usb라는 이름으로 스크립트를 작성했으며 chmod +7 /usr/bin/my_usb 명령어를 이용하여 실행 권한을 부여했다.

 

#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p my_usb
cd my_usb

# 기본적인 USB 클래스 명시
echo 0x1D6B > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB 2.0

# 내가 만들 USB 장치의 기본적인 이름
mkdir -p strings/0x409
echo "0123456789abcdef" > strings/0x409/serialnumber
echo "Dongbin Na" > strings/0x409/manufacturer
echo "My USB" > strings/0x409/product

# 하나의 Configuration 정보 작성
mkdir -p configs/c.1/strings/0x409
echo "My USB Config 1" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower

# 실질적으로 USB 기능(function)들이 들어갈 공간

# UDC (Usb Device Controller)
ls /sys/class/udc > UDC

 

※ HID 환경 설정 ※

 

  앞서 작성한 스크립트에서는 device descriptor와 configuration descriptor가 명시되었다. 이제 우리는 HID 기기를 만들 것이기 때문에 적절한 interface descriptor를 명시할 필요가 있다.

 

  USB 프로토콜에 따르면, 각각의 기능(function)은 하나의 인터페이스(interface)를 이용하도록 구현된다. HID의 경우 bInterfaceClass로 0x03을 사용하기로 약속되어 있다. 또한 bInterfaceSubclass와 bInterfaceProtocol도 명시해야 하는데, 대부분의 커스텀 HID 디바이스는 bInterfaceSubclass와 bInterfaceProtocol를 모두 0x00으로 설정한다. 참고로 키보드는 bInterfaceProtocol를 1로, 마우스는 bInterfaceProtocol를 2로 사용한다.

다시 말해 커스텀 HID의 경우 다음과 같이 설정하면 된다.

  ▶ Interface Class: 0x03
  Interface Subclass: 0x00
  Interface Protocol: 0x00

 

※ HID Report Descriptor 정의하기 ※

 

  또한 커스텀 HID 장치를 만들기 전에 HID report descriptor의 개념에 대하여 알아야 한다. 모든 HID 기기는 자신이 어떤 HID 기기인지에 대한 정보를 descriptor에 기록하여 호스트(host) PC로 보낸다. 호스트 PC 입장에서는 이 HID 기기가 어떠한 기기인지 알기 위해서 이러한 descriptor를 참고할 수 있다.

 

  HID 기기는 usage를 이용해 그 기능을 명시한다. 이때 usage pageusage ID가 사용된다. 예를 들어 usage page로 0x01을 사용하면 일반적인 데스크탑 목적이라는 의미이고, usage ID로 0x06을 사용하면 키보드 목적이라는 의미가 된다. 일반적으로 사용되는 usage 정보를 확인하고 싶다면 아래 링크를 참고하자.

 

  ▶ 참고: docs.microsoft.com/en-us/windows-hardware/drivers/hid/hid-usages

 

  다시 말해 이미 존재하는 키보드(keyboard)나 마우스(mouse)로서 동작하기를 원한다면, 이미 정해진 설정에 따라서 개발을 진행하면 되는 것이다. 하지만 커스텀 HID 장치를 만들고자 한다면 이를 직접 정의할 필요가 있다.

 

  필자는 usage page: 0xFF77, usage ID: 0x0009을 사용한다. 참고로 usage page나 usage ID를 범위 밖으로 마음대로 설정해 버리면 "보고서 ID 선언이 최상위 수준 컬렉션 외부에 있습니다."와 같은 메시지가 출력되며 정상적으로 HID 기기로 인식이 안 될 수도 있다. 따라서 usage page나 usage ID를 설정하는 것도 약속을 따라야 한다. HID descriptor를 만드는 것을 도와주는 USB 협회 공식 제공 툴도 있으니 참고하자.

 

/* Define: Custom HID Report Descriptor */
{
    0x06, 0x77, 0xFF,   // USAGE_PAGE (Vendor Defined)
    0x09, 0x00,         // USAGE (Undefined)
    0xA1, 0x01,         // COLLECTION (Application)
    0x09, 0x01,         // USAGE (Vendor Usage 1)
    0x15, 0x00,         // LOGICAL_MINIMUM (0)
    0x26, 0xFF, 0x00,   // LOGICAL_MAXIMUM (255)
    0x85, 0x01,         // REPORT_ID (1)
    0x75, 0x08,         // REPORT_SIZE (8)
    0x95, 0x40,         // REPORT_COUNT (64)
    0x81, 0x02,         // INPUT (Data, Var, Abs): Device to Host
    0x09, 0x02,         // USAGE (Vendor Usage 2)
    0x15, 0x00,         // LOGICAL_MINIMUM (0)
    0x26, 0xFF, 0x00,   // LOGICAL_MAXIMUM (255)
    0x85, 0x02,         // REPORT_ID (2)
    0x75, 0x08,         // REPORT_SIZE (8)
    0x95, 0x40,         // REPORT_COUNT (64)
    0x91, 0x02,         // OUTPUT (Data, Var, Abs): Host to Device
    0xC0                // END_COLLECTION
};

 

  따라서 최종적으로 다음과 같이 스크립트를 작성하면 된다. 현재 예시에서는 report size는 8비트, report count를 64를 사용한다. 결과적으로 패킷 크기(packet size)를 64 바이트로 설정하는 것이다.

 

mkdir -p functions/hid.usb0
echo 0 > functions/hid.usb0/protocol
echo 0 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
echo -ne \\x06\\x77\\xFF\\x09\\x00\\xA1\\x01\\x09\\x01\\x15\\x00\\x26\\xFF\\x00\\x85\\x01\\x75\\x08\\x95\\x40\\x81\\x02\\x09\\x02\\x15\\x00\\x26\\xFF\\x00\\x85\\x02\\x75\\x08\\x95\\x40\\x91\\x02\\xC0 > functions/hid.usb0/report_desc

ln -s functions/hid.usb0 configs/c.1/

 

  실제로 HID를 이용하는 경우 HID report descriptor가 매우 중요한 역할을 수행한다. HID report descriptor가 잘못 작성되어 있으면 운영체제가 정상적으로 인식하지 못한다. 특히 윈도우(Windows)의 경우 USB 장치가 무엇인지 알 수 없을 때 기본 USB 드라이버를 적용한다. 따라서 위와 같이 작성한 report descriptor가 잘 동작하지 않을 가능성도 있다.

 

  또한 라즈베리파이 측에서 스크립트에 실행 권한이 없어서 스크립트 자체가 실행되지 않는 경우에도 당연히 운영체제가 정상적으로 인식이 불가능 때문에, 이 부분에 대하여 인지할 필요가 있다. 추가로 윈도우(Windows)에서는 정상적으로 인식하지만 Virtual Machine에서는 인식을 못 하는 경우도 있다. 이럴 때는 VM을 재부팅하도록 하자.

 

※ 최종적인 환경 설정 스크립트 ※

 

  최종적으로 다음과 같은 스크립트를 작성하여 라즈베리파이가 이를 실행하도록 만들면 된다.

 

#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p my_usb
cd my_usb

# 기본적인 USB 클래스 명시
echo 0x1D6B > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB 2.0

# 내가 만들 USB 장치의 기본적인 이름
mkdir -p strings/0x409
echo "0123456789abcdef" > strings/0x409/serialnumber
echo "Dongbin Na" > strings/0x409/manufacturer
echo "My USB" > strings/0x409/product

# 하나의 Configuration 정보 작성
mkdir -p configs/c.1/strings/0x409
echo "My USB Config 1" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower

# 실질적으로 USB 기능(function)들이 들어갈 공간
mkdir -p functions/hid.usb0
echo 0 > functions/hid.usb0/protocol
echo 0 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
echo -ne \\x06\\x77\\xFF\\x09\\x00\\xA1\\x01\\x09\\x01\\x15\\x00\\x26\\xFF\\x00\\x85\\x01\\x75\\x08\\x95\\x40\\x81\\x02\\x09\\x02\\x15\\x00\\x26\\xFF\\x00\\x85\\x02\\x75\\x08\\x95\\x40\\x91\\x02\\xC0 > functions/hid.usb0/report_desc

# 만들어진 기능(function) 사용할 수 있도록 설정
ln -s functions/hid.usb0 configs/c.1/

# UDC (Usb Device Controller)
ls /sys/class/udc > UDC

 

  실제로 라즈베리파이가 위 스크립트를 실행하도록 만들고 라즈베리파이를 호스트 PC(리눅스 OS)에 연결해보자. 참고로 기본적인 OS가 윈도우(Windows)인 사람은 VM을 설치하여 사용할 수 있다. 기본적으로 윈도우(Windows) OS에 연결하면 장치 관리자에 다음과 같이 등장한다. 정상적으로 VID: 1D6B, PID: 0104로 기기가 인식되는 것을 알 수 있다.

 

 

  앞서 말했듯이 Oracle VM VirtualBox 환경이라면 다음과 같이 USB 장치를 인식하도록 해주어야 한다.

 

 

  이후에 호스트 PC에서 lsusb 명령어를 이용해 연결된 USB 정보를 확인하면 다음과 같다. 앞서 정의했던 내용 그대로 환경 설정이 되어 있는 것을 확인할 수 있다. 기본적인 Device Descriptor가 모두 정의했던 대로 인식된다.

 

 

  참고로 USB 가젯의 설정 자체를 바꾼 경우라면 UDC를 새롭게 설정하더라도 재부팅을 하지 않으면 환경 설정 내용이 바뀌지 않는 경우가 있다. 따라서 동적으로 가젯 하나만 더 추가하는 정도가 아니라면 재부팅을 수행하자.

 

2. 커스텀 HID 기기 Host 사이드 구현

 

※ 호스트(Host) 프로그램 만들기 ※

 

  리눅스 호스트(host) 상에서 커스텀 HID 기기를 처리하기 위한 프로그램을 간단히 만들어보자. 사실 Teensy 공식 사이트의 코드를 참고하여 미리 프로그램을 작성해 놓았다. 전체 코드는 필자의 깃허브 저장소에서 확인할 수 있다.

 

  코드 중에서 hid_LINUX.c리눅스의 usb.h에 정의된 함수를 이용해 HID 관련 함수를 정의한 코드이다. HID 장치를 찾는 함수, HID 장치로 USB 패킷을 보내는 함수, HID 장치에서 USB 패킷을 받는 함수가 정의되어 있다. 흔히 HID 프로토콜을 따르되 단순히 interrupt transfer를 이용하여 패킷을 주고 받는 형식을 raw HID라고 부른다. 실제 코드를 확인해 보면 usb_interrupt_read() 함수 usb_interrupt_write() 함수를 이용하는 것을 확인할 수 있다.

 

 

  이후에 custom_hid.c 코드에서는 앞서 정의한 함수들을 이용하여 HID 기기와 통신을 진행한다. 앞서 설명했듯이 vendor ID: 0x1D6B, product ID: 0x0104, usage page: 0xFF77, usage ID: 0x0009를 가지는 HID 기기를 찾는 것을 알 수 있다. HID 기기를 찾아서 연 이후에는 매번 HID 기기로부터 64 바이트씩 데이터를 전달 받도록 한다. 데이터를 받은 이후에는 전달 받은 데이터를 출력하고, 다시 64바이트의 데이터를 채워서 전송한다.

 

 

  단순히 라즈베리파이를 연결한 뒤에 custom_hid 프로그램을 실행하면 다음과 같이 HID 기기를 찾아내는 것을 알 수 있다. 참고로 알려지지 않은 HID 기기를 이용하기 위해서는 sudo 권한으로 프로그램을 실행할 필요가 있다.

 

 

3. 커스텀 HID 기기 Device 사이드 구현

 

※ 호스트(Host)로 데이터 전송해보기 ※

 

  HID gadget을 사용할 때는 단순히 HID 장치에 파일 쓰기(file write)을 이용하여 문제를 해결할 수 있다.  echo 명령어에 ne 옵션을 붙여서 (n 옵션: new line 제외, e 옵션: escape 문자 사용) 호스트로 데이터를 보낼 수 있다. 실제로 라즈베리파이에서 다음과 같은 명령어를 실행하면 된다.

 

sudo bash -c 'echo -ne "\x97\x77" > /dev/hidg0'

 

 

  그러면 호스트(host) 쪽에서는 다음과 같이 메시지를 전송 받게 된다.

 

 

  참고로 Raspberry Pi가 호스트(host) 쪽에서 온 데이터를 읽기 위한 목적으로 sudo cat /dev/hidg0을 입력한 이후에 위와 같은 echo 명령어를 다시 실행하면 write error: Resource temporarily unavailable 와 같은 오류가 발생할 수 있다. 필자의 경우 라즈베리파이와 Linux OS 재부팅 이후에 문제가 사라졌다.

 

※ 호스트(Host)로부터 받은 데이터 확인하기 ※

 

  호스트로부터 받은 데이터를 확인하기 위해서는 간단히 /dev/hidg0에 전달된 데이터가 있는지 확인하면 된다.

 

 

※ Device 사이드 프로그램 작성하기 (기본형) ※

 

  간단하게 무한정 호스트로 데이터를 보내고, 호스트에서 도착한 데이터를 받아 출력하도록 해보자.

 

#include <stdio.h>
#include <unistd.h> // sleep()
#include <string.h> // memset()

int main(void) {
    FILE* file;
    char buf[64];

    while (1) {
        // send to host
        for (int i = 0; i < sizeof(buf); i++) {
            buf[i] = 'A' + i;
        }

        printf("\nsend %d bytes:\n", sizeof(buf));
        for (int i = 0; i < sizeof(buf); i++) {
            printf("%02X ", buf[i] & 255);
            if (i % 16 == 15 && i < sizeof(buf)- 1) printf("\n");
        }
        printf("\n");
        fflush(stdout);

        file = fopen("/dev/hidg0", "w+");
        fwrite(buf, 1, sizeof(buf), file);
        fclose(file);

        printf("send complete.");
        fflush(stdout);

        // receive from host
        file = fopen("/dev/hidg0", "w+");
        fread(buf, 1, sizeof(buf), file);
        fclose(file);

        printf("\nreceive %d bytes:\n", sizeof(buf));
        for (int i = 0; i < sizeof(buf); i++) {
            printf("%02X ", buf[i] & 255);
            if (i % 16 == 15 && i < sizeof(buf) - 1) printf("\n"); 
        }
        printf("\n"); 
        fflush(stdout);

        printf("receive complete.");
        fflush(stdout);

        sleep(1);
    }
    return 0;
}

 

  실행하면 디바이스에서는 다음과 같이 동작한다. 매번 호스트로 64 바이트의 데이터를 보낸 뒤에 'F'(46)로 가득 채워진 64 바이트의 데이터를 받는 것을 알 수 있다.

 

 

  반대로 호스트 쪽에서는 데이터를 전달 받은 뒤에 매번 'F'(46)로 가득 채운 64 바이트 크기의 문자열을 돌려준다.

 

 

  이러한 과정이 무한정 반복된다. 참고로 파일 쓰기(file write)과 파일 읽기(file read) 사이에 sleep()을 주어 interrupt polling 시간을 컨트롤하려고 했는데, 중간에 sleep()을 주는 경우에는 호스트로부터 정상적으로 패킷을 받아올 수 없었다. 그 이유에 대해서는 추후에 시간이 날 때 다시 찾아 볼 예정이다. 현재 예상되는 바는, 지금 코드상에서는 호스트 쪽에서 데이터를 보내기까지 무한정 대기하는 것으로 보인다.

 

※ HID 패킷 속도(Speed) 측정하기 ※

 

  이번에는 sleep() 호출 없이 무한정 패킷을 주고 받도록 만들어 보았다. 다음의 코드와 같이 차례대로 순번을 증가시키면서 데이터를 전송했는데, 데이터의 유실 없이 모든 패킷이 빠르게 주고 받아 지는 것을 확인할 수 있었다. 다만 패킷을 주고 받는 속도가 1초당 1,000번 정도는 나올 거라고 생각했는데, 실제로는 그보다 느리게 동작했다. 그래도 초당 30번 정도는 패킷을 주고 받는 것으로 보인다. 다시 말해 초당 2KB 정도의 속도는 나온다는 것인데, 대용량 데이터를 주고 받기에는 한계가 있는 것으로 보인다.

 

#include <stdio.h>
#include <unistd.h> // sleep()
#include <string.h> // memset()

int main(void) {
    FILE* file;
    char buf[64];
    char sign = 0;
    int cnt = 0;

    while (1) {
        // send to host
        for (int i = 0; i < sizeof(buf); i++) {
            buf[i] = 'A' + i;
        }
        buf[sizeof(buf) - 1] = sign++;

        printf("\n[%d] send %d bytes:\n", cnt++, sizeof(buf));
        for (int i = 0; i < sizeof(buf); i++) {
            printf("%02X ", buf[i] & 255);
            if (i % 16 == 15 && i < sizeof(buf) - 1) printf("\n");
        }
        printf("\n");
        fflush(stdout);

        file = fopen("/dev/hidg0", "w");
        fwrite(buf, 1, sizeof(buf), file);
        fclose(file);

        printf("send complete.");
        fflush(stdout);

        // receive from host
        file = fopen("/dev/hidg0", "r");
        fread(buf, 1, sizeof(buf), file);
        fclose(file);

        printf("\nreceive %d bytes:\n", sizeof(buf));
        for (int i = 0; i < sizeof(buf); i++) {
            printf("%02X ", buf[i] & 255);
            if (i % 16 == 15 && i < sizeof(buf) - 1) printf("\n"); 
        }
        printf("\n"); 
        fflush(stdout);

        printf("receive complete.");
        fflush(stdout);
    }
    return 0;
}

 

  디바이스(device) 쪽의 출력 형태는 다음과 같다.

 

 

※ Device 사이드 프로그램 작성하기: 호스트에서 데이터 받기만 하기 ※

 

  무작정 호스트로부터 데이터를 받기만 할 때는 다음과 같이 작성한다.

 

#include <stdio.h>
#include <unistd.h> // sleep()
#include <string.h> // memset()

#define BUFFER_SIZE 64 // 패킷(버퍼) 사이즈

FILE* file;
char buf[BUFFER_SIZE];

int main(void) {
    while (1) {
        // receive from host
        file = fopen("/dev/hidg0", "w+");
        fread(buf, 1, sizeof(buf), file);
        fclose(file);

        printf("\nreceive %d bytes:\n", sizeof(buf));
        for (int i = 0; i < sizeof(buf); i++) {
            printf("%02X ", buf[i] & 255);
            if (i % 16 == 15 && i < sizeof(buf) - 1) printf("\n"); 
        }
        printf("\n"); 
        fflush(stdout);

        printf("receive complete.");
        fflush(stdout);
    }
    return 0;
}

 

  만약 호스트가 예시로 임의의 크기의 데이터를 보내는 코드를 실행하게 되면 다음과 같이 데이터를 받게 된다.

 

 

※ 라즈베리파이 부팅 이후에 프로그램 자동 실행 ※

 

  부팅 이후에 HID로 데이터를 주고 받는 프로그램이 자동으로 실행되도록 만드려면 /etc/rc.local을 수정한다. 기본적으로 프로그램은 백그라운드(background)에서 실행되어야 하므로 다음과 같이 & 기호를 붙인다.

 

 

  이어서 재부팅을 하면 다음과 같이 설정했던 프로그램이 자동으로 정상적으로 실행되는 것을 알 수 있다.

 

 

※ Mass Storage와 함께 쓰기 ※

 

  다음과 같이 파일을 저장하기 위한 목적으로 블록 디바이스를 하나 만들 수 있다.

 

mkdir -p /home/pi/images

# 1Gb 이미지 생성
dd if=/dev/zero of=/home/pi/images/usbdisk1.img bs=1048576 count=1024
mkdosfs /home/pi/images/usbdisk1.img

 

  이제 환경 설정 파일을 다음과 같이 수정하여 HID와 Mass Storage Class를 같이 제공하도록 해보자. 호스트(host) PC 입장에서는 읽기만 가능하다고 가정한다.

 

#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p my_usb
cd my_usb

# 기본적인 USB 클래스 명시
echo 0x1D6B > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB 2.0

# 내가 만들 USB 장치의 기본적인 이름
mkdir -p strings/0x409
echo "0123456789abcdef" > strings/0x409/serialnumber
echo "Dongbin Na" > strings/0x409/manufacturer
echo "My USB" > strings/0x409/product

# 하나의 Configuration 정보 작성
mkdir -p configs/c.1/strings/0x409
echo "My USB Config 1" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower

# 실질적으로 USB 기능(function)들이 들어갈 공간
mkdir -p functions/hid.usb0
echo 0 > functions/hid.usb0/protocol
echo 0 > functions/hid.usb0/subclass
echo 8 > functions/hid.usb0/report_length
echo -ne \\x06\\x77\\xFF\\x09\\x00\\xA1\\x01\\x09\\x01\\x15\\x00\\x26\\xFF\\x00\\x85\\x01\\x75\\x08\\x95\\x40\\x81\\x02\\x09\\x02\\x15\\x00\\x26\\xFF\\x00\\x85\\x02\\x75\\x08\\x95\\x40\\x91\\x02\\xC0 > functions/hid.usb0/report_desc

# 만들어진 기능(function) 사용할 수 있도록 설정
ln -s functions/hid.usb0 configs/c.1/

################# Mass Storage 1 #################
# 첫 번째 이미지 지정하기
FILE1=/home/pi/images/usbdisk1.img

# 해당 이미지에 마운트(mount) 진행하기
mkdir -p ${FILE1/img/d}
mount -o loop, -t vfat $FILE1 ${FILE1/img/d} # 라즈베리 파이 입장에서 쓰기 가능

# USB 기능(function) 작성하기 (kernel.org reference 참고하기)
mkdir -p functions/mass_storage.usb0
echo 1 > functions/mass_storage.usb0/stall
echo 0 > functions/mass_storage.usb0/lun.0/cdrom
echo 1 > functions/mass_storage.usb0/lun.0/ro # 호스트 입장에서는 일기 전용(read-only)
echo 0 > functions/mass_storage.usb0/lun.0/nofua
echo $FILE1 > functions/mass_storage.usb0/lun.0/file # 이미지 파일 명시하기

# 만들어진 기능(function) 사용할 수 있도록 설정
ln -s functions/mass_storage.usb0 configs/c.1/

# UDC (Usb Device Controller)
ls /sys/class/udc > UDC

 

  이제 재부팅 이후에 성공적으로 HID 기능을 제공하면서, 그와 동시에 Mass Storage Class로의 기능도 제공하게 된다.  다만 한가지 유의할 점은 라즈베리파이가 호스트로부터 HID로 데이터를 주고받아 블록 장치에 쓰기 연산을 수행하는 상황에서, 호스트 입장에서는 Mass Storage 기능을 이미 제공 받고 있기 때문에 블록 장치에 새롭게 기록된 정보가 호스트 입장에서 즉시 보이지는 않는다. 이때 라즈베리파이의 데이터 케이블의 연결을 잠시  끊었다가 다시 연결하면, 비로소 호스트에 다시 Mass Storage 디바이스가 잡혔을 때 해당 파일들이 보이게 된다.

 

참고 자료

 

  1) 리눅스 공식 HID Gadget 튜토리얼: 본 사이트에서는 기본적인 마우스, 키보드 기능을 구현하는 방법을 소개한다. 커스텀 HID를 만들기 전에 간단히 실습해 보기에 좋다.

 

  2) HID Report Descriptor 생성 프로그램: usb.org 공식 웹 사이트에서 제공하는 HID descriptor 도구다.

728x90
반응형

728x90
반응형

대회 링크: codeforces.com/contest/1436

A번 문제: codeforces.com/contest/1436/problem/A

 

  아래 식이 의미하는 내용은 a1 + a2 + ... + an이다. 시그마가 중첩되어 작성되어 있어서 헷갈릴 수 있지만, 식을 나열해보면 그렇다. 다시 말해 그냥 모든 원소의 합이 M과 같다면 "YES", M과 다르다면 "NO"를 출력하면 된다.

 

 

for _ in range(int(input())):
    n, m = map(int, input().split())
    data = list(map(int, input().split()))

    # 모든 원소의 합이 M인 경우 "YES" 출력
    if sum(data) == m:
        print("YES")
    else:
        print("NO")

 

B번 문제: codeforces.com/contest/1436/problem/B

 

  이 문제는 원소로 0을 사용할 수 있다는 점을 생각하면 매우 쉽다. 1을 2개 쓰고, 나머지를 0으로 채우면 된다. 이후에 각 줄마다 시프트(shift)를 하면서 출력하면 된다. 예를 들어 N = 4일 때는 다음과 같이 만들면 된다.

 

for _ in range(int(input())):
    n = int(input())
    arr = [1, 1] + [0] * (n - 2)
    arr = arr + arr

    for i in range(n):
        for j in range(i, n + i):
            print(arr[j], end=' ')
        print()

 

  사실 나는 이 문제를 푸는 당시에는 N이 100 이하라서 에라토스테네스의 체를 이용해 N = 100까지의 모든 경우에 대하여 사전에 계산하는 방식을 이용했다. 아무튼 제일 쉬운 방법은 그냥 0과 1로 배열을 채우는 것이다.

 

C번 문제: codeforces.com/contest/1436/problem/C

 

  예시로 n = 6, x = 3, pos = 1이라고 해보자. 그러면 다음과 같이 3은 항상 인덱스(index) 1에 위치할 것이다. 이때 주어진 알고리즘에 맞게 그대로 시뮬레이션하면 된다. 일단 시작 단계는 다음과 같을 것이다.

 

 

  이후에 중간점(mid)을 확인한다. 향후 중간점의 왼쪽 부분을 탐색해야 하므로, 중간점(mid)에 위치한 값은 x보다 큰 값이 되어야 한다.

 

 

  이후에 마찬가지로 중간점(mid)을 확인하는데, x를 찾은 것을 알 수 있다. 여기에서 중요한 점주어진 알고리즘에 따르면 여기에서 알고리즘을 종료하지 않고 계속해서 탐색을 진행한다는 것이다. 이 부분을 놓치면 오답 판정을 받게 된다.

 

 

  다음 단계에서도 중간점(mid)을 확인한다. 여기에서 중간점은 마찬가지로 x보다 큰 수가 되어야 한다.

 

 

  이 예시에서의 정답은 (3보다 큰 수의 개수에서 2개를 뽑는 순열의 수) * 3!이 된다. 다시 말해 정답은 36이다. 이러한 원리를 그대로 코드로 옮겨 구현하면 다음과 같다.

 

MOD = int(1e9 + 7)

n, x, pos = map(int, input().split())

under_cnt = x - 1
over_cnt = n - x

under = 0
over = 0
 
left = 0
right = n

while left < right:
    middle = (left + right) // 2
    if middle < pos:
        left = middle + 1
        under += 1
    elif middle > pos:
        right = middle
        over += 1
    # (핵심) middle == pos일 때에도 멈추지 않음
    else:
        left = middle + 1

if under > under_cnt or over_cnt < over:
    print(0)
else:
    answer = 1
    # Permutation(under_cnt, under)
    for i in range(under):
        answer = (answer * (under_cnt - i)) % MOD
    # Permutation(over_cnt, over)
    for i in range(over):
        answer = (answer * (over_cnt - i)) % MOD
    # 남은 수들에 대해서도 Permutation 돌리면 정답
    for i in range(1, n - under - over):
        answer = (answer * i) % MOD
    print(answer)
728x90
반응형

728x90
반응형

  USB 장치(device)는 태생적으로 하나의 장치가 여러 개의 기능(function)을 제공할 수 있도록 만들어졌다. 참고로 여기에서 기능(function)이란 USB 프로토콜 상에서 인터페이스(interface)를 의미한다. 예를 들어 마우스(mouse), 키보드(keyboard), HID 장치, 대용량 저장소(mass storage) 등은 각각 하나의 기능(function)이다. 이후에 각 기능은 한 개 이상의 엔드포인트(endpoint)를 가질 수 있으며, 이를 통해 호스트(host) PC와 USB 프로토콜에 맞게 데이터를 주고받는다.

 

출처: Texas Instruments 교육 자료

 

  이번 포스팅에서는 Raspberry Pi Zero W를 이용해 멀티 가젯(multi-gadget) USB 장치를 만드는 방법을 소개하고자 한다. 우리의 라즈베리 파이를 일종의 USB 장치로 쓰면서, 동시에 대용량 저장소나 마우스 등의 다양한 기능을 제공하는 기기가 될 수 있도록 하는 것이다.

 

※ 기초 환경 설정 ※

 

  가장 먼저 USB OTG 기능을 제공하기 위해 라즈베리 파이의 /boot/config.txt 파일을 열어서 제일 마지막 줄에 다음의 코드를 추가한다. dwc2 USB 드라이버를 사용할 수 있도록 하는 것이다.

 

dtoverlay=dwc2

 

  Raspberry Pi Zero W를 기준으로 다음과 같다.

 

 

  이어서 /etc/modules의 마지막 줄에 다음 두 개의 커널 모듈을 넣어주자. 부팅 과정에서 아래의 두 모듈을 자동으로 로드하여 사용할 수 있도록 한다. 만약 부팅과 동시에 멀티 가젯 기능을 제공하지는 않고 수동적으로 필요할 때마다 기능을 제공하고 싶다면 libcomposite는 빼고 넣어주도록 하자.

dwc2 
libcomposite 

 

 

  이때 libcomposite 커널 모듈이 핵심적인 역할을 수행한다. 이 모듈은 여러 개의 가젯(gadget)을 한꺼번에 활성화 할 수 있도록 해준다. 일반적으로 USB 가젯(gadget) 모드란, 리눅스 시스템을 호스트 컴퓨터의 입장에서 보았을 때 USB 장치(device)인 것처럼 보이게 하는 방법을 의미한다. 우리의 Raspberry Pi를 HID 장치나 Mass Storage Class (MSC)처럼 보이게 만들 수 있다.

 

  이러한 배경에서 USB 가젯 기능을 제공하되, 한 번에 여러 개의 USB 디바이스 기능을 제공하려면 libcomposite 모듈을 사용할 수 있다. 이를 이용하면 나의 Raspberry Pi Zero W가 동시에 3개의 USB 대용량 저장소이면서, 그와 동시에 마우스와 키보드의 역할을 수행하도록 만들 수 있다. 이처럼 간단하게 여러 개의 USB 장치 역할을 수행하도록 만들 수 있다.

 

※ 멀티 가젯(Multi-gadget) 모드 확인 ※

 

  이제 /sys/kernel/config/usb_gadget/ 폴더를 활성화해야 한다. 이 폴더 안에서 규칙에 맞게 생성된 파일들을 통해 실제로 multi-gadget 역할을 수행할 수 있다. 이 폴더는 libcomposite 모듈을 커널에 적재함으로써 활성화할 수 있다. 앞서 우리는 재부팅을 했을 때 리눅스 커널 모듈 중에서 libcomposite이 불러와지도록 만들었다. 자동으로 모듈을 불러오도록 설정하지 않았다면 modprobe 명령을 이용해 수동으로 적재하자.

 

  modprobe 명령어커널 모듈(kernel module)을 리눅스 커널에 추가하거나 커널로부터 제거할 때 사용할 수 있다. 원래 모듈을 추가하거나 제거할 때는 insmod나 rmmod를 사용할 수 있는데, modprobe는 더욱 다양한 기능을 제공한다. modprobe는 모듈을 올릴 때 여러 개의 모듈을 한꺼번에 올릴 수 있으며, 의존성이 있는 모듈도 같이 적재할 수 있다.

 

  재부팅하지 않고 바로 libcomposite을 적재하기 위해서는 다음과 같은 명령어를 입력하면 된다. (/etc/modules에 libcomposite을 추가했다면 sudo reboot 명령을 통해 재부팅 하자.)

 

sudo modprobe libcomposite

 

  이후에 /sys/kernel/config/usb_gadget 경로가 생긴 것을 확인할 수 있다. 이제 이 경로에 각종 환경 설정 파일을 생성하여 우리의 라즈베리 파이가 USB 가젯으로 동작하게 만들 수 있다.

 

 

※ 환경 설정 스크립트 만들기 ※

 

  Texas Instruments 교육 자료를 보면 USB 멀티 가젯(multi-gadget) 구조를 쉽게 확인할 수 있다. 바로 다음과 같은 형태로 파일을 배치하면 그에 맞게 다양한 기능(function)이 동작한다.

 

출처: Texas Instruments 교육 자료

 

  따라서 이러한 구조에 맞게 파일을 생성하는 하나의 환경 설정 스크립트를 작성하는 것이 좋다.

 

sudo touch /usr/bin/my_usb # 파일 생성
sudo chmod +x /usr/bin/my_usb # 실행할 수 있도록 만들기

 

 

  이제 이러한 환경 설정 스크립트를 작성해보자.

 

sudo nano /usr/bin/my_usb # 스크립트 작성하기

 

가장 먼저 global configuration에 대하여 작성할 필요가 있다. 앞서 말했듯이 하나의 configuration 안에 여러 개의 기능(function)이 들어가 있는 형태로 동작한다. 따라서 일단 기능(function) 부분은 제외하고 configuration 관련 내용을 작성하여 전체적인 틀을 잡도록 하자.

 

#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p my_usb
cd my_usb

# 기본적인 USB 클래스 명시
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2

# 내가 만들 USB 장치의 기본적인 이름
mkdir -p strings/0x409
echo "0123456789abcdef" > strings/0x409/serialnumber
echo "Dongbin Na" > strings/0x409/manufacturer
echo "My USB" > strings/0x409/product

# 하나의 Configuration 정보 작성
mkdir -p configs/c.1/strings/0x409
echo "My USB Config 1" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower

# 실질적으로 USB 기능(function)들이 들어갈 공간

# UDC (Usb Device Controller)
ls /sys/class/udc > UDC

 

※ 대용량 저장소(Mass Storage) 기능 추가하기 ※

 

  필자는 개인적으로 Mass Storage Class (MSC) 기능이 필요한 상황이다. 따라서 Mass Storage 기능을 추가하는 방법을 소개한다. 일단 가장 먼저 블록 장치(block device)를 만들 필요가 있다. 라즈베리파이를 USB 대용량 저장소로 보이도록 만들고 싶다면, 호스트(host) PC 입장에서는 블록 단위로 접근할 필요가 있기 때문이다.

 

  필자는 다음과 같이 2개의 이미지를 만들었다. 이미지 내에서 하나의 블록 크기(block size)는 1,048,576 바이트(1Mb)로 설정했다. 이후에 mkdosfs 명령어를 이용해 파일 시스템 형태로 포맷(format)할 수 있도록 한다.

 

mkdir -p /home/pi/images

# 1Gb 이미지 생성
dd if=/dev/zero of=/home/pi/images/usbdisk1.img bs=1048576 count=1024
mkdosfs /home/pi/images/usbdisk1.img

# 500Mb 이미지 생성
dd if=/dev/zero of=/home/pi/images/usbdisk2.img bs=1048576 count=512
mkdosfs /home/pi/images/usbdisk2.img

 

 

  이제 기능(function) 부분에 다음과 같은 코드를 추가하자. 필자는 다음과 같이 총 2개의 Mass Storage를 제공할 수 있도록 2개의 기능을 명시했다.

 

################# Mass Storage 1 #################
# 첫 번째 이미지 지정하기
FILE1=/home/pi/images/usbdisk1.img

# 해당 이미지에 마운트(mount) 진행하기
mkdir -p ${FILE1/img/d}
mount -o loop,ro, -t vfat $FILE1 ${FILE1/img/d} # 라즈베리 파이 입장에서는 읽기 전용(read-only)

# USB 기능(function) 작성하기 (kernel.org reference 참고하기)
mkdir -p functions/mass_storage.usb0
echo 1 > functions/mass_storage.usb0/stall
echo 0 > functions/mass_storage.usb0/lun.0/cdrom
echo 0 > functions/mass_storage.usb0/lun.0/ro # 호스트 입장에서는 쓰기 가능
echo 0 > functions/mass_storage.usb0/lun.0/nofua
echo $FILE1 > functions/mass_storage.usb0/lun.0/file # 이미지 파일 명시하기

# 만들어진 기능(function) 사용할 수 있도록 설정
ln -s functions/mass_storage.usb0 configs/c.1/

################# Mass Storage 2 #################
# 두 번째 이미지 지정하기
FILE2=/home/pi/images/usbdisk2.img

# 해당 이미지에 마운트(mount) 진행하기
mkdir -p ${FILE2/img/d}
mount -o loop,ro, -t vfat $FILE2 ${FILE2/img/d} # 라즈베리 파이 입장에서는 읽기 전용(read-only)

# USB 기능(function) 작성하기 (kernel.org reference 참고하기)
mkdir -p functions/mass_storage.usb1
echo 1 > functions/mass_storage.usb1/stall
echo 0 > functions/mass_storage.usb1/lun.0/cdrom
echo 0 > functions/mass_storage.usb1/lun.0/ro # 호스트 입장에서는 쓰기 가능
echo 0 > functions/mass_storage.usb1/lun.0/nofua
echo $FILE2 > functions/mass_storage.usb1/lun.0/file # 이미지 파일 명시하기

# 만들어진 기능(function) 사용할 수 있도록 설정
ln -s functions/mass_storage.usb1 configs/c.1/

 

※ 두 개의 Mass Storage 장치 전체 스크립트 ※

 

  두 개의 Mass Storage를 이용하는 예제의 전체 /usr/bin/my_usb 스크립트 최종본은 다음과 같다.

 

#!/bin/bash
cd /sys/kernel/config/usb_gadget/
mkdir -p my_usb
cd my_usb

# 기본적인 USB 클래스 명시
echo 0x1d6b > idVendor # Linux Foundation
echo 0x0104 > idProduct # Multifunction Composite Gadget
echo 0x0100 > bcdDevice # v1.0.0
echo 0x0200 > bcdUSB # USB2

# 내가 만들 USB 장치의 기본적인 이름
mkdir -p strings/0x409
echo "0123456789abcdef" > strings/0x409/serialnumber
echo "Dongbin Na" > strings/0x409/manufacturer
echo "My USB" > strings/0x409/product

# 하나의 Configuration 정보 작성
mkdir -p configs/c.1/strings/0x409
echo "My USB Config 1" > configs/c.1/strings/0x409/configuration
echo 250 > configs/c.1/MaxPower

################# Mass Storage 1 #################
# 첫 번째 이미지 지정하기
FILE1=/home/pi/images/usbdisk1.img

# 해당 이미지에 마운트(mount) 진행하기
mkdir -p ${FILE1/img/d}
mount -o loop,ro, -t vfat $FILE1 ${FILE1/img/d} # 라즈베리 파이 입장에서는 읽기 전용(read-only)

# USB 기능(function) 작성하기 (kernel.org reference 참고하기)
mkdir -p functions/mass_storage.usb0
echo 1 > functions/mass_storage.usb0/stall
echo 0 > functions/mass_storage.usb0/lun.0/cdrom
echo 0 > functions/mass_storage.usb0/lun.0/ro # 호스트 입장에서는 쓰기 가능
echo 0 > functions/mass_storage.usb0/lun.0/nofua
echo $FILE1 > functions/mass_storage.usb0/lun.0/file # 이미지 파일 명시하기

# 만들어진 기능(function) 사용할 수 있도록 설정
ln -s functions/mass_storage.usb0 configs/c.1/

################# Mass Storage 2 #################
# 두 번째 이미지 지정하기
FILE2=/home/pi/images/usbdisk2.img

# 해당 이미지에 마운트(mount) 진행하기
mkdir -p ${FILE2/img/d}
mount -o loop,ro, -t vfat $FILE2 ${FILE2/img/d} # 라즈베리 파이 입장에서는 읽기 전용(read-only)

# USB 기능(function) 작성하기 (kernel.org reference 참고하기)
mkdir -p functions/mass_storage.usb1
echo 1 > functions/mass_storage.usb1/stall
echo 0 > functions/mass_storage.usb1/lun.0/cdrom
echo 0 > functions/mass_storage.usb1/lun.0/ro # 호스트 입장에서는 쓰기 가능
echo 0 > functions/mass_storage.usb1/lun.0/nofua
echo $FILE2 > functions/mass_storage.usb1/lun.0/file # 이미지 파일 명시하기

# 만들어진 기능(function) 사용할 수 있도록 설정
ln -s functions/mass_storage.usb1 configs/c.1/

# UDC (Usb Device Controller)
ls /sys/class/udc > UDC

 

  최종적으로 대략 다음과 같이 작성된다. (후반부 생략)

 

 

  이제 이러한 스크립트를 sudo 권한으로 실행하면 곧바로 USB 멀티 가젯 기능이 제공된다.

 

 

  이후에 호스트(host) PC에 연결하면 다음과 같이 정상적으로 대용량 저장소로 인식된다. 윈도우(Windows)와 우분투(Ubuntu) 모두에서 정상적으로 인식되는데, 윈도우 장치 관리자로 보았을 때는 다음과 같다.

 

 

  앞서 만들었던 두 개의 이미지(image)가 정상적으로 인식된다.

 


※ 내 스크립트가 자동으로 실행될 수 있도록 하기 ※

 

  또한 라즈베리파이가 재부팅되면 곧바로 스크립트가 실행되도록 설정할 수 있다. (단, 이게 정상적으로 동작하려면 앞서 /etc/modules에 libcomposite을 추가한 상태여야 한다.) /etc/rc.local을 수정하자. 리눅스는 부팅을 진행하는 과정에서 /etc/rc.local 스크립트를 자동으로 실행한다.

 

sudo nano /etc/rc.local

 

  이후에 exit 0 위쪽 라인에 /usr/bin/my_usb를 추가하자. 라즈베리파이 구동 이후에 자동으로 우리가 작성했던 스크립트를 구동하도록 만드는 것이다.

 

 

  이제 재부팅(reboot)을 진행한다.

 

 

  그러면 재부팅 이후에는 라즈베리파이를 연결만 하면 자동으로 Mass Storage 형태로 인식이 된다.

 

 

  다만 간혹 두 개의 Mass Storage를 이용하도록 했으나 하나만 인식되는 문제가 발생하거나, 연결이 되었다가 안 되었다가 하는 문제가 발생할 수 있다. 이럴 때는 데이터 공급선(USB OTG)이 정상적으로 연결되어 있는지를 다시 한 번 체크해보도록 하자. 혹은 호스트(host) PC의 다른 포트로 연결하면 잘 동작하는 경우가 있다.

 

※ 동적으로 Mass Storage 이미지 변경하기 ※

 

  Mass Storage 목적으로 리눅스 가젯(gadget) 드라이버를 활용한다면, 다양한 기능에 대하여 추가적으로 알아 두면 좋다. 참고로 Mass Storage의 경우 동적으로 블록 장치(block device)를 변경할 수 있다. 동적으로 블록 장치를 변경해 보기 위하여 하나의 블록 장치를 만들어 보자.

 

# 500Mb 이미지 생성
dd if=/dev/zero of=/home/pi/images/usbdisk3.img bs=1048576 count=512
mkdosfs /home/pi/images/usbdisk3.img

 

  이후에 Mass Storage 기능이 제공되고 있는 상황에서 다음과 같은 명령어를 입력하여 동적으로 블록 장치 이미지를 변경할 수 있다.

 

sudo bash -c 'echo "/home/pi/usbdisk3.img" > /sys/kernel/config/usb_gadget/my_usb/functions/mass_storage.usb0/lun.0/file'

 

※ 동적으로 멀티 가젯(Gadget) 기능 올리거나 내리기 ※

 

  라즈베리파이를 호스트(host) PC에 연결한 상태에서 원할 때 멀티 가젯 기능을 열거나 닫는 작업도 가능하다. 다음과 같이 예시로 2개의 Mass Storage 기능을 제공하는 라즈베리파이가 있다고 해보자.

 

 

  가젯(gadget) 기능을 enabling 하기 위해서는 UDC(USB Device Controller)에 바운딩을 시킬 필요가 있다. 같은 원리로 disabling을 할 때는 바운딩을 해제하면 되는 것이다. 따라서 다음과 같은 명령을 실행하면 전원은 공급된 상태에서 가젯 기능만 뺄 수 있다.

 

sudo bash -c 'echo "" > /sys/kernel/config/usb_gadget/my_usb/UDC'

 

 

  다음의 명령어를 입력하면 다시 가젯 기능이 활성화된다.

 

sudo bash -c 'ls /sys/class/udc > /sys/kernel/config/usb_gadget/my_usb/UDC'

 

※ 동적으로 특정 가젯(Gadget) 올리거나 내리기 ※

 

  1) 동적으로 특정한 가젯을 새롭게 올리기

 

  그렇다면 라즈베리파이호스트(host) PC에 연결한 상태에서 원할 때 특정한 가젯 기능만을 열거나 닫고자 한다면 어떻게 할 수 있을까? 사실 우리가 Mass Storage USB를 이용할 때는, 그냥 물리적으로 USB를 PC에서 빼면 연결이 해제된다. 하지만 라즈베리파이에서 여러 개의 가젯(gadget) 기능을 제공하고 있을 때는 어떻게 하나의 기능만 제거할 수 있을지 알아보자. 현재의 라즈베리파이가 다음과 같이 2개의 MSC를 제공한다고 해보자.

 

 

  이제 간단히 하나의 블록 장치를 만들어 보자.

 

# 500Mb 이미지 생성
dd if=/dev/zero of=/home/pi/images/usbdisk4.img bs=1048576 count=512
mkdosfs /home/pi/images/usbdisk4.img

 

  이후에 라즈베리파이에서 다음의 코드를 실행해보자.

 

# 특정 이미지에 마운트(mount) 진행하기
sudo mkdir -p /home/pi/images/usbdisk4.d
sudo mount -o loop,ro, -t vfat /home/pi/images/usbdisk4.img /home/pi/images/usbdisk4.d # 라즈베리 파이 입장에서는 읽기 전용(read-only)

# USB 기능(function) 작성하기 (kernel.org reference 참고하기)
sudo mkdir -p /sys/kernel/config/usb_gadget/my_usb/functions/mass_storage.usb2
sudo bash -c 'echo 1 > /sys/kernel/config/usb_gadget/my_usb/functions/mass_storage.usb2/stall'
sudo bash -c 'echo 0 > /sys/kernel/config/usb_gadget/my_usb/functions/mass_storage.usb2/lun.0/cdrom'
sudo bash -c 'echo 0 > /sys/kernel/config/usb_gadget/my_usb/functions/mass_storage.usb2/lun.0/ro' # 호스트 입장에서는 쓰기 가능
sudo bash -c 'echo 0 > /sys/kernel/config/usb_gadget/my_usb/functions/mass_storage.usb2/lun.0/nofua'
sudo bash -c 'echo /home/pi/images/usbdisk4.img > /sys/kernel/config/usb_gadget/my_usb/functions/mass_storage.usb2/lun.0/file' # 이미지 파일 명시하기

# 만들어진 기능(function) 사용할 수 있도록 설정
sudo ln -s /sys/kernel/config/usb_gadget/my_usb/functions/mass_storage.usb2 /sys/kernel/config/usb_gadget/my_usb/configs/c.1/

 

  단순히 위 코드를 실행하는 것으로는 동적으로 반영되지 않는 것을 알 수 있다. 다음의 두 코드를 추가적으로 실행하여 UDC를 재동작 시켰을 때 비로소 정상적으로 동작한다. 참고로 단순히 호스트(Host) PC와 라즈베리파이 사이의 데이터 케이블을 연결을 해제하고 다시 연결하는 작업만으로는 정상적으로 새 기능이 추가되지 않는다.

 

  반면에 /sys/kernel/config/usb_gadget/my_usb/UDC의 값을 공백으로 바꾸었다가 다시 정상적인 UDC 값을 기입하면 새 기능이 추가되어 재연결 된 것을 확인할 수 있다.

 

sudo bash -c 'echo "" > /sys/kernel/config/usb_gadget/my_usb/UDC'
sudo bash -c 'ls /sys/class/udc > /sys/kernel/config/usb_gadget/my_usb/UDC'

 

  아무튼 케이블을 뺐다가 다시 끼지 않더라도 기능을 변경할 수 있다는 점은 확실하다.

 

 

  2) 동적으로 특정한 가젯을 내리기

 

  특정한 가젯을 내릴 때에도 비슷하다. configuration에서 바운딩 된 mass_storage.usb2 기능(function)을 다음과 같은 명령어로 동적으로 제거할 수 있다.

 

sudo rm /sys/kernel/config/usb_gadget/my_usb/configs/c.1/mass_storage.usb2

 

  물론 이 명령을 실행하면 configuration 정보가 바뀌기 때문에 UDC의 값이 자동으로 공백으로 바뀌어 호스트(Host) PC와의 연결이 해제될 수 있다. 따라서 가젯을 제거한 뒤에는 다음과 같이 UDC 값을 갱신할 필요가 있다.

 

sudo bash -c 'ls /sys/class/udc > /sys/kernel/config/usb_gadget/my_usb/UDC'

 

  결과적으로 호스트 PC는 다시 USB를 인식하고, 하나의 Mass Storage 기능이 정상적으로 제거된 것을 알 수 있다.

 

 

※ (추가) Serial로 터미널 접속하기 ※

 

  Serial을 열어서 터미널로 접속할 수도 있다. (사실 굳이 사용할 이유가 없는 경우가 많다.) 이를 위해 다음과 같은 내용을 환경 설정 파일에 추가한다. 그리고 재부팅을 하면 라즈베리파이에 /dev/ttyGS0 디바이스가 생성된다. 이 디바이스를 이용하여 통신이 가능하다.

 

mkdir -p functions/acm.usb0
ln -s functions/acm.usb0 configs/c.1/

 

  이어서 라즈베리파이에서 USB 시리얼 콘솔 접속을 활성화 해주자.

 

sudo systemctl enable getty@ttyGS0.service

 

  호스트(host) PC 입장에서는 다음과 같이 PuTTY를 이용하여 접속할 수 있다. 윈도우(Windows)의 경우 장치 관리자에서 COM 포트 번호를 확인한 뒤에 115200 레이트로 접속하면 된다.

 

 

  그러면 다음과 같이 성공적으로 접속된다.

 

 

※ (추가) Serial로 통신하기 ※

 

  Serial을 터미널 접속용이 아니라 데이터 주고 받기용으로 사용할 수도 있다. 설정 파일에 다음의 내용을 추가하고 재부팅 해보자.

 

mkdir -p functions/acm.usb1
ln -s functions/acm.usb1 configs/c.1/

 

  그러면 라즈베리파이에서는 /dev/ttyGS1이 추가적으로 생성된다. 이제 이걸로 데이터를 주고 받아보도록 하자.

 

  1) 호스트가 데이터를 보낼 때

 

  호스트의 파이썬 3.7 프로그램은 다음과 같다.

 

import serial

values = [1, 2, 3, 4, 5, 6, 7, 8, 9]

s = serial.Serial("COM7", 115200)
s.write(b'Hello\n')
s.close()

 

  라즈베리파이 쪽에서는 다음과 같이 /dev/ttyGS1에 적힌 내용을 확인하면 된다.

 

 

  2) 호스트가 데이터를 받을 때

 

  호스트의 파이썬 3.7 프로그램은 다음과 같다.

 

import serial

s = serial.Serial("COM7", 115200)

while True:
    data = s.readline()
    print(data)

s.close()

 

  라즈베리파이 쪽에서는 다음과 같이 /dev/ttyGS1를 통해 데이터를 전송하면 된다.

 

 

  그러면 다음과 같이 호스트 쪽에서는 데이터를 받을 수 있게 된다.

 

 

  이어서 라즈베리파이가 보내는 데이터를 있는 대로 계속 받아서 파일에 바이트 스트림(byte stream)으로 저장하려면 다음과 같이 하면 된다.

 

import serial

s = serial.Serial("COM7", 115200)
total = 0

while True:
    len = s.inWaiting()
    if len == 0:
        continue
    f = open("log.dat", "ab")
    data = s.read(len)
    f.write(data)
    f.close()
    total += len
    print(f"total length:", total)

s.close()

 

  그러면 파이썬 프로그램이 계속 실행되고, 라즈베리파이로부터 받은 데이터를 그대로 log.dat에 계속 쓴다.

 

 

  (참고) Serial을 데이터 송수신 목적으로 사용하는 경우에는 raw 모드로 데이터를 전송할 필요가 있다. 그렇지 않으면 \b10을 보냈을 때 \b13\b10의 형태로 보내지는 문제가 발생할 수 있다.

 

※ Mass Storage Class를 Boot Device로 사용할 때 ※

 

  이 케이스는 흔한 케이스는 아니지만, 필자는 연구 목적으로 Raspberry Pi의 Mass Storage Class를 Boot Device로 사용해 본 경험이 있다. 만약 Raspberry Pi Zero W를 부팅 디스크로 사용한다고 하면, 호스트(host) PC의 UEFI 단계에서 Raspberry Pi를 부팅 디스크로 잡아 OS를 부팅하게 된다. 다만 이때 Multi Composite Gadget을 이용하면 OS 부팅 단계에서 오류가 발생할 수 있다.

 

  구체적으로 필자는 g_mass_storage를 올릴 때만 동작하고, Multi Composite Gadget을 사용했을 때 Booting이 안 되는 문제가 발생했었다. g_mass_storage 모듈을 이용했을 때 USB 장치의 기본 Vendor ID는 0x0525, Product ID는 0xA4A5이다. 반면에 Composite Gadget은 일반적으로 Vendor ID 0x1D6B, Product ID 0x0104를 사용한다.

 

  100% 확신하는 것은 아니지만, OS 부팅 과정에서 Well-known이 아닌 Identifier를 사용하면 OS를 올리는 과정에서 USB Reconnection이 이루어지며, 이 과정에서 Mass Storage와의 연결이 일시적으로 해제되어 부팅이 안 되었던 것으로 보인다. 그래서 기존의 Raspberry Pi의 config 파일에서 Vendor ID와 Product ID를 g_mass_storage와 동일하게 맞추어 실행했더니 문제가 해결된 적이 있었다. 이렇게 해도 Multi Composite Gadget의 기능을 가지며 정상 동작하는 것을 확인했다.

728x90
반응형

728x90
반응형

  라즈베리파이(Raspberry Pi) Zero W가성비가 매우 뛰어난 소형 보드로 유명합니다. 저는 현재 참여하고 있는 프로젝트에서 라즈베리파이를 사용해야 해서 이 모델을 골라 사용하고 있습니다. Raspberry Pi Zero W는 다음과 같이 생겼습니다. SD 카드에 라즈베리파이 운영체제(OS)를 설치한 뒤에 라즈베리파이에 꽂아서 부팅하면 됩니다.

 

 

※ 스펙(Specification) 소개 ※

  ▶ 802.11 b/g/n wireless LAN: 와이파이(Wi-Fi)를 사용할 수 있습니다.
  ▶ 블루투스(Bluetooth)
  ▶ 1GHz, single-core CPU: 다른 소형 보드와 비교했을 때, 가격 대비 괜찮은 성능의 CPU가 탑재되어 있습니다.
  ▶ 512MB RAM: 간단한 웹 서버를 올릴 수도 있는 크기의 메모리(RAM)입니다. 
  ▶ USB OTG: USB 디바이스 기능을 지원합니다. (라즈베리파이를 이용한 마우스, 키보드 개발 등이 가능)
  ▶ Micro USB Power: 충전 목적의 포트입니다. 다만 전원용 USB의 전압(voltage)은 반드시 5-5.25V 사이가 되어야 합니다.  일반적인 USB 충전기의 경우 5V 전압이므로, 그냥 편의점에서 파는 마이크로 USB를 꽂아 쓰면 됩니다.
  ▶ CSI Camera Connector: CSI 포트가 존재하므로 라즈베리파이 카메라 모듈을 사용할 수 있습니다. IoT 사물 인식 기기, CCTV 기기 등을 개발할 때 효과적으로 사용됩니다.

 

※ 라즈베리파이의 일반적인 사용 형태 ※

 

  참고로 Raspberry Pi Zero W의 경우 헤드리스(headless) 서버 설정이 가능하기 때문에, 사실 Mini HDMI 케이블을 구매하지 않아도 간단히 서버로 사용이 가능합니다. 아마 단순히 라즈베리파이를 간단한 서버용으로 사용하시는 분들은 다음과 같은 형태로 사용하실 겁니다. SD카드에 우분투(Ubuntu)를 구워 넣고, 마이크로 USB로 전원을 공급하고 있는 모습입니다. 라즈베리파이 OS의 경우에 SSH 접속을 지원하기 때문에, SSH를 열어 놓고 외부에서 접속해 사용하는 방식으로 편하게 사용할 수 있습니다.

 

 

※ Raspberry Pi Zero W 구매 방법 ※

 

  Raspberry Pi Zero W는 Amazon이나 Canakit과 같은 사이트를 통해 구매할 수 있습니다. 또한 일반적인 경우 보드(Board)만 구매하는 경우에는 1개씩만 구매할 수 있도록 하는 경우가 많습니다. 사실 10달러($)에 이 정도의 보드를 살 수 있다는 것 자체가 매우 좋은 조건이긴 합니다.

 

 

  참고로 스타터 키트(Starter Kit)의 경우 다음과 같은 구성을 가지고 있습니다. 확인해 보시면 Mini HDMI 어댑터(adapter)와 USB OTG 케이블(cable)을 가지고 있습니다. 이걸로 모니터와 마우스를 연결할 수 있기 때문에, 헤드리스(headless) 서버가 아니라 하나의 고유한 컴퓨터처럼 라즈베리 파이를 이용하고 싶으신 분들은 스타터 키트를 구매하시는 것이 편할 수 있습니다.

 

 

※ 라즈비안(Raspbian) OS 설치하기 ※

 

  라즈베리파이는 기본적으로 SD 카드를 꽂을 공간을 가지고 있습니다. 따라서 SD 카드에 운영체제(OS)를 설치하면 됩니다. 가장 대표적인 운영체제로 라즈비안(Raspbian) OS를 설치할 수 있습니다.

 

▶ 라즈비안(Raspbian) 운영체제 설치 경로https://www.raspberrypi.org/downloads/raspbian/

 

Download Raspberry Pi OS for Raspberry Pi

Raspberry Pi OS (previously called Raspbian) is the Foundation's official supported operating system. Install it with NOOBS or download the image below.

www.raspberrypi.org

 

  다음과 같이 최소한의 정보만 담은 Lite 버전으로 OS를 설치하여 사용할 수 있습니다. [Download ZIP] 버튼을 눌러  다운로드를 진행합니다. 대개 3분~5분 정도면 다운로드가 완료되며, 압축된 파일은 500MB가 안 되지만 실제 이미지(image) 파일은 2GB 정도입니다.

 

 

※ SD 카드에 라즈비안(Raspbian) OS 굽기 ※

 

  라즈비안(Raspbian)을 OS에 굽기 위해서는 플래시(Flash) 프로그램을 사용할 수 있습니다.

 

  ▶ Etcher 설치 경로: www.balena.io/etcher/

 

  자신의 운영체제에 맞는 설치 프로그램을 받아서 설치를 진행하시면 됩니다. 저는 다음과 같이 윈도우(Windows) 운영체제에 맞는 프로그램을 설치했습니다.

 

 

  이어서 설치를 진행하시면 됩니다.

 

 

  이후에 Etcher 프로그램을 이용하여 라즈베리파이 이미지 파일을 선택하고, 플래시(flash)를 진행하면 됩니다. 이를 위해 SD 카드 어댑터(adapter)를 이용하여 PC에 SD 카드를 연결해야 합니다. 일반적인 컴퓨터나 노트북은 SD 카드 어댑터를 이용해 SD 카드를 연결하게 되어 있습니다.

 

 

  Etcher 프로그램을 실행하여 [Flash from file]을 눌러 라즈베리파이 이미지를 선택합니다.

 

 

  그리고 [Selected target] 버튼을 눌러 SD 카드를 선택하고 플래시(flash) 버튼을 누릅니다.

 

 

  플래시(flash) 작업이 완료되면 SD 카드에 라즈베리파이 OS가 설치가 완료됩니다.

 

 

  이제 완료되면 부트 폴더를 확인할 수 있습니다. 윈도우(Windows) 운영체제에서는 부트 폴더가 자동으로 마운트 되며, 용량이 256MB라고 나오네요. 이제 이러한 부트 폴더에 환경 설정 파일을 넣어서 라즈베리파이가 특정 환경에 맞게 부팅되도록 만들 수 있습니다.

 

 

※ 헤드리스(headless) 서버로 사용하기 ※

 

  Mini HDMI 케이블과 USB OTG 케이블을 가지고 있으신 분들은 직접 모니터와 키보드를 연결하여 사용할 수 있습니다. 다만 그러한 케이블이 없다면 SSH로 라즈베리파이에 접속하여 사용할 수 있습니다. 라즈베리파이에 SSH로 접속하기 위해서는 가장 먼저 boot 폴더에 ssh라는 이름의 파일을 만들어 주어야 합니다. 이로써 라즈베리파이가 부팅될 때 SSH 포트가 열리게 됩니다.

 

 

  또한 라즈베리파이 Zero W가 와이파이(Wi-Fi)를 사용할 수 있도록 하기 위해 와이파이 정보를 기재할 필요가 있습니다. 이를 위해 wpa_supplicant.conf 파일을 생성한 뒤에 메모장으로 열어 내용을 기입합시다. 윈도우(Windows) 운영체제의 경우 단순히 워드패드메모장으로 열어 쓰시면 됩니다.

 

 

  바로 다음의 형식에 맞게 와이파이(Wi-Fi) 정보를 기입하시면 됩니다. 참고로 netsh 명령어를 이용하면 현재 내 컴퓨터나 노트북에 저장되어 있는 와이파이 비밀번호 정보를 알아낼 수 있습니다.

 

country=US
ctrl_interface=DIR=/var/run/wpa_supplicant GROUP=netdev
update_config=1

network={
    ssid="SSID"
    psk="비밀번호"
    key_mgmt=WPA-PSK
}

 

  결과적으로 컴퓨터에서 SD 카드를 제거한 뒤에 라즈베리파이에 꽂아서 부팅하시면 됩니다. 파워(power) 케이블을 연결하면 자동으로 부팅이 진행됩니다. 이후에 SSH로 접속하여 사용하면 됩니다. 접속하고자 하는 컴퓨터와 라즈베리파이를 동일 아이피 대역에 둔 뒤에 Advanced IP Scanner와 같은 프로그램을 이용해 라즈베리파이의 IP 주소를 찾아서 접속하면 됩니다. Putty나 XShell 같은 프로그램을 이용해 접속하면 됩니다. 예를 들어 라즈베리파이의 IP 주소가 192.168.43.78로 나온다면 다음과 같이 입력하여 접속할 수 있습니다.

 

 

  기본 설정 그대로 이용할 때 SSH 접속 시 아이디pi이며, 비밀번호raspberry입니다.

 

 

  참고로 WiFi 관련 설정 파일은 SSH 접속 이후에 /etc/wpa_supplicant/wpa_supplicant.conf 파일에서 확인할 수 있습니다. 또한 실제 IP 주소는 ifconfig wlan0 커맨드를 입력해 확인하실 수 있습니다.

 

※ 로컬 SSH로 Raspberry Pi 접속하기 ※

 

  기본적으로 마찬가지로 boot 폴더에 ssh라는 이름의 파일을 생성해 준 뒤에, Raspberry Pi에 SD 카드를 꽂습니다. 그리고 Raspberry Pi 데이터 케이블을 PC에 연결합니다. putty와 같은 유틸리티를 활용하여 pi@raspberrypi.local에 접속합니다.

 

 

  접속이 된다면 성공입니다.

 

※ Serial Gadget을 이용해 Raspberry Pi 접속하기 ※

 

  소정의 이유로 현재 와이파이를 사용할 수 없는 상황일 수 있습니다. 혹은 무선 네트워크의 속도가 너무 느려서 접속이 어려울 수도 있습니다. 이럴 때는 USB OTG 기능을 이용하여 Raspberry Pi에 접속할 수 있습니다. 마찬가지로 SD 카드를 호스트 PC에 연결합니다. 그리고 /boot/config.txt 파일의 마지막 줄에 다음을 추가합니다.

 

dtoverlay=dwc2

 

  이후에 cmdline.txt를 열어서 rootwait 다음 부분에 한 칸(space bar)을 띄고 다음과 같이 작성합니다. g_serial 모듈을 로드(load) 함으로써 Serial 가젯(gadget)을 활성화할 수 있습니다.

 

modules-load=dwc2,g_serial

 

  cmdline.txt의 예시는 다음과 같습니다. 대략 다음과 같이 작성되었는지 확인합니다. 참고로 아래 커맨드는 사용하고 있는 장치마다 조금씩 다를 수 있으며, 필자의 경우 단순히 rootwait 다음에 모듈을 로드하는 내용만 추가했습니다.

 

dwc_otg.lpm_enable=0 console=serial0,115200 console=tty1 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait modules-load=dwc2,g_serial quiet init=/usr/lib/raspi-config/init_resize.sh

 

  이제 라즈베리파이를 실행하면 호스트(host) PC에 연결했을 때 정상적으로 COM 포트로 인식하는 것을 알 수 있습니다. 이후에 systemctl 명령어를 이용해 Serial 포트 기능을 활성화해 준 뒤에, 재부팅하고 호스트(host) PC에서 Raspberry Pi에 USB 시리얼로 접속하면 됩니다.

 

※ Raspberry Pi 고정 IP 이용하기 ※

 

  초기 세팅이 끝나고 나면 고정 IP를 이용하는 것이 편합니다. 이를 위해 Raspbery Pi에 접속하여 가장 먼저 DHCPCD가 동작하고 있는지 서비스(service) 상태를 확인합니다.

 

sudo service dhcpcd status

 

  정상적으로 동작하고 있다면, /etc/dhcpcd.conf 파일을 수정합니다. 가장 아랫줄에 다음과 같은 내용을 추가하면 됩니다. 필자의 경우 무선 네트워크를 사용하므로 wlan0으로 설정했습니다.

 

interface wlan0

static ip_address=192.168.0.177/24
static routers=192.168.0.1
static domain_name_servers=8.8.8.8

 

  이때 네트워크 환경에 따라서 domain_name_server를 자신의 회사/학교에서 제공하는 IP로 설정해야 할 필요가 있습니다. 필자의 경우 8.8.8.8만 설정했을 때 네트워크가 정상적으로 동작하지 않았습니다. 아무튼 설정 이후에는 재부팅하여 ifconfig를 입력하여 정상적으로 IP가 변경되었는지 확인합니다.

728x90
반응형

728x90
반응형

본 글은 cs231n 강의 노트를 정리한 것입니다.

 

※ CNN을 왜 사용할까? ※


  CNN을 이용한 분류기(classification)도 일반 뉴럴 네트워크처럼 이미지를 입력으로 받아 분류 결과를 출력한다. 다만 CNN은 이미지 데이터가 가지는 특성을 활용하여 더 적은 파라미터를 사용하며, 높은 성능을 보인다.

 

  일반적인 신경망(MLP)을 이용해 이미지를 처리할 때는 매우 많은 가중치가 필요하다. MNIST 데이터셋을 넘어서 CIFAR-10 데이터셋만 되어도 MLP를 이용하면 해결하기 어렵다. 32 x 32 크기의 색상 이미지라면 32 x 32 x 3 = 3,072개의 가중치(weight)가 필요하다. 은닉층의 크기가 1,000이라면 순식간에 3,072,000개의 파라미터가 필요하게 된다. 반면에 CNN은 커널(kernel)을 공유하기 때문에 훨씬 적은 가중치만 있어도 된다.

 

  ▶ 일반 신경망의 특징: 많은 가중치 필요, fully-connection 사용
   CNN의 특징: 적은 가중치 필요 (파라미터 공유), 이미지 처리에 적합

 

  예를 들어 컨볼루션 연산에서 커널의 개수 K = 12, 수용 영역 크기 F = 4, 이전 레이어의 채널 = 3이라고 해보자. 그러면 실제로 커널에 필요한 파라미터의 개수는 12 X 4 X 4 X 3 = 576이 된다. 이처럼 커널을 이용해 파라미터를 공유하면 필요한 가중치의 개수가 획기적으로 줄어들게 된다. 이런 아이디어가 사용될 수 있는 이유는, 하나의 패치 특징(patch feature)은 이미지의 전반적인 위치에서 나타날 수 있기 때문이다. 예를 들어 일반적인 이미지 데이터셋을 확인해 보면 대각선 엣지(edge) 형태의 특징은 이미지 전반에서 나올 수 있다.

 

※ CNN의 동작 ※

 

  CNN의 각 레이어는 너비(width), 높이(height), 깊이(depth)로 구성된다. 일반적인 MLP와 비교했을 때 깊이(depth)가 추가되었다.

 


CNN는 다음과 같은 레이어로 구성된다.

  ▶ 입력 레이어: 입력 이미지가 들어오는 레이어
  ▶ 컨볼루션 레이어: 입력 이미지의 일부 수용 영역(receptive field)에 대하여 컨볼루션 연산을 수행
  ▶ ReLU 레이어: 일반적으로 사용되는 액티베이션 함수(activation function)

  ▶ Pool 레이어: 너비(width)와 높이(height)에 대해 다운샘플링(downsampling) 수행

  ▶ FC 레이어: 네트워크의 마지막 부분에서 실질적으로 클래스 분류 수행

 

  컨볼루션 연산은 다음과 같이 수행된다.

 

 

  조금 더 자세한 예시로 확인하면 다음과 같다. indoml 사이트에서 정리한 글이 매우 보기 편하므로 참고해보자.

 

 

  이때 커널은 스트라이드(stride)의 크기만큼 슬라이딩하며 컨볼루션 연산을 수행한다.

 

 

  또한 처음에 가장 헷갈리는 부분은 컨볼루션 각 레이어의 차원이다. 커널(kernel)을 이용해 컨볼루션 연산을 수행하는데, 이때 이전 레이어의 채널(channel) 크기와 커널의 채널 크기가 동일하다. 예를 들어 입력 크기가 32 x 32 x 3이면, 커널에서도 5 x 5 x 3과 같이 채널의 크기로 3을 사용한다.

 

 

  또한 컨볼루션 연산을 수행한 뒤에는 커널의 개수가 레이어의 채널 크기가 된다. 예를 들어 32 x 32 x 3 입력에 대하여 5 x 5 x 3짜리 커널을 6개 사용했다면, 출력으로 나온 레이어는 28 x 28 x 6 크기를 가진다.

 

 

  일반적으로 CNN에서는 레이어가 깊어지면 너비와 높이는 감소하고 깊이(채널)는 증가한다. 이때 각 채널은 서로 다른 특징(feature)을 적절히 추출하도록 학습되므로, 다양한 특징들을 조합하여 적절히 분류를 수행하게 된다. 예를 들어 가장 기본적인 형태의 LeNet을 확인해보자. 최신 네트워크들은 이것보다 훨씬 깊은 레이어를 가지고 있지만, LeNet은 CNN의 전형적인 형태를 잘 보여주고 있다.

 

  레이어가 깊어질수록 너비와 높이는 감소하고 채널은 증가하는 것을 확인할 수 있다.

 

 

  또한 다운샘플링(downsampling) 목적으로 풀링 레이어(pooling layer)를 사용한다. 일반적으로 분류 네트워크에서는 max pooling과 average pooling이 사용된다. 예를 들어 2 x 2 풀링을 수행하는 경우 액티베이션(activation)의 25%만 남게 되므로 참고로 풀링을 수행해도 차원(dimension)은 변하지 않는다.

 

 

※ CNN의 일반적인 구조 ※

 

  가장 일반적인 CNN의 패턴은 다음과 같다. 결과적으로 마지막 FC 레이어를 거쳐 클래스 분류 결과가 나오게 된다. LeNet 이후에도 굉장히 다양한 CNN이 등장했으나 대부분 아래와 같은 패턴을 따른다는 점이 특징이다.

 

  INPUT → [[CONV → RELU] * N POOL] * M [FC → RELU] * K → FC

 

  예시 1) INPUT → FC

  예시 2) INPUT → CONV → RELU → FC

  예시 3) INPUT → [CONV → RELU → POOL] * 2 → FC → RELU → FC

 

※ 다양한 CNN 아키텍처 ※

 

1) AlexNet

 

  이미지 분류(Image Classification)에서 CNN이 큰 주목을 받게 해 준 네트워크다. ILSVRC 2012에 출전하여 압도적인 성능 차이를 보였다. CONV 레이어 이후에 바로 풀링 레이어를 넣지 않고, CONV를 중첩해서 사용했다는 점이 특징이다.

 

 

2) VGGNet

 

  ILSVRC 2014에서 2등을 한 네트워크다. VGG에서는 레이어를 깊게 쌓았을 때 더 좋은 성능이 나올 수 있다는 것을 보여주었다. 16개의 레이어로 구성되는 VGG-16이 대표적이다.

 

 

3) ResNet

 

  ILSVRC 2015에서 1등을 한 네트워크인 ResNet는 skip connection과 배치 정규화(batch normalization)를 사용했다는 점이 특징이다. 지금은 매우 많은 네트워크에서 skip connection과 배치 정규화를 사용하고 있으며, 2020년까지도 ResNet은 많은 논문에서 베이스라인(baseline)으로 성능 비교 대상이다.

 

728x90
반응형

728x90
반응형

  Teensy나 라즈베리파이(Raspberry Pi)와 같은 USB 장치에서 별도의 외부 파워(external power)를 이용하는 경우, USB 케이블에서는 전원이 공급되지 않도록 할 필요가 있습니다. 더욱 구체적인 예로 USB OTG 기능(USB 장치 개발 목적)을 이용하여 라즈베리파이를 Mass Storage Class(MSC)로 동작하도록 만들 때 외부 파워를 이용할 수 있는데, 이럴 때는 USB OTG 케이블에서는 전원이 공급되지 않도록 해야 합니다. 그렇지 않으면 디바이스가 고장 날 수 있습니다.

  실제로 USB 개발용 보드를 구매할 때 다음과 같은 경고 문구가 적혀 있는 것을 볼 수 있습니다.


  "WARNING: Reverse polarity power or greater than 6 volts can damage Teensy."

 

  이처럼 소형 보드에서 USB 케이블을 두 개 연결하는 상황(혹은 외부 파워와 USB OTG를 이용하는 상황)에서 우리의 USB 보드를 고장 내지 않기 위한 가장 간단한 방법은 데이터 전송을 위한 USB 케이블에 대하여 빨간색 선(+5V power line)을 잘라서 사용하는 것입니다. 가장 일반적인 형태의 USB 마이크로 케이블은 Standard A를 따르는데요. 바로 다음과 같은 형태로 4개의 선을 이용합니다.

 

 

  각각의 선은 표준에 따라서 그 용도가 정해집니다. 여기에서 빨간색 선+5V 전원 공급용 선입니다.

 


  실제로 선을 자를 때는 가능하면 다음과 같이 선의 방향에 따라서 세로로 칼을 그어 피복을 벗기는 것이 깔끔합니다.

 

  참고로 제 USB 케이블과 같이 일부 USB 케이블에서는 전력 손실 방지 목적으로 알루미늄 실드(foil shield)로 덮여 있는 경우가 있습니다. 혹시 자신이 구매한 USB 케이블에 은박지처럼 생긴 이 알루미늄 실드가 있으면, 마찬가지로 조심히 잘라주세요. 잘못 자르면 안쪽에 있는 케이블까지 잘리게 되면 번거로울 수 있습니다.

 

 

  이렇게 작은 선을 자를 때는 니퍼(nipper)를 이용하면 깔끔하게 자를 수 있습니다. 저는 다음과 같이 빨간색 라인을 제거했습니다.

 

 

  이후에 절연 테이프(이른바 검은 테이프)를 이용해 감싸면 됩니다. 절연 테이프란 이처럼 피복이 벗겨진 부분을 다시 감싸고자 할 때 사용하는 테이프를 말합니다. 결과적으로 USB 데이터 전송 목적(data-only) 케이블이 만들어졌습니다.

 

 

    저는 라즈베리파이(Raspberry Pi) Zero W 모델을 사용하는데, 다음과 같이 USB OTG 포트(port)에 data-only 케이블을 연결하여 사용하고 있습니다. 이렇게 하게 되면 전원이 중복되어 공급되지 않고 안전하게 USB 보드 개발을 진행할 수 있습니다.

 

728x90
반응형

728x90
반응형

대회 링크: codeforces.com/contest/1421

A번 문제: codeforces.com/contest/1421/problem/A

 

 문제 제목이 정답이다. 단순히 a xor b를 출력하면 정답이 된다.

 

#include <bits/stdc++.h>
 
using namespace std;
 
int main() {
    int testCase;
    cin >> testCase;
    for (int tc = 0; tc < testCase; tc++) {
        unsigned int a, b;
        cin >> a >> b;
        cout << (a ^ b) << '\n';
    }
}

 

B번 문제: codeforces.com/contest/1421/problem/B

 

  이 문제는 S와 F의 위치에서 인접한 2칸씩만 확인하면 된다. 아래 그림에서 색칠한 부분이 이에 해당한다. 이 아이디어를 떠올릴 수 있으면 문제를 해결할 수 있다.

 

 

C번 문제: codeforces.com/contest/1421/problem/C

 

  이 문제에서는 두 가지의 연산을 허용한다.

 

  ① L 연산

 

  쪽에서 두 번째 인덱스부터 i까지의 문자열뒤집어 왼쪽에 붙이는 연산

 

 

  ② R 연산

 

  오른쪽에서 두 번째 인덱스부터 i까지의 문자열뒤집어 오른쪽에 붙이는 연산

 

 

  이것저것 생각하다 보면 다음의 세 연산으로 정답 판정을 받을 수 있다는 것을 알 수 있다.

 

data = input()
n = len(data)

print(3)
print("L", n - 1)
print("R", n - 1)
print("R", 2 * n - 1)

 

  예시를 들어 그리자면 다음과 같다.

 

  ※ 첫 번째 연산 ※

 

 

  ※ 두 번째 연산 ※

 

 

  ※ 세 번째 연산 ※

 

 

D번 문제: codeforces.com/contest/1421/problem/D

 

  이 문제는 대략 다음과 같은 코드로 정답 판정을 받을 수 있다. 먼저 6가지 방향에 대해서 최소 거리를 계산한 뒤에, 실제로 6가지 이동 연산을 이용하여 목표 지점까지의 최소 거리를 출력하면 정답이다.

 

for _ in range(int(input())):
    x, y = map(int, input().split())
    c1, c2, c3, c4, c5, c6 = map(int, input().split())
 
    # 6가지 방향에 대한 이동 거리 최적화
    c1 = min(c1, c2 + c6)
    c2 = min(c2, c1 + c3)
    c3 = min(c3, c2 + c4)
    c4 = min(c4, c3 + c5)
    c5 = min(c5, c4 + c6)
    c6 = min(c6, c1 + c5)
    
    # 최단 경로 계산하기
728x90
반응형

728x90
반응형

  일반적으로 경쟁적 프로그래밍(Competitive Programming) 대회, 이른바 알고리즘 대회에서는 종종 애드혹(ad-hoc) 문제가 출제된다. 일반적으로 애드혹 문제라고 하는 것은 해당 문제를 풀기 위해 잘 알려진 정교한(sophisticated) 알고리즘을 적용하지 않고 해결할 수 있는 유형의 문제를 일컫는다. 이러한 유형의 문제는 손으로 직접 해당 문제를 해결하기 위한 (해당 문제만을 위한) 아이디어를 찾아서 문제를 해결할 수 있다. 애드혹 문제들을 굳이 분류하자면 단순히 지시(instruction)를 따르면 되는 구현 유형이나 그리디 유형 알고리즘 혹은 수학 유형으로 분류할 수 있는 경우가 많다.

 

  다시 말해 정형화된 방법론이 아니라, 그 문제를 풀기 위한 창의적인 아이디어를 떠올려야 하는 경우에 애드혹 문제라고 한다.

728x90
반응형

728x90
반응형

  코드포스(Codeforces) 웹 사이트 내에서 점수 변화 및 퍼포먼스(performance) 정보도 함께 확인하고자 한다면, 캐럿(Carrot) 확장 프로그램을 사용할 수 있습니다. 캐럿을 사용하면 다음과 같이, 내가 오늘 치른 대회에서의 퍼포먼스(performance)점수 변화량(delta)을 확인할 수 있습니다.

 

 

  ▶ 캐럿(Carrot) 확장 프로그램 다운로드

 

 

Carrot

Rating predictor for Codeforces

chrome.google.com

 

  사이트에 접속한 뒤에 [Chrome에 추가] 버튼을 눌러서 설치하시면 됩니다.

 

 

  설치 이후에 크롬(Chrome) 브라우저에서 사용 설정을 하시면 곧바로 코드포스 사이트에 접속해서 이용하실 수 있습니다.

728x90
반응형

728x90
반응형

  흔히 깃허브(GitHub)에 올라와 있는 다양한 프로젝트에서는 별도의 main.py 소스코드를 정의한 뒤에 인자(argument)를 넘겨 주어서 프로그램을 실행하는 방식을 많이 사용합니다. 이것이 가장 전통적인 방법이기도 하며, 실제로 코드를 이용하는 사람 입장에서는 소스코드를 다운로드 한 뒤에 인자 값과 함께 코드를 실행해야 합니다. 예를 들어 StarGAN공식 GitHub에 적혀 있는 내용을 가져오면 다음과 같습니다.

 

  main.py 코드를 실행하기 위해 매우 많은 인자(argument) 값을 넘겨주어야 오류 없이 실행됩니다.

 

 

  다만 Google Colab과 같이 주피터(Jupyter) 환경에서 직접 main.py의 코드를 수정하면서 실행해보고 싶은 사람은 어떻게 할 수 있을까요? 이럴 때 효과적으로 사용할 수 있는 라이브러리가 SimpleNamespace입니다. SimpleNamespace는 Python 3.3 버전에서 새롭게 표준 라이브러리로 추가되었습니다.

 

  대략 다음과 같이 사용하면 됩니다.

 

# args와 동일한 형태의 데이터 인자를 넘기기 위한 테크닉
from types import SimpleNamespace

args = SimpleNamespace()

args.dataset = "CelebA"
args.selected_attrs = ["Black_Hair", "Blond_Hair", "Brown_Hair", "Male", "Young"]
args.model_save_dir = "stargan_celeba_256/models"
args.num_workers = 1
args.mode = "test"

print("데이터셋:", args.dataset)
print("전체 인자 값:", args)

 

  실행 결과는 다음과 같습니다.

 

728x90
반응형