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

 

오늘은 Bulk Out Endpoint를 통해 호스트로부터 데이터를 디바이스로 전송하는 것을 연습해 볼 차례이다.

Bulk Endpoint로 데이터 날리기를 참조하여 회로를 꾸미고, 아래의 소스들을 다운로드 받아 설치하자.
참고적으로 다가 얘기하자면 이미 구성되어 있는 회로(Port A.7의 LED, Port A.6의 버튼 )는 떼어내지 말고, 그대로 두고서 Port B에 8개의 LED를 추가로 설치하길 바란다.

Array

UniHigh Firmware v2.5
UniHigh Application v2.5
data.bin <- 전송할 데이터를 담고 있는 파일(이전의 파일과 동일한 파일)

만약 버튼(Port A.6)을 떼어내고 실험하고자 한다면, 펌웨어의 TD_Poll()함수에서 다음의 부분을 주석처리 해줘야 한다.
if(PA6 == 1 && !bButtonMask)

이전 강좌들과 마찬가지로 펌웨어는 unihigh.sys 드라이버와 붙도록 VID, PID만 바꾼 것이다.

UniHigh Application v2.5를 살펴보면 Bulk Out transfer를 위해 버튼 두 개와 에디트 박스 하나를 리소스에 추가 했다.

“File Select” 버튼은 디바이스로 전송할 파일을 선택 하기 위한 대화상자를 열기 위한 버튼이며, 눌렸을 때는 OnButtonSelectFile() 멤버함수가 호출된다.

에디트 박스는 선택된 파일의 경로와 이름을 나타내기 위한 것이며,

“Transfer” 버튼은 앞서 “File Select” 버튼에 의해 선택된 파일을 디바이스로 전송하기 위한 버튼이다.
이 버튼이 눌리면 OnButtonBulkOutTranfer() 멤버함수가 호출된다.
우리가 중점적으로 들여다 보아야 할 곳이 바로 여기다.

이전에는 디바이스와 통신하기 위해 DeviceIoControl을 사용했는데,
여기서는 WriteFile을 사용하고 있다.

hFile = OpenFile( BULKOUT_PIPE );
if( INVALID_HANDLE_VALUE != hFile )
{
    bRet = WriteFile( hFile, pcBuffer, m_dwTransferLength, &dwBytesReturned, NULL );
    CloseHandle( hFile );
}

OpenFile(CreateFile())로 디바이스의 핸들을 얻고, WriteFile로 디바이스로 데이터를 전송하며, CloseFile로 핸들을 반환한다.

DeviceIoControl이 WriteFile로 바뀐 것 밖에 없다.

앞서 강의에서도 언급했듯이 보통 DeviceIoControl은 적은 양의 데이터를 읽거나 쓸 때, 특히 한번에 읽고 쓰려고 할 때 사용되고, ReadFile, WriteFile은 많은 양의 데이터를 한 방향으로 전송할 때 사용된다.

일반적으로 그렇게들 쓴다는 얘기이고, 어떤 함수를 언제 어떻게 사용하느냐는 어플리케이션 개발자와 WDM 드라이버 개발자가 상의해서 정하기 나름이다.

이제 그만.

Array

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

 

오늘은 Default Control Pipe를 이용하는 마지막 예제를 디벼보고, 버튼입력을 받는 것을 연습해 보자.

그 전에 잠깐. 이전에 다운 받은 예제 중에 UniHigh Application v2.1/v2.2내의 OnButtonLEDBllink()함수에서 파라메터(sizeof(PCONTROL_REQUEST))가 잘못 사용되었다.
이를 수정하여 다시 올렸으니 다운 받으시던지 아니면 그냥 아래의 정정된 예제를 참조하시든지 하시라.

이전 강의 Control Endpoint의 DATA stage 활용 - IN편에서는 IN Data Stage를 이용해서 디바이스로부터 호스트로 데이터를 전송하는 것을 설명하였었다.

이 예제들을 실험하기 위해서는 드라이버를 또 업데이트 해 주어야 한다.
바로 이전 강의를 참조하여 드라이버를 아래의 것으로 업데이트 해 주자.
UniHigh Driver v2.4

UniHigh App 소스코드의 Unihighusr.h에 보면 추가된 control code들을 볼 수 있다.

UniHigh Application v2.3 - Setup Stage를 이용한 IN Tranfer
UniHigh Application v2.4 - Button & Message 받기(IN Interrupt Transfer)

UniHigh Firmware v2.3
UniHigh Firmware v2.4

v2.4는 v2.3의 코드에 새 코드를 추가한 것이므로, v2.4만 다운받아도 된다.

v2.3은 이전 v1.3예제처럼 어플리케이션으로부터 두 값을 입력 받아서 이 값들의 곱을 FX2에서 계산 한 다음 IN Data Stage에 실어 다시 어플리케이션으로 값을 반환하는 예제이다.

OnButtonCalculate() 함수에서 모든 처리가 일어나는데, 이전의 DeicceIoControl의 사용과는 다른 점이 데이터를 받기 위해 다섯 번째와 여섯 번째 인자, 즉, LPVOID lpOutBuffer,와 DWORD nOutBufferSize,를 세팅해 주고 있다는 것이다.

그 이외에는 이전과 특별히 다른 점이 없으므로 행자들이 직접 코드를 디벼보면 별 어려움 없이 이해가 될 것이다.

다음은 디바이스에 있는 버튼이 눌렸을 때, 이를 어플리케이션으로 알리는 v2.4예제.
마찬가지로 UniHigh1.4.zip을 unihigh.sys에 맞추어 바꾼 것이다.

펌웨어는 이전과 마찬가지로 VID, PID만 바뀌었고, 회로도 이전 강의(버튼입력을 받아보자)과 같게 꾸미면 된다.
그 회로에서는 앞서 사용하던 LED 하나가 빠져 있는데, LED를 그냥 두고, 버튼회로만 추가해도 된다.

새로운 드라이버(UniHigh Driver v2.4)는 행자들이 이미 설치했을 것이고, 쫌 설명을 해야 할 것은 UniHigh Application v2.4이다.

기억을 거슬러 올라가서, 이전 강의(버튼입력을 받아보자)에서 우리가 버튼 입력을 받기 위해서 펌웨어 다운로드 -> Get Pipe버튼 누름 -> Pipe 0 선택 -> “Bulk/Int”버튼을 누름 이런 절차를 밟았다.
그런데 -> “Bulk/Int”버튼을 누르면 우리가 UniHigh 디바이스에 달린 버튼을 눌러서, 그 메시지가 EZ-USB Control Panel에 도착하기 전까지 어플리케이션이 잠시 얼어있는 것처럼 보이는데, 그 이유는 어플리케이션의 메인 쓰레드에서 버튼입력을 기다리기 때문이라고 설명 했었다.
기억 나시남?

버튼 입력을 받기 위해 메인 쓰레드를 홀딩 시킬 수는 없으므로, 우리는 이 기능을 위한 쓰레드를 하나 더 생성해야 한다.
UniHigh Application v2.4 프로젝트를 열어 보시라.
UniHighDlg.cpp의 마지막 부분에 있는 GetMsgThread(…)가 바로 버튼(메시지)입력을 받기 위한 쓰레드 본체이다.

OnInitDialog()에서 쓰레드를 시작하고,
m_bMsgThreadWorking = TRUE;
m_pGetMsgThread = AfxBeginThread( GetMsgThread, (LPVOID)this );

프로그램이 종료될 때 호출되는 OnClose에서 쓰레드를 종료한다.
m_bMsgThreadWorking = FALSE;

그리고, CUniHighDlg의 멤버함수인 MsgArrived는 디바이스로부터 버튼입력(메시지)이 도착했을 때 GetMsgThread에서 호출하는 함수이다.

GetMsgThread 내부를 살펴보면 이전의 DeviceIoControl과는 다르게 마지막 인자에 OVERLAPPED 구조체의 주소를 주고 있다.

이것은 이 쓰레드가 메시지가 도착하기 전까지는 pending 되어 있다가, 메시지가 도착했을 때, 이를 알려줄 이벤트를 위한 것이다.

Windows 프로그램을 잘 모르는 행자들를 위해 자세히 설명하려니 양도 만만치 않은게 본 강좌가 윈도우 프로그래밍강좌로 변질 될 것 같고, Windows 프로그램을 잘 아는 행자들을 위해 설렁설렁 설명하려니 다 아는거 리바이벌 밖에 더 되겠낭?

따라서 이 쓰레드 함수의 내부에 대한 설명은 생략한다. ㅋㅋㅋ
난 Windows 프로그램을 잘 모르는데 그래도 꼭 알아야 되겠다 라는 행자들은 Overlapped I/O에 관해 데브파이의 Visual C++ “강좌와 Tip”란을 디벼 보시거나 구글링 하시라.

UniHigh Application v2.4는 종료 시 쓰레드 땜에 메모리 leak이 날지도 모르겠다.
나중에 시간 나면 잡고, 안 나면 말자.
테스트하는 데는 별 지장 없다.

UniHigh Firmware v2.4를 살펴 보면 이전에 본좌가 얘기 했듯이 SendMessage 함수를 디버깅 용도로 사용한 예가 있다.

TD_Poll()함수를 잘 찾아 보시라.
이제 디버깅을 위한 LED 깜빡 신공으로부터 해방이닷.

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

 

