设计说明

本文主要介绍了瑞昱 RTL8773E 系列手表的开发环境和 SDK 的软件设计架构,旨在用户可以通过学习此文档快速在硬件评估板上搭建环境,熟悉 SDK。

RTL8773E 系列 IC 型号主要包括 RTL8773EWE、RTL8773EWE-VP 和 RTL8773EWE-VS,支持 NOR Flash 和 NAND Flash 版本,用户请根据需求选择对应的 IC 类型。 SDK 提供了蓝牙手表常用的基本功能,包括 GUI 框架、蓝牙链路管理、音乐播放等。其中,手表的蓝牙连接支持手机和耳机,通过不同的协议实现连接和数据传输。 音乐场景包括本地音乐播放、连接耳机播放音乐和播放手机音乐,音频数据通过不同的流程进行处理和传输。SDK 中还提供了一些示例界面,用户可通过 GUI 界面进行交互。

通过本文档,用户可以快速熟悉开发环境并进行应用开发。

实用案例

下图显示了手表应用的整体框架。

../../_images/Watch_App_Case.png

手表应用案例

环境需求

  1. RTL8773E 评估板母板

  2. RTL8773E 评估板子板

  3. 显示屏子板

  4. SD 卡子板

  5. 麦克风子板

  6. 喇叭

  7. 3.7V 电池或 USB 数据线供电

  1. ARM Keil MDK

  2. MPPG Tool

  3. MCUConfig Tool

  4. Debug Analyzer

  5. Image Converter Tool

更多详细使用,请查阅 快速入门

硬件连线

评估板提供了用户开发和应用调试的硬件环境,由主板和几块子板组成。它有下载模式和工作模式,用户需要使用不同的接线方法来设置 EVB 进入所需的模式,有关 EVB 母板的详细介绍和使用方法请参阅评估板指南。

配置选项

手表工程包含的一些功能可以通过以下宏定义启用或禁用,用户可以根据产品需求配置。

#define F_APP_SAVE_RECORD_FILE_TO_SD        1
#define F_APP_SAVE_RECORD_FILE_TO_FLASH     0
#define F_APP_PLAY_AND_RECORD               0

#define F_APP_A2DP_SOURCE_SUPPORT           1

#define F_APP_DSP_ONLINE_DEBUG_SUPPORT      0

#define F_APP_BLE_ANCS_CLIENT_SUPPORT       1

#define F_APP_HID_SUPPORT                   0
#define F_APP_HID_KEYBOARD_MOUSE_SUPPORT    0
#define F_APP_SUPPORT_NORMAL_OTA            1
#define F_APP_SC_KEY_DERIVE_SUPPORT         0
#define F_APP_NOT_ABS_VOL_SET_MAX_GAIN      0
#define F_APP_AIRSYNC_SERVICE_SUPPORT       0
#define F_APP_WECHAT_TRANSFER_SUPPORT       1

#ifdef TARGET_RTL8763E
#define F_APP_UI_ENGLISH_VERSION            0
#define F_APP_PACKAGE_QFN68                 0
#define F_APP_SUPPORT_USB                   1
#endif

#ifdef TARGET_RTL8773E
/*App feature setting*/
#define F_APP_HONEY_GUI                     1
#define F_APP_GUI_USE_PSRAM                 1
#define F_APP_GUI_RAMLESS                   0
#define F_APP_PACKAGE_QFN68                 1
#define F_APP_SUPPORT_USB                   0
#define F_APP_PPG                           0
#define F_APP_CWM                           0

/*Honeygui feature setting*/
#define RTK_MODULE_RTK_PPEV2
#define RTK_TP_ALGO_V2
#define RTK_ENABLE_RTK_SOC_WATCH            /*Enable for soc gui. Disable for simulator gui*/

/*Honeygui ui resource setting*/
#if (SUPPORT_NAND_FLASH == 0)
#define GUI_RESOURCE_NOR_FLASH
#else
#define GUI_RESOURCE_NAND_FLASH
#endif
#define GUI_RESOURCE_FILE_SYSTEM            0

#endif

编译和下载

请参阅 快速入门 进行编译和下载。由于 SDK 中同时支持 NOR Flash 版本和 NAND Flash 版本,所以值得注意的是:

  1. 用户需要区分两种 flash type 所搭配的 image、flash map 以及 trace 文件,如下是对应路径:

