본문 바로가기

컴퓨터/언어,프로그래밍

[RS232C 통신] 통신 프로그램 만들기

통신 프로그램 만들기

 

1 통신 프로그램 만들기

본 장에서는 RS232C 포트 즉 직렬(serial) 포트를 통해서 데이터를 전송하는 방법에 대해서 설명을 하겠습니다. 직렬 포트는 보통 COM 포트라고 합니다. 컴퓨터에서는 두 개의 직렬 포트를 설정할 수 있으며 그것을 COM1, COM2라고 합니다. 모뎀을 장착했을 경우에는 COM3, COM4도 사용 가능하지만 실질적으로는 2개의 컴포트밖에는 사용하지 못합니다. 예를 들어 마우스를 COM1에 사용하고 모뎀을 COM4에 사용하면 COM2와 COM3가 남은 것 같으나 실질적으로 COM1과 COM3가 같은 인터럽트를 사용하고 COM4와 COM2가 같은 인터럽트를 사용하기 때문에 COM2를 사용하면 COM4와 충돌하고 COM3를 사용하면 COM1과 충돌합니다.

본 장에서는 모뎀을 직접 컨트롤하는 명령어는 사용하지 않고 단순히 직렬 포트에 데이터를 넣고 직렬 포트에서 데이터를 읽어 오는 방법에 대해서 설명합니다. 모뎀을 컨트롤하는 것은 직렬 포트에 모뎀 제어 명령문을 넣으면 됩니다. 이 명령문을 헤이즈 모뎀의 명령어라고 하지요. 예를 들어, 모뎀 초기화 명령 "Atz"라고 하여 모뎀이 연결된 포트에 입력을 하면 이 명령문을 모뎀이 읽고 "OK"라고 응답합니다. 다음 "Atdt 36792000"이라고 하면 3679-2000번으로 모뎀이 전화를 겁니다. 전화를 건 후 통신이 연결되면 모뎀은 전화선으로 들어온 데이터를 그대로 포트 디바이스에 넣고 사용자가 그 포트에 있는 데이터를 읽어 오고 다시 보낼 데이터를 포트 디바이스에 넣으면 모뎀은 포트에 들어온 데이터를 그대로 전화선을 통해서 상대 컴퓨터에 전송할 뿐입니다.

따라서 모뎀 제어까지 하지 않아도 전화를 이용한 통신 프로그램을 작성할 수 있습니다.

보통 공장자동화에서 많은 제어 장치 중 컴퓨터가 로봇을 제어하기 위한 명령문을 받는 창구의 하나로 직렬 포트를 두고 있습니다. 그렇기 때문에 모뎀이 달려 있지 않은 직렬 포트에 데이터를 전송할 경우에 있어서도 본 장이 필요할 것입니다. 본 장은 윈도에서 직렬 커뮤니케이션을 할 수 있는 기법을 다루고 있습니다.


q 통신을 하는 함수 전체 보기

1.RS232C에 사용되는 신호

본장에서 사용하는 함수들은 Win32API에서 RS232C를 사용합니다. 이때 나오는 신호 용어 들을 미리 정의 합니다.

RTS(Rquest to Send) , CTS(Clear to Send): 직렬 포트와 모뎀이 연결되었을 경우 하드웨어 적으로 처리를 할 경우 사용되는 것입니다. 요즘 나온 14400이나 28800 또는 그이상의 전송속도를 가지고 있는 모뎀들은 하드웨어적인 처리를 합니다.

DSR(Data Set Read) : 데이터를 읽기 위해 준비가 되었다는 플러그 인데 만일 모뎀이 다른 모뎀과 설정되었을경우 이 신호는 1이되고 그렇지 않으면 0이됩니다.

DTR(Data Terminal Ready) : 두 대의 모뎀사이를 관리하기 위한 신호인데 만일 두 개의 모뎀이 연결되어 있다면 1 그렇지 않으면 0이됩니다.

통신을 할 때 사용하는 방법은 우선 컴포트를 열고, 데이터를 읽고, 데이터를 쓰고 컴포트를 닫는 것이 전부입니다. 사실 매우 간단하죠. 함수가 약간 복잡하지만 아래에 설명을 했습니다. 이 책에서 함수 설명이 자세히 안 되어 있는 것은 도움말을 참조하세요.

2.RS232C 포트에 접속하기

통신을 하기 위한 방식이 윈도 95로 넘어가면서 파일 개념으로 변화되었습니다. 즉 통신 포트를 하나의 파일로 놓고 파일을 열고 그 파일 안에 데이터를 넣고 그 파일에서 데이터를 읽어 오게 하면 되는 식으로 바뀌었다는 것입니다. 그러니까 굉장히 사용하기 쉽게 컴포트에 접근할 수 있게 되었습니다.

포트를 파일처럼 여는 함수가 CreateFile입니다.

HANDLE CreateFile(

LPCTSTR lpFileName, // 파일명

DWORD dwDesiredAccess, // 접근 모드 읽기쓰기인가 읽기전용인가

DWORD dwShareMode, // 다른 프로그램과 공유를 할 것인가 아닌가

LPSECURITY_ATTRIBUTES lpSecurityAttributes, //보안에 관한 속성

DWORD dwCreationDistribution, // 어떻게 열 것인가

DWORD dwFlagsAndAttributes, // 파일 속성

HANDLE hTemplateFile // 템플레이트 파일 핸들러

);

