I2C 폴링과 상태 머신, 센서 응답성이 갈린다
버튼을 누르면 모터가 늦게 움직이고, 화면은 간헐적으로 멈추는데 센서 값만큼은 정상적으로 출력됩니다. 이런 현상은 센서 자체보다 I2C 통신을 기다리는 코드 구조에서 시작되는 경우가 많습니다. 아이투씨랩은 산업용 계측기와 사물인터넷 장치를 개발해 온 임베디드 엔지니어 박선우 씨에게 폴링 방식과 상태 머신 방식의 차이를 물었습니다.
질문: 센서 값이 제대로 나오는데도 통신 구조를 바꿔야 하나요?
박선우 엔지니어: 값의 정확성과 시스템의 반응성은 별개입니다. 한 센서의 변환 완료를 기다리느라 수십 밀리초를 소비하면 버튼, 통신, 표시 장치처럼 같은 루프에 들어 있는 다른 기능이 밀립니다. 센서 하나를 시험할 때는 보이지 않던 문제가 프로젝트 규모가 커지면서 드러나는 이유입니다.
I2C 폴링과 상태 머신은 무엇이 다른가
응답을 기다리는 코드와 다음 일을 하는 코드
질문: 폴링이 무조건 나쁜 방식이라는 뜻인가요?
박 엔지니어: 그렇지 않습니다. 폴링은 마이크로컨트롤러가 센서 상태를 반복해서 확인하거나, 요청 직후 응답이 올 때까지 한 함수 안에서 기다리는 방식입니다. 코드 흐름이 직관적이어서 아두이노 입문 실습이나 단일 센서 검증에는 매우 유용합니다. 문제는 기다리는 시간이 길어질 때 그 함수가 CPU의 진행을 붙잡는다는 데 있습니다.
상태 머신은 측정 명령 전송, 변환 대기, 데이터 읽기, 오류 복구를 각각 별도의 상태로 나눕니다. 변환 대기 상태에서는 시간을 확인한 뒤 아직 준비되지 않았다면 즉시 메인 루프로 돌아갑니다. 그 사이 보드는 버튼 입력을 읽고, LED를 갱신하며, 네트워크 패킷도 처리할 수 있습니다. 아두이노의 기본 개념과 활용 범위를 함께 살펴보면 작은 보드에서도 여러 입출력 작업이 동시에 요구되는 이유를 이해하기 쉽습니다.
- 단순 폴링: 작성과 디버깅이 쉽지만 대기 시간이 다른 기능에 전파될 수 있습니다.
- 주기 폴링: 일정 간격으로 상태만 확인하므로 부담이 줄지만 호출 주기를 직접 관리해야 합니다.
- 상태 머신: 실행 흐름이 끊기지 않지만 상태 전이와 오류 경로를 명확히 설계해야 합니다.
- RTOS 태스크: 기능 분리가 편하지만 스케줄링, 동기화, 스택 크기까지 고려해야 합니다.
“논블로킹은 무조건 빠르게 읽는 기술이 아닙니다. 기다려야 할 시간에 다른 일을 수행하도록 책임을 나누는 설계입니다.”
센서 데이터시트의 시간을 코드로 옮기는 법
변환 시간과 버스 점유 시간을 구분한다
질문: 상태 머신을 만들 때 가장 먼저 확인할 수치는 무엇인가요?
박 엔지니어: 센서의 측정 변환 시간, 권장 측정 주기, 전원 인가 후 안정화 시간입니다. 예를 들어 측정 명령을 받은 센서가 내부 계산에 15ms를 쓴다면, 그 시간 내내 I2C 버스를 사용한다는 뜻은 아닙니다. 명령을 짧게 전송한 뒤 센서 내부에서 변환이 진행되는 경우가 많으므로, 마이크로컨트롤러는 15ms 후에 다시 읽으면 됩니다.
여기서 초보자가 자주 쓰는 패턴은 명령 전송 뒤 delay(15)를 넣는 것입니다. 센서 하나만 연결했다면 동작하지만 디스플레이 갱신 주기가 10ms이고 버튼 디바운싱 주기가 5ms라면 두 작업이 모두 늦어집니다. 상태 머신에서는 현재 시간을 저장한 후 다음 루프에서 경과 시간을 비교합니다. 중요한 점은 측정 간격과 타임아웃을 같은 값으로 취급하지 않는 것입니다. 측정 간격은 정상 동작의 일정이고, 타임아웃은 비정상 응답을 포기할 기준입니다.
질문: 준비 여부를 알려주는 상태 비트가 있으면 계속 읽어도 되나요?
박 엔지니어: 가능하지만 매 루프마다 읽으면 버스 트래픽이 불필요하게 증가합니다. 예상 변환 시간의 대부분이 지난 뒤 상태 레지스터를 확인하거나, 데이터시트가 허용하는 최소 간격을 지켜야 합니다. 센서 드라이버를 별도 모듈로 분리할 때는 디바이스 드라이버 통합개발환경의 개요처럼 하드웨어 제어 계층과 응용 로직의 역할을 나누는 관점도 도움이 됩니다.
- 데이터시트에서 측정 명령과 결과 레지스터의 순서를 찾습니다.
- 일반, 고정밀, 저전력 모드별 최대 변환 시간을 기록합니다.
- 명령을 보낸 시각과 다음 읽기 예정 시각을 별도 변수로 저장합니다.
- 예정 시각 전에는 I2C 요청을 보내지 않고 다른 작업으로 돌아갑니다.
- 준비 비트나 정상 응답을 확인한 뒤에만 결과를 유효 데이터로 표시합니다.
- 최대 대기 시간을 넘기면 오류 횟수를 올리고 복구 상태로 전환합니다.
세 센서가 연결된 상황을 계산해 본다면
온습도 센서는 1초마다, 조도 센서는 100ms마다, 가속도 센서는 10ms마다 읽는다고 가정해 보겠습니다. 모든 센서를 한 함수에서 순서대로 기다리면 가장 느린 온습도 센서의 처리 방식이 전체 루프를 지배합니다. 반면 각 센서에 다음 실행 시각을 부여하면 가속도 데이터는 빠르게 갱신하고 온습도 측정은 필요한 빈도만 유지할 수 있습니다. 독자의 프로젝트에서 가장 느린 센서가 메인 루프 전체의 속도를 결정하고 있지는 않은지 확인해 볼 만합니다.
실전 상태 머신은 네 단계보다 오류 경로가 중요하다
IDLE·TRIGGER·WAIT·READ에 RECOVER를 더한다
질문: 실제 코드에는 어떤 상태가 필요합니까?
박 엔지니어: 기본적으로 대기(IDLE), 측정 명령(TRIGGER), 변환 대기(WAIT), 결과 읽기(READ)가 필요합니다. 저는 여기에 복구(RECOVER)를 반드시 추가합니다. I2C 센서 연결은 정상일 때보다 케이블이 흔들리거나 센서 전원이 늦게 올라왔을 때 어떻게 행동하는지가 더 중요하기 때문입니다.
TRIGGER에서는 전송 결과를 검사하고 성공했을 때만 WAIT로 넘어갑니다. WAIT에서는 millis() 같은 단조 증가 시간을 기준으로 다음 동작 시점을 판단합니다. READ에서는 수신 바이트 수, 상태 비트, CRC 지원 여부, 값의 물리적 범위를 차례로 검사해야 합니다. 통신에 성공했더라도 온도가 비현실적인 값이라면 응용 프로그램에 그대로 전달하지 않는 편이 안전합니다.
RECOVER 상태가 있다고 해서 매번 보드를 재부팅할 필요는 없습니다. 먼저 재시도 간격을 늘리고, 센서를 다시 초기화한 뒤, 그래도 실패하면 버스 복구를 시도하는 순서가 좋습니다. 실패 직후 무한 재시도를 하면 끊어진 장치 하나가 I2C 통신 대역폭을 모두 사용하고 로그까지 가득 채울 수 있습니다.
- IDLE: 측정 주기가 도래했는지 확인하고, 아직이면 즉시 반환합니다.
- TRIGGER: 명령 전송 성공 여부와 전송된 바이트 수를 검사합니다.
- WAIT: 블로킹 지연 없이 변환 완료 예정 시각을 기다립니다.
- READ: 데이터 길이와 무결성을 검사한 후 마지막 정상 값을 갱신합니다.
- RECOVER: 제한된 횟수로 재시도하고 초기화 또는 버스 복구를 수행합니다.
마지막 정상 값과 오류 상태를 함께 보관한다
질문: 읽기에 실패하면 화면에는 무엇을 표시해야 하나요?
박 엔지니어: 0을 넣는 방식은 피해야 합니다. 실제 측정값 0과 통신 실패를 구별할 수 없기 때문입니다. 데이터 구조에 측정값, 마지막 성공 시각, 유효 여부, 연속 실패 횟수를 함께 저장하십시오. 화면에는 마지막 정상 값을 유지하되 오래된 데이터임을 표시하고, 제어 로직은 허용된 데이터 나이를 넘으면 안전 상태로 이동하게 만듭니다.
사물인터넷 서버로 전송할 때도 값만 보내지 말고 품질 정보를 포함하는 편이 좋습니다. 예를 들어 value, timestamp, valid, error_code를 구분하면 현장에서 센서 고장과 네트워크 지연을 가려낼 수 있습니다. 센서가 생활 데이터를 축적하는 장치라면 라이프로그 서비스의 데이터 축적 개념처럼 측정값의 연속성과 시점 자체가 중요한 정보가 됩니다.
“오류를 숨기지 말고 데이터의 일부로 다루세요. 마지막 정상 시각 하나만 기록해도 현장 장애를 재현하는 시간이 크게 줄어듭니다.”
센서가 바뀌면 시간표와 라이브러리 가정도 바뀐다
교체 전 실제 지연과 타임아웃을 다시 측정한다
질문: 한 번 만든 상태 머신을 다른 센서에도 그대로 사용할 수 있나요?
박 엔지니어: 뼈대는 재사용할 수 있지만 시간값과 준비 판정은 다시 설계해야 합니다. 같은 종류의 센서도 정밀도 모드에 따라 변환 시간이 달라지고, 어떤 제품은 읽기 요청에 NACK로 응답하며 아직 준비되지 않았음을 알립니다. 다른 제품은 상태 레지스터를 먼저 읽어야 합니다. 호환 라이브러리가 내부적으로 긴 지연을 사용하는지도 소스에서 확인해야 합니다.
검증할 때는 시리얼 로그의 시간만 보지 말고 GPIO 핀을 상태 전환 때 토글한 뒤 로직 분석기로 관찰해 보십시오. TRIGGER부터 READ까지의 간격, 한 번의 전송이 버스를 점유하는 시간, 오류 후 재시도 간격을 눈으로 확인할 수 있습니다. 장비가 없다면 각 상태의 진입 시각을 마이크로초 단위로 기록하되, 로그 출력 자체가 타이밍에 영향을 줄 수 있다는 점을 감안해야 합니다.
- 센서의 최대 변환 시간이 설정한 대기 시간 안에 들어오는지 확인합니다.
- I2C 클록을 100kHz에서 400kHz로 바꿀 때 모든 장치가 지원하는지 점검합니다.
- 연속 측정 모드와 단발 측정 모드의 소비전력 차이를 실제로 측정합니다.
- 케이블 길이와 풀업 저항이 바뀐 환경에서도 재시도 횟수를 기록합니다.
- 센서 분리, 전원 재인가, 데이터 고정 상황을 의도적으로 만들어 복구 경로를 시험합니다.
업데이트가 필요한 값은 설정으로 분리한다
질문: 시간이 지나도 유지보수하기 쉬운 구조는 어떤 모습인가요?
박 엔지니어: 장치 주소, 측정 주기, 변환 대기 시간, 타임아웃, 최대 재시도 횟수를 코드 곳곳에 숫자로 흩어 놓지 않아야 합니다. 센서별 설정 구조체로 모으고 단위를 이름에 표시하면 좋습니다. 예를 들어 conversion_time_ms와 timeout_ms를 분리하면 제품 교체 때 수정 범위를 빠르게 찾을 수 있습니다.
또한 라이브러리 버전과 센서 로트가 달라지면 내부 기본값이나 초기화 직후의 동작이 달라질 수 있습니다. 제조사 데이터시트 개정, 보드 코어 업데이트, 통신 라이브러리 변경 후에는 정상 경로만 확인하지 말고 타임아웃과 복구 시험도 반복하십시오. I2C 상태 머신의 구조는 오래 재사용할 수 있지만, 시간을 결정하는 숫자와 드라이버의 가정은 부품과 소프트웨어가 바뀔 때 다시 검증해야 하는 값입니다.

- 다음글“센서 값은 시리얼로 보면 되죠?” I2C EEPROM을 써본 뒤 26.08.24
등록된 댓글이 없습니다.