文件路径

文件名

文件路径

Image NOR 版本

bin\rtl87x3ep\8773EWE\nor\

Image NAND 版本

bin\rtl87x3ep\8773EWE\nand\

Flash map NOR 版本

bin\rtl87x3ep\flash_map_config\watch_flash_32M\

Flash map NAND 版本

bin\rtl87x3ep\flash_map_config\watch_flash_32M\nand\

Trace NOR 版本

trace\8773EWE\nor\

Trace NAND 版本

trace\8773EWE\nand\

  1. 在 watch_ep 工程中,包含两个 target 选项,其中 watch_ep默认对应 NOR Flash, watch_ep_NAND对应 NAND Flash。

  2. 在使用 MPPG tool 时注意 IC type 的选择:

    烧录 NOR 版本时,IC type 选择 RTL8773EWERTL8773EWE-VP

    烧录 NAND 版本时,IC type 选择 RTL8773EWE-VS

备注

  • 开发中,除了 NOR 和 NAND 共用的库文件,也需要注意对应路径的区分:NOR 版本在 \bin\rtl87x3ep\NOR_lib\ ,NAND 版本在 \bin\rtl87x3ep\NAND_lib\

测试验证

UI 测试流程

  1. 正确完成烧录固件后,将 EVB 切换到工作模式,复位重启。

  2. SDK 默认设计了几个 UI 界面,设备重启后可以看到 “表盘” 界面,在 “表盘” 界面上滑窗帘可以查看或修改当前模式,在 “表盘” 界面向左滑动可以切换到其他几个常用界面, 包括:音乐播放器、电话、联系人、录音、地图、基准测试等界面,每个页面中的控件如按键等都可进行交互。

../../_images/Watch_UI.png

Watch UI 演示

蓝牙连接测试流程

本章节侧重于 UI 上的使用,蓝牙连接的逻辑将在 连接方式 章节里介绍。

在 “表盘” 界面上滑窗帘中,可以通过点击“蓝牙图标”控件开启或关闭蓝牙。

在蓝牙打开的状态下,通过点击“手机图标”或“耳机图标”控件完成对应的蓝牙连接,连接成功后,对应的设备信息会存到手表的绑定列表中。

../../_images/UI_BT.png

蓝牙开关 UI

连接手机

  1. 首次连接某手机,可以通过点击 新手机连接 查看当前手表设备的蓝牙名称和地址,这样,手机端可以搜索对应设备主动发起连线。

  2. 连接过程包括 ACL 和 Profile 的连接,连接成功后,在“连接手机”界面会高亮显示所连接的手机名称。

  3. 如果不是首次连接,在手机端也没有删除手表的绑定信息,则点击“手机图标”时就会主动发起回连手机的行为。

  4. 在“连接手机”界面,主动点击手机名称,也会触发主动回连手机的行为。

../../_images/UI_Connect_Phone.png

连接手机 UI

备注

  • 连接手机时,Profile 连接会主要关注连接 HFPPBAP 的连接,此时可以支持打电话和同步联系人功能。

  • 连接上手机后,打开 媒体音频 开关,手表会主动发起 A2DP 协议的连接,成功后会支持在手表端播放手机音乐的功能。

连接耳机

  1. 在“连接耳机”界面,可以通过点击 搜索耳机 主动扫描当前可发现的耳机设备。

  2. 搜索到耳机设备后,会显示搜索到的耳机名称,再次点击会发起连接,连线过程包括 ACL 和 Profile( A2DPAVRCP )的连接。

  3. 连接成功后,在“连接耳机”界面会高亮显示所连接的耳机名称

  4. 如果绑定列表中已经存在耳机,则点击“耳机图标”时就会主动发起回连最近使用过的耳机。

  5. 在“连接耳机”界面,主动点击耳机名称,会进入耳机绑定列表,点击耳机名称,也会触发主动回连该耳机的行为。

../../_images/UI_Connect_Buds.png

连接耳机 UI

音乐播放测试流程

手表目前支持 3 种播放模式: 本地音乐播放,连接耳机播放音乐,播放手机音乐。

本地音乐播放

