Bulk In

출처 : http://muosys.egloos.com/112198

 

UniHigh 모듈을 가지고 bulk든 isochronous든 IN transfer를 연습해 보려면 무언가 데이터를 모듈로 전송해 줄 수 있는 게 필요하다.

UniHigh 모듈 두 개를 붙혀서 하나는 OUT transfer를 통해 받은 데이터를 I/O 포트를 통해 다른 모듈로 전해주면 다른 하나는 I/O포트를 통해 받은 데이터를 IN transfer를 통해 호스트로 전송하면 빵빵한 대역폭을 다 시험해 볼 수 있겠지만 그렇게 하다간 행자들 호주머니가 거덜나게 생겼는지라…

그래서 생각한 게 페러렐 포트를 통해 데이터를 전송하고, UniHigh모듈이 이 데이터를 I/O포트를 통해 받아 IN transaction을 실행하는 것이다.

나중에 GPIF를 사용한 transfer를 연습할 때도 지금 만들 것을 사용하면 될 것 같다.

그래서 본좌, 집에서 굴러다니던 멀쩡한 프린터 케이블 한쪽 대가리를 썩둑 썰어버렸다.
(본체에 끼우는 반대쪽 대가리를 자르시라. 반대를 자르면 대략 낭패.)

clip_image001

clip_image002

핀 번호를 헛갈리지 않기 위해 일일이 테스터로 찍어가며 라벨링을 한 다음 번호순으로 핀 헤더에 납땜했다.
18핀이 되더라.
핀 번호는 여기를 참고하시라.

Parallel 포트의 신호레벨이 5V이므로 3.3V를 쓰는 FX2랑은 안 맞지만, 버퍼 같은 거 구하기도 귀찮고 해서 어떻게 대충 저항만 가지고 때워보려고 이리저리 고민중이다.

위의 케이블과 함께 4.7KΩ 저항 10개 정도와 330Ω저항 4개정도도 미리 구해 놓으시라.

본좌 패러렐 포트 가지고 뭐 해 본적이 없어서, 학습 하려면 시간 좀 걸릴 것 같다.
빠르면 오늘 밤 안에 되겠고, 늦으면 이번 주말을 다 까먹으리라.

고로 (SPP,) ECP, EPP 모드 중 하나를 MS에서 제공하는 표준 드라이버를 통해 구현한 적이 있는 행자가 소스를 던져주면 낼롬 받아 먹겠다.
(즉, 어플리케이션 단에서 WriteFile로 데이터 전송한 예.)

당근 펌웨어 단에서 그 모드에 대응하기 위해 취해 줘야 하는 동작에 대한 소스나 설명도 있으면 감사하다.

*이미 다른 마이컴 모듈을 가지고 있어서 UniHigh 모듈로 데이터를 주는데 별 문제가 없는 행자들은 기냥 패쓰.

오늘 실험할 Bulk IN 트렌스퍼는 절차가 약간 복잡하다.
페러렐 포트를 통해 UniHigh 보드로 데이터를 전송해야 하기 때문에 그렇다.
아래의 절차를 빼먹지 말고 잘 따라가시라.

1. 페러렐 포트를 제어하기 위한 드라이버 설치

먼저 port95nt.exe를 다운받아 설치한 후, 컴퓨터를 재시작 한다.
http://electoy.cafe24.com/blog/?no=121&category=13의 아랫쪽 “Win98/2000에서 병렬포트 쉽게 사용하기” 에 다운받을 수 있는 다른 곳과 자세한 사용법이 나와 있다.
이 프로그램을 깔면 페러렐포트의 레지스터를 직접 제어할 수 있도록 해주는 드라이버가 깔린다.
이 드라이버는 DLPORTIO.lib라는 라이브러리를 통해 사용할 수 있는데, 본좌는 이 라이브러리를 이용해 Parallel.exe라는 어플리케이션을 만들었다.

2. Parallel.exe 어플리케이션 설치 및 데이터 전송 준비

Parallel이라는 어플리케이션은 본좌가 디자인한 프로토콜에 따라 페러렐포트를 통해 데이터를 전송하는 일을 한다.
전송할 데이터는 저번 강좌와 동일한 data.bin파일이다.
Parallel.exe를 실행시켜, “file” 버튼을 눌러 전송 할 데이터 파일(data.bin)을 선택한다.
그럼 “File Length”에 숫자가 나타날 것이다.
이 숫자를 잘 봐두자.

