티스토리 뷰

Hardware

fw.c 파일 디비기

알 수 없는 사용자 2008. 3. 26. 09:12

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

 

일단 우리가 지금 분석하고 있는 bulkloop 예제가 어떤 일을 수행하는 펌웨어인지부터 까보자.

desc.a51파일의 Endpoint Descriptor 들을 자세히 들여다본 행자들은 이미 알고 있겠지만, Bulkloop는 2개의 bulk OUT endpoint와 2개의 bulk IN endpoint를 가진다.
Bulk OUT endpoint는 endpoint 2, 4이고, Bulk IN endpoint는 endpoint 6, 8이다.

Bulkloop는 endpoint 2로 들어오는 데이터를 버퍼(FIFO)에 저장했다가 호스트가 endpoint 6으로 데이터를 요구하면 그 저장된 데이터를 보낸다.
마찬가지로 endpoint 4는 endpoint 8과 연계되어 있다.
뭐 실제적으로는 별 쓸모 없는 열라 간단한 예제다.
간단하니 분석하기 좋고, 쓸모 없는걸 쓸모 있는 걸로 바꿀 거니 보람 있고, 1타2피 되겄다.

Dsscr.a51에 이어 분석할 파일은 fw.c 되겠다.
파일 이름을 보라.
Fw가 뭐의 약자 같은가?
Firmware가 아니라, Frame Work의 약자다.
Frame Work, 이름이 암시하듯이 펌웨어의 골격이다.
이 말을 뒤집으면 자질구레한 일들은 지가 알아서 처리할 테니 중요한 일에만 집중하라는 거다.
얼마나 고마운가?

따라서, 우리가 이 파일에 손댈 일은 별로 없을 것이다.
그래도, 한번 훓고 넘어가자.
아마 이 파일에 관심을 가지게 될 때는 뭔가 안 풀리고 있을 때일 터이니 유비무환 이런 마음가짐으로 말이다.

이 파일 안의 주요 함수는  main(), SetupCommand() 이 두개이다.
파일 꽁다리에 인터럽트 서비스 루틴이 뽀나스로 끼워져 있다.

자 먼저 메인으로 들어가 보자.

변수 선언부와 초기화부를 지나면 TD_Init(); 이라고 나온다.
찾았는가?
요기가 우리 장치를 초기화하는 코드을 넣으라고 FrameWork님께서 마련해 주신 부분이다.
나중에 bulkloop.c를 볼 때 자세히 뒤비자.

그 다음 뭐라뭐라 주석이 달린 부분부터 EZUSB_IRQ_ENABLE(); 바로 앞까지가 한 뭉테기 이다.
주석이 뭐라고 씨부리는지 함 보면.

descriptor table은 on-part메모리에 있어야 하는데, 그렇지 않는 경우에는 외부 메모리로부터 On-part메모리로 옮겨와야 한다는 얘기다.
(외부 메모리가 boot load에 사용되는 serial EEPROM을 말하는 것은 아니다.)
(128핀의 FX2만 외부 메모리를 붙일 수 있다.)

우리의 경우에는 descriptor가 항상 FX2의 내부 RAM에 위치하므로 if문은 전부 지워버려도 상관 없다.
나중에 펌웨어를 짜다가 코드공간이 부족하면 이 부분을 과감하게 지워주자.

If문 안에 계산하는 과정은 흥미 있으신 분들만 따라가 보라.
근데 아마 FX2로 펌웨어 짜는 동안 이 부분 건드릴 일은 없을 것이다.

여차저차 사연이 길었다만 주석이 달린 부분부터 EZUSB_IRQ_ENABLE(); 바로 앞까지 이 부분이 하는 일은 우리가 앞서 열심히 디빈 각 Descriptor 테이블의 주소를
pDeviceDscr,
pDeviceQualDscr,
pHighSpeedConfigDscr,
pFullSpeedConfigDscr,
pStringDscr,
이 전역변수들에 담는 것이다.

이 변수들은 바로 다음에 분석할 SetupCommand() 함수에서 쓰이게 된다.

아래 두 줄
EZUSB_IRQ_ENABLE();
EZUSB_ENABLE_RSMIRQ();
은 C:CypressUSBTargetIncFx2.h에 선언된 매크로이다.
한번 가서 찾아보시라.
그리고 그 매크로가 세팅하는 레지스터에 대해서도 Technical Reference Manual(T.R.M.)을 읽어보시길 강추한다.
본좌. 이것저것 다 설명하기 귀찮아서 그런 건 절대 아니고 다 애정이 있어서 그런 거다. -.-;
(T.R.M은 C:CypressUSBDocFX2에 있다.)

혹시 EUSB가 어디 선언되어 있어요? 라고 물으신다면 같은 폴더안의 fx2regs.h를 보시라.