手表上电后,默认为本地播放模式,在 SD/NAND 中有音乐的情况下,滑到音乐播放器的 UI 界面,点击“播放”按钮,可以通过喇叭听见当前播放的音乐。

  1. 在“播放器”界面点击音乐名称,可以进入到“播放列表”界面,在播放列表点击歌曲名称,可以切换播放该音乐。

  2. 播放中可以通过“上一曲”,“下一曲”图标切换当前播放的音乐。

  3. 屏幕底部上滑,可以打开音量控制窗帘,通过点击相应图标来调整当前音量。

../../_images/UI_Player.png

播放器 UI

备注

  • 如果 SD/NAND 中没有音乐,可以通过 Audio Connect 手机 APP 传输音乐到 SD/NAND 中。

连接耳机播放音乐

连接耳机播放音乐,需要首先完成 连接耳机 ,连接上耳机后,在 SD 卡中有音乐的情况下,滑到音乐播放器的 UI 界面,点击“播放”按钮,可以通过耳机听见当前播放的音乐。 耳机播放同样支持音乐切换、音量调整的功能,播放器界面的使用与 本地音乐播放 中的使用相同。

播放手机音乐

手表播放手机音乐,需要首先完成 连接手机 ,连接上手机后,需要打开 媒体音频 开关,之后滑到音乐播放器的 UI 界面,点击“播放”按钮,可以通过喇叭听见当前手机播放器播放的音乐。 播放手机音乐不支持“播放列表”,上下曲切换和音量调节的功能与 本地音乐播放 中的使用相同。由于手机播放的音源来自于手机,所以该功能不需要 SD 卡。

软件设计介绍

软件架构

整体的系统架构如下,由几个重要部分组成:

  • Application:用户应用代码。下文会重点介绍该部分的实现。

  • Framework:负责音频通路,经典蓝牙 Profile,系统管理等。

  • LE Profiles/Services:LE Service 相关定义,并负责提供数据读写相关接口。

  • GAP:提供蓝牙 GAP 相关接口。

  • IO driver:提供各种外设驱动,包括 GPIOSPII2CUARTQSPIADC 等。

  • OSIF:提供 OS 相关的接口。

  • Platform:提供平台相关接口,完成包括 Memory 管理,系统 Boot 机制,电源管理,OS 管理等。

../../_images/Sys_Architecture.png

系统框图

备注

  • 详细信息可以参考 Platform

  • 但请注意,鉴于软件开发版本存在差异,本文所涉及的 SDK 信息与链接文档可能不完全一致,请结合实际需求甄别采纳相关内容。

目录结构

手表工程的目录接口如下框图所示。

└── Project: watch_ep/watch_ep_NAND
    ├── include                        编译配置
    ├── cmsis                          启动等系统设定
    └── lib
        ├── gap_utils.lib
        ├── framework.lib
        ├── ROM.lib
        ├── filesystem.lib             链接库文件
        ├── upperstack_4M.lib
        ├── hal_utils.lib
        ├── RTL87x5PPG.lib
        ├── CWM_LIB_DML_Keil_m3.lib
        └── CWM_LIB_Keil_m3.lib
    ├── ble_Profile                    BLE 协议
    ├── module                         外设模块,包括按键,触摸屏,传感器等底层驱动
    └── watch_app
        ├── main.c
        ├── app_task.c
        ├── app_cfg.c                  Watch 应用层逻辑
        :
    ├── lcd_driver                     LCD 驱动
    ├── hub_task                       外设管理
    ├── communicate                    BLE 交互协议示例
    ├── Localplayback                  本地播放、音乐传输相关代码
    ├── dfu
    ├── dfu_task                       DFU、OTA 示例
    ├── console                        Uart console 示例
    ├── mp                             MP 测试代码
    ├── honey_gui                      HoneyGUI
    ├── peripheral                     Display Controller
    └── gui_application
        ├── app_gui_main.c
        ├── watchface_mgr.c            GUI 界面示例
        ├── player_mgr.c
        ├── call_mgr.c
        :
    ├── ppe                            PPE
    ├── PPG                            PPG
    └── cwm                            CWM 算法

备注

任务和优先级

RTL87x3E 使用 FreeRTOS 操作系统。内建 timer task,upper stack task,lower stack task,idle task。

