| C++ 언어를 권장하지 않는 이유 |
|---|
|
C++ 언어는 개발 생산성 저하로 인해 Windows CE에서는 사용을 권장하지 않습니다.(SmartX사용 환경과 비교시 개발 소요 시간이 10배 이상 증가할 수 있습니다.) 또한, 개발 환경 구축 문제로 인해 개발 지원 자체가 안되는 경우도 있습니다. 따라서 C# 또는Basic 언어에서 SmartX New Framework를 사용하여 개발하실 것을 권장하고 있습니다. |
본 자료는
1. 송/수신 데이터 구조(Protocol)
2. CSerialPort Class Interface (기능과 내부인자)
3. CQueue Class Interface (기능과 내부인자)
4. 포트 설정 방법
5. 수신 스레드(Thread)
순서로 진행합니다.
본 시리얼(Serial) 통신 예제는 C++로 제작되었으며 데이터는 프레임(Frame) 단위(STX + Data + CheckSum + ETX)로 구현되었습니다. 여기서 프레임은 프로토콜을 말하며 사용하시는 장치에 따라서 프로토콜의 구조는 달라질 것입니다. 수신 처리는 스레드(Thread)로 동작하며 수신된 데이터는 프레임 단위로 수신큐(Queue)에 저장되도록 구현하였습니다.
1. 송/수신 데이터 구조(Protocol)
시리얼 통신을 위한 송/수신 데이터의 프로토콜(Protocol) 정의
데이터의 프레임(Frame) 구조| STX[1byte] | Data[N Byte] | CheckSum[1Byte] | ETX[1Byte] |
STX는 프레임의 시작 코드로서 기본 설정값은 0x02입니다.
ETX는 프레임의 종료 코드로서 기본 설정값은 0x03입니다.
체크섬(CheckSum)은 에러 체크 코드(Error Check Code)로 여기서는 CheckSum8을
사용하였습니다.
| void CSerialPort::SendCommand(CString strCmd, CString strData) { BYTE sendData[FRAMESIZE]; // FrameSize만큼의 바이트 배열 선언
} sendData[0] = STX; // Frame의 첫번째 바이트에 STX 입력 sendData[FRAMESIZE - 2] = GetCheckSum8(sendData, 0, FRAMESIZE - 2); // Frame의 마지막에서 2번째 바이트에 CheckSum 입력 sendData[FRAMESIZE - 1] = ETX; // Frame의 마지막 바이트에 ETX 입력 |
|---|
| (인자) CString strCmd (인자) CString strData (반환값) 없음 |
수신 데이터의 구조 변경은 INT CSerialPort::ReadThreadProc(LPVOID lParam) 코드를 수정해야 합니다.
2. CSerialPort Class Interface (기능과 내부인자)
시리얼(Serial) 통신에 관련된 모든 기능을 담당하는 CSerialPort 클래스로서 세부 내용은 다음과 같이 이루어져 있습니다.
| 시리얼 통신을 위한 포트 오픈(Open) bool CSerialPort::OpenPort(int Port, DWORD Baud) |
|---|
| (인자) int Port : 포트 번호 1부터 시작 (인자) DWORD Baud : 통신 속도 (반환값) bool : 포트 정상 오픈(Open)이면 True이고 실패하면 False 반환 |
| 포트 사용을 마치고 포트를 닫음 void CSerialPort::ClosePort() |
|---|
| (인자) 없음 (반환값) 없음 |
| 데이터를 송신할 때 사용 void CSerialPort::WritePort(BYTE* pData, DWORD dwWrite) |
|---|
| (인자) BYTE* pData : 송신 데이터 (인자) DWORD dwWrite : 송신 데이터의 사이즈 (반환값) 없음 |
| 데이터를 수신할 때 사용 DWORD CSerialPort::ReadPort (BYTE* pBuffer, DWORD dwR) |
|---|
| (인자) BYTE* pBuffer : 수신 데이터 (인자) DWORD dwR : 수신 블록 사이즈 (반환값) DWORD |
| 통신 버퍼의 초기화에 사용. 포트를 비울 때 사용 void CSerialPort::ResetPort() |
|---|
| (인자) 없음 (반환값) 없음 |
| 포트를 셋팅할 때 사용 BOOL CSerialPort::ConfigPort(DWORD Baud, int ByteSize, int Parity, int StopBits) m_DCB(구조체)의 변수들을 초기화함 |
|---|
| (인자) DWORD Baud : 통신속도 (인자) int ByteSize : 바이트 크기 (인자) int Parity : 패리티 비트 (인자) int StopBits : 스탑 비트 (반환값) bool : 포트 관련 설정 이후 SetCommState()가 정상 동작하는 경우 TRUE를 반환 |
| 모뎀 제어신호를 감시하거나 상태를 확인 BYTE CSerialPort::GetModemStatus() |
|---|
| (인자) 없음 (반환값) BYTE : 모뎀의 제어 레지스터 값의 상태를 반환 |
| 수신 스레드(Thread) 수신된 데이터의 처리를 담당하는 스레드로 프레임 구조 및 데이터의 Parsing 처리와 수신된 데이터에 따른 모든 처리를 하고 있는 함수로 제작하고자 하시는 프로그램에 맞게 변경하여 처리하시면 됩니다. UINT CSerialPort::ReadThreadProc(LPVOID lParam) |
|---|
| (인자) LPVOID lParam (반환값) UINT |
| 포트 오픈하고 수신 스레드 생성 bool CSerialPort::ReadStart(int Port, DWORD Baud) |
|---|
| (인자) int Port : 포트 번호 (인자) DWORD Baud : 통신 속도 (반환값) bool : 수신스레드가 정상 생성되면 TRUE 반환, 정상 생성 안되면 FALSE |
| 포트닫는 기능 수행. ClosePort()를 호출 void CSerialPort::ReadStop() |
|---|
| (인자) 없음 (반환값) 없음 |
| 수신된 데이터가 에러가 발생하였는지 확인 BYTE CSerialPort::GetCheckSum8(BYTE *pByte, int iStart, int iLen) |
|---|
| (인자) BYTE *pByte : 송신 데이터 (인자) int iStart : 시작 위치 0 (인자) int iLen : 프레임 사이즈. FRAMESIZE -2 (반환값) BYTE |
3. CQueue Class Interface (기능과 내부인자)
큐 데이터를 관리하는 CQueue 클래스(Class).
Queue에서 알아야 할 중요한 함수는 Init(큐의 사이즈 지정 및 초기화), Insert(EnQueue. 큐에 데이터 삽입), Extract(DeQueue. 큐에서 데이터 추출)입니다.
| Init : 큐의 사이즈를 정하고 메모리를 할당 void init(int size) |
|---|
| (인자) int size : Queue의size(프레임 개수)를 정하고 메모리 초기화. 예를 들어 100으로 지정하는 경우 100개의 프레임 사이즈를 정하고 초기화합니다. (반환값) 없음 |
| EnQueue : 큐에 데이터 삽입 bool insert(const T& val) |
|---|
| (인자) const T& val : 큐에 입력하는 데이터 (반환값) BOOL : 큐에 데이터가 정상 삽입되는 경우 TRUE 반환 |
| DeQueue : 큐에서 데이터 추출 bool extract(T* val) |
|---|
| (인자) T* val : 큐에서 추출하는 데이터 (반환값) BOOL : 큐에서 데이터가 정상 추출되는 경우 TRUE 반환 |
큐에 데이터 삽입 예제
////////////////////////////// CQueueDlg.h에서 //////////////////////////////
private:
CQueue
////////////////////////////// CQueueDlg.cpp에서 //////////////////////////////
m_Queue1.init(500); // 초기화, Queue의 size를 정하고 메모리 할당
// EnQueue. 삽입
void CCQueueDlg::OnBnClickedButton1()
{
dwRead =3;
BYTE* pEnQueueData; // BYTE *형 변수 선언
pEnQueueData = new BYTE[dwRead];
memset(pEnQueueData, 0, dwRead); // pEnQueueData를 dwRead 사이즈 0으로 초기화
pEnQueueData[0] = 'a';
pEnQueueData[1] = 'b';
pEnQueueData[2] = 'c';
m_Queue1.insert(pEnQueueData); // 큐에 데이터 프레임(‘abc’) 삽입
////////////////////////////// CQueue.h에서 //////////////////////////////
// 큐에 데이터 삽입
bool insert(const T& val)
{
EnterCriticalSection(&cs);
//Full인 경우는 false를 return
if(nNum+1 > nSize)
{
return false;
nNum++;
Rear → Next → value = val; // val를 큐의 마지막(Rear)에 삽입
Rear → Next = Rear → Next → Next;
LeaveCriticalSection(&cs);
return true;
큐에서 데이터 추출 예제
////////////////////////////// CQueueDlg.cpp 에서//////////////////////////////
// DeQueue.
void CCQueueDlg::OnBnClickedButton2()
{
TCHAR recData[1024];
if(m_Queue1.extract(&recRaw) == true)
{
AsciiToUnicode((LPCSTR)recRaw, recData); // ASCII를 UniCode로 변경
m_RecList.AddString(recData); // 데이터 ListBox에 추가
}
////////////////////////////// CQueue.h에서 //////////////////////////////
// 큐에 데이터를 추출
bool extract(T* val)
{
// 자료가 없는 경우는 false를 return
if(nNum==0)
{
LeaveCriticalSection(&cs); // 크리티컬 섹션 탈출
return false;
nNum--;
*val = Front → Next → value; // 큐의 맨 앞(Front)를 *val에 저장
Front->Next = Front → Next → Next;
LeaveCriticalSection(&cs);
return true;
4. 포트 설정 방법
| 포트 Open 설명 bool CSerialPort::OpenPort(int Port, DWORD Baud) { // 포트 생성
} m_hCom = CreateFile(m_strPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); // 통신 장치에 모니터링할 이벤트 세트를 지정 SetCommMask( m_hCom, EV_RXCHAR); // InQueue, OutQueue 크기 설정 SetupComm( m_hCom, 1024, 1024); // 포트 비우기. PurgeComm() 호출 ResetPort(); // timeout 설정 timeouts.ReadIntervalTimeout = 500; // 다음 문자를 받기까지 기다리는 시간 timeouts.ReadTotalTimeoutMultiplier = 0; // 읽은 문자 수에 비례하여 기다리는 시간 늘여줌 timeouts.ReadTotalTimeoutConstant = 100; // 읽기에서 총 기다리는 시간을 밀리초로 설정 timeouts.WriteTotalTimeoutMultiplier = 10; // 쓰기에서 문자 수에 비례하여 기다리는 시간 늘여줌 timeouts.WriteTotalTimeoutConstant = 0; // 쓰기에서 총 기다리는 시간을 밀리초로 설정 |
|---|
| (인자) int Port : 포트 번호 (인자) DWORD Baud : 통신속도 (반환값) bool : 정상적으로 포트가 오픈되면 TRUE |
| 지정된 통신 장치의 통신 매개변수를 초기화 SetupComm( HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue) |
|---|
| (인자) HANDLE hFile : 통신장치의 핸들, CreateFile 함수의 리턴된 핸들 (인자) DWORD dwInQueue : 장치의 내부 입력 버퍼크기를 byte 단위로 지정 (인자) DWORD dwOutQueue : 장치의 내부 출력 버퍼크기를 byte 단위로 지정 (반환값) Bool은 정상적으로 매개변수의 초기화가 되면 TRUE 반환 |
| 지정된 통신 장치의 통신 매개변수를 초기화 SetupComm( HANDLE hFile, DWORD dwInQueue, DWORD dwOutQueue) |
|---|
| (인자) HANDLE hFile : 통신 장치의 핸들, CreateFile 함수의 리턴된 핸들 (인자) DWORD dwInQueue : 장치의 내부 입력 버퍼크기를 byte 단위로 지정 (인자) DWORD dwOutQueue : 장치의 내부 출력 버퍼크기를 byte 단위로 지정 (반환값) Bool은 정상적으로 매개변수의 초기화가 되면 TRUE 반환 |
| 포트 비우기 PurgeComm( m_hCom,PURGE_TXABORT | PURGE_TXCLEAR | PURGE_RXABORT | PURGE_RXCLEAR); |
|---|
| (인자) m_hCom는 통신 자원의 핸들, CreateFile 함수의 리턴된 핸들 (인자) PURGE_TXABORT : 모든 미해결된 Overlapped 쓰기 작업을 종료하고 쓰기 작업이 완료가 되지 않았더라도 즉시 반환 (인자) PURGE_TXCLEAR : 출력 버퍼를 클리어 (인자) PURGE_RXABORT : 입력 버퍼를 클리어 (인자) PURGE_RXCLEAR : 모든 미해결된 Overlapped 읽기 작업을 종료하고 읽기 작업이 완료가 되지 않았더라도 즉시 반환 |
5. 수신 스레드(Thread)
| 수신 스레드(Thread) 수신된 데이터의 처리를 담당하는 스레드로 프레임 구조 및 데이터의 Parsing 처리와 수신된 데이터에 따른 모든 처리를 하고 있는 함수로 제작하고자 하시는 프로그램에 맞게 변경하여 처리하시면 됩니다. UINT CSerialPort::ReadThreadProc(LPVOID lParam) |
|---|
| (인자) LPVOID lParam (반환값) UINT |
| // 수신 스레드(ReadThreadProc) 설명 UINT CSerialPort::ReadThreadProc(LPVOID lParam) { // 포트를 감시하는 루프
} while (pSerial->m_bOpen) { // COM 포트에서 메시지가 들어오면 리턴하는 함수
}// 메시지가 안들어 오면 무한 블러킹 WaitCommEvent(pSerial->m_hCom, &dwEvent, NULL); // 데이터가 수신되었다는 메시지가 발생하면 if ((dwEvent & EV_RXCHAR) == EV_RXCHAR) { // 데이터 수신부
} if(dwRead = pSerial->ReadPort(lnData, pSerial->m_nMaxBlock)) //*************[ Parsing - Start Code ] *************// //*************[ Parsing - End Code ] *************/ |
|---|
| COM(m_hComPort)에서 메시지가 들어오면 리턴하는 함수. 메시지가 안 들어오면 무한 블러킹 Bool WaitCommEvent( HANDLE hFile, LPDWORD lpEvtMask, LPOVERLAPPED lpOverlapped ); |
|---|
| (인자) HANDLE hFile : 장치 핸들. CreateFile 함수에서 리턴한 핸들 (인자) LPDWORD lpEvtMask : 수신 이벤트 받을 포인터 (인자) LPOVERLAPPED lpOverlapped : 중첩 구조체 포인터. 에러 시 0값 (반환값) BOOL |
| 데이터 수신을 담당 pSerial → ReadPort(lnData, pSerial → m_nMaxBlock) |
|---|
| (인자) lnData : 수신 측에 들어오는 데이터 (인자) pSerial → m_nMaxBlock 블록의 사이즈 (반환값) DWORD는 읽은 데이터의 사이즈 |




