안경잡이개발자

728x90
반응형

USB(Universal Serial Bus)란?

  USB는 어떠한 주변기기(Device)라도 우리의 PC에 쉽게 붙여서 동작하도록 지원해주는 규격이다. 예를 들어 마우스, 키보드, 이어폰 등은 일반적으로 공통적으로 USB 프로토콜을 쓰고 있다.  또한 스토리지(Storage), 스마트 폰 등도 USB 규격을 이용해서 연결하도록 제공이 되는 것을 생각해보면 된다.

  USB 2.0 버전 이상에서는 초당 수십 MB 단위의 데이터를 송수신할 수 있으며, 하나의 PC에 매우 많은 USB 디바이스를 연결해도 문제가 없다. 우리는 흔히 컴퓨터를 이용할 때, PC에 마우스, 키보드, 이어폰을 모두 연결하여 사용하곤 한다. 그럼에도 불구하고 전혀 문제는 점을 떠올리면 된다.

  심지어 각 기기에 전원을 공급할 필요도 없다. USB를 꽂기만 하면, PC에서 마우스나 키보드에 전원도 공급해주기 때문이다. 또한 PC에 USB 포트의 개수가 적다고 하더라도, USB 허브를 별도로 구매해서 포트의 개수를 늘리면 그만이다. 이러한 장점들이 있기 때문에 USB는 두루 쓰이고 있는 것이다.

USB의 전원 공급

  앞서 USB는 꽂기만 해도 PC에서 USB 장치에 전원을 공급한다고 했는데, 이것이 가능한 이유는 USB에는 4가지 선이 있기때문이다. 2개의 선(D+, D-)은 호스트와 디바이스 간의 데이터를 주고 받기 위해 사용하고, 2개의 선(Vbus, GND)은 전원을 공급하기 위해 사용한다.

디바이스 클래스(Device Class)

  USB 디바이스를 식별할 때는 벤더(Vendor) ID와 프로덕트(Product) ID를 가지고 식별한다. 하지만, 별도로 클래스로 묶어서 이를 처리하는 경우가 대부분이다. 예를 들어 마우스, 키보드 등의 디바이스를 개발할 때는 처음부터 디바이스 드라이버를 다 작성할 필요는 없고, 이미 존재하는 HID (Human Interface Device) 클래스의 기능을 그대로 이용하면 된다.

  예를 들어서 Arduino나 Teensy 등을 이용해서 자체제작 키보드를 만들거나 할 수 있는데, 이 경우 HID 클래스의 기능을 그대로 이용해서 개발하는 것이 편할 것이다.

기본 용어 정리

  ▶ 패킷(Packet): 호스트와 디바이스간의 통신에서 가장 작은 통신 데이터 단위
  ▶ 트랜잭션(Transaction): 패킷들의 모임을 의미한다. SETUP 패킷, DATA 패킷, HANDSHAKE 패킷 세가지의 종류가 있다. 이렇게 3가지를 모두 묶어서 한 번씩 데이터가 오가는데, 이러한 패킷 묶음을 트랜잭션이라고 한다.

  ▶ 전송(Transfer): 트랜잭션의 모임을 의미한다. 예를 들어 Bulk Transfer는 한 번의 트랜잭션으로 끝날 수 없고, 여러 번 트랜잭션을 반복해야 한다. 이 경우 보내야 할 데이터가 크기 때문에 여러 프레임(Frame)으로 나누어서 전송하거나 할 수 있다.