使用 HoneyGUI 架构会创建 GUI task,此外,Watch 应用还自建 app task,hub task 和 communicate task。

各任务描述及优先级如下表:

任务说明及优先级

任务

描述

优先级

Timer

实现 FreeRTOS 所需的软件定时器

6

BT Controller stack

实现 HCI 以下的 BT 和 BLE 协议栈

6

BT Host stack

实现 HCI 以上的 BT 和 BLE 协议栈

3

APP

注册回调函数,处理 BT 和 BLE 相关状态,自由实现对蓝牙,音频等行为的控制

2

HUB

将底层 driver 部分与上层进行分离,处理各外设的上层消息

1

Communicate

BLE 的数据发送管理示例

1

GUI

处理 UI 显示及交互

1

Idle

运行后台任务

0

GUI 说明

GUI 界面

目前 SDK 中提供了几个示例 GUI 界面,有:表盘界面、播放器界面、电话界面、联系人界面、录音界面、地图界面、基准测试和配置可见的心率带界面。

目前使用 TabView 下面挂 tab 来实现界面之间的管理和跳转,以播放器界面为例:TabView 下面挂了“播放主界面”和“播放列表”两个 tab,播放主界面 tab 中又创建了一个“播放音量”的窗口。

../../_images/Watch_UI_Architecture.png

手表界面逻辑框图

GUI 更新事件

GUI 更新事件通常用于其他任务向 GUI 任务发送通信请求。该功能依赖于 GUI 控件绑定的事件与回调函数,以及 GUI 服务器的消息接收机制来实现。由于更新事件的处理发生在 GUI 服务器任务的最后阶段,因此可以更加安全地实现动态 GUI 控件的创建和销毁。

一个可被响应的 GUI 更新事件通常需要经过以下几个步骤:

更新事件枚举->更新事件和回调添加->更新事件推送->更新事件响应

更新事件枚举

watch SDK 中使用 gui_event_user_t 来表示更新事件的枚举集合。需要注意的是,更新事件的枚举值应该大于 GUI_EVENT_USER_DEFINE ,才可以被 watch SDK 正确识别并响应为 GUI 更新事件。

更新事件和回调添加

使用 gui_obj_add_event_cb() 接口来给指定的 GUI 控件添加 GUI 更新事件和回调函数。

该函数也用于其他 GUI 事件和回调的添加,通常会被封装于 GUI 控件的接口中。

更新事件推送

接口 gui_update_by_event(gui_event_user_t event, void *data, bool force_update) 用于 GUI 更新事件的推送。

  • 参数 event 和 data 用于填充 gui_msg_t 结构体的 sub_event 和 payload。

  • 参数 force_update 决定是否会再推送一条 GUI_EVENT_DISPLAY_ON 的消息用于执行 LCD 唤醒。

gui_update_by_event() 执行后,会有一条 GUI 消息通过 gui_send_msg_to_server() 接口发送至 GUI task。

更新事件响应

在 GUI server 运行的最后阶段,会执行 gui_recv_msg_to_server() 处理 GUI 任务接收到的消息。

当收到 event = GUI_EVENT_USER_DEFINE 时,GUI 会把消息再次传入到回调函数中,供用户自行处理:

  • 根据 sub_event 和 payload,以及 gui_obj_add_event_cb() 添加的事件进行匹配,如果匹配成功则执行添加的回调。

代码示例

该示例中,某个 tab 页面添加了 CALL_INCOMINGCALL_OUTGOING 事件以及对应回调,当在 app_hfp_bt_cback() 中通知 GUI 任务时,GUI 会跳转到对应的页面。