계속해서 Default Control Endpoint를 통해 디바이스와 통신하는 방법을 연습해 보자.
오늘은 이전 강의 SETUPDAT 활용하기
Control Endpoint의 DATA stage 활용 - OUT편
을 unihigh.sys를 이용해서 어플리케이션에서 디바이스로 접근할 수 있도록 고쳐본다.

먼저 새로운 버전의 unihigh.sys를 다운받아서 드라이버 업데이트를 시켜야 한다.
UniHigh Driver v2.1
위의 드라이버를 다운 받아서 원하는 위치에 압축을 풀어 놓자.
(c: 같이 ASCII문자로만 구성되며, 공백이 없는 디렉토리에 풀어 놓는게 안전하다.)

드라이버를 업데이트 시키는 방법은 다음과 같다.
UniHigh 보드를 USB포트에 꽂고, EZ-USB Control Panel로 UniHigh Firmware v2.0을 다운로드 해서 장치관리자에 UniHigh보드가 나타나도록 한다.

Array

장치관리자에 잡힌 UniHigh Board를 선택해서 오른 버튼을 클릭하면 다음과 같은 대화상자가 나타난다.
거기서 “드라이버 업데이트”를 클릭하라.

Array

다음을 누른다.

Array

다운받아 놓은 드라이버의 위치를 지정해 준다.

Array

“드라이버 파일 검색결과” 대화창에 있는 “다른 드라이버 중 하나 설치” 체크박스를 체크하고 다음을 누른다.

Array

리스트 컨트롤 박스에서 새로 다운받은 드라이버를 지정하고, 다음을 누른다.

Array

마침을 누르면, 다음과 같이 장치관리자에 새 버전의 드라이버가 깔린 것이 보일 것이다.

Array

Array

오늘 살펴볼 코드는 두 개 이다.

UniHigh Firmware v2.1
UniHigh Firmware v2.2

UniHigh Application v2.1
UniHigh Application v2.2

UniHigh xxx에 v2.1에 코드를 추가하여 UniHigh xxx v2.2를 만들었으므로 v2.1은 다운 받지 않고 v2.2만 다운 받아도 된다.

위의 펌웨어 코드 밑 어플리케이션은 맨 앞에 언급한 강의에서 사용한 예제들과 완전히 동일한 동작을 하는 예제들이다.
달라진 건 ezusb.sys 대신 unihigh.sys 드라이버를 사용한다는 것이고, 테스트 어플리케이션이 EZ-USB Control Panel에서 UniHigh Application으로 바뀐 것 뿐이다,

펌웨어야 이전 v1.x버전과 VID, PID가 달라진 것 밖에 없으므로 설명을 생략하고, UniHigh Application v2.2만 대략 설명하겠다.

솔직히 설명 할 꺼리도 없다.
각각의 CONTROL_CODE의 경우에 DeviceIoControl을 어떻게 사용하는지만 보고,
나중에 행자들이 자신의 어플리케이션을 만들 때 참고하면 되겠다.

자 UniHigh Application v2.2 프로젝트를 열어보자.

LED Blink 버튼을 누르면 UniHighDlg.cpp의 OnButtonLEDBlink()로 들어오고, LED Signal 버튼을 누르면 OnButtonLEDSignal() 맴버함수로 분기한다.

각 함수에 대한 설명은 코드의 주석으로 대신한다.
그 코드들을 설명하는 것은 본좌가 정의한 어플리케이션과 드라이버간의 인터페이스를 설명하는 것에 불과하므로 USB를 공부하는 데는 별 도움이 안 된다.

다만 본좌가 제공한 예제를 응용해서 스스로 어플리케이션을 만들 때 인터페이스의 사용법을 익히는 정도면 족하다.
따라서 DeviceIoControl의 인자를 어떻게 주는지만 중점적으로 보시면 되겠다.

v2.1예제는 index와 value값만 사용하는 Control Request의 예이고,
v2.2예제는 Control Request의 Data Stage를 활용하는 예이다.

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

 

오늘은 이전 강좌의 펌웨어 예제 UniHigh v1.0을 본좌가 만든 WDM USB 드라이버인 unihigh.sys를 써서 우리가 만들 어플리케이션에서 동작시키는 연습을 해 보자.

우선 기존의 펌웨어 예제를 살짝 바꿔주어야 한다.
바꿀 부분은 디바이스 디스크립터의 Vendor ID와 Product ID이다.
USB 디바이스가 포트에 꽂히면 운영체제(USB 호스트)는 디바이스 디스크립터 중에 Vendor ID와 Product ID를 읽어, 그에 해당하는 드라이버를 찾아 로딩하고 사용할 준비를 한다.
그 ID들에 해당하는 기존에 깔려있는 드라이버를 찾지 못하면 운영체제는 새 드라이버를 요구하는 대화상자를 띄우게 된다.

물론 UniHigh v1.0에서 사용하는 기존의 VID(Vendor ID), PID(Product ID)를 그냥 써서 unihigh.sys 드라이버를 로딩하게 할수도 있지만, 그렇게 하면 EZ-USB Control Panel을 사용할 수 없게 되므로, 일부러 다른 VID, PID를 사용했다.

다음은 UniHigh v1.0에서 VID, PID를 바꾼 펌웨어 예제이다.
UniHigh Firmware v2.0
(바꾸는 김에 bulkloop.c란 파일 이름도 unihigh.c로 변경했다.)

그리고 다음은 드라이버 예제이다. 다운받아서 적당한 위치에 압축을 풀어 놓으시라.
(c: 같이 ASCII문자로만 구성되며, 공백이 없는 디렉토리에 풀어 놓는게 안전하다.)
UniHigh Driver v2.0

이 UniHigh Firmware v2.0 예제를 EZ-USB Control Panel을 통해 UniHigh모듈로 다운로드 하면, 드라이버 설치를 요구하는 대화창이 뜰 것이다.

Array

그럼 미리 UniHigh Driver v2.0를 다운받아 풀어 놓았던 폴더로 가서 unihigh.inf파일을 지정해 주어라.

이제 드라이버도 깔렸으니 어플리케이션을 실행해서 UniHigh 보드의 LED를 키고, 꺼보자.

UniHigh App v2.0 소스
UniHigh App v2.0 실행프로그램

당근 보드 세팅은 이전처럼 되어 있어야 한다.

어플리케이션 소스에 대한 설명은 내일 계속
*드라이버는 컴터 재부팅시키기 귀찮아서 98se, xp에서는 테스트를 안 해 봤다. -.-; 이상 있으면 얼렁 연락 주시라.

오늘은 UniHigh Application v2.0의 소스를 분석하여 어플리케이션에서 어떻게 드라이버를 통해 디바이스와 통신하는지를 디벼보자.

기본적으로 UniHigh App v2.0은 MFC로 생성한 Dialog Base의 MFC 골격을 만든 후에 리소스 에디터에서 메인 Dialog에 버튼 하나를 추가하고, 그 버튼의 Handling function을 추가한 것이다.

아직 MFC를 모르는 행자들 미리부터 너무 쫄지 마시라.
본좌가 MFC 프로그래밍을 어떻게 하는지 가르쳐 줄 수는 없지만, 그걸 몰라도 전체적인 흐름을 따라잡고, 나중에 필요한 부분의 코드를 손 댈 수 있을 정도는 설명해 준다. 그까이꺼 뭐 대충~.

소스코드를 담고 있는 프로젝트 폴더를 열어보면 MFC 위저드가 생성한 파일들 외에 본좌가 따로 추가해준 파일들이 있다.
usb100.h
usbdi.h
usbioctl.h
usbiodef.h
wdm.h
그리고 unihighusr.h

맨 아래 파일은 본좌가 만들어 추가해준 헤더파일이고, 그 이외 5개의 헤더파일들은 이 프로젝트 내에서 호출하는 함수들을 위한 헤더파일 들이다.
(DDK에서 복사해 왔다. DDK가 뭔지는 나중에 알려주마.)

그 외에 행자들이 스스로 프로젝트를 생성해서 코딩을 할 때 해주어야 할 것이 하나 있는데, Visual Studio 메뉴의 “Project”->”Settings”->”Link” 탭을 클릭해서, “Object/Library Modules”항목에 “setupapi.lib”를 추가해 주어야 한다.
이 setupapi.lib 파일 역시 프로젝트 내에서 호출하는 함수들을 위한 라이브러리 이다.

Array

빨간 선으로 표시한 부분이 본좌처럼 “Win32 Debug”로 되어 있다면, 이 것을 “Win32 Release” 또는 “All Configurations”로 바꾸어 준 뒤 “Object/Library Modules”항목에 “setupapi.lib”를 추가해 주는 정도의 센.스.는 있어야 하겠다.
안 그럼 릴리즈 버전의 EXE파일을 생성할 때, 링크에러가 떵 하니 나타나버리고 말 것이기 때문이다.

변죽 울리기(I탄)은 끝났고, 본격적으로 코드를 들여다 보자.

딴 파일들은 손댄 것이 없고, UniHighDlg.cpp만 보면 된다.

본좌가 이 파일에 한 짓거리를 연대기 순으로 나열 하면, (이 부분부터 벌써 지겹다고 진저리를 치는 행자들은 반성하시라. 나중에 스스로 프로젝트를 만들려고 하면 이 설명이 필요할 것이다. 본좌가 다 애정이 있어서 그런 거다.)