위 함수는 포트만 여는 데 사용하는 함수가 아닌 일반적인 파일이나 또는 디바이스를 여는 데도 사용하기 때문에 여러 인자가 있습니다. 위 함수에서 우리가 사용하는 인자들만 가지고 설명을 하겠습니다.

lpFileName의 인자에는 컴포트 이름을 넣으면 됩니다. 만일 COM1이면 그냥 "COM1"이라고 설정해 주면 되지요. 그럼 COM3이면 무엇일까요? "COM3"이겠지요.

dwDesireAccess란 접근 모드입니다. GENERIC_READ, GENERIC_WRITE 등이 있는데 컴포트에 읽고 쓰고 해야 하니까 두 개를 합치면 되겠지요. GENERIC_ READ |GENERIC_WRITE하면 됩니다.

보안에 관한 속성이란 현재 개방된 파일이 다른 사람들에게 오픈되지 못하게 잠그는 속성을 말합니다.

그렇지만 컴포트는 그런 것이 필요없으므로 NULL로 해주면 됩니다.

파일을 어떻게 열 것인가?하는 문제는 새로 만들 것인가 아니면 항상 기존의 파일을 열 것인가 등 여러 인자가 있습니다. 컴포트는 존재하므로 새로 만들 필요는 없습니다. 결국 기존에 있는 것을 열면 되지요. 기존에 있는 파일을 열 때는 OPEN_EXISTING을 설정합니다. 파일 속성이란 보통 파일이면서 Overlapped가 되어야겠죠. 이 때는 FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED라고 설정해 줍니다. 템플레이트 파일 핸들러는 필요가 없으므로 NULL로 합니다.

위의 함수를 사용하여 COM3 포트를 열 경우 다음과 같이 하면 됩니다.

HANDLE idComDev ;

idComDev=CreateFile( "COM3", GENERIC_READ | GENERIC_WRITE,

0, // exclusive access

NULL, // no security attrs

OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL |

FILE_FLAG_OVERLAPPED, // overlapped I/O

NULL );

이 곳에서 파일이 열리면 열려진 핸들이 바로 idComDev에 설정됩니다. 이 핸들러는 계속 사용하므로 기억하시기 바랍니다.

이벤트 설정과 체크

통신 포트를 통해서 들어오는 데이터는 많은 종류가 있습니다. 글자가 들어오는 경우도 있겠지만 전화벨 데이터의 검출, 통신 에러 검출 등도 통신 함수를 통해서 모두 검출할 수 있는 내용입니다. 이중에서 우리가 필요가 없는 데이터도 있고 필요가 있는 데이터도 있습니다. 통신포트에 들어오는 데이터중 우리가 필요한 데이터만 흡수할필요가 있습니다. 즉 모뎀에게 "문자가 수신되거나 출력버퍼에 마지막 문자가 보내졌다"라는 내용만 오게 하고자 한다면 이해 해당하는 마스크를 설정하면됩니다. 이함수가 SetCommMask입니다.

BOOL SetCommMask(HANDLE hFile,DWORD dwEvent);

hFile는 CreateFile에 의해서 리턴된 값이고 dwEvent는 표1과 같은 파라미터를 설정하는 것입니다.

표1 dwEvent에 설정하는 값

 

이벤트 마스트

내용

EV_RXCHAR

문자가 수신되었음

EV_BREAK

입력시에 갑자기 중단이 되었음

EV_CTS

CTS 신호가 변경되었음

EV_DSR

DSR 신호가 변경되었음

EV_ERR

에러가 감지 되었음

EV_RING

전화벨이 울렸음

EV_RXFLAG

DCB구조체의 EvtChar 의 문자가 수신되어 입력버퍼에

저장되었음

EV_TXEMPTY

출력 버퍼로 마지막 문자가 전송되었음

 

예를 들어서 문자가 수신되었다는 것을 마스크로 설정하고자 한다면 다음과 같습니다.

SetCommMask(idComDev, EV_RXCHAR);

이런 데이터들이 들어오는지를 검사하는 함수가 WaitCommEvent인데 이 함수는 통신 포트를 통해서 데이터가 들어오기를 기다립니다. 위와 같이

SetCommMask(idComDer, EV_RXCHAR)라고 하고 WaitCommEvent 함수를 호출하면 WaitCommEvent 함수는 문자가 들어오기를 기다립니다.

WaitCommEvent(idComDev, &dwEvtMask, NULL );

위의 함수의 2번째 인자는 OVERLAPPED구조체 입니다. 보통 통신을 할 경우 동기화를 하기 때문에 이구조체를 사용하지 않고 NULL로 설정합니다. SetCommMask를 단 한 개만 설정하지는 않습니다. 필요하면 여러개을 설정할 수가 있습니다. 위와 같이 EV_RXCHAR를 설정하고 EV_TXEMPTY를 설정하였을 경우 WaitCommEvent함수를 실행하면 EV_RXCHAR과 EV_TXEMPTY두개의 이벤트에 반응합니다. 두 번째 인자는 지금 어떤 이벤트가 설정되어 있는 가에 대한 값을 저장합니다. idComDev는 위에서 만든 파일 핸들러 입니다. 문자열이 입력되었을 때 어떤 행동을 하고 문자를 보냈을 때 행동을 하라고 하고자 한다면 다음과 같이 하면 됩니다.

SetCommMask(idComDev, EV_RXCHAR|EV_TXEMPTY);

WaitCommEvent(idComDev, &dwEvtMask, NULL );

if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)

