1. Serial 프로그래밍

본 장에서는 COM Port를 이용하여 Serial 통신을 하는 클래스를 만들고 이를 이용하여 Modem에 AT 명령어를 보내어 모뎀을 제어하고 HITEL 등의 PC 통신에 접속하는 방법에 대해서 설명할 것입니다. 이때 간단한 ANSI 제어도 하게 될 것입니다.

대상자: 중급,고급

Chapter 등급: 선택

공부방법: 본내용은 RS-232C 제어가 필요하거나 통신 애물레이터를 만드는 부분이 필요할 때 예제를 작성해 보는 방향으로 공부하시기 바랍니다.

1. Serial Port 제어

DOS에서는 Serial Port를 제어하기 위해서 Interrupt를 이용하거나 직접 UART를 제어하여야만 하였습니다. 그러나 UNIX나 Window등 Workstation 이상 급의 컴퓨터에서는 가상 디바이스 개념이 적용되어 하드웨어를 직접 제어할 수 없도록 되었습니다. 그 대신에 디바이스 드라이버를 통해 하드웨어를 제어 할 수 있도록 되어 있습니다. Windows나 UINX에서는 특별히 Serial Port나 Parallel Port등의 기본적이고 자주 쓰이는 장치는 일반 File과 같이 Open, Close하여 Read, Write할 수 있도록 하였습니다. COM1, COM2, LPT1, LPT2 등의 장치 명으로 된 것들이 그러한 것들입니다. 물론 시스템에 기본적으로 정해진 이런 드라이버를 사용하지 않고 새로이 Serial 통신을 위해 디바이스 드라이버를 만들 수도 있으나 시스템에 내장된 디바이스 드라이버는 제작사의 검증을 받은 안정된 장치이므로 새로운 디바이스 드라이버를 만드는 것보다 이런  장치를 이용하는 것이 훨씬 안정된 프로그램을 할 수 있습니다.

<1> COM Port Open

Windows에서 Serial Port인 COM Port를 스트림으로 Open 하려고 할 경우는 CreateFile()함수를 사용합니다.

HANDLE CreateFile(

LPCTSTR lpFileName,          // 디바이스 파일 이름

DWORD dwDesiredAccess,      // 파일 억세스 모드

DWORD dwShareMode,         // 공유 모드

LPSECURITY_ATTRIBUTES lpSecurityAttributes,   // 보안 정보

DWORD dwCreationDisposition,  // 생성 방법

DWORD dwFlagsAndAttributes,  // 파일 플래그와 속성

HANDLE hTemplateFile

);

CreateFile로 Open된 시리얼 포트는 이 함수에서 리턴된 파일 핸들을 이용하여 콘	트롤 하게됩니다. 이 파일 핸들을 이용해서 데이터를 쓰거나 읽는 것은 직접	UART Buffer에서 데이터를 읽는 것과 동일한 역할을 하게되는 것입니다.

lpFileName 은 Open하고자 하는 디바이스 파일의 이름을 말합니다. 만약 COM1을	Open하고자 하는 경우 여기에 □COM1"이란 String을 주면 됩니다. 만약	COM1~COM4 이상의 Port를 사용하려고 하는 경우는 Windows System에서	MAX_PATH로 제한하고 있는 파일 길이를 초과 하게되므로 사용할 수가 없습니	다. 이런 경우에는 CreateFile의 Wide Version을 이용하여 확장하면 됩니다. 이 경	우에도 특별히 달라지는 것은 없고 단지 Device File Name 앞에 "\\.\"를 덧붙여	주면 됩니다. 즉	COM10을 Open하고자 하는 경우라면 □COM10" 대신에	"\\.\COM10"이라고 써주기만 하면 됩니다.

dwDesiredAccess는 Device File을 어떤 모드로 Access할 지를 정해주는 부분입니	다. 여기서는 3가지 모드를 조합하여 설정해 줄 수 있는데

0	: 디바이스의 특성만을 물어보고 데이터를 Read/Write하지 않을	경우

GENERIC_READ  : 디바이스에서 데이터를 읽고 파일 포인터를 움직일 수 있도록	한다.

GENERIC_WRITE : 디바이스에서 데이터를 쓰고 파일 포인터를 움직일 수 있도록	한다.

COM Port의 경우 우리는 데이터를 읽고 쓰는 것이 모두 가능해야 하므로

GENERIC_READ|GENERIC_WRITE로 Open해야 합니다..

dwShareMode는 파일을 어떤 특성으로(읽기 전용, 읽기/쓰기) 열 것이가를 설정하	는 부분인데, Device File의 경우는 공유될 수 없으므로 그냥 0으로 Setting합니다.

lpSecurityAttributes는 파일의 보안을 설정해 주는 부분인데 마찬가지로 디바이스	파일에서는 사용하지 않으므로 NULL로 Setting합니다.

dwCreationDisposition은 파일을 생성할 때 새로이 만들 것인가 아니면 기존의 것	에 덧붙일 것인가 아니면 이미 존재하는 경우에만 생성할 것인가를 정해주는 부분	입니다. 디바이스 파일은 이미 존재하고 있지 않다면 생성할 수 있는 것이 아니므	로 이 부분은 항상 OPEN_EXISTING으로 Setting되어야만 합니다. 그렇지 않으면	오류를 일으키게 되므로 주의하여야 합니다.

dwFlagsAndAttributes는 파일의 속성을 정해주는 부분인데 디바이스 파일의 경우	는 FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED로 설정해 주어야	합니다.

FILE_ATTRIBUTE_NORMAL : 다른 특성을 가지고 있지 않은 일반 파일임을 의	미합니다.

FILE_FLAG_OVERLAPPED : 일반 File의 경우는 데이터를 File에 Write할 때	Caching을 하게되어 있으나 디바이스 파일의 경우	는 Write할 때 현재 버퍼에 들어있는 내용을 다 전	송하고 나서 전송하여만 합니다. 그러므로 이 Flag	를 Set하여 주어서 전송하기 전에 시스템에서 전송	하고자 하는 장치를 초기화하고 읽기나 쓰기가 끝난	경우 메시지를 띄워 프로그램에 알려 주도록 해야	합니다. 이런 목적에서 이 Flag를 Setting할 경우엔	항상 데이터를 쓰고 읽는 함수에서 OVERAPPED	Structure를 이용하여 Overapped Read/Write를 해	주어야만 합니다.

hTemplateFile은 GENERIC_READ로 Access Mode를 Setting 하였을 때 템플릿	파일을 제공하도록 하는 것으로 템플릿 파일은 Win NT에서 만 제공되므로 Win95	나 Win98에서 사용하고자 하는 경우에는 항상 NULL로 Setting하여야만 합니다.

이상과 같이 Setting해주고 File을 Open하면 성공시에는 Device 파일을 제어하기	위한 Handle이 리턴 되고 실패하는 경우는 INVALID_HANDLE_VALUE가 Return	됩니다.

<2> COM Monitor Event 설정

COM Port를 Open하고 나면 이제 COM Port에서 발생하는 이벤트 중 어떤 이벤트를 사용할 지를 결정해 주어야만 합니다. 이것을 해주는 함수는 SetCommMask()입니다.

BOOL SetCommMask(

HANDLE hFile,       // Device 파일의 핸들

DWORD dwEvtMask  // 가능한 이벤트 중 사용할 것만 마스크 시킨다.

);

hFile은 위의 CreateFile에서 리턴된 파일 핸들 입니다.

dwEvtMask는 사용가능 한 이벤트 중 어느 것을 사용할 지 설정해주는 정보이며	EV_BREAK, EV_CTS, EV_DSR, EV_ERR, EV_RING, EV_RLSD, EV_RXCHAR,	EV_RXFLAG, EV_TXEMPTY 같은 값들을 조합하여 만들어 줄 수 있습니다. 본	예제에서는 단순히 데이터가 들어오는 지만 체크하므로 EV_RXCHAR만 사용하지	만 보다 정교한 제어를 원하는 경우엔 필요로 하는 플래그를 추가하여 처리 할 수	있습니다.

<3> 입출력 버퍼 크기 설정