맨 위에 헤더파일 include문 추가
#include "unihighusr.h"
#include "usbdi.h"
#include

고 밑에 define 문 추가
#define DEFAULT_CONTROL_PIPE NULL
#define MESSAGE_PIPE ("PIPE00") <- 쓸데 없는 넘

고 밑에 함수 원형 정의
HANDLE OpenFile(…);
BOOL GetUsbDeviceFileName(…);
HANDLE OpenUsbDevice(…);
HANDLE OpenOneDevice(…);

거기다가 전역변수
CHAR szCompleteDeviceName[256];
추가.

그리고 마우스를 쭉 끌어 맨 밑으로 가 보면 위에 선언한 함수원형에 해당하는 함수 본체들이 블라~블라~블라~ 있다.

그리고 오늘의 하이라이뚜
void OnButtonLED() 멤버함수 <-“LED ON/OFF 버튼의 핸들링 함수”
가 있다.

맨 마지막 OnButtonLED() 멤버함수를 제외하고 include문을 추가하는 것부터 시작하는 이 일련의 절차들은 행자들이 디바이스를 제어하는 어플리케이션을 작성하기 위해서는 기계적으로 Copy&Paste 신공을 구사해야 하는 대상이다.
이해? 이런 거 필요 없다.
딱 5초 준다. 존내 클릭하는 거다.

그래서 본좌가 같다 붙이기 쉬우라고 szCompleteDeviceName 전역변수와 네 개의 함수들을 멤버변수와 멤버함수로 안 만들고 기냥 같다 붙인 것이다.

본좌는 위의 변수와 함수들을 하나로 묶어 하나의 클래스로 만들어 쓰지만 클래스가 뭐시어라? 하는 행자들도 분명히 있을 것이기에 기냥 평범한 함수로 같다 붙였다.
(이러나 저러나 모냥만 다를 뿐, 하는 일은 같으므로 신경 쓰지 말자.)

여기까지가 변죽 울리기 II탄 이다.

행자들의 이해력을 필요로 하는 부분은 오직 OnButtonLED() 함수 하나 뿐이다.

앞서 말했지만,  “LED ON/OFF” 버튼을 클릭하면 낼롬 이 함수로 뛰어들어온다.

Array

OpenFile함수에서 우리가 접근하고자 하는 디바이스의 핸들을 얻고, DeviceIoControl로 디바이스에 어떤 일을 시키며, 할 일이 다 끝났다면 CloseHandle로 핸들을 반환한다.

이게 줄거리의 전부다.

어라? 저번에는 CreateFile로 핸들을 얻는다며?
또 구라치기냐?
하는 행자들에게는 박수를. 짝짝짝.
나머지는 업드려!
호이짜~ 호이짜~

Array

OpenFile함수를 따라 들어가 보면 거기 CreateFile이 있다.

h = CreateFile ( szCompleteDeviceName,
                         GENERIC_WRITE | GENERIC_READ,
                         FILE_SHARE_WRITE | FILE_SHARE_READ,
                         NULL,
                         OPEN_EXISTING,
                         0,
                         NULL );

시리얼 통신을 하는 프로그램을 한번이라도 작성해 본 행자들은 CreateFile의 맨 처음 인자에 “.COM1” 이라고 준 것을 기억할 것이다.
근데 이건 뭐 생뚱맞게 szCompleteDeviceName 냥?

디바이스를 오픈할 때, 그 이름을 “.COM1”, “.ezusb-0” 처럼 주는 것을 Symbolic Link를 사용한다고 하고, 본좌처럼 GUID_CLASS_UNIHIGH_TEST (unihighusr.h에 선언돼 있음)같은 GUID를 사용하여 디바이스를 오픈할 수도 있다.

두 가지 방법 다 어플리케이션에서 운영체제에다가 “야! 나는 여기에 명시된 디바이스랑 통신 할 꺼니까 그 다바이스의 Handle을 나한테 넘겨.” 라고 말하는 방법이다.

어느 방법을 사용하느냐 하는 것은 어플리케이션 프로그래머와 디바이스 프로그래머가 상의하기 나름이다.
하지만, ezusb.sys의 사용법처럼 Symbolic Link를 사용하는 것은 다른 장치와 그 이름이 중복될 가능성 때문에 될수록 피하는게 좋다.

씨바. 난 열라 독창적인 이름을 만들어내고 말테야!
라고 우기는 행자들은 말리지 않겠다. 지금 시간 햇빛도 안 나니 말릴 수 가 없다.
근데, 가끔 Symbolic Link가 중복되어 문제가 발생하는 경우가 있다.

따라서 MS에서는 GUIDGEN.exe라 하는 GUID(Globally Unique Identifier)를 만드는 프로그램을 제공한다.
이 프로그램은 확률적으로 중복의 우려가 없는 32bit짜리 GUID를 생성해 내고, 이렇게 생성한 GUID를 사용해 디바이스를 오픈하면

CreateFile할 때에 운영체제가 대략 중복! KIN! 하는 경우는 막을 수 있다.

뭐 이래저래 장황한 설명이 되었지만
HANDLE OpenFile(…);
BOOL GetUsbDeviceFileName(…);
HANDLE OpenUsbDevice(…);
HANDLE OpenOneDevice(…);
이 네 함수는 CreateFile의 맨 처음 인자를 얻기 위해 필요한 함수들이고, 우리가 손댈 필요가 없다.

요 함수들 땜에
usb100.h, usbdi.h, usbioctl.h, usbiodef.h, wdm.h
헤더 파일들과 setupapi.lib를 추가해 준 것이다.

본좌도 예전에 위 함수들이 어떤 동작을 하는지 한번 분석해 보고 난 뒤, 그 이후부터는 그냥 기계적으로 복사해서 쓴다.
이젠 이 함수들이 어떤 짓을 하는지 기억조차 안난다.

관심과 시간이 있는 행자들은 MSDN Library를 참조해 함 디벼 보시라.

이제 파일(디바이스) 핸들을 얻었으니, 그 핸들을 가지고 디바이스와 통신할 차례이다.

bRet = DeviceIoControl( hFile, lLed, NULL, 0, NULL, 0, &dwBytesReturned, NULL );
가 그 코드이다.

이 함수에 대한 설명은 여기를 참조하시라. 꼭 한번 볼 것을 권한다.

다른 인자들은 이 예에서는 사용을 안하고, 두 번째 인자 ( DWORD dwIoControlCode)가 우리가 디바이스에게 이 일을 햐쇼. 라고 명령하는 부분이 되겠다.

이 코드는 unihighusr.h에 선언되어 있는데, IOCTL_UNIHIGH_xxxxx하는 것들이 다 그것들이다.
이 코드들도 어플리케이션 프로그래머와 드라이버 프로그래머가 상의해서 내가 이런 코드 날리면 니가 그런 일 해야되… 라고 입을 맞추면 된다.(동성끼리면 대략 난감. -.-;)

여기까지가 UniHigh Application v2.0의 소스에 대한 설명이다.
정리하자면 변죽울리기 I탄과 II탄은 앞으로도 그냥 따라 Copy&Paste하면 되고, 디바이스를 컨트롤 하기 위해서는 OnButtonLED() 처럼 하면 된다. 이다.

MFC에 익숙하지 않은 행자들은 C 콘솔프로그램에서 프로그램을 시작하면 main()문으로 들어가듯이 버튼을 누르면 OnButtonLED()로 들어온다. 라는 정도만 알고, 그 버튼이 눌렸을 때 처리해야 하는 일은 그 함수 내에서 처리해 주면 되겠다.
변수 및 함수 선언들은 본좌가 했듯이 그냥 C프로그램 작성하듯 해도 된다.
더 자세한 것들은 책 사서 보시라.

'Hardware' 카테고리의 다른 글

IN Setup & 버튼(메시지) 받기  (0) 2008.04.01
UniHigh v2.1 & v2.2 와 드라이버 업데이트  (0) 2008.04.01
UniHigh 2.0 펌웨어, 드라이버, 어플리케이션  (0) 2008.04.01
WDM USB 드라이버  (0) 2008.04.01
UniHigh v1.7 완전정복  (0) 2008.04.01
GPIF  (0) 2008.04.01

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

 

우리가 인터페이스로 USB를 선택한 이유는 다른 여타의 인터페이스들과 마찬가지로 디바이스가 컴퓨터와 통신할 필요가 있기 때문이다.
다만 USB가 넓은 대역폭, Plug&Play등의 장점을 가지기 때문에 다른 인터페이스들에 비해 선호될 뿐이다.

Serial(RS232)을 이용하여 우리가 만든 디바이스와 컴퓨터(Win32 Application)간의 통신이 어떻게 이루어 질까?

아마 대부분의 행자들이 알고 있겠지만, 파일 입출력을 할 때와 동일하게 ReadFile(…), WriteFile(…)을 이용하여 디바이스에 데이터를 주거나 받는다.
ReadFile이나 WriteFile을 호출하기 위해서는 파일핸들이 필요하므로 CreateFile(.COM1…)을 호출해서 File Handle을 얻은 다음, 위 두 함수에 인자(Parameter)로 넘겨주어야 한다.
물론 더 이상 장치에 접근할 필요가 없을 때에는 CloseHandle(…)을 사용하여 운영체제로 파일 핸들을 반환하는 것도 잊지 말아야 한다.

