두둥~ 드디어 bulkloop.c

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

 

본좌. 한 이틀간 회로와 납땜 얘기로 변죽만 울렸다.
오늘은 바로 핵심으로 돌진한다.

이제까지 desc.a51 파일과 fw.c 파일을 디볐는데, 실질적으로 bulkloop 프로젝트의 핵심은 bullkloop.c 파일에 있다.

이전에 이미 언급했던 것처럼 그 중에서도 핵심은 TD_Init(), DR_VendorCmnd(), TD_Poll() 이 세 함수가 되겠다.

주말에 술 마시느라 기억력을 고스란히 술잔에 반납하신 행자들을 위해 본좌가 졸라리 싫어하는 리바이벌을 함 하자면,

TD_Init()은 우리가 필요한 초기화 루틴을 넣으라고 FrameWork님께서 마련해 놓으신 자리이고,

DR_VendorCmnd()은 Default Control Endpoint( Endpoint 0 )를 통해 호스트로부터 오는 명령 중에서 FrameWork님께서 처리하지 않는 우리만의 명령을 처리하기 위한 루틴을 위한 함수이고,

TD_Poll()은 Poll이란 단어의 뉘앙스에서 팍 느껴지듯이 (안 느껴지남?) 루프를 돌면서 계속 할 일이 있는지 체크하다가 할 일이 생기면 처리하는 그런 코드를 담은 함수가 되겠다.
여기가 바로 bulkloop의 핵심이다.

잠시 딴길로 빠지자면, 이 bulkloop의 예에서처럼 polling으로 일을 처리하는 코드는 처음에 짜기는 편하지만 프로세서가 처리해야 할 일이 많아지기 시작하면 이런 “마이크로”프로세서에서는 쥐약이다.
우리가 쓰는 컴퓨터처럼 운영체제가 멀티프로세싱을 지원하는 것도 아니어서 한가지 작업을 하는데 시간을 많이 잡아먹어 버리면 그 뒤에 처리를 기다리는 Time Critical한 작업들도 전부 다 지연되어버린다.
따라서 인터럽트 우선순위를 잘 이용한 Interrupt driven 방식으로 코드를 짜기를 권한다.
Bulkloop는 예제이니 그냥 그런가 보다 하고 넘어가자.

TD_Init()과 DR_VendorCmnd()는 후에 밟아보기로 하고, 오늘은 바로 본론(TD_Poll() )으로 들어간다.

Bulkloop가 호스트가 보내는 데이터를 FIFO에 저장했다가 호스트가 데이터를 요구하면  FIFO의 데이터를 차례로 꺼내주는 예제이니만큼 여기에 이런 기능이 구현되어 있을 것이다.

if(!(EP2468STAT & bmEP2EMPTY))
{
어쩌구
}
if(!(EP2468STAT & bmEP4EMPTY))
{
저쩌구
}

첫 번째 if 문은 Endpoint 2로 들어오는 데이터를 Endpoint 6로 내보내는 코드이고,
두 번째 if 문은 Endpoint 4로 들어오는 데이터를 Endpoint 8로 내보내는 코드이다.

두 코드는 데이터를 IN/OUT하는 엔드포인트만 다를 뿐, 하는 일은 똑 같으므로 첫 번째 if문만 설명하도록 하겠다.

먼저
if( !( EP2468STAT & bmEP2EMPTY ) )
자 통빡을 굴려보자. 데굴데굴
EP2468STAT은 무슨 레지스터 같고, bmEP2EMPTY는 읽어보니 EP2(엔드포인트 투)가 비었다는 걸 나타내는 것 같다.
통빡들 다 굴리셨남?
다 굴렸으면 데이터쉬트 찾아볼 차례다.
다들 T.R.M(Technical Reference Manual)을 뒤비시라.

본좌는 예전에 뒤볐었기 때문에 그냥 혼자 진도 나간다.
만일 EP2의 버퍼가 비어있다면
( EP2468STAT & bmEP2EMPTY ) == TRUE 일 것이고
따라서
( !( EP2468STAT & bmEP2EMPTY ) ) == FALSE일 것이다.
고로 if문을 수행하지 않는다.

정리하면 EP2의 버퍼가 비어있다면 if문을 수행하지 않는다는 말이다.
바꿔 말하면 EP2의 버퍼가 비어있지 않다면 if문으로 들어가 뭔 짓거리를 한다는 말쌈이다.

EP2의 버퍼가 비어있지 않을 때 어떤 짓거리를 하는지 따라 들어가 보자.

if(!(EP2468STAT & bmEP6FULL))
어라~! 비슷한게 또 나왔네?
이번엔 막바로 설명하면 EP6의 버퍼가 꽉차있지 않다면 if문으로 들어가 뭔 짓거리를 한다는 말쌈이다.