static void gui_call_status_update_cb(void *obj, uint16_t event, void *param)
{
   gui_log("gui_call_status_update_cb\n", event);
   switch (event)
   {
   case GUI_EVENT_CALL_INCOMING:
      {
            app_call_switch_tabs(CALL_INCOMING);
      }
      break;
   case GUI_EVENT_CALL_OUTGOING:
      {
            app_call_switch_tabs(CALL_OUTGOING);
      }
      break;
   default:
      break;
   }
}
...
void design_tab_call_main(void *parent)
{
   ...
   // add event to update incoming/outgoing/active/end call gui
   gui_obj_add_event_cb(parent, (gui_event_cb_t)gui_call_status_update_cb,
                        (gui_event_t)GUI_EVENT_CALL_INCOMING,
                        NULL);
   gui_obj_add_event_cb(parent, (gui_event_cb_t)gui_call_status_update_cb,
                        (gui_event_t)GUI_EVENT_CALL_OUTGOING,
                        NULL);
   ...
}
...
static void app_hfp_bt_cback(T_BT_EVENT event_type, void *event_buf, uint16_t buf_len)
{
   ...
   Case BT_EVENT_HFP_CALLER_ID_IND:
   gui_update_by_event(GUI_EVENT_CALL_INCOMING, NULL, true);
   ...
}

备注

  • HoneyGUI 的其他详细介绍,可以参考 GUI说明文档

  • 但请注意,鉴于软件开发版本存在差异,本文所涉及的 SDK 信息与链接文档可能不完全一致,请结合实际需求甄别采纳相关内容。

音频场景

音乐播放管理

header.bin 和 name.bin

对于 本地音乐播放连接耳机播放音乐 功能,MP3 文件是存在于 SD/NAND 中的,为了方便索引歌曲信息,SDK 中采用 header.bin 和 name.bin 两份文件来管理歌曲。 如下是对 header.bin 和 name.bin 存储格式的介绍。

name.bin 是保存各个歌曲的名称信息的,格式如下

name.bin 格式

歌曲名称1

歌曲名称2

歌曲名称3

名称数据1(Unicode)

名称数据2(Unicode)

名称数据3(Unicode)

header.bin 是用于描述歌曲总数和各个歌曲名称在 name.bin 中的偏移、长度等信息的。

header.bin 格式

数量

预留

HEAD INFO1

HEAD INFO2

2bytes

4bytes

sizeof(T_HEAD_INFO)

sizeof(T_HEAD_INFO)

对于每个文件的 HEAD INFO,按照如下定义。

typedef struct
{
uint32_t    offset;    // Start offset of the song name
uint16_t    length;       // Length of the song name
uint16_t    plIndex;    /* Play List Index, indicate which playlist the song belongs to.
                        0x0: belong to no playlist, 0x1: belong to playlist1.*/
uint8_t     isDeleted : 1;  /* flag of if song is deleted.
                                    1: deleted,
                                    0: not deleted, */
uint8_t     needToUnlink : 1;  /* flag of if song need to unlink.
                                    1: need to call audio_fs_unlink to delete the file
                                    2: not need to unlink*/
uint8_t     extension : 6;
uint8_t     fil_ext;            /* file extension*/
uint8_t     rsv;                /* Reserve for future usage, should set to "0" */
} __attribute__((packed)) T_HEAD_INFO;

因为 HEAD INFO 中提供每首歌曲的 offset 和 length,所以可以结合 name.bin 提取到各个歌曲的名称。

备注

  • 为方便应用层进行歌曲显示及列表管理,可将这两个 bin 文件信息读取并保存到 flash/PSRAM 中。需要播放指定名称的歌曲时,将对应歌曲名称传递给文件系统接口,便能直接完成该歌曲的播放。

音乐播放控制

音频控制包括播放、暂停、切歌、音量调节等。

常规播放、暂停、切歌、音量调节可以通过 MMI 命令完成,在不同播放模式下,上层共用 MMI 命令,在底下的功能代码中再做区分。

对于播放指定歌曲,可以通过 app_audio_play_by_name() 接口,通过传入歌曲名称信息,实现对应歌曲的播放。

备注

  • MMI 命令所定义的行为可参考 T_MMI_ACTION ,已实现的功能参考 app_mmi_handle_action()

本地音乐播放流程

本地音乐播放场景, MCU 读取本地音频文件,解析相关数据帧,送给 DSP 处理、输出到 codec 在手表喇叭端播放。

../../_images/Audio_Playback.png

本地音乐播放音频数据流示意图