디바이스와 통신하는데 사용하는 API는 ReadFile과 WriteFile 이외에도 DeviceIoControl(…)이라는 함수도 있다.
ReadFile, WriteFile이 함수의 이름 그대로 데이터를 읽거나 쓰기만을 위한 함수라면, DeviceIoControl은 디바이스에 데이터를 주고, 동시에 데이터를 받을 수 있다.
예를 들면, Default Control Endpoint을 통해 데이터를 주고 받는 연습을 했던 이전 강의에서 두 값을 디바이스에 주고, 디바이스로부터 그 값의 곱을 리턴받을 때 DeviceIoControl을 사용할 수 있다.
물론 WriteFile로 값을 주고, ReadFile로 결과를 읽을 수도 있겠으나 DeviceIoControl로 한번에 처리하는 것이 훨씬 편리할 것이다.
통상 Read/Write파일은 대량의 데이터를 이동시키는데 사용되고, DeviceIoControl은 그 이름이 의미하는대로 디바이스를 컨트롤하기 위한 소량의 데이터를 이동시키는데 사용된다.
꼭 그렇게 해야 된다는 제약은 없지만 말이다.

Serial(RS232)을 이용해 컴퓨터(Win32 Application)와 디바이스와 통신하는 것과 똑같은 방법이 USB에도 사용된다.
CreateFile로 파일 핸들(USB Handle)을 얻고, ReadFile/WriteFile/DeviceIoControl을 통해 USB 디바이스에 접근하며, CloseHandle로 핸들을 반환한다.
졸라 쉽지 않은가?
봐라. 알면 별거 없다.

이제까지 펌웨어를 테스트하면서 썻던 EZ-USB Control Panel도 그 안에서 위의 함수들을 이용해 디바이스와 통신을 한다.

근데 EZ-USB Control Panel이 ezusb.sys라는 드라이버를 사용해서 장치로 접근을 하듯이 디바이스에 접근하기 위해서는 드라이버가 필요하다.
우리는 MS의 Windows에서 장치에 접근하기를 원하기 때문에, 더 정확히 말하자면 WDM(Windows Driver Model) 드라이버가 필요하다.

머리 꼬리 다 띄고, 몸통을 덥썩 물어보자.
어디서 우리가 만든 USB 디바이스와 물릴 USB 드라이버를 구할 것이냐?
세가지 방법이 있다.
첫째, 드라이버를 만들어주는 툴을 이용한다.
둘째, 칩 제조사가 제공하는 드라이버를 사용한다.
셋째, 직접 드라이버를 작성한다.

각기 나름대로의 장단점이 있다.
첫 번째 방법은, 대표적으로 Jungo社의 WinDriver같은 툴을 사용해서 (그들이 주장하는 바로는 초보자도 클릭 몇방으로) WDM 드라이버를 만들 수 있다.
본좌도 예전에 그 광고문구에 혹하여 몇 번 시도를 해 보았으나 번번히 OTL하고 말았다.
분명히 그때 본좌는 쌩초보는 아니었고, 디바이스 드라이버에 대한 개념은 가지고 있다고 자부 했었는데, 고것만 갖고는 부족했었나 보다.
클릭으로 드라이버가 만들어진 것 까지는 좋은데, 어플리케이션이랑 인터페이스 시키는게 졸라 복잡했었다는 기억이 난다.
그래서 결론적으로는 내 클릭으로 드라이버가 제대로 만들어 졌는지 확인도 못해봤다. -.-;
그 이후로 드라이버 공부에 더 용맹정진하여 스스로 드라이버를 짜는 오늘에 이르게 되었으니, 완전히 헛짓은 아니었구나 위안 삼는다.
뭐 정품 사서, 기술지원 받으면서 해보면 안 될 것도 없겠지만, 가격도 만만치 않다.
제일 큰 문제는 드라이버 소스도 감추어져 있어서 드라이버 레벨에서 문제가 생기면 디버깅 할 방법이 없다는 것이다.
( 물론 자동으로 생성되는 드라이버가 잘 검증된 것이기는 하겠지만, 운영체제도 가끔씩 삑사리를 내는 마당에 그 누구를 믿을 수 있으리요.)

두 번째 칩 제조사가 제공하는 드라이버를 이용하는 방법의 장점은 공짜라는 것이다.
우리가 이제까지 써 왔던 ezusb.sys처럼 대부분의 usb칩 vender들이 자기들 칩에 붙는 드라이버를 제공한다.
CYPRESS는 고맙게도 USB 드라이버의 소스코드까지도 제공한다.
(C:CypressUSBDriversezusbdrv에 가보면 구경할 수 있다. 뭐 WDM 드라이버에 대한 지식이 없다면 있으나마나 하지만.)
C:CypressUSBDocEZ-USB GeneralEZ-USB General Purpose Driver Spec.pdf 같이 제공하는 드라이버를 쓸 수 있도록 인터페이스에 관한 문서를 제공하므로 이것을 잘 읽어보면 드라이버에 관한 지식 없이도 USB 장치와 인터페이스 하는데 별 문제 없겠다.
단점이라면 위 첫 번째의 경우도 마찬가지 이겠지만, 범용적인 코드이다 보니 오버헤드가 있어서 최적화가 된 드라이버 보다는 느리다는 것이다.

세 번째 누군가 직접 직접 드라이버를 작성하는 방법은 디바이스를 (운영체제가 정한 범위 안에서) 원하는 대로 제어할 수 있다.
그리고 튜닝을 잘하면 극한의 성능까지 끌어올릴 수 있다.
단점은 WDM 드라이버 프로그래밍을 배우려면 오래 걸린다는 것이다.
물론 그럴 땐 본좌 같은 드라이버 프로그래머를 불러다 쓰면 되지만 말이다.
이 방법의 가장 큰 장점은 디바이스 개발자의 부담이 덜어지고, 개발기간이 절약된다는 것이다.( 드라이버 프로그래머를 갖다 쓸 경우에. )
우리들의 보스들은 항상 토막만한 개발기간을 주고는 쪼아대면서, 눈은 이만치 높다.
하드웨어 만들고 펌웨어 짜기도 바쁜데, 어느 시간에 드라이버를 신경 쓰며, 언제 어플리케이션이랑 아웅다웅 하리요?

요럴 때, USB 드라이버 개발자가 중간에 끼어서 정리를 해주면 개발의 방향이 잡히면서 스트레스 지수가 확 떨어진다.

드라이버를 만드는 방법 중 어느 것이 최선이라 말할 수는 없고, 행자들이 처한 상황에 따라 적절한 방법을 선택하는 것만이 최선이다.

본좌는 앞으로 본좌가 작성한 USB WDM드라이버(unihigh.sys)를 가지고 어플리케이션과 펌웨어간의 인터페이스를 설명할 것이다.
(드라이버 작성법을 강의 한다는 말은 아니다. -.-; 언감생심. 드라이버가 본좌가 재미삼아 강의 할 만큼 만만한 분량과 실력이 아니라서…)

Ezusb.sys를 설명하자니 EZ-USB General Purpose Driver Spec.pdf를 번역하는 것 밖에 안 되겠고, 결정적으로 꽁수스러운 그 코드가 본좌 맘에 안 들기 때문이다.

'Hardware' 카테고리의 다른 글

UniHigh v2.1 & v2.2 와 드라이버 업데이트  (0) 2008.04.01
UniHigh 2.0 펌웨어, 드라이버, 어플리케이션  (0) 2008.04.01
WDM USB 드라이버  (0) 2008.04.01
UniHigh v1.7 완전정복  (0) 2008.04.01
GPIF  (0) 2008.04.01
Bulk In  (0) 2008.03.31

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

 

gpif waveform을 다 그렸다면, 이제 gpif.c 파일을 생성하여 이를 우리의 프로젝트에 포함시킬 차례이다.

메뉴의 Tools->Export to GPIF.c file을 선택하면 나타나는 대화상자에서 원하는 위치를 지정하고, (프로젝트가 위치한 폴더가 좋으리라.) 파일 이름을 gpif.c로 해서 “저장” 버튼을 누르자.

Keil u-Vision을 열고, 왼쪽 창에서 “Source Group 1”을 선택한 후 마우스 오른버튼을 클릭하면, “Add Files to Group ‘Source Group 1’ ”이란 메뉴가 나타나는데, 이를 클릭하여 앞서 저장한 gpif.c파일을 선택하여 “Add” 버튼을 눌러 준다.

그리고나서 (우리의 경우에는) bulkloop.c파일의 앞부분에 함수의 프로토 타입을 선언해주고, ( UniHigh v1.7 소스 참조 ) TD_Init()에서 GpifInit()함수를 호출해주면 gpif를 사용할 준비절차가 다 마무리 된 것이다.

어떻게 gpif를 시작하느냐?
TD_Poll() 함수를 보시라.

GPIFTRIG = GPIFTRIGRD | GPIF_EP6; 가 gpif 엔진을 구동시키는 키 이다.

위 코드는 세번째 waveform을 시작해서 EP6의 FIFO를 채우겠다는 의미이다.

4개중 어떤 waveform을 가지고 gpif를 동작시켜서 어느 Endpoint의 FIFO에 데이터를 인출할 지 결정하기 위해서는 GPIFWFSELECT 레지스터와 GPIFTRIG(혹은 EPxGPIFTRIG) 레지스터를 세팅해 주어야 한다.
GPIF Designer가 생성하는 기본적인 코드는 그 tab label에도 표시되어 있던 것처럼
첫 번째 waveform은 single read
두 번째는 single write
세 번째는 FIFO read
네 번째는 FIFO write를 위한 것이다.
gpif.c파일의
GPIFWFSELECT = InitData[ 5 ]( = 0x4E )와
T.R.M. p15-83의 GPIFWFSELECT 레지스터 설명을 직접 찾아 보시라.

