기록

[GPT가 알려주는] CAN 통신 TP & BAM 본문

잡동사니/GPT가 알려주는

[GPT가 알려주는] CAN 통신 TP & BAM

heylo 2025. 10. 16. 10:14
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 흐름

  1. TP.CM(BAM) 전송: 전체 크기, 총 패킷 수, 대상 PGN 공지 (DA=255)
  2. TP.DT 패킷 1..N을 연속 브로드캐스트
  • 수신자는 자기 속도로 받아 조립(누락되면 재요청 불가 → 드롭)

(2) RTS/CTS 흐름

  1. TP.CM(RTS): “총 크기/패킷수, 이 PGN 보낼게 괜찮지?”
  2. TP.CM(CTS): “한 번에 M개까지 보내”
  3. TP.DT: 지정 개수만큼 보냄
  4. 반복 … 마지막에 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은 뭐다”까지 바로 해석해 줄게.