USB 디스크립터(Descriptor)

  USB 디바이스(Device)에 대한 기본적인 정보는 디스크립터(Descriptor)가 가지고 있다. USB를 PC에 꽂으면, 설정(Configuration) 정보를 주고 받는데, 이 때 Descriptor 정보를 보내게 되는 것이다. 이로써 호스트는 연결된 디바이스의 종류를 알게 되고, 디바이스의 특성에 맞게 데이터의 전송 양이나 폴링 주기를 설정하게 된다.

  디스크립터(Descriptor)에는 여러 종류가 있다. USB 정보를 호스트 PC에 디스크립터 정보를 보내는 데에, 왜 이렇게 많은 종류가 필요한지 궁금할 수 있다. 이것은 USB가 다양한 기능을 지원할 수 있다는 점에서 기인한다. 실제로 하나의 USB 장치는 동시에 마우스이면서, 동시에 키보드이면서, 동시에 조이스틱이 될 수도 있다.

 

  Device Descriptor, Configuration Descriptor, Interface Descriptor, Endpoint Descriptor 이렇게 네 가지 종류가 있다고 보면 된다. 아래 그림과 같이 트리(Tree) 구조로 되어 있다. Device Descriptor는 오직 하나만 존재하고, 이는 여러 개의 Interface Descriptor (마우스, Stroage, 키보드 등)를 가질 수 있다. 또한 하나의 Interface는 여러 개의 Endpoint (데이터를 주고 받는 통로)를 가지게 된다.

 

 

  ▶ 디바이스 디스크립터(Device Descriptor): 디바이스에 대한 가장 기본적인 정보를 담고 있음

    * USB 디바이스는 하나의 Device Descriptor를 가진다.
    * ProductID와 VenderID 정보가 담겨 있다. 
    * 얼마나 많은 Configuration을 가지고 있는지에 대한 정보가 담겨 있다.
    * 클래스(Class) 정보가 담겨 있다.

 

  ▶ 환경설정 디스크립터(Configuration Descriptor): 환경 설정 관련 정보를 가지고 있음

 

    * USB 디바이스는 여러 개의 Configuration Descriptor를 가질 수 있다.

    * 인터페이스(Interface)의 개수, 전력 소비 정보 등의 정보가 담겨 있다.

    * 하나의 USB 디바이스는 일반적으로 하나의 Configuration을 가지고 있다.

    * 여러 개의 전원 공급망을 가진 특별한 USB 디바이스의 경우 여러 개의 Configuration이 존재하는 경우가 있다.

 

  ▶ 인터페이스 디스크립터(Interface Descriptor): 한 가지 기능에 대한 정보를 가지고 있음

 

    * 하나의 USB 디바이스는 여러 개의 기능을 제공할 수 있다. (마우스 + 키보드 + 스캐너)

    * 각 기능마다 하나의 인터페이스(Interface)를 할당한다.

    * 인터페이스 번호, 엔드포인트의 개수 등의 정보가 담겨 있다.

    * 하나의 인터페이스는 여러 개의 엔드포인트(Endpoint)를 가질 수 있다.

    * 예를 들어 마우스이면서 Mass Storage의 기능을 제공하려면 Interface는 2개가 될 것이다.

  ▶ 엔드포인트 디스크립터(Endpoint Descriptor): 하나의 엔드포인트에 대한 정보를 가지고 있음

 

    * 실제로 호스트와 디바이스간의 통신이 이루어지는 통로 (소켓과 유사함)

    * 통로라는 의미에서 파이프(Pipe)라고 말하기도 한다.

    * 엔드포인트(Endpoint)의 번호, 전송 타입(Transfer Type), 방향성 등의 정보가 담겨 있다.

    * 엔드포인트의 전송 타입: Control, Interrupt, Isochronous or Bulk와 같은 타입이 있다.

    * 기본적으로 엔드포인트 0번은 디폴트로 Control 타입으로 사용된다.

USB Enumeration

  USB Enumeration이란 새로운 디바이스(Device)가 Bus에 연결이 되었는지 감지한 뒤에 그 디바이스를 인식하고, 적절한 드라이버를 로드하는 작업을 의미한다. 이를 위해 당연히 적절한 USB Driver를 Host 쪽에서 가지고 있어야 한다.

 

  USB Device가 PC에 꽂히게 되면, Host Controller는 Device의 0번 엔드포인트에게 정보를 요청한다. (이 때는 임시 Address를 이용한다.) 이 때 호스트는 디바이스 디스크립터(Device Descriptor) 정보를 받게 된다. 이후에 해당 Device에게 적절한 Address를 할당하고, 그 Address를 이용하여 나머지 Descriptor 정보를 요청한다. 결과적으로 모든 Descriptor 정보를 받고 나면 Enumeration 과정이 종료된다.

