Sun넘었조 - 차량 운전자 눈부심 방지 선바이저 로봇
| Anti-Glare Sunvisor Robot for Vehicle Driver | ||
|---|---|---|
| Sun넘었조 | ||
| | ||
| 학교 | 서울시립대학교 | |
| 학과 | 기계정보공학과 | |
| 학번 및 성명 | 20204300** | 최*현(팀장) |
| 20194300** | 이*현 | |
| 20204300** | 오*택 | |
| 20204300** | 조*호 | |
| 20204300** | 조*규 | |
프로젝트 개요
프로젝트 요약
완성된 이미지
본 프로젝트는 운전 도중 태양, 밝은 조명 등으로 인해 발생할 수 있는 눈부심(일명 Glare)을 자동으로 방지해줄 수 있는 선바이저 로봇을 제작하는 것을 목표로 하였다. 본 프로젝트에서 제안하는 선바이저 로봇은 운전 중에 차량의 앞 유리를 통해 들어오는 강한 Glare를 인식한다. 이후 자동으로 선바이저가 눈부심을 가릴 수 있는 위치로 이동하여, 사용자의 전방 시야를 확보하고 사고를 예방할 수 있다.
프로젝트 배경 및 기대효과
배경
강한 햇빛, 조명 등과 같이 운전 중에 발생하게 되는 강한 눈부심은 운전자의 시야를 일시적으로 방해하게 되는데, 이는 교통사고로 이어질 수 있는 심각한 안전 문제이다. 실제로 운전자가 돌발적인 눈부심을 겪게 되면 순간적으로 전방 차량 및 보행자가 시야에서 사라지거나 신호를 놓치게 되는 등 사고 발생의 위험이 증가한다. 현재로서는 위와 같은 문제 상황에 대해서, 운전자가 눈부심을 인지한 뒤 선바이저를 수동으로 조작하거나, 선글라스를 착용하는 방식으로 대응하고 있으나, 이는 운전 중 손을 핸들에서 떼거나 시선이 흐트러지는 등 주행 안전성을 저해하는 요소로 작용한다. 기존의 선바이저나 고정형 선팅 필름만으로는 해당 문제 상황에 능동적으로 대응하기 어렵고, 빠르게 발전하는 차량 기술에 비해, 눈부심 방지 기술은 여전히 수동적인 방식에 머물러 있는 것이 현실이다. 따라서 본 프로젝트는 운전자의 별도 조작 없이, 실시간으로 Glare를 인식하고, 자동으로 운전자의 시야를 확보할 수 있는 선바이저 로봇을 제안하고자 한다.
기대 효과
자동 선바이저를 사용하면 운전 중 Glare에 의한 눈부심으로 인해 발생할 수 있는 돌발 상황을 예방할 수 있다. 또한 운전자로 하여금 선바이저 작동에 있어 별도의 조작을 필요로 하지 않기 때문에 안전성 측면에서 효과를 기대할 수 있다. 따라서 본 시스템을 사용하게 되면 별도의 운전자의 조작이 필요하지 않다는 점, Glare에 반응하여 상하좌우 모두 유동적으로 시야를 확보할 수 있다. 또한 블랙박스와의 연동 가능성뿐만 아니라, 차량 내부 옵션으로의 발전할 수 있다는 확장성도 가지고 있다.
프로젝트 개발 목표
경제성
- 재료비
- 불필요한 고사양 부품 사용을 지양하며, 오픈 소스 하드웨어 및 소프트웨어를 적극적으로 활용하고, 각 부품(모터, 구동장치, 제어어장치 등) 선정 시 요구 성능을 만족하면서 전체 시스템 구축 비용을 개발 소요 비용 목표치(500천원) 내에서 해결할 수 있도록 시스템을 구축하였다.
기능성
- 정확성
- 본 시스템은 Glare의 존재 유무에 따라 선바이저의 작동 여부가 결정되고, Glare의 위치에 따라 선바이저가 목표 위치로 이동하는 기능을 갖는다. 따라서 Glare의 존재 유무 및 Glare의 위치를 정확하게 특정할 수 있어야 한다.
- 실시간성
- 본 시스템은 차량 운전 상황에서 동적으로 변화하는 Glare의 위치에 맞게 신속하게 대응할 수 있어야 한다.
안정성
- 구조안정성
- 본 시스템은 차량 주행 중 발생하는 진동 환경에서도 안정적으로 작동할 수 있도록,각 부품의 내구성을 고려하여 설계되어야 한다. 특히 반복적인 선바이저 구동 상황에서도 성능의 저하나 물리적 손상없이 지속적인 작동이 가능하도록 해야한다.
- 유지보수성
- 본 시스템은 제어부와 구동부로 핵심 구성 요소들을 독립적인 모듈 단위로 설계함으로써, 특정 부분에 문제가 발생했을 경우 해당 모듈만 쉽게 분리하여 진단 및 교체가 가능할 수 있도록 하여 유지보수의 편의성을 지니게 하고자 한다.
- 또한, 시스템 전체의 물리적 구조와 배선을 최대한 단순화함으로써, 고장이 발생할 수 있는 잠재적 요소를 줄이며, 문제 발생 시 원인 파악과 해당 부품 접근이 용이하도록 구성하고자 한다.
프로젝트 개요
프로젝트 요약
프로젝트의 배경 및 기대효과
프로젝트 개발 목표
동작 시나리오
본 프로젝트에서 제안하는 시스템은 다음과 같은 4가지 운전 상황에 대해 선바이저 로봇이 각각의 상황에 맞는 동작을 수행하도록 설계되었다.
- Case 1. 전방에 Glare가 존재하지 않는 경우
- 선바이저 로봇은 전방에 Glare가 존재하지 않을 경우,
- → 기본 상태인 접힌 형태를 유지한다.
- Case 2. 전방에 Glare가 존재하는 경우
- 선바이저 로봇이 전방의 Glare를 인식하게 될 경우,
- → 피에조 부저(Piezo Buzzer)를 통해 운전자에게 경고음 알림을 제공하고,
- → 선바이저가 펼쳐진다.
- → Glare 위치에 해당하는 Grid 좌표로 선바이저 로봇이 이동하여 Glare를 차단한다.
- Case 3. Glare의 위치가 이동하는 경우
- 차량 주행 중 Glare의 위치가 변화하는 경우,
- → 선바이저 로봇은 Glare의 움직임을 실시간 추적하고,
- → 현재 위치에 해당하는 Grid 좌표로 계속해서 이동하며 Glare를 차단한다.
- Case 4. 전방의 Glare가 사라진 경우
- 전방에 존재하던 Glare가 사라질 경우,
- → 피에조 부저를 통해 운전자에게 알림을 준 뒤,
- → 선바이저가 접힌 상태로 복귀한다.
구현 내용
하드웨어 설계 및 구현
시스템 개략도
회로부
회로부는 크게 전원부, 제어부, 출력부로 구성된다. 전원부는 외부 전원 제어 장치(차량용 시거잭 인버터), 내부 전원 제어 장치(SMPS)로 구성되며, 제어부는 라즈베리파이와, 아두이노로 구성되어 Glare detection 및 좌표 변환을 담당한다. 또한, 출력부는 피에조 부저 및 모터로 구성되며, 제어부의 신호를 바탕으로 선바이저를 구동시키게 된다.
- 전원부
- - 외부 전원 제어 장치(차량용 시거잭 인버터)
- 차량의 12V DC 전원을 AC 전원처럼 사용 할 수 있도록 변환해 주는 장치로, 이를 통해 외부 배터리 없이도 라즈베리파이 및 아두이노 기반 구동부에 안정적인 전원 공급을 가능하게 한다.또한 장시간 운행시에도 지속적으로 시스템이 작동할 수 있도록 하며, 별도의 충전이나 전력 소모 걱정 없이 차량 자체의 전원으로 구동이 가능하다.
- - 내부 전원 제어 장치(SMPS)
- 콘센트용 케이블을 통해 차량용 시거잭 인버터와 연결 되며,인버터로 받아온 차량의 AC전원을 다시 DC 전원으로 변환해 준다. 사용 예상 전력을 토대로, 프로젝트에서 사용한 SMPS의 사양(12V 10A)을 선택했다. SMPS에서 나온 전력은 전체 내부 시스템(라즈베리파이5, 아두이노 메가, 모터 등)의 전력을 담당한다.
- 제어부
- - 라즈베리파이 카메라 모듈 V3
- 차량 내부에 고정되어, 차량 전방의 영상을 라즈베리파이로 전달한다.
- - 라즈베리파이 5
- 카메라로부터 영상 입력을 수신하여 OpenCV 기반으로 Glare 인식 및 위치를 특정하고, 좌표 변환 모듈을 통해 카메라 내의 Glare 위치 좌표를 선바이저의 그리드 좌표로 변환한다. 이때 Glare 유무, 지속시간 등을 판단하여 최종 제어 명령을 생산하여 아두이노로 전달한다.
- - 아두이노 메가
- 라즈베리파이와의 시리얼 통신을 통해 제어 명령을 수신하여, 수신된 명령을 해석하고 연결된 모터 드라이버에 정밀한 제어 신호를 출력한다. 즉, 실제 모터를 구동하고 선바이저의 물리적 위치를 제어한다. 라즈베리파이와 아두이노 간의 통신은 UART 기반의 유선 시리얼 통신 방식을 사용한다. 데이터 전송 속도는 시스템의 요구 반응 속도를 고려하여 115200 bps로 설정하였다. 안정성을 고려하여 전송 속도를 늦출 수도 있었으나 안정성 문제는 발견되지 않아 높은 전송 속도를 채택하였다.
- - 모터 드라이버
- 아두이노 메가로부터 받은 명령을 기반으로 스텝모터를 제어한다. 스텝모터의 방향 제어를 수행하며, 이를 통해 로봇이 레일 위를 정확하게 이동하도록 한다. 프로젝트에서 사용한 드라이버는 마이크로 스테핑 기능을 내장하는데, 이는 분주비를 조절하여 정밀한 제어를 가능하게 하는 방법이다. 소음을 고려하고자 마이크로 스테핑 기능을 사용하고자 했으나, 분주비의 증가로 모터의 발열에 큰 영향을 미치게 되어, 기능 사용을 철회했다.
소프트웨어 설계 및 구현
좌표 변환
이 모듈은 위 Glare Detection 모듈을 통해 2차원 카메라 이미지 평면에서 추출된 Glare의 픽셀 좌표를, 3차원 월드 좌표계에서의 기하학적 분석을 통해 최종적으로 아두이노가 이해할 수 있는 이산적인 3x3 그리드 인덱스로 변환한다. 이 과정의 정확성은 시스템 전체의 Glare 차단 성능과 직결되므로, 정밀한 수학적 모델링과 구현이 요구된다. 성능 최적화 및 시스템 통합을 고려하여 전체 로직은 C++와 OpenCV 라이브러리를 사용하여 작성하였다.
OpenCV 내장 함수를 사용한 렌즈 왜곡 보정
카메라 렌즈로 인해 발생하는 이미지의 방사 왜곡(radial distortion) 및 접선 왜곡(tangential distortion)은 픽셀 위치의 정확도를 저해하는 주요 요인이다. 특히, 이미지 중심부에서 멀어질수록 왜곡의 정도가 심해져, 3D 공간으로의 역투영 시 큰 오차를 유발한다. 따라서, 입력된 Glare의 중심 픽셀 좌표는 가장 먼저 렌즈 왜곡 보정 과정을 거친다.
- undistortPoints
- 이 보정 과정은 OpenCV 라이브러리에서 제공하는 cv::undistortPoints 함수를 통해 수행된다. 이 함수는 사전에 카메라 캘리브레이션(Camera Calibration)을 통해 획득한 카메라 고유의 내부 파라미터 행렬(Intrinsic Matrix, K)과 렌즈 왜곡 계수(Distortion Coefficients, D)를 사용한다.
- 카메라 내부 파라미터 행렬 (K)
- fx, fy : 렌즈의 초점 거리를 픽셀 단위로 표현한 값.
- cx, cy : 이미지의 주점(Principal Point), 즉 렌즈의 광학축이 이미지 센서와 만나는 점의 픽셀 좌표.
- 왜곡 계수 (D)
- D = [k1, k2, p1, p2, k3, …]
- k1, k2, k3 등은 방사 왜곡을, p1, p2는 접선 왜곡을 모델링하는 계수이다.
- cv::undistortPoints 함수는 입력된 왜곡된 2D 포인트를 이 파라미터들을 이용하여, 왜곡이 제거된 정규화된 카메라 좌표계(normalized camera coordinates)의 포인트로 변환한다. 이 정규화된 좌표는 카메라 중심을 원점으로 하고 초점 거리를 1로 가정한 평면에서의 좌표이다.
- 이후 함수에 카메라 행렬을 인자로 전달함으로써, 정규화된 좌표를 다시 우리가 사용하는 픽셀 좌표계로 재투영하여 정확한 Glare 중심 좌표를 얻는다. 이 과정은 다음과 같은 C++ 코드로 구현되었다.
cv::Mat sun_center_mat(1, 1, CV_64FC2);
sun_center_mat.atcv::Vec2d(0, 0)[0] = sun_center.first;
sun_center_mat.atcv::Vec2d(0, 0)[1] = sun_center.second;
cv::Mat undistorted_points_mat;
cv::undistortPoints(
sun_center_mat, // 입력: 왜곡된 픽셀 좌표
undistorted_points_mat, // 출력: 보정된 좌표를 담을 Mat
DEFAULT_CAMERA_MATRIX, // 카메라 내부 파라미터 행렬 (K)
DEFAULT_DISTORTION_COEFFICIENTS, // 렌즈 왜곡 계수 (D)
cv::noArray(), // R: Optional rectification transform
DEFAULT_CAMERA_MATRIX // P: New camera intrinsic matrix (K와 동일하게 설정)
);
// undistorted_points_mat에는 이제 왜곡이 보정된 픽셀 좌표가 저장됨
cv::Vec2d corrected_sun_vec = undistorted_points_mat.atcv::Vec2d(0, 0);
std::pair<double, double> corrected_sun_center = {corrected_sun_vec[0], corrected_sun_vec[1]};
Ray-plane intersection을 이용하여 좌표 변환
왜곡 보정된 픽셀 좌표를 최종적으로 그리드 인덱스로 변환하기 위해, 3D 기하학에 기반한 Ray-plane intersection 기법을 사용한다. 이 과정은 카메라 좌표계의 방향 벡터를 정의하고, 이를 운전자 시점의 월드 좌표계로 변환하여 3차원 교차점을 계산하는 로직으로 구현되었다.
- 1. 전면 유리 평면과의 교차점 계산
- 픽셀 좌표 정규화 : 왜곡 보정된 픽셀 좌표를 이미지의 주점(cx, cy)을 원점으로 하고, 경계를 [-1, 1] 범위로 갖는 정규화된 이미지 평면 좌표 (norm_x, norm_y)로 변환한다. 이는 후속 계산을 이미지 해상도에 독립적으로 만들기 위함이다.
- 3D 방향 벡터 계산 : 이 정규화된 좌표와 카메라의 수평/수직 화각(FOV) 정보를 삼각함수와 결합하여, 카메라 좌표계 기준의 3차원 방향 벡터 D_vec(du, dv, dw)를 계산한다. 이 벡터는 카메라 렌즈의 중심에서 Glare를 향하는 “카메라의 시선”을 나타낸다. 이때, 카메라가 물리적으로 x축 기준으로 일정 각도만큼 위를 보도록 설치된 점을 반영하기 위해, 계산된 기본 방향 벡터에 x축 회전 행렬을 곱하여 최종 방향 벡터를 얻는다. 이는 카메라의 물리적 방향(orientation)을 수학적으로 보정하는 과정이다.
double norm_x = (x_center_px / img_w - 0.5) * 2.0;
double norm_y = (y_center_px / img_h - 0.5) * 2.0;
double angle_x_rad = to_radians(norm_x * (fov_x_deg / 2.0));
double angle_y_rad = to_radians(norm_y * (fov_y_deg / 2.0));
// 회전 전 기본 방향 벡터 계산 (카메라 전방 +Z 가정)
cv::Vec3d D_no_rotation(std::tan(angle_x_rad), -std::tan(angle_y_rad), 1.0);
// 카메라 Pitch 회전 적용
double pitch_rad = to_radians(DEFAULT_CAMERA_PITCH_DEGREES);
cv::Matx33d rotation_matrix_x(1, 0, 0,
0, std::cos(pitch_rad), -std::sin(pitch_rad),
0, std::sin(pitch_rad), std::cos(pitch_rad));
cv::Vec3d D_rotated = rotation_matrix_x * D_no_rotation;
cv::Vec3d D_vec = cv::normalize(D_rotated); // 최종 방향 벡터
- Ray-plane intersection : “태양은 매우 멀리 있어 카메라와 운전자의 시선이 평행하다”는 핵심 가정을 사용하여, 위에서 계산된 D_vec을 운전자 시점의 방향 벡터로 간주한다. 운전자의 눈 위치(DEFAULT_DRIVER_POS)를 광선의 시작점(Origin), D_vec을 방향(Direction)으로 하는 3차원 광선을 정의한다. 이 광선이 차량 전면 유리(월드 좌표계에서 z = DEFAULT_WINDSHIELD_Z로 정의된 평면)와 교차하는 3차원 물리적 지점 P_ws(px, py, pz)를 계산한다.
cv::Point3d intersection = ray_plane_intersection(driver_eye_pos, D_vec, windshield_z);
- 2. 그리드 인덱스 좌표 변환
- 계산된 전면 유리와의 교차점 P_ws의 월드 x, y 좌표를, 선바이저가 실제로 작동하게 될 물리적 영역(DEFAULT_GLASS_ORIGIN, DEFAULT_GLASS_SIZE)을 기준으로 3x3 그리드의 최종 인덱스 (grid_x, grid_y)로 변환한다. 이 과정은 교차점의 상대적인 위치를 비례식으로 계산하여 수행되며, 계산된 값이 그리드 범위를 벗어나는 경우 경계값으로 강제 조정하는 로직을 포함한다.
int grid_x = static_cast<int>(((px - x_left_glass) / glass_width) * grid_cols);
int grid_y = static_cast<int>(((y_top_glass - py) / glass_height) * grid_rows);
grid_x = std::max(0, std::min(grid_x, grid_cols - 1));
grid_y = std::max(0, std::min(grid_y, grid_rows - 1));
메인 제어 루프에서의 모듈 호출
메인 제어 루프에서는 Glare가 감지되었을 때, glare_is_detected_flag 플래그를 활성화한다. 이 플래그가 true 일 때만, 감지된 Glare의 중심 픽셀 좌표 (avg_glarePos)를 camera_to_driver_coords 함수에 전달하여 최종 그리드 좌표를 계산하도록 구현되었다. 이는 불필요한 연산을 줄이고 시스템 효율을 높인다.
bool glare_is_detected_flag = (avg_glarePos.x != -1 && avg_glarePos.y != -1);
std::pair<int, int> grid_coords = {-1, -1};
if (glare_is_detected_flag) {
std::pair<double, double> sun_center_for_transform = {
static_cast<double>(avg_glarePos.x),
static_cast<double>(avg_glarePos.y)};
grid_coords = camera_to_driver_coords(sun_center_for_transform);
}
RPi-Arduino 통신
본 시스템은 효율적인 자원 활용과 실시간성 확보를 위해 분산 제어 아키텍처를 채택하였다. 연산 집약적인 고수준 제어(Glare 인식, 좌표 변환)는 라즈베리파이(RPi)가 담당하며, 실시간성과 정밀성이 요구되는 저수준 하드웨어 제어(모터 구동)는 아두이노 메가(Arduino)가 담당한다. 이 두 이종 프로세서 간의 원활한 정보 교환을 위해, UART 기반의 유선 시리얼 통신 모듈을 C++로 구현하였다.
시리얼 통신을 위한 파일 디스크립터 열기
Linux 기반의 RPi에서 시리얼 포트(/dev/ttyACM0 등)와 통신하기 위해서는, 먼저 해당 장치 파일을 열어 파일 디스크립터(File Descriptor)를 얻어야 한다. 이 과정은 통신을 위한 초기화 단계의 핵심이다.
- 1. initialize
- 아두이노와의 시리얼 통신 시작을 위해, 지정된 시리얼 포트 장치 파일을 열고 통신 설정을 구성하는 함수이다.
- 2. open (시리얼 포트 열기)
- POSIX 표준 함수인 open()을 사용하여 지정된 시리얼 포트 장치 파일(예: /dev/ttyACM0)을 읽고 쓸 수 있는 모드(0_RDWR)로 연다.
- 0_NOCTTY : 이 포트가 프로그램을 실행하는 프로세스의 제어 터미널이 되지 않도록 설정한다. 이는 시리얼 통신 프로그래밍에서 예상치 못한 터미널 제어 신호의 간섭을 막기 위한 일반적인 설정이다.
- 3. 논블로킹 모드 설정
- fcntl() 함수를 사용하여 열린 파일 디스크립터의 속성을 변경한다. 라즈베리파이의 메인 루프가 아두이노의 상태와 관계없이 항상 신속하게 동작하도록 하기 위해, 시리얼 포트를 논블로킹(Non-blocking) 모드로 설정합니다.
- fcntl(…, 0)을 통해 현재 파일 상태 플래그를 읽어옵니다.
- 읽어온 플래그에 0_NONBLOCK 플래그를 추가(OR 연산)합니다.
- fcntl(…, flags)를 통해 수정된 플래그를 포트에 다시 설정합니다.
- 이렇게 논블로킹 모드로 설정하면, write() 함수 호출 시 아두이노 측의 수신 준비 여부나 커널 출력 버퍼 상태와 관계없이 즉시 반환됩니다. 만약 버퍼가 가득 차서 데이터를 보낼 수 없다면, write()는 대기하지 않고 오류(EAGAIN 또는 EWOULDBLOCK)를 반환합니다. 이는 RPi 프로그램 전체가 시리얼 쓰기 작업 때문에 멈추는 현상을 방지하여 시스템의 전체적인 실시간성을 보장하는 핵심적인 설정입니다.
bool initialize(const std::string &port_name, speed_t baud_rate) {
serial_port_fd = open(port_name.c_str(), O_RDWR | O_NOCTTY);
if (serial_port_fd < 0) { /* 오류 처리 */ return false; }
// Non-blocking 설정
int flags = fcntl(serial_port_fd, F_GETFL, 0);
flags |= O_NONBLOCK;
fcntl(serial_port_fd, F_SETFL, flags);
// ...
}
시리얼 모드 설정
1바이트 크기의 패킷으로 구성된 바이너리 데이터를 아두이노에 정확히 전달하기 위해, 운영체제 수준의 문자 처리를 비활성화하는 Raw 모드로 시리얼 통신 모드를 설정한다.
- 1. tcgetattr로 구조체 선언
- 현재 시리얼 포트의 터미널 속성(Baud rate, 데이터 비트, 패리티 등)을 읽어와 termios 구조체 tty에 저장한다. 이후 이 구조체의 값을 수정하여 새로운 설정을 적용한다.
- 2. 1바이트 패킷 데이터 통신을 위한 모드 설정
- cfmakeraw(&tty) : 이 함수는 tty 구조체를 “raw” 모드로 설정한다. Raw 모드는 운영체제 수준의 입력/출력 문자 처리(예: 에코, 줄바꿈 준자 변환, 특수 문자 해석 등)를 대부분 비활성화하여, 프로그램이 순수한 바이너리 데이터를 주고받을 수 있도록 한다. 1바이트 명령을 정확히 전송하기 위해 필수적이다.
- tty.c_cflag : 제어 플래그를 설정한다. 8 데이터 비트, 패리티 없음, 1 스톱 비트 (8N1) 구성을 명시하고, 하드웨어 흐름 제어(CRTSCTS)는 활성화하여 통신의 안정성을 높였다. CLOCAL과 CREAD를 통해 모뎀 제어 신호를 무시하고 수신을 활성화한다.
- tty.c_lflag, tty.c_iflag, tty.c_oflag : Canonical 모드, 에코, 인터럽트 신호 처리, 소프트웨어 흐름 제어, 출력 후처리 등을 모두 비활성화하여 Raw 데이터 통신을 보장한다.
- tty.c_cc[VMIN], tty.c_cc[VTIME] : read() 함수의 타임아웃 동작을 제어한다. (현재 쓰기 동작이 중심이므로 기본값 유지)
struct termios tty;
if (tcgetattr(serial_port_fd, &tty) != 0) { /* 오류 처리 */ }
cfmakeraw(&tty);
tty.c_cflag |= (CLOCAL | CREAD | CRTSCTS); // 하드웨어 흐름 제어 활성화
tty.c_cflag &= ~CSIZE;
tty.c_cflag |= CS8;
// ... 기타 플래그 설정 ...
- 3. Baud rate, tcflush, tcsetattr
- cfsetispeed, cfsetospeed 함수로 입출력 Baud Rate를 115200으로 설정한다.
- tcflush 함수로 포트를 열 때 수신 버퍼에 남아있을 수 있는 이전 데이터들을 모두 버린다.
- tcsetattr 함수로 수정된 tty 구조체의 설정을 시리얼 포트에 즉시 적용한다. 이 함수가 성공적으로 호출되어야 모든 설정이 반영된다.
Arduino로 데이터 전송
위에서 초기화된 시리얼 포트를 통해 Arduino로 데이터를 전송하도록 하는 함수 sendCommandToArduino를 구현하였다. 이 함수는 고수준의 제어 정보(Glare 유무, 그리드 좌표)를 1바이트 통신 프로토콜에 맞춰 패킹하고 전송하는 역할을 한다.
- 1. 전송 데이터 구조 (1바이트 command_byte)
- 최소한의 데이터로 필요한 제어 정보를 전달하기 위해, 총 5비트의 핵심 정보를 하나의 바이트 데이터로 패킹하여 전송한다.
| Bit Position | Bit Index | Field Name | Bits | Value | Description |
|---|---|---|---|---|---|
| MSB | 7 | G (Glare Flag) | 1 | 0 or 1 | Glare 감지 유무를 나타내는 플래그 (1 : 감지) |
| 6 | Reserved | 1 | 0 | 예약 비트 (향후 확장용) | |
| 5 | Reserved | 1 | 0 | 예약 비트 (향후 확장용) | |
| 4 | Reserved | 1 | 0 | 예약 비트 (향후 확장용) | |
| 3 | C1 (Column MSB) | 1 | 0 or 1 | 3x3 그리드의 컬럼(x) 좌표 최상위 비트 | |
| 2 | C0 (Column LSB) | 1 | 0 or 1 | 3x3 그리드의 컬럼(x) 좌표 최하위 비트 | |
| 1 | R1 (Row MSB) | 1 | 0 or 1 | 3x3 그리드의 로우(y) 좌표 최상위 비트 | |
| LSB | 0 | R0 (Row LSB) | 1 | 0 or 1 | 3x3 그리드의 로우(y) 좌표 최하위 비트 |
- 2. sendCommandToArduino 함수 핵심 로직
- unsigned char command_byte를 0으로 초기화한다. 이는 Glare가 감지되지 않았거나, 감지되었더라도 유효한 위치를 찾지 못했을 경우 기본적으로 “선바이저 접기” 명령을 보내기 위함이다.
- 입력받은 glare_detected가 true이고, grid_coords가 유효한 좌표일 때만 명령 생성을 시작한다.
- 명령 바이트 생성
- command_byte |= ( 1 << 7 ) : 최상위 비트를 1로 설정하여 Glare가 감지되었음을 표시한다.
- grid_coords의 col 좌표 (0, 1, 2)를 2비트로 변환하고, << 2 비트 시프트 연산을 통해 명령 바이트의 비트 3과 2에 위치시킨다.
- grid_coords의 row 과표 (0, 1, 2)를 2비트로 변환하고, 명령 바이트의 비트 1과 0에 위치시킨다.
- 디버깅용 콘솔 출력 : 최종적으로 생성된 command_byte의 값을 이진수와 십진수로 콘솔에 출력하여, RPi가 의도한 대로 명령을 생성했는지 개발자가 확인할 수 있도록 한다.
- Arduino로 명령 바이트 전송 : 구성된 1바이트 명령을 실제로 아두이노로 전송하는 내부 함수 sendByte를 호출한다. sendByte 함수는 write() 시스템 콜을 사용하며, 논블로킹 모드로 설정되어 RPi의 메인 루프가 불필요하게 대기하는 것을 방지한다.
bool sendCommandToArduino(bool glare_detected, const std::pair<int, int>& grid_coords) {
unsigned char command_byte = 0;
if (glare_detected) {
if (grid_coords.first != -1 && grid_coords.second != -1) {
command_byte |= (1 << 7);
// ... (col, row 유효성 검사) ...
unsigned char col_bits = static_cast<unsigned char>(grid_coords.first) & 0x03;
unsigned char row_bits = static_cast<unsigned char>(grid_coords.second) & 0x03;
command_byte |= (col_bits << 2);
command_byte |= row_bits;
}
}
return sendByte(command_byte);
}
프로젝트 결과
최종 결과물
결과물 사진 혹은 시연 영상 등
미구현 내용
사진 올리기 테스트





