MFC RS485 통신 예제 - MFC RS485 tongsin yeje

  • 상세정보
  • 자료후기 (0)
  • 자료문의 (0)
  • 판매자정보

소개글

MFC 의 기초부터 고급기능까지

체계적으로 정리 요약되어 있습니다.

발표자료로도 좋으며 개인 공부시에도 많은 도움이 됩니다.

목차

1. 시리얼 통신 프로그래밍
시리얼 통신
RS-232C 통신 규격
시리얼 포트 제어
시리얼 포트 설정
포트 감시 쓰레드
쓰레드를 이용한 데이터 수신
데이터 송수신을 위한 자료 구조
시리얼 통신을 위한 쓰레드 만들기
시리얼 통신 프로그램 예제

본문내용

시리얼 통신 프로그래밍
시리얼 통신
시리얼 통신은 일반적으로 컴퓨터 기기를 접속하는 방법의 하나로, 접속선하는 선의 수를 줄이고, 원거리까지 신호를 보낼 수 있도록 한 통신 방식이다.
이때, 신호가 1비트마다 직렬(시리얼, Serial)로 보내지기 때문에 시리얼 통신이라 불린다.
시리얼 통신은 데이터가 동일한 한 개의 통신선을 따라 한 비트씩 차례로 보내는 통신 방식이다.
대표적인 시리얼 통신 인터페이스는 RS-232C 표준 인터페이스이다.
컴퓨터는 기본적으로 시리얼 통신을 위해서 시리얼 포트를 내장하고 있는데, 최근 시리얼 통신이 USB 포트를 이용한 통신으로 대체되어 컴퓨터에서 사라지고 있다.
RS-232C는 미국 전자 공업 협회에서 제정한 통신 인터페이스 규격 중에 하나이다.
RS-232C는 컴퓨터와 단말기, 프린터, 모뎀 등의 각종 주변 장치를 연결하는데 사용된다.
시리얼 통신 방식은 동기 (synchronous) 통신과 비동기 (asynchronous) 통신이 있다.
시리얼 통신 프로토콜에는 RS-232, RS-422, RS-485가 있다.
*
시리얼 통신 프로그래밍
시리얼 통신
시리얼 통신에서는 1 바이트를 8개의 비트로 분리해서 1비트씩 전송한다.
시리얼 통신의 최대 데이터 전송 속도는 115,200 bps (bit per second)이다. 즉, 1초에 최대 115,200 비트가 전송 될 수 있다.
하지만, 시리얼 통신은 데이터 전송에 있어서 스타트(start) 비트와 스탑(stop) 비트를 사용하므로 실질적인 최대 데이터 전송 속도는 더 낮다.
전송 모드
단방향 통신 (Simplex): 데이터의 흐름이 한 방향으로 한정되어 있는 방식.
반이중 통신 (Half Duplex): 한쪽에서 송신을 하고 다른 쪽에서는 수신을 하는 모드. 이때, 수신측이 송신할 수 없고 송신측이 송신이 끝나야지만 송신할 수 있다.
전 이중 통신 (Full Duplex): 송신과 수신을 동시에 할 수 있는 방식. 이 방식은 데이터 송신선과 수신선이 구분되어 있는 4선식이 사용된다.
에코 (Echo): 메아리처럼 자신이 보낸 데이터가 도로 돌아오는 방식.
동기식, 비동기식 통신
비동기식: 한번에 1 바이트를 보내는 방식이다. 비동기식에서는 1 바이트를 보내기 위해 앞뒤로 데이터의 시작과 끝을 알리는 신호를 같이 보낸다. 데이터의 시작을 알리는 스타트 비트와 데이터의 안정성을 위한 패러티 (parity) 비트와 데이터의 끝을 알리는 스탑 비트가 있다.
동기식: 데이터를 전송할 때 1바이트 단위로 보내는 것이 아니고 여러 바이트를 묶어서 전송하는 방식이다. 묶음 데이터를 보내기 전에 보내는쪽과 받는 쪽의 타이밍을 맞춰줄 동기 신호를 먼저 보낸다.