主要处理流程:

  1. 本地音乐播放的入口函数为 app_audio_play_by_name()。传入音乐文件名调用后,随后开始音乐播放流程。

  2. 打开此文件,获取 MP3 handle,之后对该 MP3 文件的操作均依据此 handle 进行。

  3. 根据 MP3 文件的参数(采样率、比特率等)创建、配置并启动 audio track。

  4. 随后向 audio track 写入 MP3 数据帧即可播放出本地音频,驱动写数据帧的时机主要有两个:

    1. 当 audio track 启动后,便开始向 audio track 写数据,同时启动定时器,定时读取 MP3 数据帧写入。

    2. 当 audio track 检查出现低水位时候,APP 也会向 audio track 写数据,防止出现播放卡顿等情况。

  5. 直到用户音频播放完毕或用户主动停止播放时,释放 audio track 和 MP3 handle,关闭此文件。

../../_images/Flow_Playback.png

本地音乐播放流程

连接耳机播放音乐流程

连接耳机播放音乐场景,相比于本地播放的差异在于,DSP 处理 MP3 之后不会直接播放,而是再次进行编码,最后将编码后的 MCU 数据返回给上层,MCU 拿到数据后通过 A2DP 协议发送到耳机端,由耳机解码播放。

由于连接耳机播放音乐的场景,手表使用 A2DP 的角色是 source,所以这个场景也被称为 A2DP source 播放。

../../_images/Audio_SRC.png

连接耳机播放音频数据流示意图

主要处理流程:

  1. 与本地音乐播放一样,A2DP source 播放的入口函数也为 app_audio_play_by_name()。传入音乐文件名调用后,随后开始音乐播放流程。

  2. 打开此文件,获取 MP3 handle,之后对该 MP3 文件的操作均依此 handle 进行。

  3. 如果 A2DP stream 尚未打开,则打开 A2DP stream。打开 A2DP stream 成功后将返回 BT_EVENT_A2DP_STREAM_START_RSP 事件。

  4. 根据 MP3 文件的参数(采样率、比特率等)创建、配置并启动 audio pipe。

  5. 随后一边向 audio pipe 填入 MP3 数据帧,一边将 audio pipe 转换成的 SBC 数据帧通过 A2DP 协议发送给耳机,具体步骤为:

    1. 在 audio pipe 启动后,第一笔数据由 APP 驱动,向 audio pipe 中填充数据。

    2. 最初启动时,水位为低水位,在收到 AUDIO_PIPE_EVENT_DATA_FILLED 事件后需要向 audio pipe 中填充数据,将水位状态填充到高水位。

    3. audio pipe 成功转换出 SBC 数据帧后会通过 AUDIO_PIPE_EVENT_DATA_IND 上报,APP 将 SBC 数据帧存入缓冲队列,启动硬件定时器,等到发送周期到时发送出去。

    4. 流程运转起来后,硬件定时器通过上报 AUDIO_A2DP_SRC_EVENT_DATA_SEND 驱动着 APP 取数据和发送数据。

  6. 直到用户音频播放完毕或用户主动停止播放时,释放 audio pipe 和 MP3 handle,关闭此文件。

../../_images/Flow_SRC.png

连接耳机播放流程

播放手机音乐流程

播放手机音乐场景是手机端通过 A2DP 发送音频数据,手表端接收到音频数据后,下发给 DSP 进行解码,并输出到 codec 通过本地喇叭播放。

由于播放手机音乐场景,手表使用 A2DP 的角色是 sink,所以这个场景也被称为 A2DP sink 播放。

../../_images/Audio_Sink.png

播放手机音乐音频数据流示意图

主要处理流程:

  1. 请求开启 A2DP stream,同步创建用于给 DSP 发送数据的 audio track。

  2. audio track 启动后,手机端发送 A2DP stream 数据,会通过 BT_EVENT_A2DP_STREAM_DATA_IND 上报,MCU 将音频数据写入 audio track,送给 DSP 解码播放。

  3. 手机端发送 Stop 或 Close 命令,MCU 停止给 DSP 送音频数据,并释放对应的 audio track。

../../_images/Flow_Sink.png

播放手机音乐流程

电话音频处理流程

电话场景使用了 HFP 功能,电话音频通过 SCO 链路传输,手表端 MCU 需要和 DSP 配合,同时进行上下行数据的处理。

../../_images/Audio_Call.png

电话音频数据流示意图