3. 회로 세팅

UniHigh보드를 아래 회로에 맞게 세팅한다.
(점선 부분은 그냥 데이터가 오가는 과정을 우리 눈으로 확인하려고 설치한 부분이므로 생략해도 상관 없다.)

clip_image003

* 우측 상단의 핀 설명은 Parallel Pin 1 (STROBE), 10 (ACK), 17 (SELECT)임.

4. 펌웨어 다운로드 및 데이터를 받을 준비

UniHigh1.6을 다운받아 C:CypressUSBExamplesFX2에 복사해 넣는다.
EZ-USB Control Panel을 실행시켜, 보드로 UniHigh1.6을 다운로드한다.

Get Pipe 버튼을 누른다.
“Pipe0 : Endpoint 6 IN” 파이프가 잡힐 것이다.
Length에 앞서 Parallel.exe의 “File Length” 에디트 박스에 나타난 숫자를 적어준다.
그리고, “Bulk/Int” 버튼을 누른다.
이제 EZ-USB Control Panel은 Endpoint 6 IN 파이프에서 데이터가 들어오기를 기다리는 상태가 되었다.

clip_image005

5. 페러렐 포트를 통해 데이터 전송

데이터를 보내는 것은 Parallel.exe의 “Transfer” 버튼을 누르면 된다.

clip_image006

6. 결과 확인

그럼 Paraller포트를 통해 데이터가 전송되고, 이에 따라 LED가 깜빡 거리면서
이 데이터가 다시 USB를 통해 호스트로 전송될 것이다.
전송된 결과는 EZ-USB Control Panel에 나타난다.

clip_image008

웹캠으로 찍었더니만 누락되는 프레임이 생겨서, LED의 깜빡임이 약간 부자연 스럽게 보인다.
원래는 “Bulk Endpoint로 데이터 날리기“에서 처럼 자연스럽게 깜빡인다.

여기까지의 절차를 주의 깊게 따라해라.
하나만 삐끗해도 뭔가 잘 안될 것이다.

본좌가 Q&A 게시판에 관련 파일들을 모두 올려 놓을 테니, 다운로드가 잘 안되는 행자들은 거기서 다운 받으시라.

Parallel.exe 소스 코드

펌웨어 소스코드에 대한 설명은 내일로 미룬다.

펌웨어 소스코드를 보려면 본좌가 만든 울트라 허접 프로토콜도 알아야 하기 땜시 부지런한 행자들을 위해 프로토콜을 설명하는 그림과 간락한 설명을 미리 포스팅한다.

clip_image009

데이터 전송절차 (번호는 윗 그림의 번호와 동일)
1. 호스트는 Select를 Low로 세팅하여, 데이터 전송이 시작될 것임을 디바이스에게 알린다.
디바이스는 Select가 Low가 되면 데이터를 받을 준비를 한다.
디바이스에게 준비할 시간을 주기 위해 100mS를 delay한 후 2.로 넘어간다.
2. 호스트는 Ack가 Low인지 체크한다.
3. Ack이 Low이면 디바이스가 데이터를 받아들일 준비가 되 있는 것이므로, 버스에 Data를 싣는다.
4. 호스트는 Strobe를 Low로 세팅한다.
5. Strobe가 Low가 되면 디바이스는 호스트가 데이터를 실었다는 것을 알아채고, Ack를 High로 세팅한다.
6. 디바이스는 버스의 데이터를 FIFO로 넣은 후, Ack를 Low로 세팅한다.
7. 호스트는 3번 이후, Ack가 High가 될 때까지 기다린 다음 Ack가 High가 되면 Strobe를 High로 세팅 한 후, 전송할 데이터가 남았다면 2번부터 반복한다.
8. 데이터를 모두 전송했으면 호스트는 Select를 High로 세팅한다.

디바이스 입장에서 본 데이터 전송절차
1. Select를 Low가 되면 데이터를 전송할 준비를 한다.
(우리의 예제에서는 아무일도 하지 않는다.)
2. Strobe가 Low가 되면 데이터가 도착한 것이므로
3. Ack를 High로 세팅한다.
4. Data를 FIFO에 넣는다.(미리 설정된 길이대로 패킷을 날린다.)
5. Ack를 Low로 세팅한다.
6. Select를 High가 되면, FIFO의 나머지 데이터를 날린다.

이제까지 부산시럽게 뚝딱 거렸지만서두.
결국 우리가 하고자 한 것은 Bulk모드에서 디바이스에서 호스트로 데이터를 보내려고 한 것이다.