{

문자열이 들어왔을 때 수행하는 내용

else if((dwEvtMask & EV_TXEMPTY) ==EV_TXEMPTY)

{

문자열을 출력했을 때 수행하는 내용

}

 

통신 버퍼 초기화

통신 디바이스 데이터가 들어오는데 컴퓨터가 계속 감시할 수도 있지만 여러 일을 동시에 할 경우 놓칠 수도 있지요. 너무 빠르게 데이터가 들어오기 때문에요. 이렇기 때문에 포트로 들어오는 데이터를 무조건 버퍼에 채워 넣고 우리는 그 버퍼에 들어 있는 데이터만 읽어 오면 되게끔 시스템이 설정되어 있습니다. 그렇다고 들어오는 데이터를 무조건 넣으라고는 할 수 없지요. 어느 정도까지는 버퍼에 채우고 또 데이터가 들어오면 가장 앞단의 데이터를 삭제하게끔 하자는 이야기이죠. 이것이 큐입니다. 포트 디바이스 버퍼 크기를 input과 output 버퍼로 설정하는 함수가 있습니다. 이 함수가 SetupComm 함수입니다.

input, output 버퍼를 모두 4096이라고 설정하려 한다면 다음과 같이 하면 됩니다.

SetupComm( idComDev, 4096, 4096 ) ;

버퍼를 설정해 놓으면 그 버퍼 안에 쓰레기가 있을 수도 있어 초기 통신에 문제가 될 수 있으니까 버퍼를 모두 깨끗하게 청소해 놓고 또한 포트 디바이스를 초기화해 주어야 합니다. 이 때 사용하는 함수가 PurgeComm입니다. 다음과 같이 설정합니다.

PurgeComm( idComDev, PURGE_TXABORT | PURGE_RXABORT |

PURGE_TXCLEAR | PURGE_RXCLEAR ) ;

PurgeComm은 데이터 입력을 받은후에 현재 버퍼를 지울때도 사용을 합니다.

이렇게 함으로써 실질적인 컴포트가 열렸는데 컴포트를 통해서 데이터를 교환하려면 여러 조건이 필요합니다. 몇 bps로 할 것인가, XON/XOFF을 설정할 것인가 아닌가, 데이터 비트는 몇 비트인가, 패리티 검사를 할 것인가 아닌가, 정지 비트를 어떻게 할 것인가 등을 이런 것을 정의해 주어야 합니다. 이야기를 사용하는 사람은 baram.exe를 실행하여 통신을 설정할 때 위와 같은 내용을 본 적이 있을 것입니다.

윈도의 컴포트 통신도 마찬가지로 위와 같은 내용을 설정해 주어야 합니다. 그럼 이런 것을 설정할 구조체가 필요한데 그 구조체가 DCB입니다. 도움말을 이용해 DCB 구조체 안의 맴버를 보면 참 많은 것을 설정하는구나 느낄 겁니다.

typedef struct _DCB { // dcb

DWORD DCBlength; // DCB구조체의 크기

DWORD BaudRate; // 현재 보오 속도

// binary mode설정 1로 설정하면 binary mode가능

DWORD fBinary: 1;

// 패리티 검사 기능 유무

DWORD fParity: 1;

//CTS 지정 플러그 1이면 CTS가 1이 될 때까지 기다린다.

DWORD fOutxCtsFlow:1;

//DSR 지정 플러그 1이면 DSR이 1이 될 때 까지 기다린다.

DWORD fOutxDsrFlow:1;

//DTR 지정 플러그 DTR_CONTROL_DISBLE로 설정시 DTR이 OFF되고

// DTR_CONTROL_ENABLE 를 설정하면 DTR은 ON이된다.

DWORD fDtrControl:2;

// 이값이 1이면 DSR이 OFF동안 받은 데이터는 무시한다.

DWORD fDsrSensitivity:1;

//수신 버퍼가 꽉차있고 XoffChar 문자를 전송했을 경우

//전송 중단을 할것인가를 지정하는 플러그 들

DWORD fTXContinueOnXoff:1;

// 이값이 1이면 XoffChar문자 수신시 중단

DWORD fOutX: 1; // XON/XOFF out flow control

// 이값이 1이면 XoffChar문자는 수신 버퍼가 XoffLim바이트

// 안에 있을 경우 보내지고 XonChar 무자는 XonLim안에 있을 때

// 보내진다.

DWORD fInX: 1; // XON/XOFF in flow control

//이값이 1이고 fParity가 1이면 패리티 오류와 함께 수신된

// 바이트들은 ErrorChar멤버에 의해 지정된 문자로 대체

//된다.

DWORD fErrorChar: 1; // enable error replacement

//이값이 1이면 널값은 무시한다.

DWORD fNull: 1; // enable null stripping

//RTS는 이값이 RTS_CONTROL_DISABLE로 설정시 0이되고

// RTS_CONTROL_ENABLE로 설정될 때 ON이 된다.

DWORD fRtsControl:2; // RTS flow control

//이값이 1이면 오류가 발생하였을 때 읽기와 쓰기 작동이

//중단된다.

DWORD fAbortOnError:1; // abort reads/writes on error

DWORD fDummy2:17; // reserved

WORD wReserved; // not currently used

//XON 문자가 보내지기 전에 수신 버퍼에서 허용되는 최소 바이트

WORD XonLim; // transmit XON threshold

//XOFF문자가 보내지기전에 수신 버퍼에서 허용되는 사용가능한

//최소 바이트

WORD XoffLim; // transmit XOFF threshold

//포트에 의해 현재 사용되는 데이터 비스수

BYTE ByteSize; // number of bits/byte, 4-8

//패리티

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

//정지비트

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

//XON,XOFF문자 지정

char XonChar; // Tx and Rx XON character

char XoffChar; // Tx and Rx XOFF character

//오류에 의해 전달된 문자 전환

char ErrorChar; // error replacement character

//binary 모드가 아닐 경우 데이터의 끝을 나타내는 문자

char EofChar; // end of input character

//이문자가 수신될 때 이벤트가 발생

char EvtChar; // received event character

WORD wReserved1; // reserved; do not use

} DCB;

위의 것을 전부 설정한다기보다 우리가 필요한 부분만 설정하면 나머지는 기본적으로 정의된 값이 사용됩니다. 위의 DCB를 컴포트 파일 핸들러로 설정된 idCo- mDev와 함께 GetCommState를 사용하여 기본적인 인자를 받습니다.

DCB dcb;

GetCommState( idComDev, &dcb ) ;

이제 DCB를 이용하여 이야기처럼 몇 가지는 간단하게 정의해 주어야 합니다.

dcb.BaudRate = CBR_14400;//전송 속도

dcb.ByteSize = 8 ;//데이터 비트

dcb.Parity = 0;//패리티 체크

dcb.StopBits = 0 ;//스톱비트

다음 이 DCB를 idComDev에 연결시킵니다.

SetCommState( idComDev, &dcb ) ;

포트에서 데이터를 읽고 있을 경우 다른 작업을 하는 것이 좋습니다. 이런 것을 비동기라고 하지요. CreateFile 에서 FILE_FLAG_OVERLAPPED라는 OVERAPPED옵션을 설정하면 이렇게 비동기가 됩니다. 이렇게 비동기가 되면 데이터를 주고 받는 구조체를 할당하여 이할당된 구조체에게 작업을 맏겨야 합니다.

이런 구조체가 바로 OVERLAPPED입니다.

OVERLAPPED osWrite, osRead ;

다음 이 구조체를 초기화하고

osWrite.Offset = 0 ;

osWrite.OffsetHigh = 0 ;

osRead.Offset = 0 ;

osRead.OffsetHigh = 0 ;

이벤트를 설정합니다.

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

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

이렇게 함으로써 COM 포트에의 접속이 끝났습니다.

데이터 쓰기

데이터를 쓰는 것은 파일에 쓰듯이 WriteFile 함수를 사용합니다.

WriteFile( idComDev, lpByte, dwBytesToWrite, &dwBytesWritten, &osWrite ) ;

첫번째 인자는 파일 핸들러이고, 두 번째 인자는 써야 할 데이터, 세 번째 인자는 써야 할 바이트의 수, 네 번째 인자는 써야 할 바이트수가 들어 있는 번지, 다섯 번째 인자는 위에서 만든 osWrite입니다.

데이터 읽기

데이터를 읽는 것은 위의 데이터를 쓰는 함수와 비슷합니다. 함수명은 ReadFile입니다.

ReadFile( idComDev, lpszBlock,dwLength, &dwLength, &osRead ) ;

컴포트 닫기

컴포트를 닫는 것은 파일 닫듯이 닫으면 됩니다.

CloseHandle( idComDev ) ;

다음 데이터 구조체를 만들었으니까 만든 구조체를 해제해 줍니다.

CloseHandle( osRead.hEvent ) ;

CloseHandle( osWrite.hEvent ) ;

데이터 큐

컴포트에서는 데이터가 어쩔 때는 1개씩, 어쩔 때는 수십 개씩 한꺼번에 들어옵니다. 즉 여러 개가 들어올 경우와 한 개가 들어올 경우 등 여러 경우가 있다는 것이죠. 물론 디바이스에도 4096 정도로 큐를 만들어 놓았으나 프로그램 제작자가 데이터를 읽고 적절하게 컨버트해야 합니다. 한 가지 예를 든다면 컴포트에 "안녕하세요[0x11번캐릭터][0x13번캐릭터]이곳은 천리안입니다" 라고 들어왔을 경우 0x11과 0x13은 개행 캐릭터입니다. 따라서, 화면에는

"안녕하세요

이곳은 천리안입니다"

이렇게 표시를 해주어야 합니다. 그런데 들어오는 대로 화면에 표시하면 위와 같이 표시하기가 힘들어집니다.

0x11다음 0x13이 안 오고 0x44가 올 수도 있지 않습니까? 즉 들어오는 뒤의 데이터를 보고 앞의 데이터를 생각할 경우도 있다는 것입니다. 그렇기 때문에 데이터를 받아 두는 큐가 필요하고 이 큐를 어떻게 사용할지는 독자들의 마음입니다.

본 장의 예제에서는 큐를 사용하지 않고 그냥 에디터 상자에 데이터를 들어오는 대로 넣었습니다.

COMM 클래스 만들기

통신은 매우 많이 쓰이는 분야 중 하나입니다. 또한 많이 쓰지 않아도 클래스로 만들어 놓고 필요할 때 적절하게 쓰기 좋은 분야입니다. 컴포트를 열고, 읽고, 쓰고, 닫고 이 4가지 말고는 없기 때문이죠. 본 장에서 제작하는 클래스는 CComm입니다. 이 클래스에서는 위의 4가지 외에도 클래스에서 현재 윈도에 메시지를 보내는 함수, 읽은 데이터를 보관하는 함수, 그 외 필요한 여러 가지 함수를 만들었습니다.


CComm 헤더


//Comm.h

//Rs232c를 하기 위한 클래스 헤더

#define MAXBLOCK 80

#define MAXPORTS 4

// Flow control flags

#define FC_DTRDSR 0x01

#define FC_RTSCTS 0x02

#define FC_XONXOFF 0x04

// ascii definitions

#define ASCII_BEL 0x07

#define ASCII_BS 0x08

#define ASCII_LF 0x0A

#define ASCII_CR 0x0D

#define ASCII_XON 0x11

#define ASCII_XOFF 0x13

#define WM_RECEIVEDATA WM_USER+1

// global stuff


// function prototypes (private)

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

// CComm window

class CComm : public CObject

{

DECLARE_DYNCREATE( CComm )

public:

HANDLE idComDev ;//컴포트 디바이스 연결 핸들

BOOL fConnected;//컴포트가 연결되면 1로 설정

BYTE abIn[ MAXBLOCK + 1] ;//컴포트에서 들어오는 데이터

HWND m_hwnd;//메시지를 전달할 윈도 플래그

// Construction

public:

CComm( );

void SetXonOff(BOOL chk);//XonOff 설정

//컴포트를 설정함.

void SetComPort(int port,DWORD rate,BYTE bytesize,BYTE stop,BYTE parity);

//Dtr Rts 설정

void SetDtrRts(BYTE chk);

//comm 포트를 만든다.

BOOL CreateCommInfo();

//comm 포트를 해제한다.

BOOL DestroyComm();

//컴포트에서 데이터를 받는다.

int ReadCommBlock( LPSTR, int ) ;

//컴포트에 데이터를 넣는다.

BOOL WriteCommBlock( LPSTR, DWORD);

BOOL OpenComPort( ) ;//컴포트를 열고 연결을 시도한다.

//포트를 연결한다.

BOOL SetupConnection( ) ;

//연결을 해제한다.

BOOL CloseConnection( ) ;

//읽은 데이터를 버퍼에 저장한다.

void SetReadData(LPSTR data);

//메시지를 보낼 윈도 플래그를 설정한다.

void SetHwnd(HWND hwnd);

// Attributes

public:

BYTE bPort;

BOOL fXonXoff;

BYTE bByteSize, bFlowCtrl, bParity, bStopBits ;

DWORD dwBaudRate ;

HANDLE hWatchThread;

HWND hTermWnd ;

DWORD dwThreadID ;

OVERLAPPED osWrite, osRead ;

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CComm)

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CComm();

// Generated message map functions

// DECLARE_MESSAGE_MAP()

protected:

};

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


