Chapter 05

광고 끝까지: 확장 광고 체인, 주기적 광고와 PAwR

1권 5장의 광고는 세 채널에 31바이트를 외치는 것이었다. 5.0 이후의 광고는 다르다. 주 채널에는 “자세한 건 7번 채널 1.2 ms 뒤에”라는 짧은 안내만 두고, 본문은 데이터 채널로 옮겨 수백 바이트를 체인으로 잇는다. 같은 구조 위에 주기적 광고와 응답 있는 주기적 광고(PAwR)가 올라선다. 이 장은 그 공통 형식을 바이트로 익히고, 사양 샘플 AUX_ADV_IND 하나를 공중 비트까지 다시 만든다.

주 채널은 안내판, 본문은 보조 채널로

확장 광고는 광고를 두 층으로 나눈다. 주 채널(37·38·39)에는 ADV_EXT_IND를 보낸다. 데이터 없이 “이 광고 묶음(SID)의 몇 번째 데이터(DID)이고, 본문은 어느 채널에 몇 µs 뒤에 있다(AuxPtr)”만 싣는다. 스캐너는 그 포인터를 따라가 보조 채널(0~36번 데이터 채널 중 하나)의 AUX_ADV_IND를 받는다. 본문이 길면 AUX_ADV_IND에도 AuxPtr을 달아 AUX_CHAIN_IND로 잇는다. 호스트 광고 데이터는 조각내기 전 최대 1650바이트다.

역 게시판의 “○번 출구 안내 데스크로”

붐비는 역 게시판(주 채널)에는 큰 공지를 다 붙이지 않는다. “○○ 행사 안내는 7번 출구 데스크, 지금부터 1분 뒤”만 붙인다. 관심 있는 사람만 7번 출구로 가서 자세한 안내문(AUX_ADV_IND)을 받고, 안내문이 길면 “다음 장은 옆 데스크에서”(AUX_CHAIN_IND)로 이어진다. 게시판이 덜 붐비고, 긴 안내도 가능하다.

ADI(AdvDataInfo, 2바이트)는 SID 4비트와 DID 12비트다. 한 기기가 광고 묶음 여럿을 동시에 돌릴 때 SID로 구별하고, 스캐너는 DID가 그대로면 같은 데이터라 보고 보조 채널을 다시 따라가지 않을 수 있다. 데이터를 바꾸면 DID를 바꿔야 한다.

확장 광고 PDU와 공통 확장 페이로드(Figure 2.17, AdvMode Table 2.15): [Vol 6] Part B §2.3.4 · ADI: §2.3.4.4 · AuxPtr: §2.3.4.5 · 1650바이트: §2.3.4.9(호스트 광고 데이터)

공통 확장 페이로드를 바이트로

ADV_EXT_IND, AUX_ADV_IND, AUX_CHAIN_IND, AUX_SYNC_IND 등은 모두 같은 페이로드 형식을 쓴다. 첫 바이트는 확장 헤더 길이(아래 6비트, 0~63)와 AdvMode(위 2비트: 00 연결·스캔 불가, 01 연결 가능, 10 스캔 가능). 그 뒤 확장 헤더가 오는데, 첫 바이트 플래그의 비트 0부터 차례로 AdvA(6), TargetA(6), CTEInfo(1), ADI(2), AuxPtr(3), SyncInfo(18), TxPower(1) 칸이 있는지를 표시하고 그 순서대로 이어진다. 남은 자리가 AdvData다(길이 − 확장 헤더 길이 − 1).

AuxPtr 3바이트: 채널 번호 6비트, 시계 정확도(CA) 1비트, 오프셋 단위 1비트(0 = 30 µs, 1 = 300 µs), Aux 오프셋 13비트, Aux PHY 3비트. 오프셋은 이 패킷의 시작에서 보조 패킷 시작까지이고, 적어도 이 패킷 길이 + T_MAFS(300 µs)여야 한다. 245,700 µs 미만이면 30 µs 단위를 쓴다. 보조 패킷은 오프셋보다 이르지 않고, 오프셋 + 한 단위보다 늦지 않게 시작한다.

WORKBENCH

AUX_ADV_IND 만들기 (사양 4.2.2 샘플)

확장 헤더 / 페이로드—
AdvData에 남은 자리—
사양 샘플과 비교—
광고 PDU는 접근 주소 0x8E89BED6, CRCInit 0x555555로 보낸다. 해볼 것: ① 처음 값은 사양 [Vol 6] Part C §4.2.2의 샘플이다. PDU와 화이트닝까지는 비트 하나까지 같지만 CRC는 다르다(아래 “사양 샘플의 불일치” 참고). ② AuxPtr에 “12 1500”을 넣으면 플래그 비트 4가 켜지고 3바이트가 붙어 AdvData 자리가 줄어든다. 1500 µs는 30 µs 단위로 50. ③ TxPower를 비우면 1바이트가 빠진다.
사양 샘플의 불일치: AUX_ADV_IND의 CRC