두 if 문을 합쳐서 생각해보면
(데이터를 받는) EP2의 FIFO에 뭔가가 들어왔고,
(데이터를 보내는) EP6의 FIFO가 꽉 차서 더 이상 (호스트로 ) 데이터를 보낼 수 없지 않다면
이 짓거리를 한다.

{
APTR1H = MSB( &EP2FIFOBUF );
APTR1L = LSB( &EP2FIFOBUF );
AUTOPTRH2 = MSB( &EP6FIFOBUF );
AUTOPTRL2 = LSB( &EP6FIFOBUF );
count = (EP2BCH << 8) + EP2BCL;
for( i = 0x0000; i < count; i++ )
{
EXTAUTODAT2 = EXTAUTODAT1;
}
EP6BCH = EP2BCH;
SYNCDELAY;
EP6BCL = EP2BCL;
SYNCDELAY;
EP2BCL = 0x80;
}

라는 말쌈이 되겠다.

APTR1H, APTR1L과
AUTOPTRH2, AUTOPTRL2는
각각 Auto Pointer1과 Auto Pointer2를 위한 레지스터가 되겠다.

이 오토포인터가 뭐시기냐 하면

보통 버퍼에서 데이터를 한 바이트(워드)식 불러다가 처리 하고자 할때에는 다음과 같은 절차를 반복하게 된다.
1. 포인터를 버퍼의 맨 앞을 가리키도록 초기화한다.
2. 포인터가 가리키는 버퍼의 주소에 있는 데이터를 가져다가 지지고 볶는다.
3. 포인터를 하나 증가시킨다.
4. 원하는 만큼 2,3번을 반복한다.

그런데
Auto pointer를 쓰게 되면 3번의 절차를 생략해도
버퍼에서 데이터를 가져올 때마다 포인터가 자동으로 증가한다.

그래서 우리는
1. 포인터를 버퍼의 맨 앞을 가리키도록 초기화한다.
2. 포인터가 가리키는 버퍼의 주소에 있는 데이터를 가져다가 지지고 볶는다.
3. 원하는 만큼 2번을 반복한다.
요렇게만 해도 된다는 거다.
쫌 편해 보이지 않는가?
아님 말고.

물론 버퍼에서 데이터를 가져올 때마다 포인터를 감소시킬 수도 있고,
물론 버퍼에서 데이터를 가져올 때마다 포인터를 증가시킬 수도 있고,
물론 버퍼에 데이터를 쓸 때마다 포인터를 감소시킬 수도 있고,
물론 버퍼에 데이터를 쓸 때마다 포인터를 증가시킬 수도 있다.
그건 Auto pointer를 어떻게 세팅하는가에 따라, 그때그때~ 달라여~.

Auto pointer에 대해 자세한 것은 나중에 살피기로 하고, 다시 진도 나가자.

APTR1H = MSB( &EP2FIFOBUF );
APTR1L = LSB( &EP2FIFOBUF );

AUTOPTRH2 = MSB( &EP6FIFOBUF );
AUTOPTRL2 = LSB( &EP6FIFOBUF );

오토포인터1은 EP2 FIFO 버퍼의 시작주소를 가리키도록,
오토포인터2은 EP6 FIFO 버퍼의 시작주소를 가리키도록,
초기화 하고 있다.

count = (EP2BCH << 8) + EP2BCL;
count라는 WORD형( 2 byte ) 변수의 상위바이트에는 EP2BCH를 하위 바이트에는 EP2BCL가 들어가도록 우겨넣고 있다.

EP2BCH/EP2BCL가 뭔지 머리 떼고, 꼬리 떼고 다짜고짜 설명하면 호스트로부터 EP2 버퍼에 데이터가 얼마만큼 들어온 지를 나타내는 레지스터가 되겠다.
(EP2 Byte Count High/Low)

호스트가 데이터를 우리 디바이스에게 날리면 FX2는 그 데이터를 받아서 FIFO에 차곡차곡 넣은 다음 들어온 데이터가 얼마만큼인지를 EP2BCH/EP2BCL에 써 넣는다.
그리고 나서 요 앞서 보았던 EP2468STAT 레지스터에
딸랑~딸랑~ EP2에 데이터 들어왔어요 하고 해당 비트(bmEP2EMPTY)를 세팅하는 것이다.

FX2 졸라 착하지 않은가?
우리는 USB 트렌젝션(transaction)이 어떻게 이루어지는지 아직 알지도 못하는데, FX2가 다 알아서 해버린다.

그 다음

for( i = 0x0000; i < count; i++ )
{
EXTAUTODAT2 = EXTAUTODAT1;
}

