| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | ||
| 6 | 7 | 8 | 9 | 10 | 11 | 12 |
| 13 | 14 | 15 | 16 | 17 | 18 | 19 |
| 20 | 21 | 22 | 23 | 24 | 25 | 26 |
| 27 | 28 | 29 | 30 |
- error-passive
- 리눅스 CAN
- assigned-clocks
- 임베디드 오디오
- xHCI
- EASRC
- 커스텀 보드
- 임베디드
- Azure RTOS
- i.MX8MP
- can_filter
- restart-ms
- fsl-sai
- vcan
- PTN5150A
- 리눅스 커널
- EILSEQ
- can-utils
- 타임베이스
- SocketCAN
- TIM17
- CC 로직
- CAN 필터
- CAN FD
- HAL_Delay
- 하드웨어 디버깅
- 오디오 클럭
- bus-off
- 임베디드 리눅스
- 44.1kHz
- Today
- Total
임베디드 일기장
SocketCAN CAN FD 실전 설정 — ip link 구성, write() EILSEQ 에러, ID 마스크 필터링까지 본문
SocketCAN CAN FD 실전 설정 — ip link 구성, write() EILSEQ 에러, ID 마스크 필터링까지
jaeuuu 2026. 7. 11. 13:52리눅스에서 CAN FD를 쓰다 보면 세 군데서 순서대로 막힌다. 인터페이스 설정, write()가 뱉는 정체불명의 에러, 그리고 수신 필터. 세 가지를 실전 순서대로 정리한다. 환경은 i.MX8MP(FlexCAN)이지만 SocketCAN 공통 내용이라 다른 컨트롤러에도 그대로 적용된다.
1. 인터페이스 설정: 중재 구간과 데이터 구간은 따로 논다
CAN FD는 한 프레임 안에서 속도가 두 번 바뀐다. 중재(arbitration) 구간은 클래식 CAN과 호환되는 속도로, 데이터 구간은 BRS 비트가 켜져 있으면 고속으로 달린다. 그래서 설정도 두 벌이다.
ip link set can0 down
ip link set can0 type can \
bitrate 500000 sample-point 0.875 \
dbitrate 2000000 dsample-point 0.75 \
fd on
ip link set can0 txqueuelen 1000
ip link set can0 up
dbitrate/dsample-point가 데이터 구간용이다. 샘플포인트는 중재 구간은 CiA 권장 87.5%, 데이터 구간은 보통 더 낮게(70~80%) 잡는다. txqueuelen을 같이 만지는 이유는 기본값이 10에 불과해서다. 다채널 동시 송신 구성이면 큐가 순식간에 차서 ENOBUFS가 터진다.
설정했으면 반드시 실제 적용값을 확인해야 한다.
ip -details link show can0
BRP와 타임 세그먼트가 정수라서 요청한 샘플포인트와 실제 적용값이 다를 수 있다. 특히 FlexCAN처럼 클럭 소스가 정해져 있는 컨트롤러는 도달 가능한 조합이 제한된다. 버스 위 노드끼리 샘플포인트가 어긋나면 간헐 에러라는 최악의 형태로 나타나므로, 모든 노드에서 이 명령으로 실측값을 맞추는 게 정석이다.
부팅 시 자동 설정이 필요하면 systemd oneshot 서비스로 위 스크립트를 감싸는 방법이 systemd 버전 의존성 없이 가장 이식성이 좋다. Yocto 양산 이미지라면 해당 유닛과 스크립트를 레시피(SYSTEMD_SERVICE:${PN})로 rootfs에 굽는다.
2. write()가 EILSEQ를 뱉을 때
CAN FD로 넘어가면서 가장 많이 만나는 에러가 이것이다.
write(): Invalid or incomplete multibyte or wide character
"멀티바이트 문자"라는 메시지에 당황하게 되는데, SocketCAN에서 EILSEQ는 프레임 검증 실패로 재활용된 에러 코드다. 체크 순서:
① 전송 길이가 구조체 크기와 정확히 일치하는가. write()에 넘기는 길이는 sizeof(struct can_frame)(16) 또는 sizeof(struct canfd_frame)(72) 둘 중 하나여야 한다. 어중간한 길이는 거부된다.
② FD 프레임인데 소켓에 FD를 안 켰는가. canfd_frame을 쓰려면 소켓 옵션이 필요하다.
int enable = 1;
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FD_FRAMES, &enable, sizeof(enable));
③ len 값이 유효한가. 클래식 CAN은 0 ~ 8. CAN FD는 아무 값이나 되는 게 아니라 8, 12, 16, 20, 24, 32, 48, 64만 유효하다. 페이로드가 9바이트면 len=9가 아니라 len=12로 올리고 나머지를 패딩해야 한다. 이 이산적인 길이 체계를 모르면 특정 크기에서만 실패하는 미스터리를 겪는다.
④ 인터페이스가 FD로 올라와 있는가. ip link에서 MTU를 보면 즉시 판별된다 — 클래식은 16, FD는 72. MTU 16인 인터페이스에 FD 프레임을 보내면 커널의 프레임 체크에서 걸린다.
3. 수신 필터: 매칭식 하나만 이해하면 된다
수신 측에서 필요한 ID만 골라 받으려면 CAN_RAW_FILTER를 쓴다. 동작은 매칭식 하나로 요약된다.
(수신 ID & can_mask) == (can_id & can_mask)
mask에서 1로 세운 비트만 비교하고 0인 비트는 무시한다. 이 원리에서 두 가지 사용 패턴이 나온다.
정확히 한 ID만 (SFF 기준)
struct can_filter f = {
.can_id = 0x123,
.can_mask = CAN_SFF_MASK | CAN_EFF_FLAG, /* 0x7FF + EFF 차단 */
};
setsockopt(s, SOL_CAN_RAW, CAN_RAW_FILTER, &f, sizeof(f));
정렬된 2ⁿ 블록 (예: 0x40~0x7F, 64개)
struct can_filter f = {
.can_id = 0x40, /* 구간 base */
.can_mask = 0x7C0 | CAN_EFF_FLAG, /* 하위 6비트 무시 */
};
블록 필터의 전제는 base & mask == base가 성립하는 정렬된 구간이라는 것이다. 임의 범위(예: 0x45~0x92)는 마스크 하나로 표현이 안 되므로 필터 배열을 여러 개 등록해 조합한다.
mask에 왜 CAN_EFF_FLAG(0x80000000)를 OR하는지가 실전 포인트다. 이 비트를 mask에 세우고 can_id에는 세우지 않으면, 매칭식상 수신 프레임의 EFF 비트도 0이어야 통과한다. 즉 29비트 확장 프레임이 하위 11비트 우연 일치로 새어 들어오는 것을 차단한다. 이걸 빼먹으면 확장 ID 트래픽이 섞인 버스에서 유령 수신이 생긴다.
4. 정리
- CAN FD 설정은 중재/데이터 구간 두 벌이고,
ip -details로 실제 적용값을 전 노드에서 대조하라 txqueuelen기본 10은 다채널 구성에서 거의 확실히 부족하다- write의 EILSEQ는 인코딩 에러가 아니라 프레임 검증 실패다 — 구조체 크기, CAN_RAW_FD_FRAMES, FD의 이산 len 체계, 인터페이스 MTU 순으로 점검
- 필터는 매칭식
(recv & mask) == (id & mask)하나로 이해하고, SFF만 받을 거면 mask에 CAN_EFF_FLAG를 반드시 포함시켜라
i.MX8MP FlexCAN, 커널 5.15.71 기준 실측 내용입니다.
'Linux' 카테고리의 다른 글
| 리눅스 CAN bus-off 상태 복구 — restart-ms 자동 복구 (0) | 2026.07.12 |
|---|---|
| 커스텀 보드에서 USB 3.0이 2.0으로만 잡힐 때 — dmesg 판독법과 Type-C CC 로직(PTN5150A)/크로스바 추적 (0) | 2026.07.12 |
| 리눅스 RS-485 DE 핀 제어, libmodbus에 맡기지 마세요 — TIOCSRS485로 커널에 위임하기 (0) | 2026.07.11 |
| 리눅스 ISO-TP에서 read()가 "Communication error on send"를 뱉는 이유 — sk_err와 커널 에러 전파 메커니즘 (0) | 2026.07.11 |
| 운영체제란 (0) | 2026.07.11 |