다음
INTSETUP |= (bmAV2EN | bmAV4EN);
USBIE |= bmSUDAV | bmSUTOK | bmSUSP | bmURES | bmHSGRANT;
EA = 1;
요 세 줄도 마찬가지로 T.R.M.에서 무얼 지시하고 있는 건지 함 찾아보시라.
꼭이다. (Adobe Reader에서 검색(Find) 기능을 잘 활용하시라.)

INTSETUP 레지스터를 찾아 설명을 읽다 보면 autovectoring이란 단어가 나오는데, 뭔고 하니 USB 인터럽트가 발생하면 어떤 이유로 인터럽트가 발생했는지에 따라 해당하는 인터럽트 처리루틴으로 자동 분기하는 기능이다.
우리는 요기에 대해 자세히 알 필요가 없다.
왜냐면 FrameWork님께서 다 알아서 해주시기 때문이다.
Autovectoring에 대해 자세히 알고 싶은 행자는 T.R.M Page 4-15를 참조하시라.

계속 fw.c를 디벼보자.

#ifndef NO_RENUM
if(!(USBCS & bmRENUM))
{
    EZUSB_Discon(TRUE);
}
#endif
USBCS &=~bmDISCON;
CKCON = (CKCON&(~bmSTRETCH)) | FW_STRETCH_VALUE;

코드를 한줄 한줄 따라가며 몽조리 설명하기에는 본좌 너무 게으르고, 강좌가 너무 늘어질 우려가 있기에 앞으로는 중요한 맥만 짚고, 코드 각각 대한 설명은 중요한 부분이 아니면 행자들의 몫으로 남기고 넘어 가겠다.

코드에 달린 주석, 그리고 바로 전 강좌에서 알려준 헤더파일과 Technician Reference Manual(T.R.M)만 있으면 위 코드가 무엇을 의미하는지는 금방 찾아 볼 수 있을 것이다.
(행자들도 귀찮다면 그냥 넘어가도 좋지만, 우리의 바이블 T.R.M.에 좀 더 친숙해 보자는 의미에서 함 찾아보시라.)