728x90
반응형

728x90
반응형

USB 전송 형태(Transfer Type)

  USB 프로토콜을 제대로 이해하기 위해서 공부할 것들이 몇 가지 있다.

 

  그 중에서 꼭 알아야 하는 것은 바로 USB의 전송 형태(Transfer Type)이다. 기본적으로 USB 프로토콜은 호스트(PC)와 디바이스(USB 장치)와의 통신에 관한 것이다.

 

  구체적으로 그림과 함께 설명할 예정인데, 참고해야 할 점은 '회색(Gray)' 부분은 호스트(PC)가 보내는 패킷이고, '하얀색(White)' 부분은 디바이스(Device)가 보내는 패킷이다.

 

 

1. 등시성 전송(Isochronous Transfer)

 

  첫 번째로 알아 볼 USB 전송 타입은 등시성 전송 타입이다. 이는 데이터의 전송 시간을 보증할 때 사용하는 타입이다. 데이터에 오류가 있어도 재전송을 하지 않으며, 호스트(PC)에서는 등시성 전송을 위해 시간을 스케줄링한다.

 

  일반적으로 이어폰과 같이 실시간으로 데이터를 받아서 송출해야 하는 디바이스에서 사용된다. 생각해 보면, 이어폰처럼 실시간으로 데이터를 주고 받아야 하는 경우에서 데이터에 약간의 오류가 있다고 재전송을 시킬 수는 없을 것이다.

 

  아무튼 등시성 전송은 다음과 같이 토큰 패킷과 데이터 패킷으로 처리된다. 흔히 이어폰의 경우에는 호스트가 OUT 패킷을 보냄으로써 "나 지금 음악 스트림 보낼거야."라고 말한 뒤에, 이어폰 디바이스한테 DATA 패킷을 연달아 보내는 것이다. 여기에서 토큰(Token) 패킷이란, 데이터의 방향을 알려주기 위해 호스트가 디바이스에게 보내는 패킷이다.

 

  참고: OUT 패킷은 말 그대로 호스트 PC가 디바이스에게 보낼 데이터가 있다는 의미이다. 호스트 입장에서 받아야 할 데이터가 있다면 호스트가 먼저 IN 패킷을 보낸 뒤에, 디바이스로부터 데이터를 받게 된다.

 

 

