본문 바로가기
WORK/Sotfware

[펌] 시리얼통신 프로그램

by KANG Stroy 2008. 7. 12.
728x90
728x90

예전 한참동안 시리얼통신 관련되어서 자료를 모았는데 정리 하면서 올립니다.
혹시 이글을 쓰신분을 아시면 알려주세요 하도 오래되어서 어디서 퍼온지도 모르겠네여
죄송합니다.

Windows 98/NT 에서 직렬 데이타를 전송 또는 수신하기 위해서는 win32 통신 API를 사용한다. 모뎀을 제어하기 위한 프로그램이나 전화선을 제어하는 프로그램을 작성하기 위해서는 직렬통신의 이해가 필수적이라 할 수 있다. 직렬 포트를 제어하기 위한 프로그램을 작성하기 위해서는 먼저 직렬 통신의 기초 지식 특히, 보드 레이트(baud rate), 시작과 정지 비트, 패리티 등과 같은 기초 개념을 이해하고, 또한 RS-232 표준 컴퓨터 포트의 기초 작동과 신호에 대해 익숙해야 한다. *RS-232에 관련된 자료는 자료실에 올려놓았으니 참고 하시기 바랍니다.

win32에서의 직렬통신은 16비트 윈도우에서의 경우와는 근본적으로 다르다. 16비트 직렬 통신에서는 통신을 위한 많은 펑션들이 제공된다. 이들 함수들은 각각 OpenComm, CloseComm, ReadComm, WriteComm이 된다. Windows 98에서 직렬 포트와 다른 통신 장치들은 파일로 취급된다. 직렬 포트는 파일에서 사용되는 동일한 함수를 사용해서 열고, 닫고, 읽고, 쓸 수 있다.

이 문서는 윈도우즈 엔티와 윈도우즈 98 에서 상호 호환되는 프로그램 개발자 인터페이스에 촛점을 맞춘다. 윈도우즈 98에서는 전화 API(TAPI)를 지원 하지만 윈도우 NT 3.x는 그렇지 않다. 그래서 이자료에서는 TAPI에 대한 설명을 포함하지는 않는다. TAPI는 모뎀 인터페이스하는 응용프로그램 개발에 많은 도움을 준다.

이번 장에서는 포트 열기와 닫기, 읽기와 쓰기(논오버렙 과 오버렙)에 대해서만 설명하겠다.


포트 열기


CreateFile 펑션으로 통신 포트를 연다. 통신 포트를 여는데는 2가지(논오버렙 과오버렙)방식이 있다. 다음의 소스가 오버렙을 통신 자원을 여는 것이다.


        HANDLE hComm;

        hComm = CreateFile(gszPort,

                        GENERIC_READ | GENERIC_WRITE,

                        0,

                        0, 

                        OPEN_EXISTING,

                        FILE_FLAG_OVERLAPPED,

                        0);

        if(hComm == INVALID_HANDLE_VALUE) // error opening port; abort


CreateFile을 호출할때 FILE_FLAG_OVERLAPPED를 없애고 호출하면 논오버렙 작업이 된다. 다음 절에서 오버렙과 논오버렙 작업에 대하여 설명한다.

win32 소프트웨어 개발자 참고서에 보면 통신 포트를 열기 위해 CreateFile을 호출하는데 다음과 같은 요구사항이 있음을 알 수 있다.


>fdwShareMode 는 반드시 0이어야만 한다.


통신 포트들은 파일을 공유하는 식으로 공유될 수 없다. 응용프로그램이 TAPI의 펑션들을 사용하는 경우에는 응용프로그램 서로간에 공유될 수 있다. TAPI를 사용하지 않는 윈32 응용프로그램에서는 통신 포트를 제어하기 위하여는 핸들을 계승하거나 복제가 필요하다. 핸들의 복제는 이 문서의 핵심 사항이 아니므로 생략한다.


>fdwCreate 는 반드시OPEN_EXISTING 플래그여야 한다.

>fTemplateFile 파라미터는 반드시 NULL이어야 한다.


COM1, COM2, COM3, COM4와 같은 이름의 포트 이름들이 있는데, win32의 API는 시스템에 이러한 포트들이 있는지 검색하는 메카니즘을 제공하지 않는다. 어떤 시스템은 4개 이상의 포트를 가지고 있기도 하다. 하드웨어 공급자나 직 렬 장치 드라이버 제작자는 이러한 포트들의 이름을 자유로이 정할 수 있다. 이러한 이유로 사용자가 직접 사용하고자 하는 포트를 지정하는 것이 최선의 방법이다. 만약 포트가 존재하지 않는 경우에는 포트 열기를 시도한 이후에 오류(ERROR_FILE_NOT_FOUND)가 발생할 것이고, 사용자에게 이 사실을 알려 포트가 사용할 수 없음을 알게 한다.


포트 닫기


직렬 포트를 닫는 것은 여는 것보다 훨씬 간단하다. CreateFile이 반환한 핸들을 유일한 파라미터로 사용하는 CloseHandle을 호출하면 된다. 사용을 마치면 항상 직렬 포트를 닫아야 한다. 포트를 닫는 것을 잊으면 열린 채로 있게 되고, 다른 응용프로그램이 이 포트를 열거나 사용할 수 없게 된다.


        CloseHandle(hComm);


읽기와 쓰기


통신 포트를 통한 읽기와 쓰기는 win32에서는 매우 간단하다. 사실상 파일 입 출력하는 방법과 같다. win32에서 입출력에는 두가지 방법이 있는데 하나는 오버렙과 논오버렙이다. win32 SDK에서는 동기통신과 비동기통시에 대하여도 설명하고 있다. 그러나 여기서는 오버렙과 논어버렙에 대하여만 설명한다.

논오버렙 입출력은 대다수의 개발자들이 사용하는 방법이다. 작업이 요구되면 작업이 끝나야만 펑션이 종료된다. 오버렙 입출력의 경우는 작업이 종료 되지 않아다고 하더라도 즉시 펑션이 종료된다. 프로그램은 이 시간을 이용 하여 백그라운드 작업을 할 수 있다.

win32에서의 읽기와 쓰기는 16비트 윈도우와는 전혀 다르다. 16비트 윈도우에서는 ReadComm과 WriteComm 펑션만이 있다. win32에서는 많은 읽고 쓰기 펑션들이 있다.


논오버렙 입출력


논오버렙 입출력은 제한사항을 갖기 때문에 매우 직선적이다. 하나의 작업이 시작되면 호출한 쓰레드는 붙잡힌다. 하나의 작업이 끝나면 펑션은 종료되고 쓰레드는 작업을 계속할 수 있다. 이 타입의 입출력은 멀티쓰레드 응용프로 그램들에서 많이 사용된다. 왜냐하면 하나의 쓰레드가 입출력을 위해 붙잡히더라도 다른 쓰레드는 계속 작업을 할 수 있다. 만약 하나의 쓰레드가 입출력 작업을 위해 붙잡히면 뒤따라 다른 쓰레드 들도 원래 작업이 종료될 때까 지 붙잡힌다. 예를 들어, 하나의 쓰레드가 ReadFile펑션을 종료하는 동안 대기 중일때 다른 쓰레드가 WriteFile펑션을 호출한다면 붙잡히게 된다.