앞서 강의에서 본좌가 심지어는 모든 waveform을 FIFO read를 위해 쓸 수도 있다고 언급했었다.
어떻게 하느냐?
GPIF Designer로 waveform을 그릴 때, 모두 FIFO read를 위한 각각의 waveform을 그려주고 나서, GPIFWFSELECT의 최하위 두 비트(FIFO read를 위한 waveform을 지정하는 비트)를 상황에 맞게 waveform을 선택하도록 고쳐 줌으로써 우리의 목적을 달성할 수 있다.

gpif가 구동될 때, 어느 Endpoint의 FIFO를 대상으로 하느냐는 GPIFTRIG의 하위 두 비트를 설정하거나, EPxGPIFTRIG에서 x를 선택해 줌으로써 지정할 수 있다.

데이터의 방향 즉, FD포트(Port B)를 통해 데이터가 들어오느냐 나가느냐는 GPIFTRIG의 하위 세 번째 비트를 어떻게 설정하느냐로 결정할 수도 있고,
EPxGPIFTRIG = 0xFF; : gpif write 시작
BYTE cTemp = EPxGPIFTRIG; : gpif read 시작
요렇게 해서 정할 수도 있다.

* GPIFTRIG와 EPxGPIFTRIG는 같은 일을 하는 다른 레지스터이다.
* gpif “read”는 데이터가 FD포트(Port B)를 통해 “FX2로” 들어오는 것이라고 이미 설명했었다.

이제 GPIFTRIG = GPIFTRIGRD | GPIF_EP6;의 의미를 아시것는가?
아직도 모르겠다면 T.R.M.을 잘게 찟어, 달여서 식후 30분, 하루 세 차례 복용하시라.
행자의 내공이 30갑자 증가할 것이다.

UniHigh v1.7의 TD_Poll()에는 본좌가 디버깅을 위해 넣었다가 깜빡 잊고 안 지운 코드들이 있는데 그걸 다 지워버리면 다음과 같다.

if( bConfigured )
{
if( GPIFTRIG & 0x80 )
{
SYNCDELAY;
GPIFTRIG = GPIFTRIGRD | GPIF_EP6;
SYNCDELAY;
while( !( GPIFTRIG & 0x80 ) ); // wait for the transaction to terminate naturally...
SYNCDELAY;
INPKTEND = 0x06;
}
}

야부리를 풀자면 다음과 같다.
디바이스의 configuration이 끝났다면 gpif가 IDLE 상태인지를 확인하고, gpif 엔진을 구동한다.
그리고 gpif 엔진이 일을 마칠 때까지 기다렸다가 EP6의 FIFO에 남아있는 데이터를 마저 패킷으로 날린다.


얼랄라리요?
마지막 줄의 INPKTEND가 하는 짓을 보아하니 gpif가 작동하는 동안 누군가 데이터가 차면 packet을 자동으로 날리나 보네? 라는 생각이 드셔야 한다.
안 드시면 KIN 사이다 드시라.


그렇다.
TD_Init()에 보면
EP6AUTOINLENH = 0x00; // When AUTOIN=1, core commits IN data
EP6AUTOINLENL = 0x40; // ...when EPxAUTOINLEN value is met
가 그 짓을 하도록 세팅해 놓은 코드 되겠다.
당삼 세팅하는 그 길이가 Endpoint 디스크립터의 맥스패킷사이즈 보다 작거나 같아야 한다.


고것만 하면 되느냐?
EP6FIFOCFG = 0x08; //00001000 : AUTOIN=1, ZEROLEN=0, WORDWIDE=0
도 해 주어야 한다.


바로 FX2의 AUTOIN 기능이 이제까지 설명한 것이다.


FIFORESET = 0x80; // set NAKALL bit to NAK all transfers from host
FIFORESET = 0x06; // reset EP6 FIFO
FIFORESET = 0x00; // clear NAKALL bit to resume normal operation
는 주석 달린 그대로 EP6 FIFO를 리셋하기 위한 코드이고,


EP6GPIFFLGSEL = 0x02;는
EP6의 FIFO가 꽉 차면 “청기를 올리는” 일을 하도록 세팅한 것이다.


Array

우리가 만든 waveform의 세번째 DP.
요걸 아직 잊지 않으셨으리라 믿는다.


* SYNCDELAY에 대한 설명은 이전 강의 참조.

'Hardware' 카테고리의 다른 글

UniHigh 2.0 펌웨어, 드라이버, 어플리케이션  (0) 2008.04.01
WDM USB 드라이버  (0) 2008.04.01
UniHigh v1.7 완전정복  (0) 2008.04.01
GPIF  (0) 2008.04.01
Bulk In  (0) 2008.03.31
Bulk Endpoint로 데이터 날리기  (0) 2008.03.31

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

 

오늘은 GPIF를 가지고 데이터를 전송하기 위한 준비 학습을 해 보자.

본론에 들어가기 앞서, 어제 강의에서 설명하지 않은 parallel.exe의 소스 코드를 살짜쿵 건드려주고 넘어가자.

Parallel.exe의 기본적인 골격은 MFC 위저드로 만든 Dialog base 어플리케이션이다.

거기에 버튼과 에디트박스 몇 개를 리소스에 추가하고
CParallelDlg에 몇 개의 멤버변수와 다음의 멤버함수를 추가한 것이다.
VOID SendData(PCHAR pBuffer, DWORD dwBufLen)
VOID OnButtonTransfer()
VOID OnButtonFile()
BOOL GetPreviousFile( CString &FileName )
VOID SaveFileName( CString FileName )
VOID OnParallelButtonInit()
그리고 위저드에 의해 생성된 OnInitDialog()함수의 끝부분에 몇 줄의 코드를 추가했다.

MFC 프로그램을 할 줄 아는 행자들이야 따로 설명 안 해도 소스코드 보고, 개조하는데 별 어려움이 없겠지만, 그렇지 않은 행자들을 위해 간단히 설명하겠다.
뭐 그렇다고 MFC 프로그래밍에 대한 개괄적 설명까지는 할 시간이 없고, ( 관심 있으시면 MFC책 사서 보시라. ) 본좌가 만든 함수의 기능에 대한 설명만 언급하겠다.
WinAPI나 클래스함수에 대한 설명은 MSDN Library를 참조하시라.

VOID SendData(PCHAR pBuffer, DWORD dwBufLen)
페러렐 포트를 통해 데이터를 전송하는 함수이다. 실제로 일을 하는 부분이다. 프로토콜을 손보기 위해서는 이 부분만 건들이면 된다.

VOID OnButtonTransfer()
“Transfer” 버튼이 눌리면 호출되는 함수이다.

VOID OnButtonFile()
“File” 버튼이 눌리면 호출되는 함수이다.

BOOL GetPreviousFile( CString &FileName )
프로그램 시작시 레지스트리에 저장되어 있던 이전 열었던 파일의 이름을 얻기 위한 함수이다.

VOID SaveFileName( CString FileName )
레지스트리에 열어본 파일이름을 저장하기 위한 함수이다.

VOID OnParallelButtonInit()
페러렐 포트 레지스터의 값을 초기값(?)으로 세팅해 주는 함수이다.

BOOL OnInitDialog()
프로그램 시작 시 호출되는 함수이다. 프로그램 초기화 시 필요한 일들은 여기에서 처리해라.
아래는 SendData(..)함수에 대한 설명이다.

Array

본좌처럼 페러렐 포트를 이용하지 않고, 다른 마이컴을 물려서 테스트할 행자들은 위 플로우챠트를 참고해서 펌웨어를 코딩하시라

Cypress FX2의 GPIF(General Programmable Interface)는 이미 설명한 대로 USB 2.0의 대역폭에 비해 8051코어가 너무 느리게 동작하기 때문에 이것을 대신해 외부에서 들어오는 데이터를 빠르게 FIFO에 채우고(또는 USB를 통해 전송 받은 FIFO의 데이터를 외부로 내보내고),  패킷을 날리는 일을 해주는 State Machine이다.

이것도 일종의 프로세서이니 만큼 입력과 출력이 있고, 우리가 프로그램을 해 줘야 동작을 한다.

GPIF를 동작시키기 위해 어떻게 프로그램을 하느냐?
Cypress 사이트에 가서 GPIF Designer를 다운받아 설치하시라.

설치된 GPIF Designer를 가지고 GPIF엔진이 어떻게 동작 할지를 프로그래밍 할 수 있다.
물론 GPIF에 관련된 레지스트리에 일일이 값을 써주어서 GPIF를 프로그래밍 할 수도 있지만, 쉬운 길 놔 두고 뭐하러 사서 고생하남?

Array

그림에서 볼 수 있듯이(T.R.M. Page 10-2) GPIF는
외부로부터의 컨트롤 신호 입력을 받을 수 있는 여섯개의 핀(RDY[5:0])
외부로 컨트롤 신호를 줄 수 있는 여섯개의 핀(CTL[5:0]),
8bit 혹은 16bit로 세팅할 수 있는 데이터 버스(FD[15:0]),
그리고 외부의 장치(ex. 메모리)에 주소를 할당해 줄 수 있는 9bit의 어드레스 라인(GPIFADR[8:0])을 가지고 있다.