2. 인터럽트 전송(Interrupt Transfer)

 

  작은 크기의 데이터를 전달할 때 사용하는 타입이다. 일반적으로 마우스와 같이 클릭할 때만, 일시적으로 호스트(PC)가 데이터를 읽어들이는 경우에서 사용된다. 예시로는 키보드, 마우스 등이 있다.

 

  인터럽트 전송은 말 그대로 비주기적인 형태의 전송이다. 인터럽트 요청은 디바이스에 의해서 차례대로 큐에 담기게 되고, 호스트가 USB 디바이스한테, "너 보내 줄 데이터 있니?"라고 물어보면, 그 때 큐에 담겨진 인터럽트 요청들이 디바이스에서 호스트로 보내진다.

 

  예를 들어 우리가 키보드를 PC에 꽂은 뒤에, 타자를 친다고 가정해보자. 우리가 키보드에 입력한 데이터들은 일시적으로 키보드 디바이스의 내부 메모리 큐에 담기게 되고, 호스트가 폴링(Polling)을 하게 되면 그제서야 호스트에서 우리가 입력했던 문자들을 받아들이게 된다. (여기에서 알 수 있듯이, 호스트 입장에서는 주기적으로 폴링을 해야 한다.) 어느 정도의 주기로 폴링을 할 지는 디바이스의 엔드포인트 디스크립터(Endpoint Descriptor)에 명시되어 있다.

 

  구체적인 과정은 다음과 같다.

 

  ▶ IN 토큰 패킷: 호스트가 데이터를 받는 경우이다. 호스트가 IN 패킷을 보내면, 디바이스의 큐에 데이터가 저장된 경우 데이터를 보내주게 된다. 데이터를 잘 받았으면 ACK를 보낸다. 디바이스의 큐가 비어있으면 (인터럽트 메시지가 없으면) 디바이스에서는 NAK를 보낸다. 그리고 오류가 발생한 상황에서는 디바이스가 STALL을 보내는데, 이러면 호스트가 IN 패킷을 다시 보낸다. 군더더기 없이 깔끔하다.

 

  ▶ OUT 토큰 패킷: 호스트가 데이터를 보내는 경우이다. 호스트가 디바이스로 데이터를 보내는 경우에는, OUT 패킷을 발행한 뒤에 데이터를 보낸다. 디바이스 입장에서는 데이터를 차례대로 받는데, 이는 엔드포인트 버퍼에 담기게 된다. 아직 이전 패킷을 다 처리하지 못해서 엔드포인트 버퍼가 비어 있지 않다면, 디바이스는 NAK를 보낸다. 마찬가지로 오류 상황일 때는 STALL을 보낸다.

 

3. 벌크 전송(Bulk Transfer)

 

  대량의 데이터를 신뢰성 있게 전달할 때 사용하는 타입이다. 데이터 패킷 전송 과정에서 오류가 발생하면 재전송을 요구하며, 등시성 전송과는 다르게 전송 속도는 보증하지 않는다. 대표적인 예시로는 USB 저장 장치(Mass Storage)가 있다.

 

  기본적으로 Bulk Transfer와 Interrupt Transfer는 사실상 같은 트랜잭션 형태를 보인다. 그림 자체도 비슷한 걸 확인할 수 있는데, 벌크와 인터럽트의 차이점은 인터럽트에서는 전송요구에 대한 시간의 주기를 설정할 수 있다는 것이다. 즉, 호스트가 주기적으로 폴링을 수행하도록 요구할 수 있다.

 

  예를 들어 우리가 마우스를 클릭했는데 0.2초 뒤에 클릭이 된 것처럼 처리가 된다면 매우 불편할 것이다. 반면에 대용량 파일을 보내는 과정에서 0.2초 정도의 딜레이는 큰 문제가 없을 것이다. 더불어 Bulk Transfer에서는 1번의 트랜잭션으로 데이터가 전부 전송되지 않을 수 있기 때문에, 데이터를 분할해서 트랜잭션을 반복한다.

 

 