CComm 소스


//Comm.cpp Rs232c 통신을 하기 위한 클래스

#include "stdafx.h"

#include "comm.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

IMPLEMENT_DYNCREATE(CComm, CObject)


CComm::CComm( )

{

idComDev=NULL;

bFlowCtrl= FC_XONXOFF ;

fConnected = FALSE ;

}

CComm::~CComm( )

{

DestroyComm();

}

//BEGIN_MESSAGE_MAP(CComm, CObject)

//{{AFX_MSG_MAP(CComm)

// NOTE - the ClassWizard will add and remove mapping macros here.

//}}AFX_MSG_MAP

//END_MESSAGE_MAP()

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

// CComm message handlers

//CommWatchProc()

//통신을 하는 프로시저, 즉 데이터가 들어왔을 때 감시하는

//루틴. 본 루틴은 OpenComPort 함수 실행시 프로시저로 연결됨.

//OpenComPort 함수 참조

DWORD CommWatchProc(LPVOID lpData)

{

DWORD dwEvtMask ;

OVERLAPPED os ;

CComm* npComm = (CComm*) lpData ;

char InData[MAXBLOCK + 1];

int nLength ;

//idCommDev라는 핸들에 아무런 컴포트가 안 붙어 있으면

//에러 리턴

if ( npComm == NULL ||

!npComm->IsKindOf( RUNTIME_CLASS( CComm ) ) )

return (DWORD)(-1);

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

os.hEvent = CreateEvent( NULL, // no security

TRUE, // explicit reset req

FALSE, // initial event reset

NULL ) ; // no name

if ( os.hEvent == NULL )

{

MessageBox( NULL, "Failed to create event for thread!", "comm Error!",

MB_ICONEXCLAMATION | MB_OK ) ;

return ( FALSE ) ;

}

if (!SetCommMask(npComm->idComDev, EV_RXCHAR ))

return ( FALSE ) ;

while (npComm->fConnected )

{

dwEvtMask = 0 ;

WaitCommEvent(npComm->idComDev, &dwEvtMask, NULL );

if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)

{

do

{

memset(InData,0,80);

if (nLength = npComm->ReadCommBlock((LPSTR) InData, MAXBLOCK ))

{

npComm->SetReadData(InData);

//이곳에서 데이터를 받는다.

}

}

while ( nLength > 0 ) ;

}

}

 

