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 功能竞争

总体思路

  1. 定义 Trace 服务,包含一个 Trace Data 特性(Characteristics);
  2. 实现 Trace 事件的回调,将数据写入一个环形缓冲区;
  3. 当缓冲区内数据积累到一定量时,通过 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_airtrace.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_sendring_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;
      }