태그

이 자료와 함께 구매한 자료

데브피아에서 퍼온글입니다.

Source 자료실의 Serial통신VC소스.zip 를 참고 하세요

----------------------------- 작성자 박주학 ------------------------------

E-mail : or

( 10 쪽 참조 하면서 볼 것 ... )

시리얼 통신의 하드웨어 내용(USB도 시리얼 통신임..단 디바이스 드라이버 차원에서
프를 작성해야함 )

1. RS 232 통신 최대 거리 15 미터 통신 방법 직렬
2. RS 422 통신 최대 거리 1.2 킬로미터 통신 방법 직렬
3. RS 485 통신 최대 거리 1.2 킬로미터 통신 방법 병렬

4. RS 232 -> RS 422 RS 422 -> RS 232 거리가 멀 때에는 이런식으로 컨버터를 이
용해서
RS 232 가 가지고 있는 거리 제한인 12 미터를 1.2 킬로미터 까지 연장이 가능하다.
( 실질적으로 통신 프로그램에서는 해줄것이 아무것도 없다. 단지 양 끝단에 컨버터
를 장착하는 것
뿐.. )

프로그래머 관점에서의 RS - 232

물론 RS - 232 인터페이스 사이의 제어 기능은 하드웨어를 통해 제어된다. 일반적으
로 사용되는
비동기 기능은 UARD ( UNIVERSAL ASYNCHROUS RECEIVE/TRANSMITTER : 범용 비동기 송수

장치 )로 알려져 있는 컨트롤러 IC내에 내장되어 있다. 이 장치는 제어 기능만이 아니
라 데이터 기능도
가지고 있다. UART를 채택하면 시작/종료 비동기 I/O, 데이터 형식, RS - 232 인터페
이스와 같은
사항은 프로그래머가 신경쓰지 않아도 된다. 사실 UART로 바이트를 송수신하는 것은
RAM 위치 즉 I/O
포트에 기록하고 판독하는 작업이다. 이와 유사한 형태로, RS - 232 입출력도 어려움
없이 감시하고
조종할 수 있다.
( 물론 도스에서는 I/O 포트에 직접 접근을 해서 데이터를 읽어 오지만 윈도우에서는
핸들을 생성해서
핸들을 통해서 데이터를 읽고 쓰고 함... )

9핀 짜리 시리얼 커넥터 ( EIA/TIA 574의 9핀 커넥터 )

핀 1 ( DATA CARRIER DETECT : 데이터 반송자 검출 )
이 핀의 공식명칭은 Receive Line Signal Detect인데, 이 핀은 원거리의 반송자가 검
출되었을 때
지정되어 링크시간 내내 그 상태를 유지한다. 물론 반이중 시스템에서는 수신모드만
이 DCD를 지정할
수 있다.

핀 2 ( Receive Data : 수신 데이터 일명 RX 혹은 RD 라고도 함 )
RD는 다른 RS-232 기능과는 독립적이다. 표준에 의하면, RD가 MARK 상태로 있어야 할
경우가 두
가지 있다. 하나는 반송자가 없을 때이고, 또 하나는 반이중 모뎀의 송신 모드에서 수
신 모드로 전환한
뒤 잠시동안이다.

핀 3 ( Transmitted Data : 송신 데이터 일명 TX 혹은 TD 라고도 함 )
TD 선은 DTE의 직렬 데이터를 모뎀으로 송신하는 역할을 한다. 송신기는 회선휴지기
간 동안에는
MARK상태로 있게 된다. DTE는 송신요구, 송신허가, DSR, DTR에 의해 제한을 받는다.
참고: DTE = 데이터단말장치 DTR = 데이터단말기대기 ( DATA TERMINAL READY )
DSR = 데이터세트대기 ( DATA SET READY )