들어온 데이터 바이트 수 만큼 뺑뺑이를 돌리는데, 어랍쇼?
EXTAUTODAT2는 뭐고, EXTAUTODAT1는 또 뭐야?

바로 앞서 설명한 Auto pointer가 가리키는 버퍼의 데이터가 되시겠다.
C에서의 참조연산자 “*”라고 생각하시면 딱이다.
*Autopointer2 == EXTAUTODAT2

Autopointer2는 EP6 FIFO Buffer의 시작번지로 초기화하고, Autopointer1는 EP2 FIFO Buffer의 시작번지로 초기화 한 거 기억 하시남?
기억 안 나면 언능 앞에 가서 보고 오셔~.
루프를 한번 돌 때마다 Autopointer2, Autopointer1은 각 버퍼의 처음 주소부터 시작해서 자동으로 1씩 증가 할 것이고,
EXTAUTODAT”x”는 Autopointer”x”가 가리키는 번지의 데이터이므로 For 루프안에서 하는 일은 결국 EP2 버퍼의 데이터를 EP6로 고스란히 옮기는 거이 되겠다.

그리고 나서
EP6BCH = EP2BCH;
SYNCDELAY;
EP6BCL = EP2BCL;
SYNCDELAY;

SYNCDELAY;는 일단 생까자.
뭔 일을 하는 명령어는 아니다.

그럼 이렇게 된다.
EP6BCH = EP2BCH;
EP6BCL = EP2BCL;

보라 EP2의 EPxBCH/L를 EP6의 그것에 고스란히 복사하고 있다.
얼랄라리오.
아까는 EPxBCH/L가 받은 데이터의 바이트수가 들어있는 레지스터 라메?
하시는 행자분께 박수를.
암 생각없는 행자는 1분간 olo잡고 반성.

EP”x”BCH/L에서 “x”가 OUT Endpoint를 나타낸다면, 본좌가 앞서 설명한게 맞고,
EP”x”BCH/L에서 “x”가 IN Endpoint를 나타낸다면, 호스트에게 보낼 바이트 수를 저장하는 레지스터가 된다.
그때 그때 다른 쓰임세 되겠다.
왜 그러냐고 묻지 마라.
Cypress에서 그렇게 만들었을 뿐이다.
(아울러 IN transfer가 디바이스->호스트로 데이터가 전송된다는 것도 한번 더 상기시켜 드린다. – host 입장에서 바라보기)

그러니까 정리해보면 EP2로 받은 데이터를 고대로 EP6의 버퍼에 카피하고, EP6BCH/L을 기입함으로써 “나 이만큼 데이터를 날릴 꺼야” 라고 장전까지 마친 상태가 여기까지이다.

EP2BCL = 0x80; // re(arm) EP2OUT
본좌가 설명 하나 마나, 주석에 써 있는 대로( OUT Endpoint인 ) Endpoint2를 재장전 한다는 말이다.
FX2에서 OUT Endpoint의 버퍼로 들어온 데이터를 다 참조하고 나면 그 버퍼를 비워줘야 다음 데이터를 받을 수 있을 것이다.
그 일은 EPxBCL에 아무 값이나 써 넣음으로써 할 수 있다.
이 예제에서처럼 꼭 0x80을 써넣어야 할 필요는 없다는 말쌈이다.

반대로 IN Endpoint의 버퍼에 호스트로 날릴 데이터의 준비가 끝났다는 것을 FX2에 알리기 위해서는 어제 설명한 것처럼 EPxBCH/L에 값을 써넣으면 된다.
값을 써 넣을 때는 반드시 EPxBCH를 먼저 쓰고 그 다음에 EPxBCL을 써 넣어야 한다.
왜냐하면 FX2는 IN Endpoint의 EPxBCL가 써지는 순간을 Endpoint의 데이터가 준비된 시점으로 인식하기 때문이다.

예를 들어 설명하자면 IN Endpoint인 EP6의 버퍼에 800 byte의 데이터를 써 넣은 후 EP2의 데이터를 날릴 준비가 되었다고 FX2에 알리기 위해서는 800 byte = 0x0320이므로

EPxBCH = 0x03;
SYNCDELAY;
EPxBCL = 0x20;
이렇게 쓰면 된다.

요 SYNCDELAY가 무었에 쓰는 물건인고 하니, 그 이름이 나타내듯이 SYNC를 맞추기 위한 DELAY를 주기 위한 매크로이다.

Synchronization(동기화)이란 것은 서로 다른 페이스로 동작하는 두 개의 페이스를 맞추기 위한 것이다.
이 경우에서 두 개는 각각 FX2의 8051 코어와 Endpoint FIFO의 컨트롤을 담당하는 로직이다.