그리고 GPIF엔진에 먹일 클럭은 외부클럭(5~48MHz) 또는 30/48MHz의 내부클럭 중에 선택 할 수 있다.
먹인 클럭에 따라 동작하는 Sync모드로도 동작할 수도 있지만, 클럭에 상관없이 Async모드로도 동작하게 할 수도 있다.

이전에 언급한 바와 같이 GPIF엔진은 7개의 상태(state)를 가질 수 있는데, GSTATE[2:0]은 GPIF엔진이 지금 어떤 state에 있는지를 나타내주는 출력 핀이다.
디버깅 용도 이외에는 사용할 일이 없는 핀이다.

나머지 입출력들에 대해서는 예제를 통해 차차 설명하도록 하겠다.

그림 중간에 Waveform Descriptors라고 네 개의 Descriptor가 있는데,
이 Descriptor에 GPIF을 통제할 프로그램을 넣어주어야 한다.
Waveform Descriptor는 실제로는 관련 레지스터에 들어갈 값들이다.

GPIF Designer를 써서 이 Waveform Descriptor를 쉽게 생성할 수 있다.
우리가 GPIF를 프로그램 하기 위해 해야 할 일은 GPIF Designer를 써서 Waveform Descriptor를 작성한 후에 gpif.c파일을 생성하고, 이 파일을 프로젝트에 포함시킨 후 TD_Init()에서 GPIF_Init() 함수를 호출해 주는 일이다.
간단하지 않은가?

우리가 쓰는 56핀 Cypress FX2에서는 CTL핀이 세개, RDY핀이 두개가 있고, GPIFADDR핀은 아예 없다.
하지만 이 핀들만 가지고도 웬만한 작업은 거의 다 처리 할 수 있다.

GPIF Designer를 설치한 행자들은 C:Program FilesCypressGPIF Designer 폴더에 가면 예제들이 있으니 함 구경해 보시라.
GPIF Designer 프로그램을 실행한 후, xxx.gpf 파일을 열면 예제가 제공하는 waveform을 볼 수 있다.
거기의 waveform처럼 우리가 마우스를 클릭 클릭해서 파형을 그려주고, 메뉴의 Tool->Export to gpif.c file 을 눌러주면 gpif.c파일이 생성된다.

Array

오늘은 GPIF를 이용하여 BULK IN 예제와 똑같은 일을 하도록 예제를 꾸며보자.

예제는 앞서 BULK IN 예제에서와 마찬가지로 UniHigh v1.0에서 필요 없는 DR_VendorCmnd()함수 내부를 비우고 이를 기본으로 하여 살을 덧붙였다.

UniHigh v1.7
(실행 전에 Waveform 작성하기 앞부분 참조.)

회로는 다음과 같이 꾸몄다.
이전에는 Port A의 핀들을 사용했지만,
이번에는 GPIF를 사용하기 때문에 RDY0(STROBE), RDY1(SELECT), CTL0(ACK)핀을 사용한다.

Array

Array

GPIF가 State Machine이라고 하였으므로, GPIF Designer를 써서 WaveForm을 만들기 전에 먼저 State Diagram을 그려보는 것이 나중에 Waveform을 만들 때 헛갈리는 것을 줄여준다.

Array

일단 완성된 Diagram은 다음과 같다.
(그림이 잘 안보이면, 그림을 클릭해서 크게 보거나, 다운로드한 후 보시라.)

Array

복잡해 보이지만 하나하나 따라가다 보면 그리 복잡하지도 않다.

첫 번째,
일단 GPIF engine을 처음 구동시키는 것은 8051 펌웨어이다.
GPIF 엔진이 일단 일을 시작하면 8051은 빠지고, 전적으로 GPIF가 데이터의 흐름을 관리하게 된다. 물론 8051이 중간 중간 끼어들어 데이터를 첨삭하거나 수정하는 등 필요한 작업을 할 수도 있다.
GPIF가 시작되면 GPIF state는 Idle ->S0로 간다.

Array

두 번째,
본좌의 허접탱탱구리 프로토콜에 의하면 Parallel 포트의 SELECT(17번 핀)가 LOW로 떨어지는 것은 곧 데이터 전송이 시작될 것을 의미하므로, 다음 state로 넘어가서 데이터를 전송 받는 절차를 시작하고, 그렇지 않다면 그 state에서 SELECT가 LOW로 떨어질 때까지 기다린다.

Array

세 번째,
S1 state는 일단 없는 것으로 생각하고, SELECT가 LOW로 떨어지면 S0에서 S2로 넘어가는 것으로 생각하자.

Array

네 번째,
S2상태에서 STROBE(1번 핀)가 LOW로 떨어지면, 이는 Parallel 포트의 데이터라인에 데이터가 실려있다는 것을 알려주는 신호이므로 이를 받기 위해 S3으로 상태를 바꾼다.
그런데, 만일 우리가 FIFO에 받은 데이터를 USB를 통해 호스트로 다시 전송하는 속도가 패러렐 포트에서 데이터를 받는 속도보다 느려서(그럴 가능성은 적지만.)
FIFO가 꽉 차 있을 경우에는 데이터를 더 받으면 안되므로, FIFO Full Flag가 0이 될 때까지 기다린다.

Array

다섯 번째,
S3 state에서는 FD[7:0]을 통해 받은 데이터를 FIFO에 써넣고, 데이터를 잘 받았다는 것을 알리기 위해 ACK(10번 핀)를 HIGH로 세팅한다.
“Parallel” 어플리케이션은 ACK가 HIGH가 되면 자신도 한 바이트에 대한 데이터 전송절차를 마무리하는 뜻으로 STROBE를 HIGH로 되돌린다.

Array

여섯 번째,
STROBE가 HIGH가 되면 ACK를 원래대로 LOW로 되돌린다.

Array

일곱 번째,
SLEECT가 HIGH라면 데이터 전송이 다 끝난 것이므로, IDLE상태로 가서 GPIF transaction을 마무리하고, 아니라면 S1으로 간다.

Array

여덟 번째,
S1에서는 SELECT가 HIGH가 될 때, Idle상태로 간다

아~!!!!

뭔가 설명해 놓고도, 본좌가 뭔 소리를 지껄였는지 잘 모르겠다.
정리해서 낼 더 쉬운 말과 그림으로 풀어드릴테니 지둘리시라.

이넘의 GPIF는 할때마다 매번 헷갈린다.
한번 헷갈리면 디버깅하기도 영 엿같은 일이 된다.
따라서, GPIF에서는 시간을 좀 더 들이더라도 확실히 개념 잡고 넘어가도록 해보자.
또 그만치 중요하다.

오늘 예제는 저번 BULK IN 예제와 동일한 방법으로 테스트하면 된다.
* BULK IN 강좌에 덧글 단 대로 페러렐 포트의 CMOS setup을 ECP로 놓고 하시라.

어제 왜 그렇게 본좌의 머리가 뒤죽박죽 정리가 안되었었는지 곰곰히 생각해 보니 설명한 분량은 많은데, 그걸 짧게 줄여 설명하려고 하다 보니 두뇌회로에 데드락이 걸려버렸던 거시다.

GPIF도 막상 알고 나면 별 거 없는데, 막상 설명하려니 이것 저것 언급하고 넘어가야 할게 적지 않다.
이젠 체념하고 강좌가 길어지더라도 설명할 껀 다 설명하고 가야겠다.
에효~.

느린 처리속도를 가진 8051코어를 대신해서 데이터의 흐름을 관리해 주는 것이 GPIF이므로 데이터의 대역폭이 중요하지 않은 어플리케이션을 디자인하려고 하는 행자들은 맘편히 GPIF는 생까고 넘어가시라.
예를 들어 모터제어를 GPIF를 통해 하겠다 라고 생각한다면 그건 돼지발에 꽃신 신기는 격이다.

그렇지 않고, 내가 만들 뭐시기에는 대역폭이 너므~너므~ 중요해. 라고 생각하는 행자들은 3단 콤보 로우킥이 들어와도 GPIF는 맞고 넘어가야 할 거시다.

오늘은 GPIF Designer를 사용해서 Waveform을 만들기 전까지의 절차를 알아보자.

GPIF Designer를 시작하면 이 프로그램을 종료하기 전에 편집했던 마지막 gpf 파일이 로딩되는데, 우리는 우리의 목적에 맞는 새 gpf파일을 한번 만들어보자.

GPIF Designer를 실행하고, 메뉴의 File -> New를 선택하면 다음과 같은 창이 뜬다.
UniHigh보드의 칩은 Cypress의 56pin FX2( CY7C68013 )이므로 CY FX2(56 pin) 항목을 선택해 준다.

Array

그럼 다음과 같은 화면이 뜰 것이다.
일단 이 상태에서 File -> Save As를 눌러 parallel.gpf를 파일 이름으로 하여 원하는 위치에 저장하자.

Array

오른쪽에 Un-named라고 이름이 붙은 박스에 커서를 위치시키고 오른 버튼을 클릭하면 다음과 같은 박스가 뜬다.
거따가 적절한 이름을 써 넣자.
이 이름은 Waveform 디자인에는 아무 영향이 없는, 그저 우리에게 보여지기 위한 이름이다.

Array