핀 4 ( DATA TERMINAL READY : 데이터 단말기 대기 )

핀 5 ( PROTECTIVE GROUND : 안전 접지선 )

핀 6 ( DATA SET READY : 데이터 세트 대기 )

핀 7 ( RTS REQUEST TO SEND : 송신요구 )

핀 8 ( CTS CLEAR TO SEND : 송신허가 )
RS-232 표준에 의하면, RTS는 모뎀에게 송신 조건을 제공한다. 사실 RTS 핀의 기능은
반이중 모뎀을
송수신 모드로 전환시키는 것이다. 반이중 모뎀이 수신하는 동안에는 RTS 는 금지된
다.
데이터 단말기 장치가 송신할 때, 데이터 단말기 장치는 RTS를 지정하며, 송신하겠다
는 내용을
모뎀에게 알려준다. 모뎀은 즉각적으로 모드 교환을 할 수 없으므로, DTE도 모뎀에게
즉시 데이터를
송신할 수는 업다. 따라서 RTS를 지정한 뒤 DTE는 CTS를 감시하기 시작한다. 모드 교
환이 완벽하게
이루어 지면 모뎀은 DTE가 데이터를 송신해도 좋다는 것을 DTE에게 알려주기 위해 CTS
를 지정하게
된다. 송신 모드에서 수신 모드로 다시 전환할 때는 RTS/CTS 핸드 쉐이킹이 반대로 이
루어 질 것이다.

전이중 연결은 양방향 채널이므로, RTS/CTS 핸드쉐이킹은 불필요하다. 따라서, 전이
중 모뎀에서는
CTS가 DCD에 항상 연결되어 있다.

핀 9 여분..

25 핀 ( EIA/TIE-E의 25핀 커넥터 )

핀 1 ( PROTECTIVE GROUND : 안전 접지선 )
9 핀 짜리에 5 번과 같음..

핀 2( Transmitted Data : 송신 데이터 일명 TX 혹은 TD 라고도 함 )
TD 선은 DTE의 직렬 데이터를 모뎀으로 송신하는 역할을 한다. 송신기는 회선휴지기
간 동안에는
MARK상태로 있게 된다. DTE는 송신요구, 송신허가, DSR, DTR에 의해 제한을 받는다.
참고: DTE = 데이터단말장치 DTR = 데이터단말기대기 ( DATA TERMINAL READY )
DSR = 데이터세트대기 ( DATA SET READY )
9 핀 짜리에 3 번과 같음...

핀 3( Receive Data : 수신 데이터 일명 RX 혹은 RD 라고도 함 )
RD는 다른 RS-232 기능과는 독립적이다. 표준에 의하면, RD가 MARK 상태로 있어야 할
경우가 두
가지 있다. 하나는 반송자가 없을 때이고, 또 하나는 반이중 모뎀의 송신 모드에서 수
신 모드로 전환한
뒤 잠시동안이다.
9 핀 짜리에 2 번과 같음...

핀 4 ( RTS REQUEST TO SEND : 송신요구 )
9 핀 짜리에 7 번과 같음...

핀 5 ( CTS CLEAR TO SEND : 송신허가 )
RS-232 표준에 의하면, RTS는 모뎀에게 송신 조건을 제공한다. 사실 RTS 핀의 기능은
반이중 모뎀을
송수신 모드로 전환시키는 것이다. 반이중 모뎀이 수신하는 동안에는 RTS 는 금지된
다.
데이터 단말기 장치가 송신할 때, 데이터 단말기 장치는 RTS를 지정하며, 송신하겠다
는 내용을
모뎀에게 알려준다. 모뎀은 즉각적으로 모드 교환을 할 수 없으므로, DTE도 모뎀에게
즉시 데이터를
송신할 수는 업다. 따라서 RTS를 지정한 뒤 DTE는 CTS를 감시하기 시작한다. 모드 교
환이 완벽하게
이루어 지면 모뎀은 DTE가 데이터를 송신해도 좋다는 것을 DTE에게 알려주기 위해 CTS
를 지정하게
된다. 송신 모드에서 수신 모드로 다시 전환할 때는 RTS/CTS 핸드 쉐이킹이 반대로 이
루어 질 것이다.

