|
by trick14 카테고리
───────────
★사진첩
───────────
♧ EJ ♧ Jeimian ♧ Titaness ♧ Eclipsia ♧ Lakey ♧ Narana ♧ Repenny's Cy ─────────── ▶ SLR클럽 ▶ 클리앙 ▶ SKI114 ▶ PhotoAbs 다운받기 ──────────── ◇ 하루 한가지 ◇ 한국 100대통계지표 ──────────── 최근 등록된 덧글
복잡하다 ㅋㅋㅋ
난 그냥 U1..
by 라키.. at 11/09 나도 방명록을 원해요.... by Rena at 11/05 오 여기 방명록같은건 없나?.. by 라키.. at 10/19 ㅋㅋㅋ 3번 방법 강추네요! by GoodLife at 10/01 클리앙 통해서 연재 잘 보.. by GoodLife at 10/01 |
2008년 09월 02일
당연히 이 자료를 올렸다고 생각하고 닷넷을 이용한 시리얼 통신 구현을 알아봤는데요. 시리얼통신 구현 시리얼 통신 프로그램은 크게 아래와 같은 구조로 이루어져 있습니다. l 시리얼통신을 위한 포트 생성 l 포트의 초기화 l 읽기쓰레드 서비스루틴 정의 l 읽기쓰레드 생성 l 포트 입출력 l 포트 닫기 물론 딱 이렇게 정형화 되어있는 것은 아니지만 대체로 위와 같은 과정을 통해서 시리얼통신을 할 수 있게 됩니다. 시리얼 통신을 위해 먼저 필요한 함수, 구조체들에 대해서 알아보겠습니다. 함수와 변수 정의 LPTSTR lpszDevName = TEXT("COM1:"); // 포트이름 int lenEdit; // 입력받은문자열의길이 BYTE ReceiveBuffer[MAX_BUFF]; // 받은문자열버퍼(바이트) TCHAR szReceiveBuffer[MAX_BUFF]; // 받은문자열(TCHAR) BYTE SendBuffer[MAX_BUFF]; // BYTE형으로변환된입력받은문자열 TCHAR szSendBuffer[MAX_BUFF]; // 입력받은문자열 //프로토타입선언 BOOL PortInitialize (LPTSTR lpszPortName); // 포트생성 및 초기화함수 DWORD PortReadThread (void); // 읽기쓰레드 서비스 루틴 정의 void ReadThreadOn(void); // 읽기쓰레드 생성 void PortWrite (TCHAR *); // 포트쓰기 BOOL PortClose (HANDLE hCommPort); // 포트닫기
포트 생성 및 초기화 포트를 생성하고 초기화하는 PortInitialize함수는 포트를 생성하는 CreateFile함수와 포트를 초기화하기 위한 함수와 구조체들로 이루어져 있습니다. 먼저 포트를 생성하기 위한 CreateFile함수를 살펴보겠습니다. CreateFile (lpszPortName, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); 이 함수는 사실 파일을 생성하기 위한 함수이지만 시리얼통신을 위해 포트를 생성할 때도 파일을 생성할때와 동일한 방법으로 사용할 수 있습니다. CreateFile에서 사용되는 파라메터들은 아래와 같습니다. CreateFile (lpFileName, dwDesiredAccess, dwShareMode, lpSecurityAttributes, dwCreationDisposition, dwFlagsAndAttributes, hTemplateFile ); n lpFileName : 생성할 객체의 이름. 우리는 LPTSTR lpszDevName 변수에 TEXT(“COM1:”) 로 사용할 컴 포트를 정의해 주었습니다. n dwDesiredAccess : 객체에 대한 읽기,쓰기권한. GENERIC READ와 GENERIC WRITE를 주어 일반적인 읽기/쓰기 권한을 부여합니다. n dwShareMode : 객체에 대한 공유설정. 0이고 CreateFile이 성공했을 경우 이 핸들이 close될 때까지 다시 open하거나 공유 금지시킵니다. 0으로 설정합니다. 이곳에서 사용할 수 있는 옵션은 아래와 같습니다. FILE_SHARE_DELETE 객체에 대한 삭제 권한 공유. FILE_SHARE_READ 객체에 대한 읽기 권한 공유. FILE_SHARE_WRITE 객체에 대한 쓰기 권한 공유. n lpSecurityAttributes : child프로세스로 핸들을 넘길지에 대한 설정. NULL이면 넘기지 않음. n dwCreationDisposition : 객체의 생성옵션. OPEN_EXISTING일 경우 객체가 존재하지 않으면 Fail. CREATE_ALWAYS 항상 새로 파일을 생성합니다. 만약 파일이 존재합니다면 그 파일을 덮어씌우고새로운 파일을 생성합니다. CREATE_NEW 새로운 파일을 생성하지만 만약 같은 이름의 파일이 있습니다면 Fail. OPEN_ALWAYS 항상 파일을 열지만 파일이 존재하지 않는다면 CREATE_NEW와 같은 기능으로 파일을 생성합니다. OPEN_EXISTING 파일을 연다. 만약 존재하지 않는 파일이면 Fail. TRUNCATE_EXISTING 기존 파일의 내용을 0byte로 연다. 만약 파일이 존재하지 않으면 Fail. 반드시 GENERIC_WRITE 권한으로 사용되어야 합니다. n dwFlagsAndAttributes : 객체에 대한 속성을 설정하는 파라메터입니다. 시리얼통신에서는 사용되지 않으므로 0으로 설정합니다. n hTemplateFile : OPEN_EXISTING이 성공일 경우 이 파라메터는 무시됩니다. 즉 시리얼 통신에서는 이 파라메터를 사용할 일이 없으므로 NULL로 설정합니다. 이렇게 시리얼 포트를 생성한 후에는 통신 속도나 흐름제어(Flow Control)등 통신을 하기위한 여러 설정들을 해 주어야 합니다. 통신 디바이스의 컨트롤 셋팅 구조체 통신 디바이스의 설정을 위해 DCB구조체를 사용하게 됩니다. 이 구조체는 아래와 같이 통신에 필요한 설정들을 위한 여러가지 변수들을 제공하고 있습니다. DCB PortDCB; // 시리얼통신장비의컨트롤셋팅구조체 GetCommState (hPort, &PortDCB); // 현재 hPort의 설정을 PortDCB로 로딩 PortDCB.DCBlength = sizeof (DCB); PortDCB.BaudRate = 38400; // 통신 속도 설정 PortDCB.fBinary = TRUE; // 바이너리모드, EOF체크 안함 PortDCB.fParity = FALSE; // 패리티 체크의 사용 여부 PortDCB.fOutxCtsFlow = FALSE; // CTS 출력 흐름제어 사용 여부 PortDCB.fOutxDsrFlow = FALSE; // DSR 출력 흐름제어 사용 여부 PortDCB.fDtrControl = DTR_CONTROL_ENABLE; // DTR 흐름제어 형태 PortDCB.fDsrSensitivity = FALSE; // DSR 감도 PortDCB.fTXContinueOnXoff = TRUE; // XOFF continues Tx PortDCB.fOutX = FALSE; // XON/XOFF 출력 흐름제어 사용 여부 PortDCB.fInX = FALSE; // XON/XOFF 입력 흐름제어 사용 여부 PortDCB.fErrorChar = FALSE; // Disable error replacement PortDCB.fNull = FALSE; // Disable null stripping PortDCB.fRtsControl = RTS_CONTROL_ENABLE; // RTS 흐름제어 PortDCB.fAbortOnError = TRUE; // 에러 무시 PortDCB.ByteSize = 8; // 1바이트의 비트수 PortDCB.Parity = NOPARITY; // 패리티의 종류 // 0-4까지 각각 no,odd,even,mark,space PortDCB.StopBits = ONESTOPBIT; // 스탑비트수 0,1,2가 각각1,1.5,2를 의미 if (!SetCommState (hPort, &PortDCB)){return FALSE;} // 설정된 DCB구조체를 hPort에 적용 DCB구조체는 위의 그림에서 주석부분만 살펴보아도 쉽게 의미를 알아볼 수 있도록 되어있습니다. GetCommState를 이용해 현재 hPort의 설정을 DCB구조체로 불러오고 DCB구조체를 재 정의한 후 SetCommState함수를 이용해 hPort에 재정의한 DCB구조체를 적용하는 방식으로 사용합니다. 위 DCB구조체에서 사용하는 흐름제어방식에 대해서 잠깐 살펴보겠습니다. 윈도우에 기본적으로 포함되어있는 하이퍼터미널 프로그램을 이용해서 시리얼통신을 하려고 하면 아래와 같은 설정화면을 만나게 됩니다. 비트/초 1초에 보낼 비트 수. 즉 PortDCB.BaudRate = 38400; 에서 정해주는 BaudRate가 됩니다. 통신을 하는 두 디바이스에서 같은 값을 사용하여야 합니다. 데이터비트 한번에 보낼 비트의 단위. 패리티 패리티는 데이터를 주고받을 때 받은 테이터가 송신도중 문제가 없었는지를 확인하기 위한 도구입니다. 정지비트 비동기식 전송에서 한 문자의 종료를 표시하기 위해 추가되는 비트. 흐름제어 흐름제어는 통신도중 수신자의 버퍼가 넘칠경우 데이터가 손실되는 것을 막기위한 기법입니다. 여러가지 방법이 있지만 X-On/X-Off방식을 많이 사용합니다. X-온/X-오프 흐름 제어 데이터의 송신속도와 수신속도에 큰 차이가 있거나 데이터 파일이 큰 경우 통신버퍼의 용량이 초과될 수 있는데 X-온/X-오프 흐름제어에서는 데이터의 흐름을 온/오프시켜 버퍼의 초과를 방지하게 됩니다. 또한 이 프로토콜은 디스크 구동 장치에 전송되는 데이터가 디스크에 저장될 때 데이터의 전송 속도가 통신 하드웨어의 처리 능력을 앞지르는 것을 방지하기 위해서도 사용됩니다. 만일 X-온/X-오프 프로토콜이 통신을 하는 양 디바이스간에 잘 이루어져 있습니다면 데이터 전송 속도의 조절을 전적으로 통신 소프트웨어에 의해 제어할 수 있습니다. 통신 수신 버퍼에 저장되는 데이터 양이 버퍼의 용량에 도달하면 소프트웨어는 송신자에 XOFF신호를 보냅니다.(XOFF 문자는 아스키 DC3으로 Ctrl-S와 동일). XOFF를 수신하면 송신자는 데이터의 전송을 일시 중단하고 수신자가 버퍼에 있는 데이터를 처리하도록 해 줍니다. 버퍼가 지정된 저레벨까지 비워지면 수신자는 XON문자(아스키 DC 1(Ctrl-Q))를 송신자에게 보내고 데이터 전송을 재게하게 됩니다. 이 과정은 데이터가 전송되는 동안에 사용자 모르게 수없이 반복됩니다. Hardware RTS/CTS 흐름제어라고 알려져 있으며 TDX/RDX이외의 2개의 라인이 더 사용되므로 시리얼RS232통신에서는 사용되지 않습니다. 통신장치의 타임아웃 구조체 //통신장비의 타임아웃 파라메터 구조체. COMMTIMEOUTS CommTimeouts; GetCommTimeouts (hPort, &CommTimeouts); // COMMTIMEOUTS구조체의설정 CommTimeouts.ReadIntervalTimeout = MAXDWORD; CommTimeouts.ReadTotalTimeoutMultiplier = 0; CommTimeouts.ReadTotalTimeoutConstant = 1000; CommTimeouts.WriteTotalTimeoutMultiplier = 0; CommTimeouts.WriteTotalTimeoutConstant = 1000; if (!SetCommTimeouts (hPort, &CommTimeouts)){CloseHandle(hPort);return FALSE;} ReadIntervalTimeout 통신중의 두 바이트 도착간의 최대 허용지연시간. 밀리세컨드단위. ReadFile동작중, 첫번째 바이트가 수신된 후 타이머가 작동을 시작합니다. 만약 다음 두 바이트의 도착이 ReadIntervalTimeout을 넘어가면 ReadFile은 종료되고 버퍼에 쌓인 데이터가 리턴됩니다. 0으로 설정된 경우 time-out인터벌을 사용하지 않음을 의미합니다. ReadTotalTimeoutConstant와 ReadTotalTimeoutMultiplier가 0이고 MAXWORD값이면 수신한 데이터는 이미 수신된 bytes와 함께 즉시 리턴됩니다. ReadTotalTimeoutMultiplier 수신동작중 타임아웃 시간을 계산하기 위한 멤버변수. 밀리세컨드단위. 각각의 읽기동작에서 이 값은 읽혀질 byte수와 곱해지게 됩니다. ReadTotalTimeoutConstant 읽기동작중 타임아웃 시간을 계산하기 위한 상수. 밀리세컨드 단위. 각각의 읽기 동작중 이 값은 ReadTotalTimeoutMultiplier와 수신요청된 바이트수의 곱에 더해지게 됩니다 WriteTotalTimeoutMultiplier 쓰기 동작 중 타임아웃 시간을 계산하기 위한 멤버변수. 밀리세컨드 단위. 각각의 쓰기 동작중 이 값은 쓰여질 바이트 수에 곱해지게 됩니다. WriteTotalTimeoutConstant 쓰기 동작 중 타임아웃 시간을 계산하기 위한 상수. 밀리세컨드 단위. 각각의 쓰기동작중 이 값은 WirteTotalTimeoutMultiplier와 쓰여질 바이트수의 곱에 더해지게 됩니다. ※MAXDWORD는 0xffffffff로 winnt.h에 정의되어 있음 그 외의 포트 설정 // 지정된통신장비의통신파라메터초기화 SetupComm(hPort, 32768, 32768); //통신장비에서모니터되야할이벤트셋정의 SetCommMask (hPort, EV_RXCHAR | EV_ERR); //지정된통신리소스의output이나input버퍼의케릭터들을모두제거합니다. 진행중인읽기,쓰기작업도제거합니다. PurgeComm( hPort, PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR); SetupComm(hPort, 32768, 32768) SetupComm은 지정된 통신장비의 입출력 버퍼사이즈를 설정하는 함수입니다. 파라메터는 바이트 단위이며 32 x 1024 = 32768 즉 32KB를 입출력 버퍼사이즈로 설정합니다. n hFile [in] CreateFile에 의해 리턴된 통신디바이스의 핸들. n dwInQueue [in] 권장되는 디바이스 내부의 입력버퍼 바이트수. n dwOutQueue [in] 권장되는 디바이스 내부의 출력버퍼 바이트수. SetCommMask (hPort, EV_RXCHAR | EV_ERR) SetCommMask는 통신장비에서 모니터 되야할 이벤트셋을 정의하는 함수입니다. n EV_RXCHAR(0x0001) 케릭터를 받아서 input버퍼에 위치시킴 n EV_ERR(0x0080) 라인상태에 문제가 발생했을 경우 이벤트 발생. CM_FRAME, CE_OVERRUN, CE_RXPARITY가 라인상태 이상에 해당됨 PurgeComm( hPort, PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR) 이 함수는 첫번째 파라메터에서 지정된 통신 리소스의 output이나 input버퍼의 케릭터들을 모두 제거하는 함수입니다. 현재 진행중인 작업까지 전부 제거할 수 있습니다. n PURGE_RXABORT 0x0002 모든 읽기 작업과 리턴값을 제거 n PURGE_RXCLEAR 0x0008 입력버퍼 클리어. n PURGE_TXABORT 0x0001 모든 쓰기 작업과 리턴값을 제거 n PURGE_TXCLEAR 0x0004 출력버퍼 클리어. 여기까지 COM1 포트를 오픈하고 통신을 위한 설정 및 초기화를 해 보았습니다. 이제부터 실제로 데이터를 송신하고 수신하는 작업들을 알아보겠습니다. 시리얼 포트에 쓰기(송신)작업을 위해 PortWrite라는 함수를 만들어 보았습니다. void PortWrite (TCHAR * Data) { DWORD dwWritten; wcstombs((char *)SendBuffer, Data, MAX_BUFF); WriteFile(hPort, SendBuffer, strlen((char*)SendBuffer), &dwWritten, NULL);//포트로전송 SetWindowText(hEdit, TEXT("")); //hEdit 비우기 } CE에서는 유니코드만을 사용하기 때문에 유니코드<->MBCS에 대한 변환을 고려할 필요가 없지만 이 프로그램에서는 시리얼을 통해 통신하는 상대 프로그램이 유니코드(WBCS:WideByteCharacterSet)가 아닌 MBCS(MultiByteCharacterSet)을 사용합니다고 보고 보내야할 데이터를 wcstombs함수를 이용해 변환해 주었습니다. 일반적으로 사용하는 문자세트(Character Set)에 대해서는 이 문서의 가장 뒤쪽에 첨부해 두었습니다. 실질적으로 포트로 데이터를 내보낼 때 사용하는 함수인 WriteFile함수를 살펴보도록 하겠습니다. 함수에 대한 정의와 파라메터들은 아래와 같습니다. BOOL WriteFile( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPDWORD lpNumberOfBytesWritten, LPOVERLAPPED lpOverlapped ); n hFile [in] 쓸 포트에 대한 핸들 n lpBuffer [in] 쓸 데이터에 대한 포인터 n nNumberOfBytesToWrite [in] 쓸 데이터의 바이트 수 n lpNumberOfBytesWritten [out] WriteFile에 의해 실제 쓰여진 바이트 수 n lpOverlapped [in] CE에서 지원하지 않는 파라메터. NULL로 설정. COM1 포트에 대한 핸들인 hPort와 송신할 데이터에 대한 포인터, 쓸 데이터의 바이트수만 정확하게 지정해 준다면 별 문제없이 송신작업을 할 수 있습니다.
읽기쓰레드 서비스루틴 정의 실제 CreateThread에 의해 생성될 쓰레드가 수행하게된 서비스 루틴 PortReadThread(void)에 대한 정의입니다. DWORD PortReadThread (void) { DWORD dwCommModemStatus, dwErrorFlags; DWORD dwRead; // 읽은바이트수. COMSTAT comstat; // 통신장치에대한정보. BOOL bThread = TRUE; if (! SetCommMask( hPort, EV_RXCHAR)) bThread = FALSE; while (hPort != INVALID_HANDLE_VALUE) { // 특정장비에서이벤트가일어나는것을기다림. WaitCommEvent (hPort, &dwCommModemStatus, 0); if (dwCommModemStatus & EV_RXCHAR) // EV_RXCHAR이벤트발생시 { // 통신 에러에대한 정보를 저장하고 현재 통신장비의 상태를 보고함. // 이 함수는 통신에러가 발생했을 때 작동하며 // 에러플래그를 초기화하고 입/출력을 활성화시킴. ClearCommError(hPort, &dwErrorFlags, &comstat); // 읽은문자열 ReceiveBuffer로 MAX_BUFF만큼 읽어오고 // 실제 읽어들인 바이트 수를 dwRead에저장. if(ReadFile (hPort, ReceiveBuffer, MAX_BUFF, &dwRead, 0)) } } hReadThread = (HANDLE)0; return -1; } |