이 작업대를 만들며 사양 [Vol 6] Part C §4.2.2 샘플의 CRC(00011011 11000100 01110101)가, 같은 절의 PDU 비트에 본문 규칙(광고 채널 PDU는 CRC 레지스터를 0x555555로 시작, [Vol 6] Part B §3.1.1)을 적용한 값과 다르다는 것을 확인했다. 같은 CRC 구현이 레거시 광고(§4.2.1), 데이터 PDU(§4.3.1), 제어 PDU(§4.3.2) 샘플은 모두 그대로 재현하고, 문제의 CRC는 초기값을 0x515555(비트 18 하나 다름)로 두었을 때만 나온다. 샘플을 만들 때의 실수로 보이며, 구현은 본문 규칙(0x555555)을 따라야 한다. 검증 스크립트는 이 불일치를 그대로 기록해 둔다.

확장 헤더 필드 순서와 크기(Figure 2.18): [Vol 6] Part B §2.3.4 · AuxPtr(Table 2.17, T_MAFS 조건, 245,700 µs): §2.3.4.5 · TxPower: §2.3.4.7 · T_MAFS 300 µs: §4.1.2 · 샘플: [Vol 6] Part C §4.2.2(레거시는 §4.2.1)

1650바이트를 보내는 데 걸리는 시간

보조 채널 PDU 하나의 페이로드는 최대 255바이트지만, 확장 헤더가 그 안을 차지한다. 체인이 길수록 PDU마다 AuxPtr과 ADI가 반복되고, 사이마다 T_MAFS 이상을 띄운다. PHY도 크게 영향을 준다. 같은 데이터를 Coded S=8로 보내면 1M보다 약 8배 오래 걸리고, 그동안 다른 일(3장의 스케줄러)이 밀린다.

CALCULATOR

체인 계획: PDU 수와 공중 시간

보조 채널 PHY
PDU 수 (주 + 보조)—
보조 채널 공중 시간—
ADV_EXT_IND부터 끝까지—
예시 구성: ADV_EXT_IND(1M, 주 채널 하나)는 ADI + AuxPtr, AUX_ADV_IND는 AdvA + ADI (+ 이어지면 AuxPtr), AUX_CHAIN_IND는 ADI (+ AuxPtr). 각 PDU 사이 간격은 T_MAFS 300 µs로 가장 촘촘하게 잡았다. 패킷 시간은 1권 3장의 식(LE 1M: (10 + 페이로드) × 8 µs, LE 2M: (11 + 페이로드) × 4 µs, Coded S=8: 376 + ((2 + 페이로드) × 8 + 27) × 8 µs). 해볼 것: ① 1650바이트를 1M으로 보내면 보조 PDU가 7개다. ② Coded S=8로 바꾸면 공중 시간이 약 8배로 늘어 100 ms를 넘는다.
예측해 보기

스캐너가 AUX_ADV_IND는 받았는데 AUX_CHAIN_IND 하나를 놓쳤다. 호스트에는 무엇이 올라갈까?

체인 중간이 빠진 광고 데이터를 그대로 이어 붙여도 될까?

LE Extended Advertising Report 이벤트의 Event_Type에는 데이터 상태(완료, 더 올 것 있음, 잘림)가 있다. 체인을 놓치면 컨트롤러는 그때까지의 데이터를 “잘림”으로 올리고, 호스트는 DID가 같은 다음 광고를 다시 받아 맞춰야 한다. 체인이 길수록 한 조각이라도 놓칠 확률이 커지므로, 큰 데이터는 주기적 광고나 연결로 보내는 편이 낫다.

보조 패킷 시작 시각과 T_MAFS: [Vol 6] Part B §2.3.4.5·§4.1.2 · Extended Advertising Report의 데이터 상태: [Vol 4] Part E §7.7.65.13

주기적 광고, PAST, 그리고 PAwR

AUX_ADV_IND에 SyncInfo(18바이트)를 넣으면 “이 광고 묶음에는 주기적 광고 열차가 있다”는 뜻이다. SyncInfo는 첫 AUX_SYNC_IND까지의 오프셋, 주기, 채널 맵, 접근 주소, CRC 초기값, 이벤트 카운터를 담는다. 스캐너는 이것으로 열차에 동기화하고, 그 뒤에는 광고 채널을 스캔하지 않고 정해진 시각에만 깨어 받는다. Auracast의 BIGInfo도 이 열차에 실려 온다(9장).