UniHigh1.6은 UniHigh1.0에서 이번 예제에 필요하지 않은 코드를 지워서 뼈대만 남겨놓고 나서 Bulk IN transfer를 위한 코드를 추가한 것이다.
(그래봤자 Bulkloop.c의 DR_VendorCmnd()함수만 비운 것이다.)

자 본좌가 젤 먼저 추가한 부분이 뭘까?
빙고.
데이터를 전송할 통로를 확보하기 위해 Endpoint Descriptor를 추가했다.

;; Endpoint Descriptor
db DSCR_ENDPNT_LEN ;; Descriptor length
db DSCR_ENDPNT ;; Descriptor type
db 86H ;; Endpoint number, and direction
db ET_BULK ;; Endpoint type
db 40H ;; Maximun packet size (LSB)
db 00H ;; Max packect size (MSB)
db 00H ;; Polling interval

나머지 항목은 따로 설명 안 해도 아실테고, 세 번째 바이트(Endpoint number, and direction)가 0x86이다.
파이프의 데이터 전송 방향을 나타내는 최상위 비트가 1이고 EP6을 사용할 것이므로 0x86이 된 것이다.
엔드포인트 티스크립터에 대한 설명은 여길 참조하시라.

이번에도 역시 High Speed와 Full Speed Descriptor를 동일하게 해 주었다.
Interface Descriptor의 Number of end points 필드도 1로 세팅해 주어야 한다는 걸 잊지 마시라.

두 번째로 한 일이 TD_Init()에 초기화 코드를 추가한 것이다.

EP6CFG = 0xE2;
0xE2는 EP6CFG 레지스터의 디폴트 값이므로 쓰나 안쓰나 마찬가지지만 심심해서 그냥 한번 써 봤다.
Bulkloop예제에서도 EP6를 IN Endpoint로 쓰고 있는데, Bulk IN transfer를 위해 꼭 EP6를 써야 할 이유는 없다.
레지스터 값만 적절히 세팅해 주면 EP2/4/6/8 어느 Endpoint든 사용 가능하다.
만약, 크고(1024) 깊은(quad buffering) 버퍼가 필요하다면 FX2의 하드웨어 구조상 EP2나 EP6가 유리하다.
세팅 가능한 버퍼의 형태는 T.R.M. 1-23을 참조하시라.

OEA = 0xA0; : 10100000
Port A의
7번 핀은 OUT : ACK 신호와 같이 LED를 키고 끄기 위한 용도로 사용
6번 핀은 IN : STROBE신호를 받기 위한 핀
5번 핀은 OUT : 호스트(컴퓨터)로 ACK 신호를 보내기 위한 핀
4번 핀은 IN : SELECT 신호를 받기 위한 핀

IOB = 0x00;
이걸 먼저 적고 나중에 OEB = 0xFF;를 써 주었는데,
순서를 바꿔 써야 맞는 것 같다.
행자들은 바꿔주시라.
I/O핀의 디폴트 세팅이 input으로 되어 있는데,
이 디폴트 세팅 상태에서 IOB레지스터에 데이터를 써 봤자 아무 의미 없는 짓거리다.
본좌도 왜 이렇게 했는지 모르겠다. -.-; 본좌가 잠시 광마로 변했었나 보다.

LED = OFF;
ACK = LOW;
Port A의 7번 5번핀의 상태를 LOW로 세팅한다.

AUTOPTRH2 = MSB( &EP6FIFOBUF );
AUTOPTRL2 = LSB( &EP6FIFOBUF );
이미 설명한바 있는 autopointer를 사용하기 위해 두번째 autopointer 값을 초기화 해주고 있다.
우리는 받은 데이터를 EP6를 통해 호스트로 전송할 것이므로, EP6의 버퍼주소 시작번지로 초기화 해주고 있다.

이제까지는 기냥 준비과정이었고 지금 설명할 TD_Poll()에서 실질적인 동작을 하고 있다.

( 본좌의 울트라 허접 프로토콜에 따르면 )
컴퓨터에서 페러렐 포트를 통해 데이터가 연달아 전송할 때 어느 시점에서 데이터를 끊어 읽어야 하는지를 디바이스에게 알리기 위해 호스트는 STROBE 신호를 High->Low로 세팅한다.
그러면 디바이스가 STROBE 신호를 감지했음을 호스트에 알리고, 디바이스가 이 데이터를 다 처리하기 전에 호스트가 다음 데이터를 보내지 않도록 ACK를 High로 세팅한다.
우리가 데이터를 받아 할 일은 이것을 FIFO에 넣는 것뿐이다.
데이터를 EP6의 FIFO에 넣고 나면 다음 데이터를 받을 준비가 된 것이므로, ACK를 다시 Low로 세팅한다.

