USB를 이용한 윈도우 볼륨 컨트롤
USB의 이점은 대단해서 케이블로 전원을 이용할 수 있고, 자동 핸드셰이킹 및 오류 검사, HID 훅으로 OS에 인터페이스 할 수 있다.
기존에 사용하고 있는 마이크로컨트롤러와 툴을 고수한다면, MAX3420E와 같은 SPI 기반의
USB 컨트롤러를 이용하여 USB 주변기기를 구축할 수 있다.
글│Lane Hauck, Maxim Integrated Products, Inc.
저자는 일하면서 하루종일 음악을 듣는다. CDROM 드라이브에 CD를 넣고, 윈도우 미디어 플레이어를 누른 다음 재생 버튼을 눌러 배경음악을 듣는다. 전화벨이 울리면, 윈도우 미디어 플레이어 창에 있는 데스크톱 화면 주위로 마우스를 이리저리 움직일 필요 없이 신속하게 볼륨을 조정하거나 일시정지 버튼을 누르고 싶다. 내 친구는 PC에서 수많은 비디오 게임을 즐기는데, 대부분의 게임들이 전체 화면을 차지하고, 일부는 볼륨 컨트롤 기능이 없어 게임 음향의 볼륨을 조절할 수 없는 것이 불만이다.
이런 절박한 요구사항들이 이 글에서 설명하는 윈도우 볼륨 컨트롤 프로젝트를 만들었다. 윈도우 볼륨 컨트롤은 USB 포트에 연결하며 하나의 노브를 사용한다. 노브를 돌려 볼륨을 조정한다. 플레이어를 일시정지하려면 노브를 누르고, 다시 누르면 플레이 상태로 되돌아온다. 노브를 누른 상태에서 돌리면 플레이어 트랙을 따라갈 수 있다. 노브가 메인 윈도우 볼륨 컨트롤을 제어하기 때문에, 이메일 알림 또는 게임 음향과 같은 현재 오디오에서 재생되는 모든 볼륨을 조정한다. 볼륨 컨트롤은 액티브 윈도우 상에서 실행되지 않아도 윈도우 미디어 플레이어와 함께 동작한다.
이 프로젝트를 USB HID(Human Interface Device)로 설계했으므로 주문형 드라이버 없이도 동작한다. HID 규격 디바이스를 윈도우 시스템에 연결하면 바로 동작한다. HID 리포트 디스크립터(HID Report Descriptor)에서 설명한 바와 같이(이에 관해서는 좀 더 설명하겠음), 저자의 HID 컨트롤은 윈도우에 "원격 제어" 디바이스 역할을 하는 것이다. 이런 매력적인 컨트롤은 이제 나의 키보드 옆에 영원한 자리를 차지하고 있다(그림 1).반도체네트워크 잡지 독자들이 이 프로젝트를 손쉽게 개발하기 위해, 저자는 ExpressPCB.com의 무료 소프트웨어를 이용하여 PCB를 설계했다. 이 글의 끝부분에 회로도 및 PCB 레이아웃 파일이 포함된 설계 패키지가 실려 있다. 이러한 접근법으로 이 글을 읽는 분도 원하는 대로 레이아웃을 주문 제작할 수 있다.
저가 USB
중요한 설계 선택 작업은 저가의 마이크로컨트롤러 + USB 솔루션을 고르는 것이다. 인터넷을 검색하여 통합 USB 컨트롤러를 갖춘 몇몇 마이크로컨트롤러를 찾았지만, 가격이 너무 비싸고, 저자가 갖고 있지 않는 개발 툴을 필요로 했다. 저자는 Atmel AVR 마이크로컨트롤러를 유달리 좋아하는데다가, 싸지만 기능은 완벽히 갖춘 ICE(In-Circuit Emulator)인 Atmel JTAGICE-II를 이미 갖고 있었다.
로우 엔드급 Atmel AVR 라인에서 AtTiny13의 가격은 Digi-Key에서 개당 $1.29에 불과하다. 이 제품은 초소형 8핀 SOIC 패키지로 이용할 수 있기 때문에 본 프로젝트에는 안성맞춤이다. Atmel은 저가의 뛰어난 개발 툴과 AVRStudio라는 훌륭한 어셈블러 디버거 패키지를 공급한다. AVRStudio는 어셈블리 코드를 개발할 때 사용했다. 이 글의 끝부분에 제시된 코드 패키지는 AVRStudio에 대한 완벽한 프로젝트이다.
AtTiny13은 USB 컨트롤러가 포함되어 있지 않다. 하지만 문제없다! Maxim의 저가형 USB 컨트롤러 MAX3420E는 USB 트랜시버와 USB 디지털 스마트가 내장되어 있고, 공통 SPI(common Serial Peripheral Interface)를 이용한 몇 가지 신호만으로 모든 uC에 인터페이스 되기 때문이다. MAX3420E와 연결하려면 AtTiny의 다섯 개의 IO 핀 가운데 네 개 IO 핀이 필요하지만, MAX3420E가 자체 IO 핀을 갖고 있으므로 인터페이스에 필요한 IO 핀이 충분하다. 그림 2는 이 프로젝트의 회로도이다.
U1은 USB 컨트롤러로, 이 컨트롤러의 레지스터 세트는 SPI 인터페이스 상에서 제어된다. U2는 USB 커넥터 상에서 5V VBUS 신호에 연결되어 MAX3420E 및 AtTiny13에 필요한 3.3V로 조정한다. 로터리 인코더와 LED는 MAX3420E의 범용 입력 및 출력 핀에 연결한다. 커넥터 J2는 AtTiny 디버그와 코드 로딩을 위한 것이다. 저항 R4는 프로그래밍/로딩 리셋 신호를 시스템 리셋과 분리시키고, 풀업 저항R3는 로더 또는 ICE가 디버그 커넥터 J2를 사용하는 동안 MAX3420E SPI 포트를 선택하지 못하도록 유지한다.
AtTiny13는 핀을 절약하기 위해 하나의 양방향 데이터 핀(MOSI와 MISO를 결합한)을 이용하여 하프 듀플렉스 모드로 MAX3420E SPI를 동작시킨다. 저항R7은 이 모드를 이용하여 MAX3420E 레지스터 데이터를 판독할 경우에 회선 충돌 문제를 해결한다. 상승 에지의 여덟 번째 SCLK의 상승 에지 에서, MAX3420E SPI 인터페이스는 여덟 번째 명령어 바이트 비트를 샘플하며, 매우 짧은 시간 후, 첫 번째 데이터 비트로 데이터 핀을 구동하기 시작한다. 비트 뱅잉(Bit-Banged)된 SPI 인터페이스는 여덟 번째 데이터 비트를 구동하는 동시에 SCLK 핀을 하이로 구동할 수 없으므로, 동일 명령에서 데이터 핀을 즉시 플로팅(Floating) 시킨다. AtTiny13과 MAX3420E 모두 데이터 핀을 구동하는 데는 약간의 시간이 소요된다. 저항 R7는 이러한 짧은 기간 동안 전류를 낮게(약 3mA) 제한한다.
SPI 통신
AtTiny13는 하드웨어 SPI 유닛을 갖고 있지 않다. 다행히 MAX3420E SPI는 인터페이스가 단순하고, 어떤 속도로도 동작할 수 있다. 이것은 펌웨어가 SPI 인터페이스를 만들기 위해 AtTiny13 IO 핀을 비트 뱅 시킬 수 있음을 의미한다. MAX3420E는 3, 4 또는 5-wire SPI 인터페이스와 동작할 수 있다. 더 이상 줄일 수 없는 신호는 SCLK(직렬 클록), SS#(슬레이브 선택), 그리고 단일 양방향 데이터 핀 MOSI/MISO(Master Out Slave In/Master In Slave Out)이다. 저자는 코드 공간을 절약하고 SPI 버스 트래픽을 최소화시키기 위해 네 번째 핀을 이용하여 AtTiny13 입력 핀과 MAX3420E INT 핀을 연결했다.
HID에 관하여
USB HID 사양의 이면에 들어 있는 아이디어는 사용자가 애플리케이션 프로그램에 연결하고 싶어할 지도 모르는 다양한 센서, 버튼, 라이트 등을 정의하는 것이다. "범용 직렬 버스 HID 용도 표(Universal Serial Bus HID Usage Tables)" (www.usb.org)에서는 수많은 "용도"를 제시하고 있다. 예를 들어, 핀볼 시뮬레이션 프로그램에 대한 제어 기능을 구축하고 싶다면, "게임 컨트롤(Game Controls)" 페이지(HID에서 보면 디바이스 카테고리가 "표"이다)와 0x2A와 동일한 플립퍼를 위한 "Usage ID"를 찾을 수 있다.
자신의 디바이스에 대한 HID 리포트 포맷을 만들 때, 이 페이지와 용도 코드에 해당하는 비트 하나를 리포트 바이트에 할당할 수 있다 그런 다음, 핀볼 시뮬레이션 프로그램은 부착된 HID 디바이스를 찾고, 디바이스의 리포트 디스크립터에서 특정 용도표와 코드를 검색한다. 일치한 부분을 찾는다면, 당신이 지정한 비트가 핀볼 플리퍼에 해당한다는 것이 제법 괜찮은 아이디어임을 발견하게 될 것이다.
그렇다고 모든 핀볼 시뮬레이션이 어떤 특정 용도를 검색해 낸다고는 말할 수 없다. HID 거래는 쌍방향 협정으로, HID 디바이스와 윈도우 애플리케이션 모두 HID 사양의 특정 코드를 사용한다는 합의가 반드시 있어야 한다.
HID 문서는 개략적으로 기술한 전형적으로 복잡한 사양이다. 디바이스를 동작시키기 위해서는 사양의 단 1% 이상만 이해하면 된다. 하지만 결정적인 것은 어떤 부분의 1%인가 하는 것이다. HID를 구현하는 비법은 HID 사양을 모두 이해하는 것이라기 보다는 (a) 필요한 사양의 서브세트는 무엇인지, (b) 실제로 그것이 윈도우에서 지원되는 지 정확히 이해하는 것이다. HID 사양만으로는 (b)의 해답을 찾을 수 없기 때문에 Microsoft 문서를 찾아봐야 한다.
예를 들어, 저자는 이번 프로젝트에 어떤 기능을 추가하려고 했다. 바로 USB 볼륨 컨트롤을 꽂으면 윈도우 미디어 플레이어 애플리케이션을 실행하는 것이다. 저자는 HID 사양을 참조하면서 "A/V 캡처/플레이백"에 대한 하나의 Usage ID를 통해 광범위한 "AL(Application Launch)" 기능을 갖출 수 있는 용도(Usage) 코드를 발견했다. 이 코드로 미디어 플레이어를 실행할 수 있을 것이라고 생각했지만, 이 코드를 윈도우에 보내니 아무런 동작도 하지 않았다. HID는 가능성 있는 용도만을 정의한다는 것을 파악하게 되었다. HID 사양에 열거된 모든 AL 실행 코드들은 잠재적인 용도로 판명되었으며, Windows XP가 지원하는 단 한가지는 "이메일 리더 실행(Launch Email Reader)"이다.
HID 규격의 주변장치들은 리포트를 보내어 윈도우와 통신한다. HID 설계의 주요 작업은 리포트 포맷을 만드는 것인데, 이 포맷은 사용자의 제어 의도를 OS에 전달한다. 이번 프로젝트에서는 다음 사항들을 제어하고자 하며 각 사항은 HID 용도 표에 등록되어있다.
*Volume Up
*Volume Down
*PAUSE
*PLAY
*Next Track
*Previous Track
그림 3은 저자의 HID 리포트 디스크립터이다. www.usb.org에서 제공하는 HID_tool 프로그램을 이용하여 이 디스크립터를 만들었다. 실제 16진수들을 이해하려는 시도는 생각도 마시라. 이것은 툴에서 해결하도록 하자.
AtTiny13 펌웨어는 호스트가 Get_ Descriptor-Report를 요청할 경우, 이 테이블을 호스트로 리턴한다. 리포트 디스크립터(Report Descriptor)는 그림 4와 같이 단일 바이트 리포트에 대한 포맷을 정의한다. "Usage(VOL+)" 라인에서 시작하여, LSB로 시작하는 바이트 내의 비트들을 합한다(USB에서는 모든 것이 낮은 수를 먼저 저장하는 Little-Endian이다). 이 컨트롤에는 단지 6비트만 필요하므로, 바이트 내의 마지막 두 비트는 0으로 패딩한다("Input(Constant)" 라인).
일단 윈도우가 디바이스를 인식하고 열거하면, OS는 그림 4에서 정의한 포맷의 단일 바이트 응답을 기대하면서 주기적인 IN 요청을 Endpoint 3-IN에 전송하기 시작한다. 펌웨어는(Volume Up과 같은) 명령이 주어졌을 때, 리포트 바이트에서 그에 상응하는 변경 및 설정이 있는지 로터리 컨트롤을 모니터한다. 아무 명령이 없으면, 펌웨어는 0바이트를 리턴한다. MAX3420E는 이 USB 업데이트를 간편하게 해 준다.
SPI 마스터(AtTiny13)는 리포트 데이터 바이트를 EP3INFIFO(Endpoint 3 IN FIFO)라 부르는 레지스터에 기록한다. 그 다음에 1값을 EP3INBC(End-point 3-IN byte count)라 부르는 레지스터에 기록한다. 이는 다음 USB IN 요청에 응답해서 1바이트가 전송된다는 것을 지시한다. 전송이 일어날 때, MAX-3420E는 인터럽트 요청을 발생시켜 다음 바이트가 EP3INFIFO에 로드되도록 지시한다. 전체 전송 루틴은 아래와 같다.
꽤 간단하지만, 코드 리스트는 여러 페이지로 이루어져 있다는 것을 알 수 있다. 나머지 코드는 로터리 컨트롤을 점검하고 일부 LED를 깜박거리게 만드는 코드이며, 모든 USB 주변 장치를 관리하는 USB 관용 코드이다. 이 코드는 디바이스를 열거하고 일시 중지, 재개하며 USB 버스 리셋을 다루는 코드이다.
로터리 컨트롤 판독
로터리 인코더(Digi-Key P12418)는 Grey 인코딩된 터미널 A와 터미널 B를 통해 시계방향 또는 반시계방향 회전을 알려준다. 로터리 컨트롤은 MAX3420E 상의 GPIN 핀과 연결되어 있는데, MAX3420E는 풀업 저항이 내장되어있다. 그림 5는 좌측/우측 회전에 대한 스위치 상태를 보여준다. 멈춤쇠의 위치 상태1과 3은 컨트롤 휴지 위치이다. 이 컨트롤은 한번 또는 세번의 상태를 거쳐서 다음의 멈춤쇠 위치에 이른다는 것을 주목하자. 노브 회전을 디코딩하기 위해 펌웨어는 멈춤쇠 상태 1과 3으로의 전이 만을 주목하면 된다. 예를 들어, 오른쪽으로 한번 클릭하면 상태가 3->1 또는 2->3으로의 전이에 해당된다.
코드 맞추기
AtTiny13는 512 워드의 프로그램 메모리가 내장되어 있다. 저자는 전체 USB 애플리케이션이 이렇게 작은 메모리에 적합하도록 맞춰질 수 있는지 의문이었다. 코드는 다음과 같은 작업을 필요로 한다.
① 디바이스를 HID 클래스의 멤버로 열거한다. 이 과정은 여러 형태의 호스트 "Get_Descriptor" 요청 디코딩과 상응하는 테이블 데이터 위치 지정, 그리고 데이터를 호스트로 전송하는 Endpoint Zero FIFO 로딩으로 구성된다.
② 언제든지 일어날 수 있는 USB 버스 리셋 테스트
③ USB 버스 일시 정지 및 재시작 테스트
④ 로터리 컨트롤 위치와 누름-스위치를 주기적으로 판독 및 디코딩
⑤ 호스트가 EP3-IN에 대한 IN 패킷(HID 컨트롤 데이터를 전송)을 수용할 경우, 다음 IN 전송에 대한 엔드포인트를 로드하고 준비한다.
⑥ "동작" LED를 깜박거린다.
저자는 코드 사이즈를 최소화하기 위해 세가지 기법을 사용했다. 첫 번째로, 옵션인 USB 스트링 디스크립터를 생략했다. 이 디스크립터는 벤더 및 디바이스 종류와 같은 디바이스의 여러 부분들을 설명하는 텍스트 스트링이다. 이들 스트링은 정보만을 제공하는 것으로, USB 주변 장치로서 아무런 동작을 수행하지 않는다. 두 번째로, 옵션인 USB 원격 웨이크업(Wakeup) 기능을 구현하지 않았다. 이것은 어려운 일이 아니다(MAX3420E가 그런 작업의 대부분을 처리한다). 하지만 코드 공간을 차지한다. 세 번째로, HID 리포트 디스크립터를 프로그램(플래시) 메모리 대신에 EEPROM에 담아 두었다. AtTiny13는 64바이트의 EEPROM를 갖고 있다. EEPROM에 넣을 수 있는 모든 테이블 데이터는 프로그램(플래시) 메모리에서 워드로 저장한다.
코드 로딩
프로젝트 zip 파일에는 두 가지 로드 모듈, "max3420e_code.hex"와 "max 3420e_code.eep"이 있다. 회로도에 따라 프로젝트를 구현하려면, 프로그램 모듈을 로딩하기 위해 6핀 J2를 이용하여 AVRISP2 인 서킷 프로그래머(Digi-Key ATAVRISP2-ND)에 연결할 수 있다. 코드를 공부하거나 수정하길 원한다면, AVR 계열의 완전 기능을 갖춘 인 서킷 에뮬레이터인 ATJTAGICE2-ND을 J2에 연결할 수도 있다. 어떤 로드 방법을 이용하든 지 .hex 파일(플래시 메모리 코드)과 .eep 모듈(EEPROM 데이터), 모두를 로드하는 것을 잊어서는 안 된다.
두 가지 어셈블러의 상세 설명
디스크립터 테이블 데이터를 코딩하면서, AVR 아키텍처와 어셈블러에 대한 두 가지 흥미로운 세부사실을 발견했다.
패딩 피하기
AVR 프로그램 메모리는 16비트 폭이기 때문에, 바이트 폭 테이블 데이터를 .DB(바이트를 정의) 명령문 당 반드시 짝수 바이트로 작성해야 한다. 코드 판독을 위해 라인 당 하나의 .DB 값과 코멘트를 작성하는 게 좋다. 아래 코드 일부가 보여주는 것처럼, 이렇게 한다면, 어셈블러는 각 바이트에 0바이트를 넣어 16비트 프로그램 워드를 채운다:
DX:
0001e6 0012 .DB 0x12
0001e7 0001 .DB 0x01
0001e8 0000 .DB 0x00
0001e9 0002 .DB 0x02
솔루션은 다음과 같이 .DB 명령문 당 두 개의 바이트로 디스크립터를 그룹핑하는 것이다.
DY:
0001e6 0112 .DB 0x12,0x01
0001e7 0200 .DB 0x00,0x02
16비트 플래시 메모리에만 이 코딩을 적용한다. 반대로, EEPROM은 8비트 폭이므로, EEPROM 데이터(HID 리포트 디스크립터)는 보다 판독이 쉬운 라인 당 1바이트로 코딩할 수 있다.
바이트 포인팅
두 번째 주안점은 포인터 어드레스를 USB 디스크립터 데이터를 위한 바이트 테이블과 같은 형태로 만드는 방법과 관계 있다. AVR은 X, Y와 Z의 세 개의 인덱스 레지스터들이 있으며 이들은 플래시 메모리의 바이트 값을 가리킨다. 바이트 어드레스들은 16비트 플래시 메모리 어드레스에 2를 곱한 다음 짝수 바이트의 LSB는 0을, 홀수 바이트는 1을 설정해서 구할 수 있다. 예를 들면 디스크립터 데이터의 첫 번째 바이트 어드레스(어드레스 DD)를 Z 레지스터에 로드하기 위해서 다음과 같이 코드를 기록한다.
ldi ZH,HIGH(DD*2)
ldi ZL,LOW(DD*2)
lpm desclen,Z
위의 예는 바이트 변수 "desclen"(디스크립터 길이)에 DD 테이블 첫 번째 바이트를 로드한다. 두 번째 실례로, HID 디스크립터 길이는 테이블의 8번째 바이트(오프셋 7)에 들어있다. ZL에 이 어드레스를 로드하기 위해 다음 코드를 사용한다.
ldi ZL,LOW((HD*2)+7) ; 길이는 HID 디스크립터의 8번째 바이트(오프셋 7)이다.
근사한 블루 LED
저자는 모든 기능이 제대로 동작한다는 것을 가리켜주는 깜박거리는 조명을 설계에 포함시키는 것을 좋아한다. 이 설계는 초기에 초 당 한 번 LED를 깜박거렸다. 하지만 몇몇 프로그램 워드가 남아있다는 것을 발견하고 나서 이를 사용해야만 했다. 블루 LED는 밝기가 천천히 오르락 내리락 하며 규칙적으로 움직이는 심장박동 효과를 내고 있다. AtTiny13 타이머/카운터가 대부분의 일을 한다. 저자는 이를 빠른 PWM 모드로 설정했으며 8비트 타이머가 0부터 시작하여 0xFF가 나타나면 다시 0으로 돌리며 연속적으로 카운트한다. 그런 다음, 카운터가 OCC0B 레지스터를 비교하여 로드된 카운트 값이 되었을 때 LED를 토글링 하도록 했다. 시간에 따라 비교 값을 천천히 올림으로써 PWM 출력이 LED의 밝기를 부드럽게 변화시킨다.
코드를 줄여 주는 호의적인 호스트
이 프로젝트의 중요한 과제는 512 워드의 프로그램 메모리를 이용하여 완전 기능의 USB 디바이스를 구축하는 것이다. 저자는 처음에 코드 크기가 문제가 될 것이라고 생각했기 때문에 USB 호스트에 관한 몇 가지 가정으로 코드를 얼마나 절약할 수 있을 지가 의문이었다. 이것은 모든 USB 코드 기록자(Writer)가 반드시 고려해야 하는 매우 흥미로운 철학적인 문제이다. 이에 대한 절충안이 바로, 호스트가 잘 동작한다고 가정을 하면 할수록 더 많은 코드를 절약할 수 있다는 것이다.
예를 들어, 열거 과정에서 우리는 하나의 인터페이스(=0)와 하나의 교체 설정(=0)을 갖는다고 리포팅하는 인터페이스 디스크립터를 되돌려 보낸다. 호스트가 Set_Interface 요청을 보낸다고 가정하자. 호스트가 유일한 인터페이스(0)와 유일한 대체 인터페이스(0)를 요청한다고 확신하기 위해 그 요구를 점검할 필요가 있을까? 악의 있은 호스트만 열거 과정에서 정의된 디바이스의 존재하지 않는 기능을 설정하려고 시도할 것이다. 사실, 유일한 인터페이스와 교체 설정으로 호스트는 결코 Set_Interface 요청을 보내지 않아야 한다. 하나만을 가지고 있기 때문에 선택할 교체 설정이 없다는 것이다. 따라서 호스트가 잘 동작한다는 전제하에 Set_Interface 요청에서 IF=0, AS=0를 시험하는 안전성 점검을 안전하게 생략할 수 있다. 주류 OS를 사용하는 PC라는 안전한 가정이다.
초기에 몇몇 코드 섹션들을 주석 처리함으로써 이 같은 가정을 세웠다. 하지만 몇 개의 USB 테이블 데이터를 EEPROM에 넣는 브레인스토밍을 한 후, 점검 코드를 복구했으며 여전히 약간의 프로그램 바이트가 남아 있었다. 이런 애플리케이션은 512워드 코드 메모리의 약 90%를 사용하므로, 자신만의 코드를 꾸미는데 마음껏 사용하도록 하자.
결론
USB는 PC의 직렬 포트를 대신하여 거의 모든 인터페이스 연결로 선택된다. 본 프로젝트에서 제시하는 바는 USB가 직렬 포트보다 조금 더 복잡하지만, USB 연결은 많은 공간의 코드나 값비싼 마이크로프로세서를 필요로 하지 않는다. 대부분의 코드는 USB 관용 코드이므로, 프로젝트를 수행할 때마다 간편하게 재사용할 수 있다. USB의 이점은 대단해서 케이블로 전원을 이용할 수 있고, 자동 핸드셰이킹 및 오류 검사, HID 훅으로 OS에 인터페이스 할 수 있다. 기존에 사용하고 있는 마이크로컨트롤러와 툴을 고수한다면, MAX3420E와 같은 SPI 기반의 USB 컨트롤러를 이용하여 USB 주변기기를 구축할 수 있다.
<자료제공: 월간 반도체네트워크 2007년 02월호>