From 540c17e8901925a86956edaf1ed9074a8d77bc16 Mon Sep 17 00:00:00 2001 From: jhchun Date: Mon, 20 Jul 2026 17:09:59 +0900 Subject: [PATCH] =?UTF-8?q?MTU=EC=97=90=20=EB=94=B0=EB=A5=B8=20BLE=20?= =?UTF-8?q?=ED=8C=A8=ED=82=B7=20=EB=B6=84=ED=95=A0=20-=20Piezo=20echo=20AD?= =?UTF-8?q?C=20=EC=9D=91=EB=8B=B5=20=ED=8C=A8=ED=82=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 협상된 MTU 기준으로 reb: 응답 패킷 크기 판단 - 기존 reb:가 MTU에 들어가지 않으면 rec: chunk 분할 전송 --- src/ble/ble_service.c | 36 +++++++++++++ src/ble/ble_service.h | 1 + src/command/cmd_common.c | 110 ++++++++++++++++++++++++++++++++++++++- 3 files changed, 146 insertions(+), 1 deletion(-) diff --git a/src/ble/ble_service.c b/src/ble/ble_service.c index 6cdbcdc..e84f339 100644 --- a/src/ble/ble_service.c +++ b/src/ble/ble_service.c @@ -37,6 +37,7 @@ static bool dfu_advertising_mode; static bool advertising_unlimited; static uint8_t conn_param_update_attempts; static int64_t last_rx_conn_param_update_ms; +static uint16_t current_notify_mtu = 23U; #if IS_ENABLED(CONFIG_BT_SMP) #define BLE_REQUIRED_SECURITY_LEVEL BT_SECURITY_L4 @@ -55,6 +56,33 @@ static struct k_work adv_restart_work; static struct k_work_delayable adv_timeout_work; static struct k_work_delayable conn_param_update_work; +/* + * Store the UATT MTU used by normal GATT/NUS notifications. + * NUS notifications can carry application payload up to UATT MTU - 3 bytes. + */ +static void att_mtu_updated(struct bt_conn *conn, uint16_t tx, uint16_t rx) +{ + uint16_t uatt = bt_gatt_get_uatt_mtu(conn); + + if (uatt != 0U) + { + current_notify_mtu = uatt; + } + else + { + current_notify_mtu = (tx < rx) ? tx : rx; + } + + DBG_PRINTF("[BLE] ATT MTU updated tx=%u rx=%u uatt=%u used=%u notify_payload=%u\r\n", + tx, rx, uatt, current_notify_mtu, + (current_notify_mtu > 3U) ? (current_notify_mtu - 3U) : 0U); +} + +static struct bt_gatt_cb gatt_callbacks = +{ + .att_mtu_updated = att_mtu_updated, +}; + /* 로컬 BLE MAC 주소 로그 */ static void ble_log_local_identity(void) { @@ -424,6 +452,7 @@ static void disconnected(struct bt_conn *conn, uint8_t reason) } ble_connection_st = false; + current_notify_mtu = 23U; if (dfu_advertising_mode) { // DFU 광고는 미연결 10분 타임아웃 적용 @@ -578,6 +607,7 @@ int ble_service_init(ble_data_rx_cb_t rx_cb) ble_log_local_identity(); + bt_gatt_cb_register(&gatt_callbacks); #if IS_ENABLED(CONFIG_BT_SMP) err = ble_security_init(); if (err) @@ -790,3 +820,9 @@ bool ble_is_connected(void) { return (current_conn != NULL); } + +/* ATT MTU used to calculate notify payload size */ +uint16_t ble_current_mtu(void) +{ + return current_notify_mtu; +} diff --git a/src/ble/ble_service.h b/src/ble/ble_service.h index 18aae4f..8cd566b 100644 --- a/src/ble/ble_service.h +++ b/src/ble/ble_service.h @@ -31,6 +31,7 @@ int ble_service_init(ble_data_rx_cb_t rx_cb); int ble_advertising_start(void); int ble_advertising_stop(void); int ble_data_send(const uint8_t *data, uint16_t len); +uint16_t ble_current_mtu(void); int ble_dfu_advertising_enable(void); bool ble_dfu_advertising_is_enabled(void); bool ble_is_connected(void); diff --git a/src/command/cmd_common.c b/src/command/cmd_common.c index 117cf37..9bedd3b 100644 --- a/src/command/cmd_common.c +++ b/src/command/cmd_common.c @@ -4,6 +4,7 @@ ******************************************************************************/ #include #include +#include #include #include "cmd_common.h" @@ -22,6 +23,12 @@ static uint8_t tx_id_buf[4 + HW_NO_LENGTH + SERIAL_NO_LENGTH + SERIAL_NO_LENGTH static uint8_t tx_rim_buf[4 + 2 + (IMU_FIFO_RIM_TARGET_SAMPLES * IMU_FIFO_SAMPLE_BYTES) + 2]; static uint8_t g_echo_session; +/* ATT MTU에 따른 패킷 분할 */ +#define ATT_NOTIFY_HEADER_SIZE 3U +#define REB_OVERHEAD_SIZE 10U +#define REC_OVERHEAD_SIZE 14U +#define ECHO_SAMPLE_SIZE 2U + uint8_t cmd_next_echo_session(void) { return g_echo_session++; @@ -185,7 +192,27 @@ int cmd_send_response_imu(const int16_t accel[3], const int16_t gyro[3]) return ble_data_send(buf, 18); } -void cmd_send_response_echo(uint8_t session, uint8_t channel, const uint16_t *samples, uint16_t num_samples) +/* ATT MTU에 따라 패킷 분할이 필요한지 계산 */ +static uint16_t calc_rec_samples_per_packet(uint16_t notify_payload_max) +{ + uint16_t samples_per_pkt = 1U; + + // rec:는 고정 오버헤드가 14B, chunk 크기 다시 계산 + if (notify_payload_max > REC_OVERHEAD_SIZE) + { + samples_per_pkt = (uint16_t)((notify_payload_max - REC_OVERHEAD_SIZE) / ECHO_SAMPLE_SIZE); + } + + // 방어코드 + if (samples_per_pkt == 0U) + { + samples_per_pkt = 1U; + } + + return samples_per_pkt; +} + +static void cmd_send_response_echo_reb(uint8_t session, uint8_t channel, const uint16_t *samples, uint16_t num_samples) { // 정적 버퍼 사용 (한 번에 한 명령만 처리) uint8_t *buf = tx_echo_buf; @@ -210,6 +237,87 @@ void cmd_send_response_echo(uint8_t session, uint8_t channel, const uint16_t *sa ble_data_send(buf, payload_len + 2); } +/* +* rec: packet layout: +* tag (4B): "rec:" +* session (1B): 측정 세션 번호 +* channel (1B): piezo 채널 번호 +* offset (2B): 전체 샘플 배열에서 시작 인덱스 +* total_samples (2B): 전체 샘플 수 +* chunk_samples (2B): 현재 패킷에 들어간 샘플 수 +* sample data (N): Piezo ADC samples +* crc (2B): tag부터 sample data까지 CRC16 +*/ +static void cmd_send_response_echo_rec(uint8_t session, uint8_t channel, const uint16_t *samples, uint16_t num_samples, uint16_t samples_per_pkt) +{ + uint8_t *buf = tx_echo_buf; + + for (uint16_t off = 0U; off < num_samples; off = (uint16_t)(off + samples_per_pkt)) + { + uint16_t remain = (uint16_t)(num_samples - off); + uint16_t chunk_samples = MIN(samples_per_pkt, remain); + + buf[0] = 'r'; buf[1] = 'e'; buf[2] = 'c'; buf[3] = ':'; + buf[4] = session; + buf[5] = channel; + buf[6] = (uint8_t)(off >> 8); + buf[7] = (uint8_t)(off & 0xFF); + buf[8] = (uint8_t)(num_samples >> 8); + buf[9] = (uint8_t)(num_samples & 0xFF); + buf[10] = (uint8_t)(chunk_samples >> 8); + buf[11] = (uint8_t)(chunk_samples & 0xFF); + + // 마지막 chunk는 samples_per_pkt보다 작을 수 있음 + for (uint16_t i = 0U; i < chunk_samples; i++) + { + buf[12 + i * 2] = (uint8_t)(samples[off + i] >> 8); + buf[13 + i * 2] = (uint8_t)(samples[off + i] & 0xFF); + } + + uint16_t payload_len = (uint16_t)(REC_OVERHEAD_SIZE - 2U + (chunk_samples * ECHO_SAMPLE_SIZE)); + uint16_t crc = cmd_crc16_compute(buf, payload_len); + DBG_PRINTF("[CMD] rec tx ch=%u off=%u total=%u chunk=%u len=%u\r\n", + channel, off, num_samples, chunk_samples, (uint16_t)(payload_len + 2U)); + + buf[payload_len] = (uint8_t)(crc & 0xFF); + buf[payload_len + 1U] = (uint8_t)(crc >> 8); + + // ble_data_send()는 NUS sent 콜백을 기다리고 다음 chunk 전송 + if (ble_data_send(buf, (uint16_t)(payload_len + 2U)) != 0) + { + break; + } + } +} + +/* Piezo echo ADC 응답 패킷 */ +void cmd_send_response_echo(uint8_t session, uint8_t channel, const uint16_t *samples, uint16_t num_samples) +{ + uint16_t notify_payload_max = 0U; + uint16_t mtu = ble_current_mtu(); + uint16_t reb_len = (uint16_t)(REB_OVERHEAD_SIZE + (num_samples * ECHO_SAMPLE_SIZE)); + + // 협상된 ATT MTU에서 최대 payload + if (mtu > ATT_NOTIFY_HEADER_SIZE) + { + notify_payload_max = (uint16_t)(mtu - ATT_NOTIFY_HEADER_SIZE); + } + + // 분할이 필요 없는 경우(기존 reb: 패킷이 MTU 안에 들어가는 경우) reb: 응답 + if ((notify_payload_max == 0U) || (reb_len <= notify_payload_max)) + { + cmd_send_response_echo_reb(session, channel, samples, num_samples); + return; + } + + // 기존 reb: 패킷이 현재 MTU 보다 큰 경우 rec:로 전환 + uint16_t samples_per_pkt = calc_rec_samples_per_packet(notify_payload_max); + + DBG_PRINTF("[CMD] rec split mtu=%u samples=%u chunk=%u\r\n", mtu, num_samples, samples_per_pkt); + + cmd_send_response_echo_rec(session, channel, samples, num_samples, samples_per_pkt); +} + void cmd_send_response_bundle(uint16_t batt_mv, const int16_t accel[3], const int16_t gyro[3], int16_t temp_cdeg) { uint8_t *buf = tx_bundle_buf;