우리가 사용할 데이터 버스의 폭은 8bit이므로 이를 세팅해 주어야 한다.
gpf 프로젝트를 생성할 때는 디폴트로 16bit로 되어 있으므로 아래쪽 데이터버스(Data[15:8])에 대고 마우스 오른 버튼을 클릭하면 나타나는 “Use This Bus” 버튼을 클릭해주면 체크가 해제된다.
T.R.M. p.1-19에 나와 있듯이 GPIF모드로 동작할 때는 Port B가 Data[7:0]으로 사용되므로 여기에 우리가 직접 만든 페러렐 포트의 2번~9번 핀까지를 물려주면 되겠다.

Array

아래 그림의 빨간 박스부분을 누르면 Clock property라는 대화상자가 뜬다.
이 대화상자는 IFCFG라는 레지스터를 세팅해 주기 위한 것으로, IFCLK가 Internal로 되어 있으면 GPIF에 공급되는 클럭을 30/48 MHz로 세팅할 수 있고, IFCLK가 External이라면 IFCLK핀으로부터 들어오는 5~48MHz사이의 클럭에 맞취 GPIF가 동작하게 된다.
옵션으로 Clock을 반전시켜 사용할 수 있는데, T.R.M. p.10-9의 아랫쪽 그림처럼 Signal Setup Time을 충분히 갖기 위한 용도로 사용할 수 있다.

Array

우리는 Async모드로 GPIF를 동작시킬 것이므로 Clock세팅은 48MHz에 맞추어 놓자.
IFCLK output은 우리가 쓸일이 없으므로 Disable시키자.
Enable 시켜서 IFCLK 핀으로 48MHz의 클럭이 출력되더라도 별 상관은 없다.

Array

Synchronous모드에서 동작한다는 것은 Clock에 맞추어 데이터와 Signal이 Sampling되고, Control 신호가 asserting되는 것을 의미함은 잘 알고 계시리라.
반면에 Asynchronous 모드로 동작한다는 것은 클럭 입력 없이 Signal의 Transition에 맞추어 동작하는 것을 의미하지만, GPIF는 Async모드에서도 내부적으로는 클럭(30/48MHz)에 동기되어 동작한다.
예를 들어 S1 상태에서 S2로 넘어가기 위한 조건이 READY0핀이 Low->High로 되는 것이라면, 이상적으로는 READY0핀이 High가 되자마자 S2 상태가 되어야 하지만, FX2에서는 (48MHz 일때) 최대 24 nSec까지 상태변화가 지연될 수 있다

RDY선 또는 글자 근처에 대고 오른버튼을 클릭하면 다음과 같은 대화상자가 나타난다.
GPIF 입력핀의 이름을 우리가 정한 이름으로 바꾸어 주자.
(이것도 역시 우리가 보기 편하게 하기 위함이며, Waveform과는 별 관계 없다.)

Array

빨간 박스로 표시한 곳에 체크를 하면 GPIF가 Sync모드로 동작하게 된다.
우리는 Async모드로 동작하므로 그냥 놔두자.

Array

마찬가지로 CTL에 오른버튼 클릭하면 다음과 같은 대화 창이 뜨는데, 우리가 필요한 CTL핀은 하나 뿐이므로, 나머지는 체크를 해제하고, 그 CTL0의 이름을 ACK로 바꾸어 주자. (마찬가지로 이름은 우리가 보기 위한 것이다.)

Array

GPIF Designer의 Block Diagram 탭 옆에는 이름이 Single Read/Write, FIFO Read/Write인 다른 탭들이 있는데, Single Read/Write는 GPIF를 이용해서 FIFO에 한 바이트(워드)씩을 읽거나 쓰기 위한 waveform을 위한 자리로 Cypress가 마련해 놓은 것이며, FIFO Read/Write는 한번에, 즉 GPIF가 시작되어 그 사이클을 끝낼 때까지 여러 바이트(워드)의 데이터를 읽거나 쓰기 위해 마련해 놓은 곳이다.

한 바이트나 워드를 위해 왜 굳이 GPIF를 쓰는지 궁금해 하실 행자들을 위해 설명하자면 바로 연습용도이다.
T.R.M.에서는 GPIF를 이용해 한 바이트나 워드를 읽고 쓰기에 성공한 후에, FIFO Read/Write을 시도하라고 일러주고 있지만, 본좌는 대개 무시하고 막바로 FIFO Read/Write을 시도한다.

탭들의 이름이 미리 저렇게 붙어 있지만, 그 이름대로 그 자리에 꼭 그 일을 하는 waveform을 넣어야 하는 것은 아니다.
첫 번째 탭(첫 번째 waveform)에 FIFO Write를 위한 waveform을 넣어도 되며, 모든 탭에 FIFO Read를 위한 waveform을 넣어도 된다.
하지만 관행대로 탭들에 붙은 제목대로 waveform을 넣기를 권장한다.

우리는 FIFO Read를 위한 waveform만 있으면 되므로, 나머지 탭의 제목은 다 unused로 바꾸어 주자.
해당 탭을 활성화 한 뒤 탭 제목부분에서 마우스 오른클릭을 하면 Tab Label을 바꿀 수 있는 메뉴가 나온다.

Array

* FIFO Read/Write이라는 용어에서 Read나 Write냐 하는 기준은 FX2입장에서 봤을때이다.
즉, FX2가 데이터버스( FD[15:0] )를 통해 데이터를 읽을때는 (FIFO) Read, 쓸때는 (FIFO) Write이다.

Array

이제 waveform을 그리면 된다.
Save를 눌러 이제까지 설정한 내용 저장하시는거 잊지마시라.

'Hardware' 카테고리의 다른 글

WDM USB 드라이버  (0) 2008.04.01
UniHigh v1.7 완전정복  (0) 2008.04.01
GPIF  (0) 2008.04.01
Bulk In  (0) 2008.03.31
Bulk Endpoint로 데이터 날리기  (0) 2008.03.31
버튼입력을 받아보자  (0) 2008.03.31

출처 : 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를 연습할 때도 지금 만들 것을 사용하면 될 것 같다.

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

Array

Array

핀 번호를 헛갈리지 않기 위해 일일이 테스터로 찍어가며 라벨링을 한 다음 번호순으로 핀 헤더에 납땜했다.
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보드를 아래 회로에 맞게 세팅한다.
(점선 부분은 그냥 데이터가 오가는 과정을 우리 눈으로 확인하려고 설치한 부분이므로 생략해도 상관 없다.)

Array

* 우측 상단의 핀 설명은 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 파이프에서 데이터가 들어오기를 기다리는 상태가 되었다.

Array

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

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

Array

6. 결과 확인

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

Array

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

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

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

Parallel.exe 소스 코드

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

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

Array

데이터 전송절차 (번호는 윗 그림의 번호와 동일)
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는 앞서 어딘가에서 설명했으므로 기억 안 나는 행자들은 잘 찾아 보시라.

'Hardware' 카테고리의 다른 글

UniHigh v1.7 완전정복  (0) 2008.04.01
GPIF  (0) 2008.04.01
Bulk In  (0) 2008.03.31
Bulk Endpoint로 데이터 날리기  (0) 2008.03.31
버튼입력을 받아보자  (0) 2008.03.31
Control Endpoint의 DATA stage 활용  (0) 2008.03.31

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

 

오늘은 BULK OUT 트렌스퍼를 연습해 보자.

UniHigh1.5.zip

IN/OUT의 주체가 호스트란 것은 이미 설명 했으니, 이번 연습이 호스트가 데이터를 디바이스로 보내는 것이라는 것은 설명 안 해도 다들 잘 알고 계시리라.
라고 하면서 설명해 버렸다. -.-;

무엇부터 해야 될까?

빙고.
엔드포인트 디스크립터부터 추가해 주어야 하겠다.
그래서 본좌가 추가해 주었다.
Full/High Speed 똑같이 말이다.
이전 강의에서 Full Speed에서 Bulk Transfer의 Max Packet Size는 8, 16, 32, 64 byte중에 하나여야 하고, High Speedd에서는 최대 512byte여야 한다고 언급한 적이 있다.
근데 본좌가 High Speed Endpoint Descriptor의 Max Packet Size도 64로 세팅해버린 데는 남다른 사연이 있다.
바로 본좌의 귀차니즘 때문이다. -.-;
디바이스가 High Speed로 동작할 때와 Full Speed로 동작할 때를 따로 코딩해야 한다고 생각하니 너무 너무 귀찮은 생각이 들어서, 기냥 High Speed건 Full Speed건 상관없이 본좌가 코드 짜기 편하게끔 두 디스크립터를 똑같이 해버렸다.

행자들은 부디 본좌보다 부지런하길 바라며, Fx2.h에 선언된 EZUSB_HIGHSPEED() 매크로를 이용하면 내 장치가 구닥다리 컴퓨터에 붙여져서 Full Speed로 동작하는지, 아니면 최신형 신삥에 붙어서 High Speed로 동작하는 지 알 수 있다.

EZUSB_HIGHSPEED()를 사용하는 예는 Bulkloop.c의 ISR_Highspeed() 함수를 참조하시라.

추후에는 본좌가 암말 않터라도 위의 매크로를 이용해 Full/High Speed로 동작할 때, 달리 코딩해 주어야 하는 부분은 if 문으로 구분해서 코딩해 주시라.