主要处理流程:

  1. 首先请求 SCO 链路创建。

  2. SCO 链路创建完成后,创建 audio track 用于音频数据的读写。

  3. audio track 启动后,手机端发送音频数据,MCU 通过 audio track 将音频数据送给 DSP 解码播放。同时通过 audio track 读取 MIC 数据并发送给手机。

  4. 直到 SCO 链路断开,释放对应的 audio track。

../../_images/Flow_Call.png

电话处理流程

备注

  • Audio track、Audio pipe 等音频概念的详细介绍,可参考 Audio Subsystem

  • 但请注意,鉴于软件开发版本存在差异,本文所涉及的 SDK 信息与链接文档可能不完全一致,请结合实际需求甄别采纳相关内容。

蓝牙连接

手表的蓝牙连接主要是连接手机和耳机,需要使用 DSP 在线调试时,也支持连接电脑。针对不同设备,手表端连接的协议和处理流程有一些区别。

typedef enum
{
    T_DEVICE_TYPE_PHONE           = 0x00,
    T_DEVICE_TYPE_EARPHONE        = 0x01,
    T_DEVICE_TYPE_PC              = 0x02,//for DSP online debugging
    T_DEVICE_TYPE_DEFAULT         = 0x03,
} T_DEVICE_TYPE;

手表应用中一般会使用到的经典蓝牙协议,包括 A2DP,AVRCP,HFP,PBAP, SPP 等,各协议初始化时,会注册对应的回调接口。这样,当相关协议有事件返回时,会通过对应的回调接口通知上层。

bt_mgr_cback_register(app_hfp_bt_cback);
bt_mgr_cback_register(app_pbap_bt_cback);
bt_mgr_cback_register(app_avrcp_bt_cback);
...

备注

  • 对于各个协议的详细介绍,请参阅 BREDR Profiles

  • 但请注意,鉴于软件开发版本存在差异,本文所涉及的 SDK 信息与链接文档可能不完全一致,请结合实际需求甄别采纳相关内容。

绑定列表管理

手表最多支持绑定8个设备,包括1个手机和7个耳机,使用设备连接信息表 T_APP_BOND_DEVICE 对连接过的设备进行管理,表格的第一个位置约定为存手机。

设备连接信息表定义包括:设备地址、设备连接情况、设备类型、设备名称、设备优先级、当前是否存有设备、设备名称长度。

手表根据连接的状态对表格进行增/删/改/查,主要实现在 app_bond.c中。

为防止断电数据丢失,表格内容会同步保存在 FTL 中,手表每次上电后都会从 FTL 中获取数据读到全局变量。

typedef struct
{
    uint8_t bd_addr[6];
    bool used;
    T_DEVICE_TYPE device_type;
    uint16_t device_name[MAX_DEVICE_NAME_NUM];
    uint16_t priority;
    uint8_t  exist_addr_flag;
    uint8_t  device_name_len;
} T_APP_BOND_DEVICE;

typedef struct
{
    ...
    T_APP_BOND_DEVICE           bond_device[MAX_BOND_INFO_NUM];
    ...
}
T_APP_DB;

连接状态介绍

蓝牙开启 Inquiry scan 模式,才能够被其他的蓝牙设备搜索到,开启 Page scan 模式,才能够响应其他蓝牙设备的连接请求,底层结合 Page 和 Inquiry 定义了如下四种模式:

typedef enum t_bt_device_mode
{
    BT_DEVICE_MODE_IDLE                     = 0x00, /**< Page scan and inquiry scan disabled */
    BT_DEVICE_MODE_DISCOVERABLE             = 0x01, /**< Inquiry scan enabled */
    BT_DEVICE_MODE_CONNECTABLE              = 0x02, /**< Page scan enabled */
    BT_DEVICE_MODE_DISCOVERABLE_CONNECTABLE = 0x03, /**< Page scan and inquiry scan enabled */
} T_BT_DEVICE_MODE;