4. 제어 전송(Control Transfer)

 

  USB 기기에게 명령을 내리거나 상태 관련 연산을 수행하기 위해 사용하는 타이다. 모든 USB 기기는 제어 전송을 위해 엔드포인트(Endpoint) 0번을 사용하며, 어떤 데이터를 보낼지 또한 USB 규격으로 정해져 있다. 제어 전송의 경우 모든 USB 기기가 사용하는 전송 타입이다. 기본적으로 처음에 USB 디바이스를 PC에 꽂았을 때부터, 제어 전송을 통해 디스크립터(Descriptor) 정보를 받게 되기 때문이다. 이러한 작업들을 수행하는 것이 바로 제어 전송(Control Transfer)이다.

 

  기본적으로 제어 전송은 3가지 단계로 구성된다. 차례대로 설정 단계(Setup Stage), 데이터 단계(Data Stage), 상태 단계(Status Stage)이다.

 

  1) 설정 단계(Setup Stage): 제어 전송에서 가장 기본적인 단계이다. 이 단계는 초기 설정을 수행하며 세 개의 패킷(Token, Data, Handshake)으로 구성된다. 먼저 토큰 패킷으로는 설정을 진행할 Address 정보와 Endpoit 번호가 담기게 된다. 이후에 정확히 어떤 내용을 설정할 것인지 데이터 패킷에 담아서 보낸다. 예를 들어, 처음에 USB 디바이스에 연결된 상태라면 Descriptor 정보를 보내달라는 내용을 데이터 패킷에 담아서 보낼 것이다.

 

 

  2) 데이터 단계(Data Stage): 데이터 단계는 필요하다면 존재할 수 있다. 만약 데이터 단계가 있다면, 설정 단계에서 '데이터 단계에서 얼마나 많은 패킷이 전달될 지' 미리 설정이 된다. 데이터 단계는 다수의 IN 토큰 (호스트가 데이터를 받는 경우)과 OUT 토큰 (호스트가 데이터를 보내는 경우)으로 구성되는 경우가 많다.

 

  예를 들어 '설정 단계'에서 디바이스에게 Descriptor 정보를 달라는 패킷을 보냈다면, 이 데이터 단계에서는 IN 토큰을 발행해서 디바이스로부터 Descriptor 정보를 받게 된다.

 

 

  3) 상태 단계(Status Stage): 제어 요청의 처리 상태를 보고하기 위해 사용되는 단계이다. 즉, 정상적으로 제어 전송이 완료되었는지를 확인하는 마지막 단계라고 할 수 있다. 

 

  데이터 단계에서 호스트가 IN 토큰을 보냈다면, 호스트 입장에서는 데이터를 모두 다 정상적으로 받았는지 궁금하다. 그래서 OUT 토큰과 길이가 0인 데이터 패킷을 하나 보내서, 디바이스한테 "너 데이터 다 보낸 거야?"라고 물어본다. 그래서 디바이스에서 데이터를 다 보냈다면 호스트에게 ACK를 보냄으로써 현재의 COMMAND가 종료되었음을 알린다. 혹은 NAK를 보내서 아직 더 처리할 게 남았다는 것을 알리게 된다.

 

  반면에 데이터 단계에서 호스트가 OUT 토큰을 보냈다면, 정상적으로 데이터가 다 보내졌는지 확인하기 위해서 IN 토큰을 하나 보내서 디바이스에게 정상적으로 통신이 완료된 건지 물어보는 것이다. 

 

  * 상태 단계에서 OUT 토큰을 보내는 경우는 다음과 같다. (데이터 단계에서 호스트가 IN 토큰을 보낸 경우)

 

 

  * 상태 단계에서 IN 토큰을 보내는 경우는 다음과 같다. (데이터 단계에서 호스트가 OUT 토큰을 보낸 경우)

 

 

USB 기기 클래스(Device Class)

  USB 기기 클래스에 따라서 사용하는 전송 형태(Transfer Type)에도 차이점이 있다. 앞서 언급했듯이, 마우스나 키보드와 같은 디바이스들은 인터럽트 전송 타입을 많이 사용할 것이다. USB 기기 클래스를 간략히 소개하면 다음과 같은 것들이 있다.

 

  * MSC (Mass Storage Class): USB 외부 저장장치에 대한 클래스로, Bulk Transfer를 사용한다.
  * ADC (Audio Device Class): 오디오 장치에 대한 클래스로, Isochronous Transfer를 사용한다.

  * HID (Human Interface Device): I/O 장치(키보드, 마우스) 같은 것에 대한 클래스로, Interrupt Transfer를 사용한다.

 

  이외에도 MTP (Media Transfer Protocol)과 같은 클래스도 있다. 이 클래스는 MSC (Mass Storage Class)의 대용으로 사용될 수 있다. 기본적으로 USB 시스템에서는 Host가 주도권을 가지게 되는데, MSC의 경우 호스트가 마운트를 하고 나면, 해당 USB 저장 장치에 대해서 절대적인 제어권을 가진다. MSC는 블록 인터페이스 수준에서 동작하기 때문이다.

  반면에 MTP는 논리적인 파일 수준의 송수신이 가능하다. 예를 들어 USB를 MTP로 제공할 때는, 특정 파일에 접근할 때는 암호를 입력하도록 하거나 할 수도 있다. 즉, MTP나 MSC나 모두 내부적으로 Bulk Transfer와 비슷한 기능을 이용하지만 목적 자체가 다르다는 점이 특징이다.