CloseHandle( os.hEvent ) ;

return( TRUE ) ;

}

//데이터를 읽고 데이터를 읽었다는

//메시지를 리턴한다.

void CComm::SetReadData(LPSTR data)

{

lstrcpy((LPSTR)abIn,(LPSTR)data);

//ConverData

//설정된 윈도에 WM_RECEIVEDATA 메시지를

//날려 주어 현재 데이터가 들어왔다는 것을

//알려준다.

SendMessage(m_hwnd,WM_RECEIVEDATA,0,0);

}

//메시지를 전달할 hwnd 설정

void CComm::SetHwnd(HWND hwnd)

{

m_hwnd=hwnd;

}

//컴포트를 설정한다.

void CComm::SetComPort(int port,DWORD rate,BYTE bytesize,BYTE stop,BYTE parity)

{

bPort=port;

dwBaudRate=rate;

bByteSize=bytesize;

bStopBits=stop;

bParity=parity;

}

//XonOff, 즉 리턴값 더블 설정

void CComm::SetXonOff(BOOL chk)

{

fXonXoff=chk;

}

void CComm::SetDtrRts(BYTE chk)

{

bFlowCtrl=chk;

}

//컴포트 정보를 만든다.

//이것을 만들 때 이전에

// SetComPort(); -> SetXonOff() ->SetDtrRts()한 다음 설정한다.