논오버렙과 오버렙 작업 방식의 호환성을 생각할때 오버렙 방식은 좋은 선택이 아니다. 왜냐면 대부분의 운영체제가 지원하지 않는다. 대부분의 운영체제는 멀티쓰레딩과 같은 류를 제공한다. 그래서 멀티쓰레드 방식의 논오버렙 입출력이 이러한 이유로 인해 가장 좋은 선택이된다.


오버렙 입출력


오버렙 입출력은 논오버렙과 달리 직선적이지 않지만 많은 유연성과 효과를 갖는다. 오버렙 방식으로 하나의 포트를 열면 그 작업이 진행중인 동안 같은 시간에 다른 작업을 할 수 있다. 더 나아가 싱글 쓰레드에서 많은 작업을 동 시에 처리할 수 있다.

싱글 쓰레드와 멀티 쓰레드 양쪽 모두는 처리 요구를 하는 것과 그 결과를 처리하는데 있어서 작업의 결과가 유효해 질때까지 하나의 쓰레드는 붙잡혀야만 한다.

오버렙 입출력 작업은 2개의 파트를 갖는다. 작업의 시작과 종료의 확인이다. OVERLAPPED구조를 생성하고, 동기통신에 대한 이벤트를 생성하고, 관련 펑션(ReadFile또는 WriteFile)을 호출. 입출력 작업은 즉시 종료될 수도 그 렇지 않을 수도 있다. 만약 작업이 즉시 종료되면 다른 작업을 즉시할 수 있다. 두번째 파트는 데이타가 어떻게되었는지 처리가 완료되었는지 파악하는것이다. 처리 중에 타임아웃이 걸렸는지 등을 파악한다.


읽기


ReadFile 펑션은 읽기 작업을 한다. ReadFileEx또한 읽기 작업을 하는데 다른점은 윈도우 95에서는 사용할 수 없는 것이다. 이점에 대하여는 이 자료에서 설명하지 않는다. 다음은 읽기 요구를 어떻게 처리하는지 보여준다. 주의 할 것은 펑션은 ReadFile이 TRUE를 되돌려 줄때에만 그 데이타를 처리한다. 이것은 오버렙에서와 같은 펑션 호출이다. fWaitingOnRead 플래그가 코드에 정의되어 있고 그것은 오버렙된 읽기 작업인지 아닌지를 나타낸다. 그것은 외부에서 새로이 읽기 조작이 일어나는 것을 미리 예방하는데 사용되어진다.


DWORD dwRead;

BOOL fWaitingOnRead = FALSE; OVERLAPPED osReader = {0};

// Create the overlapped event. Must be closed before exiting

// to avoid a handle leak.

osReader.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

if (osReader.hEvent == NULL)

        // Error creating overlapped event abort.

if (!fWaitingOnRead)

{

        // Issue read operation.

        if (!ReadFile(hComm, lpBuf, READ_BUF_SIZE, &dwRead, &osReader))

        {

                if (GetLastError() != ERROR_IO_PENDING) // read not delayed?

                        // Error in communications; report it.

                else

                        fWaitingOnRead = TRUE;

        } 

        else

        {

                // read completed immediately

                HandleASuccessfulRead(lpBuf, dwRead);

        }

} 


두번째 파트의 오버렙 작업은 작업의 종료여부를 확인하는 것이다. OVERLAPPED 구조체 안의 이벤트 핸들은WaitForSingleObject펑션에 전달되어서 객체가 신호가 올때까지 기다리게 한다. 이벤트 신호가 오면 작업은 종료 된다. 이것은 성공적으로 종료되었다는 것을 의미하지는 않고, 단지 종료되었다는 것이다. GetOverlappedResult가 작업의 결과를 레포트 해준다. 만약오류가 발생하였다면 GetOverlappedResult는 FALSE를 돌려주고 GetLastError 가 오류 코드를 되돌려 준다. 만약 작업이 성공적으로 끝났다면 GetOverlappedResult는 TRUE를 돌려준다.

주의: GetOverlappedResult는 작업이 종료되었는지 실패된 상태인지를 알수 있다. GetOverlappedResult가 FALSE를 돌려주고 GetLastError가 ERROR_IO_INCOMPLETE이면 그 작업은 종료된 것이 아니다. 또한GetOverlappedResult는 작업이 종료될 때까지 붙잡을 수 있다. bWait파라미터와 같은 것에 TRUE를 넘겨 줌으로써 오버렙과 논오버렙을 전환할 수 있다.

여기 중첩 읽기 작업에 대하여 종료를 확인하는 소스가 있다. 소스 코드는 호출되었을 때 즉시 종료되었는지 검사하기 위해 똑같은 함수를 호출한다. 물론 fWaitingOnRead 플래그를 사용할 수도 있다.


#define READ_TIMEOUT       500     // millisecondsDWORD dwRes;

if (fWaitingOnRead)

{

        dwRes=WaitForSingleObject(osReader.hEvent,READ_TIMEOUT);


        switch(dwRes)

        {

        // Read completed.

        case WAIT_OBJECT_0:

                if (!GetOverlappedResult(hComm, &osReader, &dwRead, FALSE))

                        // Error in communications; report it.

                else

                        // Read completed successfully.

                        HandleASuccessfulRead(lpBuf, dwRead);

                        // Reset flag so that another opertion can be issued.

                        fWaitingOnRead = FALSE;

                        break;


        case WAIT_TIMEOUT:

                // Operation isn't complete yet. fWaitingOnRead flag isn't

                // changed since I'll loop back around, and I don't want

                // toissue anotherread untilthe firstone finishes.

                // This is a good time to do some background work.

                break;


        default:

                // Error in the WaitForSingleObject; abort.

                // This indicates a problem with the OVERLAPPED structure's

                // event handle.

                break;

        }

}


쓰기


통신 포트를 통해 데이타를 송신하는 것은 읽기에서와 거의 유사하다. 그것은 많은 똑같은 API를 사용한다. 다음에 그 예이다.


BOOL WriteABuffer(char * lpBuf, DWORD dwToWrite)

{

        OVERLAPPED osWrite = {0};

        DWORD dwWritten;

        DWORD dwRes;

        BOOL fRes;

        // Create this write operation's OVERLAPPED structure's hEvent.

        osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

        if (osWrite.hEvent == NULL)

                // error creating overlapped event handle

                return FALSE;

                // Issue write.

                if (!WriteFile(hComm, lpBuf, dwToWrite, &dwWritten, &osWrite))

                {

                        if (GetLastError() != ERROR_IO_PENDING)

                        {

                                // WriteFile failed, but isn't delayed. Report error and abort.

                                fRes = FALSE;

                        }

                        else

                                //Write is pending.

                                dwRes = WaitForSingleObject(osWrite.hEvent, INFINITE);

                        switch(dwRes)

                        {

                        // OVERLAPPED structure's event has been signaled. case

                        WAIT_OBJECT_0:

                                if(!GetOverlappedResult(hComm,&osWrite,&dwWritten, FALSE))

                                        fRes = FALSE;

                                else

                                        // Write operation completed successfully.직렬 포트 닫기

                                        fRes = TRUE;

                                break;

                        default:

                                // An error has occurred in WaitForSingleObject.

                                // This usually indicates a problem with the

                                // OVERLAPPED structure's event handle.

                                fRes = FALSE;

                                break;

                        }

                }

        }

        else

                // WriteFile completed immediately.

                fRes = TRUE;

        CloseHandle(osWrite.hEvent);

        return fRes;

} 