아시다시피 FX2의 8051코어는 48MHz로 동작하고(물론 더 낮은 클럭으로 동작할 수도 있지만…), Endpoint FIFO의 컨트롤을 담당하는 로직은 USB 2.0의 전송속도 480Mbps ( 60 M byte/sec )를 달성할 수 있을 만큼 빠르게 동작해야 한다.
당삼 8051보다 허벌나게 빠르다.

두 로직의 동작속도가 이렇게 다르므로 이 두 로직간에 의사소통이 필요할 때에는(위의 경우처럼 FX2의 8051코어가 FIFO control 로직에 Endpoint FIFO의 데이터가 준비되었음을 알릴 때) 두 로직간의 SYNC를 맞추어 주어야 한다.
이 SYNC를 맞추는 작업을 하기 위해서 SYNCDELY라는 매크로를 쓰고 있는 것이다.

C:CypressUSBTargetIncfx2sdly.h에 이 매크로의 definition이 나와있다.
가서 한번 쓱 보기 바란다.
뭐 별거 없다.
적당한 시간 동안 아무일 말고 놀아라(nop)라고 정의되어 있다.

그럼 이 SYNCDELAY라는 매크로를 언제 사용해야 되느냐?
T.R.M. 15.14 Synchronization Delay 에 나와있다.

뭐시라고 씨부리는지 친절하게 해석해 보면,
1. 0xE600-0xE6FF번지 사이의 레지스터에 값을 쓴 다음, 0xE600-0xE6FF번지 사이의 레지스터에 값을 쓸 때.
2. 0xE600-0xE6FF번지 사이의 레지스터에 값을 쓴 다음 아래의 레지스터 중의 하나에 값을 쓸 때.

FIFORESET FIFOPINPOLAR
INPKTEND EPxBCH:L
EPxFIFOPFH:L EPxAUTOINLENH:L
EPxFIFOCFG EPxGPIFFLGSEL
PINFLAGSAB PINFLAGSCD
EPxFIFOIE EPxFIFOIRQ
GPIFIE GPIFIRQ
UDMACRCH:L GPIFADRH:L
GPIFTRIG EPxGPIFTRIG
OUTPKTEND REVCTL
GPIFTCB3 GPIFTCB2
GPIFTCB1 GPIFTCB0

그리고,
3. 위의 레지스터중의 하나에 값을 쓴 다음 0xE600-0xE6FF번지 사이의 레지스터의 값을 읽을 때.
라고 써있다.

(이론~ 왜 줄이 안맞는 걸까? 쓰읍~.)

0xE600-0xE6FF사이의 레지스터가 무엇 무엇이 있는가는 T.R.M. 15.5 General Configuration Registers 부터 15장의 끝까지를 한번 휘리릭 보시라.
clip_image001
요 부분에 주소가 나와 있다.

요 SYNCDELAY를 빼먹으면 FX2가 정확히 동작하는 것을 보장할 수 없다고 무시무시한 경고를 하고 있으므로 혹시나 나중에 실수 하지 않도록 위에 나열된 레지스트와 0xE600-0xE6FF사이의 레지스터의 이름만이라도 한번씩 더 들여다보자.

이제 TD_Init()을 함 설명해 볼꺼나?

주구장창 소스분석만 하는 지루한 강의 따라오시느라 수고가 많으시다.
쫌만 더 지둘리면 따끈따끈한 디바이스가 나올 것이니 그때까지는 지겨워도 좀 참고 가자.

이제하나 저제하나 어차피 한번은 보고 넘어가야 할 것들이니 말이다.
부품들은 이미 도착했고, PCB는 주말 전에 나올 예정이니, 다음주 월/화요일 쯤에는 받아볼 수 있으리라 예상한다.

TD_Init()을 보자.
여기가 앞으로 행자들이 초기화 코드를 넣어야 할 곳이다.

// set the CPU clock to 48MHz
CPUCS = ((CPUCS & ~bmCLKSPD) | bmCLKSPD1);

주석의 영어가 너무너무 쉽게 나온 것이 본좌가 해석하면 돌 날라올 것만 같다.
따라서 행자들이 T.R.M Page 15-13을 함 뒤벼보기 바란다.

IFCONFIG |= 0x40;
IFCONFIG 즉 Interface Configuration Register의 각 비트가 무엇을 설정하는지 하나씩 들여다 보자.
이 레지스터는 뭘 좀 해볼라 그러면 꼭 건드리고 넘어야 할 레지스터다.

Bit 0 & Bit 1( IFCFG1:0 )은 인터페이스 모드를 선택하기 위한 비트라고 나와있다.
IFCFG1:0가 00이면 Port A, B, D가 모두 그대로 I/O포트로 쓰이고, 10이면 GPIF Master Mode로 동작하고, 11이면 Slave FIFO Mode로 동작한다고 T.R.M의 Table 15-7.에 나와 있다.

