오버랩드 시리얼 통신 소스 MFC로 되어 있음
출처는... 잊어먹었음... ㅡㅡ;
아마도 박씨가 만든거 같음....
참고로 메모리 릭은 있는거 같음... 아님말고~
=================================================================
시리얼 통신에 관한 글 퍼온거
출처 : http://elishot.tistory.com/entry/dip-win32-%EC%8B%9C%EB%A6%AC%EC%96%BC-%ED%86%B5%EC%8B%A0-%EA%B0%95%EC%A2%8C
Win32 시리얼 통신 강좌
#10768 박경환 (jiocafe )
[강좌] WIN32 시리얼 통신 #1 07/24 12:47 60 line
윈도우에서 통신 프로그램을 개발하고자 하는 모든 이에게 제가 가진 모든
지식을 드립니다. 저는 도스에서 그래픽 통신 에뮬레이터를 개발한바 있고,
터치 스크린 등의 시리얼 제어에 많은 시간을 투자한 바 있는 사람입니다.
이제 부터 설명하고자 하는 내용은 Visual C++ 4.0버전에서 MFC를 사용하고
멀티 쓰레드로 RS232포트를 감시하는 프로그래밍 과정입니다.
직렬 포트 제어에 관한 기본 지식은 설명하지 않으며, 도스와 윈도우 3.1에
서 프로그래밍을 하셨던 분들이 WIN32프로그래밍을 하려 할때 차이점에 대
하여 논하는 방식으로 진행하도록 하겠습니다.
DOS 세계와 WIN32세계 차이
==========================
도스에서는 RS232포트를 제어하기 위해 시리얼 포트 인터룹트 핸들러 루틴
을 제작하여야 했고, 인터룹트 벡터를 바꾸어야 했고, 버퍼를 잡아 주는 등
의 작업이 우선되어야 하지만. 이 일은 윈도우 운영체계가 합니다.
프로그래머가 직접 제어하는 일은 윈도우에서 권장되고 있지 않을 뿐더러
다른 많은 문제점을 유발 시킵니다. 동작하는 프로그램이 도스 처럼 1개가
아니기 때문에 시리얼을 사용하려는 프로그램이 몇개가 될지 아무도 알수
없기 때문입니다.
윈도우에서는 버퍼를 얼마만큼 쓰고싶고, 통신 파라메터는 어떻게 쓰겠다고
주어진 규칙에 따라 청구서를 만들어 윈도우에 제출하면, 윈도우가 그 장치
가 놀고 있으면 사용허가를 즉시 내 주고, 다른 프로그램이 사용중이면 사
용을 불허합니다. 청구서는 DCB입니다. DCB를 키보드로 치고 도움말 키를
누르면 VC ++ 4.0은 즉시 보여 줍니다.
WIN16과 WIN32차이
=================
윈도우 16비트 버전에서(3.1과 3.11 포함)에서 지원되던 SetCommEventMask
(), GetCommError()등의 함수가 있었는데, VC ++ 4.0에서는 이 함수들이 사
라졌다. 다른 함수로 통합되거나 기능이 아예 없어졌다. 이런 함수들은 한
둘이 아니며 그 목록을 알고자 하면 VC++ 4.0의 도움말에 Summary of Funct
ion and Message Differences를 찾아보면 알 수 있다.
제어 가능한 범위
================
시리얼 포트의 속도는 최대 256000BPS까지 가능하다. 이는 CBR_110 부터 CB
R_256000으로 define되어 있는 헤더 파일을 보면 알 수 있다.
CTS, RTS, DSR, DTR등의 제어가 가능하다.
이벤트는 WaitCommEvent()함수를 참조하면 된다.
1. 입력에서 BREAK신호가 오는 경우 EV_BREAK
2. CTS(clear to send) 신호가 오는 경우 EV_CTS
3. DSR(data set ready)신호가 오는 경우 EV_DSR
4. 라인이 에러가 발생한 경우 EV_ERR
5. 전화가 걸려온 경우 EV_RING
6. RLSD(receive line signal detect) 신호가 오는 경우 EV_RLSD
7. 입력 버퍼에 하나의 문자가 수신된 경우 EV_RXCHAR
8. 이벤트 문자가 수신되고 입력 버퍼에 놓이면 EV_RXFLAG
이벤트 문자는 DCB구조체 안에 지정되고, SetCommState()함수에 의
해 직렬포트를 조작한다.
9. 출력 버퍼에서 마지막 문자가 보내지면 EV_TXEMPTY
이벤트 문자는 다소 생소한 내용일 것이다. 이것은 이벤트가 발생했을때 입
력 버퍼로 어떤 문자가 수신된 것으로 처리하는 것을 말한다. DCB에서 '?'
문자를 지정했다면 이벤트가 발생하면 '?'문자가 수신된 문자와 함께 들어
온다.
#10770 박경환 (jiocafe )
[강좌]WIN32 시리얼 통신 #2 07/28 19:10 238 line
이번이 두번째 강좌입니다. 몇번째까지 가게 될지 아직 모릅니다. 충분히
설명이 되었다고 생각되면 강좌를 마칠 생각입니다. 이 강좌에 대한 질문을
하시는 분의 메일이 오면 참고하여 그것에 대하여도 설명할까 합니다.
이번 강좌는 포트 초기화가 주제입니다. 포트를 초기화 하기 위해서는 윈도
우 운영체제로 부터 포트 사용허가를 얻어야 합니다. 다른 프로그램이 그
포트를 사용하고 있지만 않다면 허가는 바로 떨어집니다. 또한 자신의 프로
그램이 포트 사용을 완료하고도 닫아주지 않으면 자신도 허가를 다시 얻을
수 없습니다. 사용허가가 나면 핸들을 돌려주는데 핸들을 받드시 보관하고
있다가 사용이 끝나면 그 장치 핸들로 장치를 닫아 주어야만 합니다.
단계별 요약
1. 장치에 대한 핸들을 구한다. CreateFile() 함수를 사용합니다.
2. 핸들을 보관한다. 글로벌 변수에 저장하는 것이 편합니다.
3. 포트를 어떻게 초기화 할것인지 초기화 값을 설정합니다. DCB구조체룰
채워서 합니다.
4. 초기화 값을 핸들에 적용합니다.
1단계 : 장치 핸들 구하기
어떤 함수든 Visual C++ 4.0의 편집기에서 이름만 치고 그 위에 커서를 같
다 두고 F1키를 누르면 그 함수에 대한 정보를 볼 수 있습니다. 도움말이
나타나면 분류별 선택을 하게 됩니다. 대부분의 경우 각 함수들은 WIN32
SDK용과 MFC에서 사용되는 것으로 구분되어 도움말이 나옵니다.
우리가 하고자 하는 것은 MFC로 하는 것이므로 MFC를 선택합니다. 함수의
프로토타입과 그 함수가 속한 클래스, 함수 인자 목록과 각 함수 인자 목록
이 어떤 의미를 갖는지에 대하여 설명이 되어 있습니다. 설명은 거의 완벽
하다고 생각됩니다. 다만, 한글이 아니고 순수 영어라는 점과 그 함수에 대
한 예재가 함께 들어있지 않다는 것입니다.
만일 그 함수에 대한 예제 소스를 보고자 한다면 SAMPLE디렉토리의 파일들
중에 그 함수가 들어있는 것을 모두 검색하는 기능을 사용하면 쉽게 어떤
SAMPLE에 포함되어 있는지를 알 수 있습니다.
이제 CreateFile 함수에 대하여 알아볼 차례입니다. 이 함수는 파일을 생
성하는데도 사용될 수 있으며 시리얼 포트를 여는데도 사용됩니다. 시리얼
포트는 1번이면 " COM1", 2번이면 "COM2" 식으로 이름이 이미 정의되어
있으므로 그 이름을 사용하면 쉽게 핸들을 구할 수 있게 됩니다. 따라서
멀티 포트카드를 사용하거나 할때 멀티포트 카드를 본체에 장착하고 그 디
바이스 드라이버를 시스템에 등록하면 새로운 장치 이름이 시스템에 등록되
어 지금 설명하고자 하는 방식으로 핸들을 구해내어 사용할 수 있게 됩니다.
CreateFile 함수에 대한 설명을 앞서 설명한 방법으로 찾아보면 A4용지로
인쇄했을때 7페이지 분량의 설명이 나옵니다. 첫줄에 씌어 있기를 "한 파
일, 파이프, 커뮤니케이션 자원, 디스크 장치, 콘솔 등을 생성하기, 열기,
자르기 한다. 리턴값은 객체에 접근하는데 사용되어질 수 있는 핸들이다."
라고 되어 있습니다.
윈도우 3.1에서는 Com포트에 대한 함수인 OpenComm(), CloseComm()함수가
별도로 정의되어 있으나 Visual C++ 4.0에서는 존재하지 않습니다. 무섭게
시리 Dropped라고 도큐멘트에 나와 있지요.
이제 제가 만들어서 사용하는 소스는 이렇습니다. 예재의 내용과 비슷합니
다.
str.Format("COM%d", pComm->PortNumber);
pComm->hDev = CreateFile(str, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, NULL);
if (pComm->hDev == INVALID_HANDLE_VALUE)
{
CString strError;
strError.Format("시스템으로 부터 %s 장치 사용 허가를 얻어낼수 없습니
다", str);
AfxMessageBox(strError);
return FALSE;
}
str은 CString 형 변수로 MFC에서 제공하는 문자열 클래스죠. 이 클래스
에 대한 설명은 생략하겠습니다. 필요하신 분은 도움말을 보시기 바랍니다.
pComm이 지시하는 것은 포트에 대한 정보를 담고 있는 것이고, PortNumber
항목은 사용하고자 하는 시리얼 포트의 번호입니다.
이렇게 첫줄이 실행되면 1일 경우 "COM1"이 되고 2는 "COM2"가 되죠. 전통
적인 C로 작성한다면 다음과 같습니다.
char str[5];
sprintf(str, "COM%d", pComm->PortNumber);
이제 두번째 줄 차례...
str이 "COM1"이 되었다고 가정하면, CreateFile()함수의 첫번째 인자인
pointer to name of the file에 "COM1"이 대응됩니다.
두번째 인자는 읽고 쓰기 모드를 선택합니다. 이부분은 다 아실테니
GENERIC_READ | GENERIC_WRITE 가 읽기와 쓰기를 다 할 수 있도록 한다는
것만으로 넘어갑니다.
세번째 인자는 파일 공유 모드로 도스에서는 프로그램이 혼자 돌아가니 다
른 프로그램이 자신의 파일을 접근하는 일이 없어 무의미 하지만 윈도우 다
중 작업 환경에서는 다른 프로그램도 동시에 동작하니 의미가 있습니다.
FILE_SHARE_READ를 지정하면 자신의 프로그램이 사용하는 파일을 다른 프로
그램도 읽을 수가 있습니다. 그러나 FILE_SHAR_READ를 지정하지 않으면 다
른 프로그램이 읽고자 할때 윈도우가 접근을 거부하므로 그 프로그램은 읽
기 작업을 실패하게 됩니다.
네번째 인자는 보안에 대한 설정입니다. 시스템이 지원해야만 효과가 있다
고 도큐먼트에 나와 있습니다. 필요없으므로 생략합니다.
다섯번째 인자는 파일이 이미 존재할때와 존재하지 않을때 어떤 액션을 취
할 것인지 지정합니다.
CREATE_NEW 파일을 새로 만들되 이미 존재하면 실패합니다.
CREATE_ALWAYS 새로이 만들되 파일이 이미 존재하면 덮어 쓰기가 됩니다.
OPEN_EXISTING 파일이 이미 존재해야만 성공합니다.
OPEN_ALWAY
S 파일이 존재하면 열고, 존재하지 않으면 CREATE_NEW처럼 새로
만듭니다.
TRUNCATE_EXISTING 파일이 이미 존재하면 파일의 크기가 0바이트 크기로 짤
려 데이타가 없어집니다.
너무 자세히 설명하다보니 라이브러리 레퍼런스를 만드는 것 같습니다. 나
머지 설명은 생략합니다.
함수의 리턴값은 핸들이 됩니다. 만일 윈도우에서 어떤 프로그램인가가 그
포트를 사용중이거나 그 포트가 없다면 리턴값이 INVALID_HANDLE_VALUE이
됩니다. INVALID_HANDLE_VALUE은 헤더에 #define되어 있습니다.
HANDLE은 void *로 #define되어 있습니다. 물론 이것도 헤더에 있지요.
if (pComm->hDev == INVALID_HANDLE_VALUE)
이 조건문에서 그 객체(장치나 파일이)가 사용할 수 있는 상태인지를 알 수
있습니다. 사용할 수 없는 상태라면 INVALID_HANDLE_VALUE가 아닌 어떤 포
인터가 넘어옵니다.
조건이 참이 되는 경우, 즉 사용할 수 없는 경우 적절한 메시지를 표시하면
됩니다. 이때 AfxMessageBox()를 사용하면 편리합니다. MFC에서 제공되는
함수이고 윈도우 3.1버전에서의 메시지 박스와 사용법이 비슷합니다. 설명
이 필요하면 역시 F1을 누르면 됩니다.
2. 단계 : 핸들을 보관
자 이렇게 핸들을 구했습니다. 핸들은 저장되어야 하고 나중에 장치를 닫을
때 그 핸들이 다시 사용됩니다. 클래스 내에서만 사용한다면 클래스 내부
변수에 저장할 수도 있겠지만, 이 강좌의 목적이 멀티 쓰레드 기능을 이용
해 포트를 계속 적으로 감시하며 다른 작업을 하는 것을 설명하고자 하는
것이므로 다른 함수 내에서도 사용할 수 있어야 하는 이유로 글로벌 변수
(광역 변수라고도 칭하지요)에 저장합니다.
소스에서 pComm은 구조체가 글로벌 변수로 정의되어 있는 곳을 지정하는 포
인터로 정의되어 있으므로 pComm->hDev 에 대입하는 것만으로 이미 저장이
끝났습니다.
3. 단계 : 포트 초기화 값 지정
DCB는 직렬 통신 장치에 대한 제어 샛팅을 정의합니다. 역시 같은 방법으로
DCB를 치고 F1을 누르면 그 설명이 나옵니다. 직접 포트를 제어하는 도스
에서는 볼 수 없었던 것이므로 도스 프로그래머는 다소 황당한 느낌이 들수
있습니다. 프로그래머가 하는일이 늘 그렇듯 차근 차근히 뜯어보는 수밖에.
DCB는 헤더에 typedef되어 있는 것이므로 앞에 struct를 붙일 필요는 없습
니다. 아래는 DCB의 실체입니다. 주석문은 제가 내용을 변경한 것이므로
원래의 주석문은 아니니 착오 없길 바랍니다.
typedef struct _DCB { // dcb
DWORD DCBlength; // sizeof(DCB)
DWORD BaudRate; // 전송 속도(BPS단위의 수치)
DWORD fBinary: 1; // 바이너리 모드, 항상 1임
DWORD fParity: 1; // 패리티 검사함
DWORD fOutxCtsFlow:1; // CTS 출력 흐름 제어
DWORD fOutxDsrFlow:1; // DSR 출력 흐름 제어
DWORD fDtrControl:2; // DTR 흐름 제어 타입
DWORD fDsrSensitivity:1; // DSR 민감하게
DWORD fTXContinueOnXoff:1; // XOFF면 전송 계속
DWORD fOutX: 1; // XON/XOFF 출력 흐름 제어
DWORD fInX: 1; // XON/XOFF 입력 흐름 제어
DWORD fErrorChar: 1; // 에러시 문자로 교환할까?
DWORD fNull: 1; // 0번 문자 없애기
DWORD fRtsControl:2; // RTS 흐름 제어
DWORD fAbortOnError:1; // 에러면 읽고 쓰기 중지
DWORD fDummy2:17; // 나중에
쓰려고 예약된 영역
WORD wReserved; // 나중에 쓰려고 예약된 영역
WORD XonLim; // XON 보낼 때
WORD XoffLim; // XOFF 보낼 때
BYTE ByteSize; // 데이타 비트 수, 4-8
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; // end of input character
char EvtChar; // received event character
WORD wReserved1; // reserved; do not use
} DCB;
DCBlength DCB구조체의 길이를 바이트 단위로 지정합니다.
Specifies the length, in bytes, of the DCB structure.
BaudRate
전송속도를 지정하는데 이 값은 다음과 같이 정의되어 있습니다.
CBR_110 CBR_19200
CBR_300 CBR_38400
CBR_600 CBR_56000
CBR_1200 CBR_57600
CBR_2400 CBR_115200
CBR_4800 CBR_128000
CBR_9600 CBR_256000
CBR_14400
fBinary
이진 모드를 가능하게 할 것인지 지정하는데, WIN32 API는 이진
모드만을 지원합니다. 따라서 항상 TRUE로 지정하고, TRUE가 아니면
동작하지 않는다고 되어 있습니다.
윈도우 3.1에서 FALSE로 지정되면 EofChar로 지정한 문자가 들어오면
데이타의 끝으로 인식합니다.
fParity
패리티 검사를 할것인지 지정합니다. 만약 이것이 TRUE이면 패리티
검사를 하게되고 에러가 레포트되어집니다.
fOutxCtsFlow
송신 흐름 제어에 대한 CTS(clear-to-send)신호를 감시할 것인지
정합니다. 만약 이것이 TRUE이고 CTS가 꺼지면 CTS가 다시 보내져올
때까지 송신이 억제됩니다.
fOutxDsrFlow
송신 흐름 제어에 대한 DSR (data-set-ready)신호를 감시할 것인지를
모니터합니다. 만약 이것이 TRUE이고 DSR이 꺼지면 DSR이 다시
올때까지 송신이 억제됩니다.
DCB에 대한 설명은 여기까지만 하기로 합니다. 아직 많이 남아 있지만
구구 절절이 장황하게 레퍼런스를 번역하는 것 보다는 소스를 보고
원하는 제어 방식에서 바뀌는 부분만을 지적하는 것이 더
효율적이겠습니다. 저는 현재 왼쪽 팔이 부러져 기브스를 하고 있는
상태라 워드를 치는 것이 쉽지는 않은 상태라 힘이 듭니다.
단계 4. 초기화 값을 적용
마지막 단계를 설명합니다. 자, 이제 이번 강좌의 마지막 부분입니다.
저도 기쁜 마음이 듭니다.
먼저 소스를 보시기 바랍니다.
BOOL ret = SetCommState(pComm->hDev, &pComm->dcb);
if (ret == FALSE) {
TRACE1("SetCommState() 포트%d실패", pComm->PortNumber);
}
else
{
pComm->bConnected = TRUE;
}
이 소스는 제가 실제 사용하고 있는 부분을 그대로 복사하여 붙인
것입니다.
초기화 값을 적용하는 방법은 SetCommState()함수를 사용하는 것입니다.
첫째 인자에는 핸들을 두번째 인자에는 DCB가 있는 주소를 넣어 줍니다.
리턴되는 값은 성공 여부가 불린으로 나옵니다. 물론 BOOL은 타입
정의되어 있는 것입니다. 핸들은 앞서 설명한 CreateFile함수에서 얻어진
포인터 입니다. CreateFile에서 성공하면 SetCommState함수에서는
실패하는 경우가 없었습니다. COM포트가 사용중이면 이미 CreateFile에서
에러가 발생해 더이상 진행하지 못하게 되니 성공 여부 확인 루틴은
무의미 할 수도 있다고 생각되지만 도큐멘트 상으로 그것이 확실히 나와
있지는 않으므로 확인 루틴을 넣은 것입니다.
이번 강좌는 이것으로 마칩니다. 끝까지 읽느라 수고 많으셨습니다. 다음
강좌에는 설정한 포트에 대한 송신, 수신 버퍼를 잡는 과정에 대하여
설명하도록 하겠습니다.
#10771 박경환 (jiocafe )
[강좌] WIN32 시리얼 통신 #3 08/02 22:34 474 line
이번 강좌는 세번째 강좌입니다. 이번 강좌의 주제는 시리얼 포트의 송수신 버퍼
잡기입니다.
어떤 분이 편지를 보내 오셨는데, 그 요지가 설명은 이해가 가는데 코딩을 할 수
없더라는 것이었습니다. 그래서 코딩 예재를 보여 드려야겠다는 생각이 들었습니
다. 아직도 저의 왼쪽 팔은 기브스가 있기 때문에 워드를 치기가 쉽지 않고 시간
도 없어서 새로운 예재를 만들기 보다 제가 현재 개발중인 프로그램 중 한 파일
을 그대로 공개합니다. 참고하시기 바라며, 이 소스는 VC++ 4.0의 예재와 거의
같습니다.
#include "stdafx.h"
#include "wingen.h"
#include "CommIO.h"
#define ASCII_BEL 0x07
#define ASCII_BS 0x08
#define ASCII_LF 0x0A
#define ASCII_CR 0x0D
#define ASCII_XON 0x11
#define ASCII_XOFF 0x13
DWORD FAR PASCAL CommWatchPrinter( LPSTR lpData )
{
DWORD dwEvtMask ;
OVERLAPPED os;
#define MAXBLOCK 4096
BYTE abIn[MAXBLOCK + 1];
int nLength;
memset( &os, 0, sizeof( OVERLAPPED ) ) ;
// create I/O event used for overlapped read
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!", "TTY Error
!",
MB_ICONEXCLAMATION | MB_OK ) ;
return ( FALSE ) ;
}
if (!SetCommMask(WinGenInfo.Printer.hDev, EV_RXCHAR | EV_TXEMPTY)) {
MessageBox( NULL, "SetCommMask on CommWatchPrinter fail", "TTYErro
r!",
MB_ICONEXCLAMATION | MB_OK ) ;
return ( FALSE ) ;
}
while (WinGenInfo.Printer.bConnected)
{
dwEvtMask = 0 ;
WaitCommEvent(WinGenInfo.Printer.hDev, &dwEvtMask, NULL );
if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)
{
do {
if (nLength = ReadCommBlock(&WinGenInfo.Printer, (LPSTR)abI
n, MAXBLOCK)) {
abIn[nLength] = 0;
TRACE1("Printer [%s]", abIn);
}
} while (nLength > 0);
}
}
// get rid of event handle
CloseHandle( os.hEvent ) ;
// clear information in structure (kind of a "we're done flag")
WinGenInfo.Printer.dwThreadID = 0 ;
WinGenInfo.Printer.hCommWatchThread = NULL ;
return( TRUE ) ;
} // end of CommWatchPrinter()
DWORD FAR PASCAL CommWatchScanner( LPSTR lpData )
{
DWORD dwEvtMask ;
OVERLAPPED os;
#define MAXBLOCK 4096
BYTE abIn[MAXBLOCK + 1];
int nLength;
memset( &os, 0, sizeof( OVERLAPPED ) ) ;
// create I/O event used for overlapped read
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!", "TTY Error
!",
MB_ICONEXCLAMATION | MB_OK ) ;
return ( FALSE ) ;
}
if (!SetCommMask(WinGenInfo.Scanner.hDev, EV_RXCHAR ))
{
MessageBox( NULL, "SetCommMask on CommWatchScanner fail", "TTY Erro
r!",
MB_ICONEXCLAMATION | MB_OK ) ;
return ( FALSE ) ;
}
while (WinGenInfo.Scanner.bConnected)
{
dwEvtMask = 0 ;
WaitCommEvent(WinGenInfo.Scanner.hDev, &dwEvtMask, NULL );
if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)
{
do {
if (nLength = ReadCommBlock(&WinGenInfo.Scanner, (LPSTR)abI
n, MAXBLOCK)) {
WinGenInfo.wndWork->SendMessage(WM_PENWINFIRST, nLengt
h, (LONG)&abIn);
}
} while (nLength > 0);
}
}
// get rid of eventhandle
CloseHandle( os.hEvent ) ;
// clear information in structure (kind of a "we're done flag")
WinGenInfo.Scanner.dwThreadID = 0 ;
WinGenInfo.Scanner.hCommWatchThread = NULL ;
return( TRUE ) ;
} // end of CommWatchScanner()
int ReadCommBlock(struct tagCommInfo * pComm, LPSTR lpszBlock, int nMaxLeng
th)
{
DWORD dwErrorFlags;
DWORD dwLength;
COMSTAT ComStat;
CString szError;
DWORD dwError = 0;
ClearCommError(pComm->hDev, &dwErrorFlags, &ComStat);
dwLength = min((unsigned)nMaxLength, ComStat.cbInQue);
if (dwLength > 0) {
int ret = ReadFile(pComm->hDev, lpszBlock, dwLength, &dwLength, &pC
omm->osRead);
if (!ret) {
if (GetLastError() == ERROR_IO_PENDING) {
while(!GetOverlappedResult(pComm->hDev, &pComm->osRead, &dw
Length, TRUE)) {
dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE) continue;
else
{
szError.Format("<CE-%u>", dwError);
MessageBox( NULL, szError, "TTY Error!",
MB_ICONEXCLAMATION | MB_OK ) ;
ClearCommError(pComm->hDev, &dwErrorFlags, &ComSta
t);
if (dwErrorFlags > 0) {
szError.Format("<CE-%u>", dwError);
MessageBox( NULL, szError, "TTY Error!",
MB_ICONEXCLAMATION | MB_OK ) ;
}
break;
}
}
}
else
{
dwLength = 0;
ClearCommError(pComm->
hDev, &dwErrorFlags, &ComStat);
if (dwErrorFlags > 0) {
szError.Format("<CE-%u>", dwError);
MessageBox( NULL, szError, "TTY Error!", MB_ICONEXCLAMA
TION | MB_OK ) ;
}
}
}
}
return dwLength;
}
BOOL WritecommBlock(struct tagCommInfo * pComm, LPSTR lpByte, DWORD dwBytes
ToWrite)
{
BOOL fWriteStat;
DWORD dwBytesWritten;
DWORD dwErrorFlags;
COMSTAT ComStat;
CString szError;
if (pComm->bConnected == FALSE) return FALSE;
fWriteStat = WriteFile(pComm->hDev, lpByte, dwBytesToWrite, &dwBytesWri
tten, &pComm->osWrite);
if (!fWriteStat) {
if (GetLastError() == ERROR_IO_PENDING) {
pComm->bConnected = FALSE;
if (pComm->hDev == WinGenInfo.Printer.hDev) {
AfxMessageBox("프린터에 명령을 전송할 수 없습니다. 작업 중
지 후 케이블 연결을 다시 확인하십시오");
}
else
{
AfxMessageBox("스케너에 명령을 전송할 수 없습니다. 작업 중
지 후 케이블 연결을 다시 확인하십시오");
}
return FALSE;
}
else
{
ClearCommError(pComm->hDev, &dwErrorFl
ags, &ComStat);
return FALSE;
}
}
return TRUE;
}
BOOL OpenComm(struct tagCommInfo * pComm) // 불특정 포트 열기
{
CString str;
pComm->osRead.Offset = 0;
pComm->osRead.OffsetHigh = 0;
pComm->osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (pComm->osRead.hEvent == NULL) {
TRACE0("osRead.hEvent = CreateEvent()이 실패하여 NULL을 돌려줌\n");
return FALSE;
}
pComm->osWrite.Offset = 0;
pComm->osWrite.OffsetHigh = 0;
pComm->osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (pComm->osWrite.hEvent == NULL) {
TRACE0("osWrite.hEvent = CreateEvent()이 실패하여 NULL을 돌려줌\n")
;
CloseHandle(pComm->osRead.hEvent);
return FALSE;
}
str.Format("COM%d", pComm->PortNumber);
pComm->hDev = CreateFile(str, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (pComm->hDev == INVALID_HANDLE_VALUE) {
CString strError;
strError.Format("시스템으로 부터 %s 장치 사용 허가를 얻어낼수 없습
니다", str);
AfxMessageBox(strError);
return FALSE;
}
// DCB설정
pComm->dcb.DCBlength = sizeof(DCB);
pComm->dcb.BaudRate = pComm->BaudRate;
pComm->dcb.Parity = pComm->ParityBit;
pComm->dcb.ByteSize = pComm->DataBit;
SetCommMask(pComm->hDev, EV_RXCHAR);
SetupComm(pComm->hDev, 4096, 4096); // 입력, 출력 버퍼 크기 설
정
PurgeComm(pComm->hDev, PURGE_TXABORT | PURGE_RXABORT | // 입력, 출력
버퍼 클리어
PURGE_TXCLEAR | PURGE_RXCLEAR);
pComm->CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF;
pComm->CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
pComm->CommTimeOuts.ReadTotalTimeoutConstant = 1000;
pComm->CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
pComm->CommTimeOuts.WriteTotalTimeoutConstant = 1000;
SetCommTimeouts(pComm->hDev, &pComm->CommTimeOuts);
pComm->dcb.fOutxCtsFlow = TRUE; // CTS흐름 제어함
pComm->dcb.fOutxDsrFlow = TRUE; // DSR흐름 제어함
pComm->dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
pComm->dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
pComm->dcb.fBinary = TRUE; // 항상 TRUE여야 함
pComm->dcb.fParity = TRUE; // 패리티 검사함
pComm->dcb.fInX = pComm->dcb.fOutX = FALSE;
pComm->dcb.XonChar = ASCII_XON ;
pComm->dcb.XoffChar = ASCII_XOFF ;
pComm->dcb.XonLim = 100 ;
pComm->dcb.XoffLim = 100 ;
BOOL ret = SetCommState(pComm->hDev, &pComm->dcb); // 상기와 같이 포트
상태를 설정함
if (ret == FALSE) {
TRACE1("SetCommState() 포트 %d실패", pComm->PortNumber);
}
else
{
pComm->bConnected = TRUE;
}
return TRUE;
}
BOOL CloseComm(struct tagCommInfo * pComm) // 불특정 포트 닫기
{
if (NULL == pComm) return ( FALSE ) ;
// set connected flag to FALSE
pComm->bConnected = FALSE;
// disable event notification and wait for thread
// to halt
SetCommMask(pComm->hDev, 0 ) ;
// block until thread has been halted
while(pComm->hCommWatchThread != 0);
// drop DTR
EscapeCommFunction(pComm->hDev, CLRDTR) ;
// purge any outstanding reads/writes and close device handle
PurgeComm(pComm->hDev, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | P
URGE_RXCLEAR ) ;
CloseHandle(pComm->hDev) ;
return ( TRUE ) ;
}
이번 강좌의 주제에 해당하는 부분은 이것 뿐입니다.
SetupComm(pComm->hDev, 4096, 4096); // 입력, 출력 버퍼 크기 설
정
PurgeComm(pComm->hDev, PURGE_TXABORT | PURGE_RXABORT | // 입력, 출력
버퍼 클리어
PURGE_TXCLEAR | PURGE_RXCLEAR);
헤더 파일도 보여주지 않으면 소용이 없겠습니다. 여기 헤더 파일이 있습니다.
// WinGen.h : main header file for the WINGEN application
//
#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif
#include "resource.h" // main symbols
///////////////////////////////////////////////////////////////////////////
//
// CWinGenApp:
// See WinGen.cpp for the implementation of this class
//
class CWinGenApp : public CWinApp
{
public:
CWinGenApp();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CWinGenApp)
public:
virtual BOOL InitInstance();
//}}AFX_VIRTUAL
// Implementation
//{{AFX_MSG(CWinGenApp)
// NOTE - the ClassWizard will add and remove member functions her
e.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
///////////////////////////////////////////////////////////////////////////
//
// 시리얼 포트 관리 정보 구조체
struct tagCommInfo {
int DataBit; // 데이타 비트
DWORD BaudRate; // Baud
int StopBit; // Stop bit 수
int ParityBit; // 페리티 비트
int PortNumber; // 포트번호
HANDLE hDev; // 장치 핸들
DCB dcb; // 데이타 콘트롤 블럭
COMMTIMEOUTS CommTimeOuts;
DWORD dwThreadID;
HANDLE hCommWatchThread;
BOOL bConnected; // 연결되었는가?
OVERLAPPED osRead, osWrite;
};
// 바코드 라벨 인쇄 내용 저장 구조체
struct tagLabelInfo {
char txtJepum[30]; // 제품번호
char txtSuju[30]; // 수주번호
char txtSangSan[30]; // 생산일자
char txtNapum[30]; // 납품일자
char txtBupum[30]; // 부품번호
int nGoods; // 박스당 제품 수
long lTotalGoods; // 전체 제품 수
long lTotalBoxs; // 전체 상자 수
int nJa; // 마지막 박스의 제품 수(짜투리)
long lCountGoods; // 현재 생산된 제품 수
long lSerialNumber; // 박스의 일련 번호
long lCountBoxs; // 현재 생산되 박스의 수
int nCountCOA; // 현재 라벨에 들어가는 코아 번호 카운트
int nCountChar; // 현재 코아 문자 수신 카운트
char txtCOA[60][10]; // 코아번호 최대 60개
};
// 전체 구성 정보 관리 구조체
struct tagWinGenInfo {
CWnd *wndWork;
struct tagCommInfo Printer; // 라벨 프린터 통신 케이블 설정
struct tagLabelInfo Label; // 라벨 인쇄 내용
struct tagCommInfo Scanner; // 바코드 스케너 통신 케이블 설정
CDaoDatabase *CoaBase;
CDaoTableDef *CoaTable;
CDaoRecordset *CoaRecord;
};
extern struct tagWinGenInfo WinGenInfo;
이정도면 소스는 다 보신 셈이고...
이번 강좌는 몇자 치지 않아도 많은 페이지가 되어 버렸네요. 별도의 예재를 만
들어 강좌를 하는 것이 옳은 일인줄은 알지만 앞서 설명했듯이 팔이 말이 아니
라....
SetupComm()은 입력과 출력에 대한 버퍼의 크기를 잡아 줍니다. 앞서 나열된 소
스에는 4096바이트가 지정되어 있습니다. 도스에서는 인터룹트 핸들러가 했던 큐
영역 버퍼 작업이지만 윈도우 환경에서는 그작업은 윈도우가 하게 되고, 그 동
작 규칙은 강좌 2번에서 설명한 DCB에 설정된 값에 따라 동작합니다. 따라서 윈
도우에서는 도스에서는 만들어야만 했던 ISR루틴을 만들지 필요가 없고, 만들 수
도 없습니다.
다음은 PurgeComm(pComm->hDev, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR
| PURGE_RXCLEAR ); 차례인데 이것은 입출력 버퍼에 들어 있는 내용을 모두 지워
서 초기화 하는 것이라고 도큐멘트에 나와 있습니다. 왜 이 작업을 해야만 하는
지, 어느 위치에 있는 메모리를 클리어 하는지에 대하여는 밝혀져 있지 않습니
다. 그냥 '버퍼를 깨끗히 청소하는구나' 라고만 생각해야 하죠. 궁금하기 짝이
없지만 어쩌겠어요. 방법이 없는걸....
이번 강좌는 이것으로 마칩니다. 다음 강좌는 이번 강좌에서 공개한 함수들을 어
떻게 호출하는지에 대하여 설명하고자 합니다. 그 다음 강좌는 쓰레드를 생성하
고, 쓰레드와 주 프로그램간의 자료 송수신에 대하여 강좌를 하면 시리얼 포트
제어에 대한 자료는 거의 마무리되는 것이라 생각합니다.
이 소스에 ZMODEM프로토콜 소스를 덧붙이면 좋은 에뮬레이터를 만들수도 있겠다
는 생각이 듭니다. 그럼 이만.
#10772 박경환 (jiocafe )
[강좌] VisualC 4.0 & MFC 통신 #4 08/07 01:37 17 line
이번 강좌가 4번째 이군요. 시간은 잘도 갑니다. 강좌를 빨리 진행해 달라는 편
지도 있었고 해서 열심히 하고자 하는데 시간적 여유가 나지 않는군요.
편지 중에 CommIO.h파일이 없다는 내용이 있었는데, CommIO.CPP파일(가장 긴 소
스)의 함수 헤더들을 모아 하나의 파일로 만들면 그것이 끝입니다. CommIO.H는
몇줄이 안되는 간단한 파일입니다.
지난 3번 강좌에서 소스에서는 RTS, CTS, DSR등이 연결되어야만 송수신이 되는
소스입니다. 제가 바코드 프린터와 바코드 스케너를 제어하려고 만들고 있기 때
문에 프로그램을 간단히 하려 이 부분을 고정 시켰죠.
송수신 케이블의 내부에 전선이 3가닥 밖에 되지 않으면 송수신이 안됩니다. 정
식으로 완전한 케이블을 준비하셔야 송수신이 제대로 에러없이 송수신 됩니다.
만일 정식이 아닌 케이블로 사용해야 겠다고 결심을 하셨다면 소스에서 HANDSHAK
E를 찾아 ENABLE시키고, ENABLE되어 있는것은 DISABLE시키십시오. 그러면 됩니
다.
이번 강좌는 이것으로 마칩니다. 깊고 고요한 밤에 .... 경기도 화성군에서...
#10773 박경환 (jiocafe )
[강좌] Visual C 4.0 & MFC통신 #5 08/22 15:45 150 line
지난 강좌에서 시리얼 포트를 Visual C++ 4.0 MFC & WIN32 API & 멀티 쓰레드로 제어
하는 강좌를 하였다. 이번 강좌는 시리얼 포트가 많아야 하는 경우에 대하여 강의 한
다. 시리얼 포트를 많이 늘리는 장비인 멀티 포트에 대하여 설명한다.
기브스는 풀렀고... 이젠 물리치료를 다니고 있는데 일하느라 잠이 부족해 강좌를 위
한 정리가 잘 되지 않으니 잘 걸러서 읽기를 바랍니다.
1. 시리얼 포트 수 증가 방법
그 방법은 간단하다. 시리얼 포트를 설치하는 것이다. 포트의 수를 얼마만큼 필요
로 하는지가 관건이 된다.
일반적으로 PC에는 2개의 시리얼 포트가 있다. 그 이름은 COM1, COM2이다. 내장형
모뎀을 설치하게 되면 COM3나 COM4가 생긴다. 그래서 통신을 할때 COM3나 COM4를 선
택하여 통신한다. 어떤이는 COM2를 Disable시켜서 동작하지 못하게 하고 내장형 모뎀
의 점퍼나 딥 스위치를 COM2를 사용하기도 한다. COM2에 내장형 모뎀을 사용하는 방
법은 소프트웨어적으로 가장 안정적인 방법이다. 물론 COM1을 Disable시키고 COM1으
로 모뎀을 맞추는 방법도 있다. 이와 같은 원리이다. 내장형 모뎀을 설치하니 포트의
수가 8개 늘어났다고 생각하면 된다. 이러한 카드를 멀티 포트라 한다.
1.1 멀티 포트 설치법
이것을 이해하기 쉽게 설명하자면 내장형 모뎀을 생각하면 쉽다. 내장형 모뎀은 자체
적으로 포트를 가지고 있어서 모뎀 기판을 본체 마더보드 슬롯에 꽂기만 하면 포트의
수가 1개 증가한다. 이 포트의 갯수가 여러개인 모뎀이라고 생각하면 된다. 이러한
기판을 멀티 포트라고 한다.
멀티 포트는 1개의 카드(내장형 모뎀과 비슷한 기판)
1.2 멀티 포트가 갖는 능력
MULTI PORT는 COM1부터 COM4까지 있는 PC를 COM1부터 COM12까지 사용할 수 있게 하는
것이다. 즉, 시리얼 포트를 여러개 확장할 수 있다. 이 시리얼 포트들에 모뎀을 연
결하여 BBS를 구축할 수도 있고, 시리얼로 제어가 가능한 여러개의 기기를 제어할 수
도 있다. 멀티 포트의 종류에 따라서 포트의 수가 더욱 늘어날 수도 있다.
멀티 포트 제작 회사는 세계적으로 볼때 많다. 각 회사마다 각기 성능과 가격이 차이
가 있다. 이 모든 기기를 다루어본 사람은 아무도 없을 것이다. 이번 강좌에서 설명
하고자 하는 멀티 포트는 윈도우 95운영체제에서 동작하는 보드에 대하여만 국한하여
설명하고자 한다. 왜냐면 필자는 여러 회사의 제품을 사용해 보지는 못하고 국내의
한 업체에서 생산하는 제품만을 사용하여 보았기 때문이고, 또한 이 강좌의 목적이
비주얼 씨 플러스 플러스에서 시리얼 포트를 제어하는데 대한 설명이 목적이기 때문
이다. 생산하는 회사의 이름을 밝혀도 통신망의 어떤 법에 저촉되는지는 모르지만 강
좌의 설명을 위해서 밝혀야만 할 수 밖에 없으므로 밝힌다. 그 회사의 이름은 '시스
템 베이스'이다. 이 회사에서 제작하여 판매하고 있는 제품중 MULTI-8을 사용하여 설
명하고자 한다. 필자가 가지고 있는 제품이 이것 뿐이기 때문이다. 이하 멀티 포트라
고 하는 것은 모두 시스템 베이스에서 만들어 판매한 MULTI-8이라는 제품이다.
멀티 포트는 RS232각 포트를 제어하기 위해 16450칩을 사용한다. 9600BPS까지만 안정
적으로 지원했던 8250칩을 개선한 칩이 16450이라고 어떤 강사들이 이미 다른 여러
강좌에서 설명한바 있으니 그것을 참조하기 바라고 여기에서는 그냥 넘어간다.
따라서 115200BPS까지 안정적으로 지원한다. 그러나 이 속도를 다 사용해야만 하는
일은 별로 없을 것이다. 일단 필자는 19200BPS만을 사용하여 공장의 생산라인을 자동
화해야 하므로 이 작업을 진행하면서 계속적으로 강좌를 하고자 한다.
1.3 멀티 포트의 종류
포트의 수에 따라 멀티 포트는 포트의 수가 각각 4, 8, 16, 32개 짜리가 있다. 이들
멀티 포트는 연결하여 사용할 수 있으므로 더욱 많이 늘어날 수 있다.
포트의 제어 방식에 따라 포트가 인텔리전트형인가 아니면 더미 형인가로 구분지어진
다. 인텔리한 포트는 HOST의 CPU의 부하를 적게 주어 좀더 효율적인 통신망을 운영할
수 있다고 한다. 사실 필자는 이런 포트는 사용하여 보지 못했다.
포트 규격에 따라 232,485, 422방식으로 나뉜다. 흔히 사용하는 것이 232고 나머지
는 공장이나 전기 시설이 많은 곳에서 노이즈에 민감하지 않도록 설계되었다고 한다
. 물론 이 강좌에서 설명할 범위가 아니므로 생략한다.
1.4 멀티 포트에 대한 정리
앞서 설명한 바와 같은 멀티 포트를 PC에 설치하면 윈도우 95환경에서 여러 시리얼
포트를 확보할 수 있고 그곳에 외장형 모뎀을 설치한다면...... 두뇌 회전이 빠르신
분은 BBS를 운영할 수도 있겠다는 것을 미리 짐작했을 것이다. 물론 강좌의 범위에
벗어나므로 그 설명은 생략한다.
2. 윈도우 95에 설치
윈도우 95를 OS로 사용하는 컴퓨터에서 멀티 포트를 사용하고자 한다면 하드웨어 제
작 회사가 제공하는 소프트웨어를 설치하여야 한다. 흔히 디바이스 드라이버라고 부
르는 것이다. 시스템 베이스의 메뉴얼을 보면 각종 OS에 대한 모든 설치 방법이 나와
있다.
2.1 전원을 끈다.
다른 모든 경우와 마찬가지로 슬롯에 새로운 기판을 꽂으려면 전원을 꺼야 한다. 끄
기 싫으면 끄지 않고 해보고 그 결과를 저에게 알려주기 바란다. 어떤 결과가 나오는
지 알고 싶으니까.... 아마 마더 보드가 맛이 가거나 .... 그럴 것이다.
2.2 비어있는 슬롯을 찾는다.
멀티포트 카드는 ISA슬롯에 맞도록 제작되어 있다. 16BIT슬롯 하나면 된다. 비어 있
는 슬롯이 없으면 우선순위에 따라 가장 덜 사용하는 카드를 뽑아 버린다. 물론 멀티
포트 카드를 설치하면 IRQ, BASE ADDRESS때문에 다른 장비와 충돌하지 않으려면 가
능한 다 뽑아 버리는 것이 좋다.
2.3 카드의 딥 스위치를 조절한다.
메뉴얼에 나와 있는데로 잘 조절한다. 정답이 없다. 보드마다 IRQ등을 사용하는 것이
각기 다르기 때문이다. 딥 스위치는 24개가 있다. 동작 방식, IRQ, BASEADDRESS,
POLL ADDRESS를 선택하기 위해서 이다.
같은 어드레스를 쓰거나 하여 충돌하게 되면 동작이 원활히 되지 않는다. 윈도우 95
의 시작-설정-제어판-시스템-장치 관리자에서 충돌하는지 여부를 확인해 본다. 충돌
하여 동작하지 않는 장치들은 노란 바탕의 느낌표 표시가 아이콘에 나온다. 딥 스위
치를 조절하기 위해 수십번 또는 수백번을 시도해 보게 될것이다. 운이 좋으면 단 1
번에.. 뭐가 그리 어렵냐는 질문을 하신분은 컴퓨터를 배우기 시작한지 얼마 되지 않
은 분일 것이다. PnP기능이 나온지는 1년도 되지 않았다. 몇 개월 전까지만 하더라도
꽂고서 IRQ, DMA등을 맞추어 주느라 조립할때 고생 꽤나 했었다.
2.4 슬롯에 꽂는다.
그냥 꽂는다. 슬롯의 방향과 위치만 정확하다면 그리 힘주지 않아도 잘 들어간다.
2.5 켠다.
켜보면 동작하지 않는 장치 때문에 윈도우 95에서 어떤 메시지가 나올 수도 있다. 충
돌하는 경우에만 그렇다. 2.3번에서 설명한 바와 같이 장치 관리자를 살펴본다. 느낌
표를 발견하였다면 다시 컴퓨터 끄고, 슬롯 빼고 딥스위치를 다른 값으로 바꾸어 본
다.
이번 강좌는 이것으로 마치고 다음 강좌에 이어서......
#10774 박경환 (jiocafe )
[강좌] Visual C 4.0& 시리얼 통신 #6 11/04 00:09 79 line
그동안 너무나 바빠서 통신에 접속할 시간이 없어서 강좌를 하지 못하였음을
먼저 알립니다.
몇 통의 편지를 받았는데 질문의 요점은 "이해는 가는데 짜질 못하겠다"였습니다.
왜 짤수가 없을까? 이 생각을 많이 해보았는데 저는 이해를 할 수 없었습니다.
더 이상 쉽게 설명을 할 수 없을 만큼 쉽게 설명을 했다고 믿고 있으며,
시리얼 제어가 트루타입 폰트 제어 처럼 관련 자료를 모두 모아야 조금 이해가
가는 것도 아니기 때문입니다.
물론 편지를 주시지 않은 대다수의 사람들은 프로그램을 문제없이 짤 수 있었기
때문에 저에게 그런 편지를 보낼 필요가 없었으리라고 생각합니다.
어찌 되었든지간에 소수의 프로그램을 짜고자 하는 이가 프로그램을 짜지
못하고 있다는 것은 사실이고 이것을 묵과해서는 안된다고 생각이 듭니다.
좀더 자세한 예재를 들거나 아예 프로그램 소스를 공개하는 방법도 있으리라
생각됩니다.
그런데, 두번째 방법인 소스를 모두 공개하는 것은 저로서는 등록하는데 전혀
노력이 필요치 않은 쉬운 방법이기는 하나 회사의 자산으로 볼 수 밖에 없는
소스이므로 그렇게는 해서는 안된다고 생각합니다.
자. 그럼 어떻게 해야 할까요?
설명을 위해 제가 예재를 만들어서 공개하는 방법이 있겠습니다.
회사에도 상관없고, 프로그램을 짜려는 소수의 프로그래머에게도 희소식일 테니
그러나... 저는 더 바빠져야 한다는 것입니다.
그래서 결론은 이렇습니다.
이번 강좌에서는 시리얼 제어에 관련한 철학에 대하여 설명하고,
다음 강좌에서는 완벽한 예제 소스 파일을 공개하는 것입니다.
시리얼 제어에 관련한 철학을 설명하겠습니다.
이 강좌가 불필요하다고 생각하시면 오산입니다. 왜냐면 프로그래머가 시리얼을
제어해야 하는 경우는 기기를 제어하거나 하는 먹고 사는일에 관련된 일이기
때문입니다.
주어진 시간 안에 업무를 처리하기 위해서는 시리얼 제어에 관한 철학이
좋을때 쉽게 해결할 수 있습니다.
첫번째 철학은?
시리얼 포트로 들어오는 정보는 언제든 엉망이 될 수 있다.
한 바이트가 없어질 수도 있으며, 한바이트가 추가될 수도 있으며,
11개의 문자중에서 1개의 문자만 불특정한 문자로 변경될 수도 있습니다.
그래서 항상 시리얼 제어에는 불 만족스러운 값이 들어올 경우에
그 들어온 값을 무시해 버리거나 재 전송 요구를 하거나
복구를 하는 루틴이 반드시 있어야 합니다.
물론 재저송 요구를 하고 마냥 기다리다가는 큰 일입니다.
재 전송 요구 신호를 상대가 받지 못하는 경우도 있기 때문입니다.
두번째 철학은?
시리얼 포트로 들어오는 정보는 언제 들어올지 모른다.
언제 갑자기 많은 데이타가 들어올지 모르기 때문에 타스크가 느릴 경우
그 데이타를 모두 처리할 수 없어서 수신받은 문자가 큐 버퍼에서
넘쳐 버리는 경우가 생겨 자료를 잃어버리게 될 수 도 있다는 것입니다.
타이머 메시지를 받아 정기적으로 큐에 문자가 수신되었는지 확인하는
방법이 프로그래밍 하기에는 쉬울수 있으나
기기를 제어하는데 있어서 기기가 항상 신호를 보내는 것이 아니므로
처
리가 시간이 낭비될 수 있습니다.
데이타를 달라고 요청해야 보내주는 원격 장치에 대하여
타이머로 끊임없이 달라고 하면 없다는 메시지만 계속 보내올 것입니다.
데이타를 달라고 요구하는 지연 시간 값을 자동으로 늘이거나 줄이는
기교가 필요합니다.
세번째 철학은?
선로 상태 자체가 엉망일 수 있다.
시리얼 케이블을 아무도 건드리지 않는다는 보장이 없습니다.
천정을 통해 배선을 했다고 하더라도 쥐가 쏠아서 끊어 버리는 경우나
비가 스며들어 합선되는 경우가 없을 것이라고 단정 지을 수는 없습니다.
선로 주변에 전압이 높은 전력선이 지나지 않나요? 노이즈의 주 원인이
무엇이라 생각하십니까?
기기를 제어한다면 피복이 제대로 된 케이블을 다른 전선과 멀게 하고
케이블의 길이를 가장 짧은 길이가 되도록 설계하십시오.
네번째 철학은?
내가 짠 소스는 효율적이지 않을 수 있다.
프로그램을 완성하신 후에는 시리얼 데이타가 들어오는 빈도나
순간 적으로 얼마나 많이 데이타가 쌓이게 되는지 확인해 보시기
바랍니다.
가장 많이 들어오는 때 가장 많은 처리를 해야 하므로
효율적이어야 합니다. 윈도우 95나 NT와 같은 시스템은
여러 프로그램이 함께 동작하는 멀티 타스킹 환경이므로
내 프로그램이 일정 시간(사람이 느끼지 못할 정도)은 동작을
멈출 수도 있다는 것을 잊지 마십시오.
시리얼 제어가 가장 쉽고 안정적인 시스템은 DOS라고 생각합니다.
다섯번째로 철학은?
지금 알고 있는 문제 이외로 다른 문제가 시리얼 통신에는 존재한다.
이렇게 나열해 놓고 보니까 시리얼 제어가 어려운 것 같지만
쉽다면 무지 쉬운게 시리얼 제어입니다.
이번 강좌는 이것으로 마칩니다. 다음 강좌에서는 예재 소스를
보여 드리겠습니다.
출처는... 잊어먹었음... ㅡㅡ;
아마도 박씨가 만든거 같음....
참고로 메모리 릭은 있는거 같음... 아님말고~
=================================================================
시리얼 통신에 관한 글 퍼온거
출처 : http://elishot.tistory.com/entry/dip-win32-%EC%8B%9C%EB%A6%AC%EC%96%BC-%ED%86%B5%EC%8B%A0-%EA%B0%95%EC%A2%8C
Win32 시리얼 통신 강좌
#10768 박경환 (jiocafe )
[강좌] WIN32 시리얼 통신 #1 07/24 12:47 60 line
윈도우에서 통신 프로그램을 개발하고자 하는 모든 이에게 제가 가진 모든
지식을 드립니다. 저는 도스에서 그래픽 통신 에뮬레이터를 개발한바 있고,
터치 스크린 등의 시리얼 제어에 많은 시간을 투자한 바 있는 사람입니다.
이제 부터 설명하고자 하는 내용은 Visual C++ 4.0버전에서 MFC를 사용하고
멀티 쓰레드로 RS232포트를 감시하는 프로그래밍 과정입니다.
직렬 포트 제어에 관한 기본 지식은 설명하지 않으며, 도스와 윈도우 3.1에
서 프로그래밍을 하셨던 분들이 WIN32프로그래밍을 하려 할때 차이점에 대
하여 논하는 방식으로 진행하도록 하겠습니다.
DOS 세계와 WIN32세계 차이
==========================
도스에서는 RS232포트를 제어하기 위해 시리얼 포트 인터룹트 핸들러 루틴
을 제작하여야 했고, 인터룹트 벡터를 바꾸어야 했고, 버퍼를 잡아 주는 등
의 작업이 우선되어야 하지만. 이 일은 윈도우 운영체계가 합니다.
프로그래머가 직접 제어하는 일은 윈도우에서 권장되고 있지 않을 뿐더러
다른 많은 문제점을 유발 시킵니다. 동작하는 프로그램이 도스 처럼 1개가
아니기 때문에 시리얼을 사용하려는 프로그램이 몇개가 될지 아무도 알수
없기 때문입니다.
윈도우에서는 버퍼를 얼마만큼 쓰고싶고, 통신 파라메터는 어떻게 쓰겠다고
주어진 규칙에 따라 청구서를 만들어 윈도우에 제출하면, 윈도우가 그 장치
가 놀고 있으면 사용허가를 즉시 내 주고, 다른 프로그램이 사용중이면 사
용을 불허합니다. 청구서는 DCB입니다. DCB를 키보드로 치고 도움말 키를
누르면 VC ++ 4.0은 즉시 보여 줍니다.
WIN16과 WIN32차이
=================
윈도우 16비트 버전에서(3.1과 3.11 포함)에서 지원되던 SetCommEventMask
(), GetCommError()등의 함수가 있었는데, VC ++ 4.0에서는 이 함수들이 사
라졌다. 다른 함수로 통합되거나 기능이 아예 없어졌다. 이런 함수들은 한
둘이 아니며 그 목록을 알고자 하면 VC++ 4.0의 도움말에 Summary of Funct
ion and Message Differences를 찾아보면 알 수 있다.
제어 가능한 범위
================
시리얼 포트의 속도는 최대 256000BPS까지 가능하다. 이는 CBR_110 부터 CB
R_256000으로 define되어 있는 헤더 파일을 보면 알 수 있다.
CTS, RTS, DSR, DTR등의 제어가 가능하다.
이벤트는 WaitCommEvent()함수를 참조하면 된다.
1. 입력에서 BREAK신호가 오는 경우 EV_BREAK
2. CTS(clear to send) 신호가 오는 경우 EV_CTS
3. DSR(data set ready)신호가 오는 경우 EV_DSR
4. 라인이 에러가 발생한 경우 EV_ERR
5. 전화가 걸려온 경우 EV_RING
6. RLSD(receive line signal detect) 신호가 오는 경우 EV_RLSD
7. 입력 버퍼에 하나의 문자가 수신된 경우 EV_RXCHAR
8. 이벤트 문자가 수신되고 입력 버퍼에 놓이면 EV_RXFLAG
이벤트 문자는 DCB구조체 안에 지정되고, SetCommState()함수에 의
해 직렬포트를 조작한다.
9. 출력 버퍼에서 마지막 문자가 보내지면 EV_TXEMPTY
이벤트 문자는 다소 생소한 내용일 것이다. 이것은 이벤트가 발생했을때 입
력 버퍼로 어떤 문자가 수신된 것으로 처리하는 것을 말한다. DCB에서 '?'
문자를 지정했다면 이벤트가 발생하면 '?'문자가 수신된 문자와 함께 들어
온다.
#10770 박경환 (jiocafe )
[강좌]WIN32 시리얼 통신 #2 07/28 19:10 238 line
이번이 두번째 강좌입니다. 몇번째까지 가게 될지 아직 모릅니다. 충분히
설명이 되었다고 생각되면 강좌를 마칠 생각입니다. 이 강좌에 대한 질문을
하시는 분의 메일이 오면 참고하여 그것에 대하여도 설명할까 합니다.
이번 강좌는 포트 초기화가 주제입니다. 포트를 초기화 하기 위해서는 윈도
우 운영체제로 부터 포트 사용허가를 얻어야 합니다. 다른 프로그램이 그
포트를 사용하고 있지만 않다면 허가는 바로 떨어집니다. 또한 자신의 프로
그램이 포트 사용을 완료하고도 닫아주지 않으면 자신도 허가를 다시 얻을
수 없습니다. 사용허가가 나면 핸들을 돌려주는데 핸들을 받드시 보관하고
있다가 사용이 끝나면 그 장치 핸들로 장치를 닫아 주어야만 합니다.
단계별 요약
1. 장치에 대한 핸들을 구한다. CreateFile() 함수를 사용합니다.
2. 핸들을 보관한다. 글로벌 변수에 저장하는 것이 편합니다.
3. 포트를 어떻게 초기화 할것인지 초기화 값을 설정합니다. DCB구조체룰
채워서 합니다.
4. 초기화 값을 핸들에 적용합니다.
1단계 : 장치 핸들 구하기
어떤 함수든 Visual C++ 4.0의 편집기에서 이름만 치고 그 위에 커서를 같
다 두고 F1키를 누르면 그 함수에 대한 정보를 볼 수 있습니다. 도움말이
나타나면 분류별 선택을 하게 됩니다. 대부분의 경우 각 함수들은 WIN32
SDK용과 MFC에서 사용되는 것으로 구분되어 도움말이 나옵니다.
우리가 하고자 하는 것은 MFC로 하는 것이므로 MFC를 선택합니다. 함수의
프로토타입과 그 함수가 속한 클래스, 함수 인자 목록과 각 함수 인자 목록
이 어떤 의미를 갖는지에 대하여 설명이 되어 있습니다. 설명은 거의 완벽
하다고 생각됩니다. 다만, 한글이 아니고 순수 영어라는 점과 그 함수에 대
한 예재가 함께 들어있지 않다는 것입니다.
만일 그 함수에 대한 예제 소스를 보고자 한다면 SAMPLE디렉토리의 파일들
중에 그 함수가 들어있는 것을 모두 검색하는 기능을 사용하면 쉽게 어떤
SAMPLE에 포함되어 있는지를 알 수 있습니다.
이제 CreateFile 함수에 대하여 알아볼 차례입니다. 이 함수는 파일을 생
성하는데도 사용될 수 있으며 시리얼 포트를 여는데도 사용됩니다. 시리얼
포트는 1번이면 " COM1", 2번이면 "COM2" 식으로 이름이 이미 정의되어
있으므로 그 이름을 사용하면 쉽게 핸들을 구할 수 있게 됩니다. 따라서
멀티 포트카드를 사용하거나 할때 멀티포트 카드를 본체에 장착하고 그 디
바이스 드라이버를 시스템에 등록하면 새로운 장치 이름이 시스템에 등록되
어 지금 설명하고자 하는 방식으로 핸들을 구해내어 사용할 수 있게 됩니다.
CreateFile 함수에 대한 설명을 앞서 설명한 방법으로 찾아보면 A4용지로
인쇄했을때 7페이지 분량의 설명이 나옵니다. 첫줄에 씌어 있기를 "한 파
일, 파이프, 커뮤니케이션 자원, 디스크 장치, 콘솔 등을 생성하기, 열기,
자르기 한다. 리턴값은 객체에 접근하는데 사용되어질 수 있는 핸들이다."
라고 되어 있습니다.
윈도우 3.1에서는 Com포트에 대한 함수인 OpenComm(), CloseComm()함수가
별도로 정의되어 있으나 Visual C++ 4.0에서는 존재하지 않습니다. 무섭게
시리 Dropped라고 도큐멘트에 나와 있지요.
이제 제가 만들어서 사용하는 소스는 이렇습니다. 예재의 내용과 비슷합니
다.
str.Format("COM%d", pComm->PortNumber);
pComm->hDev = CreateFile(str, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL |
FILE_FLAG_OVERLAPPED, NULL);
if (pComm->hDev == INVALID_HANDLE_VALUE)
{
CString strError;
strError.Format("시스템으로 부터 %s 장치 사용 허가를 얻어낼수 없습니
다", str);
AfxMessageBox(strError);
return FALSE;
}
str은 CString 형 변수로 MFC에서 제공하는 문자열 클래스죠. 이 클래스
에 대한 설명은 생략하겠습니다. 필요하신 분은 도움말을 보시기 바랍니다.
pComm이 지시하는 것은 포트에 대한 정보를 담고 있는 것이고, PortNumber
항목은 사용하고자 하는 시리얼 포트의 번호입니다.
이렇게 첫줄이 실행되면 1일 경우 "COM1"이 되고 2는 "COM2"가 되죠. 전통
적인 C로 작성한다면 다음과 같습니다.
char str[5];
sprintf(str, "COM%d", pComm->PortNumber);
이제 두번째 줄 차례...
str이 "COM1"이 되었다고 가정하면, CreateFile()함수의 첫번째 인자인
pointer to name of the file에 "COM1"이 대응됩니다.
두번째 인자는 읽고 쓰기 모드를 선택합니다. 이부분은 다 아실테니
GENERIC_READ | GENERIC_WRITE 가 읽기와 쓰기를 다 할 수 있도록 한다는
것만으로 넘어갑니다.
세번째 인자는 파일 공유 모드로 도스에서는 프로그램이 혼자 돌아가니 다
른 프로그램이 자신의 파일을 접근하는 일이 없어 무의미 하지만 윈도우 다
중 작업 환경에서는 다른 프로그램도 동시에 동작하니 의미가 있습니다.
FILE_SHARE_READ를 지정하면 자신의 프로그램이 사용하는 파일을 다른 프로
그램도 읽을 수가 있습니다. 그러나 FILE_SHAR_READ를 지정하지 않으면 다
른 프로그램이 읽고자 할때 윈도우가 접근을 거부하므로 그 프로그램은 읽
기 작업을 실패하게 됩니다.
네번째 인자는 보안에 대한 설정입니다. 시스템이 지원해야만 효과가 있다
고 도큐먼트에 나와 있습니다. 필요없으므로 생략합니다.
다섯번째 인자는 파일이 이미 존재할때와 존재하지 않을때 어떤 액션을 취
할 것인지 지정합니다.
CREATE_NEW 파일을 새로 만들되 이미 존재하면 실패합니다.
CREATE_ALWAYS 새로이 만들되 파일이 이미 존재하면 덮어 쓰기가 됩니다.
OPEN_EXISTING 파일이 이미 존재해야만 성공합니다.
OPEN_ALWAY
S 파일이 존재하면 열고, 존재하지 않으면 CREATE_NEW처럼 새로
만듭니다.
TRUNCATE_EXISTING 파일이 이미 존재하면 파일의 크기가 0바이트 크기로 짤
려 데이타가 없어집니다.
너무 자세히 설명하다보니 라이브러리 레퍼런스를 만드는 것 같습니다. 나
머지 설명은 생략합니다.
함수의 리턴값은 핸들이 됩니다. 만일 윈도우에서 어떤 프로그램인가가 그
포트를 사용중이거나 그 포트가 없다면 리턴값이 INVALID_HANDLE_VALUE이
됩니다. INVALID_HANDLE_VALUE은 헤더에 #define되어 있습니다.
HANDLE은 void *로 #define되어 있습니다. 물론 이것도 헤더에 있지요.
if (pComm->hDev == INVALID_HANDLE_VALUE)
이 조건문에서 그 객체(장치나 파일이)가 사용할 수 있는 상태인지를 알 수
있습니다. 사용할 수 없는 상태라면 INVALID_HANDLE_VALUE가 아닌 어떤 포
인터가 넘어옵니다.
조건이 참이 되는 경우, 즉 사용할 수 없는 경우 적절한 메시지를 표시하면
됩니다. 이때 AfxMessageBox()를 사용하면 편리합니다. MFC에서 제공되는
함수이고 윈도우 3.1버전에서의 메시지 박스와 사용법이 비슷합니다. 설명
이 필요하면 역시 F1을 누르면 됩니다.
2. 단계 : 핸들을 보관
자 이렇게 핸들을 구했습니다. 핸들은 저장되어야 하고 나중에 장치를 닫을
때 그 핸들이 다시 사용됩니다. 클래스 내에서만 사용한다면 클래스 내부
변수에 저장할 수도 있겠지만, 이 강좌의 목적이 멀티 쓰레드 기능을 이용
해 포트를 계속 적으로 감시하며 다른 작업을 하는 것을 설명하고자 하는
것이므로 다른 함수 내에서도 사용할 수 있어야 하는 이유로 글로벌 변수
(광역 변수라고도 칭하지요)에 저장합니다.
소스에서 pComm은 구조체가 글로벌 변수로 정의되어 있는 곳을 지정하는 포
인터로 정의되어 있으므로 pComm->hDev 에 대입하는 것만으로 이미 저장이
끝났습니다.
3. 단계 : 포트 초기화 값 지정
DCB는 직렬 통신 장치에 대한 제어 샛팅을 정의합니다. 역시 같은 방법으로
DCB를 치고 F1을 누르면 그 설명이 나옵니다. 직접 포트를 제어하는 도스
에서는 볼 수 없었던 것이므로 도스 프로그래머는 다소 황당한 느낌이 들수
있습니다. 프로그래머가 하는일이 늘 그렇듯 차근 차근히 뜯어보는 수밖에.
DCB는 헤더에 typedef되어 있는 것이므로 앞에 struct를 붙일 필요는 없습
니다. 아래는 DCB의 실체입니다. 주석문은 제가 내용을 변경한 것이므로
원래의 주석문은 아니니 착오 없길 바랍니다.
typedef struct _DCB { // dcb
DWORD DCBlength; // sizeof(DCB)
DWORD BaudRate; // 전송 속도(BPS단위의 수치)
DWORD fBinary: 1; // 바이너리 모드, 항상 1임
DWORD fParity: 1; // 패리티 검사함
DWORD fOutxCtsFlow:1; // CTS 출력 흐름 제어
DWORD fOutxDsrFlow:1; // DSR 출력 흐름 제어
DWORD fDtrControl:2; // DTR 흐름 제어 타입
DWORD fDsrSensitivity:1; // DSR 민감하게
DWORD fTXContinueOnXoff:1; // XOFF면 전송 계속
DWORD fOutX: 1; // XON/XOFF 출력 흐름 제어
DWORD fInX: 1; // XON/XOFF 입력 흐름 제어
DWORD fErrorChar: 1; // 에러시 문자로 교환할까?
DWORD fNull: 1; // 0번 문자 없애기
DWORD fRtsControl:2; // RTS 흐름 제어
DWORD fAbortOnError:1; // 에러면 읽고 쓰기 중지
DWORD fDummy2:17; // 나중에
쓰려고 예약된 영역
WORD wReserved; // 나중에 쓰려고 예약된 영역
WORD XonLim; // XON 보낼 때
WORD XoffLim; // XOFF 보낼 때
BYTE ByteSize; // 데이타 비트 수, 4-8
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; // end of input character
char EvtChar; // received event character
WORD wReserved1; // reserved; do not use
} DCB;
DCBlength DCB구조체의 길이를 바이트 단위로 지정합니다.
Specifies the length, in bytes, of the DCB structure.
BaudRate
전송속도를 지정하는데 이 값은 다음과 같이 정의되어 있습니다.
CBR_110 CBR_19200
CBR_300 CBR_38400
CBR_600 CBR_56000
CBR_1200 CBR_57600
CBR_2400 CBR_115200
CBR_4800 CBR_128000
CBR_9600 CBR_256000
CBR_14400
fBinary
이진 모드를 가능하게 할 것인지 지정하는데, WIN32 API는 이진
모드만을 지원합니다. 따라서 항상 TRUE로 지정하고, TRUE가 아니면
동작하지 않는다고 되어 있습니다.
윈도우 3.1에서 FALSE로 지정되면 EofChar로 지정한 문자가 들어오면
데이타의 끝으로 인식합니다.
fParity
패리티 검사를 할것인지 지정합니다. 만약 이것이 TRUE이면 패리티
검사를 하게되고 에러가 레포트되어집니다.
fOutxCtsFlow
송신 흐름 제어에 대한 CTS(clear-to-send)신호를 감시할 것인지
정합니다. 만약 이것이 TRUE이고 CTS가 꺼지면 CTS가 다시 보내져올
때까지 송신이 억제됩니다.
fOutxDsrFlow
송신 흐름 제어에 대한 DSR (data-set-ready)신호를 감시할 것인지를
모니터합니다. 만약 이것이 TRUE이고 DSR이 꺼지면 DSR이 다시
올때까지 송신이 억제됩니다.
DCB에 대한 설명은 여기까지만 하기로 합니다. 아직 많이 남아 있지만
구구 절절이 장황하게 레퍼런스를 번역하는 것 보다는 소스를 보고
원하는 제어 방식에서 바뀌는 부분만을 지적하는 것이 더
효율적이겠습니다. 저는 현재 왼쪽 팔이 부러져 기브스를 하고 있는
상태라 워드를 치는 것이 쉽지는 않은 상태라 힘이 듭니다.
단계 4. 초기화 값을 적용
마지막 단계를 설명합니다. 자, 이제 이번 강좌의 마지막 부분입니다.
저도 기쁜 마음이 듭니다.
먼저 소스를 보시기 바랍니다.
BOOL ret = SetCommState(pComm->hDev, &pComm->dcb);
if (ret == FALSE) {
TRACE1("SetCommState() 포트%d실패", pComm->PortNumber);
}
else
{
pComm->bConnected = TRUE;
}
이 소스는 제가 실제 사용하고 있는 부분을 그대로 복사하여 붙인
것입니다.
초기화 값을 적용하는 방법은 SetCommState()함수를 사용하는 것입니다.
첫째 인자에는 핸들을 두번째 인자에는 DCB가 있는 주소를 넣어 줍니다.
리턴되는 값은 성공 여부가 불린으로 나옵니다. 물론 BOOL은 타입
정의되어 있는 것입니다. 핸들은 앞서 설명한 CreateFile함수에서 얻어진
포인터 입니다. CreateFile에서 성공하면 SetCommState함수에서는
실패하는 경우가 없었습니다. COM포트가 사용중이면 이미 CreateFile에서
에러가 발생해 더이상 진행하지 못하게 되니 성공 여부 확인 루틴은
무의미 할 수도 있다고 생각되지만 도큐멘트 상으로 그것이 확실히 나와
있지는 않으므로 확인 루틴을 넣은 것입니다.
이번 강좌는 이것으로 마칩니다. 끝까지 읽느라 수고 많으셨습니다. 다음
강좌에는 설정한 포트에 대한 송신, 수신 버퍼를 잡는 과정에 대하여
설명하도록 하겠습니다.
#10771 박경환 (jiocafe )
[강좌] WIN32 시리얼 통신 #3 08/02 22:34 474 line
이번 강좌는 세번째 강좌입니다. 이번 강좌의 주제는 시리얼 포트의 송수신 버퍼
잡기입니다.
어떤 분이 편지를 보내 오셨는데, 그 요지가 설명은 이해가 가는데 코딩을 할 수
없더라는 것이었습니다. 그래서 코딩 예재를 보여 드려야겠다는 생각이 들었습니
다. 아직도 저의 왼쪽 팔은 기브스가 있기 때문에 워드를 치기가 쉽지 않고 시간
도 없어서 새로운 예재를 만들기 보다 제가 현재 개발중인 프로그램 중 한 파일
을 그대로 공개합니다. 참고하시기 바라며, 이 소스는 VC++ 4.0의 예재와 거의
같습니다.
#include "stdafx.h"
#include "wingen.h"
#include "CommIO.h"
#define ASCII_BEL 0x07
#define ASCII_BS 0x08
#define ASCII_LF 0x0A
#define ASCII_CR 0x0D
#define ASCII_XON 0x11
#define ASCII_XOFF 0x13
DWORD FAR PASCAL CommWatchPrinter( LPSTR lpData )
{
DWORD dwEvtMask ;
OVERLAPPED os;
#define MAXBLOCK 4096
BYTE abIn[MAXBLOCK + 1];
int nLength;
memset( &os, 0, sizeof( OVERLAPPED ) ) ;
// create I/O event used for overlapped read
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!", "TTY Error
!",
MB_ICONEXCLAMATION | MB_OK ) ;
return ( FALSE ) ;
}
if (!SetCommMask(WinGenInfo.Printer.hDev, EV_RXCHAR | EV_TXEMPTY)) {
MessageBox( NULL, "SetCommMask on CommWatchPrinter fail", "TTYErro
r!",
MB_ICONEXCLAMATION | MB_OK ) ;
return ( FALSE ) ;
}
while (WinGenInfo.Printer.bConnected)
{
dwEvtMask = 0 ;
WaitCommEvent(WinGenInfo.Printer.hDev, &dwEvtMask, NULL );
if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)
{
do {
if (nLength = ReadCommBlock(&WinGenInfo.Printer, (LPSTR)abI
n, MAXBLOCK)) {
abIn[nLength] = 0;
TRACE1("Printer [%s]", abIn);
}
} while (nLength > 0);
}
}
// get rid of event handle
CloseHandle( os.hEvent ) ;
// clear information in structure (kind of a "we're done flag")
WinGenInfo.Printer.dwThreadID = 0 ;
WinGenInfo.Printer.hCommWatchThread = NULL ;
return( TRUE ) ;
} // end of CommWatchPrinter()
DWORD FAR PASCAL CommWatchScanner( LPSTR lpData )
{
DWORD dwEvtMask ;
OVERLAPPED os;
#define MAXBLOCK 4096
BYTE abIn[MAXBLOCK + 1];
int nLength;
memset( &os, 0, sizeof( OVERLAPPED ) ) ;
// create I/O event used for overlapped read
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!", "TTY Error
!",
MB_ICONEXCLAMATION | MB_OK ) ;
return ( FALSE ) ;
}
if (!SetCommMask(WinGenInfo.Scanner.hDev, EV_RXCHAR ))
{
MessageBox( NULL, "SetCommMask on CommWatchScanner fail", "TTY Erro
r!",
MB_ICONEXCLAMATION | MB_OK ) ;
return ( FALSE ) ;
}
while (WinGenInfo.Scanner.bConnected)
{
dwEvtMask = 0 ;
WaitCommEvent(WinGenInfo.Scanner.hDev, &dwEvtMask, NULL );
if ((dwEvtMask & EV_RXCHAR) == EV_RXCHAR)
{
do {
if (nLength = ReadCommBlock(&WinGenInfo.Scanner, (LPSTR)abI
n, MAXBLOCK)) {
WinGenInfo.wndWork->SendMessage(WM_PENWINFIRST, nLengt
h, (LONG)&abIn);
}
} while (nLength > 0);
}
}
// get rid of eventhandle
CloseHandle( os.hEvent ) ;
// clear information in structure (kind of a "we're done flag")
WinGenInfo.Scanner.dwThreadID = 0 ;
WinGenInfo.Scanner.hCommWatchThread = NULL ;
return( TRUE ) ;
} // end of CommWatchScanner()
int ReadCommBlock(struct tagCommInfo * pComm, LPSTR lpszBlock, int nMaxLeng
th)
{
DWORD dwErrorFlags;
DWORD dwLength;
COMSTAT ComStat;
CString szError;
DWORD dwError = 0;
ClearCommError(pComm->hDev, &dwErrorFlags, &ComStat);
dwLength = min((unsigned)nMaxLength, ComStat.cbInQue);
if (dwLength > 0) {
int ret = ReadFile(pComm->hDev, lpszBlock, dwLength, &dwLength, &pC
omm->osRead);
if (!ret) {
if (GetLastError() == ERROR_IO_PENDING) {
while(!GetOverlappedResult(pComm->hDev, &pComm->osRead, &dw
Length, TRUE)) {
dwError = GetLastError();
if(dwError == ERROR_IO_INCOMPLETE) continue;
else
{
szError.Format("<CE-%u>", dwError);
MessageBox( NULL, szError, "TTY Error!",
MB_ICONEXCLAMATION | MB_OK ) ;
ClearCommError(pComm->hDev, &dwErrorFlags, &ComSta
t);
if (dwErrorFlags > 0) {
szError.Format("<CE-%u>", dwError);
MessageBox( NULL, szError, "TTY Error!",
MB_ICONEXCLAMATION | MB_OK ) ;
}
break;
}
}
}
else
{
dwLength = 0;
ClearCommError(pComm->
hDev, &dwErrorFlags, &ComStat);
if (dwErrorFlags > 0) {
szError.Format("<CE-%u>", dwError);
MessageBox( NULL, szError, "TTY Error!", MB_ICONEXCLAMA
TION | MB_OK ) ;
}
}
}
}
return dwLength;
}
BOOL WritecommBlock(struct tagCommInfo * pComm, LPSTR lpByte, DWORD dwBytes
ToWrite)
{
BOOL fWriteStat;
DWORD dwBytesWritten;
DWORD dwErrorFlags;
COMSTAT ComStat;
CString szError;
if (pComm->bConnected == FALSE) return FALSE;
fWriteStat = WriteFile(pComm->hDev, lpByte, dwBytesToWrite, &dwBytesWri
tten, &pComm->osWrite);
if (!fWriteStat) {
if (GetLastError() == ERROR_IO_PENDING) {
pComm->bConnected = FALSE;
if (pComm->hDev == WinGenInfo.Printer.hDev) {
AfxMessageBox("프린터에 명령을 전송할 수 없습니다. 작업 중
지 후 케이블 연결을 다시 확인하십시오");
}
else
{
AfxMessageBox("스케너에 명령을 전송할 수 없습니다. 작업 중
지 후 케이블 연결을 다시 확인하십시오");
}
return FALSE;
}
else
{
ClearCommError(pComm->hDev, &dwErrorFl
ags, &ComStat);
return FALSE;
}
}
return TRUE;
}
BOOL OpenComm(struct tagCommInfo * pComm) // 불특정 포트 열기
{
CString str;
pComm->osRead.Offset = 0;
pComm->osRead.OffsetHigh = 0;
pComm->osRead.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (pComm->osRead.hEvent == NULL) {
TRACE0("osRead.hEvent = CreateEvent()이 실패하여 NULL을 돌려줌\n");
return FALSE;
}
pComm->osWrite.Offset = 0;
pComm->osWrite.OffsetHigh = 0;
pComm->osWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
if (pComm->osWrite.hEvent == NULL) {
TRACE0("osWrite.hEvent = CreateEvent()이 실패하여 NULL을 돌려줌\n")
;
CloseHandle(pComm->osRead.hEvent);
return FALSE;
}
str.Format("COM%d", pComm->PortNumber);
pComm->hDev = CreateFile(str, GENERIC_READ | GENERIC_WRITE, 0, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, NULL);
if (pComm->hDev == INVALID_HANDLE_VALUE) {
CString strError;
strError.Format("시스템으로 부터 %s 장치 사용 허가를 얻어낼수 없습
니다", str);
AfxMessageBox(strError);
return FALSE;
}
// DCB설정
pComm->dcb.DCBlength = sizeof(DCB);
pComm->dcb.BaudRate = pComm->BaudRate;
pComm->dcb.Parity = pComm->ParityBit;
pComm->dcb.ByteSize = pComm->DataBit;
SetCommMask(pComm->hDev, EV_RXCHAR);
SetupComm(pComm->hDev, 4096, 4096); // 입력, 출력 버퍼 크기 설
정
PurgeComm(pComm->hDev, PURGE_TXABORT | PURGE_RXABORT | // 입력, 출력
버퍼 클리어
PURGE_TXCLEAR | PURGE_RXCLEAR);
pComm->CommTimeOuts.ReadIntervalTimeout = 0xFFFFFFFF;
pComm->CommTimeOuts.ReadTotalTimeoutMultiplier = 0;
pComm->CommTimeOuts.ReadTotalTimeoutConstant = 1000;
pComm->CommTimeOuts.WriteTotalTimeoutMultiplier = 0;
pComm->CommTimeOuts.WriteTotalTimeoutConstant = 1000;
SetCommTimeouts(pComm->hDev, &pComm->CommTimeOuts);
pComm->dcb.fOutxCtsFlow = TRUE; // CTS흐름 제어함
pComm->dcb.fOutxDsrFlow = TRUE; // DSR흐름 제어함
pComm->dcb.fDtrControl = DTR_CONTROL_HANDSHAKE;
pComm->dcb.fRtsControl = RTS_CONTROL_HANDSHAKE;
pComm->dcb.fBinary = TRUE; // 항상 TRUE여야 함
pComm->dcb.fParity = TRUE; // 패리티 검사함
pComm->dcb.fInX = pComm->dcb.fOutX = FALSE;
pComm->dcb.XonChar = ASCII_XON ;
pComm->dcb.XoffChar = ASCII_XOFF ;
pComm->dcb.XonLim = 100 ;
pComm->dcb.XoffLim = 100 ;
BOOL ret = SetCommState(pComm->hDev, &pComm->dcb); // 상기와 같이 포트
상태를 설정함
if (ret == FALSE) {
TRACE1("SetCommState() 포트 %d실패", pComm->PortNumber);
}
else
{
pComm->bConnected = TRUE;
}
return TRUE;
}
BOOL CloseComm(struct tagCommInfo * pComm) // 불특정 포트 닫기
{
if (NULL == pComm) return ( FALSE ) ;
// set connected flag to FALSE
pComm->bConnected = FALSE;
// disable event notification and wait for thread
// to halt
SetCommMask(pComm->hDev, 0 ) ;
// block until thread has been halted
while(pComm->hCommWatchThread != 0);
// drop DTR
EscapeCommFunction(pComm->hDev, CLRDTR) ;
// purge any outstanding reads/writes and close device handle
PurgeComm(pComm->hDev, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR | P
URGE_RXCLEAR ) ;
CloseHandle(pComm->hDev) ;
return ( TRUE ) ;
}
이번 강좌의 주제에 해당하는 부분은 이것 뿐입니다.
SetupComm(pComm->hDev, 4096, 4096); // 입력, 출력 버퍼 크기 설
정
PurgeComm(pComm->hDev, PURGE_TXABORT | PURGE_RXABORT | // 입력, 출력
버퍼 클리어
PURGE_TXCLEAR | PURGE_RXCLEAR);
헤더 파일도 보여주지 않으면 소용이 없겠습니다. 여기 헤더 파일이 있습니다.
// WinGen.h : main header file for the WINGEN application
//
#ifndef __AFXWIN_H__
#error include 'stdafx.h' before including this file for PCH
#endif
#include "resource.h" // main symbols
///////////////////////////////////////////////////////////////////////////
//
// CWinGenApp:
// See WinGen.cpp for the implementation of this class
//
class CWinGenApp : public CWinApp
{
public:
CWinGenApp();
// Overrides
// ClassWizard generated virtual function overrides
//{{AFX_VIRTUAL(CWinGenApp)
public:
virtual BOOL InitInstance();
//}}AFX_VIRTUAL
// Implementation
//{{AFX_MSG(CWinGenApp)
// NOTE - the ClassWizard will add and remove member functions her
e.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
///////////////////////////////////////////////////////////////////////////
//
// 시리얼 포트 관리 정보 구조체
struct tagCommInfo {
int DataBit; // 데이타 비트
DWORD BaudRate; // Baud
int StopBit; // Stop bit 수
int ParityBit; // 페리티 비트
int PortNumber; // 포트번호
HANDLE hDev; // 장치 핸들
DCB dcb; // 데이타 콘트롤 블럭
COMMTIMEOUTS CommTimeOuts;
DWORD dwThreadID;
HANDLE hCommWatchThread;
BOOL bConnected; // 연결되었는가?
OVERLAPPED osRead, osWrite;
};
// 바코드 라벨 인쇄 내용 저장 구조체
struct tagLabelInfo {
char txtJepum[30]; // 제품번호
char txtSuju[30]; // 수주번호
char txtSangSan[30]; // 생산일자
char txtNapum[30]; // 납품일자
char txtBupum[30]; // 부품번호
int nGoods; // 박스당 제품 수
long lTotalGoods; // 전체 제품 수
long lTotalBoxs; // 전체 상자 수
int nJa; // 마지막 박스의 제품 수(짜투리)
long lCountGoods; // 현재 생산된 제품 수
long lSerialNumber; // 박스의 일련 번호
long lCountBoxs; // 현재 생산되 박스의 수
int nCountCOA; // 현재 라벨에 들어가는 코아 번호 카운트
int nCountChar; // 현재 코아 문자 수신 카운트
char txtCOA[60][10]; // 코아번호 최대 60개
};
// 전체 구성 정보 관리 구조체
struct tagWinGenInfo {
CWnd *wndWork;
struct tagCommInfo Printer; // 라벨 프린터 통신 케이블 설정
struct tagLabelInfo Label; // 라벨 인쇄 내용
struct tagCommInfo Scanner; // 바코드 스케너 통신 케이블 설정
CDaoDatabase *CoaBase;
CDaoTableDef *CoaTable;
CDaoRecordset *CoaRecord;
};
extern struct tagWinGenInfo WinGenInfo;
이정도면 소스는 다 보신 셈이고...
이번 강좌는 몇자 치지 않아도 많은 페이지가 되어 버렸네요. 별도의 예재를 만
들어 강좌를 하는 것이 옳은 일인줄은 알지만 앞서 설명했듯이 팔이 말이 아니
라....
SetupComm()은 입력과 출력에 대한 버퍼의 크기를 잡아 줍니다. 앞서 나열된 소
스에는 4096바이트가 지정되어 있습니다. 도스에서는 인터룹트 핸들러가 했던 큐
영역 버퍼 작업이지만 윈도우 환경에서는 그작업은 윈도우가 하게 되고, 그 동
작 규칙은 강좌 2번에서 설명한 DCB에 설정된 값에 따라 동작합니다. 따라서 윈
도우에서는 도스에서는 만들어야만 했던 ISR루틴을 만들지 필요가 없고, 만들 수
도 없습니다.
다음은 PurgeComm(pComm->hDev, PURGE_TXABORT | PURGE_RXABORT | PURGE_TXCLEAR
| PURGE_RXCLEAR ); 차례인데 이것은 입출력 버퍼에 들어 있는 내용을 모두 지워
서 초기화 하는 것이라고 도큐멘트에 나와 있습니다. 왜 이 작업을 해야만 하는
지, 어느 위치에 있는 메모리를 클리어 하는지에 대하여는 밝혀져 있지 않습니
다. 그냥 '버퍼를 깨끗히 청소하는구나' 라고만 생각해야 하죠. 궁금하기 짝이
없지만 어쩌겠어요. 방법이 없는걸....
이번 강좌는 이것으로 마칩니다. 다음 강좌는 이번 강좌에서 공개한 함수들을 어
떻게 호출하는지에 대하여 설명하고자 합니다. 그 다음 강좌는 쓰레드를 생성하
고, 쓰레드와 주 프로그램간의 자료 송수신에 대하여 강좌를 하면 시리얼 포트
제어에 대한 자료는 거의 마무리되는 것이라 생각합니다.
이 소스에 ZMODEM프로토콜 소스를 덧붙이면 좋은 에뮬레이터를 만들수도 있겠다
는 생각이 듭니다. 그럼 이만.
#10772 박경환 (jiocafe )
[강좌] VisualC 4.0 & MFC 통신 #4 08/07 01:37 17 line
이번 강좌가 4번째 이군요. 시간은 잘도 갑니다. 강좌를 빨리 진행해 달라는 편
지도 있었고 해서 열심히 하고자 하는데 시간적 여유가 나지 않는군요.
편지 중에 CommIO.h파일이 없다는 내용이 있었는데, CommIO.CPP파일(가장 긴 소
스)의 함수 헤더들을 모아 하나의 파일로 만들면 그것이 끝입니다. CommIO.H는
몇줄이 안되는 간단한 파일입니다.
지난 3번 강좌에서 소스에서는 RTS, CTS, DSR등이 연결되어야만 송수신이 되는
소스입니다. 제가 바코드 프린터와 바코드 스케너를 제어하려고 만들고 있기 때
문에 프로그램을 간단히 하려 이 부분을 고정 시켰죠.
송수신 케이블의 내부에 전선이 3가닥 밖에 되지 않으면 송수신이 안됩니다. 정
식으로 완전한 케이블을 준비하셔야 송수신이 제대로 에러없이 송수신 됩니다.
만일 정식이 아닌 케이블로 사용해야 겠다고 결심을 하셨다면 소스에서 HANDSHAK
E를 찾아 ENABLE시키고, ENABLE되어 있는것은 DISABLE시키십시오. 그러면 됩니
다.
이번 강좌는 이것으로 마칩니다. 깊고 고요한 밤에 .... 경기도 화성군에서...
#10773 박경환 (jiocafe )
[강좌] Visual C 4.0 & MFC통신 #5 08/22 15:45 150 line
지난 강좌에서 시리얼 포트를 Visual C++ 4.0 MFC & WIN32 API & 멀티 쓰레드로 제어
하는 강좌를 하였다. 이번 강좌는 시리얼 포트가 많아야 하는 경우에 대하여 강의 한
다. 시리얼 포트를 많이 늘리는 장비인 멀티 포트에 대하여 설명한다.
기브스는 풀렀고... 이젠 물리치료를 다니고 있는데 일하느라 잠이 부족해 강좌를 위
한 정리가 잘 되지 않으니 잘 걸러서 읽기를 바랍니다.
1. 시리얼 포트 수 증가 방법
그 방법은 간단하다. 시리얼 포트를 설치하는 것이다. 포트의 수를 얼마만큼 필요
로 하는지가 관건이 된다.
일반적으로 PC에는 2개의 시리얼 포트가 있다. 그 이름은 COM1, COM2이다. 내장형
모뎀을 설치하게 되면 COM3나 COM4가 생긴다. 그래서 통신을 할때 COM3나 COM4를 선
택하여 통신한다. 어떤이는 COM2를 Disable시켜서 동작하지 못하게 하고 내장형 모뎀
의 점퍼나 딥 스위치를 COM2를 사용하기도 한다. COM2에 내장형 모뎀을 사용하는 방
법은 소프트웨어적으로 가장 안정적인 방법이다. 물론 COM1을 Disable시키고 COM1으
로 모뎀을 맞추는 방법도 있다. 이와 같은 원리이다. 내장형 모뎀을 설치하니 포트의
수가 8개 늘어났다고 생각하면 된다. 이러한 카드를 멀티 포트라 한다.
1.1 멀티 포트 설치법
이것을 이해하기 쉽게 설명하자면 내장형 모뎀을 생각하면 쉽다. 내장형 모뎀은 자체
적으로 포트를 가지고 있어서 모뎀 기판을 본체 마더보드 슬롯에 꽂기만 하면 포트의
수가 1개 증가한다. 이 포트의 갯수가 여러개인 모뎀이라고 생각하면 된다. 이러한
기판을 멀티 포트라고 한다.
멀티 포트는 1개의 카드(내장형 모뎀과 비슷한 기판)
1.2 멀티 포트가 갖는 능력
MULTI PORT는 COM1부터 COM4까지 있는 PC를 COM1부터 COM12까지 사용할 수 있게 하는
것이다. 즉, 시리얼 포트를 여러개 확장할 수 있다. 이 시리얼 포트들에 모뎀을 연
결하여 BBS를 구축할 수도 있고, 시리얼로 제어가 가능한 여러개의 기기를 제어할 수
도 있다. 멀티 포트의 종류에 따라서 포트의 수가 더욱 늘어날 수도 있다.
멀티 포트 제작 회사는 세계적으로 볼때 많다. 각 회사마다 각기 성능과 가격이 차이
가 있다. 이 모든 기기를 다루어본 사람은 아무도 없을 것이다. 이번 강좌에서 설명
하고자 하는 멀티 포트는 윈도우 95운영체제에서 동작하는 보드에 대하여만 국한하여
설명하고자 한다. 왜냐면 필자는 여러 회사의 제품을 사용해 보지는 못하고 국내의
한 업체에서 생산하는 제품만을 사용하여 보았기 때문이고, 또한 이 강좌의 목적이
비주얼 씨 플러스 플러스에서 시리얼 포트를 제어하는데 대한 설명이 목적이기 때문
이다. 생산하는 회사의 이름을 밝혀도 통신망의 어떤 법에 저촉되는지는 모르지만 강
좌의 설명을 위해서 밝혀야만 할 수 밖에 없으므로 밝힌다. 그 회사의 이름은 '시스
템 베이스'이다. 이 회사에서 제작하여 판매하고 있는 제품중 MULTI-8을 사용하여 설
명하고자 한다. 필자가 가지고 있는 제품이 이것 뿐이기 때문이다. 이하 멀티 포트라
고 하는 것은 모두 시스템 베이스에서 만들어 판매한 MULTI-8이라는 제품이다.
멀티 포트는 RS232각 포트를 제어하기 위해 16450칩을 사용한다. 9600BPS까지만 안정
적으로 지원했던 8250칩을 개선한 칩이 16450이라고 어떤 강사들이 이미 다른 여러
강좌에서 설명한바 있으니 그것을 참조하기 바라고 여기에서는 그냥 넘어간다.
따라서 115200BPS까지 안정적으로 지원한다. 그러나 이 속도를 다 사용해야만 하는
일은 별로 없을 것이다. 일단 필자는 19200BPS만을 사용하여 공장의 생산라인을 자동
화해야 하므로 이 작업을 진행하면서 계속적으로 강좌를 하고자 한다.
1.3 멀티 포트의 종류
포트의 수에 따라 멀티 포트는 포트의 수가 각각 4, 8, 16, 32개 짜리가 있다. 이들
멀티 포트는 연결하여 사용할 수 있으므로 더욱 많이 늘어날 수 있다.
포트의 제어 방식에 따라 포트가 인텔리전트형인가 아니면 더미 형인가로 구분지어진
다. 인텔리한 포트는 HOST의 CPU의 부하를 적게 주어 좀더 효율적인 통신망을 운영할
수 있다고 한다. 사실 필자는 이런 포트는 사용하여 보지 못했다.
포트 규격에 따라 232,485, 422방식으로 나뉜다. 흔히 사용하는 것이 232고 나머지
는 공장이나 전기 시설이 많은 곳에서 노이즈에 민감하지 않도록 설계되었다고 한다
. 물론 이 강좌에서 설명할 범위가 아니므로 생략한다.
1.4 멀티 포트에 대한 정리
앞서 설명한 바와 같은 멀티 포트를 PC에 설치하면 윈도우 95환경에서 여러 시리얼
포트를 확보할 수 있고 그곳에 외장형 모뎀을 설치한다면...... 두뇌 회전이 빠르신
분은 BBS를 운영할 수도 있겠다는 것을 미리 짐작했을 것이다. 물론 강좌의 범위에
벗어나므로 그 설명은 생략한다.
2. 윈도우 95에 설치
윈도우 95를 OS로 사용하는 컴퓨터에서 멀티 포트를 사용하고자 한다면 하드웨어 제
작 회사가 제공하는 소프트웨어를 설치하여야 한다. 흔히 디바이스 드라이버라고 부
르는 것이다. 시스템 베이스의 메뉴얼을 보면 각종 OS에 대한 모든 설치 방법이 나와
있다.
2.1 전원을 끈다.
다른 모든 경우와 마찬가지로 슬롯에 새로운 기판을 꽂으려면 전원을 꺼야 한다. 끄
기 싫으면 끄지 않고 해보고 그 결과를 저에게 알려주기 바란다. 어떤 결과가 나오는
지 알고 싶으니까.... 아마 마더 보드가 맛이 가거나 .... 그럴 것이다.
2.2 비어있는 슬롯을 찾는다.
멀티포트 카드는 ISA슬롯에 맞도록 제작되어 있다. 16BIT슬롯 하나면 된다. 비어 있
는 슬롯이 없으면 우선순위에 따라 가장 덜 사용하는 카드를 뽑아 버린다. 물론 멀티
포트 카드를 설치하면 IRQ, BASE ADDRESS때문에 다른 장비와 충돌하지 않으려면 가
능한 다 뽑아 버리는 것이 좋다.
2.3 카드의 딥 스위치를 조절한다.
메뉴얼에 나와 있는데로 잘 조절한다. 정답이 없다. 보드마다 IRQ등을 사용하는 것이
각기 다르기 때문이다. 딥 스위치는 24개가 있다. 동작 방식, IRQ, BASEADDRESS,
POLL ADDRESS를 선택하기 위해서 이다.
같은 어드레스를 쓰거나 하여 충돌하게 되면 동작이 원활히 되지 않는다. 윈도우 95
의 시작-설정-제어판-시스템-장치 관리자에서 충돌하는지 여부를 확인해 본다. 충돌
하여 동작하지 않는 장치들은 노란 바탕의 느낌표 표시가 아이콘에 나온다. 딥 스위
치를 조절하기 위해 수십번 또는 수백번을 시도해 보게 될것이다. 운이 좋으면 단 1
번에.. 뭐가 그리 어렵냐는 질문을 하신분은 컴퓨터를 배우기 시작한지 얼마 되지 않
은 분일 것이다. PnP기능이 나온지는 1년도 되지 않았다. 몇 개월 전까지만 하더라도
꽂고서 IRQ, DMA등을 맞추어 주느라 조립할때 고생 꽤나 했었다.
2.4 슬롯에 꽂는다.
그냥 꽂는다. 슬롯의 방향과 위치만 정확하다면 그리 힘주지 않아도 잘 들어간다.
2.5 켠다.
켜보면 동작하지 않는 장치 때문에 윈도우 95에서 어떤 메시지가 나올 수도 있다. 충
돌하는 경우에만 그렇다. 2.3번에서 설명한 바와 같이 장치 관리자를 살펴본다. 느낌
표를 발견하였다면 다시 컴퓨터 끄고, 슬롯 빼고 딥스위치를 다른 값으로 바꾸어 본
다.
이번 강좌는 이것으로 마치고 다음 강좌에 이어서......
#10774 박경환 (jiocafe )
[강좌] Visual C 4.0& 시리얼 통신 #6 11/04 00:09 79 line
그동안 너무나 바빠서 통신에 접속할 시간이 없어서 강좌를 하지 못하였음을
먼저 알립니다.
몇 통의 편지를 받았는데 질문의 요점은 "이해는 가는데 짜질 못하겠다"였습니다.
왜 짤수가 없을까? 이 생각을 많이 해보았는데 저는 이해를 할 수 없었습니다.
더 이상 쉽게 설명을 할 수 없을 만큼 쉽게 설명을 했다고 믿고 있으며,
시리얼 제어가 트루타입 폰트 제어 처럼 관련 자료를 모두 모아야 조금 이해가
가는 것도 아니기 때문입니다.
물론 편지를 주시지 않은 대다수의 사람들은 프로그램을 문제없이 짤 수 있었기
때문에 저에게 그런 편지를 보낼 필요가 없었으리라고 생각합니다.
어찌 되었든지간에 소수의 프로그램을 짜고자 하는 이가 프로그램을 짜지
못하고 있다는 것은 사실이고 이것을 묵과해서는 안된다고 생각이 듭니다.
좀더 자세한 예재를 들거나 아예 프로그램 소스를 공개하는 방법도 있으리라
생각됩니다.
그런데, 두번째 방법인 소스를 모두 공개하는 것은 저로서는 등록하는데 전혀
노력이 필요치 않은 쉬운 방법이기는 하나 회사의 자산으로 볼 수 밖에 없는
소스이므로 그렇게는 해서는 안된다고 생각합니다.
자. 그럼 어떻게 해야 할까요?
설명을 위해 제가 예재를 만들어서 공개하는 방법이 있겠습니다.
회사에도 상관없고, 프로그램을 짜려는 소수의 프로그래머에게도 희소식일 테니
그러나... 저는 더 바빠져야 한다는 것입니다.
그래서 결론은 이렇습니다.
이번 강좌에서는 시리얼 제어에 관련한 철학에 대하여 설명하고,
다음 강좌에서는 완벽한 예제 소스 파일을 공개하는 것입니다.
시리얼 제어에 관련한 철학을 설명하겠습니다.
이 강좌가 불필요하다고 생각하시면 오산입니다. 왜냐면 프로그래머가 시리얼을
제어해야 하는 경우는 기기를 제어하거나 하는 먹고 사는일에 관련된 일이기
때문입니다.
주어진 시간 안에 업무를 처리하기 위해서는 시리얼 제어에 관한 철학이
좋을때 쉽게 해결할 수 있습니다.
첫번째 철학은?
시리얼 포트로 들어오는 정보는 언제든 엉망이 될 수 있다.
한 바이트가 없어질 수도 있으며, 한바이트가 추가될 수도 있으며,
11개의 문자중에서 1개의 문자만 불특정한 문자로 변경될 수도 있습니다.
그래서 항상 시리얼 제어에는 불 만족스러운 값이 들어올 경우에
그 들어온 값을 무시해 버리거나 재 전송 요구를 하거나
복구를 하는 루틴이 반드시 있어야 합니다.
물론 재저송 요구를 하고 마냥 기다리다가는 큰 일입니다.
재 전송 요구 신호를 상대가 받지 못하는 경우도 있기 때문입니다.
두번째 철학은?
시리얼 포트로 들어오는 정보는 언제 들어올지 모른다.
언제 갑자기 많은 데이타가 들어올지 모르기 때문에 타스크가 느릴 경우
그 데이타를 모두 처리할 수 없어서 수신받은 문자가 큐 버퍼에서
넘쳐 버리는 경우가 생겨 자료를 잃어버리게 될 수 도 있다는 것입니다.
타이머 메시지를 받아 정기적으로 큐에 문자가 수신되었는지 확인하는
방법이 프로그래밍 하기에는 쉬울수 있으나
기기를 제어하는데 있어서 기기가 항상 신호를 보내는 것이 아니므로
처
리가 시간이 낭비될 수 있습니다.
데이타를 달라고 요청해야 보내주는 원격 장치에 대하여
타이머로 끊임없이 달라고 하면 없다는 메시지만 계속 보내올 것입니다.
데이타를 달라고 요구하는 지연 시간 값을 자동으로 늘이거나 줄이는
기교가 필요합니다.
세번째 철학은?
선로 상태 자체가 엉망일 수 있다.
시리얼 케이블을 아무도 건드리지 않는다는 보장이 없습니다.
천정을 통해 배선을 했다고 하더라도 쥐가 쏠아서 끊어 버리는 경우나
비가 스며들어 합선되는 경우가 없을 것이라고 단정 지을 수는 없습니다.
선로 주변에 전압이 높은 전력선이 지나지 않나요? 노이즈의 주 원인이
무엇이라 생각하십니까?
기기를 제어한다면 피복이 제대로 된 케이블을 다른 전선과 멀게 하고
케이블의 길이를 가장 짧은 길이가 되도록 설계하십시오.
네번째 철학은?
내가 짠 소스는 효율적이지 않을 수 있다.
프로그램을 완성하신 후에는 시리얼 데이타가 들어오는 빈도나
순간 적으로 얼마나 많이 데이타가 쌓이게 되는지 확인해 보시기
바랍니다.
가장 많이 들어오는 때 가장 많은 처리를 해야 하므로
효율적이어야 합니다. 윈도우 95나 NT와 같은 시스템은
여러 프로그램이 함께 동작하는 멀티 타스킹 환경이므로
내 프로그램이 일정 시간(사람이 느끼지 못할 정도)은 동작을
멈출 수도 있다는 것을 잊지 마십시오.
시리얼 제어가 가장 쉽고 안정적인 시스템은 DOS라고 생각합니다.
다섯번째로 철학은?
지금 알고 있는 문제 이외로 다른 문제가 시리얼 통신에는 존재한다.
이렇게 나열해 놓고 보니까 시리얼 제어가 어려운 것 같지만
쉽다면 무지 쉬운게 시리얼 제어입니다.
이번 강좌는 이것으로 마칩니다. 다음 강좌에서는 예재 소스를
보여 드리겠습니다.