BOOL CComm::CreateCommInfo()

{

osWrite.Offset = 0 ;

osWrite.OffsetHigh = 0 ;

osRead.Offset = 0 ;

osRead.OffsetHigh = 0 ;

//이벤트 창구 설정

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

if (osRead.hEvent == NULL)

{

return FALSE ;

}

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

if (NULL == osWrite.hEvent)

{

CloseHandle( osRead.hEvent ) ;

return FALSE;

}

return TRUE ;

}

//컴포트를 열고 연결을 시도한다.

//OpenComport()

BOOL CComm::OpenComPort( )

{

char szPort[ 15 ] ;

BOOL fRetVal ;

COMMTIMEOUTS CommTimeOuts ;

if (bPort > MAXPORTS)

lstrcpy( szPort, "\\\\.\\TELNET" ) ;

else

wsprintf( szPort, "COM%d", bPort ) ;

// COMM device를 파일 형식으로 연결한다.

if ((idComDev =

CreateFile( szPort, GENERIC_READ | GENERIC_WRITE,

0, // exclusive access

NULL, // no security attrs

OPEN_EXISTING,

FILE_ATTRIBUTE_NORMAL |

FILE_FLAG_OVERLAPPED, // overlapped I/O

NULL )) == (HANDLE) -1 )

return ( FALSE ) ;

else

{

//컴포트에서 데이터를 교환하는 방법을 char 단위를 기본으로 설정

//하자.

SetCommMask( idComDev, EV_RXCHAR ) ;

SetupComm( idComDev, 4096, 4096 ) ;

//디바이스에 쓰레기가 있을지 모르니까 깨끗이 청소를 하자.

PurgeComm( idComDev, PURGE_TXABORT | PURGE_RXABORT |

PURGE_TXCLEAR | PURGE_RXCLEAR ) ;

 

CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF ;

CommTimeOuts.ReadTotalTimeoutMultiplier = 0 ;

CommTimeOuts.ReadTotalTimeoutConstant = 1000 ;

CommTimeOuts.WriteTotalTimeoutMultiplier = 0 ;

CommTimeOuts.WriteTotalTimeoutConstant = 1000 ;

SetCommTimeouts( idComDev, &CommTimeOuts ) ;

}

fRetVal = SetupConnection() ;

if (fRetVal)//연결이 되었다면 fRetVal TRUE이므로

{

fConnected = TRUE ;//연결되었다고 말해 줌.

//프로시저를 CommWatchProc에 연결하니까 나중에 데이터가 왔다갔다

//하면 모든 내용은 CommWatchProc가 담당한다.

AfxBeginThread((AFX_THREADPROC)CommWatchProc,(LPVOID)this);

}

else

{

fConnected = FALSE ;

CloseHandle( idComDev) ;

}

return ( fRetVal ) ;

}

//파일로 설정된 컴포트와 실질 포트를 연결시킨다.

//SetupConnection 이전에 CreateComPort를 해주어야 한다.

BOOL CComm::SetupConnection()

{

BOOL fRetVal ;

BYTE bSet ;

DCB dcb ;

dcb.DCBlength = sizeof( DCB ) ;

GetCommState( idComDev, &dcb ) ;//dcb의 기본값을 받는다.

//이 부분을 수정해야 한다.

dcb.BaudRate = dwBaudRate;//전송 속도

dcb.ByteSize = bByteSize ;//데이터 비트

dcb.Parity = bParity;//패리티 체크

dcb.StopBits = bStopBits;//스톱 비트

dcb.fOutxDsrFlow =0 ;//Dsr Flow

dcb.fDtrControl = DTR_CONTROL_ENABLE ;//Dtr Control

dcb.fOutxCtsFlow = 0 ;//Cts Flow

dcb.fRtsControl = RTS_CONTROL_ENABLE ; //Ctr Control

dcb.fInX = dcb.fOutX = 1 ; //XON/XOFF 관한 것

dcb.XonChar = ASCII_XON ;

dcb.XoffChar = ASCII_XOFF ;

dcb.XonLim = 100 ;

dcb.XoffLim = 100 ;

dcb.fBinary = TRUE ;

dcb.fParity = TRUE ;


dcb.fBinary = TRUE ;

dcb.fParity = TRUE ;

fRetVal = SetCommState( idComDev, &dcb ) ; //변경된 Dcb 설정

return ( fRetVal ) ;

}

//컴포트로부터 데이터를 읽는다.

int CComm::ReadCommBlock(LPSTR lpszBlock, int nMaxLength )