다음으로 해 줄 일은 입출력 큐의 크기를 정해주는 것입니다. 입출력 큐의 크기를 정해주는 함수는 SetupComm() 입니다.

BOOL SetupComm(

HANDLE hFile,       // Device 파일의 핸들

DWORD dwInQueue,  // 입력 buffer의 크기

DWORD dwOutQueue // 출력 buffer의 크기

);

입출력 버퍼의 크기는 주고받는 데이터의 량에 따라 결정되겠지만 일반적으로 4096	(80*25*4) 정도의 크기로 설정해 주는 것이 좋습니다. 80*25는 VT100 단말기의 모	니터 사이즈입니다.

<4> 입출력 버퍼의 초기화

처음 버퍼를 생성하고 난 후에는 시리얼 포트에 대기하고 있던 모든 입출력 데이터	를 제거하고 전송이나 수신하기 위해 대기하고 있던 것들을 없애 주어야 합니다.	이 일을 해주는 함수가 PurgeComm()입니다.

BOOL PurgeComm(

HANDLE hFile,    // Device 파일의 핸들

DWORD dwFlags  // 수행 작업

);

dwFlags는 초기화 시 수행할 작업들을 설정해 주는 것으로 다음과 같습니다.

PURGE_TXABORT : Overapped 전송 작업을 취소 합니다.

PURGE_RXABORT : Overapped 전송 작업을 취소 합니다.

PURGE_TXCLEAR : 출력 버퍼를 클리어

PURGE_RXCLEAR : 입력 버퍼를 클리어

<5> 타임아웃 값 설정

이제 읽기 쓰기 Overapped I/O를 위한 Time Out 값들을 설정해 주어야할 단계입	니다. MFC에서 Serial Port의 Timeout 값을 설정해 주기 위해 사용하는 structure	는  COMMTIMEOUTS입니다.

typedef struct _COMMTIMEOUTS {

DWORD ReadIntervalTimeout;

DWORD ReadTotalTimeoutMultiplier;

DWORD ReadTotalTimeoutConstant;

DWORD WriteTotalTimeoutMultiplier;

DWORD WriteTotalTimeoutConstant;

} COMMTIMEOUTS,*LPCOMMTIMEOUTS;

ReadIntervalTimeout : 데이터가 들어올 때 두 바이트의 입력이 이루어지는 시간으	로 이 이상의 시간만큼 다음 바이트의 입력이 없을 경우 입력 값을 리턴하고	RX_CHAR Message를 발생시킵니다.

ReadTotalTimeoutMultiplier*ReadTotalTimeoutConstant :	읽기 동작에 걸리는 총	시간으로 이 값이 0이면 사용하지 않는다는 뜻입니다.

WriteTotalTimeoutMultiplier*WriteTotalTimeoutConstant : 쓰기 동작에 걸리는 총	시간으로 이 값이 0이면 사용하지 않는 다는 뜻입니다.

위의 값들을 설정한 후 SetCommTimeouts() 함수를 호출하여 Timeout값을 설정합	니다.

BOOL SetCommTimeouts(

HANDLE hFile,  // Device 파일의 핸들

LPCOMMTIMEOUTS lpCommTimeouts // COMMTIMEOUTS

);

즉, ReadIntervalTimeout만큼 기다리다 다음 데이터가 없으면 읽은 데이터를 리턴	하고, ReadTotalTimeoutMultiplier * ReadTotalTimeoutConstant의 시간만큼 기다	려도 읽기로한 데이터 사이즈만큼 읽지 못하거나	WriteTotalTimeoutMultiplier * WriteTotalTimeoutConstant 시간 안에 전송을 못	할 경우 에러 이벤트를 발생시키도록 설정하는 것입니다.

<6> 포트 설정

다음은 포트의 전송 속도, 데이터 비트 수, 패리티, 스톱 비트 수, XON/XOFF	Flow Control 설정을 해줄 차례입니다. MFC에서는 이런 설정을 위해 DCB라는	structure를 제공합니다.

typedef struct _DCB {

DWORD DCBlength;           // sizeof(DCB)

DWORD BaudRate;            // baud rate

DWORD fBinary: 1;            // binary mode, EOF을 check 하지 않음

DWORD fParity: 1;            // Parity를 체크함

DWORD fOutxCtsFlow:1;      // CTS output flow control을 함

DWORD fOutxDsrFlow:1;      // DSR output flow control을 함

DWORD fDtrControl:2;         // DTR flow control type

DWORD fDsrSensitivity:1;      // DSR 감지

DWORD fTXContinueOnXoff:1; // XOFF 라도 전송을 계속함

DWORD fOutX: 1;             // XON/XOFF 전송 흐름 제어

DWORD fInX: 1;               // XON/XOFF 입력 흐름 제어

DWORD fErrorChar: 1;         // 오류 수정

DWORD fNull: 1;               // 입력 받은 데이터 중 NULL을 없앰

DWORD fRtsControl:2;          // RTS flow control

DWORD fAbortOnError:1;       // Error 발생 시 입출력 취소

DWORD fDummy2:17;          // reserved

WORD wReserved;             // 현재 사용 안함

WORD XonLim;                // 전송 XON 제한 치

WORD XoffLim;               // 전송 XOFF 제한 치

BYTE ByteSize;               // 한 Byte당 비트 수

BYTE Parity;                  // 0-4=no,odd,even,mark,space

BYTE StopBits;                // 0,1,2 = 1, 1.5, 2

char XonChar;                  // XON으로 사용할 문자

char XoffChar;                  // XOFF로 사용할 문자

char ErrorChar;                // 오류 시 대체할 문자

char EofChar;                  // EOF 문자

char EvtChar;                  // 수신 이벤트 문자

WORD wReserved1;            // reserved

} DCB;

위의 값들을 모두 일일이 설정해도 되지만 편의를 위해 GetCommState() 함수를	이용할 수도 있습니다. 이 함수는 시스템에서 이미 사용 중인 설정을 읽어옵니다.

BOOL GetCommState(

HANDLE hFile,   // Device 파일의 핸들

LPDCB lpDCB    // DCB의 Pointer

);

이렇게 읽어온 설정 중 원하는 설정을 바꾸어 주고 SetCommState() 함수를 이용	해서 다시 설정해 줍니다.

BOOL SetCommState(

HANDLE hFile,   // Device 파일의 핸들

LPDCB lpDCB    // DCB의 Pointer

);

<7> 포트 감시 쓰레드 생성과 데이터 수신

포트로 데이터가 들어오는 지를 알기 위해서는 계속해서 포트에서 데이터를 읽는	방법이 있을 수 있습니다. 그렇지만 프로그램 전체에서 이런 식으로 무한 루프를	이용해서 입력을 감시하는 것은 좋지 못한 방법일 뿐 아니라 메시지를 기반으로 움	직이는 Windows의 철학에도 맞지 않는 방법입니다. 이와 별도로 윈도우에서는	WaitCommEvent() 함수를 제공합니다. 이 함수를 실행하면 프로세스는 아무 것도	하고 있지 않다가 COM Port에서 위에서 설정한 Event가 발생할 경우 리턴됩니다.

BOOL WaitCommEvent(

HANDLE hFile,                // Device 파일의 핸들

LPDWORD lpEvtMask,         // 리턴 된 이벤트를 저장할 포인터

LPOVERLAPPED lpOverlapped, // Overapped structure에로의 포인터

);

이런 방법을 써도 간단한 프로그램에서는 가능하겠지만 실제 프로그램에서는 입력	이 없을 경우에는 자신의 일을 수행해야 하므로 위와 같이 입력을 마냥 기다릴 수	는 없습니다. 그러므로 입력 이벤트를 감시하기 위한 Thread를 생성하여 입력의 감	시는 이 쓰레드에 전담시키고 원래의 프로그램은 자신의 일을 계속하도록 해 주어	야만 합니다. 윈도우에서 쓰레드를 생성시키는 함수는 CreateThread()입니다.