전이중 연결은 양방향 채널이므로, RTS/CTS 핸드쉐이킹은 불필요하다. 따라서, 전이
중 모뎀에서는
CTS가 DCD에 항상 연결되어 있다.
9 핀 짜리에 8 번과 같음...

핀 6 ( DATA SET READY : 데이터 세트 대기 )
9 핀 짜리에 6 번과 같음...

핀 7 ( Signal Ground or Common : 신호접지선 또는 공통선 )

핀 8 ( DATA CARRIER DETECT : 데이터 반송자 검출 )
이 핀의 공식명칭은 Receive Line Signal Detect인데, 이 핀은 원거리의 반송자가 검
출되었을 때
지정되어 링크시간 내내 그 상태를 유지한다. 물론 반이중 시스템에서는 수신모드만
이 DCD를 지정할
수 있다.
9 핀 짜리에 1번과 같음...

핀 9 예비 검사용

핀 10 예비 검사용

핀 11 예비 검사용

핀 12 ( Secondary Received Line Signal Detect : 수신 회선신호검출 - 2차 )

핀 13 ( Secondary Clear to Send : 송신 허가 - 2차 )

핀 14 ( Secondary Transmitted Data : 송신 데이터 - 2차 )

핀 15 ( Transmission Signal Element Timing : 신호 소자 타이밍 전송 )

핀 16 ( Secondary Received Data : 수신 데이터 - 2차 )

핀 17 ( Receiver Signal Element Timing : 신호 소자 타이밍 수신기 )

핀 18 ( Local Loopback : 지역 복귀루프 )

핀 19 ( Secondary Request to Send : 송신 요구 - 2차 )

핀 20 ( Data Terminal Ready : 데이터 단말기 대기 )
9 핀 짜리에 4번하고 같음...

핀 21 ( Signal Quality Detect : 신호 품질 검출기 )

핀 22 ( Ring Indicator : 링 지시기 )

핀 23 ( Data Signal Rate Detector : 데이터 신호 전송율 검출기 )

핀 24 ( Transmit Signal Element Timing : 신호 소자 타이밍 송신 )

핀 25 ( Test mode : 시험 모드 )

---------------------- 프로그램 ( 비주얼 씨++ 6.0 ) -----------------------
헤더 파일

// SCom.h: interface for the CSCom class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_SCOM_H__5A71548F_CE4C_11D4_A6B3_00E09833FB7C__INCLUDED_)
#define AFX_SCOM_H__5A71548F_CE4C_11D4_A6B3_00E09833FB7C__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

class CSCom
{
public:
CString ReadCmd();
void WriteCmd(CString write_str);
void ComEnd();
void ComStart();
CSCom();
virtual ~CSCom();

DCB dcb;
OVERLAPPED osRead;
OVERLAPPED osWrite;
COMMTIMEOUTS CommTimeOuts;
HANDLE idCom;
};

#endif // !defined(AFX_SCOM_H__5A71548F_CE4C_11D4_A6B3_00E09833FB7C__INCLUDED_)

CPP 파일

// SCom.cpp: implementation of the CSCom class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "Serial.h"
#include "SCom.h"

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[]=__FILE__;
#define new DEBUG_NEW
#endif

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

CSCom::CSCom()
{

}

CSCom::~CSCom()
{

}