주의할 것은 WaitForSingleObject에서 INFINITE를 지정하면 작업이 종료될때까지 영원히 기다린다.

주의를 요할 것이 있는데 이전의 중첩처리가 완료되지 않았음에도 불구하고OVERLAPPED 구조체를 재사용한다는 것이다. 만약 새로운 중첩 처리가 필요한데 아직 이전의 중첩처리가 끝나지 않았다면 새로이 구조체를 생성하여 사용 하여야만 한다. 구조체를 생성할 때는 역시 hEvent 멤버도 새로 만들어야 한다. 중첩 작업이 끝나면 그 구조체와 그 이벤트도 해제하여 다시 사용할 수 있도록 한다.

OVERLAPPED 구조체에서 직렬 통신을 위해 수정되어야 하는 것은 오로지 hEvent멤버 뿐다. 다른 멤버들은 모두 0으로 초기화될 것이다. 다른 멤버를 수정하는 것은 직렬 통신에서는 필요하지 않다.



직렬 상태


직렬 통신 포트의 상태를 알아내는 방법은 두 가지다. 첫 번째 방법은 이벤트 마스크를 사용하여 이벤트가 발생한 것을 알아내는 방법이다. SetCommMask 함수는 이벤트 마스크를 설정하고, WaitCommEvent는 지정한 이벤트가 발생할 때까지 기다린다. 이 함수들은 16비트 윈도우에서의 SetCommEventMast와 EnableCommNotification과 비슷한데 단지 윈32 함수는 WM_COMMNOTIFY메시지를 보내주지 않는다는 점이 다르다. 사실상 WM_COMMNOTIFY메시지는 윈32에서는 없다. 두 번째 방법은 거의 차이점이 없다.


통신 이벤트


통신 이벤트는 통신 포트를 사용하는 중에 계속 발생한다. 이 이벤트를 알아내는 방법은 2단계가 있다.


(1) SetCommMask로 알고자 하는 이벤트를 설정한다.

(2) WaitCommEvent 로 상태를 체크한다. 상태 체크는 중첩/비중첩 작업 어디서든 사용할 수 있다.


여기서 이벤트라고 하는 것은 통신포트 이벤트만을 말한다. 동기통신을 위한 이벤트 객체에 대한 것이 아니다. 여기에 SetCommMask함수의 예제가 있다


DWORD dwStoredFlags;

dwStoredFlags =    EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING |

                EV_RLSD | EV_RXCHAR | EV_RXFLAG | EV_TXEMPTY;

if (!SetCommMask(hComm, dwStoredFlags))

        // 통신 마스크 설정 오류


각 이벤트의 설명은 다음과 같다


EV_BREAK

입력에서 브레이크가 검색되었다

EV_CTS

CTS(Clear-to-send)신호가 변경 상태. CTS선에 대한 실제적 인상태를 검사하기 위하여는GetCommModemStatus를 호출하 여야 한다

EV_DSR

DSR(Data-set-ready)신호가 변경 상태.DSR선에 대한 실제적 인상태를 검사하기 위하여는GetCommModemStatus를 호출하 여야 한다

EV_ERR

오류가 발생하였다. 라인 상태 오류는 CE_FRAME, CD_OVERRUN, CE_RXPARITY이다. 오류의 종류를 알려면 GetCommModemStatus가 호출되어야 한다.

EV_RING

전화벨이 울렸다는 신호가 왔다

EV_RLSD

RLSD(reveive-line-signal-detect 수신선 신호 검색됨)상태 가변경되었다. RLSD선에 대한 실제적인 상태를 검사하기 위하여는 GetCommModemStatus를 호출하여야 한다. 이것은 CD(carrier detect 접속된 상태)를 볼 때 공통으로 사용한다.

EV_RXCHAR

새로운 문자가 수신되어 입력 버퍼에 놓여있다.

EV_RXFLAG

이벤트 문자가 수신되어 입력 버퍼에 놓여 있다. 이벤트 문 자는 DCB구조체에서 EvtChar로 지정하는데 차후 설명한다.

EV_TXEMPTY

마지막 문자가 송신되어 송신 버퍼가 비었다. 만약 하드웨 어적인 버퍼가 사용된다면 이것은 하드웨어에 전송이 끝났 다는 것만을 알려준다. 하드웨어의 버퍼가 송신이 끝나 비 워졌다는 것을 알 수 있는 방법은 없다.



이벤트 마스크를 설정한 후엔 WaitCommEvent 펑션은 이벤트가 발생했다는 것을 검색하게 한다. 만약 포트가 비중첩 작업을 위해 열렸다면WaitCommEvent 펑션은OVERLAPPED구조체를 포함하지 않는다. 지정한 이벤트 중의 하나가 발생할 때까지 프로그램은 진행되지 않고 붙잡힌다. 만약 이벤트가 전혀 발생하지 않는다면 그 쓰레드 역시 붙들린다.

여기에 단편적인 예제가 있다. 포트는 비중첩 작업으로 열렸고 EV_RING 이벤트를 기다리는 것을 보여준다.


DWORD dwCommEvent;

if (!SetCommMask(hComm, EV_RING))

        // 통신 마스크 세트하는 중 오류

        return FALSE;

if (!WaitCommEvent(hComm, &dwCommEvent, NULL))

        // 이벤트를 기다리는 중에 오류가 발생

        return FALSE;

else

        // 이벤트가 발생하였다.

        return TRUE;


주의 사항 : 마이크로소프트 소프트 개발자 기술자료집에는 윈도우 95에서 EV_RING을 사용하는데 문제점이 있다는 설명을 해준다. 위의 코드는 윈도우 95에서는 절대 복귀되지 않고 무한정 붙들려 있는 상태가 된다. 왜냐하면 시스템에서 EV_RING 이벤트를 검사할 수 없다. 윈도우 NT에서는 철두철미하게 EV_RING이벤트에 대하여 알려준다. 이 버그에 대한 정보는 윈32 SDK 기술자료집에 좀더 많은 정보가 있으니 참고합니다.

위의 코드는 만일 이벤트가 영원히 발생하지 않는다면 영원히 붙들려있는 상태가 된다. 더 나은 솔루션은 포트를 열 때 중첩 작업방식으로 열어서 다음과 같은 요령으로 처리하는 것이다


#define STATUS_CHECK_TIMEOUT         500     // 1000분의 1초

DWORD          dwRes;

DWORD          dwCommEvent;

DWORD          dwStoredFlags;

