1. Serial Port 제어
UNIX나 Window등 Workstation 이상 급의 컴퓨터에서는 가상 디바이스 개념이 적용되어 하드웨어를 직접 제어할 수 없도록 되었습니다. 그 대신에 디바이스 드라이버를 통해 하드웨어를 제어 할 수 있도록 되어 있습니다. Windows나 UINX에서는 특별히 Serial Port나 Parallel Port등의 기본적이고 자주 쓰이는 장치는 일반 File과 같이 Open, Close하여 Read, Write할 수 있도록 하였습니다.
COM1, COM2, LPT1, LPT2 등의 장치 명으로 된 것들이 그러한 것들입니다.
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통신을 하게됩니다.
※ 참고사항
1. 스레드란 ?
; 컴퓨터 프로그래밍에서, 스레드는 다수의 사용자들을 동시에 처리할 수 있는 프로그램이 각각의 사용과
관련하여 가지고 있는 정보들 말한다. 프로그램의 관점에서 보면, 스레드는 한 명의 개별 사용자 또는 특정한 서비스 요청을 서비스하는데 필요한 정보이다. 만약 다수의 사용자들이 그 프로그램을 쓰고 있거나, 또는 다른 프로그램들로부터 동시에 요청이 발생했을 때, 각각의 사용자나 프로그램들을 위해 스레드가 만들어지고, 또 유지된다. 스레드는 프로그램에게 현재 어떤 사용자가 서비스를 받고있는지를 파악하게 함으로써, 다른 사용자들을 위하여 재진입 해야할 것인지의 선택을 할 수 있도록 한다 (단방향 스레드 정보는 특별한 데이터 저장소 내에 그것을 저장하고, 데이터 저장소의 주소를 레지스터에 집어넣음으로써 유지된다. 운영체제는 항상 프로그램이 중단되었을 때 레지스터의 내용을 저장하며, 그리고 다시 제어권이 주어졌을 때 그 내용을 복구한다).
2. 포인터란 ?
; 기억 공간에 저장된 자료의 주소값을 의미하는데 링크라고도 한다. 이것은 자료들 사이의 전후 연결 관계를 나타내고자 할때 사용한다.
3. 큐와 스택이란 ?
; 큐 : 선형 리스트의 안으로 자료가 입력되는 것은 리스트의 한쪽 끝(tail 혹은 rear)에서 이루어지게 되고,
밖으로 자료가 출력되는 것은 이 리스트의 다른 끝(head 혹은 front)에서 이루어지는 자료구조를
말한다. 다른 말로는 선입선출(FIFO, First In First Out)이라고도 하는데 머저 들어 간 것이 먼저
나오는 것을 의미한다.
스택 : 새로운 항목을 리스트 안으로 입력하는 것과 리스트내에 이미 들어있는 항목을 출력하는 것이 모두
리스트의 한쪽 끝에서만 일어나는 항목들의 집합을 의미한다. 다른 표현으로는 후입선출(LIFO, Last
In First Out)이라고도 하는데 일상 생활 속에서도 그 예를 많이 찾아 볼 수 있는 자료구조의 한
형태이다. 예를 들면 쌓아 올려진 접시들을 들수 있다.
4. 헥사와 바이너리, 아스키 코드 ?
; 헥사 : 16진법에서는 숫자 0~9까지와 영문 알파벳 문자 A~F까지를 사용한다. 16진수는 한 바이트가 거의 항상 8 비트로 정의되는 근대의 컴퓨터에서 2진수를 표현하는 편리한 방법이다. 컴퓨터 기억장소의 내용을 보여줄 때 (새로운 프로그램을 디버깅하기 위하여 메모리 덤프를 하거나, 또는 텍스트 문자 열을 표현할 때, 또는 2진수 값의 스트링을 프로그램이나 HTML 페이지에 코딩할 때 등), 하나의 16 진수 숫자는 4개의 2진수 숫자를 표시할 수 있다 두 개의 16진수 숫자는 8 비트, 즉 한 바이트를 표현할 수 있다.
바이너리 : 2진수는 2를 기반으로 하는 숫자체계로서, 컴퓨터 내에서 데이터를 표현하기 위해 사용된다.
2 진수는 "0"과 "1"이라는 오직 2가지 종류의 숫자로만 구성된다.
2진법에서도 각 숫자의 위치는 2의 멱(冪)으로 나타내진다.
예를 들어 2진수 1101은(8 이 1개 + 4 가 1개 + 2 가 0개 + 1 이 1개) 즉 1 × 8 + 1 × 4 + 0 × 2 + 1 × 1인 것을 의미한다(여기서 8은 23, 4는 22, 2는 21, 1은 20을 각각 계산한 결과이다). 그러므로 2진수 1101은 10진수 13과 같은 정확히 같은 값이다.
아스키 코드 : (American Standard Code for Information Interchange) 아스키코드는 7비트로 구성되어
있어서 최대 27 = 128 가지의 정보를 나타낼수 있는데 Parity를 검사할 수 있는 1 비트를
더 추가하여 8 비트로 사용하고 있다.
※ 간단한 통신 에뮬레이터 예제
▶ 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());
}
}