CAN-over-UDP 펌웨어 아키텍처 재설계
로봇별 CAN 변환을 raw CAN 포워딩으로 통합, 펌웨어 바이너리를 N개에서 1개로 줄임
배경
- 시스템: 4~6종의 로봇 라인업 (모델 추가/퇴역에 따라 변동)
- 통신 스택: ROS PC ↔ UDP ↔ STM32 펌웨어 ↔ CAN 버스 ↔ 주변 모듈
- 기존 설계: 펌웨어가 로봇별로 CAN 메시지를 다르게 변환해서 커스텀 UDP 형식으로 PC에 전송
- 드라이버 구현: 소독 모듈 ROS 드라이버 (~1026줄)
핵심 문제
펌웨어가 두 가지 역할을 동시에 수행하고 있었습니다:
- 전송: CAN 버스와 네트워크 간 데이터 이동
- 해석: 로봇별 프로토콜에 따른 CAN 메시지 변환
새 로봇이 나올 때마다 펌웨어를 수정해야 했고, 배포 시 로봇별로 다른 바이너리를 플래싱해야 했습니다. 기종이 늘어날수록 펌웨어 코드도 계속 커졌습니다.
어려운 점: CAN 프로토콜은 로봇마다 다르지만, 하드웨어 가까이에서 처리하고 싶은 유혹이 있습니다. 그 결과 모든 로봇 기종을 알아야 하는 펌웨어가 되었습니다.
핵심 아이디어
로봇마다 다른 건 해석뿐입니다. 전송은 동일합니다.
펌웨어가 해석 없이 raw CAN 프레임만 포워딩하면 로봇 종류를 몰라도 됩니다. 로봇별 파싱을 PC의 ROS 노드로 이전하면 소프트웨어 업데이트도 쉽고 디버깅 도구도 풍부합니다.
접근 방식
1단계: 표준 전송 계층 정의
로봇 종류와 무관한 최소 패킷 구조를 정의했습니다:
typedef struct CAN_PACKET_t {
uint32_t id; // CAN 중재 ID
uint8_t ide; // 확장 ID 플래그
uint8_t rtr; // 원격 전송 요청
uint8_t dlc; // 데이터 길이 (0-8)
uint8_t data[8]; // Raw 페이로드
} CAN_PACKET;
펌웨어 역할: 이 형식으로 UDP를 통해 CAN 프레임을 포워딩합니다. 로봇별 로직은 없습니다.
2단계: 로봇별 ROS 드라이버
로봇별 해석은 PC의 ROS 노드에서 처리합니다. 소독 드라이버 예시:
// CAN ID 0x200-0x211은 소독 ROS 노드에서 파싱
void ParseCobiMessage(const CAN_PACKET& packet) {
switch (packet.id) {
case 0x201: ParseDustSensor(packet.data); break;
case 0x202: ParseAirQuality(packet.data); break;
case 0x211: ParseStatusFlags(packet.data); break;
// ...
}
}
3단계: 신뢰성 패턴
명령 전송 간격 조절: CAN 버퍼 오버플로우를 방지하기 위해 30ms 간격으로 명령을 전송합니다. 오버플로우가 발생할 때까지 간격을 줄여보고 안전 마진을 추가해서 결정했습니다.
Reconciler 패턴 (쿠버네티스에서 착안): 팬, 플라즈마, UVC 서브시스템마다 독립 Reconciler를 둡니다. 목표 상태와 실제 상태를 비교하고 불일치 시 자동으로 재시도합니다. 분산된 재시도 로직을 제거했습니다.
안전 인터락:
- UVC는 기울어지지 않았을 때만 켜짐
- 플라즈마는 팬이 안정적일 때만 켜짐
연결 복구 에스컬레이션:
- 연결 없이 10초: 미션 중지
- 연결 없이 30초: 전원 재시작
팬 워치독:
- RPM 허용 오차: Low=900±150, Mid=1300±150, High=1600±150
- 경고 타임아웃: 60초
- 오류 타임아웃: 120초
트레이드오프
| 결정 | 이유 | 대가 |
|---|---|---|
| 펌웨어에서 Raw CAN 포워딩 | 펌웨어가 로봇 종류별로 커지지 않음 | PC 측 복잡도 증가 |
| CAN_PACKET 최소 구조 | 로봇과 무관한 전송 계층 | 없음 |
| 30ms 명령 전송 간격 | CAN 메시지 손실 방지 | 명령 실행이 조금 느려짐 |
| Reconciler 패턴 | 자동 재시도, 분산 로직 제거 | 실패 시 1초 이상 지연 |
| 독립 Reconciler 3개 | 플라즈마↔팬, UVC↔기울기 의존성 분리 | 코드량 증가 |
| 안전 인터락 | 위험 조건에서 자동 차단 | 운영 유연성 감소 |
| 연결 복구 에스컬레이션 | UX와 하드웨어 보호 균형 | 공격적 재시작이 다른 모듈에 영향 가능 |
결과
- 펌웨어 바이너리: 4~6개에서 1개로 통합
- 지원 로봇: 단일 펌웨어로 4~6종
- 하드웨어 없이 테스트: FakeCobi 모듈로 소독 드라이버를 검증했습니다. 팬 상태, 센서 데이터, 오류 코드 등 모든 CAN 응답을 시뮬레이션합니다. 테스트 커버리지: 상태 전환(off→low→mid→high), 오류 주입(타임아웃, 잘못된 RPM), 안전 인터락(UVC 중 기울기, 플라즈마 중 팬 실패)
- 명령 전송 튜닝: 30ms 간격으로 실제 운영에서 메시지 손실 없음
핵심 교훈
전송과 해석을 분리했습니다. 전송은 펌웨어에 (하드웨어에 가깝고 리소스 제한적), 해석은 ROS 드라이버에 (업데이트 쉽고 디버깅 도구 풍부). 결과: 펌웨어 바이너리 1개가 모든 로봇에 서비스하고, 로봇별 로직은 소프트웨어에서 독립적으로 개발합니다.