{

BOOL fReadStat ;

COMSTAT ComStat ;

DWORD dwErrorFlags;

DWORD dwLength;

// only try to read number of bytes in queue

ClearCommError( idComDev, &dwErrorFlags, &ComStat ) ;

dwLength = min( (DWORD) nMaxLength, ComStat.cbInQue ) ;

if (dwLength > 0)

{

fReadStat = ReadFile( idComDev, lpszBlock,

dwLength, &dwLength, &osRead ) ;

if (!fReadStat)

{

//이곳에 에러를 넣다.

//즉 ReadFile했을 때 데이터가 제대로 안 나오면 fReadState에 여러

//에러 코드를 리턴한다. 이 때 복구할 수 있으면 좋지만 실질적인

//복구가 불가능하다. 따라서, 재송출을 해달라는 메시지를 해주는 것이

//좋다.

}

}

 

return ( dwLength ) ;

}

//컴포트를 완전히 해제한다.

BOOL CComm::DestroyComm()

{

if (fConnected)

CloseConnection( ) ;

CloseHandle( osRead.hEvent ) ;

CloseHandle( osWrite.hEvent ) ;

return ( TRUE ) ;

}

//연결을 닫는다.

BOOL CComm::CloseConnection()

{

// set connected flag to FALSE

fConnected = FALSE ;

// disable event notification and wait for thread

// to halt

SetCommMask( idComDev, 0 ) ;


EscapeCommFunction( idComDev, CLRDTR ) ;

PurgeComm( idComDev, PURGE_TXABORT | PURGE_RXABORT |

PURGE_TXCLEAR | PURGE_RXCLEAR ) ;

CloseHandle( idComDev ) ;

return ( TRUE ) ;

}


BOOL CComm::WriteCommBlock( LPSTR lpByte , DWORD dwBytesToWrite)

{

BOOL fWriteStat ;

DWORD dwBytesWritten ;

fWriteStat = WriteFile( idComDev, lpByte, dwBytesToWrite,

&dwBytesWritten, &osWrite ) ;

if (!fWriteStat)

{

//컴포트에 데이터를 제대로 써넣지 못했을 경우이다.

//이 때는 어떻게 할까. 그것은 사용자 마음이다.

//다시 보내고 싶으면 재귀송출을 하면 된다.

//그러나 무한 루프를 돌 수 있다는 점을 주의하자.

}

return ( TRUE ) ;

}

e 통신 프로그램 예제 CommEx 프로그램

CommEx 프로그램은 위에서 만든 CComm 클래스를 사용하여 통신을 하는 아주 간단한 통신 프로그램 예제입니다.

그림 1은 CommEx을 실행한 모습입니다. FormView로 작성하여 에디터 상자에는 현재 포트에서 읽히는 데이터를 출력하고 밑의 콤보 상자에서는 컴포트에 데이터를 출력하게끔 했습니다.

 


 

 

그림 1




CommEx 프로그램 실행 모습

 

밑의 콤보 상자에서 "Atz"를 치고 "Atdt 36792000"하여 천리안에 접속을 했을 때 출력되는 화면입니다. 출력되는 데이터에 여러 기호키들이 옆으로 보일 것입니다. 본 프로그램은 단지 컴포트에서 데이터를 입력받아 출력하는 기능만 만들었기에 통신 라인에서 들어오는 특수 기호들이 그대로 화면에 출력됩니다.

이 부분은 독자 여러분들이 수정하여 새로운 프로그램으로 만들어 보기 바랍니다.

본 프로그램을 제작한 부분에 대해서는 뷰 부분의 소스만 기재합니다. 전에 모두 다 설명한 내용이므로 소스만 읽어 보아도 이해가 될 것입니다.


프로그램 소스

// CommExView.h : interface of the CCommExView class

//

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

#include "mycombo.h"

#include "comm.h"

#if !defined(AFX_COMMEXVIEW_H__C930616E_474D_11D1_9A0C_0000E81C79AB__INCLUDED_)

#define AFX_COMMEXVIEW_H__C930616E_474D_11D1_9A0C_0000E81C79AB__INCLUDED_

#if _MSC_VER >= 1000

#pragma once

#endif // _MSC_VER >= 1000

class CCommExView : public CFormView

{

protected: // create from serialization only

CCommExView();

DECLARE_DYNCREATE(CCommExView)

public:

//{{AFX_DATA(CCommExView)

enum{ IDD = IDD_COMMEX_FORM };

// NOTE: the ClassWizard will add data members here

//}}AFX_DATA

// Attributes

public:

CCommExDoc* GetDocument();

CMyCombo m_pComboBox;

CString m_strEdit;

//컴 클래스

CComm m_pComm;

// Operations

public:

// Overrides

// ClassWizard generated virtual function overrides

//{{AFX_VIRTUAL(CCommExView)

public:

virtual BOOL PreCreateWindow(CREATESTRUCT& cs);

protected:

virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV support

virtual BOOL OnPreparePrinting(CPrintInfo* pInfo);

virtual void OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnEndPrinting(CDC* pDC, CPrintInfo* pInfo);

virtual void OnPrint(CDC* pDC, CPrintInfo*);

//}}AFX_VIRTUAL

// Implementation

public:

virtual ~CCommExView();

#ifdef _DEBUG

virtual void AssertValid() const;

virtual void Dump(CDumpContext& dc) const;

#endif

protected:

// Generated message map functions

protected:

//{{AFX_MSG(CCommExView)

afx_msg void OnSelchangeCombo1();

afx_msg int OnCreate(LPCREATESTRUCT lpCreateStruct);

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

afx_msg void OnClose();

//}}AFX_MSG

afx_msg LONG OnReceiveData(UINT,LONG);

DECLARE_MESSAGE_MAP()

};

