Trace 对于分析协议栈流程问题非常重要。考虑到蓝牙设备的多样性,Trace 数据的导出、记录由开发者在应用里实现。 作为参考,SDK 里提供了 SWD、UART 两种“有线”方式,Wizard 能够自动生成相关代码。 在某些情况下,“有线”方式可能难以使用,得益于 ING918xx EFlash 的应用内编程(In-Application Programming,或称自编程,Self Programming)特性,可以先将 Trace 保存到 Flash 里,然后导出。
还有一条通道可以用来导出数据:BLE 空口。本文介绍如何通过 BLE 从空中抓取 Trace 数据。 针对本文提供的参考实现,我们提供了网页版 Log 记录器。
说明:该功能相关代码将(或已)在版本 8.0.0 中发布,参考 UART GATT Console 示例。
在版本发布之前,开发者可以按照需要将本文提供的方法加入已有的项目中。
注意:本方法仅用调试。强烈不建议在量产产品中保留此功能,以免泄露敏感信息。
目前存在的四种导出/记录 Trace 数据的方法总结如下。
方法 | 端口 | 侵入性 | 数据量 | 传输速率 |
---|---|---|---|---|
SWD/RTT | SWD,2 线 | 最低 | 无限 | 高 |
UART | UART,1 线 | 较低。UART 产生较多中断,占用 MCU 处理时间 | 无限 | 较高 |
Flash | 无 | 一般。占用 Flash 空间,写入速率低,占用 MCU 处理时间 | 受限于 Flash 空间 | 一般 |
BLE 空口 | 无 | 很高。需要修改 GATT Profile,多一条 BLE 连接 | 无限 | 一般,与原有 BLE 功能竞争 |
总体思路
- 定义 Trace 服务,包含一个 Trace Data 特性(Characteristics);
- 实现 Trace 事件的回调,将数据写入一个环形缓冲区;
- 当缓冲区内数据积累到一定量时,通过 Trace Data 特性发送数据。
接口定义
我们将该功能跟 GATT Profile 做隔离,定义以下接口。
// 初始化。
// msg_id 用于 `btstack_push_user_msg`
// req_thres 用于控制 `btstack_push_user_msg` 的调用时机
void trace_air_init(trace_air_t *ctx, uint32_t msg_id, uint8_t req_thres);
// 当用于 Trace 的 BLE 连接建立时,使能;当连接断开时,禁用
// conn_handle 为连接句柄
// value_handle 为 Trace Data 的句柄
void trace_air_enable(trace_air_t *ctx, int enable, uint16_t conn_handle, uint16_t value_handle);
// 在收到 msg_id 这条用户消息时,调用该函数发送 Trace 数据
void trace_air_send(trace_air_t *ctx);
// Trace 事件的回调
uint32_t cb_trace_air(const platform_evt_trace_t *trace, trace_air_t *ctx);
具体实现
-
定义 Trace Service
项目 UUID Trace Service 00000006-494e-4743-4849-505355554944
Trace Data 特性 bf83f3f2-399a-414d-9035-ce64ceb3ff67
-
上下文信息
上下文信息的定义如下,主要包含环形缓冲区,句柄信息和状态:
typedef struct { // 环形缓冲区 uint8_t buffer[TRACE_BUFF_SIZE]; uint16_t write_next; uint16_t read_next; SemaphoreHandle_t mutex; uint32_t msg_id; uint16_t value_handle; uint16_t conn_handle; uint8_t req_thres; uint8_t enabled:1; uint8_t msg_sent:1; // 防止重复调用 btstack_push_user_msg } trace_air_t;
-
cb_trace_air
cb_trace_air
与trace.c
模块的cb_trace_uart
相似,唯一需要注意的是需要丢弃用于 Trace 的 BLE 连接上的 ACL 数据:uint32_t cb_trace_air(const platform_evt_trace_t *trace, trace_air_t *ctx) { #pragma pack (push, 1) typedef struct { uint32_t A; uint32_t B; uint8_t id; uint8_t tag; } header_t; if (trace->len1 == sizeof(header_t)) { const header_t *p = (const header_t *)trace->data1; if ((p->id == PLATFORM_TRACE_ID_HCI_ACL) && (p->tag == (ctx->conn_handle << 1))) return 0; } #pragma pack (pop) uint16_t next; int free_size; uint8_t use_mutex = !IS_IN_INTERRUPT(); if (use_mutex) xSemaphoreTake(ctx->mutex, portMAX_DELAY); next = ctx->write_next; free_size = ctx->read_next - ctx->write_next; if (free_size <= 0) free_size += TRACE_BUFF_SIZE; if (free_size > 0) free_size--; free_size -= trace->len1; free_size -= trace->len2; if (free_size < 0) { if (use_mutex) xSemaphoreGive(ctx->mutex); trace_air_send_req(ctx); return 0; } next = trace_add_buffer(ctx->buffer, (const uint8_t *)trace->data1, trace->len1, next) & TRACE_BUFF_SIZE_MASK; next = trace_add_buffer(ctx->buffer, (const uint8_t *)trace->data2, trace->len2, next) & TRACE_BUFF_SIZE_MASK; ctx->write_next = next; if (use_mutex) xSemaphoreGive(ctx->mutex); if (free_size < ctx->req_thres) trace_air_send_req(ctx); return 0; }
其中
trace_air_send_req
的参考实现如下:static void trace_air_send_req(trace_air_t *ctx) { if ((0 == ctx->enabled) || ctx->msg_sent) return; ctx->msg_sent = 1; btstack_push_user_msg(ctx->msg_id, NULL, 0); }
-
trace_air_send
trace_air_send
与ring_buf.c
模块的ring_buf_peek_data
类似:static int peek_data(trace_air_t *ctx, uint8_t *data, int len, uint8_t has_more) { int r = 0; int mtu = att_server_get_mtu(ctx->conn_handle) - 3; uint8_t *p = data; while (len) { int size = len > mtu ? mtu : len; if (att_server_notify(ctx->conn_handle, ctx->value_handle, p, size)) { break; } len -= size; p += size; r += size; } return r; } void trace_air_send(trace_air_t *ctx) { ctx->msg_sent = 0; if (0 == ctx->enabled) return; uint32_t read_next = ctx->read_next; const uint32_t write_next = ctx->write_next; while (read_next != write_next) { int cnt = read_next > write_next ? sizeof(ctx->buffer) - read_next : write_next - read_next; int has_more = read_next > write_next ? 1 : 0; int c = peek_data(ctx, ctx->buffer + read_next, cnt, has_more); if (c < 1) break; read_next += c; if (read_next >= sizeof(ctx->buffer)) read_next = 0; } ctx->read_next = read_next; }