void CSCom::ComStart()
{

if((idCom = CreateFile("COM1", GENERIC_READ | GENERIC_WRITE, 0, NULL,

OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED, NULL)) != (HANDLE)-1)
{
SetCommMask(idCom, EV_RXCHAR);
SetupComm(idCom,4096,4096);
PurgeComm(idCom,PURGE_TXABORT | PURGE_RXABORT |
PURGE_TXCLEAR |
PURGE_RXCLEAR);

CommTimeOuts.ReadIntervalTimeout = 5000;//0xFFFFFFFF;
CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
CommTimeOuts.ReadTotalTimeoutConstant = 0;
CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
CommTimeOuts.WriteTotalTimeoutConstant = 5000;
SetCommTimeouts(idCom, &CommTimeOuts);
}
GetCommState(idCom,&dcb);

dcb.BaudRate = CBR_9600;
dcb.ByteSize = 8;
dcb.Parity = EVENPARITY;
dcb.StopBits = ONESTOPBIT;

if(SetCommState(idCom,&dcb) != TRUE)
AfxMessageBox(_T("Error : Set COM1 State"));

////////////////////////////////////////////////////
osRead.Offset = 0;
osRead.OffsetHigh = 0;
//--> Read 이벤트 생성에 실패..
if ( !(osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)) )
{
AfxMessageBox("!(osRead.hEvent = CreateEvent(NULL, TRUE,
FALSE, NULL))");
// return FALSE;
}

osWrite.Offset = 0;
osWrite.OffsetHigh = 0;
//--> Write 이벤트 생성에 실패..
if (! (osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL)))
{
AfxMessageBox("! (osWrite.hEvent = CreateEvent(NULL, TRUE,
FALSE, NULL))");
// return FALSE;
}
///////////////////////////////////////////////

}

void CSCom::ComEnd()
{
if(idCom != NULL)
{
PurgeComm(idCom,PURGE_TXABORT | PURGE_RXABORT |
PURGE_TXCLEAR |
PURGE_RXCLEAR);
if(CloseHandle(idCom) != TRUE)
{
AfxMessageBox(_T("Error : Close COM Port"));
return;
}
}

}

void CSCom::WriteCmd(CString write_str)
{
DWORD nToWrite , dwWrite;
char imsi ;
imsi = 0x0d ;
nToWrite = write_str.GetLength()+1 ;
write_str = write_str + imsi ;

WriteFile(idCom,write_str,nToWrite,&dwWrite,&osWrite);

}

CString CSCom::ReadCmd()
{
DWORD dwRead, dwErrorFlags;
DWORD nToRead = 1;
BYTE pBuff[40], pBuff1[40];
int BufIndex;
COMSTAT comstat;

memset(&pBuff,"\0",40);

BufIndex = 0;
do
{
ClearCommError(idCom, &dwErrorFlags, &comstat);
dwRead = comstat.cbInQue;
if(dwRead >= 1)
{
ReadFile(idCom,pBuff1,nToRead,&nToRead,&osRead);
pBuff[BufIndex++] = pBuff1[0];
}
}while(pBuff1[0] != 0x0d);

CString RtnMsg;
RtnMsg = pBuff;
// int str_su ;
// str_su = RtnMsg.GetLength();
// RtnMsg = RtnMsg.Left(str_su-1);

return RtnMsg;
}

4. 시리얼 통신시 계속해서 데이터를 체크 해야 하는 경우에는 이벤트 쓰레드를 가동
한다..

이벤트쓰레드로 큐를 체크하는 방식으로 작성을 하면 됨..

( 위에 방법은 일반적으로 통신 프로그램에서 쓰임.. ) 예 ) 이야기 7.0 , 새롬 데이
터맨

5 쓰레드 동기화 관련 ..
쓰레드 동기화 에는 크게 4종류에 쓰레드가 있음..
크리티컬 섹션 ( criticalsection ) 뮤덱스 ( mutex ) 세마포어 ( semaphore ) 위에

언급한 이벤트 ( event )

5 - 1 criticalsection