HANDLE CreateThread(

LPSECURITY_ATTRIBUTES lpThreadAttributes, // 쓰레드의 보안 특성을 설정

DWORD dwStackSize,     // 쓰레드에서 사용할 스택의 초기 크기를 설정

LPTHREAD_START_ROUTINE lpStartAddress,// 쓰레드로 사용할 함수의 포인터

LPVOID lpParameter,

DWORD dwCreationFlags,

LPDWORD lpThreadId

);

lpThreadAttributes : 생성된 쓰레드가 부모의 특성을 물려받을 지를 결정. 이 경우	우리는 NULL로 설정하여 특성을 물려받지 않아도 지장이 없습니다.

dwStackSize : 생성되는 쓰레드에서 사용할 스택의 크기. 이것을 0으로 설정하여	주 Process의 크기에 따라 자동으로 설정하게 해 두면 쓰레드가 종료될 경우에 자	동으로 해지도 됩니다. 예제에서는 0으로 설정 이러한 특징을 이용하였다. 만약 여	기서 설정한 스택 사이즈 만큼의 memory를 할당 받지 못하면 쓰레드가 생성되지	않습니다.

lpStartAddress : 쓰레드로 수행할 함수의 포인터를 설정합니다.

lpParameter : 쓰레드에 전달할 파라메터가 있을 경우 사용, 우리의 경우 부모	Process의 Window Handle을 이를 이용하여 전달함으로써 부모 프로세스에 메시지	를 전달하는데 사용합니다..

dwCreationFlags : 이 값을 CREATE_SUSPENDED로 해두면 ResumeThread()를	이용하여 활성화시키기 전에는 쓰레드가 정지해 있습니다.. 우리는 쓰레드가 즉각	작동해야하므로 0으로 Setting 합니다.

lpThreadId : 쓰레드를 식별하기 위한 핸들의 포인터입니다.

이렇게 쓰레드를 생성시키고 Serial Port를 감시하도록 한 뒤에는 원 프로그램은 계	속 일을 수행할 수 있습니다. 이제 이 쓰레드가 Serial Port에서 데이터가 들어왔다	는 것을 원 프로그램에 전달하는 방법에 대해서 알아보도록 하겠습니다.

쓰레드를 생성시킨 프로그램과 쓰레드 간에 통신을 위해서는 Message를 이용하여	야 합니다. 즉, 쓰레드 생성 시에 원 프로그램의 윈도우 핸들을 저장해두었다가 이	윈도우 핸들을 이용해서 사용자 정의 Message를 원 프로그램에 보내고 원 프로그	램은 이 Message의 Callback 함수를 만들어 두고 여기서 Serial Port에서 데이터를	읽도록 하는 것입니다. Buffer를 생성시키고 lpBuffer에 이 포인터를 전달하고 읽기	를 원하는 크기를 설정	해주고 ( 이 크기는 꼭 실제로 읽을 크기와 달르더라도	괜찮습니다 ) ReaFile()을	실행시키면 Serial Port의 Buffer속에 있던 데이	터를 읽어서 Buffer에 저장해 줍니	다.

[ 쓰레드에서 하는 일 ]

먼저 WaitCommEvent함수를 실행 시켜서 Serial Port에서 Event가 발생할 때까지	대기 합니다.

WaitCommEvent(m_hComm,&dwEvent,NULL);    //Serial Port Event 대기

Event가 발생하면 WaitCommEvent함수가 리턴됩니다. 그러면 리턴된 Event가 무	엇인지에 따라 처리를 해 줍니다. 가령 입력이 있어서 EV_RXCHAR가 발생했다면

if ((dwEvent & EV_RXCHAR) == EV_RXCHAR) // Event가 데이터 입력이면

{

do

{

포트에서 데이터를 읽어서 버퍼에 저장합니다.

} while (dwRead);

그리고 나서 모 프로세스에 메시지를 보내어 데이터를 읽어가도록 합니다.

::PostMessage(hCommWnd,WM_USER+1,0, 0);

}

[모 프로그램에서 하는 일]

먼저 위에서 얘기한 WM_USER+1등의 사용자 정의 메시지에 따른 콜백 함수를 만	들어 줍니다.

BEGIN_MESSAGE_MAP(CAccuMView, CFormView)

//{{AFX_MSG_MAP(CAccuMView)

:

ON_MESSAGE(WM_USER+1,OnCommRead) // Callback 함수로 설정

:

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

// Callback 함수에서 데이터를 읽어서 다양한 처리를 해주면 됩니다.

LONG CComuView::OnCommRead(UINT wParam, LONG lParam )

// WM_USER+1의 Callback 함수

{

:

버퍼에서 데이터를 읽고 처리한다.

:

}

데이터를 포트에서 읽어오는 함수는 ReadFile()입니다.

BOOL ReadFile(

HANDLE hFile,                   // Device 파일의 핸들

LPVOID lpBuffer,                 // 데이터를 저장한 버퍼의 포인터

DWORD nNumberOfBytesToRead,  // 읽을 바이트 수

LPDWORD lpNumberOfBytesRead,  // 읽은 바이트 수를 저장할 변수의 포인터

LPOVERLAPPED lpOverlapped     // Overapped structure에로의 포인터

);

<8> 데이터를 주고 받기 위한 자료 구조

위에서 Thread와 모 프로그램간에 데이터를 주고 받기 위한 버퍼가 있어야 한다	했습니다. 이 경우 Thread 측에서는 데이터를 입력만 시키고 모 프로그램에서는 데	이터를 읽기만 하므로 Queue를 사용하는 것이 좋습니다. 즉, Thread에서는 큐에	데이터를 넣고 이벤트를 발생 시키고 모 프로 그램에서는 큐에서 데이터를 읽어오	는 것입니다.

[ 쓰레드에서 하는 일 ]

먼저 Serial Port에서 데이터를 읽어옵니다.

dwRead = pComm->ReadComm( buff, 2048);

읽은 데이터를 한 Byte씩 Queue에 집어 넣습니다.

if (BUFF_SIZE - pComm->m_QueueRead.GetSize() > (int)dwRead)

{

for ( WORD i = 0; i < dwRead; i++)

pComm->m_QueueRead.PutByte(buff[i]);

}

else AfxMessageBox("m_QueueRead FULL!");

[모 프로그램에서 하는 일]

현재 Queue에 들어 있는 데이터의 크기를 얻어옵니다.

int size= (m_ComuPort.m_QueueRead).GetSize();

Queue가 빌때까지 데이터를 읽어옵니다.

for( int i=0; i< size; i++ )

{

(m_ComuPort.m_QueueRead).GetByte(&aByte);

if( aByte!= NULL ) buff[i]= aByte;

else { i--; size--; }

}

<9> 한글 처리

Serial Communication에서는 1 Byte 단위로 데이터가 전송되어 옵니다. 그러므로,	한글 같이 16 Bit로 표현되는 문자의 경우 2 Byte 씩 모아서 출력해 주지 않으면	데이터가 깨지게 됩니다. 이런 문제를 해결하기 위해서는 근본적으로는 한글	Automata를 만들어서 해결하는 것이 좋은 방법이겠으나 줄단위로 출력해도 이상이	없는 경우에는 본 예제에서와 같이 줄단위 버퍼를 만들어서 데이터를 계속 저장하	고 줄단위로 출력하는 것도 한 방법이라 할 수 있습니다. 이런 방법을 쓰는 경우	데이터의 처리 속도가 너무 늦어지므로 입력 데이터가 충돌을 일으킬 수도 있습니	다. 통신 에뮬레이터와 같은 응용이 아니라면 이것도 가능한 방법이라 하겠습니다.

<10> 데이터 송신

데이터를 송신하기 위해서는 WriteFile()을 이용 합니다.

BOOL WriteFile(

HANDLE hFile,                    // Device 파일의 핸들

LPCVOID lpBuffer,	// 데이터를 저장할 버퍼의 포인터

DWORD nNumberOfBytesToWrite,  // 보낼 바이트 수

LPDWORD lpNumberOfBytesWritten,// 보낸 바이트 수

LPOVERLAPPED lpOverlapped      // Overapped structure에로의 포인터

);

Buffer를 생성시키고 lpBuffer에 이 포인터를 전달하고 쓰기를 원하는 크기를 설정	해주고 WriteFile()을 실행시키면 Buffer속에 있던 데이터를 Serial Port를 통해 출	력해 줍니다.