BOOL            fWaitingOnStat = FALSE;

OVERLAPPED       osStatus = {0};

dwStoredFlags = EV_BREAK | EV_CTS | EV_DSR | EV_ERR | EV_RING | EV_RLSD |

                EV_RXCHAR | EV_RXFLAG | EV_TXEMPTY ;

if (!SetCommMask(comHandle, dwStoredFlags))

        // 통신 마스크 설정중 오류 중단함

        return 0;

osStatus.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);

if (osStatus.hEvent == NULL)

        // 이벤트를 생성하는데 실패 중단함

        return 0;

for ( ; ; )

{

        // 상태 채크가 이미 되지 않았다면 이벤트를 체크한다.

        if (!fWaitingOnStat)

        {

                if (!WaitCommEvent(hComm, &dwCommEvent, &osStatus))

                {

                        if (GetLastError() == ERROR_IO_PENDING)

                                bWaitingOnStatusHandle = TRUE;

                        else

                                // WaitCommEvent에서 오류 발생 중단함

                                break;

                }

                else

                        // WaitCommEvent는 즉시 복귀된다.

                        // Deal with status event as appropriate.

                        ReportStatusEvent(dwCommEvent);

        }

        // 중첩 작업에서 채크.

        if (fWaitingOnStat)

        {

                // 이벤트가 발생할 때까지 잠깐 기다린다.

                dwRes=WaitForSingleObject(osStatus.hEvent, STATUS_CHECK_TIMEOUT);

                switch(dwRes)

                {

                // 이벤트가 발생.

                case WAIT_OBJECT_0: 

                        if(!GetOverlappedResult(hComm,&osStatus,&dwOvRes, FALSE))

                                // 중첩 작업에서 오류가 발생하였다

                                // GetLastError호출로 무슨일인지 확인하고

                                // 심각한 상황이면 중단한다

                        else

                                // 상태 이벤트는 WaitCommEvent를 호출할 당시 지정한

                                // 이벤트 플래그에 저장되어 있다.

                                // 이벤트를 사용한다.

                                ReportStatusEvent(dwCommEvent);

                        // Set fWaitingOnStat flag to indicate that a new

                        // WaitCommEvent is to be issued. 

                        fWaitingOnStat = FALSE;

                        break;

                case WAIT_TIMEOUT:

                        // 작업이 아직까지 완료되지 않았다.fWaitingOnStatusHandle

                        // flag isn't changed since I'll loop back around and

                        // I don't want to issue another WaitCommEvent until

                        // the first one finishes.

                        //

                        // This is a good time to do some background work.

                        DoBackgroundWork();

                        break;

                default:

                        // Error in the WaitForSingleObject; abort

                        // This indicates a problem with the OVERLAPPED

                        // structure's

                        // event handle.

                        CloseHandle(osStatus.hEvent);

                        return 0;

                }

        }

}

CloseHandle(osStatus.hEvent);


위의 코드는 중첩 읽기와 유사하다. SetCommMask 와 WaitCommEvent는 두 가지 관심 거리가 있다. 첫째, 통신 포트가 비중첩 작업으로 열었을 때는 WaitCommEvent는 이벤트가 발생할 때까지 무한정 기다린다. 만약 별도의 쓰레드가SetCommMask에 새로운 마스크를 설정하고자 한다면 그 쓰레드도 무한정 붙잡힌다. 그 이유는 첫번째 쓰레드가 WaitCommEvent를 호출하여 멈춘 상태에 있기 때문이다. SetCommMask를 호출한 쓰레드는 WaitCommEvent 펑션이 복귀될 때까지 붙잡혀 있게된다. 비중첩 입출력으로 포트를 열어두면 이러한 면이 있다. 만일 어떤 쓰레드가 직렬 통신 펑션을 호출하였을 경우 다른 두 번째 쓰레드가 통신 펑션을 호출하면 두 번째 쓰레드는 첫 번째 쓰레드가 종료될 때 까지 붙잡혀 있게 된다.

둘째, 중첩 작업에서 포트를 열었을 경우이다. 만약 SetCommMask가 새로운 이벤트 마스크를 설정하면 (예를 들면 이벤트 마스크를 NULL로 했을때)어떠한 계류중인 WaitCommEvent도 성공적으로 완료될 수 있다.


통보(Caveat)


포트에 하나의 바이트가 도착하였다는 것을 그 쓰레드에게 통보하기 위해 EV_RXCHAR 플래그를 사용한다. 이 이벤트는 ReadFile 펑션과 짝을 이루어 사용된다. 데이터가 수신될 때까지 기다리지 않고 수신된 이후에만 읽어 들이기 작업하기 가능하도록. 이것은 비중첩 자업에서 매우 유용한데 왜냐면 데이터가 들어왔는지 폴링(1초이하의 시간 간격으로 데이터가 들어왔는지 계속 읽어 보는 행위)이 필요하지 않다. 프로그램은 EV_RXCHAR이벤트로 데이터가 수신되었다는 사실을 통보받는다.


DWORD  dwCommEvent;

DWORD  dwRead;

charch   Read;

if (!SetCommMask(hComm, EV_RXCHAR))

        // 이벤트 마스크 세트에 오류.

for ( ; ; )

{

        if (WaitCommEvent(hComm, &dwCommEvent, NULL))

        {

                if (ReadFile(hComm, &chRead, 1, &dwRead, NULL))

                        // 한 바이트가 읽어졌다.; 처리요망

                else

                        // ReadFile 호출에 문제 발생 

                        break;

        }

        else

                // WaitCommEvent에서 문제

                break;

} 



위의 코드는 EV_RXCHAR 이벤트가 발생할 때까지 기다린다. 발생할 땐 ReadFile을 호출하여 한 바이트를 수신한다. 루프(반복 원위치 되는 구간)는 다시 시작되고EV_RXCHAR 이벤트가 발생할 때까지 기다린다. 이 코드는 한두 바이트가 도작할 때빨리 잇달아서 처리하기 좋다. EV_RXCHAR이벤트가 발생 하였다는 원인으로 바이트를 수신한다. WaitCommEvent을 호출하기 전에 이미 수신된 바이트가 있어도 좋다. 새로운 문자가 수신되면EV_RXCHAR가 발생하여 이미 수신된 바이트를 읽어 들이고, 새로이 수신된 바이트는 내부적으로EV_RXCHAR플래그가 세트되어 ReadFile을 호출하여 다시 읽혀질 수있다.