GPIF가 무언고 하니 General Programmable Interface의 약자이다.
이해가 확 안 와닿지 않는가?
이해가 가시는 행자는 그만 하산해도 되겠다.

GPIF는 7개의 state(상태)를 가지는 state machine이다.
대학교에 관련학과 댕기시거나 댕기셨던 행자들은 기억 너머 어렴풋이 state machine이 뭔지 옛 생각이 날것이다.
기억이 안 난다고 그런 슬픈 눈으로 본좌를 보지 마시라.

모른척하시는 분들을 위해 간단히 설명하자면 Microprocessor를 베베꼬아 말한거이 바로 state machine이다.
어떤 조건에 의해 한 상태(state)에서 다른 상태(state)로 넘어가거나, 그냥 그 상태에 머물거나 하는 일을 하는 기계(machine)가 되겠다.

이 강좌는 컴퓨터기본구조 강좌나 논리회로 강좌가 아니므로 자세한 설명은 각개전투 하시라.

우옛든, GPIF를 아주 단순한 형태의 CPU라 보심되겠다.
아니. 8051이 있는데 왜 CPU가 하나 더 필요해? 하는 의문이 당근 드셔야 한다.
왜 그러냐 하면, 듀얼 CPU가 대세이기 때문이다.

가 아니고,
8051이 FX2의 최대 클럭인 48Mhz로 동작한다고 하더라도 High Speed USB의 대역폭을 데이터로 다 채우기에는 너무 느리기 때문이다.
개량된 FX2의 8051 core는 명령어를 처리하는데 4 clock을 소모한다.
(물론 더 느린 명령어도 몇 있지만 말이다.)

어떤 조건을 체크해서 그 조건이 만족하면 1 byte의 데이터를 FIFO에 쓴 다음 USB로 commit(날리는)하는 코드를 8051이 수행한다고 가정해 보자.

조건을 체크하는데 최소 하나의 명령어가 필요하겠고, 1 byte의 데이터를 FIFO에 쓰는데 또 하나의 명령어가 필요하고, 그 데이터를 USB로 commit하는데 또 하나의 명령어가 필요할 것이다.
(가장 짧은 시간이 걸리는 경우를 가정한 상황이다.)
따라서 한 바이트의 데이터를 날리는데 최소 3개의 명령어가 필요하므로 총 12 clock이 필요하다.
48MHz / 12 = 4 Mbyte/sec

어제 본좌가 보였듯이 High Speed USB의 대역폭은 60MByte / Sec이다.
(물론 오버헤드 빼고, 뭐 빼고 하다 보면 저 이론치가 다 나오는 것은 아니지만 말이다.)
8051이 눈썹이 휘날리게 데이터를 보내도, USB대역폭이 대충 15배 크다.
( 이 값은 본좌가 예를 들기 위해 어물쩍 만든 값이다. -.-; 인용하지 말자.)

USB 대역폭이 크면 뭐하나? CPU가 그걸 다 채울 능력이 안 되는데…
그래서 이런 느려빠진 8051을 대신해서 USB로 보낼 데이터를 FIFO에 채우거나, FIFO에 도착한 데이터를 FX2 외부로 내보낼 수 있는 재빠른 놈을 만들었는데, 그게 GPIF이다.

8051은 USB와 GPIF를 세팅하고 뒤로 살짝 빠져 놀고 있으면 실제 데이터를 내오고 가져오고 하는 일은 GPIF가 알아서 한다.
물론 GPIF를 안 쓰고, 8051이 다 할 수도 있다. 느려도 상관 없다면 말이다.
이 FX2 강좌의 마지막 부분에 이 GPIF를 이용하는 법을 알려 줄 터이다.

이걸로 일단 GPIF Master모드가 무언지 대충 알고 넘어가고,

다음은 FIFO Slave 모드 이다.
요건 또 뭐이냐 하면 외부에서 FIFO에 데이터를 넣고, 빼고, USB로 날리고 하는 등의 일을 해주는 어떤 놈이 FIFO를 노예처럼 부리는 경우를 말한다.
이 경우에도 8051은 초기 세팅만 하고 뒤로 빠져 논다.
8051. 알고 보면 디게 게으른 놈이다.

FIFO를 부리는 외부의 어떤 것은 Microprocessor나 DSP 일수도 있고, 또는 FIFO의 시그널을 읽고 컨트롤 신호를 줄 수 있는 State Machine이면 아무거나 좋다.
물론 USB 2.0의 대역폭을 최대한 활용하고자 한다면 빨라야 함은 말 할 것도 없고 말이다.