이상과 같은 절차를 이용한 CCommThread를 만들고 이를 이용하여 뒤의 예제와	같이 Serial통신을 하게됩니다.

2. AT Command를 이용한 Modem제어

FAX나 모뎀의 경우 이를 제어하기 위해 표준으로 사용되는 명령어가 AT	Command로서 대부분의 FAX나 모뎀은 이 AT Command와 모뎀의 Register를 제	어하기 위한 S Register를 이용하여 제어할 수 있습니다. 다음은 이 모뎀 제어	Command의 목록입니다.

<1> AT Command

AT Command는 Modem에 명령을 내리기 위한 제어 코드로서 Escape Sequence인	□+++□와 이전 명령 반복 명령인 □A/'를 제외한 모든 명령은 명령 모드로 들어가도록	하는 'AT' 뒤를 이어 입력하게 됩니다. 다음은 주요 AT Command 목록 입니다.

명령	설 명
A	수동 응답
A/	이전 명령 반복 (AT 들 붙이지 않는다)
AT	명령 모드로 들어감
D	전화를 검
modifiers	0-9 : 전화 번호 P : 기계식 전화 걸기 T : 전자식 전화 걸기 W : Dial Tone이 들어올 때 까지 기다림 , : 잠시 중지 ! : Flash Hook ; : 명령 모드로 돌아감 L : 이전 번호로 전화 걸기
E, E0	모뎀에 들어오는 명령어를 단말기로 Echo 시킴
E1	모뎀에 들어오는 명령어를 단말기로 Echo 시키지 않음
H, H0	전화 수화기 들기 ( 전화를 끊는다 )
H1	전화 수화기 놓기
L0,L1	모뎀 스피커의 볼륨을 작게함
L2	모뎀 스피커의 볼륨을 중간 크기로 함
L3	모뎀 스피커의 볼륨을 크게함
M,M0	모뎀의 스피커를 끔
M1	Carrier가 검출 될 때까지 모뎀의 스피커를 끔
M2	스피커를 항상 켜둠
M3	전화걸기를 할 때를 제외하고 Carrier가 있을때는 스피커를 켜둠
Q,Q0	통신 결과를 표시함
Q1	통신 결과를 표시하지 않음
Q2	전화 걸기 진행 상황을 표시하지 않음
V,V0	통신 결과를 에러코드 숫자로 보여줌
V1	통신 결과를 문자열로 보여줌
X,X0	Dial Tone, Busy Signal을무시하고 접속 결과 코드를 표시함
X1	Dial Tone, Busy Signal을무시하고 접속 결과 속도를 표시함
X2	Busy Signal을무시하고 접속 결과 코드를 표시함
X3	Dial Tone을무시하고 접속 결과 속도를 표시함
X4	Dial Tone, Busy Signal을 검사하고 접속 결과 코드와 접속 속도를 표시함
Z,Z0	모뎀을 Reset하고 사용자 설정 0을 로드함
Z1	모뎀을 Reset하고 사용자 설정 1을 로드함
+++	Escape Sequence, 코맨드 모드로 돌아옴
&C,&C0	항상 Carrier Detect
&C1	Data Carrier를 Detect한 후 Carrier Detect
\G0	XON/XOFF를 사용하지 않음
\G1	XON/XOFF를 사용함
\Q0	DTE Flow Control을 사용하지 않음
\Q1	쌍방향 XON/XOFF Flow Control
\Q2	단방향 CTS Interface Signal 사용
\Q3	양방향 RTS/CTS Flow Control

<2> Command Result Code

AT Command로 Modem에 명령을 내리면 처리 결과에 따라 모뎀으로부터 다음과	같은 결과 코드가 전송되어 옵니다.

결과 코드	결과 코드 문자열	내 용
0	OK	명령이 잘 수행되었음
1	CONNECT	연결됨
2	RING	Ring 신호가 검출됨
3	NO CARRIER	Carrier Signal을 검출하지 못함
4	ERROR	지원하지 않는 명령어이거나, Checksum이 잘못되었거나 명령이 40문자 이상으로 너무 김
5	CONNECT 1200	1200 bps로 접속됨
6	NO DIAL TONE	Dial Tone을 검출 되지 않음
7	BUSY	통화중
8	NO ANSWER	서버 응답이 없음
0	CONNECT 2400	2400 bps 로 접속됨

<3> 연결 절차

AT Command로 통신을 연결하려면

- Serial Port를 초기화하고

- 모뎀 초기화 명령을 보내어 모뎀을 준비 시키고

- ATDT (또는 ATDP) 등의 명령을 이용 전화를 걸고

- 결과 처리 코드가 CONNECT인지 확인 하면 된다.

이 경우 초기화 명령어는 생략이 가능하나, 만약 외부 회선을 사용하는 경우	ATX3로 모뎀을 초기화 하여 Dial Tone을 무시하도록 해야 합니다. CONNECT되	면 자동으로 접속 속도와 연결 Protocol이 모뎀으로 부터 입력 됩니다.

3. ANSI CRT Control

위와 같은 방식으로 HITEL 같은 Service Provider에 접속을 하게되면 이상한 콘트	롤 문자들이 전송되어 오는 것을 볼 수 있을 겁니다. 이것은 ANSI란 화면 콘트롤	이스케이프 시퀀스로 □American National Standard Institute□의 약자로써 미국 표준	협회에서.만든 이스케이프 시퀸스를 간단하게 안시라고 부릅니다. 이 코드들은 국	제표준이며 도스에서 ANSI.SYS에서 사용하던 DOS ANSI라 부르는 이스케이프	시퀸스를 통해 널리 알려졌습니다. ANSI의 사용 목적은 CRT화면의 제어에 있습니	다. 물론 Windows와 같은 화려한 User Interface는 아니지만 ANSI를 사용하여	Text를 기반으로한 그래피컬한 User Interface를 구성할 수 있습니다. 이 ANSI는	크게 커서 제어, 화면의 전체 또는 일부 지움, 화면 폭과 컬러 제어, 워드 랩(wrap),	문자의 속성지정등의 기능이 있습니다.

<1> 커서 제어 (control cursor ent)