사설이 길어졌는데….
어쨌든 Bulk Endpoint Descriptor를 추가 했으니 Desc.a51에 가서 휙~ 둘러보고 오시라.
디스크립터에 관한 설명은 이전에도 했고 해서 따로 안한다.

TD_Init()에 PortB를 output으로 설정하는 초기화 코드를 추가 했고,

SYNCDELAY;
EP2BCL = 0x80; // arm EP2OUT by writing byte count w/skip.
SYNCDELAY;
EP2BCL = 0x80;

도 추가했다.


EP2CFG = 0xA2;
요 초기화 코드는 위 코드의 앞에서 생략된 것으로 보심 되는데, 디폴트 값이기 때문이다.
T.R.M. 가서 휘딱 디비고 오시라.


EP2BCL에 아무 값이나 써 넣는 것은 OUT Endpoint Buffer를 쓰기 전에 꼭 우리가 해주어야 할 짓거리다.
http://muosys.egloos.com/81469를 참조하시라.


TD_Poll()의 마지막에 추가한 if문은 이전에 Bulkloop를 분석할 때 본 거를 쬐끔 변형한 것이므로 생각이 안 나면 http://muosys.egloos.com/75316를 다시 함 보시든지, 병원 가서 치매 치료를 받아보시라.


소스 설명은 끝이고…
아래의 회로를 꾸미시라.
Host에서 날라오는 데이터 대로 LED가 점등되도록 함 해보자.


Array

어떻게 테스트를 하냐 하면 펌웨어를 다운로드 한 후에, Get Pipe 하고, Pipe 1을 선택한 후에, File Transfer버튼을 눌러 본좌가 제공한 파일을 지정해 주면 LED가 파일의 데이터 대로 깜빡거린다.
data.bin

Array

이전에는 Hex … 리스트박스에 우리가 직접 데이터를 적어 넣어 주었지만, 길이가 긴 데이터는 매번 그렇게 하기가 번거로우므로 File Transfer 기능을 사용한 것이다.
이거나 그거나 데이터를 디바이스로 날린다는 거는 매한가지다.

Array

본좌가 만든 bin파일은 Visual Studio같은 바이너리 파일이 편집 가능한 툴로 편집할 수 있다.


그렇게 데이터를 날리면 아래와 같이 호스트가 날린 테이터에 따라 LED가 깜빡인다.
LED가 너무 빨리 깜빡이면 우리 눈에는 안 보이므로 본좌가 TD_Poll()의 마지막 if문에서 delay를 준 것이다.

'Hardware' 카테고리의 다른 글

GPIF  (0) 2008.04.01
Bulk In  (0) 2008.03.31
Bulk Endpoint로 데이터 날리기  (0) 2008.03.31
버튼입력을 받아보자  (0) 2008.03.31
Control Endpoint의 DATA stage 활용  (0) 2008.03.31
SETUPDAT 활용하기  (0) 2008.03.31

인터럽트 모드 트렌스퍼에서 인터럽트의 의미가 “끼여들기”라기 보다는 “가끔”이라는 것은 이미 언급했었다.
인터럽트 엔드포인트 디스크립터를 보면 Polling Time이라는 항목이 있는데 여기에 세팅된 시간 간격으로 호스트가 디바이스에게 전송할 데이터가 있는지 정기적으로 물어 보게 된다.

USB의 구조가 호스트-슬레이브의 구조이므로, 디바이스가 호스트에게 전송할 무언가가 있다고 해서 디바이스 맘대로 호스트를 호출 할 수 없다.
노예(Slave)는 항상 주인님(Host)이 물어볼 때만 대답할 수 있다.
따라서 디바이스가 호스트에게 데이터를 보내야 할 필요가 있다면 호스트가 정기적으로 디바이스에게 보낼 데이터가 있는지를 물어보도록 만들어야 한다.
이런 때 쓰이는 Transfer Mode가 Interrupt Transfer Mode이다.

디바이스의 버튼이 눌린 것을 어플리케이션에 알리고자 한다면, 인터럽트 엔드포인트를 사용하는 것이 적절 할 것이다.

소스코드 : UniHigh1.4.zip

먼저 Endpoint Descriptor를 추가하자.

;; Endpoint Descriptor
db DSCR_ENDPNT_LEN ;; Descriptor length
db DSCR_ENDPNT ;; Descriptor type
db 81H ;; Endpoint number(1), and direction(IN)
db ET_INT ;; Endpoint type
db 40H ;; Maximun packet size (LSB)
db 00H ;; Max packect size (MSB)
db 64H ;; Polling interval(100ms)

Maximum Packet Size는 64byte로 설정 하고 있는데, High Speed에서는 1024byte, Full Speed에서는 64byte, Low Speed에서는 8byte까지 설정 할 수 있다.
그러나 우리가 인터럽트 엔드포인트로 쓸 EP1의 버퍼사이즈가 64byte이므로 위와 같이 설정해 주었다.
(T.R.M. Page 1-23의 그림 참조)
물론 High Speed의 1024byte의 인터럽트 트렌스퍼를 사용하고 싶다면 EP2나 EP6를 이용하면 된다.


Polling Interval은 1~255까지 설정 할 수 있고, 단위는 mSec이다.


위의 디스크립터를 High Speed Config Descriptor와 Full Speed Config Descriptor에 추가해 주고, 각각의 Interface Descriptor의 Number of end points 필드도 1로 세팅해 주어라.


본좌가 메시지를 EP1을 통해 호스트로 날리기 위한 함수를 하나 추가 했다.

void SendMessage( BYTE cMessage, WORD wParam )
{
while((EP1INCS & bmEPBUSY)); //wait until EP1IN Endpoint available

EP1INBUF[0] = cMessage;
EP1INBUF[1] = LSB(wParam);
EP1INBUF[2] = MSB(wParam);

EP1INBC = 3;
}

EP0에서 데이터를 날리기 위해 EP0BCH/L을 썻던 것과 마찬가지로 EP1에서는 EP1INBC를 쓰면 된다.


이 루틴은 비단 버튼이 눌릴 때만이 아니라, 디버깅 용도로도 유용하다.
EZ-USB Control Panel을 이용하는 지금이야 디버깅 용도로 사용하기가 좀 번거롭지만, 앞으로 우리가 어플리케이션을 코딩하게 되면 이 루틴을 사용해 디바이스의 상태를 계속 USB포트를 통해 우리에게 보여 줄 수 있다.
그래서 본좌가 “WORD wParam”인자를 추가로 확보해 둔 것이다.
버튼 눌린 상태만 알고자 한다면 “BYTE cMessage”로도 충분한 데 말이다.


그 외에 Timer2를 사용하기 위해 TD_Init에 초기화 코드를 추가하고, Fw.c에 timer2_isr() Interrupt Service Routine을 추가 한 다음 TD_Poll()에 버튼입력을 체크하는 루틴을 추가 했다.


타이머는 버튼이 한 번 눌리면 일정시간 동안은 버튼 입력을 무시하도록 하는데 사용했다.
버튼이 눌릴 때 노이즈를 걸러내기 위해 세 번쯤 포트의 레벨 상태를 검사하는 루틴은 이 코드에서는 추가하지 않았다.
행자들이 추가 해 보시든가 하시라.


회로도는 아래와 같다.
아래 사진에서는 본좌는 점선으로 표시된 저항은 안 달았다.
사실 버튼입력 받을 때 이렇게 회로를 꾸며야 맞는지도 잘 모르겠다.
(본좌 하드웨어하는 넘이 아니므로 행자들이 이해 하시라.)
하지만 잘 작동하니까 대~충 맞다고 치자.
본좌처럼 점선 안의 저항을 생략하면 스위치가 눌릴 때 VCC와 GND가 쇼트되므로 별로 좋을 것 같지는 않다.


Array

Array

EZ-USB Control Panel로 버튼 입력을 받으려면, 펌웨어를 다운로드 한 다음에 “Get Pipe”버튼을 눌러주어 Pipe0(Endpoint 0)이 나타나는 지를 확인하고, “Bulk/Int”버튼을 누르면 된다.
잠시 어플리케이션이 응답이 없는 것처럼 보일 것이다.
디바이스로부터 응답을 기다리는데, 아직 버튼이 안 눌렸으므로 디바이스에서 응답을 안 하여서 마냥 기다리는 것이다.

Array

나중에 우리의 어플리케이션을 짤 때, 이러한 상황을 피하기 위해서는 버튼을 체크하는 루틴은 다른 쓰레드에서 돌려주어야 한다.
안 그럼 이 경우처럼 프로그램의 메인 쓰레드가 디바이스로부터 응답이 있을 때까지 얼어 있게 된다.


자 이제 버튼을 눌러보자.
버튼 눌림이 표시 될 것이다.


그럼 버튼 먼저 누르고, “Bulk/Int”버튼을 눌러보자.
바로 응답이 올 것이다.
버튼이 눌렸다는 데이터가 FX2 버퍼에서 대기하고 있다가, 호스트가 요청하자마자 바로 보내기 때문에 즉각적인 응답이 나타난 것이다.

'Hardware' 카테고리의 다른 글

Bulk In  (0) 2008.03.31
Bulk Endpoint로 데이터 날리기  (0) 2008.03.31
버튼입력을 받아보자  (0) 2008.03.31
Control Endpoint의 DATA stage 활용  (0) 2008.03.31
SETUPDAT 활용하기  (0) 2008.03.31
UniHigh v1.0  (0) 2008.03.31

+ Recent posts