IFCONFIG의 0:1 bit을 설명했으니 그 다음 IFCONFIG의 bit 2, GSTATE를 알아보자.
이 넘은 Port E의 세 개의 핀을 디버깅용으로 GPIF가 어느 state에 있는지를 나타내는 용도로 쓰고자 할 때 세팅하는 비트이다.
우리가 쓰는 56pin FX2에는 Port E 자체가 없으므로 대략 무효.

Bit 3 ASYNC는
GPIF Master Mode나 FIFO Slave모드에서 (FX2) 외부와 데이터를 주고받을 때 Synchronous하게 할 것이냐 asynchronous하게 할 것이냐를 세팅하기 위한 bit이 되겠다.
거북이들을 위해 단순 무식하게 설명하자면, Clock의 Rising 또는 Falling Edge에 맞추어 데이터를 주고 받는 것을 Synchronous(씽크로너스)하다 라고 하고, 클럭 필요 없이, 그냥 컨트롤 신호 떨어질 때 데이터를 읽고 쓰는 것을 Asynchronous(어씽크로너스)하다 라고 들 한다.

Bit 4 IFCLKPOL
FX2의 핀중에 IFCLK이란 핀이 있다.
GPIF모드나 Slave FIFO모드로 동작할 때 이 핀으로 들어오는 외부 클럭에 맞춰 동작하도록 할 수도 있고, 아니면 IFCLK을 FX2 내부에서 생성해 쓸 수도 있다.
요렇게 IFCLK을 쓸 때 이 클럭을 반전시켜 쓸려면 이 비트를 세팅하면된다.
왜 반전 시켜 쓰느냐?
기냥 쓰면 안되느냐?
고 물으신다면 기냥 쓰셔도 됩니다. 되구요~.
반전이 필요한 경우는 T.R.M. Page 10-9에 설명되어 있으니 들여다 보도록 하세요~.

Bit 5 ~ Bit 7번 까지는
T.R.M Page 15-15 아래쪽 그림을 곁들여 설명을 휘리릭 보시면 이해하는데 아무 문제가 없을 꺼라 생각되어 이만 땡땡

이론~
그만 쓸려고 봤더니 오늘은 겨우 코드 두줄 설명했다.

clip_image001

본좌 오늘 내공이 소진되어 이제 그만.
clip_image002
이제 지겨운 코드 후비기도 다 끝나간다.
이틀만 더 하면 종치고, 본격적인 실전에 들어갈 수 있을 것 같다.

 

진도 나가기 전에 행자들에게 확인하고 싶은 게 있다.
Cypress에서 Development Kit을 다운받아 설치할 때, 같이 설치한 Keil Compiler를 가지고 우리가 지금 보고 있는 Bulkloop 예제가 컴파일 되는지 함 확인해 보시라.

같이 깔린 Trial Version Keil Compiler는 컴파일 가능한 코드의 용량에 제한이 있는 것으로 알고 있는데, 본좌는 Trial 버전으로 컴파일을 안 해봐서 되는지 안 되는지 잘 모르겠다.
만약 컴파일이 안 된다면, 어떡하냐고 본좌에게 물어도 소용없다.
본좌 해줄 말이 없으니, 가만히 눈을 들어 저 어둠 속을 조용히 응시할 수 밖에…

오늘은 Endpoint Config Register들이 어떻게 세팅되느냐를 먼저 살펴보자.
우선 Endpoint 1
EP1OUTCFG = 0xA0;
EP1INCFG = 0xA0;
T.R.M을 뒤적여 보면(Page 15-26 )
Endpoint 1을 Bulk 타입의 엔드포인트로 쓰겠다.
라고 세팅한 것을 알 수 있다.

옛날 옛적 한 옛날에 우리가 desc.a51파일의 Endpoint Descriptor를 뒤비고 있을 때, 엔드포인트 디스크립터를 살짜쿵 들여다 봤었다.
분명 거기에는 Endpoint 1에 대한 Descriptor가 없었다.
(Endpoint 2, 4, 6, 8 Desc. 만 있었다.)
어찌된 걸까?

이 예제를 쓴 넘이 돌았었나 보다. @.@
하나 확실한 것은 Endpoint Config Register에다가 VALID bit을 천만번 세팅하더라도 Endpoint Descriptor에 해당 디스크립터가 없다면 호스트는 결코 그 Endpoint로 데이터를 보내거나, 받지 않을 거라는 거다.