728x90
반응형

728x90
반응형

대회 링크: https://codeforces.com/contest/1345

A번 문제: https://codeforces.com/contest/1345/problem/A

 

[ 문제 해설 ]

 

  이 문제는 그림을 그려가면서 다양한 케이스들을 확인해 보면, 문제 풀이 아이디어를 빠르게 떠올릴 수 있다.

 

  행이나 열의 크기가 1이라면, 항상 퍼즐을 풀 수 있다. 그리고 행이나 열의 크기가 둘 다 2, 2일 때에도 항상 퍼즐을 풀 수 있다. 하지만 그 이외의 모든 경우에 대해서는 퍼즐을 풀 수 없다.

 

[ 정답 코드 ]

 

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

    # 행이나 열의 크기가 1이라면 풀 수 있음
    if n == 1 or m == 1:
        print("YES")
    # 행과 열의 크기가 모두 2일 때 풀 수 있음
    elif n == 2 and m == 2:
        print("YES")
    # 이외의 경우에는 모두 풀 수 없음
    else:
        print("NO")

 

B번 문제: https://codeforces.com/contest/1345/problem/B


  이 문제는 N장의 카드를 가지고 있을 때, 최대한 높은 층의 카드 쌓기를 반복적으로 수행하는 문제이다. 그래서 마지막에 몇 개의 카드 피라미드가 존재할 지 출력하면 된다.

 

  아래의 그림에서 볼 수 있듯이,

 

  1층짜리 피라미드: 2장 필요

  2층짜리 피라미드: 7장 필요

  3층짜리 피라미드: 15장 필요

 

 

  그래서, 예를 들어 N = 24라고 해보자.

 

  처음에 15장을 투자해 3층짜리 피라미드를 짓고, 9장이 남는다.

  그리고 7장을 투자해 2층짜리 피라미드를 짓고, 2장이 남는다.

  남은 2장을 투자해 1층짜리 피라미드를 짓고, 0장이 남는다.

 

  그래서 결과적으로 피라미드를 3개 지을 수 있게 된다.

 

[ 문제 해설 ]

 

  이 문제를 해결하기 위한 방법은 간단하다. 문제에서 요구하는 내용을 그대로 구현하면 된다. 이 때 피라미드는 층이 증가할수록 다음과 같이 카드가 필요하다는 특징이 있다.

 

  - 1층 피라미드: 2장

  - 2층 피라미드: 2장 + 5장

  - 3층 피라미드: 2장 + 5장 + 8장

  - 4층 피라미드: 2장 + 5장 + 8장 + 11장

  - 5층 피라미드: 2장 + 5장 + 8장 + 11장 + 14장

 

  즉, 층이 올라갈수록 '추가적으로 필요한 카드의 개수는 3장씩 증가'한다.

 

[ 정답 코드 ]

 

for _ in range(int(input())):
    n = int(input())
    result = 0

    # 남아있는 카드가 2장 이상이라면, 피라미드 만들기 수행
    while n >= 2:
        # 초기 상태
        summary = 0
        count = 2
        height = 0

        # 더 높이 쌓을 수 없을 때까지 쌓기
        while n >= summary + count:
           summary += count
           count += 3
           height += 1

        # 피라미드 하나를 완성했으므로, n에서 필요한 카드의 개수만큼 빼주기
        result += 1
        n -= summary

    print(result)

 

C번 문제: https://codeforces.com/contest/1345/problem/C


  이 문제는 비둘지깁의 원리같은 느낌의 문제다. 

728x90
반응형