-
윈도우 콘솔 시리얼 통신개발환경 구축 2023. 2. 20. 00:51
VScode로 AVR 개발환경을 만들고 보니, 시리얼 모니터가 아쉽다. 시리얼 모니터를 사용하자고 아두이노 IDE을 열자니, 2.0으로 올라오면서 은근 여는데 시간이 오래 걸린다. 윈도우에서도 리눅스처럼 콘솔로 간단하게 시리얼 출력을 볼 수 있으면 좋겠다고 생각을 해서 처음으로 window 프로그램을 코딩해보았다.
https://playground.arduino.cc/Interfacing/CPPWindows/
Arduino Playground - CPPWindows
Interfacing... Arduino and C++ (for Windows) As I found it pretty hard finding the good information, or an already working code to handle Serial communication on windows based system, I finally made a class that do what is needed for basic Serial Communica
playground.arduino.cc
[MFC] Serial 통신에 필요한 DCB 구조체
다음은 winbase.h 에 내장된 DCB 구조체로써 시리얼 통신 환경을 설정하는데 필요한 구조체이다...
blog.naver.com
여기를 많이 참고했다.
실행시 전달 인자 설명 -p COMn COM 포트 지정 -b (baud rate) 보율 설정(기본값 9600) -B (byte size) 한번에 주고 받을 1바이트의 크기(기본값 8)* -Sn 0: 1 stop bit(기본값)
1: 1.5 stop bit
2: 2 stop bit-Pn 0: 패리티 없음** (기본값)
1: 홀수 패리티
2: 짝수 패리티
3: 마크 패리티
4: 스페이스 패리티-Dn 0:장치를 열거나 DTR이 on 되려할 때 DTR을 off 한다.
1:장치를 열거나 DTR이 off 되려할 때 DTR을 on 한다.(기본값)
2:DTR handshaking을 사용한다.*아두이노는 8비트 마이크로컨트롤러이니 한 번에 8비트를 처리할 수 있음, 만약 16비트 프로세서면, 한번에 2바이트 즉 16비트를 전송하는 것이 가능함
**패리티 비트: 데이터가 제대로 전송되었는지 확인을 위해 추가로 보내는 비트, 홀수 패리티일 경우 1의 갯수가 홀수가 되도록, 짝수면 짝수가 되도록 8비트를 보내고 추가로 1비트를 더 전송한다. 예를 들어 0b10101110을 전송한다면 1의 갯수가 5개 이니 홀수 패리티 규칙인 경우 0을, 짝수 패리티 규칙인 경우 1을 추가로 전송한다. 마크 패리티는 패리티 비트가 항상 1, 스페이스는 항상 0이다.
***Data Terminal Ready: 통신버퍼가 오버플로우 되지 않도록 전송속도를 조절하는 흐름제어 방식이다.
https://github.com/sidreco214/winserial
GitHub - sidreco214/winserial: Simple Serial Terminal for Windiw Console
Simple Serial Terminal for Windiw Console. Contribute to sidreco214/winserial development by creating an account on GitHub.
github.com
WinSerial.h
/* 참고 https://playground.arduino.cc/Interfacing/CPPWindows/ */ #ifndef WINSERIAL #define WINSERIAL #define ARDUINO_WAIT_TIME 2000 //아두이노 보드 리셋되는 시간 #include <windows.h> #include <stdio.h> #include <stdlib.h> class WinSerial { private: bool connection; //COMPORT 연결 상태 HANDLE hWinSerial; //WinSerial Handler COMSTAT status; //COMPORT에 대한 여러가지 상태 출력 DWORD errors; //최근 에러를 저장 public: WinSerial(const char* COMPort, const unsigned int& baud, const int& ByteSize, const int& StopBit, const int& Parity, const int& DTR); ~WinSerial(); bool connected(); int read(char* buffer, unsigned int buf_size); //시리얼 통신으로 읽어온 값을 버퍼에 저장후 읽은 바이트 수를 리턴, 읽을게 없으면 0리턴 bool send(const char* buffer, unsigned int buf_size); //입력된 값을 시리얼 통신으로 출력, 성공하면 1출력 }; #endif
WinSerial.cpp
#include "WinSerial.h" WinSerial::WinSerial(const char* COMPort, const unsigned int& baud, const int& ByteSize, const int& StopBit, const int& Parity, const int& DTR) : connection(false) { hWinSerial = CreateFile(COMPort, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL ); //시리얼 연결 확인 if(hWinSerial == INVALID_HANDLE_VALUE) { if(GetLastError() == ERROR_FILE_NOT_FOUND) { printf("ERROR: Handle was not attached. Reason: %s is not available.\n", COMPort); } else printf("ERROR!!"); } else { //DCB 구조체 설정, 기본적으로 0이어야하는 게 있기 때문에 구조체 초기화 DCB dcbSerialParams = {0}; //현 상태 확인 if(!GetCommState(hWinSerial, &dcbSerialParams)) { printf("failed to get current serial parameters!"); } else { dcbSerialParams.BaudRate = baud; dcbSerialParams.ByteSize = ByteSize; dcbSerialParams.StopBits = StopBit; dcbSerialParams.Parity = Parity; dcbSerialParams.fDtrControl = DTR; if(SetCommState(hWinSerial, &dcbSerialParams)) { //설정 저장한 구조체가 제대로 전달됨 connection = true; PurgeComm(hWinSerial, PURGE_RXCLEAR | PURGE_TXCLEAR); //버퍼 비우기 Sleep(ARDUINO_WAIT_TIME); //아두이노 보드가 리셋될때 까지 기다리기 } else { //구조체 전달 실패 printf("ALERT: Could not set Serial Port parameters"); } } } } WinSerial::~WinSerial() { if(connection) { connection = false; CloseHandle(hWinSerial); } } bool WinSerial::connected() { return connection; } int WinSerial::read(char* buffer, unsigned int buf_size) { DWORD bytesRead; unsigned int num; //읽을 데이터 수, 오버플로우 방지 ClearCommError(hWinSerial, &(errors), &(status)); //COM포트 상태 읽기 //읽을 게 있으면 if(status.cbInQue > 0) { if(status.cbInQue > buf_size-1) num = buf_size-1; //배열 인덱스는 배열크기-1 까지 else num = status.cbInQue; if(ReadFile(hWinSerial, buffer, num, &bytesRead, NULL)) return bytesRead; } //읽어올게 없는 경우, '0'을 int로 바꾸면 0이 아니니 이래도 괜찮음 return 0; } bool WinSerial::send(const char* buffer, unsigned int buf_size) { DWORD bytesSend; if(WriteFile(hWinSerial, (void*)buffer, buf_size-1, &bytesSend, 0)) return true; else { ClearCommError(hWinSerial, &(errors), &(status)); return false; } }
main.cpp
/* 참고 https://playground.arduino.cc/Interfacing/CPPWindows/ */ #include <stdio.h> #include <string> using std::string; #include "src/WinSerial.h" #define BUF_LENGTH 256 const string Arg[] = {"-p", //COMPORT "-b", //Baud Rate "-B", //Byte Size "-S0", "-S1", "-S2", //Stop Bit "-P0", "-P1", "-P2", "-P3", "-P4", //Parity "-D0", "-D1", "-D2", //DTR }; enum _arg { p = 0, b, B, S0, S1, S2, P0, P1, P2, P3, P4, D0, D1, D2 }; int main(int arc, char* argv[]) { char* comport = argv[0]; //printf("arc: %d\n",arc); //printf("%s\n",comport); unsigned int baud = 9600, ByteSize = 8, StopBit = ONESTOPBIT, Parity = NOPARITY, DTR = DTR_CONTROL_ENABLE; //argv 의 첫 문자열은 winserial.exe, arc는 배열 원소 갯수이니 //그래서 1번 인덱스 부터 arc-1 인덱스까지 검색 for(int i=1; i<arc; i++) { int arg = -1; for(int j=0; j<sizeof(Arg)/sizeof(Arg[0]); j++) { if(Arg[j] == argv[i]) {arg = j; break;} } switch(arg) { default: break; case p: comport = argv[++i]; break; case b: baud = atoi(argv[++i]); break; case B: ByteSize = atoi(argv[++i]); break; case S0: StopBit = ONESTOPBIT; break; case S1: StopBit = ONE5STOPBITS; break; case S2: StopBit = TWOSTOPBITS; break; case P0: Parity = NOPARITY; break; case P1: Parity = ODDPARITY; break; case P2: Parity = EVENPARITY; break; case P3: Parity = MARKPARITY; break; case P4: Parity = SPACEPARITY; break; case D0: DTR = DTR_CONTROL_DISABLE; break; case D1: DTR = DTR_CONTROL_ENABLE; break; case D2: DTR = DTR_CONTROL_HANDSHAKE; break; } } //comport가 지정되지 않은 경우 if(string(comport) == argv[0]) {printf("ERROR: COMPORT must be assigned!"); return 0;} //printf("TEST Baud Rate: %d, Byte Size: %d\n%s is connected\n", baud, ByteSize, comport); WinSerial Serial(comport, baud, ByteSize, StopBit, Parity, DTR); char buffer[BUF_LENGTH] = ""; int readResult = 0; if(Serial.connected()) { printf("Baud Rate: %d, Byte Size: %d\n%s is connected\n", baud, ByteSize, comport); while(1) { readResult = Serial.read(buffer, BUF_LENGTH); buffer[readResult] = 0; //저장한 데이터 바로 뒤의 바이트를 초기화 printf("%s", buffer); } } return 0; }
2023.02.24 argument bug fix
Makefile
#사용할 컴파일러 CC = g++ #컴파일러에 전달할 argument CFLAGS = -O2 -Wall -std=c++17 #소스+헤더파일 경로 SOURCEDIR = src #빌드시 나올 오브젝트 파일과 실행 파일 저장위치 BUILDDIR = build #실행파일 이름 EXECUTABLE = serial.exe #실행파일 실행시 전달할 argument, 아무것도 없는 경우 RUNFLAG = RUNFLAG = -p COM3 #main 함수가 정의된 파일 이름 MAINSRC = main.cpp MAINFOBJ = $(BUILDDIR)/$(patsubst %.cpp,%.o,$(MAINSRC)) #patsubst = pattern substitude SOURCES = $(wildcard $(SOURCEDIR)/*.cpp) OBJECTS = $(patsubst $(SOURCEDIR)/%.cpp,$(BUILDDIR)/%.o,$(SOURCES)) #그냥 make 하면 make all로 인식됨 all: dir $(BUILDDIR)/$(EXECUTABLE) #build 폴더가 없을 때만 폴더를 생성하도록 함 dir: $(BUILDDIR) $(BUILDDIR): mkdir -p $(BUILDDIR) #링크 과정 $(BUILDDIR)/$(EXECUTABLE): $(OBJECTS) $(MAINFOBJ) $(CC) $^ -o $@ #소스파일 컴파일, 소스파일 안에 include로 헤더를 포함하기 때문에 #소스파일만 컴파일 하면 됨, 다만 해더파일이 변경되었을 때도 새로 컴파일 하도록 종속성에 추가함 $(OBJECTS): $(SOURCES) $(patsubst %.cpp,%.h,$(SOURCES)) $(CC) $< -c -o $@ $(CFLAGS) #메인 함수있는 파일 컴파일 $(MAINFOBJ): $(MAINSRC) $(CC) $(MAINSRC) -c -o $@ $(CFLAGS) #build 폴더내 오브젝트 파일과 실행파일 비우기 clean: rm -f $(BUILDDIR)/*o $(BUILDDIR)/$(EXECUTABLE) #프로그램 실행 run: $(BUILDDIR)/$(EXECUTABLE) $(RUNFLAG)
make 파일은 자동으로 종속성이 추가되도록 만들어 놔서 다른 프로젝트에서도 그대로 복붙해서 쓸 수 있다.
사용은 실행파일 위치를 윈도우 환경변수에 추가하거나, avr-gcc 컴파일러 있는 위치에 복붙한 뒤 아래 명령어를 콘솔창에 입력하면 된다.(VScode인 경우 ctrl + `(탭 위에 있음)으로 쉽게 콘솔창을 열 수 있다.)
serial -p COM3 -b 9600
시리얼 연결이 끊기면 자동으로 프로그램이 종료되도록 할 수 있을 텐데, connected() 매서드가 리턴전에 현재 포트 상태를 확인하도록 할 방법을 모르겠다. ClearCommError 함수로 상태 핸들에 저장시키는건 알겠는데, 구조체에 어떤 변수가 무슨 값이어야 연결이 끊긴건지를 모른다.
참고로 터미널에서 프로세스가 실행 중일때 ctrl+c를 입력하면 화면상에서는 ^C가 입력되면서 종료된다.(git 콘솔에서는 안보인다.)
그리고 VScode git 콘솔에서는 제대로 출력되는데, 그냥 git Bash에서 사용해서는 출력이 안보이다가, ctrl+c를 눌러서 종료하면 한번에 출력을 보여준다. (git 콘솔에서는 hello 예제를 실행할 때 endl대신 \n을 사용하면 제대로 안보이는 것으로 보아 버퍼를 비워줘야 출력이 되는 것 같다.)
'개발환경 구축' 카테고리의 다른 글
[WSL]윈도우에서 리눅스 시스템 사용하기 (2) 2023.12.22 ESP-IDF VScode Window 개발환경 구축 (0) 2023.06.28 VScode 라즈베리파이 피코 Windows C/C++ 개발환경 구축 (0) 2023.02.20 VScode AVR 개발환경 구축 (0) 2023.02.16 윈도우 C/C++ 개발 환경 구축 (0) 2023.02.14