ESC[#A -- 커서을 # 줄만큼 위로 이동. (#는 1-24, 디폴트는 1)

ESC[#B -- 커서를 # 줄만큼 아래로 이동. (#는 1-24, 디폴트는 1)

ESC[#C -- 커서를 # 컬럼만큼 오른쪽으로 이동. (#는 1-79, 디폴트는 1)

ESC[#D -- 커서를 # 컬럼만큼 왼쪽으로 이동. (#는 1-79, 디폴트는 1)

ESC[#1;#2H -- 커서를 특정 위치로 이동. #1은 라인, #2는 컬럼.

ESC[#1,#2f -- 위의 경우와 같다.

ESC[s -- 현재의 커서 위치를 *저장. 아래의 복귀 멩자 이 위치로 감.

ESC[u -- 저장되었던 커서 위치로 이동.

<2> 화면의 전체 또는 일부 지움.(erase)

ESC[2J -- 화면 전체를 지우고 커서는 화면 왼쪽 위 (HOME)로 감.

ESC[K -- 커서 위치에서 부터 뒷쪽으로 라인 끝까지 지움.

<3> 화면 폭과 컬러 제어

ESC[=0h -- 40x25 단색 모드(텍스트)

ESC[=1h -- 40x25 컬러 모드(텍스트)

ESC[=2h -- 80x25 단색 모드(텍스트)

ESC[=3h -- 80x25 컬러 모드(텍스트)

ESC[=4h -- 320x200 컬러 모드(그래픽)

ESC[=5h -- 320x200 단색 모드(그래픽)

ESC[=6h -- 640x200 컬러 모드(그래픽)

<4> 워드 랩(wrap)기능

워드랩이란 한 라인이 너무 길어서 오른 쪽의 칼럼을 넘어갈 때 넘어 가는 부분을	다음 라인으로 넘기는 것을 말한다. 대부분의 워드프로세서에서 볼 수 있드시 미리	정해진 오른쪽 마진을 넘어가는 라인에 대해서는 자동적으로 다음라인으로 넘어가	게 되는 기능을 말합니다.

ESC[=7h -- 워드 랩을 하도록 한다.

ESC[=7l -- 워드 랩을 하지 않는다.(l은 소문자 L임에 주의)

<5> 문자의 속성지정

ESC[#1;#2;#3;...;#m

0 모든 속성 제거     30 black   40 배경 black

1 밝은 화면          31 red     41 배경 red

2 보통 밝기          32 green   42 배경 green

4 밑줄(단색에서만)   33 yellow  43 배경 yellow

5 깜박임(blink)      34 blue    44 배경 blue

7 반전(reverse)      35 magenta 45 배경 magenta (자주색)

8 안 보이게함        36 cyan    46 배경 cyan (하늘색)

37 white   47 배경 white

(30 부터 47번은 ISO 6429 standard와 같음.)

이런 ANSI 문자를 처리하기 위해서는 항상 입력되는 데이터를 감시하고 있어야	합니다. 즉, ESC+'['의 문자열이 입력되면 ANSI Sequence가 시작된다는 것을 인식	하고 그다음 들어오는 데이터를 ANSI buffer에 저장해 두었다가 정해진 명령어를	입력받으면 이를 처리하여 화면에 출력되는 문자의 속성을 변경해 주어야 합니다.

4. 간단한 통신 에뮬레이터 예제

<1> Serial 통신 Class CCommThread

[CommThread.h]

#define	BUFF_SIZE	8192  // Thread와 모 프로세스가 데이터를 주고받을 버퍼 크기

#define	WM_COMM_READ	(WM_USER+1) // Thread가 모프로세스에 보낼 메시지

#define	ASCII_LF	0x0a

#define	ASCII_CR	0x0d

#define	ASCII_XON	0x11

#define	ASCII_XOFF	0x13

// Buffer로 사용할 Queue 클래스의 정의 //

class	CQueue

{

public:

BYTE	buff[BUFF_SIZE]; // Queue Buffer

int	m_iHead, m_iTail; // Queue Head Tail Position

CQueue();

void	Clear();   // Queue의 내용을 지운다.

int	GetSize(); // Queue에 들어있는 데이터의 길이를 리턴한다.

BOOL	PutByte(BYTE b);	// Queue에 1 byte 넣기

BOOL	GetByte(BYTE *pb);	// Queue에서 1 byte 꺼내기

};

// 통신 클래스 CCommThread 정의

class	CCommThread

{

public:

HANDLE m_hComm;	// 통신 포트 파일 핸들

CString	m_sPortName;	// 포트 이름 (COM1 ..)

BOOL	m_bConnected;	// 포트가 열렸는지 유무를 나타냄.

OVERLAPPED m_osRead, m_osWrite;	// 포트 파일 IO를 위한 Overlapped structure

HANDLE	m_hThreadWatchComm;	// 포트를 감시할 함수 Thread의 핸들을 보관

WORD	m_wPortID;	// WM_COMM_READ와 함께 보내는 인수로

// 여러개의 포트를 열었을 경우 어떤 포트인지

// 식별하는데 사용한다.

CQueue	m_QueueRead; // Thread와 모 프로세스간의 통신 버퍼

// Serial Port를 Open한다. 인자로 포트명의 String과 속도, 포트 번호를 준다.

BOOL	OpenPort(CString sPortName, DWORD dwBaud, WORD wParam);

// Serial Port를 Close한다.

void	ClosePort();

// Serial Port에 데이터를 쓴다.

DWORD	WriteComm(BYTE *pBuff, DWORD nToWrite);

// Thread가 메시지를 보내온 경우 이 함수를 이용해서 데이터를 읽는다.

DWORD	ReadComm(BYTE *pBuff, DWORD nToRead);

};

// Thread로 사용할 함수

DWORD	ThreadWatchComm(CCommThread* pComm);

[CommThread.cpp]

#include "stdafx.h"

#include "CommThread.h"

// 메세지를 받을 윈도우 핸들, 이 클래스를 사용하는 윈도우에서 HWND hCommWnd= this->m_hWnd	// 로 설정해 준다.

extern HWND hCommWnd;

// Queue를 초기화

void CQueue::Clear()

{

m_iHead = m_iTail = 0;

memset(buff, 0, BUFF_SIZE);

}

// Queue의 생성자

CQueue::CQueue()

{

Clear();

}

// Queue에 들어 있는 자료 개수를 리턴

int CQueue::GetSize()

{

return (m_iHead - m_iTail + BUFF_SIZE) % BUFF_SIZE;

}

// Queue에 1 byte 넣음.

BOOL CQueue::PutByte(BYTE b)

{

// Queue가 꽉 찼는지 검사

if (GetSize() == (BUFF_SIZE-1)) return FALSE;

// Queue에 한 바이트를 넣고 Head Pointer를 증가

buff[m_iHead++] = b;

m_iHead %= BUFF_SIZE;

return TRUE;

}

// Queue에서 1 byte 꺼냄.

BOOL CQueue::GetByte(BYTE* pb)

{

// Queue가 비었는지 검사

if (GetSize() == 0) return FALSE;

// Queue에서 한 바이트를 꺼내고 Tail Pointer를 증가

*pb = buff[m_iTail++];

m_iTail %= BUFF_SIZE;

return TRUE;

}

BOOL CCommThread::OpenPort(CString sPortName, DWORD dwBaud, WORD wPortID)

{

// Local 변수.

COMMTIMEOUTS	timeouts; // Timeout Value를 설정하기 위한 구조체

DCB  dcb; // Serial Port의 특성을 설정하기 위한 구조체

DWORD	dwThreadID; // Thread 생성 시 Thread ID를 얻음

// 변수 설정

m_bConnected = FALSE;

m_wPortID= wPortID; // COM1-> 0, COM2->1,,,,,

// overlapped structure 변수 초기화.

m_osRead.Offset = 0;

m_osRead.OffsetHigh = 0;

if (! (m_osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))

return FALSE;

m_osWrite.Offset = 0;

m_osWrite.OffsetHigh = 0;

if (! (m_osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))

return FALSE;

// 포트 열기

m_sPortName = sPortName;

m_hComm = CreateFile( m_sPortName,

GENERIC_READ | GENERIC_WRITE, 0, NULL,

OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,

NULL);

if (m_hComm == (HANDLE) -1) return FALSE; // 포트 열기 실패

// EV_RXCHAR event 설정

SetCommMask( m_hComm, EV_RXCHAR);

// Serial Port 장치의 InQueue, OutQueue 크기 설정.

SetupComm( m_hComm, 4096, 4096);

// 포트 InQueue, OutQueue 초기화.

PurgeComm( m_hComm,

PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR);

// timeout 설정.

timeouts.ReadIntervalTimeout = 0xFFFFFFFF;

timeouts.ReadTotalTimeoutMultiplier = 0;

timeouts.ReadTotalTimeoutConstant = 0;

timeouts.WriteTotalTimeoutMultiplier = 2*CBR_9600 / dwBaud;

timeouts.WriteTotalTimeoutConstant = 0;

SetCommTimeouts( m_hComm, &timeouts);

// dcb 설정

dcb.DCBlength = sizeof(DCB);

GetCommState( m_hComm, &dcb);	// 예전 값을 읽음.

dcb.BaudRate = dwBaud;

dcb.ByteSize = 8;

dcb.Parity = 0;

dcb.StopBits = 0;

dcb.fInX = dcb.fOutX = 1;	// Xon, Xoff 사용.

// Xon, Xoff를 사용하지 않을 경우는 CriticalSection을 설정	// 하여 보호해 주어야만 한다.

dcb.XonChar = ASCII_XON;

dcb.XoffChar = ASCII_XOFF;

dcb.XonLim = 100;

dcb.XoffLim = 100;

if (! SetCommState( m_hComm, &dcb))	return FALSE;

// 포트 감시 쓰레드 생성.

m_bConnected = TRUE; // 연결이 성립 되었음을 Setting

m_hThreadWatchComm = CreateThread( NULL, 0,

(LPTHREAD_START_ROUTINE)ThreadWatchComm, this, 0, &dwThreadID);

if (! m_hThreadWatchComm)

{

// 실패하면 포트를 닫는다

ClosePort();

return FALSE;

}

return TRUE;

}

// 포트를 닫는다.

void CCommThread::ClosePort()

{

m_bConnected = FALSE;

// 모든 Event mask를 없앤다.

SetCommMask( m_hComm, 0);

// 통신 Queue들을 초기화 한다.

PurgeComm( m_hComm,

PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR);

// 파일 핸들을 닫는다.

CloseHandle( m_hComm);

}

// 포트에 pBuff의 내용을 nToWrite만큼 쓴다.

// 실제로 쓰여진 Byte수를 리턴한다.

DWORD CCommThread::WriteComm(BYTE *pBuff, DWORD nToWrite)

{

DWORD	dwWritten, dwError, dwErrorFlags;

COMSTAT comstat;

if (! WriteFile( m_hComm, pBuff, nToWrite, &dwWritten, &m_osWrite))

{

if (GetLastError() == ERROR_IO_PENDING)

{

// 읽을 문자가 남아 있거나 전송할 문자가 남아 있을 경우 Overapped IO의

// 특성에 따라 ERROR_IO_PENDING 에러 메시지가 전달된다.

//timeouts에 정해준 시간만큼 기다려준다.

while (! GetOverlappedResult( m_hComm, &m_osWrite, &dwWritten, TRUE))

{

dwError = GetLastError();

if (dwError != ERROR_IO_INCOMPLETE)

{

ClearCommError( m_hComm, &dwErrorFlags, &comstat);

break;

}

}

}

else

{

dwWritten = 0;

ClearCommError( m_hComm, &dwErrorFlags, &comstat);

}

}

return dwWritten;

}

// 포트로부터 pBuff에 nToWrite만큼 읽는다.

// 실제로 읽혀진 Byte수를 리턴한다.

DWORD CCommThread::ReadComm(BYTE *pBuff, DWORD nToRead)

{

DWORD dwRead, dwError, dwErrorFlags;

COMSTAT comstat;

ClearCommError( m_hComm, &dwErrorFlags, &comstat);

// input Queue에 들어와 있는 데이터의 길이

dwRead = comstat.cbInQue;

if (dwRead > 0)

{

if (! ReadFile( m_hComm, pBuff, nToRead, &dwRead, &m_osRead))

{

if (GetLastError() == ERROR_IO_PENDING)

{

//입출력 중인 데이터가 있는 경우 timeouts에 정해준 시간만큼 기다려준다.

while (! GetOverlappedResult( m_hComm, &m_osRead, &dwRead, TRUE))

{

dwError = GetLastError();

if (dwError != ERROR_IO_INCOMPLETE)

{

ClearCommError( m_hComm, &dwErrorFlags, &comstat);

break;

}

}

}

else

{

dwRead = 0;

ClearCommError( m_hComm, &dwErrorFlags, &comstat);

}

}

}

return dwRead;

}

// 포트를 감시하고, Serial Port에서 Message가 발생하면 입력 메시지인가 건사하고 내용이 있으면

// m_ReadData에 저장한 뒤에 MainWnd에 메시지를 보내어 Buffer의 내용을

// 읽어가라고 WM_COMM_READ Message를 보낸다..

DWORD	ThreadWatchComm(CCommThread* pComm)

{

DWORD  dwEvent;

OVERLAPPED	os;

BOOL	bOk = TRUE; // 리턴 값

BYTE	buff[2048];     // InQueue의 데이터를 일어오기 위한 Input Buffer

DWORD  dwRead;    // 읽은 바이트수.

// Event, Overap Structure 설정.

memset( &os, 0, sizeof(OVERLAPPED));

if (! (os.hEvent = CreateEvent( NULL, TRUE, FALSE, NULL)))

bOk = FALSE;

if (! SetCommMask( pComm->m_hComm, EV_RXCHAR))

bOk = FALSE;

if (! bOk)

{

AfxMessageBox("Error while creating ThreadWatchComm, " + pComm->m_sPortName);

return FALSE;

}

// 포트를 감시하는 루프.

while (pComm->m_bConnected) // 주 프로세스에서 이값을 FALSE로 바꿔주면 Thread가 끝난다.

{

dwEvent = 0;

// 포트에서 Message가 발생할 때까지 기다린다.

WaitCommEvent( pComm->m_hComm, &dwEvent, NULL);

if ((dwEvent & EV_RXCHAR) == EV_RXCHAR)

{

// 포트에서 읽을 수 있는 만큼 읽는다.

do

{

dwRead = pComm->ReadComm( buff, 2048);

// CCommThread의 Read Queue가 꽉차있는지 검사

if (BUFF_SIZE - pComm->m_QueueRead.GetSize() > (int)dwRead)

{

for ( WORD i = 0; i < dwRead; i++)

pComm->m_QueueRead.PutByte(buff[i]);

}

else

AfxMessageBox("m_QueueRead FULL!");

} while (dwRead);

//  읽어 가도록 메시지를 보낸다.

::PostMessage(hCommWnd,WM_COMM_READ, pComm->m_wPortID, 0);

}

}

// 포트가 ClosePort에 의해 닫히면 m_bConnected 가 FALSE가 되어 종료.

CloseHandle( os.hEvent);

pComm->m_hThreadWatchComm = NULL;

return TRUE;

}

[ComView.h]

// ComuView.h : interface of the CComuView class

//

/////////////////////////////////////////////////////////////////////////////

#if !defined(AFX_COMUVIEW_H__56796CD0_2A7E_11D2_A0DD_006097AEB8A7__INCLUDED_)

#define AFX_COMUVIEW_H__56796CD0_2A7E_11D2_A0DD_006097AEB8A7__INCLUDED_

#if _MSC_VER > 1000

#pragma once

#endif // _MSC_VER > 1000

// 통신용 Class 사용

#include "CommThread.h"

class CComuView : public CView

{

protected: // create from serialization only

CComuView();

DECLARE_DYNCREATE(CComuView)

// Attributes

public:

CComuDoc* GetDocument();

// Flags

BOOL m_bPortInit;    // Com Port가 성공적으로 초기화 되었는지 표시

BOOL m_bAnsiStart;  // ANSI Escape Character가 왔는지 표시

// Com Port Variables

UINT m_nPort;

UINT m_nBaudRate;

CCommThread m_ComuPort; // 앞에서 만든 통신용 클래스

// Buffer

CString m_strAnsi;    // ANSI String이 들어올 경우 입력되는 데이터를 저장

CString m_strLine[30]; // Screen에 출력하기 위한 Line Buffer

// 본 예제에서는 라인단위로 출력한다.

// Text Position

int m_nAnsiCount; // ANSI Escape Character가 들어온 후부터 입력 데이터 수

int m_nLinePos;   // 현재 화면에서 출력하고 있는 라인

int m_nColPos;    // 현재 화면에서 출력하고 있는 라인 캐릭터 위치

// Simple Edit Function

void ClearAll(); // 입력 화면 전체를 지운다.

void PutChar(unsigned char ch); // m_nLinePos, m_nColPos에 한 글자를 출력한다.

void DeleteLine(int pos, int type); // 한 줄을 지운다.

void DelChar(); // 한 글자를 지운다.

void ProcessErrorMessage( CString msg ); // 현재 상태를 StatusBar에 출력한다.

// ANSI Processing

BOOL IsAnsiCommand( unsigned char ch ); // ANSI Escape Character이후 입력 되는 문자가

// ANSI Command인가를 검사

void ProcessAnsi(); // ANSI Command를 처리한다.

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CComuView)

public:

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual void OnDraw(CDC* pDC);  // overridden to draw this view

//}}AFX_VIRTUAL

afx_msg LONG OnCommunication(UINT, LONG); // 우리가 설정한 WM_COMM_READ Message의

// Callback 함수

// Implementation

public:

virtual ~CComuView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CComuView)

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

afx_msg void OnSerialSet();

afx_msg void OnSerialInit();

afx_msg void OnModemConnect();

afx_msg void OnModemInit();

afx_msg void OnModemDisconnect();

afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags);

//}}AFX_MSG

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG  // debug version in ComuView.cpp

inline CComuDoc* CComuView::GetDocument()

{ return (CComuDoc*)m_pDocument; }

#endif

/////////////////////////////////////////////////////////////////////////////

//{{AFX_INSERT_LOCATION}}

// Microsoft Visual C++ will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_COMUVIEW_H__56796CD0_2A7E_11D2_A0DD_006097AEB8A7__INCLUDED_)

[ComView.cpp]

// ComuView.cpp : implementation of the CComuView class

//

#include "stdafx.h"

#include "Comu.h"

#include "MainFrm.h"

#include "ComuDoc.h"

#include "ComuView.h"

#include "ComCfg.h"  // Serial Config Dialog

#include "GetNum.h"  // 전화번호 입력 Dialog

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

// Max Value Define

#define MAX_ANSI_LEN 11 // ANSI character는 11 Byte를 넘지않는다.

// CommThread에서 Extern으로 설정된 Message를 받을 윈도우 핸들, OnCreate에서 현재의 윈도우로 Setting

HWND hCommWnd;

/////////////////////////////////////////////////////////////////////////////

// CComuView

IMPLEMENT_DYNCREATE(CComuView, CView)

BEGIN_MESSAGE_MAP(CComuView, CView)

//{{AFX_MSG_MAP(CComuView)

ON_WM_CREATE()

ON_COMMAND(IDM_SERIAL_SET, OnSerialSet)

ON_COMMAND(IDM_SERIAL_INIT, OnSerialInit)

ON_COMMAND(IDM_MODEM_CONNECT, OnModemConnect)

ON_COMMAND(IDM_MODEM_INIT, OnModemInit)

ON_COMMAND(IDM_MODEM_DISCONNECT, OnModemDisconnect)

ON_WM_CHAR()

ON_MESSAGE(WM_COMM_READ, OnCommunication)    // Communication Message Handleer

//}}AFX_MSG_MAP

END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////

// CComuView construction/destruction

CComuView::CComuView()

{

//{{AFX_DATA_INIT(CComuView)

//}}AFX_DATA_INIT

m_nPort= 0;

// Set Flag

m_bPortInit= FALSE;

m_bAnsiStart= FALSE;

for( int i= 0; i< 30; i++ ) m_strLine[i].Empty();

m_nLinePos= 0;

m_nColPos= 0;

}

CComuView::~CComuView()

{

}

BOOL CComuView::PreCreateWindow(CREATESTRUCT& cs)

{

return CView::PreCreateWindow(cs);

}

/////////////////////////////////////////////////////////////////////////////

// CComuView drawing

void CComuView::OnDraw(CDC* pDC)

{

CComuDoc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

hCommWnd= m_hWnd;

}

/////////////////////////////////////////////////////////////////////////////

// CComuView diagnostics

#ifdef _DEBUG

void CComuView::AssertValid() const

{

CView::AssertValid();

}

void CComuView::Dump(CDumpContext& dc) const

{

CView::Dump(dc);

}

CComuDoc* CComuView::GetDocument() // non-debug version is inline

{

ASSERT(m_pDocument->IsKindOf(RUNTIME_CLASS(CComuDoc)));

return (CComuDoc*)m_pDocument;

}

#endif //_DEBUG

/////////////////////////////////////////////////////////////////////////////

// CComuView message handlers

// Error Message processing

void CComuView::ProcessErrorMessage( CString msg )

{

((CMainFrame *)AfxGetMainWnd())->m_wndStatusBar.SetWindowText(msg);

}

// Window Size에 맞는 Font크기를 구한다.

int CComuView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

if (CView::OnCreate(lpCreateStruct) == -1)

return -1;

// Client영역의 크기를 구한다.

CRect rect;

GetClientRect(rect);

// 현재의 Text Matrics를 구한다.

CDC *pDC;

TEXTMETRIC tm;

pDC= GetDC();

pDC->GetTextMetrics(&tm);

// 화면 Size는 80x25

tm.tmHeight= (rect.bottom-rect.top)/25;

// 새로 사용할 Font를 Create

CFont newFont;

newFont.CreateFont( tm.tmHeight, tm.tmHeight,

0, 0, tm.tmWeight,

tm.tmItalic, tm.tmUnderlined,

tm.tmStruckOut,

tm.tmCharSet, OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,

DEFAULT_QUALITY, DEFAULT_PITCH, "굴림체" );

pDC->SelectObject(&newFont);

return 0;

}

//

// 간단한 Editor Function

//

// 화면 전체를 지운다.

void CComuView::ClearAll()

{

CDC *pDC;

CRect rect;

CBrush brush;

pDC= GetDC();

GetClientRect(rect);

brush.CreateSolidBrush(RGB(255,255,255));

pDC->FillRect( &rect, &brush );

Invalidate(TRUE);

for( int i= 0; i< 30; i++ )

m_strLine[i].Empty();

}

// m_nLinePos의 출력열의 m_nColPos에 한글자를 추가하고 화면에 그열을 출력한다.

void CComuView::PutChar(unsigned char ch)

{

CDC *pDC;

TEXTMETRIC tm;

pDC= GetDC();

pDC->GetTextMetrics(&tm);

if( ch==10 ) // LF 문자일 경우

{

m_nLinePos++; // 현재 출력 열을 증가

if( m_nLinePos>25 ) // 화면 크기인 25 Line을 넘으면 화면 전체를 지우고 처음부터 다시 쓴다.

{

m_nLinePos= 0;

m_nColPos= 0;

ClearAll();

}

}

else if( ch==13 ) // CR

{

m_nColPos= 0;

}

else

{

// 현 Cursor 위치에 글자를 쓴다.

m_strLine[m_nLinePos].Insert( m_nColPos, ch );

pDC->TextOut(0,m_nLinePos*tm.tmHeight,m_strLine[m_nLinePos]);

m_nColPos++;

}

}

// 한 줄을 지운다. Type이 0이면 뒤쪽을 Type이 1이면 한 줄  전체를 지운다.

void CComuView::DeleteLine(int pos, int type)

{

CDC *pDC;

TEXTMETRIC tm;

CRect rect;

CBrush brush;

pDC= GetDC();

pDC->GetTextMetrics(&tm);

GetClientRect(rect);

brush.CreateSolidBrush(RGB(255,255,255));

if( type== 0 )

{

rect.top= m_nLinePos*tm.tmHeight;

rect.bottom= (m_nLinePos+1)*tm.tmHeight;

}

else

{

rect.left= m_nColPos*tm.tmHeight;

rect.top= m_nLinePos*tm.tmHeight;

rect.bottom= m_nLinePos*(tm.tmHeight+1);

}

pDC->FillRect( &rect, &brush );

Invalidate(TRUE);

m_strLine[m_nLinePos].Empty();

m_nColPos= 0;

m_nLinePos--;

}

// 한 글자를 지운다.

void CComuView::DelChar()

{

CDC *pDC;

TEXTMETRIC tm;

CString strTemp;

CRect rect;

CBrush brush;

pDC= GetDC();

pDC->GetTextMetrics(&tm);

GetClientRect(rect);

brush.CreateSolidBrush(RGB(255,255,255));

if( m_strLine[m_nLinePos].GetLength()== 0 ) return;

else if( m_strLine[m_nLinePos].GetLength()== 1 )

{

m_strLine[m_nLinePos].Empty();

}

else

{

strTemp.Empty();

strTemp+= m_strLine[m_nLinePos].Left(m_strLine[m_nLinePos].GetLength()-1);

m_strLine[m_nLinePos].Empty();

m_strLine[m_nLinePos]+= strTemp;

}

rect.top= m_nLinePos*tm.tmHeight;

rect.bottom= (m_nLinePos+1)*tm.tmHeight;

pDC->FillRect( &rect, &brush );

pDC->TextOut(0,m_nLinePos*tm.tmHeight,m_strLine[m_nLinePos]);

m_nColPos--;

}

// Serial Port 관련 메뉴

void CComuView::OnSerialSet()

{

// Serial Setting Dialog를 실행시키고 입력된 데이터를 읽어온다.

CComCfg dlg;

if( dlg.DoModal()== IDOK )

{

m_nPort= dlg.GetPort(); // 선택한 Port번호를 읽어 온다.

m_nBaudRate= dlg.GetBaudRate(); // 선택한 속도를 읽어온다.

}

}

void CComuView::OnSerialInit()

{

// 선택된 포트가 있는지 검사

if( m_nPort== 0 )

{

ProcessErrorMessage(_T("포트를 Setting 해 주십시오."));

return;

}

// Serial Port를 Open하고 초기화 한다.

CString strTemp;

strTemp.Format("COM%d", m_nPort);

if( !m_ComuPort.OpenPort( strTemp, m_nBaudRate, m_nPort ) )

{

ProcessErrorMessage(_T("포트를 초기화 실패."));

}

else

{

CString strTemp;

strTemp.Format( "포트 COM%d초기화 성공", m_nPort );

ProcessErrorMessage(strTemp);

m_bPortInit= TRUE;

}

}

// Keyboard Hit Message로 사용자 키보드 입력을 받아서 Serial Port로 출력한다.

void CComuView::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)

{

CString strTemp;

if( !m_bPortInit )

{

ProcessErrorMessage("포트가 Open되지 않았습니다.");

CView::OnChar(nChar, nRepCnt, nFlags);

return;

}

else

{

if( nChar== 10 ) // Linefeed-Carridge return이 입력될 경우는 CR만 처리한다.

{

strTemp.Format( "%c", 13 );

m_ComuPort.WriteComm((unsigned char*)(LPCTSTR)strTemp,strTemp.GetLength());

}

else

{

strTemp.Format( "%c", nChar );

m_ComuPort.WriteComm((unsigned char*)(LPCTSTR)strTemp,strTemp.GetLength());

}

}

CView::OnChar(nChar, nRepCnt, nFlags);

}

//

// ANSI Command Processing

//

BOOL CComuView::IsAnsiCommand( unsigned char ch )

{

// ANSI Command는 길이가 11Byte이하이고 다음의 Character로 끝난다.

unsigned char AnsiCommand[22]= { 'm','K','h','l','J','G','F','s','u','A','B','C','D',

'L','M','@','P','Z','H','r','t','d' };

for( int i= 0; i< 22; i++ )

if( ch==AnsiCommand[i] ) return TRUE;

return FALSE;

}

// ANSI Command를 처리한다.

void CComuView::ProcessAnsi()

{

int nBrench, nCount;

int nVal[3], nIndex;

LONG nStart=0, nEnd=0;

// 수신된 ANSI Command의 길이

nBrench= m_strAnsi.GetLength();

// 라인이나 전체화면 지우기 ANSI 처리

if( m_strAnsi.Right(1)==_T("K") || m_strAnsi.Right(1)==_T("J") )

{

if( nBrench==1 ) // ESC J or ESC K

{

DeleteLine( m_nColPos,1 );

}

else if( nBrench==2 && m_strAnsi[0]=='[' ) // ESC[J or ESC[K

{

DeleteLine( m_nColPos,1 );

}

else if( m_strAnsi[1]=='1' ) // ESC[1J or ESC[1K

{

DeleteLine( m_nColPos, 0 );

}

else if( m_strAnsi[1]=='2' ) // ESC[2J or ESC[2K

{

ClearAll();

}

}

// Cursor 이동 ANSI 처리

nVal[0]= 0; // ESC[숫자;숫자;숫자CMD 형태의 입력이므로 ;로 분리되는 숫자를 읽어서 Integer로 가지고 있	nVal[1]= 0; // 는다.

nVal[3]= 0;

nCount= 0;

nIndex= 0;

if( m_strAnsi.Right(1)==_T("H") || m_strAnsi.Right(1)==_T("f") )

{

for( nCount= 1; nCount< nBrench-1; nCount++ )

{

if( m_strAnsi[nCount]==';' )

{

nIndex++;

}

else

{

nVal[nIndex]= nVal[nIndex]*10 + (m_strAnsi[nCount]-'0');

}

}

m_nLinePos= nVal[0];

m_nColPos= nVal[1];

}

}

//

// Process Serial Port Read Message

//

// Thread에서 보내온 WM_COMM_READ Message Callback 함수

LONG CComuView::OnCommunication(UINT port, LONG lParam)

{

unsigned char ch;

char buff[2048]="";

BYTE aByte;

int size= (m_ComuPort.m_QueueRead).GetSize(); // CComthread의 통신 Queue에 있는 데이터의 길이

for( int i=0; i< size; i++ )

{

(m_ComuPort.m_QueueRead).GetByte(&aByte); // 한 Byte 씩 읽는다.

if( aByte!= NULL ) buff[i]= aByte;

else { i--; size--; }

}

for( i= 0; i< size; i++ )

{

ch= buff[i];

if( ch==8 ) // Back Space를 입력했을 때의 처리

{

DelChar();

}

else if( ch==0x1B ) // ESC ( ANSI Start )

{

m_bAnsiStart= TRUE;

m_nAnsiCount= 0;

m_strAnsi.Empty();

}

else

{

if( m_bAnsiStart ) // ANSI 문자 수신이 시작되면 출력하지 않고 Buffering

{

m_strAnsi+= ch;

m_nAnsiCount++;

if( IsAnsiCommand(ch) ) // Command Character가 들어왔는지 Check

{

ProcessAnsi();

m_bAnsiStart= FALSE;

m_nAnsiCount= 0;

m_strAnsi.Empty();

}

if( m_nAnsiCount==MAX_ANSI_LEN ) // 11 Byte가 넘으면 손상으로 보고 무시

{

m_bAnsiStart= FALSE;

m_nAnsiCount= 0;

m_strAnsi.Empty();

}

return 0;

}

PutChar((unsigned char)ch); // 화면의 현재 m_nLinePos, m_nColPos에 출력

}

}

return 0;

}

//

// Modem Control Menu

//

// 전화걸기 메뉴 선택 처리

void CComuView::OnModemConnect()

{

CGetNum dlg;

CString strTemp;

// 전화 번호 입력 다이얼로그 실행

if( dlg.DoModal()==IDOK )

{

if( dlg.IsUseExt() ) // 외부 회선 사용의 경우 ATDT9,를 덧붙인다.

{

strTemp.Format( "ATDT%s,%s\r", dlg.GetExt(), dlg.GetNum() );

m_ComuPort.WriteComm((unsigned char*)(LPCTSTR)strTemp,strTemp.GetLength());

}

else

{

strTemp.Format( "ATDT%s\r", dlg.GetNum() );

m_ComuPort.WriteComm((unsigned char*)(LPCTSTR)strTemp,strTemp.GetLength());

}

}

}

// 모뎀 초기화 메뉴 처리

void CComuView::OnModemInit()

{

CString strTemp;

if( !m_bPortInit )

{

ProcessErrorMessage("포트가 Open되지 않았습니다.");

return ;

}

else

{

strTemp.Format( "ATZ\r");

m_ComuPort.WriteComm((unsigned char*)(LPCTSTR)strTemp,strTemp.GetLength());

Sleep(500); // 모뎀에서 데이터를 처리할 여유를 준다.

strTemp.Format( "AT&C1X3\r"); // 기본 초기화 명령어

m_ComuPort.WriteComm((unsigned char*)(LPCTSTR)strTemp,strTemp.GetLength());

}

}

// 전화 끊기

void CComuView::OnModemDisconnect()

{

CString strTemp;

unsigned char esc[4]="+++";

if( !m_bPortInit )

{

ProcessErrorMessage("포트가 Open되지 않았습니다.");

return ;

}

else

{

// +++를 보내 Command Mode로 빠져나온다.

strTemp.Format( "%s", esc );

m_ComuPort.WriteComm((unsigned char*)(LPCTSTR)strTemp,strTemp.GetLength());

Sleep(5000); // 모뎀에서 데이터를 처리할 여유를 준다.

// 수화기를 들어 전화를 끊는다.

strTemp.Format( "ATH\r" );

m_ComuPort.WriteComm((unsigned char*)(LPCTSTR)strTemp,strTemp.GetLength());

}

}