#ifndef _DEBUG // debug version in CommExView.cpp

inline CCommExDoc* CCommExView::GetDocument()

{ return (CCommExDoc*)m_pDocument; }

#endif

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

//{{AFX_INSERT_LOCATION}}

// Microsoft Developer Studio will insert additional declarations immediately before the previous line.

#endif // !defined(AFX_COMMEXVIEW_H__C930616E_474D_11D1_9A0C_0000E81C79AB__INCLUDED_)

// CommExView.cpp : implementation of the CCommExView class

//

#include "stdafx.h"

#include "CommEx.h"

#include "CommExDoc.h"

#include "CommExView.h"

#ifdef _DEBUG

#define new DEBUG_NEW

#undef THIS_FILE

static char THIS_FILE[] = __FILE__;

#endif

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

// CCommExView

IMPLEMENT_DYNCREATE(CCommExView, CFormView)

BEGIN_MESSAGE_MAP(CCommExView, CFormView)

//{{AFX_MSG_MAP(CCommExView)

ON_CBN_SELCHANGE(IDC_COMBO1, OnSelchangeCombo1)

ON_WM_CREATE()

ON_WM_CHAR()

ON_WM_CLOSE()

//}}AFX_MSG_MAP

// Standard printing commands

ON_COMMAND(ID_FILE_PRINT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_DIRECT, CFormView::OnFilePrint)

ON_COMMAND(ID_FILE_PRINT_PREVIEW, CFormView::OnFilePrintPreview)

END_MESSAGE_MAP()

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

// CCommExView construction/destruction

CCommExView::CCommExView()

: CFormView(CCommExView::IDD)

{

//{{AFX_DATA_INIT(CCommExView)

// NOTE: the ClassWizard will add member initialization here

//}}AFX_DATA_INIT

// TODO: add construction code here

}

CCommExView::~CCommExView()

{

}

void CCommExView::DoDataExchange(CDataExchange* pDX)

{

CFormView::DoDataExchange(pDX);

//{{AFX_DATA_MAP(CCommExView)

DDX_Control(pDX,IDC_COMBO1,m_pComboBox);

DDX_Text(pDX,IDC_EDIT1,m_strEdit);

//}}AFX_DATA_MAP

}

BOOL CCommExView::PreCreateWindow(CREATESTRUCT& cs)

{

// TODO: Modify the Window class or styles here by modifying

// the CREATESTRUCT cs

return CFormView::PreCreateWindow(cs);

}

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

// CCommExView printing

BOOL CCommExView::OnPreparePrinting(CPrintInfo* pInfo)

{

// default preparation

return DoPreparePrinting(pInfo);

}

void CCommExView::OnBeginPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add extra initialization before printing

}

void CCommExView::OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)

{

// TODO: add cleanup after printing

}

void CCommExView::OnPrint(CDC* pDC, CPrintInfo*)

{

// TODO: add code to print the controls

}

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

// CCommExView diagnostics

#ifdef _DEBUG

void CCommExView::AssertValid() const

{

CFormView::AssertValid();

}

void CCommExView::Dump(CDumpContext& dc) const

{

CFormView::Dump(dc);

}

CCommExDoc* CCommExView::GetDocument() // non-debug version is inline

{

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

return (CCommExDoc*)m_pDocument;

}

#endif //_DEBUG

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

// CCommExView message handlers

int CCommExView::OnCreate(LPCREATESTRUCT lpCreateStruct)

{

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

return -1;

m_strEdit="Atz\r\nOk";

//콤보박스에서 키를 누르면 그 키값이

//본 윈도우에 전달할수 있도록 HWND를 전달한다.

m_pComboBox.SetHwnd(this->m_hWnd);

//컴포트를 맞춘다.

m_pComm.SetComPort(4,28800,8,0,0);

//컴포트의 정보를 만든다.

m_pComm.CreateCommInfo();

//컴토포트를 연다

m_pComm.OpenComPort();

//컴포트에서 이벤트가 생기면 현재 윈도우로 메세지를

//넘길수 있도록 HWND를 넘긴다.

m_pComm.SetHwnd(this->m_hWnd);

return 0;

}

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

{

//키를 누르면 크 키값을 컴포트에 넘긴다.

m_pComm.WriteCommBlock((LPSTR)&nChar ,1);

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

}

LONG CCommExView::OnReceiveData(UINT WParam,LONG LParam)

{

//컴포트에서 데이타를 받으면 받은 데이타를

//Edit 박스에 넘긴다.

UpdateData(TRUE);

m_strEdit+=(LPSTR)m_pComm.abIn;

UpdateData(FALSE);

return TRUE;

}

void CCommExView::OnClose()

{

//컴포트를 닫는다.

m_pComm.DestroyComm();

CFormView::OnClose();

}

void CCommExView::OnSelchangeCombo1()

{

}

출처 : 
http://blog.naver.com/wonik/40049529645


  

제주삼다수, 2L,... 오뚜기 진라면 매운... 상하목장 유기농 흰... 남양 프렌치카페 카... 고려인삼유통 홍삼 ... 종근당건강 오메가3... 요이치 카링 유무선...