위 코드의 문제는 3개나 그 이상의 바이트가 연속하여 빨리 도착하였을 경우이다. 첫 번째 바이트는EV_RXCHAR 이벤트를 발생한다. 두 번째 바이트는 내부적으로 EV_RXCHAR 플래그가 세트되는 원인이 되어 다음번에 WaitCommEvent 를 호출할 때 EV_RXCHAR 이벤트가 발생하도록 한다. 이 시점에서 세 번째 바이트가 도착하였을 때 EV_RXCHAR 플래그를 내부적으로 세트하려하지만 실패한다. 왜냐하면 이미 두 번째 바이트가 도착했을 때 발생했던 것이라 세 번째 바이트가 도착한 사실은 통보되지 않는다. 결국 이 코드는 첫 바이트는 문제없이 읽는다. 그런 다음 WaitCommEvent이 호출될 것이고 두 번째 바이트에 의해서 EV_RXCHAR이벤트를 받는다. 두 번째 바이트가 읽혀지고 WaitCommEvent가 호출되면 세 번째 바이트는 시스템의 내부 수신 버퍼에 놓여진 상태로 기다린다. 코드와 시스템은 이제 동시성을 갖지 않는다. 네 번째 바이트가 성공적으로 도착하면 EV_RXCHAR이벤트가 발생하고, 세 번째 바이트였던 하나의 바이트가 읽혀지고 그렇게 계속된다.

이 문제의 손쉬운 해법은 읽기 작업의 요구 숫자를 늘이는 방법이다. 하나의 바이트를 요구하는 것보다 둘이나 열 개 또는 그이상의 숫자를 정한다. 이 생각에도 여전히 문제는 있는데 둘이나 그 이상의 남은 바이트가 있게된다. 그래서 만약 2바이트를 읽기로 했을 땐 4바이트가 연달아 도착했을 때 문제가 되고, 열 바이트를 요구하는 때에는 20바이트가 도착하였을 때 문제가 된 다.

실제 해법은 남은 바이트가 없을 때까지 읽는 방법이다. 다음은 이 문제를 해결하여 0바이트가 읽혀질 때까지 반복하여 읽는 소스이다. 또다른 가능한 방법은 ClearCommError를 사용하여 버퍼에 남아있는 바이트의 수를 파악하여 한번에 읽어들이는 작업을 하는 것이다. 이 방법은 매우 복잡한 버퍼 관리를 요구하지만 엄청나게 많은 데이터가 수신된 것을 읽어들이는 횟수를 한번으로 줄여준다.


DWORD  dwCommEvent;

DWORD  dwRead;

char     chRead;

if (!SetCommMask(hComm, EV_RXCHAR))

        // Error setting communications event mask

for ( ; ; )

{

        if (WaitCommEvent(hComm, &dwCommEvent, NULL))

        {

                do

                {

                        if (ReadFile(hComm, &chRead, 1, &dwRead, NULL))

                                // A byte has been read; process it.

                        else

                                // An error occurred in the ReadFile call.

                                break;

                } while (dwRead);

        }

        else

                // Error in WaitCommEvent

                break;

} 


위의 코드는 시간초과(time-outs)를 지정하지 않고서는 올바르게 동작하지 않는다. 통신 시간 초과는 나중에 설명되고ReadFile 작업에서 바이트가 도착할 때를 기다리지 않는 영향을 미치게 된다. "통신 시간 초과" 절을 참고하기 바란다.

위의 코드는 바이트가 연달아 도착하여도EV_RXFLAG가 발생하지 않을 것이다. 다시 강조하건대 가장 좋은 해법은 한 바이트도 남지 않을 때까지 반복하여서 읽는 것이다.

위의 통보 방법은 문자 이벤트가 아닌 다른 이벤트에서도 적용된다. 만약 다른 이벤트가 발생하고 연이어 계속 발생한다면 몇몇은 잃어버리게 될 것이다. 만약 CTS라인의 전압이 고위(high), 저위(low)로 반복되어 이벤트가 발생한다. 만약 CTS라인이 아주 빨리 변경된다면 WaitCommEvent는 실제적인 EV_CTS이벤트를 검사하는 것을 보장해 주지 않는다. 이러한 이유로 인해 WaitCommEvent는 라인 상태를 추적하는데 사용할 수 없다. 라인 상태는 "모뎀 상태들" 절에서 커버한다.


오류 핸들링과 통신 상태들


SetCommMask를 호출하여 이벤트 플래그를 지정하는 때에 EV_ERR도 지정할 수 있다.EV_ERR 이벤트는 통신 포트에 오류 컨디션이 존재한다는 것을 나타낸다. EV_ERR 이벤트를 지정하지 않은 포트에서도 오류가 발생할 수도 있는데 오류 컨디션이 삭제될 때까지 모든 입출력 작업은 억제된다. ClearCommError는 오류 검사와 오류 컨디션을 청소하기 위해 호출되는 펑션이다.

ClearCommError는 물론 통신이 왜 멈추게 되었는지 멈춘 이유도 제공한다. 또한 보내기와 받기 버퍼에 몇 개의 바이트가 대기 중인지도 나타낸다. 오류나 흐름제어에 의하여 통신이 멈출 수 있다. 흐름제어는 뒤에 이 문서에서 설명한다.

여기 ClearCommError 호출하는 법을 보여주는 예시가 있다.


COMSTAT comStat;

DWORD  dwErrors;

BOOL    fOOP, fOVERRUN, fPTO, fRXOVER, fRXPARITY, fTXFULL;

BOOL    fBREAK, fDNS, fFRAME, fIOE, fMODE;

// Get and clear current errors on the port.

if (!ClearCommError(hComm, &dwErrors, &comStat))

        // Report error in ClearCommError.

        return;

// Get error flags.

fDNS = dwErrors &CE_DNS;

fIOE = dwErrors &CE_IOE;

fOOP = dwErrors &CE_OOP;

fPTO = dwErrors &CE_PTO;

fMODE = dwErrors &CE_MODE;

fBREAK = dwErrors &CE_BREAK;

fFRAME = dwErrors &CE_FRAME;

fRXOVER = dwErrors &CE_RXOVER;

fTXFULL = dwErrors &CE_TXFULL;

fOVERRUN = dwErrors &CE_OVERRUN;

fRXPARITY = dwErrors &CE_RXPARITY;

// COMSTAT structure contains information regarding

// communications status.

if (comStat.fCtsHold)

        // Tx waiting for CTS signal

if (comStat.fDsrHold)

        // Tx waiting for DSR signal

if (comStat.fRlsdHold)

        // Tx waiting for RLSD signal

if (comStat.fXoffHold)

        // Tx waiting, XOFF char rec'd

if (comStat.fXoffSent)

        // Tx waiting, XOFF char sent

if (comStat.fEof)

        // EOF character received

if (comStat.fTxim)

        // Character waiting for Tx; char queued with TransmitCommChar

if (comStat.cbInQue)

        // comStat.cbInQue bytes have been received, but not read

if (comStat.cbOutQue)

        // comStat.cbOutQue bytes are awaiting transfer


모뎀 상태


EV_CTS, EV_DSR, EV_RING, and EV_RLSD플래그를 포함하여 SetCommMask를 호출할 수 있다. 이 플래그들은 직렬 포트의 라인에 전압이 변경된 것을 나타낸다. 변화가 발생하여도 실질적인 이들의 라인의 상태를 나타내는 것은 없다. GetCommModemStatus펑션은 실질적인 이들 라인의 상태를 비트 마스크로 0이면 저위(low)이고 1이면 고위(high)로 각 라인의 상태를 나타내 준다.

기억할 것은RLSD (Receive Line Signal Detect)는 일반적으로 CD (Carrier Detect)라인을 참조한다.