우리가 여기서 알고 넘어가야 할 것은 FX2의 Endpoint 1은 IN 버퍼와 OUT버퍼가 분리되어 있다는 것과 이 버퍼들은 오직 FX2의 8051 코어에서만 접근할 수 있다는 것이다.
Endpoint 0 ( Default Control Endpoint )는 양방향 버퍼를 가지고, 나머지 Endpoint( 2, 4, 6, 8)의 버퍼들은 외부에서 접근할 수 있는 단 방향 버퍼를 쓴다는 것이 다르다.
밑줄 쫙 치고 외울 필요는 없다.
펌웨어를 짜다 보면 저절로 체득 될 것이다.

EPxCFG ( x = 2, 4, 6, 8 )의 나머지 bit들은 행자들이 보면 열나 쉽게 알 수 있을 것이고, 본좌는 bit 3과 bit1:0만 설명하고 넘어가고자 한다.

clip_image002

( Page 1-23의 )
요 그림이 Endpoint Buffer가 어떻게 세팅될 수 있는지 보여주는 그림이닷.
앞으로 행자들이 뭘 좀 해볼려고 하면 이 그림부터 들여다보고 시작해야 할 꺼시다.
그러니 지금 모니터가 뚫어지도록 보아주자.

본좌는 EP2만 예로 들어 설명한다.
왼짝부터 오른짝으로 EP2가 어찌 설정되는 거인지 자근자근 보면…
Buffer size 512 byte, Double buffering
Buffer size 512 byte, Quad buffering
Buffer size 1024 byte, Double buffering
Buffer size 512 byte, Triple buffering
Buffer size 1024 byte, Triple buffering
Buffer size 1024 byte, Quad buffering
요러케 가지가지로 세팅할 수 있다.
맘에 드는 걸로 골라 잡으시라.

Buffering depth가 깊을수록, bursty Traffic에 효율적으로 대응할 수 있을 것이고, 버퍼 사이즈가 클수록 한번에 보낼 수 있는 데이터 양이 늘어날 것이다.
버뜨, 과유불급이라. 벌크모드로 전송하면서 1024 byte 사이즈의 버퍼를 사용할 필요는 없을 것이다.
왜냐면 High Speed USB에서 Bulk Mode의 최대 패킷 사이즈는 512byte이기 때문이다.
Bulk Mode에서 1024 byte 사이즈의 버퍼를 사용해도 상관은 없지만서두.
512 byte의 데이터가 채워지고 남는 521byte의 공간은 고스란히 낭비될 거시다.
그러고 싶은거야? 그런거야?

고 다음 코드

SYNCDELAY;
EP2BCL = 0x80;
SYNCDELAY;
EP2BCL = 0x80;
SYNCDELAY;
EP4BCL = 0x80;
SYNCDELAY;
EP4BCL = 0x80;

EP2BCL에 아무 숫자나( 0x80 ) 두 번 써주고,
EP4BCL에 아무 숫자나( 0x80 ) 또 두 번 써주고 있다.

왠지 낯이 익지 않나?
Bingo!.
clip_image004
몇 회 전에 봤던 거시다.
IN Endpoint의 버퍼를 장전하기 위한 코드가 되겠다.
바로 앞에서 EP2와 EP4가 Double Buffering을 사용한다고 세팅한 것은 다 아시겠고, 따라서 각각 두 개의 버퍼쌍을 가지므로
(EPxBCL = 0x80; ) X 2
해준 거시다.

만약 Quad Buffering을 사용한다면 당근빠따 (EPxBCL = 0x80; ) X 4 해죠야 한다.

왜 초기화 루틴에서 이딴짓을 해 주냐 하면
그냥.
clip_image005
물론 안 해줘도 된다.
대신 뒷감당은 행자들이 해 줘라. ㅋㅋㅋ

AUTOPTRSETUP |= 0x01;
T.R.M.Page 15-8에 가서 휙 둘러보고 오시라.
따로 설명은 안 하겠다.
Autopointer를 사용할 때 Side effect에 대한 것도 잘 읽어보고 말이다.

 

오늘은 DR_VendorCmnd()를 분석해볼 차례다.
드디어 코드 분석의 마지막 장이다.

앞서 보았듯이 DR_VendorCmnd()는 fw.c의 SetupCommand()에서 호출되는 함수이다.
FrameWork님께서 우리를 대신하여 Default Control Endpoint ( Endpoint 0 )로 내려오는 명령들을 다 처리해 주시는데, FrameWork님께서 미처 처리하지 못하시는 명령은 우리보고 처리하라고 이 DR_VendorCmnd() 함수를 마련해 주신 것이다.

다들 아시겠지만 Default Control Endpoint는 USB에 관련된 잡일들( Enumeration, Set/Get Feature, …)을 처리하기 위해 주로 호스트에서 사용하는 Endpoint이다.

그런데 이 Endpoint를 호스트(정확히 말하면 USB Host Controller)만 쓸 수 있는 것이 아니라, 어플리케이션이나 드라이버에서 내려오는 Vender Specific한 명령을 처리하는데도 유용하다.

