기록
[GPT가 알려주는] CAN 통신 TP & BAM 본문
CAN데이터에서 BAM이랑 TP가 모야?
좋아, 초간단 핵심부터!
한 줄 요약
- TP(J1939 Transport Protocol): 8바이트를 넘는 큰 메시지를 여러 프레임으로 쪼개서 보내는 규약.
- BAM(Broadcast Announce Message): TP의 브로드캐스트 방식. 수신자 지정/흐름제어 없이 모두에게 뿌림.
PGN & 프레임 종류
- TP.CM (Connection Management): PGN 60416 (0x00EC00)
전송 모드/길이/패킷수/대상 PGN 등을 알리는 “제어 프레임” - TP.DT (Data Transfer): PGN 60160 (0x00EB00)
실제 데이터 조각(각 7바이트)을 나르는 “데이터 프레임”
TP.CM(Control Byte) 종류(주요)
- 0x10 (16) = RTS: 연결형 시작(보내도 돼? → 수신자가 CTS로 허가)
- 0x11 (17) = CTS: 수신자가 “N개까지만 보내”라고 허가
- 0x13 (19) = EOMACK: 다 받았다 확인
- 0x20 (32) = BAM: 브로드캐스트 시작(흐름제어 없음)
- 0xFF (255) = ABORT: 중단
BAM vs RTS/CTS 차이
항목 BAM RTS/CTS(연결형)
| 대상 | 브로드캐스트(DA=255) | 단일 수신자(DA=특정 주소) |
| 흐름제어(CTS) | 없음 | 있음(혼잡시 속도 제어) |
| 신뢰성 | 낮음(버스 혼잡시 드롭 가능) | 높음(수신자 속도에 맞춤) |
| 최대 크기 | 최대 255 패킷 × 7B = 1785B | 이론상 더 큼(세션 나눠 가능) |
| 사용 예 | 간단 공지/상태 대용량 | 확실히 전송해야 하는 진짜 큰 데이터 |
프레임 포맷(바이트 필드)
TP.CM — BAM/RTS 공통 레이아웃 (PGN 60416)
Byte1 : Control (BAM=0x20, RTS=0x10, CTS=0x11, EOMACK=0x13, ABORT=0xFF)
Byte2-3 : Total Message Size (LSB, MSB)
Byte4 : Total Packets (N) // 각 DT 7바이트 → 전체 = N*7
Byte5 : (BAM/RTS/ABORT에서는 일반적으로 0xFF 등 모드별 의미)
Byte6-8 : PGN of the message being transported (LSB..MSB)
TP.DT — 데이터 (PGN 60160)
Byte1 : Sequence Number (1..N)
Byte2-8 : Data payload (7 bytes)
흐름 예시
(1) BAM 흐름
- TP.CM(BAM) 전송: 전체 크기, 총 패킷 수, 대상 PGN 공지 (DA=255)
- TP.DT 패킷 1..N을 연속 브로드캐스트
- 수신자는 자기 속도로 받아 조립(누락되면 재요청 불가 → 드롭)
(2) RTS/CTS 흐름
- TP.CM(RTS): “총 크기/패킷수, 이 PGN 보낼게 괜찮지?”
- TP.CM(CTS): “한 번에 M개까지 보내”
- TP.DT: 지정 개수만큼 보냄
- 반복 … 마지막에 TP.CM(EOMACK)
캡처/로그에서 식별하는 법 (네 CAN 앱 기준)
- TP.CM 식별: PGN 0x00EC00, Byte1=Control Code
- 0x20이면 BAM 세션 시작
- 0x10이면 RTS 시작
- 묶일 대상 PGN: TP.CM Byte6-8에 들어있는 원본 PGN
- 총 크기: TP.CM Byte2-3 (LSB,MSB) → 예상 총 패킷 수 = ceil(size/7) 와 Byte4 비교
- TP.DT 식별: PGN 0x00EB00, Byte1=시퀀스(1부터 증가)
코드 처리 팁(의사코드)
when (pgn) {
0x00EC00 -> { // TP.CM
val ctrl = data[0].toUByte().toInt()
val size = data[1].toInt() or (data[2].toInt() shl 8)
val totalPackets = data[3].toUByte().toInt()
val targetPgn = data[5].toInt() or (data[6].toInt() shl 8) or (data[7].toInt() shl 16)
when (ctrl) {
0x20 -> startBamSession(sa, targetPgn, size, totalPackets) // BAM
0x10 -> startRtsSession(sa, da, targetPgn, size, totalPackets) // RTS
0x11 -> onCts(sa, da, allowCount, nextSeq) // CTS
0x13 -> onEomAck() // 완료
0xFF -> onAbort(reason)
}
}
0x00EB00 -> { // TP.DT
val seq = data[0].toUByte().toInt()
val chunk = data.sliceArray(1..7) // 7 bytes
appendChunk(sessionKeyFrom(sa, targetPgn), seq, chunk)
}
}
- 세션 키: (송신자SA, 대상PGN) 조합으로 관리하면 편함(브로드캐스트는 DA=255).
- 완성 조건: 수신한 패킷 수 == TotalPackets 이면 조립 완료 → 상위에 원본 PGN 프레임 전달.
- BAM 성능 주의: 혼잡 시 누락될 수 있으므로 타임아웃/재시작 처리(드롭) 로직 필요.
빠른 트러블슈팅
- BAM인데 안 합쳐짐: DT 시퀀스 누락/중복 체크, 타임아웃 값 재확인(예: 첫 DT 50~200ms 내 미수신 시 드롭).
- RTS/CTS인데 멈춤: CTS 없거나 허용 패킷수 0 → 재요청 또는 세션 중단 확인.
- 대상 PGN이 누구?: TP.CM Byte6-8이 원본 메시지 PGN. 이 PGN으로 최종 상위에 넘겨야 의미가 맞음.
원하면 네가 캡처한 실제 바이트 덤프(몇 줄)만 주면, “이건 BAM/RTS고, 총 크기/패킷/원본 PGN은 뭐다”까지 바로 해석해 줄게.
'잡동사니 > GPT가 알려주는' 카테고리의 다른 글
| [GPT가 알려주는] 명령형 vs 선언형 (0) | 2025.10.22 |
|---|---|
| [GPT가 알려주는] Kotlin CharSequence | String | CharArray (0) | 2025.10.21 |
| [GPT가 알려주는] ksp와 implmenetation의 차이 (0) | 2025.10.15 |
| [GPT가 알려주는] RecyclerView의 scrollbarStyle (0) | 2025.10.15 |
| [GPT가 알려주는] [setprop log W옵션] setprop log.tag.AutofillManager W (0) | 2025.10.14 |