이 부분은 Renumeration에 대한 설명으로 대신하고자 한다.
Renumeration은 Re와 Enumeration의 합성어이다.
다시 이뉴머레이션 한다는 말쌈이닷.
Enumeration은 장치가 USB 포트에 끼워졌을 때, 그 장치를 인식하고 동작시키기 위해 일어나는 일련의 과정이다.
(http://www.lvr.com/usbcenum.htm)

따라서 이 Enumeration을 다시 하려고 한다면 (쉽게 생각해서) USB 디바이스를 뺐다가 다시 꼽으면 될 것이다.
이것처럼 Renumeration은 호스트가 보기에는 마치 디바이스가 포트에서 제거되었다가 다시 꽂아진 것처럼 보이게 하는 FX2의 기능이다.

그럼 왜 이런 기능을 넣었느냐?
펌웨어를 USB를 통해 다운받아서 동작하기 위함이다.

쪼~ 앞의 강의에서 언급했듯이 펌웨어를 담은 EEPROM 없이 우리가 만들 FX2 보드를 포트에 끼워도 장치관리자에서 인식이 된다.

Array

장치관리자에서 인식되는 이 디폴트 디바이스는 우리가 만든 펌웨어를 다운로드 받아 FX2의 RAM에 저장한 후에 Renumeration을 행한다.
그럼, USB 호스트가 보기에는 디폴트 디바이스가 USB 포트에서 제거되고, 우리가 만든 디바이스가 새로 꽂아진 것처럼 보이는 것이다.

자세한 사항은 T.R.M. Chapter3. Enumeration and Renumeration을 참조하시라.

펌웨어를 USB를 통해 다운로드하는 방법은 두가지가 있는데, 하나는 Develpoment Tool을 설치할 때 같이 설치된 EZ-USB Control Panel을 이용하는 방법이고, 다른 하나는 EZLoader Custom USB Firmware Loader Driver를 이용하는 방법이다.
(드라이버 소스 코드 : C:CypressUSBDriversezloader)

전자는 개발이 진행 중일 때, 우리가 주로 쓰게 될 방법이고, 후자는 개발이 끝난 후, EEPROM없이 디바이스를 동작 시키려고 하거나, 인터넷 네트웍을 통해 펌웨어를 업데이트 시키려고 할 때, 사용해야 할 방법이다.

자 이제는 While문을 디벼보자.
사실상 이 While문이 main함수의 핵심이 되겠다.
이 While loop를 돌면서 Endpoint 0로 오는 명령을 받아 처리하고, 우리가 수행해야 할 작업을 수행하게 된다.

if(GotSUD)
{
    SetupCommand();
    GotSUD = FALSE;
}

이 부분이 Default Control Endpoint로 하달되는 호스트의 명령을 처리하는 부분이 되겠고,

if (Sleep) {…} 이 부분은 디바이스가 Sleep모드로 들어갔을 때 깨어나오기 위한 코드이다.

마지막으로
TD_Poll();
이 부분이 FrameWork님께서 우리에게 필요한 작업을 하는 코드를 여따가 넣으라고 마련해주신 함수가 되겠다.

-main() 끝-

SetupCommand() 함수를 보자.
졸라리 길다.
핵심은 이거다.
디스크립터를 달라는 요청이 오면 해당 디스크립터를 주고, Interface나 Configuration을 바꾸라는 요청이 오면 바꿔주고, 현재 Interface나 Configuration이 뭐냐고 물으면 알려주고, 디바이스 상태를 물어보면 알려주고, 등등등

여기서 한번 보고 넘어가야 할 것은

default:
if(DR_VendorCmnd())
EZUSB_STALL_EP0();

가 되겠다.
그 위의 것들이야 우리가 손대지 않아도 되는 것들이다.
우리가(호스트가 아니라 사용자가) Endpoint 0를 통해 디바이스에 명령을 내리면 DR_VendorCmnd()에서 처리를 하게 된다.
SetupCommand() 함수에서
SETUPDAT
SUDPTRH, SUDPTRL
EP0BCH, EP0BCL
EP0BUF
에 대한 설명만 본좌가 해 주면 코드를 보는데 별 문제가 없을 꺼라 생각된다.
(비록 그게 무얼 의미하는지는 아직 잘 몰라도 말이다.)

고걸 설명하려면 우선 Control Transfer가 이루어지는 과정을 한번 살펴봐야 한다.

가장 간단한 경우가 아래와 같은 경우이다.
호스트가 디바이스에게 지시할 내용이 8바이트 안에 다 들어가서, 추가로 디바이스에게 보낼 데이터도 없고, 디바이스로부터 데이터를 받을 필요도 없는 경우이다.
그럴 경우 Control Transfer는 Setup Stage와 Status Stage로만 이루어진다.

Array

그 중에서 실제적으로 필요한 부분은 Setup Stage이고, Sataus Stage는 이 경우에는 뭐 형식적인 절차, 이딴 게 되겠다.

Setup Stage 중에서도 “8byte setup data” 이 부분이 핵심 되겠다.
저런 Control Transfer가 일어날 때, 우리의 FX2의 SIE(Serial Interface Engine)이 이런저런 절차를 다 처리해 주고, 8byte setup data를 SETUPDAT[0~7]에 담아서 우리에게 넘긴다.
그럼 우리는 그걸 참고해서 어떤 명령이 어떤 인자와 함께 도착했는지를 알 수 있는 것이다.

만약 호스트가 디바이스에게 Interface Descriptor를 요구하는 경우에는 위의 과정만 가지고는 안 된다.
디스크립터 데이터를 호스트에게 전송해야 하기 때문이다.
그럴 때는 Setup Stage와 Status Stage 중간에 Data Stage가 더 들어간다.

Array

우리는 Data Stage의 Payload Data에 Interface Descriptor를 담아 보내면 된다.
어떻게 담느냐?
Interface Descriptor가 있는 메모리 주소를 SUDPTRH(주소의 상위 바이트), SUDPTRL(하위 바이트)에 적어주고, EP0CS |= bmHSNAK; 해주면 SIE에 의해 모든 절차가 자동으로 이루어진다.

SUDPTRH, SUDPTRL가 디스크립터를 전송하기 위한 전용 레지스터라면, EP0BCH, EP0BCL 그리고 EP0BUF는 그 경우를 제외한 나머지 Data Stage가 필요한 Control Transfer를 위한 레지스터 들이다.

만약 우리가 EEPROM의 데이터를 읽어오라는 명령을 Control Transfer를 통해 내린다고 가정해보자.

펌웨어 프로그래머와 드라이버, 어플리케이션 프로그래머는 미리
SETUPDAT[0]에는 이 명령이 EEPROM Read임을 나타내는 0x0F,
SETUPDAT[1]에는 EEPROM 주소의 상위바이트,
SETUPDAT[2]에는 EEPROM 주소의 하위바이트,
SETUPDAT[3]에는 읽어올 길이의 상위바이트,
SETUPDAT[4]에는 읽어올 길이의 하위바이트
이런 식으로 하자고 약속을 할 것이다.

이 명령이 디바이스로 전달되었을 때, 펌웨어는 해당하는 EEPROM번지의 데이터를 해당하는 길이만큼 읽어서
EP0BUF[0~63]에 넣고
EP0BCH(데이터의 길이, 상위바이트),
EP0BCL(데이터의 길이, 하위바이트)를 써주면 자동으로 데이터가 호스트로 날아간다.

이제 EP0BCH, EP0BCL 그리고 EP0BUF의 용도를 아시겠는가?
앞으로 펌웨어를 짜며 자주 쓰게 될 테니, 지금은 모르더라도 곧 알게 될 것이다.

Control transfer에 관한 자세한 사항은 여기를 참조하시라.

다음 회에는 우리가 만들게 될 디바이스의 회로도와 Parts List를 올리도록 하겠다.
부품을 구해야 할 행자들은 참조하시라.