왜냐면 명령을 주고 그 결과를 바로 확인할 수 있걸랑요.
fw.c 뒤비기- 투의 마지막 그림을 기억 저편으로부터 소환하시라.
Setup Stage에서 8 Byte짜리(더 잛아도 되는)명령을 host로부터 받고, Data Stage에서 Payload Data에다가 호스트가 원하는 데이터를 실어 보낼 수 있다.
Endpoint 0가 아닌 딴 Endpoint들은 단 방향으로만 동작하기 때문에 요런 짓거리를 하려면 IN과 OUT 두개의 Endpoint를 써야 한다.
( Endpoint 1도 사실 단방향이다. 단지 FX2에서는 IN/OUT 버퍼가 따로 분리되어 있을 뿐.)

간단한 데이터를 주고 받는데는 이 Endpoint 0만 써도 충분하다.
다만 이 Endpoint는 호스트 컨트롤러로부터의 명령을 재깍재깍 받아 처리할 수 있어야 하므로, 코딩하는데 좀 더 주의를 기울일 필요가 있다.
Stall 상태에 빠지지 않도록…(USB 디바이스가 먹통이 되지 않도록..)

Bulkloop 예제에서는 DR_VendorCmnd()에서 VR_NAKALL_ON/OFF 명령어를 처리하고 있는데, 이 명령어는 호스트로부터 EP 2,4,6,8로 뭔 요구가 오더라도 생까거나/응답하라 라는 명령이다.
이게 왜 필요한지는 잘 모르겠지만 말이다.

NAK은 USB Spec.의 용어정의에 보면 Handshake packet indicating a negative acknowledgment. 라고 나와 있다.

그 반대말인 ACK은 Handshake packet indicating a positive acknowledgment. 라고 나와 있고 말이다.

이 말들을 이해하려면 Handshake packet이 어떤 건지를 먼저 알아야 하는데, 다시 fw.c 뒤비기- 투두번째 그림을 보시라.

호스트가 뭐라 그러면, 디바이스가 대답하고 디바이스가 뭐라 그러면 호스트가 대답한다.
요런 과정을 handshake라고 한다.

요렇게 주거리 받거니 대화하는 게 왜 필요할까?
예를 들어 간단히 설명하자면,

호스트가 벌크 모드로 데이터를 디바이스로 보낼 때는 ( OUT Transfer )
OUT token과 이에 연달아 DATA Packet를 디바이스로 보낸다.
디바이스의 FIFO가 꽉 차서 더 이상 데이터를 받아들일 수 없을 때는 디바이스는 호스트에게 Handshake Packet으로 NAK을 보내게 되고, 그럼 호스트는 아! 디바이스가 아직 준비가 안되어 있구나 하고 처음부터 다시 시도를 하게 된다.
만약 디바이스가 데이터를 받아들였다면 ACK을 호스트로 보내고, 그럼 호스트는 다음 데이터를 디바이스로 보낸다.

반대로
디바이스가 벌크 모드로 데이터를 호스트로 보내고자 할 때에는( IN Transfer )
호스트가 IN token을 디바이스로 보낼 때 디바이스에서 보낼 데이터가 다 준비되어 있다면, DATA Packet을 호스트로 보내고, 호스트는 데이터를 잘 받았다고 ACK을 디바이스로 날린다.
만일 디바이스에서 아직 데이터가 준비가 안되어 있다면 호스트로 NAK을 날리고, 그럼 호스트는 일련을 절차를 처음부터 다시 시작한다.

clip_image001[4]

그림은 위 설명을 간단히 표현한 것이다.

호스트와 디바이스가 왜 대화를 하며, 어떤 방식으로 하는지에 대해 쬐~에끔 감을 잡으셨으리라 믿는다.
더불어 VR_NAKALL_ON/OFF 명령어가 무얼 의미하는지도 아셨으리라 믿는다.
언제나 그러하듯이 T.R.M을 뒤비는 거 잊지 마시고.

사실 본좌가 열쒸미 설명한 위의 절차는 FX2가 자동으로 처리하는 절차이기 때문에 우리가 관여할 필요는 없다.
하지만 배워서 남 주나? 알아두면 다 피가 되고 살이 된다.

오늘 PCB가 도착하면 땜질해서 사진 올리려고 했는데, 낼(토) 도착한다 그런다.
모래(일) 땜질이 끝나면 올려 보겠다.
본좌는 오늘 취권 수련이 있어 그만…

답글 남기기

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

WordPress.com 로고

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

Twitter 사진

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

Facebook 사진

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

Google+ photo

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

%s에 연결하는 중

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