내용 : 공유 데이터나 리소스(메모리나 파일) 에 대한 접근을 하나의 스레드로 제한
할 때 사용함..
( dll 이나 기타 active x 혹은 컴포넌트로 만들 수 없음 ) .. 만들고 싶음 만
들어 보세요..

특징 : 동일한 프로세스 내의 스레드간 동기화에 사용한다.

5 - 2 mutex

내용 : 크리티컬 섹션과 동일한 기능을 가지지만 차이가 있다면 다른 프로세스에 속
한 스레드를 제어
할 수 있다. ( dll 이나 기타 active x 혹은 컴포넌트로 만들 수 있음 )

특징 : 다른 프로세스 내의 스레드간에 사용가능

5 - 3 semaphore

내용 : 뮤텍스와 유사하지만 동시에 수행할 수 있는 스레드를 여러 개로 제한할 수 있
다.
심지어. 먼저실행할 스레드까지도 가능함.. ( 이건 잘 모르겠당.. 브이씨에서
는 돼던데.. )
( 예 : 4 개의 스레드중 2 개씩을 cpu에서 돌린다는가 하는... )

특징 : 다른 프로세스 내의 스레드간에 사용가능

5 - 4 event

내용 : 스레드간 동기화 방법 중 가장 널리 사용되는 것으로 하나의 스레드가 다른 스
레드에 특정
이벤트가 발생했음을 알려준다.

특징 : 다른 프로세스 내의 스레드간에 사용가능

시리얼 통신에서 염두할 것은.. 데이터에 신빙성과..( 깨진 데이터를 가지고 장난 하
면 안돼겠쥐영..)

도스에서는 인터럽트를 직접 건드려서 시리얼 통신을 하지만.. 윈도우에서는 핸들이라
는 놈을 통해서
( 위에 땜시 VXD 파일이라는 놈도 있고 디바이스 드라이버 프로그래머도 먹고
사는 것이쥐영 )
모든 하드웨어 통신을 하는데 각 포트당 핸들은 하나라는 점....

( 공장 자동제어 쪽에서는 특성상 크리티컬 섹션이 용이함.. ) <= DLL 이나 ACTIVE X

COMPONENT 로 만든다면 MUTEX 로...
단 간단한 프로토콜은 이벤트가 훨씬 유리함..

어떤 스레드를 사용할지는 그 상황에 맏게 적절히 선택해야 한다..
( 잘못하면 엿먹는 수가 있음... ) < = 소스 전체를 뒤집어 업는.........

델파이에서는 시리얼 통신을 컴포넌트야 장난 이고.... 위에 개념만 알면.. API 함수
를 끌어다가

하면 돼겠쥐영..

참고.. 시리얼 통신할때에는 아스키코드 테이블 필수입니다...

-------------------------- 이 다음 부분은 퍼온 것 임 ---------------------------
-
... 워드 치기 귀찮아서
리.. ㅋㅋㅋㅋ
일이 넘 바빠서리.. 지금도 3일째 날새고.. 쓰러지기 일부직전.. 회사서 맛 갔다가
고.. 오늘은 들어가서
쉬라는 군여.. 참 올만에 저녁 9시전에 퇴근을 했네영.... -.-;;; 퍼온 것이라고
그냥 넘어가느냐..
절대 알짤 없쥐영..물론 위에서 빠진 부분만 하겠습니다.. -.-;;; < = 아 다음 사람
을 배려하는 이맘....

통신 프로그램 만들기

제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가 올 수도 있지 않습니까? 즉 들어오는 뒤의 데이터를
보고 앞의
데이터를 생각할 경우도 있다는 것입니다. 그렇기 때문에 데이터를 받아 두는 큐가 필
요하고 이 큐를
어떻게 사용할지는 독자들의 마음입니다.

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

w 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 클래스를 사용하여 통신을 하는 아주 간단한 통
신 프로그램
예제입니다.

콤보 상자에서 "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()
{
}