手表结合模式的设定,封装成了几个蓝牙状态,可以通过 app_bt_policy_enter_state() 设定,其转换关系如图所示:

  1. 蓝牙关闭状态下状态是 STATE_INIT,对应 BT_DEVICE_MODE_IDLE,不允许其他蓝牙设备发现和连接。

  2. 通过打开蓝牙开关切换到 STATE_STANDBY,对应 BT_DEVICE_MODE_DISCOVERABLE_CONNECTABLE,允许其他蓝牙设备发现和连接。

  3. 对于设备连接信息表中有绑定信息的,手表可以主动发起连接,会进入到 STATE_LINKBACK 状态,此时不改变当前 Inquiry scan 和 Page scan 的设定。

  4. 当回连结束、或者在其他情况下连接成功或失败都会通过 app_bt_policy_set_state() 根据当前的连接手机/耳机情况切换到相应状态:STATE_STANDBY、STATE_CONNECTED_PHONE、STATE_CONNECTED_EARPHONE、STATE_CONNECTED_TWO, 状态下会对应更新 Inquiry scan 和 Page scan 的模式,可以根据用户需求自行修改。

../../_images/BT_State.png

蓝牙状态切换图

连接方式介绍

连接手机方式
  1. 首次连接手机,需要保证蓝牙开关打开即蓝牙不在 STATE_INIT 状态下进行,在手机端搜索发现手表设备,并主动发起连接。

  2. 连接成功后会默认将已连接手机的信息存在设备连接信息表中,在手机也没有删除手表信息的情况下,断线后可以在手表端主动发起回连手机。

连接耳机方式

手表连接耳机设备的方式有三种:

  1. 主动扫描连接,首次连接新的耳机设备时必须通过该方式。扫描连接时,如果有连接中的耳机,会断开当前的连接,去连接主动扫描连接的设备。

  2. 在已配对列表直接发起连接,适用于配对列表中有耳机的情况,同时如果有连接中的耳机,会断开当前连接的耳机音箱设备,连接要切换的设备。

  3. 耳机回连手表,耳机回连必须在手表没有连接耳机或音箱设备的场景下才能回连成功。

回连逻辑介绍

手表回连需要依赖设备连接信息表,在有绑定信息的情况下,回连的场景主要有以下几种:

  1. 主动在 UI 的绑定列表中发起回连。

  2. 音乐模式切换过程中会主动回连:

    1. 切换到本地播放时,如果设备连接信息表中有手机,将会回连手机,此时主要连接的协议是 HFP、PBAP,支持打电话、同步通讯录的功能。

    2. 切换到 A2DP Sink 播放时,首先需要判断当前 A2DP 的角色是不是 Sink,如果不是,需要先切成 Sink 角色。 之后如果设备连接信息表中有手机,将会回连手机,此时连接的协议是 HFP、PBAP 还有 A2DP、AVRCP。除了支持打电话、同步通讯录的功能以外,还支持手机音乐播放。

    3. 切换到连接耳机播放音乐功能时,同样需要先判断 A2DP 的角色,如果不是 Source,需要先切换成 Source 角色,之后再根据切换模式时有无手机连接决定是否要回连手机。 最后如果连接信息表中存在耳机设备,将主动回连最高优先级(最近连接过)的耳机设备。

../../_images/BT_Linkback_Case1.png

音乐模式切换图

备注

  • 如果 UI 在手机音乐或者耳机音乐模式下,但没有用相应设备连接,将不能正常播放音乐。

  1. 连接 ACL 链路成功后,根据功能主动发起 Profile 的连接,在断线或连接失败时,也需要根据其原因设置是否需要发起 ACL 回连或者 Profile 回连。

../../_images/BT_Linkback_Case2.png

蓝牙交互中的回连

备注

  • 回连流程中,一次只能回连一个设备。

  • 回连包括 ACL 链路的连接和 Profiles 的连接。

常见问题

详情请参考 常见问题

参考文档

  1. Bluetooth Common SDK https://docs.realmcu.com/sdk/rtl87x3ep/common/en/latest/index.html

  2. HoneyGUI https://docs.realmcu.com/gui/cn/latest/index.html

注意

  • 在本文档中,为方便用户详细了解相关内容,我们引用 Bluetooth Common SDK 和 HoneyGUI 中的部分链接,供参考查阅。 需要特别说明的是,由于文档所基于的软件开发版本分支不完全一致,本文档所提及的链接内容并非均适用于当前的版本环境。 在参考文档进行操作或查阅时,请结合实际情况进行选择性采纳。

  • 若参考文档的相关信息无法直接应用,或在参考过程中产生疑问,欢迎随时联系我们获得进一步的技术支持与指导, 以免造成信息误读或操作风险。感谢您的理解与配合!