동기화하려면 처음 한 번은 스캔해야 하는데, 보청기처럼 전력이 빠듯한 기기에는 부담이다. PAST(Periodic Advertising Sync Transfer)는 이미 동기화한 휴대폰이 연결을 통해 LL_PERIODIC_SYNC_IND(opcode 0x1C)로 SyncInfo를 건네주는 절차다. 보청기는 스캔 없이 열차에 바로 탄다.

PAwR(5.4)은 주기적 광고에 응답 슬롯을 붙인 것이다. 한 주기 안에 서브이벤트를 최대 128개 두고, 서브이벤트마다 AUX_SYNC_SUBEVENT_IND 뒤에 응답 슬롯 여러 개를 연다. 수천 개의 전자 가격표가 각자 정해진 서브이벤트·슬롯에서만 깨어나 받고 답한다(1권 12장). 시간 규칙은 이렇다.

CALCULATOR

PAwR 설계 점검기

    한 주기에 답할 수 있는 기기—
    한 기기의 갱신 간격—
    슬라이더 단위: 주기·서브이벤트 간격·슬롯 지연은 1.25 ms, 슬롯 간격은 0.125 ms. 해볼 것: ① 서브이벤트 128개 넘게 늘리면 위반이다. ② 슬롯 수를 늘리면 슬롯 간격 상한(남은 시간 ÷ 슬롯 수)에 걸린다. ③ 서브이벤트 수 × 간격이 주기보다 길면 다음 주기와 겹친다.

    SyncInfo: [Vol 6] Part B §2.3.4.6 · 주기적 광고 이벤트: §4.4.2.2.3 · PAwR 이벤트와 시간 규칙: §4.4.2.2.4 · LL_PERIODIC_SYNC_IND(PAST): §2.4.2.27·§5.1.13

    주 채널 ADV_EXT_IND AuxPtr 보조 채널 AUX_ADV_IND SyncInfo 주기적 광고 AUX_SYNC_IND AUX_SYNC_IND … 주기 (1.25 ms 배수, 7.5 ms ~ 81.91875 s) SyncInfo 18바이트: 첫 AUX_SYNC_IND까지 오프셋, 주기, 채널 맵, 접근 주소, CRC 초기값, 이벤트 카운터 PAST: 휴대폰 → 보청기 LL_PERIODIC_SYNC_IND (0x1C)
    그림 5-1. 주기적 광고에 타는 두 길. 스캐너는 주 채널의 ADV_EXT_IND에서 AuxPtr을 따라 AUX_ADV_IND를 받고, 거기 실린 SyncInfo로 주기마다 오는 AUX_SYNC_IND 열차에 동기화한다. 그 뒤로는 광고 채널을 스캔하지 않고 정해진 시각에만 깨어난다. 스캔이 부담인 기기는 PAST로 이미 동기화한 휴대폰에게서 연결을 통해 SyncInfo를 건네받아 바로 열차에 탄다.

    핵심 정리

    1. 주 채널의 ADV_EXT_IND는 안내만(ADI + AuxPtr), 본문은 보조 채널의 AUX_ADV_IND와 AUX_CHAIN_IND. 호스트 데이터 최대 1650바이트.
    2. 공통 확장 페이로드: [확장 헤더 길이 6비트 · AdvMode 2비트] [플래그] AdvA · TargetA · CTEInfo · ADI · AuxPtr · SyncInfo · TxPower · ACAD · AdvData.
    3. AuxPtr = 채널 6 · CA 1 · 단위 1(30/300 µs) · 오프셋 13 · PHY 3. 오프셋 ≥ 패킷 길이 + T_MAFS(300 µs).
    4. ADI의 SID는 광고 묶음, DID는 데이터 판. 데이터를 바꾸면 DID를 바꾼다.
    5. SyncInfo로 주기적 광고에 동기화하고, PAST로 연결을 통해 넘겨준다. PAwR: 서브이벤트 ≤ 128, 슬롯 간격 ≤ (서브이벤트 간격 − 슬롯 지연) ÷ 슬롯 수.

    확인 퀴즈

    확장 헤더 플래그가 0x49(0100 1001)이면 있는 칸은?

    비트 0 AdvA, 비트 3 ADI, 비트 6 TxPower. 사양 4.2.2 샘플의 플래그가 이것이다.

    Aux 오프셋 1,200 µs를 AuxPtr에 넣으면 단위와 값은?

    245,700 µs 미만이면 30 µs 단위를 써야 한다.

    PAST가 해결하는 문제는?

    이미 동기화한 기기가 연결로 SyncInfo를 넘겨주면, 받는 기기는 스캔 없이 열차에 탄다.

    PAwR 한 주기에 둘 수 있는 서브이벤트의 최대 수는?

    [Vol 6] Part B §4.4.2.2.4: “Up to 128 subevents can be used in a Periodic Advertising Interval.”