주의사항: EV_RING플래그는 윈도우95에서는 앞서 설명한 바와같이 동작하지않는다. 그러나 GetCommModemStatus 펑션은 RING라인을 검색한다.

이들 라인이 변경되면 흐름제어 이벤트의 원인이 된다. ClearCommError펑션은 흐름제어에 의하여 송수신이 억제된 상태를 알려준다.만약 필요하다면 쓰레드는 ClearCommError펑션을 호출하여 이벤트가 발생한 상황을 검사한다. 흐름제어는 "흐름제어" 절에서 자세히 설명한다.




GetCommModemStatus 호출법 예시

        DWORD dwModemStatus;

        BOOLfCTS, fDSR, fRING, fRLSD;

        if (!GetCommModemStatus(hComm, &dwModemStatus))

                // Error in GetCommModemStatus;

                return;

        fCTS = MS_CTS_ON &dwModemStatus; 

        fDSR = MS_DSR_ON &dwModemStatus;

        fRING = MS_RING_ON &dwModemStatus;

        fRLSD = MS_RLSD_ON &dwModemStatus;

        // Do something with the flags.


확장된 펑션들


장치 제어기(통신 포트를 제어하는 소프트웨어, 예를 들면 Windows폴더 안의 System 폴더안에Serial.Vxd 파일과 같은 것) 는 제어 라인(직렬 통신 전선 상에서 흐름제어를 위해 사용하는 전선 가닥)의 상태를 필요에 따라 자동적 으로 변경한다. 일반적으로 라인들의 상태를 제어하는 것은 제어기에 의한 것이다. 만약 RS-232 표준과는 다른 방식으로 포트의 제어라인을 사용하고자 않다면 표준 직렬 장치 제어기는 동작하지 않을 것이다. 만약 표준 직렬 통신 장치제어기가 장치를 제어하지 못한다면 별도 제작된 장치 제어기가 필요하다.

그러한 일이 닥치면 응용프로그램은 직접 스스로 흐름 제어를 하여야 한다. 응용프로그램은 RTS와 DTR라인의 상태를 직접제어해야 하는 책임이 있다. EscapeCommFunction는 직접적으로 특별한 작업을 할 수 있다. EscapeCommFunction는 브레이크를 설정하거나 지우는 것과 같은 컨디션을 할 수 있다. 이에 대하여 더 많은 정보를 원한다면 Win32 SDK 문서를 참조한다.


직렬 설정들


DCB 설정


직렬통신 프로그래밍에서 아주 중요한 것은 장치 제어 블록 구조체 (Device-Control Block, DCB)를 설정하는 것이다. 직렬 통신 프로그래밍의 대부분의 공통적인 오류는 이것을 잘못 설정한 것에 기인한다. 직렬 통신이 원하는데로 잘 동작하지 않으면 DCB구조체가 잘되어 있는지 문제점을 찾아 본다.

DCB구조체를 초기화 하는 방법은 3가지가 있다. 첫 번째 방법은 GetCommState펑션을 사용하는 방법으로 이 펑션은 통신 포트의 현재 DCB를 돌려준다. 다음 코드는 GetCommState펑션의 사용법을 보인다.


        DCB    dcb = {0};

        if (!GetCommState(hComm, &dcb))

                // Error getting current DCB settings

        else

                // DCB is ready for use.

두 번째 방법은 BuildCommDCB를 사용하여 DCB를 초기화 하는 방법이다. 이 펑션은 DCB에 통신 속도, 패리티 종류, 스톱 비트 수, 데이터 비트의 수를 채워준다. 흐름제어 항목들은 기본 값으로 설정한다. BuildCommDCB펑션의 흐름제어에 대한 기본 값은 BuildCommDCB펑션에 대한 설명 문서를 참조한다. DCB의 다른 항목들은 이 펑션에 의하여 영향받지 않으므로 프로그램짜는 것을 난잡하게 한다. 아래에 사용법에 대한 소스이다.


        DCB dcb;

        FillMemory(&dcb, sizeof(dcb), 0);

        dcb.DCBlength = sizeof(dcb);

        if (!BuildCommDCB("9600,n,8,1", &dcb))

        {

                // Couldn't build the DCB. Usually a problem

                // with the communications specification string.

                return FALSE;

        }

        else

        // DCB is ready for use.


세 번째 방법은 DCB 구조체를 수작업으로 하는 것이다. DCB구조체의 메모리 를 할당하고 각 항목들을 설정한다. 이 방법은 향후 DCB가 변경될 소지가 있으므로 Win32에서는 권장할 것이 못된다.

응용프로그램은 일반적으로 DCB의 항목을 실행 도중에라도 변경해야 할 필요가 있다.DCB 구조체의 멤버를 수정하여도 SetCommState를 호출하기 전까지는 아무런 효과가 없다. 여기 예제에는 현재의 DCB를 구하여 통신 속도를 변경하여 적용시키는 것이다.


        DCB dcb;

        FillMemory(&dcb, sizeof(dcb), 0);

        if (!GetCommState(hComm, &dcb))

                // get current DCB

                // Error in GetCommState

                return FALSE;

        //DCB 속도 갱신.

        dcb.BaudRate = CBR_9600 ;

        // 새로운 상태로 설정 I

        f (!SetCommState(hComm, &dcb))


여기에 DCB의 각 멤버가 어떠한 의미를 갖는지에 대한 설명이 있다.

주의사항 : 대부분의 정보는 Win32 SDK 문서에 있다. 이 표는 운영체제의 변경이 일어나면 예고없이 변경될 수 있다.


DCB 구조체 항목들

DCBlength

바이트 단위의 크기. 이 구조체. SetCommState를 호출하기 이전에 세트되어야 한다.

BaudRate

통신 포트의 전송 속도를 지정한다. 이 항목은 실제적인 속 도 이거나 속도의 인덱스이다.(CBR_9600이면 9600BPS)

fBinary

이진(binary)모드 가능함을 지정. Win32 API는 이진모드만 을 지원하므로 이 항목은 반드시 TRUE이어야만 한다. FALSE로 해보면 동작하지 않는다.

fParity

패리티 검사 여부를 지정한다. 이를 TRUE로 하면 패리티 검 사가 수행되고 오류가 발생하면 알려지게 된다. Parity와 혼돈하기 쉬운데 이것은 통신에서 패리티 타입을 지정한 다.

fOutxCtsFlow

송신 흐름 제어를 위하여 CTS신호를 주시할 것을 지정한다. 이 항목이 TRUE이고 CTS가 저위이면 고위가 될 때까지 송 신은 억제된다. The CTS signal is under control of the DCE (usually a modem), the DTE (usually the PC) simply monitors the status of this signal, the DTE does not change it.

fOutxDsrFlow

송신 흐름 제어를 위하여 DSR (data-set-ready) 신호를 주 시할 것을 지정한다. 이 항목이 TRUE이고 DSR이 저위일 경 우 고위가 될 때까지 송신은 억제된다. Once again, this signal is under the control of the DCE; the DTE only monitors this signal.

fDtrControl