여기까지가 아래 코드에 대한 설명이다.

if( STROVE == LOW ) <-앗 오타닷! 
{
ACK = HIGH;
LED = ON;

IOB = IOD;
EXTAUTODAT2 = IOD;
wCount++;
EZUSB_Delay(20);

ACK = LOW;
LED = OFF;

EZUSB_Delay(20);
}

Delay는 Port B에 달아놓은 LED가 너무 빨리 깜빡거리는 것을 방지하기 위해 삽입한 것이다. 빠른 데이터 전송을 위해서는 불필요하다.

IOB=IOD는 전송된 데이터에 따라 Port B의 LED를 키기 위한 것이고,

EXTAUTODAT2 = IOD;가 EP6 FIFO로 받은 데이터를 넣는 코드이다.

앞서 AUTOPTRH2를 EP6 버퍼의 시작번지로 세팅해 놓았으므로, EXTAUTODAT2에 데이터를 쓸 때마다 자동으로 autopointer가 증가하여 EP6 버퍼의 다음 번지를 가리키게 된다.
따라서 우리는 이것저것 신경 쓸 필요 없이 그냥 EXTAUTODAT2 = IOD;만 해주면 된다.

wCount를 증가시켜 주는 것은 우리가 엔드포인트 디스크립터에서 설정한 Packet Size가 64 Byte이므로 이 패킷 사이즈만큼 버퍼가 차면 패킷을 날리기 위해서이다.

if( PACKET_SIZE == wCount )
{
// wait until IN buffer empty
while(EP2468STAT & bmEP6FULL);

// Initilize Autopointer
AUTOPTRH2 = MSB( &EP6FIFOBUF );
AUTOPTRL2 = LSB( &EP6FIFOBUF );

// Commit Packet
EP6BCH = MSB(wCount);
SYNCDELAY;
EP6BCL = LSB(wCount); // arm EP6IN

wCount=0;
}

버퍼에 64바이트가 차면 Autopointer를 초기화 하고 패킷을 날리는 루틴이다.

While문은 USB의 대역폭이 감당하지 못할 만큼 데이터가 너무 빨리 들어오는 경우, 버퍼가 꽉 차 버리면 다음 데이터를 받지 않기 위해 삽입한 문장이다.

마지막으로 패러렐 포트의 SELECT신호가 High가 되면 디바이스는 호스트가 전송할 데이터를 다 전송했구나 하는 것을 알게 된다.
들어온 데이터가 우리가 정한 패킷사이즈(64byte)에 딱 나눠 떨어지는 크기라면 별 문제가 없겠지만 그렇지 않을 경우에는 버퍼에 전송되지 못하고 남은 데이터가 남아있게 된다.
이럴 때, SELECT신호를 감지해서 버퍼에 남은 자투리 데이터를 전송하기 위한 코드가 아래의 코드이다.

if( LOW == SELECT )
{

}
else
{
if( wCount )
{
// Commit Remnant Data
EP6BCH = MSB(wCount);
SYNCDELAY;
EP6BCL = LSB(wCount); // arm EP6IN

wCount=0;
}
}

Bulk IN을 수행하기 위한 핵심 코드는
EXTAUTODAT2 = IOD; -> 데이터를 버퍼에 넣고

EP6BCH = MSB(wCount);
SYNCDELAY;
EP6BCL = LSB(wCount); -> 패킷을 날린다.

뭣도 없는 것을 졸라 길게 설명하자니 본좌 힘들었다. -.-;;;

SYNCDELAY는 앞서 어딘가에서 설명했으므로 기억 안 나는 행자들은 잘 찾아 보시라.

답글 남기기

아래 항목을 채우거나 오른쪽 아이콘 중 하나를 클릭하여 로그 인 하세요:

WordPress.com 로고

WordPress.com의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Twitter 사진

Twitter의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Facebook 사진

Facebook의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

Google+ photo

Google+의 계정을 사용하여 댓글을 남깁니다. 로그아웃 / 변경 )

%s에 연결하는 중

%d 블로거가 이것을 좋아합니다: