개요: 이 애플리케이션 노트에서는 호스트 USB 컨트롤러처럼 동작하는 MAX3421E-Revision 1 및 2에 대해 살펴본다. MAX3421E는 이중 버퍼 Send FIFO를 사용하여 USB 주변기기에 데이터를 전송한다. 부품의 Rev 1 및 2에서 OUT 전송을 프로그래밍할 때에는 특별한 주의가 요구된다. 이 애플리케이션 노트에서는 전송 메커니즘에 대해 설명하고, 단일 버퍼 및 이중 버퍼 OUT 전송을 프로그래밍하는 예제 코드가 제공된다.
머리말
MAX3421E는 USB 호스트/주변기기 컨트롤러로, 호스트처럼 동작할 때 이중 버퍼 전송 FIFO를 사용하여 데이터를 USB 주변기기에 전송한다. 이 FIFO 쌍은 다음 두 레지스터에 의해 제어된다.
R2: SNDFIFO, FIFO 데이터 레지스터
R7: SNDBC, 바이트 카운트 레지스터
마이크로컨트롤러는 반복적으로 SNDFIFO 레지스터 R2에 써서 FIFO에 최대 64 데이터 바이트를 로드한다. 그런 다음 마이크로컨트롤러는 SNDBC 레지스터에 쓰는데, 이것은 다음 세 가지 동작을 수행한다.
MAX3421E SIE(직렬 인터페이스 엔진)에 FIFO에서 전송할 바이트 수를 지시한다.
USB 전송을 위해 SNDFIFO 및 SNDBC 레지스터를 USB 로직에 연결한다.
SNDBAVIRQ 인터럽트 플래그를 소거한다. uC 로딩을 위해 두 번째 FIFO를 사용할 수 있는 경우, SNDBAVIRQ는 즉시 재어서트(re-assert)된다.
그림 1. SNDFIFO 및 SNDBC 레지스터는 FIFO와 바이트 카운트 레지스터의 "핑퐁 (ping-pong)" 쌍을 로드한다.
그림 1에서 보듯이 첫 번째 FIFO 바이트는 물리적 FIFO로부터 발생되지 않는다. 대신 첫 번째 FIFO 바이트는 2개의 내부 클록 영역(하나는 마이크로컨트롤러용이고 다른 하나는 USB 로직용)을 조정하는 데 사용되는 동기 레지스터로부터 발생한다.
단일 버퍼 전송 프로그래밍
대역폭이 중요하지 않은 간단한 전송에서 패킷을 전송하는 펌웨어는 비교적 간단하다. 단계는 다음과 같다.
SNDFIFO를 로드한다.
SNDBC 레지스터를 로드한다.
HXFR 레지스터에 OUT PID 및 엔드 포인트 번호를 써서 전송을 실행한다.
HXFRDNIRQ(호스트 전송 완료 인터럽트 요청)를 기다린다.
HRSL 레지스터로부터 전송 결과 코드를 읽는다.
ACK이면 단계가 완료된다.
NAK이면 다음 단계로 간다.
다음과 같이 첫 번째 FIFO 바이트를 다시 로드하고 전송을 재실행한다
SNDBC = 0을 쓴다. 이 값은 OUT 데이터가 포함된 FIFO를 마이크로컨트롤러의 제어로 다시 전환하는 더미 값이다.
첫 번째 FIFO 바이트만 SNDFIFO 레지스터에 다시 쓴다. 이 바이트는 그림 1의 SYNC REGISTER에 쓰여진다.
SNDBC 레지스터에 재전송된 패킷에 대해 정확한 바이트 카운트를 다시 쓴다. 이렇게 하면 USB 재전송을 위해 FIFO는 다시 USB측으로 전환된다.
단계 3으로 가서 패킷을 재실행한다.
단계 4는 이 절차를 단일 버퍼 전송으로 만든다. 이 시퀀스는 단계 4가 완료되기를 기다리기 때문에 마이크로컨트롤러는 첫 번째 FIFO가 MAX3421E SNDFIFO로부터 USB 주변기기로 이동하는 동안 두 번째 FIFO를 로드하지 않는다.
단계 6은 MAX3421E-Revision 1에서 모든 반복되는 USB OUT 전송에 대해 동기 플립 플롭(flip-flop)을 다시 초기화해야 하기 때문에 필요하다.
이중 버퍼 전송 프로그래밍
이중 버퍼링은 다중 64바이트 패킷으로 구성된 긴 데이터 레코드를 USB 호스트로부터 USB 주변기기로 전송할 때 성능을 향상시킨다. 이와 같은 성능 향상은 하나의 SNDFIFO가 USB에 연결되어 패킷을 전송하는 동안 마이크로컨트롤러가 동시에 다른 SNDFIFO에 다음 64바이트 데이터 패킷을 로드할 수 있기 때문에 가능하다. 그러나 프로그램 단계는 단일 버퍼 전송에 비해 조금 복잡하다. 주어진 시간에 프로그램은 FIFO를 플립하고(즉, NAK가 발생될 때), 첫 번째 바이트를 재로드하며, 그리고 바이트 카운트 레지스터를 재로드하여 첫 번째 바이트를 다시 플립해야 하므로 2개의 데이터 버퍼를 트래킹해야 한다. 그림 2는 가능한 하나의 단계 시퀀스를 보여준다. 이 애플리케이션 노트의 끝에 나와 있는 예제 함수 Send_OUT_Record()는 그림 2 플로우차트를 구현한 것이다.
그림 2. 이중 버퍼 OUT 패킷
오른쪽 루프(단계 1 ~ 4)는 FIFO에 USB 데이터를 로드하며, 단계 5부터 시작하는 왼쪽 루프는 USB를 통해 FIFO를 발송하고 NAK 재시도를 처리한다. "First Pass?" 검사(단계 3)는 호스트 컨트롤러가 첫 번째 SNDFIFO를 로드한 후 즉시 USB 전송이 실행되도록 보장한다. 플로우차트는 전송보다 FIFO 로드에 우선순위를 주도록 정렬되어 이중 버퍼 성능을 유지한다.
플로우차트는 다음 3가지 경우를 고려할 때 가장 잘 이해된다.
1개 패킷 전송 (1 ~ 64바이트의 전체 페이로드)
2개 패킷 전송 (65 ~ 128바이트의 전체 페이로드)
3개 이상 패킷 전송 (129바이트 이상 전체 페이로드)
이 애플리케이션 노트의 끝에 나와 있는 Send_OUT_Record() 함수를 보면 호출 프로그램은 다음과 같은 4개의 파라미터를 이 함수에 전달한다.
ep, OUT 패킷을 발송해야 하는 엔드 포인트 번호
*pBUF, 바이트 데이터로 가득찬 버퍼를 가리키는 포인터
TBC, 전송할 전체 바이트 카운트 (버퍼의 바이트 수)
NAKLimit, 포기 및 리턴 전에 주변기기로부터 수락하는 연속 NAK 응답 수
함수는 마지막으로 전송된 패킷에서 읽혀진 MAX3421E 호스트 결과 코드 레지스터(HRSL)의 값을 리턴한다. 이 값은 전송이 성공적이면 "ACK"를 표시하고 NAK 제한이 초과되면 "NAK"을, 또 기타 문제 발생 시 에러 코드를 표시한다.
1개 OUT 패킷 전송
루프는 전송할 64 이하 바이트와 함께 단계 1에 들어간다. 단계 1 결과가 참이므로 SPI 마스터는 SNDFIFO를 채운다. 함수는 단계 10에서 BC와 FB가 필요한 경우를 대비하여, 즉 주변기기가 OUT 전송을 "NAK"하는 경우를 위해 바이트 카운트(BC)와 SNDFIFO의 첫 번째 바이트(FB)를 저장한다. 이것은 레코드의 첫 번째 전송이므로 함수는 단계 4에서 OUT 전송을 실행한다. 단계 1로 다시 돌아가면 전송할 바이트가 없으므로 함수는 단계 5에서 전송 완료를 기다리고 단계 6에서 소자 응답을 테스트한다. 이 지점에서 ACK이면 단계 7에서는 더 이상 전송할 데이터가 없으므로 함수는 단계 11에서 리턴한다. 그러나 NAK이면 단계 9에서 NAK 제한이 테스트되고 초과되지 않은 경우에는 패킷이 단계 10과 4에서 재전송된다.
2개 OUT 패킷 전송
함수는 전송할 65 ~ 128바이트와 함께 다시 단계 1에 들어간다. 앞에서와 마찬가지로 함수는 첫 번째 SNDFIFO를 채우고 단계 2 ~ 4에서 즉시 전송을 실행한다. 단계 1로 다시 돌아가면 함수는 전송할 바이트가 더 있음을 발견하고 다시 단계 2에서 SNDFIFO를 채운다. 이것은 첫 번째 전송이 아니므로 함수는 단계 4에서 아직 다음 패킷을 실행하지 않는다. 단계 1로 다시 돌아가서 첫 번째 SNDFIFO는 두 번째 패킷이 두 번째 SNDFIFO에 남아 전송을 준비하는 동안 USB를 통해 첫 번째 패킷을 이동시킨다. 단계 2에서 함수는 실제로 2세트의 BC/FB 날짜(바이트 카운트와 FIFO 첫 번째 바이트, 한 세트는 현재 전송 중인 SNDFIFO용이고 다른 한 세트는 두 번째 SNDFIFO에서 대기하는 보류 데이터용)를 저장한다. 전송할 데이터가 더 이상 없으므로(또한 두 SNDFIFO가 모두 데이터로 가득 찼으므로) 제어는 단계 5로 넘어간다.
함수는 단계 5에서 첫 번째 패킷이 전송되기를 기다린 다음 단계 6에서 전송 결과를 검사한다. 앞에서와 마찬가지로 패킷이 "NAK"되면 함수는 단계 10에서 FIFO와 바이트 카운트 레지스터에 보류 중인 BC/FB 값을 재로드하고 단계 4에서 패킷을 재전송하고 (단계 1을 통과하여) 단계 5로 다시 돌아가 완료를 기다린다. 단계 5-6-9-10-4-5 루프는 ACK가 수신되거나 NAK 제한에 도달할 때까지 모든 NAK에 대해 계속된다.
주변기기가 OUT 전송을 ACK하면 함수는 단계 7에서 종료를 테스트한다. 완료되지 않을 경우 함수는 단계 8로 가서 두 번째 SNDFIFO에서 대기하고 있는 다음 패킷을 전송한다. 단계 8에서 함수는 다음과 같은 여러 작업을 수행한다. 단계 10에서 필요한 경우에 대비하여 "보류 중인" BC/FC 값을 "현재" BC/FC 변수에 복사하고, 현재 바이트 카운트를 로드하여 두 번째 SNDFIFO를 USB로 전환하고, 단계 4에서 OUT 패킷을 실행한다. 다음으로 루프는 앞에서와 마찬가지로 모든 NAK에 대해 계속 진행한다. ACK 응답이 수신되면 함수는 단계 7 ~ 11에서 종료한다.
3개 이상 OUT 패킷 전송
이 함수는 기본적으로 앞의 2패킷 절차를 따르지만 한 패킷은 진행 중이고 또 다른 패킷은 (두 번째 SNDFIFO에서 대기하면서) 보류 중인 지점까지만 같다. 그 다음에 함수는 단계 4에서 다른 패킷을 실행할 때마다 단계 1에서 SNDFIFO에 로드할 더 많은 데이터를 검사한다. 전송할 데이터가 더 많기 때문에 단계 2에 도달하기 위해 프로그램 흐름에 FIFO를 사용해야 한다. 단계 1에서 3까지 "SNDFIFO 로드" 루프는 단계 5...4의 "FIFO 전송" 루프보다 우선권을 가지기 때문에 데이터가 동시에 USB를 통해 이동되는 동안 데이터는 언제나 SNDFIFO에 로드됨으로써 이중 버퍼링이 수행된다.
성능
그림 3. USB 패킷을 로드하고 전송하는 MAX3421E의 스코프 트레이스
그림 3은 Send_OUT_Record() 함수를 사용하여 64바이트 OUT 패킷의 로드와 전송 간에 겹쳐지는 부분을 보여준다. 레코드 크기는 512바이트로, 64바이트 패킷 8개로 구성된다. 이 트레이스에서 MAX3421E에 연결된 주변기기는 NAK를 발생시키지 않으므로 최대 전송 대역폭을 측정할 수 있으며, 따라서 한 번의 스코프 캡처에 전체 전송이 다 들어간다.
상단의 2개 트레이스는 SPI 마스터(SPI 하드웨어가 탑재된 ARM7)가 MAX3421E SNDFIFO에 64바이트 데이터를 로드할 때 SPI 포트 동작을 보여준다. SNDFIFO 로드가 발생할 때마다 SS# (Slave Select) 라인은 로우가 되고 SCK(Serial Clock)는 65 x 8번 펄싱하는데, 한 번은 명령 바이트를 로드하기 위해 실행되고, 그런 다음 8번은 64데이터 바이트 각각에 대해 실행된다. 첫 번째 SNDFIFO 로드는 이 트레이스가 시작되기 전에 완료되므로 그림 3은 7개의 SNDFIFO 로드를 보여준다.
세 번째 트레이스는 USB D+ 신호로서 USB를 통해 MAX3421E 호스트로부터 주변기기로 이동하는 64바이트 OUT 패킷을 보여준다. 하단 트레이스는 전송을 시작하기 위해 MAX3421E HXFR 레지스터를 로드하는 ARM7을 나타내는 펄스이다.
USB 패킷이 시작되면 ARM7은 곧바로 두 번째 SNDFIFO를 로드하기 시작하는데, 이와 동시에 USB 패킷은 버스를 통해 이동한다. 이러한 이중 버퍼링은 전송 대역폭을 향상시킨다.
대역폭 측정
그림 4. 그림 3 전송에 대한 CATC (LeCroy) 트레이스 및 버스 분석
그림 4에서 보듯이 Send_OUT_Record() 함수는 6.76Mbps에서 512바이트 레코드를 전송하였다. 참고로 UHCI USB 컨트롤러(USB만 부착됨)를 사용하는 PC에 연결된 풀 스피드 USB thumb 드라이브는 5.94Mbps에서 512바이트 레코드를 전송했다.
구현 시 참고 사항
아래 예제 코드는 Maxim USB Laboratory를 사용하여 작성 및 테스트되었으며, 자세한 내용은 애플리케이션 노트 3936에 나와있다. 이 연구용 킷에는 MAX3421E 및 MAX3420E USB 주변기기 컨트롤러가 모두 포함되어 있다. 펌웨어를 테스트하기 위해 USB 케이블을 사용하여 MAX3421E 호스트를 MAX3420E 주변기기에 연결하였으며 "*1*"로 표시된 commented-out 명령을 사용하여 MAX3420E에 OUT 전송을 수락하도록 (NAK 없음) 지시했다.
참고: MAX3421E의 향후 버전에서는 FIFO 재로드 문제가 없을 것이다. MAX3421E에서는 간단히 HXFR 레지스터를 재로드하면 OUT 패킷을 재실행하게 되므로 Send_OUT_Record() 함수는 더 이상 필요하지 않을 것이다.
Send_OUT_Record() 예제 함수
// *******************************************************************************
// Send an OUT record to end point 'ep'.
// pBuf points to the byte buffer; TBC is total byte count.
// NAKLimit is the number of NAKs to accept before returning.
//
// Returns HRSL code (0 for success, 4 for NAK limit exceeded, HRSL for problems)
// *******************************************************************************
//
BYTE Send_OUT_Record(BYTE ep, BYTE *pBuf, WORD TBC, WORD NAKLimit)
{
static WORD NAKct,rb; // Buf index, NAK counter, remaining bytes to send
WORD bytes2send; // temp
BYTE Available_Buffers; // Remaining buffers count (0-2)
BYTE FI_FB; // Temporary FIFO first byte
static BYTE CurrentBC; // Byte count for currently-sending FIFO
static BYTE CurrentFB; // First FIFO byte for currently-sending FIFO
static BYTE PendingBC; // Byte count for next 64 byte packet scheduled for sending
static BYTE PendingFB; // First FIFO byte for next 64 byte packet scheduled for sending
BYTE dum;
BYTE Transfer_In_Progress,FirstPass; // flags
//
NAKct=0;
Available_Buffers = 2;
rb = TBC; // initial remaining bytes = total byte count
FirstPass = 1;
//
do
{
while((rb!=0)&&(Available_Buffers!=0))
// WHILE there are more bytes to load and a buffer is available
{
// Pwreg(rEPIRQ,bmOUT1DAVIRQ);// *1* enable the 3420 for another OUT transfer
FI_FB = *pBuf; // Save the first byte of the 64 byte packet
bytes2send = (rb >= 64) ? 64: rb; // Lower of 64 bytes and remaining bytes
rb -= bytes2send; // Adjust 'remaining bytes'
Hwritebytes(rSNDFIFO,64,pBuf);
pBuf += 64; // Advance the buffer pointer to the next 64-byte chunk
Available_Buffers -= 1 // One fewer buffer is now available
//
if(Available_Buffers==1) // Only one has been loaded
{
CurrentBC = bytes2send;
CurrentFB = FI_FB;
}
else // Available_Buffers must be 0, both loaded.
{
PendingBC = bytes2send;
PendingFB = FI_FB;
}
//
if(FirstPass)
{
FirstPass = 0;
Hwreg(rSNDBC,CurrentBC); // Load the byte count
L7_ON // Light 7 is used as scope pulse
Hwreg(rHIRQ,bmHXFRDNIRQ); // Clear the IRQ
Hwreg(rHXFR,(tokOUT | ep)); // Launch an OUT1 transfer
L7_OFF
}
} // While there are bytes to load and there is space for them
//
do // While a transfer is in progress (not yet ACK'd)
{
// while((Hrreg(rHRSL) & 0x0F) == hrBUSY) ; // Hang here until current packet completes
while((Hrreg(rHIRQ) & bmHXFRDNIRQ) != bmHXFRDNIRQ) ;
dum = Hrreg(rHRSL) & 0x0F; // Get transfer result
if (dum == hrNAK)
{
Transfer_In_Progress = 1;
NAKct += 1;
if (NAKct == NAKLimit)
return(hrNAK);
else
{
Hwreg(rSNDBC,0); // Flip FIFOs
Hwreg(rSNDFIFO,CurrentFB);
Hwreg(rSNDBC,CurrentBC); // Flip FIFOs back
L7_ON // Scope pulse
Hwreg(rHIRQ,bmHXFRDNIRQ); // Clear the IRQ
Hwreg(rHXFR,(tokOUT | ep)); // Launch an OUT1 transfer
L7_OFF
}
}
else if (dum == hrACK)
{
Available_Buffers += 1;
NAKct = 0;
Transfer_In_Progress = 0; // Finished this transfer
if (Available_Buffers != 2) // Still some data to send
{
CurrentBC = PendingBC;
CurrentFB = PendingFB;
Hwreg(rSNDBC,CurrentBC);
L7_ON // Scope pulse
Hwreg(rHIRQ,bmHXFRDNIRQ); // Clear the IRQ
Hwreg(rHXFR,(tokOUT | ep)); // Launch an OUT1 transfer
L7_OFF
}
}
else return(dum);
}
while(Transfer_In_Progress);
}
while(Available_Buffers!=2); // Go until both buffers are available (have been sent)
return(0);
}
의견을 보내주세요! 위 내용이 도움이 되셨나요? 여러분의 의견을 기다립니다 — Maxim은 보내주신 정정이나 제안사항을 반영하고 있습니다.
이 페이지를 평가하고 의견을 보내주십시오.