수신 흐름 제어를 위하여 DTR (data-terminal-ready)을 사 용한다. 이 항목은 다음 중의 하나가 될 수 있다.


DTR_CONTROL_DISABLE 장치를 열었을 때 DTR 라인을 저위로 한다. 응용프로그램은 EscapeCommFunction으로 조정할 수 있다. DTR_CONTROL_ENABLE 장치를 열었을 때 DTR 라인을 고위로 한다. 응용프로그램은 EscapeCommFunction으로 조정할 수 있다. DTR_CONTROL_HANDSHAKE DTR 흐름제어 신호 변경 가능하도록 한다. 이 값이 사용되면 EscapeCommFunction으로 조정할 때 오류가 발생한다.


fDsrSensitivity

통신 제어기가 DSR신호에 민감하게 한다. 이 항목이 TRUE 이면 제어기는 수신된 모든 바이트를 무시하고, 그렇지 않 다면 DSR 모뎀 입력 라인이 고위가 된다.

fTXContinueOnXoff

수신 버퍼가 가득 찼을 때 송신을 중단하고 제어기는 XOFF문자를 송신할 것을 지정한다. 이 항목이 TRUE이면 XOFF문자가 송신되고 송신이 계속된다. 이 항목이 FALSE이 면 수신 버퍼가 XonLim 바이트 이하가 될 때까지 송신은 멈추고 XonLim 바이트 이하가 되면 제어기는 XON 문자를 전송한다.

fOutX

송신 중에 XON/XOFF 흐름제어를 할것인지를 지정한다. TRUE 이면 XOFF 문자가 수신되었을 때 송신을 중단하고 XON문자 가 수신되면 송신을 재개한다.

fInX

수신중에 XON/XOFF 흐름제어를 할것인지를 지정한다. 만약 이 항목이 TRUE이면 XoffLim 바이트로 버퍼가 가득 찼을 때 XOFF 문자를 송신하고XonLim 바이트 이하로 비었을 때 XON 문자를 송신한다.

fErrorChar

수신도중에 패리티 오류가 발생하면 이 문자로 ErrorChar 항목에서 지정한 문자로 대치할 것인가를 지정한다. 이 항 목이 TRUE이고 fParity 항목이 TRUE이면 오류가 발생하였 을 경우에 대체된다.

fNull

NULL 바이트를 제외할 것인지 지정한다. TRUE이면 NULL이 수신되었을 때 무시한다.

fRtsControl

RTS (request-to-send) 입력 흐름 제어. 이 값이 0이면 기 본 값인 RTS_CONTROL_HANDSHAKE가 된다. 이 항목은 다음 중 하나가 될 수 있다.


RTS_CONTROL_DISABLE 장치가 열리면 RTS 라인은 저위가 된다. 응용프로그램은 EscapeCommFunction으로 조정할 수 있다. RTS_CONTROL_ENABLE 장치가 열리면 RTS 라인은 고위가 된다. 응용프로그램은 EscapeCommFunction으로 조정할 수 있다. RTS_CONTROL_HANDSHAKE RTS 흐름제어를 자동으로 하게 한다. 제어기는 입력 버퍼가 데이터를 받아들이기에 충분한 공간을 가지고 있을 때 RTS를 고위로 하고 DCE를 enabling한다. 입력 버퍼가 충분한 공간을 가지고 있지 않을 때 RTS라인을 저위로하고, 송신을 위해 DCE를 Prevening한다. 이 값을 사용하면 응용프로그램은 EscapeCommFunction으로 조정할 때 오류가 발생한다. RTS_CONTROL_TOGGLE 송신할 것이 있으면 RTS라인은 고위가 되고 송신이 모두 끝나면 RTS라인은 저위가 된다. 이 값을 사용하면 응용프로그램은 EscapeCommFunction으로 조정할 때 오류가 발생한다. 이 값은 윈도우 95에서 는 무시되어 RTS_CONTROL_ENABLE 지정한 상태가 된다.


fAbortOnError

오류가 발생하면 읽기와 쓰기 작업이 종료될 것인가를 정한 다. 이 항목이 TRUE이면 오류(ERROR_IO_ABORTED)가 발생하 였을 때 제어기는 읽기와 쓰기 작업을 중지하고 오류 상태 와 함께 종료된다. 제어기는 응용프로그램에서 ClearCommError 펑션으로 오류가 발생한 상황을 알아챌 때 까지 모든 통신 작업을 중지한다.

fDummy2

Reserved; do not use.

wReserved

Not used; must be set to zero.

XonLim

XON문자를 보내기 전에 입력 버퍼에 놓일 수 있는 최소 바 이트 수를 지정한다.

XoffLim

XOFF문자를 보내기 전에 입력 버퍼 안에 놓일 수 있는 최대 바이트 수를 지정한다.

Parity

사용하고자 하는 패리티 방식을 지정한다. 이 항목은 다음 값들 중의 하나가 될 수 있다.

EVENPARITY

짝수

MARKPARITY

마크

NOPARITY

패리티 없음

ODDPARITY

홀수

StopBits

사용하고자 하는 스톱 비트 수를 지정한다. 이 항목은 다음 값들 중의 하나가 될 수 있다.

ONESTOPBIT

1개의 스톱비트

ONE5STOPBITS

1.5개의 스톱비트

TWOSTOPBITS

2개의 스톱비트

XonChar

송신과 수신에서 사용되는 XON 문자 값을 지정한다.

XonChar

송신과 수신에서 사용되는 XON 문자 값을 지정한다.

ErrorChar

패리티 오류가 발생할 때 대치하여 사용할 문자를 지정한다.

EofChar

데이터의 끝 신호로 사용할 문자의 값을 지정한다.

EvtChar

EV_RXFLAG 이벤트가 발생함을 알리는 문자값을 지정한다. 이 설정은 SetCommMask 펑션과 WaitCommEvent를 사용하는 중에 EV_RXFLAG 없이 사용한다면 아무런 일도 발생하지 않 는다.

wReserved1

Reserved; do not use.


흐름 제어


흐름제어는 직렬 통신에서 제공되는 메카니즘으로서 하나의 장치가 바쁘거나 어떤 이유로 인하여 어떤 통신도 불가능한 상태에 있는 동안에 통신을 억제하는 것이다. 거기에는 2가지 방식의 흐름제어가 있는데 하드웨어와 소프트웨어이다.

직렬 통신에서 공통적인 문제점은 쓰기 작업인데, 실질적으로 장치에 데이터 쓰기가 되지 않는 것이다. 흔히 프로그램에서 흐름제어에 대한 명시를 하지 않은 경우에 문제점이 있다. A close examination of the DCB structure reveals that one or more of the following members may be TRUE: fOutxCtsFlow, fOutxDsrFlow, or fOutX. Another mechanism to reveal that flow control is enabled is to call ClearCommError and examine the COMSTAT structure. It will reveal when transmission is suspended because of flow control. 흐름제어의 타입들을 설명하기에 앞서 이해를 돕기위한 설명을 하자면. 직렬 통신은 장치와 장치 사이에 있다. 전통적으로 그것들은 PC와 모뎀, PC와 프린터이다. PC는 데이터 터미널 장치(Data Terminal Equipment, DTE)라고 이 름이 붙고, DTE는 때로는 호스트라고도 한다. 모뎀이나 프린터 등은 데이터 통신 장치(Data Communications Equipment,DCE)라고 이름이 붙는다. DCE는 때론 장치(device)이다.


하드웨어 흐름 제어


하드웨어 흐름제어는 제어 라인에 전압을 이용하여 송신과 수신의 가능함을 제어하는 것이다. DTE와 DCE는 한 종류의 흐름 제어를 사용하여야만 한다. DCB 구초제에 흐름제어를 가능하도록 DTE에서만 세트하면 된다. 윈32에서는 DCE에 대한 흐름 제어를 하는 메카니즘을 제공하지 않는다. 장치에 있는 딥 스위치들을 이용한다거나 명령을 보낸다.

다음은 제어 라인, 흐름 제어 방향에 대하여 설명한다.


< 하드웨어 흐름제어 라인들 >

CTS(Clear To Send) : Output flow control

DCE는 데이터를 받을 수 있을 때 고위(high, 전압이 높음) 상태로 한다. DCE는 데이터를 받을 수 없을 때 저위(Low, 전압이 낮음) 상태로 한다. DCB 의 fOutxCtsFlow 항목이 TRUE이면 라인이 저위일 때 DTE는 데이터를 보내지 않을 것이다. 라인이 고위가 되면 보내기가 계속된다. 만약 DCB의 fOutxCtsFlow항목이 FALSE이면 라인의 상태는 송수신에 영향을 주지 않는다.


DSR(Data Set Ready) Output flow control

DCE 는 데이터를 수신할 수 있으면 고위로 한다. DCE 는 데이터를 수신할 수 없으면 저위로 한다. DCB의 fOutxDsrFlow 항목이 TRUE이면 DTE는 이 라인이 저위일 때 데이터를 보내지 않는다.라인이 고위가 되면 보내기를 계속한다. 만약 DCB의 fOutxDsrFlow항목이 FALSE이면 라인의 상태는 송수신에 영향을 주지 않는다.


DSR(Data Set Ready)

DSR라인이 저위일 때 포트를 통해 들어온 데이터는 무시된다. DSR라인이 고위일 경우 도착한 데이터는 포트에 수신된다. DCB의 fDsrSensitivity 가 TRUE일 경우 이렇게 되며, FALSE이면 송수신에 영향을 주지 않는다.


RTS (Ready To Send)Input flow control

RTS라인은 DTE에서 제어된다. DCB의 fRtsControl 항목이 RTS_CONTROL_HANDSHAKE로 되어 있을 때 흐름제어가 된다. 입력 버퍼가 데이터를 받기에 충분하면(적어도 버퍼의 절반이상이 비어야 한다) 장치 제어기는 RTS를 고위로 한다. 입력 버퍼가 데이터를 받기에 적으면(4분의 1보다 적게 버퍼가 비어 있는 경우) 장치 제어기는 RTS라인을 저위로 한다.

DCB의 fRtsControl 항목이 RTS_CONTROL_TOGGLE로 되어 있다면 보낼 데이터가 있으면 장치 제어기는 RTS를 고위로 한다. 보낼 데이터가 없으면 장치 제어기는 이 라인을 저위로 한다. 윈도우 95에서는 이 값이 무시되고 RTS_CONTROL_ENABLE과 같은 값이 된다.

DCB의 fRtsControl 항목이 RTS_CONTROL_ENABLE이나 RTS_CONTROL_DISABLE로 세트되면 응용프로그램은 필요에 따라 라인의 상태를 바꿀 수 있다. 이러한 경우엔 이 라인은 수신에 영향을 주지 않는다. DCE는 라인이 저위인 동안 송신을 억제한다. 라인이 고위가 되면 송신을 계속한다.


DTR(Data Terminal Ready)Input flow control

DTE에 의하여 DTR 라인이 제어된다.DCB의 fDtrControl항목이 DTR_CONTROL_HANDSHAKE로 설정되어 있다면 흐름제어가 사용된다.입력 버퍼가 충분하면(절반이상 비어있어야 함) 장치 제어기는 DTR라인을 고위로 한다. 만약 입력 버퍼가 작으면(4분의 1 이하만 비어 있는 경우)는 장치 제어기는 DTR를 저위로 한다. DCB의 fDtrControl 항목이 DTR_CONTROL_ENABLE 이나 DTR_CONTROL_DISABLE로 설정되어 있다면 응용프로그램은 이 라인의 상태를 필요에 따라 자유로이 변경할 수 있다. 이 경우 이 라인의 상태를 수신에 영향을 주지 않는다.

라인이 저위가 되면 DCE는 송신을 억제하고 라인이 고위가 되면 DCE는 송신을 계속한다. 흐름제어가 필요한 것은 CE_RXOVER 오류가 발생했을 때 쉽게 알아내기 위함이다. 이 오류는 수신 버퍼가 넘치고 데이터를 잃어 버렸다는 것을 나타낸다. 데이터를 읽는 것보다 빨리 수신되는 경우에 CE_RXOVER 가 발생할 수 있다. 수신 버퍼의 크기를 늘리는 것은 오류가 발생하는 빈도를 줄인다. 그러나 완벽하게 문제를 해결하는것이 아니다. 장치 제어기가 수신 버퍼가 이미 가득 찼다는 것을 검색하면 제어기는 입력 흐름 제어 라인들을 저위로 할 것이다. 이는 DCE로 하여금 송신을 멈추게 하고, DTE로 하여금 수신 버퍼에서 읽들일 시간을 준다. 수신 버퍼가 비게 되면 흐름제어 라인의 전압이 올라가서 DCE는 보내기를 계속한다.

이와 유사한 오류는 CE_OVERRUN이다. 이 오류는 새로운 데이터가 이전의 데이터 수신을 완료하기 전에 도착하였을 때 발생한다. 이 경우는 전송 속도가 통신 하드웨어나 CPU의 타입에 비해 너무 빠를 경우에 발생한다. 또한 운영체제가 통신 하드웨어를 서비스 하기에 바쁠 경우에도 발생한다. 이 문제점을 해결하는 방법은 통신 속도를 낮추거나 통신 하드웨어를 교체하거나 CPU의 속도를 증가시키는 방법이다. 때로는 (써드 파티)제3의 하드웨어 장치 제어기가 CPU와 매우 비능률적인 방법으로 동작하여 이런 오류가 발생하기도 한다. 흐름제어는 CE_OVERRUN 문제를 완벽하게 해결할 수는 없고 단지 발생 빈도를 줄이는 것이다.


728x90

'WORK > Sotfware' 카테고리의 다른 글

pragma에 관한 사용법  (0) 2008.07.17
AVR 90s8535  (0) 2008.07.15
AVR 다운로드 프로그램  (0) 2008.07.11
makefile  (0) 2008.07.11
AcroEdit AVR 컴파일 하기.  (0) 2008.07.11

댓글