under NDA: 泰凌 Kite BLE SDK 开发指南
under NDA: 泰凌 Kite BLE SDK 开发指南
DA
AN-19011501-C5
rN
Ver1.4.0
TELINK SEMICONDUCTOR
de
2019/9/26
un
ed
简介:
ar
Published by
Telink Semiconductor
© Telink Semiconductor
All Right Reserved
DA
Legal Disclaimer
This document is provided as-is. Telink Semiconductor reserves the right to make
rN
improvements without further notice to this document or any products herein. This
document may contain technical inaccuracies or typographical errors. Telink
Semiconductor disclaims any and all liability for any errors, inaccuracies or
de
incompleteness contained herein.
un
Information:
For further information on the technology, product and business term, please
Sh
AN-19011501-C5 1 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
版本历史
DA
修订章节:
1.4.2 BLE master demo,
1.4.3 feature demo 和 driver demo,
2.1.3.2 Flash 空间的操作,
2.3.4 GPIO 数字状态在 deepsleep
rN
retention mode 失效,
de
3.1.2.2 Telink BLE slave,
3.2.4.5 Conn state Slave role 时序,
un
3.2.9.13 blc_ll_setScanEnable,
Sh
3.2.9.14 blc_ll_createConnection,
3.2.9.20 blc_ll_getCurrentState,
3.3.2 L2CAP,
3.3.3 ATT & GATT,
4.1.1 低功耗模式,
新增章节:
3.3.1 BLE host 介绍,
3.3.4 SMP,
3.3.5 GAP,
12.2 32k 时钟源选择。
根据 SDK 3.3.1,修订章节:
1.1 软件组织架构,
1.3.0 2.1.2 Sram 空间分配, 2019/5 TYF, WSH, Cynthia
4.2.6 API
blc_pm_setDeepsleepRetentionType
AN-19011501-C5 2 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
新增章节:
1.2 适用 IC 介绍,
1.3 software bootloader 介绍
修订章节: FQH,TYF, WSH, JF
1.4.0 2019/8
1.3 software bootloader 介绍
2.1.2.1 Sram 和 Firmware 空间
3.2.3.1 Link Layer 状态机初始化
3.2.9.13 blc_ll_setScanEnable
3.3.3.3 Attribute PDU & GATT API
3.3.5.2 GAP event
7.1.1 FLASH 存储架构
DA
7.1.3 修改 Firmware size 和 boot
address
根据 SDK 3.4.0,新增章节:
3.2.10 Coded PHY/2M PHY
rN
3.2.11 Channel Selection
de
Algorithm#2
3.2.12 Extended Advertising
un
AN-19011501-C5 3 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
目录
1 SDK 介绍 ............................................................................................................... 17
1.1 软件组织架构................................................................................................ 17
1.1.1 main.c ...................................................................................................... 18
1.2 适用 IC 介绍 .................................................................................................. 20
DA
1.4 Demo 介绍 ..................................................................................................... 22
1.4.1 rN
BLE slave demo ....................................................................................... 24
1.4.2 BLE master demo .................................................................................... 24
de
1.4.3 feature demo 和 driver demo ................................................................ 25
2 MCU 基础模块 ..................................................................................................... 26
un
AN-19011501-C5 4 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
3.1.2.2 Telink BLE slave ........................................................................................ 57
3.2 rN
BLE controller ................................................................................................. 60
AN-19011501-C5 5 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
3.2.9.2 API 返回类型 ble_sts_t ......................................................................... 101
AN-19011501-C5 6 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
3.3.1 BLE host 介绍........................................................................................ 127
3.3.2 rN
L2CAP .................................................................................................... 127
3.3.2.1 注册 L2CAP 数据处理函数 .................................................................. 128
de
3.3.2.2 更新连接参数 ...................................................................................... 129
AN-19011501-C5 7 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
4.2.3 相关变量............................................................................................... 201
4.2.4 API bls_pm_setSuspendMask ............................................................... 201
4.2.5
rN
API bls_pm_setWakeupSource ............................................................. 203
4.2.6 API blc_pm_setDeepsleepRetentionType ............................................ 204
de
4.2.7 PM 软件处理流程 ................................................................................ 204
un
AN-19011501-C5 8 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
5.2.2.3 低压报警 .............................................................................................. 236
5.2.3
rN
低电检测和 Amic Audio ....................................................................... 238
de
6 Audio................................................................................................................... 240
AN-19011501-C5 9 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
8.4 Deepsleep 唤醒快速扫键(wake_up fast keyscan) ................................ 267
8.5 Repeat Key 处理 .......................................................................................... 269
8.6
rN
卡键处理...................................................................................................... 270
9 LED 管理 ............................................................................................................. 273
de
9.1 LED 任务相关调用函数 .............................................................................. 273
un
11 IR ......................................................................................................................... 282
11.1 PWM Driver.................................................................................................. 282
AN-19011501-C5 10 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
11.1.10.3 IR DMA FIFO mode 的开启与停止 .................................................... 290
AN-19011501-C5 11 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
12.5.1.4 emi_con_prbs9 .................................................................................... 307
13 附录 .................................................................................................................... 315
13.1 附录 1:crc16 算法 ..................................................................................... 315
ed
AN-19011501-C5 12 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
图目录
图 1-1 SDK 文件结构 ......................................................................................... 17
图 1-2 不同 IC 对应的 bootloader 以及 boot.link 路径.................................... 20
图 1-3 software bootloader 设置 ....................................................................... 22
图 1-4 BLE SDK 提供的 demo code ................................................................... 23
图 2-1 MCU 地址空间分配 ............................................................................... 26
图 2-2 各 IC 在 16k 和 32k retention 对应的 Sram 空间分配 .......................... 27
图 2-3 Sram 空间分配& Firmware 空间分配 ................................................... 28
图 2-4 list 文件 section 统计 ............................................................................. 35
DA
图 2-5 list 文件 section 地址 ............................................................................. 36
图 2-6 512K FLASH 地址分配 ............................................................................ 43
rN
图 2-7 system clock & System Timer .................................................................. 45
图 3-1 BLE SDK 标准架构 .................................................................................. 55
de
图 3-2 Host 和 Controller 的 HCI 数据交互 ...................................................... 56
图 3-3 8258 hci 架构 .......................................................................................... 57
un
图 3-6 State diagram of the Link Layer state machine in BLE Spec .................... 61
图 3-7 Telink Link Layer state machine .............................................................. 61
ar
AN-19011501-C5 13 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
图 3-29 LE Connection Update Complete Event ................................................. 86
图 3-30 连接请求包单元 PDU ........................................................................... 92
rN
图 3-31 BLE 协议栈 LL_CONNECTION_UPDATE_REQ 格式 ................................ 96
图 3-32 BLE 协议栈广播包格式 ....................................................................... 102
de
图 3-33 BLE 协议栈里 Advertising Event .......................................................... 104
图 3-34 BLE 协议栈四种广播事件 ................................................................... 105
un
AN-19011501-C5 14 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
图 3-60 ATT Permission 定义 ............................................................................. 158
图 3-61 本地设备配对状态 ............................................................................. 161
rN
图 3-62 抓包显示 Pairing Disable .................................................................... 162
图 3-63 传统配对模式下 MITM、OOB flag 使用规则 ................................... 165
de
图 3-64 根据不同 IO 能力映射 KEY 产生方法................................................ 165
图 3-65 抓包显示 Pairing Peer Trigger ............................................................. 170
un
图 4-4 suspend & deepsleep retention timing & power ................................... 211
图 4-5 T_init timing ............................................................................................ 215
图 4-6 sleep timing for conn_latency valid ........................................................ 221
图 4-7 Early wake_up at app_wakup_tick ......................................................... 227
图 6-1 audio 数据抓包 ...................................................................................... 242
图 6-2 MIC service in Attribute Table ............................................................. 243
图 6-3 数据压缩处理 ........................................................................................ 244
图 6-4 压缩算法对应数据 ................................................................................ 246
图 7-1 Flash 存储结构 ....................................................................................... 247
图 7-2 BLE 协议栈 Write Command 格式 ......................................................... 251
AN-19011501-C5 15 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
图 11-4 demo IR 协议 ........................................................................................ 292
图 11-5 IR timing 1 ............................................................................................. 293
rN
图 11-6 IR timing 2 ............................................................................................. 294
图 12-1 24M 晶体电路 ...................................................................................... 300
de
图 12-2 EMI test tool.......................................................................................... 310
图 12-3 选择芯片型号 ..................................................................................... 310
un
AN-19011501-C5 16 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
1 SDK 介绍
1.1 软件组织架构
DA
rN
de
un
ed
ar
Sh
application:提供一些通用的应用处理程序,如 print、keyboard 等。
boot:提供芯片的 software bootloader,即 MCU 上电启动或 deepsleep 唤醒
后的汇编处理过程,为后面 C 语言程序的运行搭建好环境。
common:提供一些通用的跨平台的处理函数,如内存处理函数、字符串处
理函数等。
drivers:提供与 MCU 紧密相关的硬件设置和外设驱动程序,如 clock、flash、
i2c、usb、gpio、uart 等。
AN-19011501-C5 17 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
1.1.1 main.c
包括 main 函数入口,系统初始化的相关函数,以及无限循环 while(1)的写法,
建议不要对此文件进行任何修改,直接使用固有写法。
DA
blc_pm_select_internal_32k_crystal(); //选择内部 32k rc 作为 32k counter 时钟源
cpu_wakeup_init();//MCU 最基本的硬件初始化
rN
int deepRetWakeUp = pm_is_MCU_deepRetentionWakeup();
if( deepRetWakeUp ){
ed
else{
irq_enable(); //开全局中断
while (1) {
#if (MODULE_WATCHDOG_ENABLE)
wd_clear(); //clear watch dog
#endif
AN-19011501-C5 18 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
1.1.2 app_config.h
用户配置文件,用于对整个系统的相关参数进行配置,包括 BLE 相关参数、
GPIO 的配置、PM 低功耗管理的相关配置等。
后面介绍各个模块时会对 app_config.h 中的各个参数的含义进行详细说明。
DA
理文件。
irq_blt_sdk_handler ();
……
ar
}
Sh
AN-19011501-C5 19 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
1.2 适用 IC 介绍
8251 512 kB 32 kB
8253 512 kB 48 kB
8258 512 kB 64 kB
DA
异外,其他部分完全共用。
AN-19011501-C5 20 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
以 cstartup_8258_RET_16K.S 为例,第一句#ifdef
MCU_STARTUP_8258_RET_16K 说明了只有当 user 定义了
MCU_STARTUP_8258_RET_16K 时,该 bootloader 才会生效。
用户可以根据实际使用的 IC 以及是否使用 deep retention(16K 或者 32k)功
能选择不同的 software bootloader。
DA
以 8258_ble_remote 为例说明如何选择 8258 的 software bootloader 改为
deepsleep retention 32K sram。
1. user 可以在 8258_ble_remote 工程设置中定义
-DMCU_STARTUP_8258_RET_32K 即可,如下图所示。
rN
de
un
ed
ar
Sh
AN-19011501-C5 21 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
图 1-3software bootloader 设置
注意:根据前面介绍我们知道 8251、8253 和 8258 的 Sram size 是不一样,所以,
实际用户在选择不同的 software bootloader 文件后还需要修改 SDK 根目录下的
boot.link 文件(根据下表对应关系将其中的 link 文件中的内容替换到 SDK 根目录
下的 boot.link 中),不同 IC 的 software bootloader 以及 boot.link 选用关系见下
表。
Retention
type 16kB retention 32kB retention
IC
boot_16k_retn_8251_8253_8258.link boot_32k_retn_8251.link
8251
cstartup_8251_RET_16K.S cstartup_8251_RET_32K.S
DA
boot_16k_retn_8251_8253_8258.link boot_32k_retn_8253_8258.link
8253
cstartup_8253_RET_16K.S cstartup_8253_RET_32K.S
8258
rN
boot_16k_retn_8251_8253_8258.link boot_32k_retn_8253_8258.link
cstartup_8258_RET_16K.S cstartup_8258_RET_32K.S
de
2. 根据上面的例子以及映射表,我们知道 software bootloader 文件为
cstartup_8258_RET_32K.S,需要选择将
un
1.4 Demo 介绍
AN-19011501-C5 22 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
rN
de
un
ed
ar
Sh
AN-19011501-C5 23 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
Controller,HCI 接口与
8258 hci BLE controller No
其他 MCU host 通信
BLE controller +
8258 ble remote 遥控器应用 主控 MCU
host
最简单的 slave
DA
BLE controller +
8258 ble sample demo,只有广播和 主控 MCU
host
连接功能
rN
8258 hci 是一个 BLE slave controller,提供了基于 USB/UART 的 HCI,和其他
de
MCU 的 host 通信,形成一个完整的 BLE slave 系统。
8258 ble remote/8258 module 都是 Telink 提供的完整的 BLE slave stack。8258
un
AN-19011501-C5 24 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
进行选择性的定义,即可切换到不同 feature test 的 Demo。
rN
8258 driver test 对基本的 driver 给出了 sample code,供用户参考并实现自己
的 driver 功能。本文档 driver 部分会详细介绍各个 driver。
在 8258 driver test 工程里面 app_config.h 中对宏“DRIVER_TEST_MODE”进行
de
选择性的定义,即可切换到不同 driver test 的 Demo。
un
ed
ar
Sh
AN-19011501-C5 25 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
2 MCU 基础模块
DA
0x84FFFF
Sram rN
Peripheral
Space
0x840000
de
0x810000
Register
0x800000
un
0x7FFFFF
ed
Firmware Flash
ar
Space
Sh
0x000000
AN-19011501-C5 26 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
32kB Sram 地址空间范围为 0x840000 ~ 0x848000,48kB Sram 地址空间范围
为 0x840000 ~ 0x84C000,64kB Sram 地址空间范围为 0x840000 ~ 0x850000。
下图是 8258、 8253、8251 在 16k retention 和 32k retention 模式下对应的 SRAM
空间分配说明。需要注意的是 IC 为 8251 且使用 deepsleep retention 32K sram 模
rN
式时,其 sram 空间分配的各个段是动态调整的,具体可以参考对应的 software
de
bootloader 和 link 文件。
64k SRAM 64k SRAM 48k SRAM 48k SRAM 32k SRAM 32k SRAM
un
0x848900
Cache(2.25 K) 0x848900
Cache(2.25 K) 8251 16k retn 8251 32k retn
unused data
data
sram area
Sh
Bss
Bss unused
sram area
unused
sram area normal stack normal stack
0x84C000 0x84C000
AN-19011501-C5 27 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
下面以 Sram size 64K 的 IC:8258、SDK 中默认的 deepsleep retention 16K sram
模式为例详细介绍 Sram 区域各个部分。如果 Sram size 是其他值,或者 deepsleep
retention 32k sram 模式,用户可以类推一下即可。
64k Sram 对应的 Sram 和 Firmware 空间分配如下图所示。
Sram Flash
0x00000
0x840000 vector vector
_ramcode_size_
ram_code ram_code
deepsleep power on
load _rstored_ _retention_size_
retention
area(16 K) retention data retention data
wasteful wasteful
sram area flash area
0x844000 0x04000
Cache(2.25 K)
0x844900
data
Firmware
DA
bss
unused text
sram area
normal stack
rN
0x850000
de
rodata
data initial value
un
ed
ar
512K
Sh
AN-19011501-C5 28 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
1) vectors、ram_code
“vectors”段是汇编文件 cstartup_8258_RET_16K.S 对应的程序,是软件启动代
码(software bootloader)。
“ramcode”段是 Flash Firmware 中需要常驻内存的 code,对应 SDK 中所有加
了关键字“_attribute_ram_code_”的函数,比如 flash erase_sector 函数:
_attribute_ram_code_ void flash_erase_sector(u32 addr);
函数常驻内存有两个原因:
DA
一是某些函数由于涉及到和 Flash MSPI 四根管脚的时序复用,必须常驻内存,
如果放到 flash 中就会出现时序冲突,造成死机,如 flash 操作相关所有函数;
rN
二是放到 ram 中执行的函数每次被调用时不需要从 flash 重新读取,可以节
省时间,所以对于一些执行时间有要求的函数可以放到常驻内存,提高执行
效率。SDK 中将 BLE 时序相关的一些经常要执行的函数常驻到内存,大大降
de
低执行时间,最终达到节省功耗。
用户如果需要将某个函数常驻内存,可以仿照上面 flash_erase_sector,在自
un
己的函数上添加关键字“_attribute_ram_code_”,编译之后就能在 list 文件
中看到该函数在 ramcode 段了。
ed
AN-19011501-C5 29 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
2) retention_data
8258 的 deepsleep retention mode 支持 MCU 进入 deepsleep 后,Sram 的前
16K/32K 可以不掉电保持住 Sram 上的数据不丢失。
程序中的全局变量如果直接编译的话,会分配在”data”段或者”bss”段,这两
段的内容不在前 16K 的 retention 区域,进入 deepsleep 会掉电丢失。
如果希望一些特定的变量在 deepsleep(deepsleep retention mode)期间能够
不掉电保存,只要将它们分配到”retention_data”段即可,方法是在定义变量
时添加关键字”_attribute_data_retention_”。以下为几个示例:
_attribute_data_retention_ int AA;
_attribute_data_retention_ unsigned int BB = 0x05;
_attribute_data_retention_ int CC[4];
DA
_attribute_data_retention_ unsigned int DD[4] = {0,1,2,3};
rN
参考下面即将要介绍的“data/bss”段可知,data 段的全局变量对应的 initial
value 需要提前存放在 flash 上;bss 段的变量 initial value 为 0,无需提前准备,
bootloader 运行时直接在 sram 上设为 0 即可。
de
但”retention_data”段的全局变量不管 initial value 是否为 0,会无条件准备好
它们的 initial value,存放在 flash 的 retention_data area 上。上电(或 normal
un
AN-19011501-C5 30 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
以上配置的含义为:编译的时候看到含有“retention_data”关键字的变量,
在 flash firmware 中分布的起始地址为“_rstored_”,在 Sram 上对应的地址
为 0x840000 + (_rstored_)。而“_rstored_”这个值就是"ram_code” section 的
结尾。
用户可以通过下面的一种方式修改错误:
1. 减少所定义的”_attribute_data_retention_”属性的数据。
2. 选择切换为 deepsleep retention 32K Sram,详细配置方法参考小节 1.3
DA
当“_retention_size_”不超过 16K 时(假设为 12K),flash 上存在一片 4K
rN
的“wasteful flash area”(无效 flash 区域),对应的 firmware binary file 上
可以看到 12K~16K 的内容全部是无效的“0”,拷贝到 Sram 后,Sram 上也
会有 4K 的“wasteful sram area”(无效 SRAM 区域)。
de
如果用户不希望浪费太多的 flash/sram,可以适当增加自己的 ram_code 和
un
3) Cache
Cache 是 MCU 的指令高速缓存,且必须被配置为 Sram 中的一段才可以正常
运行。Cache size 是固定的,包括 256 字节的 tag 和 2048 字节的 Instructions
cache,总共 0x900 = 2.25K。
常驻内存的 code 可以直接从内存中读取并执行,但 firmware 中可以常驻内
存的 code 只是很小的一部分,剩下绝大部分都还在 Flash 中。根据程序的局
部性原理,可以将一部分 Flash code 存储到 Cache 中,如果当前需要执行的
code 在 Cache 里,直接从 Cache 读取指令并执行;如果不在 Cache 中,则从
Flash 读取 code 并覆盖 Cache 中之前的 code,再从 Cache 中读取指令执行。
图 2-3 中 firmware 的 text 段是没有放到 Sram 中的 Flash code,这部分 code
符合程序局部性原理,需要一直被 load 到 Cache 中才能被执行。
AN-19011501-C5 31 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
rN
de
un
ed
ar
Sh
AN-19011501-C5 32 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
4) data / bss
“data” 段是 Sram 中存放程序已经初始化的全局变量,即 initial value 非 0 的
全局变量。”bss” 段是 Sram 中存放程序未初始化的全局变量,即 initial value
为 0 的全局变量。这两部分是连在一起的,data 段后紧跟 bss 段,所以这里
作为一个整体介绍。
“data” + “bss” 段紧跟 Cache,起始地址即 Cache 的结束地址 0x844900。下面
为 boot.link 中的代码,直接定义 Sram 上 data 段开始的地址:
. = 0x844900;
.data :
DA
5) stack / unused area rN
对于默认 64K 的 Sram ,”stack”是从最高地址 0x850000(48K Sram 对应地址
为 0x84C000,32K Sram 对应地址为 0x848000)开始的,其方向为从下往上
de
延伸,即 stack 指针 SP 在数据入栈时自减,数据出栈时自加。
默认情况下,SDK library 使用了 stack 的 size 不超过 256 byte,但由于 stack
un
6) text
“text”段是 Flash firmware 中所有非 ram_code 函数的集合。程序中的函数如
果加了“_attribute_ram_code_”,会被编译为 ram_code 段,其他没有加这
个关键字的函数就全部编译到了”text”段。一般情况下,”text”段是 Firmware
AN-19011501-C5 33 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
前面介绍的“data”段是程序中已初始化的全局变量,比如定义全局变量如下:
int testValue = 0x1234;
rN
那么编译器就会将初值 0x1234 存放到“data initial value”中,在运行 bootloader
时,会将该初值拷贝到 testValue 对应的内存地址。
de
un
这里以 BLE slave 最简单的 demo 8258 ble sample 为例,结合“Sram 空间分
ed
analyze”。
以下分析中,会出现多处截图,均来自 boot.link、cstartup_8258_RET_16K.S、
Sh
AN-19011501-C5 34 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
图 2-4 list 文件 section 统计
rN
根据 section 统计先将需要了解的信息列出来,和后面具体的 section 介绍都
de
会一一对应。
1) vectors:从 Flash 0 开始,Size 为 0x170,计算出结束地址为 0x170;
un
AN-19011501-C5 35 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
rN
de
un
ed
1) vector
“vector”段在 flash firmware 中起始地址为 0,结束地址为 0x170(最后一笔数
据地址为 0x16e~0x16f),size 为 0x170。上电搬移到 Sram 后,在 Sram 上的
地址为 0x840000 ~ 0x840170。
2) ram_code
“ram_code” section 起始地址为 0x170,结束地址为 0x2560(最后一笔数据地
址为 0x255c~0x255f)。上电搬移到 Sram 后,在 Sram 上的地址为 0x840170 ~
0x842560。
AN-19011501-C5 36 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
3) retention_data
“retention_data”在 flash 中起始地址“_rstored_”为 0x2560,也是”ram_code”
的结尾。
“retention_data”在 Sram 中起始地址为 0x842560,结束地址为 0x843248(最
后一笔数据地址为 0x843244~0x843247)。
DA
4) Cache
rN
Cache 在 Sram 中地址范围为:0x844000~0x844900。
Cache 的相关信息在 list 文件中不会体现出来。
de
5) text
un
Section 统计中数据一致。
ar
6) rodata
Sh
7) data
“data”段在 Sram 上起始地址为 Cache 的结束地址 0x844900,list 文件 Section
统计部分给出的 size 为 0x2c。
“data”段在 Sram 上的结束地址为 0x84492c(最后一笔数据地址为
0x844928~0x84492b)。
8) bss
“bss”段在 Sram 上起始地址为“data”段的结束地址 0x844930(16 字节对齐),
list 文件 Section 统计部分给出的 size 为 0x259。
AN-19011501-C5 37 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
2.1.3.1 外设空间的读写操作
DA
外设空间(register 和 sram)的读写操作直接用指针访问实现:
u8 x = *(volatile u8*)0x800066; //读 register 0x66 的值
rN
*(volatile u8*)0x800066 = 0x26; //给 register 0x66 赋值
//读 sram 0x40000-0x40003 地址的值
de
u32 y = *(volatile u32*)0x840000;
*(volatile u32*)0x840000 = 0x12345678; //给 sram 0x40000-0x40003 地址赋值
un
程序中使用函数 write_reg8、write_reg16、write_reg32、read_reg8、
ed
read_reg16、read_reg32 对外设空间进行读写,其实质是指针操作。更多信息,
请参照 drivers/8258/bsp.h。
ar
AN-19011501-C5 38 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
flash 读写操作使用 flash_read_page 和 flash_write_page 函数来实现。
void flash_read_page(u32 addr, u32 len, u8 *buf);
void
rN
flash_write_page(u32 addr, u32 len, u8 *buf)
de
flash_read_page 函数读取 flash 上的内容:
void flash_read_page(u32 addr, u32 len, u8 *buf);
un
u8 data[6] = {0 };
flash_read_page(0x11000, 6, data); //读 flash 0x11000 开始的 6 个 byte 到
ed
data 数组。
ar
AN-19011501-C5 39 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
2) flash 擦除操作
使用 flash_erase_sector 函数来擦除 flash。
void flash_erase_sector(u32 addr);
一个 sector 为 4096 byte,如 0x13000 ~0x13fff 是一个完整的 sector。
addr 必须是一个 sector 的首地址,该函数每次擦除整个 sector。
DA
擦除一个 sector 的时间会比较长,16M 系统时钟时,大约需要 30~100ms 甚
至更长时间。
rN
3) flash 读写和擦除操作对系统中断的影响
de
上面介绍的三个 flash 操作函数 flash_read_page、flash_write_page、
flash_erase_sector 在执行时,都必须先将系统中断关掉 irq_disable(),操作结
un
中断影响很小,但连续读写的地址越长,时间就越长。所以强烈建议用户在
main_loop 里 BLE 连接状态时,不要连续读写太长的地址。
flash_erase_sector 函数的执行时间为几十到几百个 ms,所以在主程序的
main_loop 里,一旦进入 BLE 连接状态,不允许去调用 flash_erase_sector 函
数,否则会破坏 BLE 收发包的时间点,造成连接断开。如果是无法避免在 BLE
连接的时候需要擦除 flash,请根据本文档后面介绍的 Conn state Slave role 时
序保护实现方法来操作。
4) 读 flash 可以使用指针访问来实现
BLE SDK 的 firmware 存储在 flash 上,程序运行时,只是将 flash 前一部分的
代码作为常驻内存代码放在 ram 上执行,剩余的绝大部分代码根据程序的局
部性原理,在需要的时候从 flash 读到 ram 高速缓存 cache 区域(简称 cache)。
MCU 通过自动控制内部 MSPI 硬件模块,读取 flash 上的内容。
AN-19011501-C5 40 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
user_init 里面读取 flash 上的校准值并设定到对应的 register 时,都是采用指
针访问 flash 的方式实现的,请参考 SDK 里函数 rN
static inline void blc_app_loadCustomizedParameters(void);
de
flash_write_page 来实现)。
ed
据没有被其他内容覆盖时,又有新的访问该数据的请求发生,此时 MCU 会
直接用 cache 里缓存的内容作为结果。如果 user 的代码出现下面这种情况:
Sh
u8 result;
result = *(volatile u16*)0x40000; //指针读取 flash
u8 data = 0x5A;
flash_write_page(0x40000, 1, &data );
result = *(volatile u16*)0x40000; //指针读取 flash
if(result != 0x5A){ ..... }
AN-19011501-C5 41 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
FLASH 存储信息以一个 sector 的大小(4K byte)为基本的单位,因为 flash
的擦除是以 sector 为单位的(擦除函数为 flash_erase_sector),理论上同一种类
的信息需要存储在一个 sector 里面,不同种类的信息需要在不同的 sector(防止
擦除信息时将其他类的信息误擦除)。所以建议 user 在使用 FLASH 存储定制信
rN
息时遵循“不同类信息放在不同 sector”的原则。
de
un
ed
ar
Sh
AN-19011501-C5 42 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
0x80000
User Data
Area
0x78000
Customed value
0x77000
MAC address
0x76000
Pair&Sec info
0x74000
User Data
Area
DA
0x40000
0x20000
de
Old Firmware
un
bin
0x00000
ed
#endif
AN-19011501-C5 43 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
3) 第三个 64bytes 用来存储外部 32k crystal 的电容校准值,后面的第四个、
第五个等留着做其他可能会校准的值。
rN
3. 0x74000 ~ 0x75FFF 这两个 sector 被 BLE 协议栈系统占用,用来存储配对和加
de
密信息。user 也可以修改这两个 sector 的位置,size 固定为两个 sector 8K,
无法修改,可以调用下面函数修改配对加密信息存储的起始地址:
un
AN-19011501-C5 44 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
2.2 时钟模块
DA
Divider
FHS
RC
Oscillator
RC_24M
rN Divider
system clock
24MHz Crystal
de
Pad_24M
Oscillator
un
3/2 16M
Syetem Timer
Divider
ed
enum{
CLOCK_SYS_CLOCK_1S = CLOCK_SYS_CLOCK_HZ,
CLOCK_SYS_CLOCK_1MS = (CLOCK_SYS_CLOCK_1S / 1000),
CLOCK_SYS_CLOCK_1US = (CLOCK_SYS_CLOCK_1S / 1000000),
};
DA
pwm_set_cycle_and_duty(PWM0_ID, (u16) (1000 * CLOCK_SYS_CLOCK_1US),
(u16) (500 * CLOCK_SYS_CLOCK_1US) );
rN
System Timer 是固定的 16M,所以对于这个 timer,SDK code 中使用如下的
de
数值来表示 S、mS 和 uS。
//system timer clock source is constant 16M, never change
un
enum{
CLOCK_16M_SYS_TIMER_CLK_1S = 16000000,
CLOCK_16M_SYS_TIMER_CLK_1MS = 16000,
ed
CLOCK_16M_SYS_TIMER_CLK_1US = 16,
};
ar
API 操作时,都使用上面类似”CLOCK_16M_SYS_TIMER_CLK_xxx”的方式来表示时
间。
void sleep_us (unsigned long microsec);
unsigned int clock_time(void);
AN-19011501-C5 46 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
该 BLE SDK 整个 BLE 时序都是基于 System Timer tick 设计的,程序中也大量
使用这个 System Timer tick 来完成各种计时和超时判断,强烈推荐 user 使用这个
System Timer tick 来实现一些简单的定时和超时判断。
rN
比如要实现一个简单的软件定时。软件定时器的实现基于查询机制,由于是
de
通过查询来实现,不能保证非常好的实时性和准备性,一般用于对误差要求不是
特别苛刻的应用。实现方法:
un
的需求清除计时器或启动新一轮的定时。
假设需要定时的时间为 100 ms,查询计时是否到达的写法为:
Sh
AN-19011501-C5 47 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
a_trig_flg = 0;
B();
} rN
}
de
2.3 GPIO 模块
un
代码中涉及到对寄存器的操作,请参考文档《gpio_lookuptable》来理解。
ar
Sh
2.3.1 GPIO 定义
AN-19011501-C5 48 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
7 个 GPIO 比较特殊,需要注意:
1) MSPI 的 4 个 GPIO。这 4 个 GPIO 是 MCU 系统总线中主 SPI 总线,用于读写
flash 操作,上电默认为 spi 状态,user 永远不能操作它们,程序中不能使用。
这个 4 个 GPIO 为 PE0、PE1、PE2、PE3。
2) SWS(Single Wire Slave),用于 debug 和烧写 firmware,上电默认为 SWS 状态,
程序中一般不使用。8x5x 的 SWS 管脚为 PA7。
3) DM 和 DP,上电默认为 GPIO 状态。当需要 USB 功能时,DM 和 DP 需要使用;
当不需要 USB 时,可以作为 GPIO 使用。8x5x 的 DM、DP 管脚为 PA5、PA6。
DA
1) func(功能配置:特殊功能/一般 GPIO),如需要使用输入输出功能,需配置为
一般 GPIO。
rN
void gpio_set_func(GPIO_PinTypeDef pin, GPIO_FuncTypeDef func);
2) ie(input enable):输入使能
void gpio_set_input_en(GPIO_PinTypeDef pin, unsigned int value);
ed
4) oe(output enable):输出使能
void gpio_set_output_en(GPIO_PinTypeDef pin, unsigned int value);
AN-19011501-C5 49 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
GPIO_PullTypeDef up_down);
up_down 的四种配置为:
typedef enum {
DA
PM_PIN_UP_DOWN_FLOAT = 0,
PM_PIN_PULLUP_1M = 1,
PM_PIN_PULLDOWN_100K
PM_PIN_PULLUP_10K
= 2,
= 3,
rN
}GPIO_PullTypeDef;
de
un
GPIO 配置应用举例:
ar
1) 将 GPIO_PA4 配置为输出态,并输出高电平。
gpio_set_func(GPIO_PA4, AS_GPIO) ; // PA4 默认为 GPIO 功能,可以不设置
Sh
gpio_set_input_en(GPIO_PA4, 0);
gpio_set_output_en(GPIO_PA4, 1);
gpio_write(GPIO_PA4,1)
AN-19011501-C5 50 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
if(!gpio_read(GPIO_PC6)){ //是否低电平
......
}
DA
2.3.3 GPIO 的初始化
3) oe
全部为 0。
Sh
4) dataO
全部为 0。
5) 内部上下拉电阻配置
全部为 float。
AN-19011501-C5 51 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
当在 app_config 中可以提前定义这些宏,这些宏就不再使用以上这种默认
值。
在 app_config.h 中配置 GPIO 状态方法为(以 PA0 为例):
1) 配置 func: #define PA0_FUNC AS_GPIO
2) 配置 ie: #define PA0_INPUT_ENABLE 1
DA
3) 配置 oe: #define PA0_OUTPUT_ENABLE 0
4) 配置 dataO:#define PA0_DATA_OUT
5) 配置内部上下拉电阻:
rN
0
de
#define PULL_WAKEUP_SRC_PA0 PM_PIN_UP_DOWN_FLOAT
un
GPIO 的初始化总结:
1) 可以提前在 app_config.h 中定义 GPIO 的初始状态,在 gpio_init 中得以设定;
ed
AN-19011501-C5 52 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
gpio_init( !deepRetWakeUp );
DA
register)控制,其他所有的状态(func、ie、oe、dataO 等)都是被数字寄存器
(digital register)控制的。
rN
参考文档后面低功耗管理的介绍可知,deepsleep retention 期间所有 digital
register 的状态掉电丢失。
de
在 Telink 上一代 826x 系列 IC 上,suspend 期间可以用 gpio output 来控制一
些外围设备,但到了 8x5x 上如果 suspend 被切换为 deepsleep retention mode 后,
un
AN-19011501-C5 53 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
#endif
DA
rN
de
un
ed
ar
Sh
AN-19011501-C5 54 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
3 BLE 模块
Application
App
Profile 1 Profile 2 ... Profile n
DA
Generic Access Profile
HCI
ar
Physical Layer
AN-19011501-C5 55 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
BLE Host
HCI HCI
cmd data
HCI
HCI HCI
data event
BLE Controller
DA
rN
图 3-2 Host 和 Controller 的 HCI 数据交互
de
1) BLE Host 通过 HCI cmd 去操作设置 Controller,这些 HCI cmd 对应本章后面将
要介绍的 controller API。
un
AN-19011501-C5 56 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
BLE
Other MCU
Host
UART/USB
HCI
BLE
Link Layer
Controller
DA
Physical Layer
Telink BLE SDK 在 BLE host 上,只对 Slave 部分的 stack 完全支持;对于 Master
无法做到完全支持,因为 SDP(service discovery)太复杂。
当 user 只需要使用标准的 BLE slave 时,Telink BLE SDK 运行 Host(slave 部分)
+ 标准的 Controller,实际的协议栈架构会对上面标准的结构做一些简化处理,
使得整个 SDK 的系统资源开销(包括 sram、运行时间、功耗等)最小,其架构
如下图所示。SDK 中 8258 ble sample、8258 remote、8258 module 都是基于该架
构。
AN-19011501-C5 57 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
Application
App
GAP HIDS BAS AUDIO OTA … Profile
DA
HCI
Power
Link Layer Controller
Management
Physical Layer
rN
de
图 3-4 Telink BLE slave 架构
图中实心箭头所示的数据交互是 user 可以通过各种接口来操作控制的,会
un
关接口进行功耗管理的设置。
考虑到效率,应用层与 Host 的数据交互不通过 GAP 来访问控制,协议栈在
Sh
AN-19011501-C5 58 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
Application
Host
GAP App
SDP
SMP ATT (simple
reference)
DA
L2CAP
rN
de
HCI
un
Physical Layer
ar
AN-19011501-C5 59 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
3.2 BLE controller
节中详细介绍,本章只稍微提一下,不做过多介绍。
本章只介绍 Link Layer 和 HCI,且由于 HCI 主要是 interface,其实现的功能也
都是为了操作 Link Layer 和获取 Link Layer 的数据,所以重点详细介绍 Link layer,
ed
AN-19011501-C5 60 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
图 3-6 State diagram of the Link Layer state machine in BLE Spec
Scanning
un
ed
ar
connection
Telink BLE SDK Link Layer 状态机和 BLE spec 定义的一致,拥有 5 种基本状态:
Idle(Standby)、Scanning、Advertising、Initiating、Connection。将 Conneciton
状态区分为 Slave Role 和 Master Role。
AN-19011501-C5 61 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
基于上面 5 种状态,stack/ble/ll/ll.h 中定义了状态机的命名。其中
ConnSlaveRole 和 ConnMasterRole 时状态名都是 BLS_LINK_STATE_CONN。
//ble link layer state
#define
rN
BLS_LINK_STATE_IDLE 0
#define BLS_LINK_STATE_ADV BIT(0)
de
#define BLS_LINK_STATE_SCAN BIT(1)
#define BLS_LINK_STATE_INIT BIT(2)
#define BLS_LINK_STATE_CONN BIT(3)
un
u8 blc_ll_getCurrentState(void);
Sh
AN-19011501-C5 62 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
上面 API 中实参 public_adr 都是 BLE public mac address 的指针。
rN
void blc_ll_initConnection_module(void);
de
用于初始化master和slave共用的module
un
bls_ll_setAdvEnable(0)
Advertising Idle
bls_ll_setAdvEnable(1)
AN-19011501-C5 63 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
blc_ll_initAdvertising_module(tbl_mac);
Scanning
blc_ll_setScanEnable(1,x)
blc_ll_setScanEnable(0,x)
DA
Idle
rN
图 3-9 Idle + Scanning
de
un
blc_ll_initBasicMCU();
blc_ll_initStandby_module(tbl_mac);
Sh
blc_ll_initScanning_module( tbl_mac);
Idle和Scanning状态的切换通过blc_ll_setScanEnable来实现。
AN-19011501-C5 64 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
bls_ll_setAdvEnable(0)
Advertising Idle
bls_ll_setAdvEnable(1)
ac
t
ou
te
ce
me
rm
pt
ti
in
ma
at
on
st
e/
ti
e
co
r’
ec
nn
nn
s
ec
co
co
ti
e/
nn
on
at
_r
DA
in
ti
eq
rm
me
te
ou
t
rN
Connection
Slave role
de
blc_ll_initBasicMCU();
blc_ll_initStandby_module(tbl_mac);
blc_ll_initAdvertising_module(tbl_mac);
blc_ll_initConnection_module();
blc_ll_initSlaveRole_module();
该状态机组合下,状态变化情况描述如下。
1) 8x5x MCU 上电后,进入 Idle state。Idle state 时将 adv Enable,Link Layer 切换
到 Advertising State;adv Disable 时,回到 Idle state。
Adv Enable 和 Disable 通过下面 API bls_ll_setAdvEnable 来控制。
上电后,Link layer 默认处于 Idle 状态,一般需要在 user_init 里面将 Adv Enable,
进入 Advertising state。
AN-19011501-C5 65 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
触发 BLE 的 connection supervision timeout,退出 ConnSlaveRole。
Link layer 的 ConnSlaveRole 退出该 state 时,根据 Adv 是否被 Enable 切换到不
rN
同 state:若 Adv 被 enable,Link Layer 重新回到 Advertising state;若 Adv 被
de
Disable,Link Layer 回到 Idle state。Adv 处于 Enable 或者 Disable 取决于 user
在应用层最后一次调用 bls_ll_setAdvEnable 时设置的值。
un
Scanning
ar
cre blc
ate _ll
con _cr
Sh
nec eat
tio eCo
n t nne
blc_ll_setScanEnable(1,x) ime cti
out on
blc_ll_setScanEnable(0,x)
blc_ll_createConnection
Idle Initiating
create connection timeout
te
s
rm
es
in
cc
at
su
e/
on
co
ti
nn
ec
ec
nn
ti
co
on
te
ti
ea
me
cr
ou
t
Connection
Master role
AN-19011501-C5 66 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
该状态机组合下,状态变化情况描述如下。
DA
1) 8x5x MCU 上电后,进入 Idle state。Idle state 时将 Scan Enable,Link Layer 切
换到 Scanning State;在 Scanning State 时将 Scan Disable 时,回到 Idle state。
rN
Scan Enable 和 Disable 通过 API blc_ll_setScanEnable 来控制。
上电后,Link layer 默认处于 Idle state,一般需要在 user_init 里面将 Scan
de
Enable,进入 Scanning state。
Link Layer 处于 Scanning state 时,会将 Scan 到的 adv packet 通过 event
un
AN-19011501-C5 67 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
……
irq_blt_sdk_handler ();
……
}
rN
de
void main_loop (void)
{
///////////////////// BLE entry ////////////////////////////
un
blt_sdk_main_loop();
////////////////////// UI entry ////////////////////////////
ed
……
}
ar
AN-19011501-C5 68 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
TX RX TX RX TX RX
Adv interval
DA
Advertising state 时序图如上所示。每一个 adv interval 时间内 Link Layer 触发
一个 Adv event。一个典型的 3 个 adv channel 都发包的 Adv event 会在 channel 37、
38、39 上都发送一个广播包。每发一个广播包之后都进入收包状态,等待 master
rN
可能的回包,回包有两种:一个是 scan request,收到这个包后再发送一个 scan
de
response 作为回复;另一个是 connect reqeust,收这个包后和 master 建立 ble 连
接,进入 Connection state Slave Role。
un
BLT_EV_FLAG_ADV_DURATION_TIMEOUT、BLT_EV_FLAG_SCAN_RSP、
BLT_EV_FLAG_CONNECT 等。
Scan interval
AN-19011501-C5 69 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
3.2.4.4 Initiating state 时序
Scanning/UI task
rN
Scanning/UI task Scanning/UI task
de
Scan interval
AN-19011501-C5 70 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
送一个 ack 包回复,若有 more data,则继续收 master 包并回复,这个过程简称
为 brx event。
rN
在该 BLE SDK 中,根据软硬件的工作分配,每个 brx 过程分为 3 个阶段:
1) brx start 阶段
de
当 master 发包的时间快要到来时,会由 system tick irq 触发进入 brx start 阶
段,在这个中断里 MCU 设置 PHY 的 BLE 状态机进入 brx 状态,底层硬件做
un
好收发包的相关准备,然后退出中断 irq。
2) brx working 阶段
ed
3) brx post 阶段
Sh
blt_sdk_main_loop 对数据包的处理包括:
1) 数据包的解密
2) 数据包的解析
解析的数据若发现是属于 master 发给 Link Layer 的控制命令,立即执行该命
令,若是 master 发给 Host 层的数据,则会通过 HCI 接口将数据丢到 L2CAP
层处理。
DA
btx btx btx
start working post
rN
UI task UI task UI task UI task
de
btx btx
event event
Conn interval
un
图 3-16 ConnMasterRole 时序
ConnMasterRole 时序图如上所示。每一个 conn interval 开始的时候,Link Layer
ed
event。
Sh
AN-19011501-C5 72 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
理。
int bls_ll_requestConnBrxEventDisable(void);
void bls_ll_disableConnBrxEvent(void);
Sh
void bls_ll_restoreConnBrxEvent(void);
AN-19011501-C5 73 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
parameter 且还没有到更新的时间点时,返回时间为更新的时间点减去当
前时间。即停掉 Brx Event 的时间不能超过更新的时间点,否则会造成后
面所有包收不到,最终断开连接。
C. 若当前为 Conn state,且没有 master 的更新请求,返回值为当前
connection supervision timeout 值的一半。比如当前 timeout 为 1S,返回
值为 500ms。
DA
rN
de
un
ed
ar
Sh
AN-19011501-C5 74 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
Adv event Scanning/UI task Adv event Scanning/UI task Adv event
Adv interval rN
图 3-17 Scanning in Advertising state 时序
de
当前 Link Layer 还是处于 Advertising state(BLS_LINK_STATE_ADV),在每个
un
的 channel(channel 37/38/39)。
Scanning in Advertising state 的使用请参考 8258_feature_test 中
ar
TEST_SCANNING_IN_ADV_AND_CONN_SLAVE_ROLE。
Sh
AN-19011501-C5 75 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
Conn interval 内,除去 brx event 剩余的时间全部用来做 Scanning。
在每个 Set Scan 的时候,会判断一下当前时间距离上次 Set Scan 时间点是否
rN
超过一个 Scan interval(来自 blc_ll_setScanParameter 的设定),若超过则切换 Scan
的 channel(channel 37/38/39)。
Scanning in ConnSlaveRole 的使用请参考 8258_feature_test 中
de
TEST_SCANNING_IN_ADV_AND_CONN_SLAVE_ROLE。
un
ble_sts_t blc_ll_addAdvertisingInConnSlaveRole(void);
Sh
AN-19011501-C5 76 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
UI task Adv event Scanning/UI task
brx rN
event
Conn interval
de
图 3-20 Advertising and Scanning in ConnSlaveRole 时序
un
的 channel(channel 37/38/39)。
Sh
AN-19011501-C5 77 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
1) RX fifo 数量为 8;
2) 在 brx_event(n)开启前 RX fifo 的读、写指针分别为 0 和 2;
3) 在 brx_event(n)和 brx_event(n+1)阶段 main_loop 存在任务阻塞,没有及时去
取 RX fifo;
rN
de
4) 两个 brx_event 阶段都是多包情况。
由上文“Conn state Slave role 时序”小节描述我们知道,在 brx_working 阶段收
un
brx brx
start brx working post start brx working post
Sh
UI task UI task
RX1 TX1 RX2 TX2 RX3 TX3 RX4 TX4 RX5 TX5 RX6 TX6
UI task/sleep UI task/sleep
Brx event(n) Brx event(n+1)
rptr:0 rptr:0
wptr(2+6)&(8-1)=0
7 0 7(5) 0(6)
If the RX fifo rptr is 0 before the
pkt is received, assuming that
6 1 6(4) 1 there are multiple pkts in one brx
event and main_loop does not
process RX fifo(means rptr not ++),
then after receiving the 6th RX
5 2 5(3) 2 data, the RX fifo wptr will cross
the rptr area, causing 1st pkt to
wptr:2 be covered.
4 3 4(2) 3(1)
图 3-21 RX overflow 图示 1
AN-19011501-C5 78 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
上面的例子由于间隔了 1 个连接间隔,任务阻塞时间得要足够长,有点极端,
下面这个 RX overflow 情形则相对而言出现的概率更高:在一个 brx_event 期
间,master 向 slave 写入多笔数据,比如多包数量 7、8 个,这种情况下由于
master 一下子发送了很多数据,slave 来不及进行处理。如下图,读指针只
移动了 2 笔,但是写指针移动了 8 笔也会造成数据溢出。
brx brx
start brx working post
UI task
RX1 TX1 RX2 TX2 RX3 TX3 RX4 TX4 RX5 TX5 RX6 TX6 RX7 TX7 RX8 TX8 UI task/sleep
Brx event(n)
DA
rptr:0
wptr(2+8)&(8-1)=2
7 0 7(5) 0(6)
If the RX fifo rptr is 0 before the
6 1 6(4)
rN 1(7)
pkt is received, assuming that
there is multiple pkts in one brx
event and main_loop does process 2
RX pkts(means rptr point to 2),
then after receiving the 8th RX
de
5(3) 2(8) data, the RX fifo wptr will cross
5 2
the rptr area, causing 2nd pkt to
wptr:2 be covered.
4(2) 3(1)
un
4 3 rptr:2
ed
图 3-22 RX overflow 图示 2
ar
AN-19011501-C5 79 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
TX fifo 使用上,如果客户先看多少个剩余,再决定是否直接 push 数据时,
要留一个 fifo,防止发生各种边界问题。
{
……
ar
AN-19011501-C5 80 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
Host event
(GAP event)
Host Application
DA
HCI event Telink defined event
rN
BLE Controller
de
HCI event 是按 BLE Spec 标准设计的,而 Telink defined event 只在 BLE slave
ar
AN-19011501-C5 81 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
BLE Host
HCI Host
cmd data
HCI
Controller HCI
data event
BLE Controller
DA
图 3-24 HCI event
rN
Controller HCI event 的定义,详情请参照《Core_v5.0》 (Vol 2/Part E/7.7
“Events”)。其中 7.7.65“LE Meta Event”指 HCI LE(low energy) Event,其他的都是
普通的 HCI event。根据 Spec 的定义,Telink BLE SDK 也将 Controller HCI event 分
de
为两类:HCI Event 和 HCI LE event。由于 Telink BLE SDK 主打低功耗蓝牙,所以对
HCI event 只支持了最基本的几个,而 HCI LE event 绝大多数都支持。
un
callback函数原型中的u32 h是一个标记,底层协议栈多处会用到,user只需要
知道以下两个即可:
#define HCI_FLAG_EVENT_TLK_MODULE (1<<24)
#define HCI_FLAG_EVENT_BT_STD (1<<25)
blc_hci_registerControllerEventHandler(controller_event_callback);
1. HCI event
1) HCI_EVT_DISCONNECTION_COMPLETE
DA
详情请参照《Core_v5.0》(Vol 2/Part E/7.7.5 “Disconnection Complete Event”)。
该 event 的总体数据长度为 7,param len 为 4,如下所示,具体数据含义请
rN
直接参考 BLE spec。
2) HCI_EVT_ENCRYPTION_CHANGE 和 HCI_EVT_ENCRYPTION_KEY_REFRESH
ar
跟 Controller 加密相关,
这两个 event 可以在 8258 master kma dongle 中看到,
具体的处理封装到 library 中完成了,这里不介绍细节。
3) HCI_EVT_READ_REMOTE_VER_INFO_COMPLETE
详情请参照《Core_v5.0》(Vol 2/Part E/7.7.12)。
当 Host 使用了 HCI_CMD_READ_REMOTE_VER_INFO 命令 Controller 和 BLE peer
device 交互 version 信息,并且收到了 peer device 的 version 后,向 Host 上报
该 event。
该 event 的总体数据长度为 11,param len 为 8,如下所示,具体数据含义请
直接参考 BLE spec。
AN-19011501-C5 83 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
4) HCI_EVT_LE_META
表示当前是 HCI LE event,根据后面的 sub event code 判断具体的 event 类型。
DA
event mask的定义如下所示:
#define HCI_EVT_MASK_DISCONNECTION_COMPLETE 0x0000000010
#define HCI_EVT_MASK_ENCRYPTION_CHANGE
rN
#define HCI_EVT_MASK_READ_REMOTE_VERSION_INFORMATION_COMPLETE
0x0000000080
0x0000000800
de
un
2. HCI LE event
ar
1) HCI_SUB_EVT_LE_CONNECTION_COMPLETE
详情请参照《Core_v5.0》(Vol 2/Part E/7.7.65.1 “LE Connection Complete
Event”)。
当 controller Link Layer 和 peer device 建立 connection 后,上报该 event。
该 event 的总体数据长度为 22,param len 为 19,如下所示,具体数据含义
请直接参考 BLE spec。
AN-19011501-C5 84 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
2) HCI_SUB_EVT_LE_ADVERTISING_REPORT
详情请参照《Core_v5.0》(Vol 2/Part E/7.7.65.2 “LE Advertising Report Event”)。
当 controller 的 Link Layer scan 到正确的 adv packet 后,通过
DA
HCI_SUB_EVT_LE_ADVERTISING_REPORT 上报给 Host。
该 event 的数据长度不定(取决于 adv packet 的 payload),如下所示,具体
数据含义请直接参考 BLE spec。 rN
0x04 0x3e 0x02
de
hci event param subevent num event
code report
address type[1...i]
event code len type
un
address[1...i] length[1..i]
data[1...i] rssi[1..i]
ed
3) HCI_SUB_EVT_LE_CONNECTION_UPDATE_COMPLETE
详情请参照《Core_v5.0》(Vol 2/Part E/7.7.65.3 “LE Connection Update Complete
Event”)。
当 Controller 上的 connection update 生效时,向 Host 上报
HCI_SUB_EVT_LE_CONNECTION_UPDATE_COMPLETE。
该 event 的总体数据长度为 13,param len 为 10,如下所示,具体数据含义
请直接参考 BLE spec。
AN-19011501-C5 85 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
4) HCI_SUB_EVT_LE_CONNECTION_ESTABLISH
HCI_SUB_EVT_LE_CONNECTION_ESTABLISH 是对
HCI_SUB_EVT_LE_CONNECTION_COMPLETE 的补充,除了 subevent 不一致外,
DA
其他所有参数相同。SDK 中 8258 master kma dongle 使用了该 event。
该 event 是唯一的非 BLE spec 标准的 event,属于 Telink 私有定义,且只在
8258 master kma dongle 中使用。 rN
下面详细说明 Telink 定义该 event 的原因。
BLE Controller 在 Initiating state 时,扫描到指定需要连接的 device adv packet
de
时,向它发送 connection request 包,此时不管对方是否有收到这个 connection
request,都会无条件认为 Connection complete,向 Host 上报 LE Connection
un
//eventMask: LE
DA
中查到。
#define HCI_LE_EVT_MASK_CONNECTION_COMPLETE 0x00000001
#define HCI_LE_EVT_MASK_ADVERTISING_REPORT rN
#define HCI_LE_EVT_MASK_CONNECTION_UPDATE_COMPLETE
0x00000002
0x00000004
#define HCI_LE_EVT_MASK_CONNECTION_ESTABLISH 0x80000000
de
//telink private
不打开。
ed
ar
Sh
AN-19011501-C5 87 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
#define BLT_EV_FLAG_LL_REJECT_IND 5
#define BLT_EV_FLAG_RX_DATA_ABANDOM 6
#define BLT_EV_FLAG_PHY_UPDATE 7
#define
#define
rN
BLT_EV_FLAG_DATA_LENGTH_EXCHANGE
BLT_EV_FLAG_GPIO_EARLY_WAKEUP
8
9
#define BLT_EV_FLAG_CHN_MAP_REQ 10
de
#define BLT_EV_FLAG_CONN_PARA_REQ 11
#define BLT_EV_FLAG_CHN_MAP_UPDATE 12
un
#define BLT_EV_FLAG_CONN_PARA_UPDATE 13
#define BLT_EV_FLAG_SUSPEND_ENTER 14
#define BLT_EV_FLAG_SUSPEND_EXIT 15
ed
1) 第 一 种 方 式 是 每 个 event 的 回 调 函 数 单 独 注 册 。 这 种 方 式 我 们 称 为
“independent registration”。
回调函数的原型说明为:
typedef void (*blt_event_callback_t)(u8 e, u8 *p, int n);
其中 e 是 event number;p 为回调函数执行时,底层传上来相关数据的指
针,不同回调函数传上来的指针数据都不一样;n 是回传指针所指的有效
数据长度。
注册回调函数的 API 为:
void bls_app_registerEventCallback (u8 e,
blt_event_callback_t p);
AN-19011501-C5 88 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
void
blc_hci_registerControllerEventHandler(
hci_event_handler_t handler);
DA
虽然这里共用了 HCI event 的注册回调函数,但二者在实现上有一些区别。
HCI event 回调函数里面
h = HCI_FLAG_EVENT_BT_STD rN | hci_event_code;
de
Telink defined event “shared event entry”方式里面
h = HCI_FLAG_EVENT_TLK_MODULE | e;
un
AN-19011501-C5 89 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
bls_app_registerEventCallback (BLT_EV_FLAG_CONNECT,
&task_connect);
rN
bls_app_registerEventCallback (BLT_EV_FLAG_TERMINATE,
&task_terminate);
de
{
Sh
case BLT_EV_FLAG_CONNECT:
{
// add connect callback code here
}
break;
case BLT_EV_FLAG_TERMINATE:
{
// add terminate callback code here
}
break;
default:
break;
AN-19011501-C5 90 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
blc_hci_registerControllerEventHandler(event_handler);
bls_hci_mod_setEventMask_cmd( BIT(BLT_EV_FLAG_CONNECT) |
BIT(BLT_EV_FLAG_TERMINATE) );
1) BLT_EV_FLAG_ADV
DA
该事件目前没有使用。
rN
2) BLT_EV_FLAG_ADV_DURATION_TIMEOUT
de
事件触发条件:如果 user 调用 API bls_ll_setAdvDuration 设置了广播时间限
制,BLE 协议栈底层启动一个计时,在这个限时达到后,停止广播,同时触发该
un
事件,user 可以在该事件回调函数中进行修改广播事件类型、重新打开广播使能、
再次设置广播时间限制等操作。
ed
3) BLT_EV_FLAG_SCAN_RSP
AN-19011501-C5 91 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
4) BLT_EV_FLAG_CONNECT
DA
rN
de
图 3-30 连接请求包单元 PDU
un
u32 dma_len;
u8 type;
ar
u8 rf_len;
u8 scanA[6];
Sh
u8 advA[6];
u8 accessCode[4];
u8 crcinit[3];
u8 winSize;
u16 winOffset;
u16 interval;
u16 latency;
u16 timeout;
u8 chm[5];
u8 hop;
}rf_packet_connect_t;
AN-19011501-C5 92 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
5) BLT_EV_FLAG_TERMINATE
DA
B. Master 发送 terminate 主动断开连接,slave 收到 terminate 命令,对这个
命令进行 ack 后,触发该事件,连接断开,回到 None Conn state。terminate
rN
reason 是 slave 在 Link Layer 上收到的 LL_TERMINATE_IND 控制包中的 Error
Code,该 Error Code 是由 master 决定的。常见的 Error code 有
HCI_ERR_REMOTE_USER_TERM_CONN(0x13)、
de
HCI_ERR_CONN_TERM_MIC_FAILURE(0x3D)等。
C. Slave 端调用了 API bls_ll_terminateConnection(u8 reason),主动断开连接。
un
6) BLT_EV_FLAG_LL_REJECT_IND
ar
LL_REJECT_IND(or LL_REJECT_EXT_IND),此时触发该事件。
回传指针 p:指向发送的 command(LL_REJECT_IND or LL_REJECT_EXT_IND)。
数据长度 n:1。
更多信息请参照《Core_v5.0》(Vol 6/Part B/2.4.2 )。
7) BLT_EV_FLAG_RX_DATA_ABANDOM
事件触发条件:当 BLE RX fifo overflow 时(参考前文“Link Layer TX fifo & RX
fifo”小节),或者在一个连接间隔里收到多包数量>设定的多包数量阈值(用户
需要调用 API:blc_ll_init_max_md_nums 且参数不为 0,SDK 底层才会做多包数
量的检查),触发 BLT_EV_FLAG_RX_DATA_ABANDOM 事件。
回传指针 p:空指针 NULL。
数据长度 n:0。
AN-19011501-C5 93 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
8) BLT_EV_FLAG_PHY_UPDATE
事件触发条件:当 slave 或者 master 主动发起 LL_PHY_REQ,更新成功或者失
败后触发;或者当 slave 或者 master 被动收到 LL_PHY_REQ,并且 PHY 更新
成功后触发。
数据长度 n:1
回传指针 p:指向一个 u8 类型的变量,指示当前连接的 PHY mode。
typedef enum {
BLE_PHY_1M = 0x01,
BLE_PHY_2M = 0x02,
BLE_PHY_CODED = 0x03,
} le_phy_type_t;
DA
9) BLT_EV_FLAG_DATA_LENGTH_EXCHANGE
u16 connEffectiveMaxRxOctets;
u16 connEffectiveMaxTxOctets;
ar
u16 connMaxRxOctets;
u16 connMaxTxOctets;
Sh
u16 connRemoteMaxRxOctets;
u16 connRemoteMaxTxOctets;
……
}ll_data_extension_t;
AN-19011501-C5 94 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
10) BLT_EV_FLAG_GPIO_EARLY_WAKEUP
DA
数据长度 n:1。
回传指针 p:指向一个 u8 型变量 wakeup_status,wakeup_status 变量记录了
rN
当前 suspend 哪些唤醒源状态生效了,由 drivers/8258/pm.h 可看到如下唤醒状态。
enum {
de
WAKEUP_STATUS_TIMER = BIT(1),
WAKEUP_STATUS_PAD = BIT(3),
un
STATUS_GPIO_ERR_NO_ENTER_PM = BIT(7),
STATUS_ENTER_SUSPEND = BIT(30),
ed
};
以上参数的含义请参考“低功耗管理”部分下的详细说明。
ar
Sh
11) BLT_EV_FLAG_CHN_MAP_REQ
12) BLT_EV_FLAG_CHN_MAP_UPDATE
DA
数据长度 n:5。
13) BLT_EV_FLAG_CONN_PARA_REQ
rN
事件触发条件:Slave 设备处于连接态(Conn state Slave role),master 设备
de
需要更新当前连接的参数,发送 LL_CONNECTION_UPDATE_REQ 命令,Slave 设备
收到这个请求后触发该事件,此时还没有处理这个请求。
un
数据长度 n:11。
回传指针 p:p 指向 LL_CONNECTION_UPDATE_REQ 的 PDU,如下图所示的
ed
11 个 byte。
ar
Sh
14) BLT_EV_FLAG_CONN_PARA_UPDATE
15) BLT_EV_FLAG_SUSPEND_ENETR
16) BLT_EV_FLAG_SUSPEND_EXIT
DA
发该事件。
回传指针 p:空指针 NULL。
数据长度 n: 0。
rN
de
注意:实际 SDK 底层执行 cpu_sleep_wakeup 唤醒后执行该回调,且不管是
被 gpio 唤醒还是被 timer 唤醒,都会无条件触发该事件。如果同时发生了
un
BLT_EV_FLAG_GPIO_EARLY_WAKEUP 事件,这两个事件的先后执行顺序请参考“低
功耗管理—低功耗管理工作机制”部分伪代码的描述。
ed
该 BLE SDK Link Layer 上支持 data length extension,且 rf_len 长度支持到 BLE
spec 上最大长度 251 bytes。
详情请参照《Core_v5.0》(Vol 6/Part B/2.4.2.21 “LL_LENGTH_REQ and
LL_LENGTH_RSP”)。
User 如果需要使用 data length extension 功能,按如下步骤设置。
1) 设置合适的 TX & RX fifo size
收发长包都需要更大的 TX &RX fifo size,考虑到这些 fifo 会占据大量的 Sram
空间,在设置 fifo size 时应选取最合适的值,避免 Sram 的浪费。
发长包需要加大 TX fifo size。TX fifo size 至少比 TX rf_len 大 12,且必须按 4
字节对齐。如:
TX rf_len = 56 bytes: MYFIFO_INIT(blt_txfifo, 68, 8);
TX rf_len = 141 bytes: MYFIFO_INIT(blt_txfifo, 156, 8);
AN-19011501-C5 97 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
MYFIFO_INIT(blt_rxfifo, 224, 8);
回调函数各个参数的含义。
在这个 BLT_EV_FLAG_DATA_LENGTH_EXCHANGE 事件回调函数里,可以获得
最终的最大 TX 包长和 RX 包长。
AN-19011501-C5 98 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
rN
de
un
ed
ar
Sh
AN-19011501-C5 99 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
4) 收发长包的操作
请 user 先参考本文档“ATT & GATT”部分的一些说明,包括 Handle Value
Notification 和 Handle Value Indication,Write request 和 Write Command 等。
DA
在以上 3 个步骤都正确完成的基础上,可以开始收发长包。
发长包调用 ATT 层的 Handle Value Notification 和 Handle Value Indication 对应
的 API 即可,分别如下所示,将要发送的数据地址和长度分别带入下面的形
参"*p”和"len”即可。
rN
ble_sts_t bls_att_pushNotifyData (u16 handle, u8 *p, int len);
de
ble_sts_t bls_att_pushIndicateData (u16 handle, u8 *p, int len);
un
DA
Controller API 的声明在 stack/ble/ll 和 stack/ble/hci 目录下的头文件中,其中
ll 目录下根据 Link Layer 状态机功能的分类分为 ll.h、ll_adv.h、ll_scan.h、ll_init.h、
ll_slave.h、ll_master.h,user 可以根据 Link Layer 的功能去寻找,比如跟 advertising
rN
相关功能的 API 就应该都在 ll_adv.h 中声明。
de
3.2.9.2 API 返回类型 ble_sts_t
un
适用。
flash_addr填flash上存储MAC address的地址即可,参考文档前面的介绍,8x5x
512K flash上对应的这个地址为0x76000。如果不需要random static address,上面
的mac_random_static填”NULL”即可。
BLE public MAC address 成功获取后,调用 Link Layer 初始化的 API,将 MAC
address 传入 BLE 协议栈:
blc_ll_initStandby_module (tbl_mac); //mandatory
DA
结合前面对 Link Layer 状态机的详细介绍,以下几个 API 用于配置搭建 BLE
状态机时各个模块的初始化。
void blc_ll_initBasicMCU (void)
void
void
rN
blc_ll_initStandby_module (u8 *public_adr);
blc_ll_initAdvertising_module(u8 *public_adr);
de
void blc_ll_initScanning_module(u8 *public_adr);
void blc_ll_initInitiating_module(void);
void blc_ll_initSlaveRole_module(void);
un
void blc_ll_initMasterRoleSingleConn_module(void);
ed
3.2.9.5 bls_ll_setAdvData
ar
BLE_SUCCESS 0
user可以在初始化的时候调用该API设置广播数据,也可以于程序运行时在
main_loop里随时调用该API来修改广播数据。
DA
该BLE SDK中8258 ble remote工程中定义的Adv PDU如下,其中各个字段的含
义请参考文档BLE Spec《CSS v6》(Core Specification Supplement v6.0)中Data Type
Specifcation的具体说明。
u8 tbl_advData[] = {
rN
0x05, 0x09, 'k', 'h', 'i', 'd',
de
0x02, 0x01, 0x05,
0x03, 0x19, 0x80, 0x01,
un
};
ed
上面广播数据里,设置广播设备名为"khid"。
ar
Sh
3.2.9.6 bls_ll_setScanRspData
BLE_SUCCESS 0
该 BLE SDK 中 8258 ble remote 工程中定义 scan response data 如下,scan 设
备名为"KRemote" 。各个字段的含义请参考文档 BLE Spec《CSS v6》(Core
Specification Supplement v6.0)中 Data Type Specifcation 的具体说明。
u8 tbl_scanRsp [] = {
0x08, 0x09, 'K', 'R', 'e', 'm', 'o', 't', 'e',
};
DA
1) 一些设备只看广播包,那么显示的设备名称为"khid";
rN
2) 一些设备看到广播后,发送 scan request,并读取回包 scan response,那么
显示的设备名称可能就会是"KRemote"。
de
user 也可以在这两个包中将设备名称写为一样,被扫描时就不会显示两个不
同的名字了。
un
3.2.9.7 bls_ll_setAdvParam
Command”)。
channel 39)上各发一个包。
下面的 API 对 Adv Event 相关的参数进行设置。
ble_sts_t bls_ll_setAdvParam( u16 intervalMin, u16 intervalMax,
adv_type_t advType, own_addr_type_t ownAddrType,
1) intervalMin 和 intervalMax
设置广播时间间隔 adv interval 的范围,以 0.625ms 为基本单位,范围在 20ms
~10.24S 之间,并且 intervalMin 小于等于 intervalMax。
BLE spec 要求 adv interval 不要设一个固定的值,需要有一些随机的变化。
Telink BLE SDK 通过 intervalMin 和 intervalMax 设置为不同的值来实现,最终
的 adv interval 是在 intervalMin~intervalMax 之间随机变化。若设置的
DA
intervalMin 和 intervalMax 相等,adv interval 会等于固定的 intervalMin,不会
变化。
rN
根据不同广播包的类型,intervalMin 和 intervalMax 的值有一些限定,请参照
(Vol 6/Part B/ 4.4.2.2 “Advertising Events”)。
de
2) advType
un
参考 BLE Spec,四种基本的广播事件类型如下:
ed
ar
Sh
上图中 Allowable response PDUs for advertising event 部分用 YES 和 NO 说明了
各种类型广播事件是否对其他设备的 Scan request 和 Connect Request 进行响
应,如:第一个 Connectable Undirected Event 对 Scan request 和 Connect
DA
ADV_TYPE_CONNECTABLE_UNDIRECTED = 0x00, // ADV_IND
ADV_TYPE_CONNECTABLE_DIRECTED_HIGH_DUTY = 0x01,
//ADV_INDIRECT_IND (high duty cycle)
ADV_TYPE_SCANNABLE_UNDIRECTED rN
ADV_TYPE_NONCONNECTABLE_UNDIRECTED
= 0x02 //ADV_SCAN_IND
= 0x03, //ADV_NONCONN_IND
ADV_TYPE_CONNECTABLE_DIRECTED_LOW_DUTY = 0x04,
de
//ADV_INDIRECT_IND (low duty cycle)
}adv_type_t;
un
默认最常用的广播类型为 ADV_TYPE_CONNECTABLE_UNDIRECTED。
ed
ar
3) ownAddrType
指定广播地址类型时,ownAddrType 4 个可选的值如下。
Sh
typedef enum{
OWN_ADDRESS_PUBLIC = 0,
OWN_ADDRESS_RANDOM = 1,
OWN_ADDRESS_RESOLVE_PRIVATE_PUBLIC = 2,
OWN_ADDRESS_RESOLVE_PRIVATE_RANDOM = 3,
}own_addr_type_t;
这里只介绍前两个参数。
OWN_ADDRESS_PUBLIC 表示广播的时候使用 public MAC address,实际地址来
自 MAC address 初始化时 API blc_ll_initAdvertising_module(u8 *public_adr)的
设置。
OWN_ADDRESS_RANDOM 表示广播的时候使用 random static MAC address,
AN-19011501-C5 106 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
4) peerAddrType 和*peerAddr
当 advType 被设置为直接广播包类型 directed adv
(ADV_TYPE_CONNECTABLE_DIRECTED_HIGH_DUTY 和
ADV_TYPE_CONNECTABLE_DIRECTED_LOW_DUTY)时,peerAddrType 和
*peerAddr 用于指定 peer device MAC Address 的类型和地址。
当 advType 为其他类型时,peerAddrType 和*peerAddr 的值都无效,可以设
定为 0 和 NULL。
5) adv_channelMap
DA
设定广播 channel,可以选择 channel 37、38、39 中任意一个或多个。
adv_channelMap 的值可设置如下 3 个或它们中任意或组合。
#define
rN
BLT_ENABLE_ADV_37 BIT(0)
#define BLT_ENABLE_ADV_38 BIT(1)
de
#define BLT_ENABLE_ADV_39 BIT(2)
#define BLT_ENABLE_ADV_ALL
un
6) advFilterPolicy
用于设定发送广播包时,对其他设备的 scan request 和 connect request 采取
ar
中详细解释。
可设置的 4 种过滤类型如下,若不需要 whitelist 过滤功能,选择
ADV_FP_NONE。
typedef enum {
ADV_FP_ALLOW_SCAN_ANY_ALLOW_CONN_ANY = 0x00,
ADV_FP_ALLOW_SCAN_WL_ALLOW_CONN_ANY = 0x01,
ADV_FP_ALLOW_SCAN_ANY_ALLOW_CONN_WL = 0x02,
ADV_FP_ALLOW_SCAN_WL_ALLOW_CONN_WL = 0x03,
ADV_FP_NONE = ADV_FP_ALLOW_SCAN_ANY_ALLOW_CONN_ANY
} adv_fp_type_t;
BLE_SUCCESS 0
intervalMin 或 intervalMax 的
HCI_ERR_INVALID_HCI_CMD_PARAMS 0x12
值不符合 BLE spec 的规定
DA
围检查就无法控制。
但是考虑到 user 对一些常用的参数可能会经常性的去修改,而又不希望每次
rN
都要调用 bls_ll_setAdvParam 同时设置 8 个参数,SDK 对其中 4 个不会跟其他
参数有耦合关系的参数,单独封装了 API,以方便 user 的使用。单独封装 3
个 API 如下:
de
ble_sts_t bls_ll_setAdvInterval(u16 intervalMin, u16 intervalMax);
ble_sts_t bls_ll_setAdvChannelMap(u8 adv_channelMap);
un
返回值 ble_sts_t:
Sh
3.2.9.8 bls_ll_setAdvEnable
3.2.9.9 bls_ll_setAdvDuration
使用 bls_ll_setAdvParam 对广播所有参数设置成功后,使用
bls_ll_setAdvEnable(1)开始广播。若希望对设置好的广播事件进行时间限定,
让它持续发送一段时间后就自动关闭,可以调用上面的 API。
DA
duration_en 设为 1 表示开启计时功能,设为 0 表示关闭计时功能。只有在计
时功能开启的情况下,duration_us(单位:us)的设置才有意义。
程序从设置的时间点开始计时,一旦超出预设的时间时,停止广播,广播使
rN
能(AdvEnable)失效,在 None Conn state 下,会切换到 Idle State。此时会触发
de
Link Layer 事件 BLT_EV_FLAG_ADV_DURATION_TIMEOUT。
BLE Spec 中规定 ADV_TYPE_CONNECTABLE_DIRECTED_HIGH_DUTY 广播类型,
un
BLE_SUCCESS 0
Sh
ADV_TYPE_CONNECTABLE_DIRECTED_HI
HCI_ERR_INVALID_HCI_CMD_PARAMS 0x12 GH_DUTY 广播类型不能被设置
Duration Time
触发 BLT_EV_FLAG_ADV_DURATION_TIMEOUT 需要注意一种特殊的情况:
假设设定了 duration_us 为 2000000,即 2s。
如果 Slave 一直在广播,那么广播时间到达 2s 时会触发 timeout,执行
BLT_EV_FLAG_ADV_DURATION_TIMEOUT 回调。
如果广播时间不到 2s 的情况下(假设在 0.5s 的时候),Slave 和 Master 连接
上了,这个 timeout 的计时在底层并没有被清除掉,而是被缓存起来。在进入连
接状态 1.5s 的时候,也就是到达实际设定的 timeout 时间点正好 2s 时,由于此
时已经处于连接状态,不会去检查广播事件是否超时,也就不会触发
BLT_EV_FLAG_ADV_DURATION_TIMEOUT 回调。当进入连接状态一段时间(如 10s)
后断开连接重新回到广播(adv)状态,在发第一个广播包前,协议栈认为此时
的时间超过了之前设定的 2s timeout,触发
BLT_EV_FLAG_ADV_DURATION_TIMEOUT 回调。在这种情况下触发的
BLT_EV_FLAG_ADV_DURATION_TIMEOUT 回调已经远远超过了实际设定的 timeout
DA
时间点,所以需要特别注意。
3.2.9.10 blc_ll_setAdvCustomedChannel
rN
de
下面 API 用于定制特殊的 advertising channel/scanning channel,只对一些非
常特殊的应用有意义,如 BLE mesh,其他常规 BLE 应用不要使用该 API。
un
3.2.9.11 rf_set_power_level_index
user_set_rf_power(0, 0, 0);
bls_app_registerEventCallback (BLT_EV_FLAG_SUSPEND_EXIT,
&user_set_rf_power);
3.2.9.12 blc_ll_setScanParameter
DA
ble_sts_t blc_ll_setScanParameter (u8 scan_type,
u16 scan_interval, u16 scan_window,
rN
own_addr_type_t ownAddrType,
scan_fp_type_t filter_policy);
de
参数解析:
un
1) scan_type
可选择 passive scan 和 active scan,区别是 active scan 会在收到 adv packet 基
础上发 scan_req 以获取设备 scan_rsp 的更多信息,scan rsp 包也会通过 adv
ed
SCAN_TYPE_PASSIVE = 0x00,
SCAN_TYPE_ACTIVE,
Sh
} scan_type_t;
2) scan_inetrval/scan window
scan_interval 设置 Scanning state 时频点切换时间,单位为 0.625ms,
scan_window 在 Telink BLE SDK 中暂时没有处理,实际的 scan window 设置为
scan_interval。
3) ownAddrType
指定 scan req 包地址类型时,ownAddrType 4 个可选的值如下:
typedef enum{
OWN_ADDRESS_PUBLIC = 0,
OWN_ADDRESS_RANDOM = 1,
OWN_ADDRESS_RESOLVE_PRIVATE_PUBLIC = 2,
OWN_ADDRESS_RESOLVE_PRIVATE_RANDOM = 3,
}own_addr_type_t;
DA
4) filter_policy
目前支持的 scan filter policy 为下面两个:
typedef enum {
rN
SCAN_FP_ALLOW_ADV_ANY= 0x00,//except direct adv address not
de
match
SCAN_FP_ALLOW_ADV_WL=0x01,//except direct adv address not match
un
3.2.9.13 blc_ll_setScanEnable
DA
1) 在 Idle state 时,Enable Scanning,Link Layer 进入 Scanning state。
2) 在 Scanning state 时,Disable Scanning,Link layer 进入 Idle state。
= 0x01,
} dupFilter_en_t;
BLE_SUCCESS 0
3.2.9.14 blc_ll_createConnection
DA
1) scan_inetrval/scan window
scan_interval 设置 Initiating state 中 Scan 频点切换时间,单位为 0.625ms。
rN
scan_window 在 Telink BLE SDK 中暂时没有处理,实际的 scan window 设置为
scan_interval。
de
2) initiator_filter_policy
un
指定当前连接设备的策略,可选如下两种:
typedef enum {
ed
} init_fp_type_t;
3) adr_type/ mac
initiator_filter_policy 为 INITIATE_FP_ADV_SPECIFY 时,连接地址类型为
adr_type(BLE_ADDR_PUBLIC 或者 BLE_ADDR_RANDOM)、地址为 mac[5…0]
的设备。
4) own_adr_type
指定建立连接的 Master role 使用的 MAC address 类型。ownAddrType 4 个可
选的值如下。
typedef enum{
OWN_ADDRESS_PUBLIC = 0,
OWN_ADDRESS_RANDOM = 1,
OWN_ADDRESS_RESOLVE_PRIVATE_PUBLIC = 2,
OWN_ADDRESS_RESOLVE_PRIVATE_RANDOM = 3,
}own_addr_type_t;
DA
ble_sts_t blc_ll_setRandomAddr(u8 *randomAddr);
6) ce_min/ ce_max
Sh
BLE_SUCCESS 0
3.2.9.15 blc_ll_setCreateConnectionTimeout
DA
3.2.9.16 blm_ll_updateConnection rN
详情请参照《Core_v5.0》(Vol 2/Part E/ 7.8.18 “LE Connection Update
de
Command”)。
ble_sts_t blm_ll_updateConnection (u16 connHandle,
un
1) connection handle
指定需要更新连接参数的connection。
ar
3) ce_min/ce_max
目前不处理。
3.2.9.17 bls_ll_terminateConnection
DA
Telink BLE SDK 底层 stack 中,只有一个地方会调用该 API 主动 terminate:解
密对方设备的数据包,如发现认证数据 MIC 错误,调用
bls_ll_terminateConnection(HCI_ERR_CONN_TERM_MIC_FAILURE),告知对方解密
错误,断开连接。
rN
de
Slave 调用该 API 主动发起断开连接后,一定会触发 BLT_EV_FLAG_TERMINATE
事件,在该事件的回调函数里可以看到对应的 terminate reason 和这个手动设置
un
的 reason 是一样的。
在 Connection state Slave role 时,一般情况下直接调用该 API 可以成功发送
terminate 并断连,但也存在一些特殊情况会导致该 API 调用失败,根据返回值
ed
BLE_SUCCESS 0
Controller busy(有大量数据正在发送)暂时
HCI_ERR_CONTROLLER_BUSY 0x3A
无法接受该命令。
3.2.9.18 blm_ll_disconnect
ble_sts_t blm_ll_disconnect (u16 handle, u8 reason);
BLE_SUCCESS 0
Controller busy(有大量数据正在发送)暂时
HCI_ERR_CONTROLLER_BUSY 0x3A
无法接受该命令。
DA
3.2.9.19 Get Connection Parameters
u16 bls_ll_getConnectionTimeout(void);
un
2) 若返回为非 0 值表示参数值:
bls_ll_getConnectionInterval 返回的是实际 conn interval 除以 1.25ms 单位值,
ar
3.2.9.20 blc_ll_getCurrentState
user 在应用层判断当前状态,如:
if( blc_ll_getCurrentState() == BLS_LINK_STATE_ADV)
if( blc_ll_getCurrentState() == BLS_LINK_STATE_CONN )
AN-19011501-C5 118 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
3.2.9.21 blc_ll_getLatestAvgRSSI
DA
Whitelist,会根据 Whitelist 中的设备进行相应的操作。实际 Whitelist 概念中包含
Whitelist 和 Resolvinglist 两部分。
rN
通过 peer_addr_type 和 peer_addr 可以判断 peer device 地址类型是否 RPA
(resolvable private address)。使用下面的宏判断即可。
#define IS_NON_RESOLVABLE_PRIVATE_ADDR(type, addr)
de
( (type)==BLE_ADDR_RANDOM && (addr[5] & 0xC0) == 0x00 )
un
相关接口:
ar
ble_sts_t ll_whiteList_reset(void);
Sh
添加一个设备到 whitelist,返回值列表:
BLE_SUCCESS 0 添加成功
相关 API 如下:
ble_sts_t ll_resolvingList_reset(void);
DA
不需要解析的时候,可以关闭。
ble_sts_t rN
ll_resolvingList_add(u8 peerIdAddrType, u8 *peerIdAddr,
u8 *peer_irk, u8 *local_irk);
de
添加使用 RPA 地址的设备,peerIdAddrType/ peerIdAddr 和 peer-irk 填 peer
device 宣称的 identity address 和 irk,这些信息会在配对加密过程中存储到 Flash
中,user 可以在文档 SMP 部分找到获取这些信息的接口。对于 local_irk SDk 暂
un
删除之前添加的设备。
ar
Sh
Telink 提供的 Kite BLE SDK 中,为节省 Sram,Coded PHY/2M PHY 默认是关闭
AN-19011501-C5 120 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
API(SDK 里默认是关闭的)
blc_ll_init2MPhyCodedPhy_feature();
rN
3.2.10.3 Coded PHY/2M PHY API 介绍:
de
1. API blc_ll_init2MPhyCodedPhy_feature()
un
void blc_ll_init2MPhyCodedPhy_feature(void)
ed
3. API blc_ll_setPhy()
4. API blc_ll_setDefaultConnCodingIndication()
ble_sts_t blc_ll_setDefaultConnCodingIndication(le_ci_prefer_t
prefer_CI);
非BLE Spec标准接口,当Peer Device通过API blc_ll_setPhy ()主动发出PHY_Req
申请时,被申请方可以通过此API设置本地设备的preferenced Encode Mode
(S2/S8)。
DA
Kite BLE SDK 中相关 Demo 参考。
Slave 端参考 Demo “8258_feature_test”
#define FEATURE_TEST_MODE
rN
在 vendor/8258_feature_test/app_config.h 中定义宏如下
TEST_CSA2
de
1. 如果使用《Core_4.2》API 定义的广播,用户可以选择使用或者不使用跳频算
法#2, SDK 中默认是不使用的,如果想要使用跳频算法#2,需要通过下面的
un
API 使能。
void blc_ll_initChannelSelectionAlgorithm_2_feature(void)
ed
API 我们会在以后的章节中描述为《Core_5.0》API(下面小节后引用这个名称),
而《Core_4.2》API 指“Controller API”章节中介绍的 bls_ll_setAdvData()、
bls_ll_setScanRspData()、bls_ll_setAdvParam(),这 3 个 API 只能实现《Core_4.2》
定义的广播功能,不能够实现《Core_5.0》的广播功能。
DA
Extended Advertising Demo “8258_feature_test”使用方法:
Demo1:用于说明《Core_5.0》支持的所有的基础广播功能用法。
rN
1. 在 vendor/8258_feature_test/app_config.h 定义宏
de
#define FEATURE_TEST_MODE TEST_EXTENDED_ADVERTISING
所支持的所有广播包类型,所支持类型如下图所示。
ed
ar
Sh
DA
们又不希望底层写死的 code 导致 Sram 的浪费(因为大部分客户可能使用的数据
长度很短),所以将底层需要用到的 Sram 全部交给用户层去定义。
rN
当前 SDK 只支持 1 个 Advertising set 不支持 Multiple Advertising Sets,但是为
了方便以后扩展为 multiple adv sets,code 中的 API 都是兼容 Multiple Advertising
Sets 而设计的,用户可暂时忽略 multiple adv sets 的参数设置。
de
根据以上的设计思想,设计了如下的 API。
1. 初始化时需要调用如下 API 来分配 Extended Advertising 所使用的 Sram。
un
blc_ll_initExtendedAdvertising_module(app_adv_set_param,
app_primary_adv_pkt, APP_ADV_SETS_NUMBER);
ed
blc_ll_initExtSecondaryAdvPacketBuffer(app_secondary_adv_pkt,
MAX_LENGTH_SECOND_ADV_PKT);
ar
blc_ll_initExtAdvDataBuffer(app_advData, APP_MAX_LENGTH_ADV_DATA);
blc_ll_initExtScanRspDataBuffer(app_scanRspData,
Sh
APP_MAX_LENGTH_SCAN_RESPONSE_DATA);
app_advData app_primary_adv_pkt
44bytes
APP_MAX_LENGTH_ADV_DATA
app_adv_set_param
*dat_extAdv
*dat_scanRsp
app_scanRspData app_secondary_adv_pkt
*primary_adv
APP_MAX_LENGTH_SCAN_RESPONSE_DATA MAX_LENGTH_SECOND_ADV_PKT
*secondary_adv
DA
APP_MAX_LENGTH_ADV_DATA:一个Advertising Set 的数据长度,用户需要根 rN
据实际应用,调节宏定义大小,以节省deepRetention空间。
APP_MAX_LENGTH_SCAN_RESPONSE_DATA: 一个Advertising Set 的
de
scanResponse数据长度,用户需要根据实际应用,调节宏定义大小,以节省
deepRetention空间。
un
(vendor/8258_feature_test/feature_2m_coded_phy_adv.c)用户可以根据自己的需
求来定义以下的宏来分配 sram,以达到节省 Sram 的目的。
#define APP_ADV_SETS_NUMBER 1 // Number of
Supported Advertising Sets(本版SDK只支持1个Adv Set,所以当前只能为1)
#define APP_MAX_LENGTH_ADV_DATA 1024 // Maximum
Advertising Data Length, (if legacy ADV, max length 31 bytes is enough)
2. API blc_ll_setExtAdvParam()
ble_sts_t blc_ll_setExtAdvParam(……);
BLE Spec标准接口,用于设置广播参数,详细请参考《Core_5.0》(Vol 2/Part
E/7.8.53 “LE Set Extended Advertising Parameters Command”), 并结合SDK上枚举
AN-19011501-C5 125 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
类型定义和demo用法去理解。
注意:参数中Advertising_Tx_Power暂不支持选择发送power值,需要另外调用API
void rf_set_power_level_index (RF_PowerTypeDef level)来配置发送power。
3. API blc_ll_setExtScanRspData()
4. API blc_ll_setExtAdvEnable_n()
DA
ble_sts_t blc_ll_setExtAdvEnable_n(u32 extAdv_en, u8 sets_num, u8
*pData); rN
BLE Spec标准接口,用于打开/关闭Extended Advertising,详细可参考
《Core_5.0》(Vol 2/Part E/7.8.56 “LE Set Extended Advertising Enable
de
Command”),并结合SDK上枚举类型定义和demo用法去理解。
un
但目前SDK只支持1个Adv Sets,所以此API暂时不支持,只是为以后multipleAdv
sets预留,不过Telink SDK根据此API功能写了一个简化的API,用来操作打开/
关闭1个Adv Sets,执行效率更高。简化的API如下所示,输入参数和返回值和
ed
标准的API一样,但只用来设置1个Adv Set。
ble_sts_t blc_ll_setExtAdvEnable_1(u32 extAdv_en, u8 sets_num, u8
*pData);
ar
Sh
5. API blc_ll_setAdvRandomAddr()
6. API blc_ll_setDefaultExtAdvCodingIndication()
7. API blc_ll_setAuxAdvChnIdxByCustomers()
8. API blc_ll_setMaxAdvDelay_for_AdvEvent()
DA
T_advEvent = advInterval + advDelay
如果max_delay_ms = 0 ,T_advEvent的时间是精确的advInterval时间;
rN
如果max_delay_ms = 8, T_advEvent的时间在advInterval基础上有0 -- 8ms的随
机偏移量。
de
9. 下面的API,为Multiple Advertising Sets API所预留,本版SDK不支持,用户可
un
以暂时忽略。
ble_sts_t blc_ll_removeAdvSet(u8 advHandle);
ble_sts_t blc_ll_clearAdvSets(void);
ed
3.3.2 L2CAP
逻辑链路控制与适配协议通常简称为 L2CAP(Logical Link Control and
Adaptation Protocol),它向上连接应用层,向下连接控制器层,发挥主机与控
制器之间的适配器的作用,使上层应用操作无需关心控制器的数据处理细节。
BLE 的 L2CAP 层是经典蓝牙 L2CAP 层的简化版本,它在基础模式下,不执行
分段和重组,不涉及流程控制和重传机制,仅使用固定信道进行通信。L2CAP 的
简化结构如下图所示,简单说就是将应用层数据分包发给 BLE controller,将 BLE
controller 收到的数据打包成不同 CID 数据上报给 host 层。
DA
rN
de
图 3-35 BLE L2CAP 结构以及 ATT 组包模型
un
API 进行设置即可。
ar
该函数已经在协议栈中实现,它会对接收到的数据进行解析后向上传输给
ATT、SIG 或 SMP。
初始化:
blc_l2cap_register_handler (blc_l2cap_packet_receive);
初始化:
blc_l2cap_register_handler (app_l2cap_handler);
DA
3.3.2.2 更新连接参数
1) slave 请求更新连接参数 rN
在 BLE 协议栈中,slave 通过 l2cap 层的 CONNECTION PARAMETER UPDATE
REQUEST 命令向 master 申请一组新的连接参数。该命令格式如下所示,详情请
de
参照《Core_v5.0》(Vol 3/Part A/ 4.20 “CONNECTION PARAMETER UPDATE
REQUEST”)。
un
ed
ar
Sh
{
bls_l2cap_requestConnParamUpdate (6, 6, 99, 400);
//interval=7.5ms latency=99 timeout=4s
bls_l2cap_setMinimalUpdateReqSendingTime_after_connCreate(1000
);
DA
图 3-37 抓包显示 conn para update reqeust 和 response
其中 API:
rN
void bls_l2cap_setMinimalUpdateReqSendingTime_after_connCreate(int
de
time_ms)用于设置在连接建立后,slave 设备等待 time_ms(单位:毫秒)后执行
API:bls_l2cap_requestConnParamUpdate,来更新连接参数。用户如果在连接建
un
blc_l2cap_registerConnUpdateRspCb(l2cap_conn_update_rsp_callback_t
cb);
参考 slave 初始化用例:
blc_l2cap_register_handler (blc_l2cap_packet_receive);
其中blc_l2cap_packet_receive函数参考如下:
int app_conn_param_update_response(u8 id, u16 result)
{
if(result == CONN_PARAM_UPDATE_ACCEPT){
//the LE master Host has accepted the connection parameters
}
else if(result == CONN_PARAM_UPDATE_REJECT)
{
//the LE master Host has rejected the connection parameter
}
AN-19011501-C5 130 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
return 0;
}
2) master 回应更新申请
DA
只能靠 user 在平时的 master 兼容性测试中去慢慢总结归纳。
图 3-37 所示的抓包显示 master 接受了申请。
rN
de
un
ed
ar
Sh
Telink 的 8258 master kma dongle 中处理 slave 的连接参数更新 demo code 如
下:
DA
rN
de
interval < 200ms;long suspend time<20S; supervision timeout >= 2* long suspend
time。符合这些条件就接受该参数申请,不符合则拒绝。这是个简单的 demo 设
计,user 可以根据需要进行相关的修改。
ar
int result);
}conn_para_up_rsp;
Slave 发送 conn para update req,并且 master 回 conn para update rsp 接受申
请后,master 会发送 link layer 层的 LL_CONNECTION_UPDATE_REQ 命令,如下图
所示。
DA
rN
图 3-39 抓包显示 ll conn update req
de
un
DA
rN
de
un
ed
ar
Sh
2) Attribute Handle
slave 拥有多个 Attribute,这些 Attribute 组成一个 Attribute Table。在 Attribute
Table 中,每一个 Attribute 都有一个 Attribute Handle 值,用来区分每一个不同的
Attribute。slave 和 master 建立连接后,master 通过 Service Discovery 过程解析读
取到 slave 的 Attribute Table,并根据 Attribute Handle 的值来对应每一个不同的
Attribute,这样它们后面的数据通信只要带上 Attribute Handle,对方就知道是哪
个 Attribute 的数据了。
3) Attribute Value
每个 Attribute 都有对应的 Attribute Value,用来作为 request、response、
notification 和 indication 的数据。在该 BLE SDK 中,Attribute Value 用指针和指针
所指区域的长度来描述。
DA
3.3.3.2 Attribute and ATT Table
u8 perm;
u8 uuidLen;
u32 attrLen; //4 bytes aligned
ed
u8* uuid;
u8* pAttrValue;
ar
att_readwrite_callback_t w;
att_readwrite_callback_t r;
Sh
} attribute_t;
DA
rN
图 3-41 该 BLE SDK Attribute Table 截图
de
请注意,Attribute Table 的定义前面加了 const:
const attribute_t my_Attributes[] = { ... };
un
1) attNum
attNum 有两个作用。
Sh
DA
除了第 0 项 Attribute 和每一个 service 首个 Attribute 外,其他所有的 Attribute
的 attNum 的值都必须设为 0。
rN
2) perm
de
perm 是 permission 的简写。
un
3) uuid、uuidLen
DA
中的地址,uuidLen 为 2,master 来读这个 Attribute 时,UUID 会是 0x2803。
rN
de
un
ed
ar
Sh
{0x18,0x2B,0x0d,0x0c,0x0b,0x0a,0x09,0x08,0x07,0x06,0x05,0x04,0x03,0x02,0x01,0x0}
DA
4) pAttrValue、attrLen
const u8 hidInformation[] =
Sh
0x00, // bCountryCode
0x01 // Flags
};
DA
Attribute Handle=40 battery value 的 Attribute,相关代码:
u8 my_batVal[1] = {99};
{0,1,2,1,(u8*)(&my_batCharUUID), rN
(u8*)(my_batVal), 0},
5) 回调函数 w
ed
回调函数 w 是写函数。函数原型:
typedef int (*att_readwrite_callback_t)(void* p);
ar
的,只能有一个生效。
user 设置回调函数 w 是为了处理 master 在 ATT 层的 Write Request、Write
Command 和 Execute Write Request 命令,如果没有设置回调函数 w,需要评估
pAttrValue 所指向的区域是否能够完成对以上命令的处理(如 pAttrValue 指向 flash
无法完成写操作;或者 attrLen 长度不够,master 的写操作会越界,导致其他数
据被错误的改写)。
DA
rN
图 3-43 BLE 协议栈中 Write Request
de
un
ed
ar
Sh
u8 att; //opcode
u8 hl; //low byte of Atthandle
u8 hh; //high byte of Atthandle
DA
u8 dat[20];
}rf_packet_att_data_t;
rN
p 指向 dma_len。写过来的数据有效长度为 l2cap - 3,第一个有效数据为
de
pw->dat[0]。
{
rf_packet_att_data_t *pw = (rf_packet_att_data_t *)p;
int len = pw->l2cap - 3;
ed
return 1;
}
6) 回调函数 r
回调函数 r 是读函数。函数原型:
typedef int (*att_readwrite_callback_t)(void* p);
user 如果需要定义回调读函数,须遵循上面格式。回调函数 r 是 optional 的,
对某一个具体的 Attribute 来说,user 可以设置回调读函数,也可以不设置回调
(不设置回调的时候用空指针 0 表示)。
slave 收到以上读命令后,
A. 如果 user 设置了回调读函数,执行该函数,根据该函数的返回值决定是
否回复 Read Response/Read Blob Response:
a. 若返回值为 1,slave 不回复 Read Response/Read Blob Response 给
master。
b. 若返回值为其他值,slave 从 pAttrValue 指针所指向的区域读 attrLen
DA
个值用 Read Response/Read Blob Response 回复给 master。
B. 如果 user 没有设置回调读函数,slave 从 pAttrValue 指针所指向的区域读
rN
attrLen 个值用 Read Response/Read Blob Response 回复给 master。
de
如果 user 想在收到 master 的 Read Request/Read Blob Request 后修改即将回
复的 Read Response/Read Blob Response 的内容,就可以注册对应的回调函数 r,
在回调函数里修改 pAttrValue 指针所指 ram 的内容,并且 return 的值只能是 0。
un
ed
7) Attribute Table 结构
DA
rN
de
un
ed
ar
Sh
DA
ATT PDU 进行分析。
rN
1) Read by Group Type Request、Read by Group Type Response
DA
rN
de
图 3-47 Read by Group Type Request/Read by Group Type Response
un
DA
Attribute。
rN
de
DA
Find information request 和 Find information response 详请参照《Core_v5.0》
(Vol 3/Part F/3.4.3.1 and 3.4.3.2)。
rN
master 发送 Find information request,指定起始和结束的 attHandle。slave 收
到该命令后,将起始和结束的所有 attHandle 对应 Attribute 的 UUID 通过 Find
de
information response 回复给 master。如下图所示,master 要求获得 attHandle
0x0016 ~0x0018 三个 Attribute 的 information,slave 回复这三个 Attribute 的 UUID。
un
ed
ar
Sh
DA
F/3.4.4.5 and 3.4.4.6)。
当 slave 某个 Attribute 的 Attribute Value 值的长度超过 MTU_SIZE(目前 SDK
rN
中为 23)时,master 需要启用 Read Blob Request 来读取该 Attribute Value,从而
使得 Attribute Value 可以分包发送。master 在 Read Blob Request 指定 attHandle
和 ValueOffset,slave 收到该命令后,找到对应的 Attribute,根据 ValueOffset 值
de
通过 Read Blob Response 回复 Attribute Value(若设置了回调函数 r,执行该函数)。
un
DA
据,涉及到 GATT 层分包和拼包时,需要提前和 master 交互双方的 RX MTU size,
也就是 MTU size exchange 的过程。MTU size exchange 的目的是为了实现 GATT 层
长包数据的收发。 rN
de
A. 用户可以通过注册 GAP event 回调并开启 eventMask:
GAP_EVT_MASK_ATT_EXCHANGE_MTU 来获取 EffectiveRxMTU,其中:
un
EffectiveRxMTU=min(ClientRxMTU, ServerRxMTU)。
本文档 “GAP event”小节会详细介绍 GAP event。
ed
BLE_SUCCESS 0
见 SDK
GATT_ERR_INVALID_PARAMETER mtu_size 大于最大值 250
中定义
DA
再调用下面 API 主动发起一个 ATT_Exchange_MTU_req:
ble_sts_t rN
blc_att_requestMtuSizeExchange (
blc_att_requestMtuSizeExchange(BLS_CONN_HANDLE, 158);
ed
ClientRxMTU 会上报给用户。
Sh
9) Write Command
DA
master 发送 Write Command,指定某个 attHandle,并附带相关数据。slave
收到后,找到指定的 Attribute,根据 user 是否设置了回调函数 w 决定数据是使
rN
用回调函数 w 来处理还是直接写入对应的 Attribute Value,不回复任何信息。
de
10) Queued Writes
un
A. 提供长属性值的写入功能。
B. 允许在一个单独执行的原子操作中写入多个值。
Sh
DA
rN
de
DA
ble_sts_t Value ERR reason
BLE_SUCCESS 0
LL_ERR_CONNECTION_NOT_ESTABLIS 见 SDK
rNLink Layer 处于 None Conn state
H 中定义
de
见 SDK 处于配对或加密阶段,不能发送
LL_ERR_ENCRYPTION_BUSY
中定义 数据
见 SDK 有大数据量任务在运行,软件 Tx
un
LL_ERR_TX_FIFO_NOT_ENOUGH
中定义 fifo 不够用
GATT_ERR_DATA_PENDING_DUE_TO_ 见 SDK
处于遍历服务阶段,不能发数据
ed
SERVICE_DISCOVERY_BUSY 中定义
ar
DA
该 BLE SDK 提供 API,用于某个 Attribute 的 Handle Value Indication。user 调
用这个 API 以将自己需要 indicate 的数据 push 到底层的 BLE 软件 fifo,协议栈会
rN
在最近的收发包 interval 时将软件 fifo 的数据 push 到硬件 fifo,最终通过 RF 发送
出去。
de
ble_sts_t bls_att_pushIndicateData (u16 handle, u8 *p, int len);
un
DA
attHandle, u8 *p, int len);
应用层每调用一次 bls_att_pushIndicateData(或者调用
blc_gatt_pushHandleValueIndicate),向 master 发送 indicate 数据后,master 会
Sh
DA
rN
de
un
ed
ar
Sh
图3-59 服务请求响应映射关系
DA
图3-60 ATT Permission定义
rN
最终 GATT service security 的实现跟 SMP 初始化时的参数配置包括支持的最
高安全级别设置、ATT 表中的特性权限设置等都有关系,而且跟 master 也有关系,
de
比如我们 slave 设置的 SMP 能支持的最高等级是 Authenticated pairing with
encryption,但是 master 具备的最高安全等级是 Unauthenticated pairing with
un
需要注意的是:用户设置的安全级别只表示设备能支持的最高安全级别,只
要 ATT 表中特性的权限(ATT Permission)不超过实际生效的最高级别就可以通
过 GATT service security 管控。对于 LE 安全模式 1 中的等级 4 来说,如果用户只
设置 Authenticated LE Secure Connections 一种级别,则代表当前设置支持 LE
Secure Connections only。
dat 实际长度(byte):11 。
DA
void att_req_read_by_type (u8 *dat, u16 start_attHandle, u16
end_attHandle, u8 *uuid, int uuid_len);
dat 实际长度(byte):9 。
un
dat 实际长度(byte):11 。
ar
u8 cmd[12];
att_req_find_info(cmd, 0x0001, 0x0003);
if( blm_push_fifo (BLM_CONN_HANDLE, cmd) ){
//cmd send OK
}
DA
}
if(pAtt->opcode == ATT_OP_FIND_INFO_RSP){
}
//add your code rN
else if(pAtt->opcode == ATT_OP_FIND_BY_TYPE_VALUE_RSP){
de
//add your code
}
un
}
else if(pAtt->opcode == ATT_OP_READ_BLOB_RSP){
Sh
3.3.4 SMP
Security Manager(SM)在 BLE 中的主要目的是为 LE 设备提供加密所需要的
各种 Key,确保数据的机密性。加密链路可以确保避免第三方“攻击者”拦截、
破译或者读取空中数据原始内容。SMP 详细内容请用户参考《Core_v5.0》(Vol
3/Part H/ Security Manager Specification)。
DA
rN
de
图 3-61 本地设备配对状态
un
注意:本端设备设定的安全级别只表示本端设备可能达到的最高安全级别,
想要达到设定的安全级别跟两个因素有关:1、master 对端设定能支持的最高安
全级别>=slave 本端设定能支持的最高安全级别;2、本端和对端按照各自设定的
SMP 参数正确处理完配对整个流程(如果存在配对的话)。
DA
}le_security_mode_level_t;
blc_smp_setSecurityLevel(No_Security);
Sh
表示设备端不会对当前连接进行配对加密过程,即使对方请求配对加密时,
设备端也会拒绝配对加密。一般用于当前设备不支持设备加密配对的过程。
如下图,master 发起配对请求,slave 回复 SM_Pairing_Failed。
DA
B. 使用 LE security mode1 level1 以外的安全级别配置就必须要调用如下 API
用于初始化 SMP 各参数配置,包括绑定区域 FLASH 的初始化配置:
int blc_smp_peripheral_init (void);
rN
de
如果在初始化阶段只调用了该 API,则 SDK 会使用默认参数去配置 SMP:
默认支持的最高安全等级:Unauthenticated_Paring_with_Encryption;
un
默认绑定模式:Bondable_Mode(存储配对加密后分发的 KEY 到
FLASH);
ed
默认 IO 能力是 IO_CAPABILITY_NO_INPUT_NO_OUTPUT。
ar
DA
Authentication 能确保配对双方身份的合法性,提供这种方式的保护又可以称
为 MITM(Man in the Middle)中间人保护。
rN
de
A. 具备 Authentication 的设备需要设置其 MITM flag 或 OOB flag,SDK 提供
如下两个 API 用于设置 MITM 和 OOB flag 的值:
un
DA
式还是根据 IO 能力决定选择什么样的 KEY 产生方式。下图是 SDK 根据 IO
能力映射关系选择不同的 KEY 产生方法(行、列参数类型 io_capability_t):
rN
de
un
ed
图3-64 根据不同IO能力映射KEY产生方法
ar
Sh
DA
这里因为涉及到用户输入 TK 值,SDK 在应用层提供了相关的 GAP event
给用户,请参考“GAP event”章节。提供给用户设置 Passkey Entry 的 TK 值
API 如下: rN
void blc_smp_setTK_by_PasskeyEntry (u32 pinCodeInput);
参数 pinCodeInput 表示设置的 pincode 值,范围在“0~999999”。在 Passkey
de
Entry 方式下,master 显示 TK,slave 需要输入 TK 的情况下使用。
un
参数 YES_or_NO:在数值比较配对方式下,用于给用户确认比较两端显示
的数值是否一致。当用户确认显示的 6 位数值和对端一致时,可以输入 1:
“YES”,不一致则输 0:“NO”。
DA
该部分目前 SDK 并未实现,所以这里就不再做具体介绍。
Bondable_Mode = 1,
}bonding_mode_t;
ar
该 API 仅在安全连接配对情况下使用,由于安全连接配对情况下使用了椭
圆加密算法,可以有效避免窃听,这对调试开发就不那么友好了,用户无
法通过 sniffer 工具抓取 BLE 空中包,进而进行数据分析调试,所以 BLE spec
也规定给出了一组用于 Debug 的椭圆加密私钥/公钥对,只要开启这个模
式,BLE sniffer 工具就可以用已知的密钥去解密链路。
前面已经介绍了每个参数含义,这里就不再重复了。
DA
3.3.4.3 SMP 安全请求配置
rN
SMP 安全请求(Security Request)只有 slave 可以发送,所以这部分只对 slave
设备来说。
de
我们知道配对流程阶段 1 有一个可选的安全请求包(Security Request),该
包的目的是使 slave 可以主动触发配对流程的开始。SDK 提供了如下 API 用于灵
un
每个参数的意义介绍如下:
SecReq_NOT_SEND:连接建立后,slave 不会主动发送 Security Request;
SecReq_IMM_SEND:连接建立后,slave 会立即发送 Security Request;
SecReq_PEND_SEND:连接建立后,slave 等待 pending_ms(单位毫秒)后再决定
是否发送 Security Request(1、首次连接,slave 在 pending_ms 毫秒前就收到 master
的 Pairing_request 包也不会再发送 Security Request;2、在回连阶段,pending_ms
毫秒之前如果 master 已经发送 LL_ENC_REQ 加密回连链路,则不再发送 Security
Request)。
AN-19011501-C5 168 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
newConn_cfg:用于配置新设备,reConn_cfg:用于配置回连的设备。这里 SDK
在回连时也提供配置是否发配对请求的目的:配对绑定过的设备,下次再连接的
时候(即回连),master 有时候不一定会主动发起 LL_ENC_REQ 来加密链路,此
时如果 slave 发一下 Security Request 就会去触发 master 主动加密链路,所以 SDK
提供了 reConn_cfg 配置,客户可以根据实际需要配置。
注意:当前函数只能在连接之前调用。建议在初始化的时候调用。
函数blc_smp_configSecurityRequestSending的输入参数有如下9种组合:
reConn_cfg
SecReq_NOT_SEND SecReq_IMM_SEND SecReq_PEND_SEND
newConn_cfg
DA
第一次连接不发
第一次连接不发
第一次连接或者回连 SecReq,回连
SecReq,回连立即发
SecReq_NOT_SEND 都不发SecReq(参数 pending_ms毫秒后发
pending_ms无效)
rN
SecReq(参数
pending_ms无效)
SecReq(*见前面参数
说明)
de
第一次连接立即发
第一次连接立即发 第一次连接或者回
SecReq,回连
SecReq,回连不发 连都立即发SecReq
SecReq_IMM_SEND pending_ms毫秒后发
un
SecReq(参数 (参数pending_ms
SecReq(*见前面参数
pending_ms无效) 无效)
说明)
ed
第一次连接 第一次连接
第一次连接或者回连
pending_ms毫秒后发 pending_ms毫秒后
都pending_ms毫秒后
SecReq_PEND_SEND SecReq(*见前面参数 发SecReq(*见前面
ar
发SecReq(*见前面参
说明),回连不发 参数说明),回连立
数说明)
SecReq 即发SecReq
Sh
我们挑其中两组做一下详细说明,其他组合类似,不做具体介绍:
1) newConn_cfg:SecReq_NOT_SEND,
reConn_cfg:SecReq_NOT_SEND,
pending_ms:此时该参数不起作用。
newConn_cfg:SecReq_NOT_SEND表示新设备slave不会主动发起Security
Request,只有在对方发起配对请求时响应对方的配对请求。如果对方不发送
配对请求,则不会进行加密配对。如下图,在master发送配对请求包
SM_Pairing_Req时,slave会响应,但是不会主动触发master发起配对请求。
reConn_cfg:SecReq_NOT_SEND表示设备已经配对,回连时slave设备不会发
送Security Request。
2) newConn_cfg:SecReq_IMM_SEND,
reConn_cfg:SecReq_NOT_SEND,
DA
pending_ms:此时该参数不起作用。
newConn_cfg:SecReq_IMM_SEND表示新设备slave一经连接便会主动向
rN
master发Security Request,以触发master开始配对流程。如下图,slave主动
发送SM_Security_Req 触发master发送配对请求:
de
un
reConn_cfg:SecReq_NOT_SEND表示回连的时候slave不会发送Security
ar
Request。
Sh
此外SDK还提供了一个单独发送Security Request包的API,用于特殊应用场合,
应用层可以随时调用该API发送Security Request包:
int blc_smp_sendSecurityRequest (void);
这里需要注意的时,用户如果使用blc_smp_configSecurityRequestSending管控
安全配对请求包的话,就不要再使用调用blc_smp_sendSecurityRequest函数。
DA
下面 API 用于获取当前 slave 在 flash 上存储的配对成功的 master 设备数量。
rN
u8 blc_smp_param_getCurrentBondingDeviceNumber(void);
de
假设返回值为 3,就说明 flash 上目前存储了 3 个配对成功的设备,这 3 个
设备都可以回连成功。
un
1) 绑定信息存储顺序
ed
DA
C、D,此时 masterD 是 index3 设备,若 slave 和 masterB 回连一次,则 master
B 成为最新的 index3 设备。
由于 index 是以最近连接时间为顺序的。所以也要注意设备配对超过 4 的情
rN
况:若连续配对 masterA、B、C、D,再配对 master E,则 slave 会删除最老
的 masterA;但如果在配对 masterA、B、C、D 后,先和 masterA 回连一次,
de
此时顺序变为 B、C、D、A,再配对 masterE 的话 slave 就会删除 masterB 的
配对信息。
un
B. 根据设备配对时间先后为顺序(Index_Update_by_Pairing_Order)
ed
2) 绑定信息格式及相关 API 说明
Master 设备绑定信息存储在 flash 上,其格式为:
DA
绑定信息共 64byte。
peer_addr_type 和 peer_addr 是 link layer 上 master 的连接地址,设备 direct
rN
adv 时使用这个地址。
peer_id_adrType/peer_id_addr 和 peer_irk 是 master 在 key distribution 阶段宣
de
称的 identity address 和 irk。
只有当 peer_addr_type 和 peer_addr 是可解析的私有地址(resolvable private
un
addr,简称 RPA),且用户需要使用地址过滤时,才需要将相关的信息添加到
resolving list 中,以便 slave 可以解析出(参考 8258_feature_test 中 TEST_WHITELIST
的用法)。
ed
DA
3.3.4.5 master SMP
下面分别介绍这三个 API:
A. blm_smp_configParingSecurityInfoStorageAddr
该 API 可以用于 master 设备配置绑定信息存储在 FLASH 中的位置,其中
参数 addr 可以根据实际需要修改。
B. blm_smp_registerSmpFinishCb
该回调函数在配对第三阶段密钥分发完成后触发,用户可以在应用层注册
以获取配对完成事件。
C. blm_host_smp_setSecurityTrigger
该 API 主要用于配置 master 是否主动发起加密、回连时主动加密链路。
DA
具体参数可以选择如下:
#define SLAVE_TRIGGER_SMP_FIRST_PAIRING 0
rN
#define MASTER_TRIGGER_SMP_FIRST_PAIRING
#define SLAVE_TRIGGER_SMP_AUTO_CONNECT
BIT(0)
0
#define MASTER_TRIGGER_SMP_AUTO_CONNECT BIT(1)
de
具体说就是:1、第一次配对时,是 master 选择 master 主动发起配对请
求还是在收到 slave 发送的 Security Request 后再开始配对;2、已经配对
un
第一次配对主动发起配对请求,回连时主动发 LL_ENC_REQ。
ar
blm_smp_configParingSecurityInfoStorageAddr(0x78000);
blm_smp_registerSmpFinishCb(app_host_smp_finish);
blc_smp_central_init();
//SMP trigger by master
blm_host_smp_setSecurityTrigger(MASTER_TRIGGER_SMP_FIRST_PAIRING |
MASTER_TRIGGER_SMP_AUTO_CONNECT);
2) 非标准的自定义配对管理(设置宏 BLE_HOST_SMP_ENABLE 为 0)
A. Flash 存储方法设计
默认使用的 flash 数据区 sector 为 0x78000 ~ 0x78FFF,在 app_config.h 中可
以修改:
#define FLASH_ADR_PARING 0x78000
DA
area 可以存储一个 Slave 的 mac address,其中第一个 byte 是标志位,第二个 byte
为地址类型,后面 6 个为 6 bytes 的 mac address。
typedef struct {
u8 bond_mark;
rN
u8 adr_type;
de
u8 address[6];
} macAddr_t;
un
DA
user_salveMac_t user_tbl_slaveMac;
rN
用上面结构在 ram 中使用 slave mac table 维护所有的配对设备,改变宏
USER_PAIR_SLAVE_MAX_NUM 即可定义自己想要的最多允许几只配对,Telink 默
de
认为 1,指维护 1 个设备的配对,user 可以修改这个值。
un
C. 相关 API 说明
基于上面 flash 存储设计和 ram 中 slave mac table 的设计,分别有以下几个
API 可以调用。
a. user_master_host_pairing_flash_init
void user_master_host_pairing_flash_init(void);
b. user_tbl_slave_mac_add
int user_tbl_slave_mac_add(u8 adr_type, u8 *adr);
DA
User 可以根据自己的策略去修改这个函数的实现。
c. user_tbl_slave_mac_search
rN
int user_tbl_slave_mac_search(u8 adr_type, u8 * adr)
de
根据 adv report 的设备地址搜索该设备是否已经在 slave mac table 中,
即判断当前发广播包的设备是否之前已经和 master 配对上,若是已
un
经配对过的设备可以直接连接。
ed
d. user_tbl_slave_mac_delete_by_adr
int user_tbl_slave_mac_delete_by_adr(u8 adr_type, u8 *adr)
ar
通过指定地址删除一个配对的设备。
Sh
e. user_tbl_slave_mac_delete_by_index
void user_tbl_slave_mac_delete_by_index(int index)
f. user_tbl_slave_mac_delete_all
void user_tbl_slave_mac_delete_all(void)
删除所有配对设备。
g. user_tbl_salve_mac_unpair_proc
void user_tbl_salve_mac_unpair_proc(void)
处理解配对命令,参考代码中删除所有配对设备,是默认最大配对个
数为 1 时的处理方法。User 可以修改该函数实现。
D. 连接和配对
master 收到 Controller 上报的广播包时,有以下两种情况会和 Slave 进行连
接:
DA
master_auto_connect = user_tbl_slave_mac_search(pa->adr_type,
pa->mac);
rN
if(master_auto_connect) { create connection }
de
b. 若当前广播设备不在 slave mac table 里面,不符合自动连接,检查是
否满足手动配对条件。SDK 中默认设置了两个手动配对方案,在当
un
E. 解配对
DA
参考上面 code,当解配对条件生效时,Master 先调用 blm_ll_disconnect 断
开连接,然后调用 user_tbl_salve_mac_unpair_proc 函数处理解配对,Demo code
直接删掉所有的配对设备,由于默认的最大配对个数是 1,所以也就只删掉了一
个。如果 user 设置了比较复杂的配对多个设备,此时应该调用
rN
user_tbl_slave_mac_delete_by_adr 或 user_tbl_slave_mac_delete_by_index 去删除
de
某个设备。
un
User 也可以按照自己的需要去修改解配对的触发条件。
ar
3.3.5 GAP
3.3.5.1 GAP 初始化
Sh
DA
void blc_gap_registerHostEventHandler (gap_event_handler_t handler);
rN
callback 函数原型中的 u32 h 是 GAP event 标记,底层协议栈多处会用到,
下面列出几个用户可能会用到的事件:
de
#define GAP_EVT_SMP_PARING_BEAGIN 0
#define GAP_EVT_SMP_PARING_SUCCESS 1
#define GAP_EVT_SMP_PARING_FAIL 2
un
#define GAP_EVT_SMP_CONN_ENCRYPTION_DONE 3
#define GAP_EVT_SMP_TK_DISPALY 4
ed
#define GAP_EVT_SMP_TK_REQUEST_PASSKEY 5
#define GAP_EVT_SMP_TK_REQUEST_OOB 6
#define GAP_EVT_SMP_TK_NUMERIC_COMPARE 7
ar
#define GAP_EVT_ATT_EXCHANGE_MTU 16
Sh
#define GAP_EVT_GATT_HANDLE_VLAUE_CONFIRM 17
eventMask的定义也对应上面给出一些,其他的event mask用户可以在
ble/gap/gap_event.h中查到。
#define GAP_EVT_MASK_SMP_PARING_BEAGIN
(1<<GAP_EVT_SMP_PARING_BEAGIN)
#define GAP_EVT_MASK_SMP_PARING_SUCCESS
(1<<GAP_EVT_SMP_PARING_SUCCESS)
#define GAP_EVT_MASK_SMP_PARING_FAIL
(1<<GAP_EVT_SMP_PARING_FAIL)
#define GAP_EVT_MASK_SMP_CONN_ENCRYPTION_DONE
(1<<GAP_EVT_SMP_CONN_ENCRYPTION_DONE)
#define GAP_EVT_MASK_SMP_TK_DISPALY
(1<<GAP_EVT_SMP_TK_DISPALY)
#define GAP_EVT_MASK_SMP_TK_REQUEST_PASSKEY
DA
(1<<GAP_EVT_SMP_TK_REQUEST_PASSKEY)
#define GAP_EVT_MASK_SMP_TK_REQUEST_OOB
(1<<GAP_EVT_SMP_TK_REQUEST_OOB)
rN
#define GAP_EVT_MASK_SMP_TK_NUMERIC_COMPARE
(1<<GAP_EVT_SMP_TK_NUMERIC_COMPARE)
de
#define GAP_EVT_MASK_ATT_EXCHANGE_MTU
(1<<GAP_EVT_ATT_EXCHANGE_MTU)
un
#define GAP_EVT_MASK_GATT_HANDLE_VLAUE_CONFIRM
(1<<GAP_EVT_GATT_HANDLE_VLAUE_CONFIRM)
若 user 未通过该 API 设置 GAP event mask,那么当 GAP 相应的 event 产生时
ed
将不会通知应用层。
ar
应的 eventMask。
1) GAP_EVT_SMP_PARING_BEAGIN
数据长度 n: 4。
回传指针 p:指向一片内存数据,对应如下结构体:
typedef struct {
u16 connHandle;
u8 secure_conn;
u8 tk_method;
} gap_smp_paringBeginEvt_t;
connHandle 表示当前连接句柄。
secure_conn 为 1 表示使用安全加密特性(LE Secure Connections),否则将
使用 LE legacy pairing。
tk_method 表示接下来配对使用什么样的 TK 值方式:例如 JustWorks、
PK_Init_Dsply_Resp_Input、PK_Resp_Dsply_Init_Input,Numric_Comparison 等。
DA
2) GAP_EVT_SMP_PARING_SUCCESS rN
事件触发条件:配对整个流程正确完成时产生该事件,该阶段即为 LE 配对
阶段之密钥分发阶段 3(Key Distribution, Phase 3),如果有密钥需要分发,则等
de
待双方密钥分发完成后触发配对成功事件,否则直接触发配对成功事件。
数据长度 n:4。
un
回传指针 p:指向一片内存数据,对应如下结构体:
typedef struct {
ed
u16 connHandle;
u8 bonding;
ar
u8 bonding_result;
} gap_smp_paringSuccessEvt_t;
Sh
connHandle 表示当前连接句柄。
bonding 为 1 表示启用 bonding 功能,否则不启用。
bonding_result 表示 bonding 的结果:如果没有开启 bonding 功能,则为 0,
如果开启了 bonding 功能,则还需要检查加密 Key 是否被正确的存储在 FLASH 中,
存储成功为 1,否则为 0。
3) GAP_EVT_SMP_PARING_FAIL
typedef struct {
u16 connHandle;
u8 reason;
} gap_smp_paringFailEvt_t;
connHandle 表示当前连接句柄。
reason 表示配对失败的原因,这里列出几个常见的配对失败原因值,其他配
对失败原因值我们可以参考 SDK 目录下的“stack/ble/smp/smp_const.h”文件。
配对失败值具体含义可以参照《Core_v5.0》(Vol 3/Part H/3.5.5 “Pairing
Failed”)。
#define PARING_FAIL_REASON_CONFIRM_FAILED 0x04
#define PARING_FAIL_REASON_PARING_NOT_SUPPORTED 0x05
#define PARING_FAIL_REASON_DHKEY_CHECK_FAIL 0x0B
DA
#define PARING_FAIL_REASON_NUMUERIC_FAILED 0x0C
#define PARING_FAIL_REASON_PARING_TIEMOUT 0x80
#define PARING_FAIL_REASON_CONN_DISCONNECT 0x81
rN
4) GAP_EVT_SMP_CONN_ENCRYPTION_DONE
de
事件触发条件:Link Layer 加密完成时(Link Layer 收到 master 发的 start
encryption response)触发。
un
数据长度 n:3。
回传指针 p:指向一片内存数据,对应如下结构体:
ed
typedef struct {
u16 connHandle;
ar
} gap_smp_connEncDoneEvt_t;
connHandle 表示当前连接句柄。
re_connect 为 1 表示快速回连(将使用之前分发的 LTK 加密链路),若该值
为 0 则表示当前加密是第一次加密。
5) GAP_EVT_SMP_TK_DISPALY
#else//使用底层随机生成的pincode码
u32 pinCode = *(u32*)para;
#endif
}
DA
break;
rN
用户将 slave 上看到的 6 位 pincode 码输入到 master 设备上(如手机),完
成 TK 输入,配对流程得以继续执行。如果用户输入 pincode 错误或者点击取消,
de
则配对流程失败。
关于 Passkey Entry 应用的实例,用户可以参考 SDK 提供的 demo
un
“sdk/8258_feature_test/feature_security.c”。
ed
6) GAP_EVT_SMP_TK_REQUEST_PASSKEY
ar
7) GAP_EVT_SMP_TK_REQUEST_OOB
事件触发条件:当 slave 设备启用传统配对 OOB 方式时,会触发该事件,通
知用户需要通过 OOB 方式输入 16 位 TK 值。用户在收到该事件后就需要通过 IO
输入能力输入 16 位 TK 值(超时 30s 如果还未输入则配对失败),输入 TK 值的
API:blc_smp_setTK_by_OOB 在“SMP 参数配置”章节有说明。
数据长度 n:0。
AN-19011501-C5 185 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
回传指针 p:NULL。
8) GAP_EVT_SMP_TK_NUMERIC_COMPARE
DA
的 6 位 pincode 码,应用层需要显示该 6 位码值,并提供“YES”和“NO”的确
认机制。
关于数值比较应用的实例,用户可以参考 SDK 提供的 demo rN
“sdk/8258_feature_test/feature_security.c”。
de
9) GAP_EVT_ATT_EXCHANGE_MTU
un
数据长度 n:6。
ar
回传指针 p:指向一片内存数据,对应如下结构体:
typedef struct {
Sh
u16 connHandle;
u16 peer_MTU;
u16 effective_MTU;
} gap_gatt_mtuSizeExchangeEvt_t;
connHandle 表示当前连接句柄。
peer_MTU 表示对端的 RX MTU 值。
effective_MTU = min(CleintRxMTU, ServerRxMTU),CleintRxMTU 表示客户端的
RX MTU size 值,ServerRxMTU 表示服务端的 RX MTU size 值。Master 和 slave 交
互了彼此的 MTU size 后,取两者最小值作为彼此交互的最大 MTU size 值。
10) GAP_EVT_GATT_HANDLE_VLAUE_CONFIRM
事件触发条件:应用层每调用一次 bls_att_pushIndicateData(或者调用
blc_gatt_pushHandleValueIndicate),向 master 发送 indicate 数据后,master 会
回复一个 confirm,表示对这个数据的确认,slave 收到该 confirm 时触发。
数据长度 n:0。
回传指针 p:NULL。
DA
rN
de
un
ed
ar
Sh
4 低功耗管理(PM)
4.1.1 低功耗模式
DA
sleep mode
suspend deepsleep retention deepsleep
module
analog
100% keep 99% lost 99% lost
register
ed
1) suspend mode
sleep mode 1
此时程序停止运行,类似一个暂停功能。MCU 大部分硬件模块断电,PM 模
块维持正常工作。此时 IC 电流在 60~70 uA 之间。当 suspend 被唤醒后,程
序继续执行。
suspend mode 下所有的 Sram 和 analog register 都能保存状态,绝大部分 digital
register 都保持状态。digital register 中存在少量会掉电的,包括:
a) baseband 电路中少量的 digital register,user 需要关注的是 API
rf_set_power_level_index 设置的寄存器,本文档前面已经介绍,这个 API
需要在每次 suspend 醒来后都重新调用一次。
2) deepsleep mode
sleep mode 2
此时程序停止运行,MCU 绝大部分的硬件模块都断电,PM 硬件模块维持工
作。在 deepsleep mode 下 IC 电流小于 1uA。如果内置 flash 的 standby 电流
出现较大的 1uA 左右,可能导致测量到 deepsleep 为 1~2uA。deepsleep mode
wake_up 时,MCU 将重新启动,类似于上电的效果,程序会重新开始进行初
始化。
Deepsleep mode 下,除了 analog register 上有少数几个 register 能保存状态,
DA
其他所有 Sram、digital register、analog register 全部掉电丢失。
DA
替 gpio output high,下拉 100K 代替 gpio output low。
模拟寄存器数量比较少,建议客户使用其每一个bit指示不同的状态位信息,
具体可以参考SDK的vendor目录下的“8258_ble_remote”。
ar
如下几组不掉电模拟寄存器可能会因为错误的GPIO唤醒而丢掉信息,比如
Sh
GPIO_PAD高电平唤醒deepsleep,但是在调用cpu_sleep_wakeup函数前gpio
已经为高电平,这就会导致错误的GPIO唤醒,那么这些模拟寄存器值将会丢
失。
#define DEEP_ANA_REG6 0x35
#define DEEP_ANA_REG7 0x36
#define DEEP_ANA_REG8 0x37
#define DEEP_ANA_REG9 0x38
#define DEEP_ANA_REG10 0x39
4.1.2 低功耗唤醒源
}SleepWakeupSrc_TypeDef;
PA0
PA1 wakeup Suspend wakeup
DA
. Mode
.
.
.
rN
GPIO Deepsleep wakeup 32k
. wakeup
de
PAD Retention
. timer
Mode
.
un
.
.
wakeup Deepsleep wakeup
ed
PD6 Mode
PD7
ar
Sh
DA
举例说明:
cpu_set_gpio_wakeup (GPIO_PC2, Level_High, 1); //GPIO_PC2 PAD 唤醒打开, 高电平唤醒
4.1.3 低功耗模式的进入和唤醒
ed
SleepWakeupSrc_TypeDef wakeup_src,
unsigned int wakeup_tick);
Sh
}SleepMode_TypeDef;
DA
由于 wakeup_tick 是绝对时间,必须在 32bit 的 System Timer tick 能表示的范
围之内,所以这个 API 能表示的最大睡眠时间是有限的。目前的设计是最大睡眠
时间为 32bit 能表示的最大 System Timer tick 对应时间的 7/8。System Timer tick
rN
最大能表示大概 268S,那么最长 sleep 时间时间为 268*7/8=234 S,即下面
delta_Tick 不能超过 234 S。
de
cpu_sleep_wakeup(SUSPEND_MODE, PM_WAKEUP_TIMER,
clock_time() + delta_tick);
un
源为:
enum {
ar
WAKEUP_STATUS_TIMER = BIT(1),
WAKEUP_STATUS_PAD = BIT(3),
Sh
STATUS_GPIO_ERR_NO_ENTER_PM = BIT(7),
};
一般采用如下的形式来控制睡眠时间:
cpu_sleep_wakeup (SUSPEND_MODE , PM_WAKEUP_TIMER,
clock_time() + delta_Tick);
delta_Tick 是一个相对的时间(比如 100* CLOCK_16M_SYS_TIMER_CLK_1MS),
加上当前的 clock_time()就变成了绝对时间。
DA
2) cpu_sleep_wakeup (SUSPEND_MODE , PM_WAKEUP_TIMER, clock_time() + 10*
CLOCK_16M_SYS_TIMER_CLK_1MS;
clock_time() + 8* CLOCK_16M_SYS_TIMER_CLK_1S);
程序执行该函数时进入 deepsleep retention 16K Sram mode,可被 Timer 唤醒,
唤醒时间为执行该函数的 8 s 后。
6) cpu_sleep_wakeup (DEEPSLEEP_MODE_RET_SRAM_LOW32K , PM_WAKEUP_PAD |
PM_WAKEUP_TIMER,clock_time() + 10* CLOCK_16M_SYS_TIMER_CLK_1S);
4.1.4 低功耗唤醒后运行流程
Power on
Running
hardware bootloder
DA
Running
software bootloder
System initialization
rN
de
User initialization
un
main_loop
ed
while(1)
{
Operation set A
ar
Sh
no
cpu_sleep_wakeup(...)
sleep
wakeup
deepsleep
suspend deepsleep
retention
wakeup wakeup
Operation set B
DA
hardware bootloader 运行结束之后,MCU 开始运行 software bootloader。
Software bootloader 就是前面介绍过的 vector 端(对应
cstartup_8258_16K_RET.S 里面的汇编程序)。
rN
Software bootloader 是为了给后面 C 语言程序的运行设置好内存环境,可以
理解为整个内存的初始化。
de
3) 系统初始化(System initialization)
un
设置各硬件模块的数字/模拟寄存器状态。
ar
4) 用户初始化(User initialization)
Sh
5) main_loop
User initialization 完成后,进入 while(1)控制的 main_loop。main_loop 中进入
sleep mode 之前的一系列操作称为"Operation Set A”,sleep 唤醒之后一系列
操作称为"Operation Set B”。
2) suspend
如果调用 cpu_sleep_wakeup 函数进入 suspend mode,当 suspend 被唤醒后,
相当于 cpu_sleep_wakeup 函数的正常退出,MCU 运行到"Operation Set B”。
suspend 是最干净的 sleep mode,在 suspend 期间所有的 Sram 数据能保持不
变,所有的数字/模拟寄存器状态也保持不变(只有几个特殊的例外);suspend
唤醒后,程序接着原来的位置运行,几乎不需要考虑任何 sram 和寄存器状
态的恢复。suspend 的缺点是功耗偏高。
DA
3) deepsleep rN
如果调用 cpu_sleep_wakeup 函数进入 deepsleep mode,当 deepsleep 被唤醒
后,MCU 会重新回到 Run hardware bootloader。
de
可以看出,deepsleep wake_up 跟 Power on 的流程是几乎一致的,所有的软
硬件初始化都得重新做。
un
4) deepsleep retention
如果调用 cpu_sleep_wakeup 函数进入 deepsleep retention mode,当 deepsleep
Sh
DA
或deepsleep wake_up。
DA
}
}
rN
当 Link Layer 处于 Advertising state 或 Conn state Slave role 时,下图所示为
de
sleep mode 的时序。注意,图中 Conn state Slave role 为 connection latency = 0 的
情况。
un
T_advertising T_wakeup
Adv interval
ar
图 4-3 sleep timing for Advertising state & Conn state Slave role
2) 处于 Conn state Slave role 时,每个 Conn interval 内,brx Event(brx start+brx
working+brx post)时间是必须的,除去 UI task 占用的时间,剩余时间 MCU
可以进入 sleep mode (suspend/deepsleep retention)。
图中,第一个 Connection interval 上 Brx event 开始的时间我们定义为 T_brx;
sleep 需要唤醒的时间我们定义为 T_wakeup,T_wakeup 也是下一个
Connection interval 上 Brx event 的开始。T_brx 和 T_wakeup 在本文档后面的
介绍中需要使用到。
DA
BLE 低功耗管理的实质是对上面两个状态的 sleep 时间进行管理,user 可以决
定如何使用这些时间:不进入 sleep、进入 suspend mode 或进入 deepsleep
rN
retention mode。
de
8x5x 的 sleep mode 分 3 种:suspend、deepsleep、deepsleep retetnion。
un
处理。
4.2.3 相关变量
BLE PM 软件处理流程部分会出现很多变量,用户有必要了解这些变量。
请在文件 ll_pm.h 找到结构体 ”st_ll_pm_t” 的定义,下面只列出该结构体部
分变量(API 介绍时需要用到的变量)。
typedef struct {
u8 suspend_mask;
u8 wakeup_src;
u16 sys_latency;
u16 user_latency;
u32 deepRet_advThresTick;
u32 deepRet_connThresTick;
u32 deepRet_earlyWakeupTick;
DA
}st_ll_pm_t;
API:
void bls_pm_setSuspendMask (u8 mask);
Sh
u8 bls_pm_getSuspendMask (void);
使用 bls_pm_setSuspendMask 设置 bltPm.suspend_mask(默认值为
SUSPEND_DISABLE)。
这两个 API 的源码为:
void bls_pm_setSuspendMask (u8 mask)
{
bltPm.suspend_mask = mask;
}
u8 bls_pm_getSuspendMask (void)
{
return bltPm.suspend_mask;
AN-19011501-C5 201 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
bltPm.suspend_mask的设置,可以选择下面几个值中的一个,或者选择多个
值的“或操作”。
#define SUSPEND_DISABLE 0
#define SUSPEND_ADV BIT(0)
#define SUSPEND_CONN BIT(1)
#define DEEPSLEEP_RETENTION_ADV BIT(2)
#define DEEPSLEEP_RETENTION_CONN BIT(3)
DA
SUSPEND_CONN 和 DEEPSLEEP_RETENTION_CONN 分别用于控制 Conn state Slave
role 时 MCU 进入 suspend 和 deepsleep retention。
rN
SDK 低功耗 sleep mode 的设计上,deepsleep retention 是 suspend 的替代模
de
式,目的是降低 sleep mode 的功耗。
以 Conn state slave role 为例,SDK 首先得看到 bltPm.suspend_mask 中
un
SUSPEND_CONN 和 DEEPSLEEP_RETENTION_CONN。
Sh
除了上面 3 种常用的情况,也可以出现一些特殊的用法,如:
1) bls_pm_setSuspendMask(SUSPEND_ADV)
只有 Advertising state 可以进入 suspend,Conn state Slave role 不允许进
入 sleep mode。
2) bls_pm_setSuspendMask(SUSPEND_CONN | DEEPSLEEP_RETENTION_CONN),
只有 Conn state Slave role 可以进入 suspend 或 deepsleep retention,
Advertising state 不允许进入 sleep mode。
DA
或 deepsleep retention),通过下面的 API 可设置 sleep mode 的唤醒源。
void bls_pm_setWakeupSource(u8 source);
source可以选择唤醒源PM_WAKEUP_PAD。
rN
de
该API设置底层变量bltPm.wakeup_src,SDK中源码为:
void bls_pm_setWakeupSource (u8 src)
{
un
bltPm.wakeup_src = src;
}
ed
醒源为:
Sh
bltPm.wakeup_src | PM_WAKEUP_TIMER
可选的模式只有以下两种:
typedef enum {
DEEPSLEEP_MODE_RET_SRAM_LOW16K = 0x43,
DEEPSLEEP_MODE_RET_SRAM_LOW32K = 0x07,
}SleepMode_TypeDef;
DA
SDk 中默认的 deepsleep retention mode 为
DEEPSLEEP_MODE_RET_SRAM_LOW16K,user 如果需要 retention 32K sram,初始化的
时候调用如下 code 即可。 rN
注意:该 API 的调用必须在 blc_ll_initPowerManagement_module 之后才能生效。
de
blc_pm_setDeepsleepRetentionType(DEEPSLEEP_MODE_RET_SRAM_LOW32K);
un
4.2.7 PM 软件处理流程
低功耗管理的软件处理流程,下面将使用代码与伪代码相结合的方式来说
明,目的是为了让 user 了解处理流程的所有逻辑细节。
4.2.7.1 blt_sdk_main_loop
DA
blt_sdk_main_loop 函数在 while(1)中不断被执行,BLE 低功耗管理的 code 在
rN
blt_sdk_main_loop 函数中,所以低功耗管理的 code 也是一直在被执行。
de
下面是 blt_sdk_main_loop 函数中低功耗管理逻辑的实现。
un
return 0;
}
Sh
if( (Link Layer State == Advertising state) || (Link Layer State == Conn state Slave role) )
{
if(Link Layer is in Adv Event or Brx Event) //RF is working, can not enter
{ //sleep mode
return 0;
}
else
{
blt_brx_sleep (); //process sleep & wakeup
}
}
return 0;
}
4.2.7.2 blt_brx_sleep
DA
blt_brx_sleep 函数的逻辑实现如下所示。
注意: 这里以默认的 deepsleep retention 16K Sram 来说明。
rN
void blt_brx_sleep (void)
de
{
if( (Link Layer state == Adv state)&& (bltPm. suspend_mask &SUSPEND_ADV) )
un
{ //当前广播状态,允许进 suspend
T_wakeup = T_advertising + advInterval;
ed
DA
else
{
}
T_wakeup = T_brx + conn_interval; rN
de
”BLT_EV_FLAG_SUSPEND_ENTER” event callback execution
un
T_wakeup); //suspend
Sh
调整 BLE 时序相关的处理
}
}
bltPm.wakeup_src = 0;
bltPm.user_latency = 0xFFFF;
}
上面 blt_brx_sleep 函数的流程看起来比较复杂,我们的分析从最简单的情况
开始:首先 conn_latency 方面只考虑 conn_latency =0 的情况。其次暂时不考虑
deepsleep retention 生效的情况。此时只有 suspend,跟 Telink 上一代 826x 系列
IC 的低功耗管理是一致的。当应用层设置 suspend mask 时使用的是
bls_pm_setSuspendMask(SUSPEND_ADV | SUSPEND_CONN)时,就对应这种情况。
DA
结合文档前面介绍的 controller event,这里看到几个 suspend 相关 event 回
调函数的执行的时机:BLT_EV_FLAG_SUSPEND_ENTER、 rN
BLT_EV_FLAG_SUSPEND_EXIT、BLT_EV_FLAG_GPIO_EARLY_WAKEUP。
de
Link Layer Advertising state 时 bltPm. suspend_mask 中 SUSPEND_ADV 生效,
或者 Link Layer Conn state slave role 时 bltPm. suspend_mask 中 SUSPEND_CONN 生
效,可以进入 suspend。
un
PM_WAKEUP_TIMER | bltPm.wakeup_src,
T_wakeup);
ar
引入 deepsleep retention,对上面的软件处理流程继续分析。
当应用层按如下设置时,deepsleep retention mode 被打开。
bls_pm_setSuspendMask( SUSPEND_ADV | DEEPSLEEP_RETENTION_ADV
| SUSPEND_CONN | DEEPSLEEP_RETENTION_CONN);
DA
第一个条件 bltPm. suspend_mask 中 DEEPSLEEP_RETENTION_ADV 需要生效,
前面已经介绍过。
第二个条件 T_sleep > bltPm.deepRet_advThresTick 或
rN
T_sleep > bltPm.deepRet_connThresTick 中,
我们先介绍两个时间阀值设置的 API,如下所示为源码,设置时间单位为
ed
mS。
void blc_pm_setDeepsleepRetentionThreshold( u32 adv_thres_ms,
ar
u32 conn_thres_ms)
{
Sh
bltPm.deepRet_advThresTick = adv_thres_ms *
CLOCK_16M_SYS_TIMER_CLK_1MS;
bltPm.deepRet_connThresTick = conn_thres_ms *
CLOCK_16M_SYS_TIMER_CLK_1MS;
}
DA
的功耗平均化,等效电流为 I_brx,持续时间为 t_brx(这里取名 t_brx 是为了和
前面已有概念 T_brx 做区分)。Suspend 的底电流为 I_suspend,deep retention
的底电流为 I_deepRet。
rN
“Run software bootloader + System initialization + User initialization”的过程平
均电流等效为 I_init,持续的总时间为 T_init。实际的应用中,T_init 的值需要 user
de
去把控和测量,后面会介绍如何实现。
un
ed
ar
Sh
I_brx
Brx
Brx
Event
Event
suspend
I_suspend
t_brx t
I T_cycle
I_brx
DA
Brx
Event Brx
Event
I_init
rN
de
deepsleep retention
un
I_deepRet
t
T_init
T_wakeup’ T_wakeup
ed
以下是图中几个名词说明。
T_cycle:两个相邻的 Brx event 之间的时间差值
I_brx:将 Brx Event 的功耗平均化,等效电流为 I_brx
t_brx:l_brx 持续时间
l_suspend:suspend 底电流
l_deepRet:deep retention 的底电流
l_init:Software bootloader + System initialization + User initialization 过程的
等效平均电流
T_init:l_init 持续的总时间
DA
部分为:
I_avgSuspend – I_avgDeepRet
=
=
rN
I_suspend* T_cycle – I_init*T_init – I_ deepRet * T_cycle
T_cycle( (I_suspend – I_ deepRet) – (T_init*I_init)/T_cycle)
de
对于功耗调试正确的应用程序(硬件电路和软件的功耗调试都正确),最终
un
I_avgSuspend – I_avgDeepRet
ar
DA
由于 deepsleep retention 的功耗比 suspend 低,并且“Run software bootloader
+ System initialization + User initialization” 3 个步骤的存在会多出一部分的功耗,
rN
根据以上分析,一定是 T_cycle 大于某个临界值之后,使用 deepsleep retention
才会更省功耗。上面例子中的数值只是简单的 demo,user 在实现功耗优化时需
要根据一定的方法先测出上面公式中对应的数值,最终才可以确定临界值。
de
只要程序设计上参考 SDK 中的 demo,且在 user initialization 这部分没有错
误的增加很大的时间消耗,上面的 T_cycle 临界值都不会太大。一般超过 100ms
un
4.2.8.2 blc_pm_setDeepsleepRetentionEarlyWakeupTiming
ar
DA
CLOCK_16M_SYS_TIMER_CLK_1US;
}
rN
User 将测量到的 T_init 的值直接设置到上面 API 即可,或者设置的值比 T_init
略大一点,但不能小于这个值。
de
1) T_init timing
ar
那么,
T_init = T_cstatup + T_sysInit + T_userInit。
T_init
main_loop
DA
T_wakeup’ T_brx or
T_advertising
int main(void)
rN
图 4-5 T_init timing
de
结合前面已有的图和概念继续分析。
un
2) T_userInit
由前面的介绍可知,user initialization 在 power on、deepsleep wake_up、
deepsleep retention wake_up 的时候都需要被执行。
对于没有使用 deepsleep retention mode 的应用来说,user initialization 不需
要区分 deepsleep retention wake_up 和 power on/deepsleep wake_up。Telink 上一
代 826x 系列 BLE SDK 中,所有的 user initialization 都是用下面函数即可完成。该
BLE SDK 中"8258_master_kma_dongle” project 也一样。
void user_init(void);
DA
user_init 函数中所有的 initialization 可以被分为两类:一是硬件寄存器的初
始化,二是 Sram 上逻辑变量的初始化。 rN
根据 deepsleep retention mode 可以保持 Sram 前 16K(32K)不掉电的特性,
我们可以将逻辑变量定义成 retention_data,这样的话 deepsleep retention
de
wake_up 时就可以省掉逻辑变量初始化的时间。由于寄存器的状态无法被保持,
deepsleep retention wake_up 时寄存器的初始化必须重新执行。
un
if( deepRetWakeUp ){
user_init_deepRetn ();
}
else{
user_init_normal ();
}
DA
是 BLE 初始化中必不可少的相关硬件寄存器的初始化;
blc_ll_recoverDeepRetention 是对 Link Layer 相关软硬件状态的恢复,由 stack
底层处理。
rN
以上几个初始化都属于固定写法,user 不要去修改。
de
最后 app_ui_init_deepRetn 是 user 对应用层使用到的硬件寄存器的重新初始
化。SDK demo “8258_ble_remote”中 GPIO 的唤醒设置、Led 灯状态的设置都属于
un
4) T_cstartup
T_cstartup 是执行 cstartup_xxx.S(比如 cstartup_8258_RET_16K.S)所消耗的
时间,请 user 参考 SDK 中 cstartup_8258_RET_16K.S 文件。
T_cstartup 按照时间顺序可以被拆成 4 个时间组成:
T_cstartup = T_cs_1 + T_cs_bss + T_cs_data + T_cs_2
DA
T_cs_data 是 Sram 中“data”段的初始化时间。“data”段是已初始化的全局变
量,它们的初始值存储在 flash 的“data initial value”区域上。“data”段初始化的时
间就是 MCU 从 flash “data initial value”区域上的初值拷贝到 Sram “data”段的过程。
对应的汇编 code 如下: rN
tloadr r1, DATA_I
de
tloadr r2, DATA_I+4
tloadr r3, DATA_I+8
COPY_DATA:
un
tcmp r2, r3
tjge COPY_DATA_END
ed
tadd r2, #4
tj COPY_DATA
Sh
COPY_DATA_END:
ZERO:
tcmp r1, r2
tjge ZERO_END
tstorer r0, [r1, #0]
tadd r1, #4
tj ZERO
DA
ZERO_END:
rN
T_cs_bss 是“bss”段数据清 0 操作的时间,每个 word (4 byte)清 0 的速度非常
快,当“bss”较小时,T_cs_bss 很小。但如果“bss”段很大(比如程序中定义了一
个很大的全局数组 int AAA[2000] = {0}),T_cs_bss 的时间也会变大很多,所以
de
user 还是需要注意,可以在 list 文件中查看“bss”段的大小。
如果“bss”段偏大,需要优化 T_cs_bss。优化方法和“data”段一样,在 deepsleep
un
5) T_init 测量
ar
#if 0
@ add debug, PB4 output 1
tloadr r1, DEBUG_GPIO @0x80058a PB oen
tmov r0, #139 @0b 11101111
tstorerb r0, [r1, #0]
END: tj END
DA
_attribute_ram_code_ int main (void) //must run in ramcode
{
DBG_CHN0_LOW; //debug
cpu_wakeup_init();
……
rN
}
de
PB4 的一高一低可以测量出 T_cstartup 的持续时间。
un
如 T_sysInit、T_userInit 等。
Sh
前面关于 Conn state slave role 的 sleep mode 的介绍(参考图“sleep timing for
Advertising state & Conn state Slave role”所示),都是基于 connection latency(简
称 conn_latency)没有生效时的前提。
PM 软件处理流程上,T_wakeup = T_brx + conn_interval,对应的 code 如下。
if(conn_latency != 0)
{
latency_use = bls_calculateLatency();
T_wakeup = T_brx + (latency_use +1) * conn_interval;
}
else
{
T_wakeup = T_brx + conn_interval;
}
DA
T_brx T_wakeup
Slave
brx
rN
sleep
brx
event event
Conn interval Conn interval Conn interval
de
un
latency_use 的计算过程如下。
首先计算 system latency:
1) 若当前连接参数中 connection latency 为 0,system latency 为 0。
2) 若当前连接参数中 connection latency 非 0:
A. 若当前系统还有一些任务没有处理完,必须在下一个 connection interval
DA
醒来收发包继续处理(比如还有数据没有发送完、收到 master 的数据还
没处理完等等),system latency 为 0。
rN
B. 若当前系统已经没有任务需要处理了,则 system latency 等于 connection
latency。但是有一个例外,如果收到了 master 的 update map request 或
update connection parameter request 且实际的更新时间点在(connection
de
latency+1)个 interval 之前,则实际的 system latency 会强制 MCU 在实际
更新时间点之前那个 interval 醒来,确保 BLE 时序的正确。
un
然后
ed
DA
}
以上这个回调函数要实现的作用是防止按键丢失。
一个正常的人为机械按键动作大概会持续几百毫秒,按的快的时候也会有一
rN
两百毫秒。当 user 通过 bls_pm_setSuspendMask 设置了 Advertising state 和 Conn
de
state 都要进入 sleep mode,在 conn_latency 没有生效的前提下,只要 Adv interval
和 conn_interval 的值不是特别大(一般设置在 100ms 以内),sleep 的时间不会
un
如果出现以上情况,可能会造成意想不到的问题,比如本来想进入 deepsleep
后被唤醒,程序重新执行,结果 MCU 无法进入 deepsleep,导致 code 继续运行,
不是我们预想的状态,整个程序的 flow 可能会乱掉。
DA
PM 模块上没有检测到 GPIO PAD 生效的状态,返回
STATUS_GPIO_ERR_NO_ENTER_PM
2) deepsleep mode
rN
如果是 deepsleep mode,PM driver 会在底层自动将 MCU reset(此时的 reset
de
跟 watchdog reset 效果一致),程序回到“Run hardware bootloader”开始重新
运行。
un
{
if( blc_ll_getCurrentState() == BLS_LINK_STATE_CONN &&
((u32)(bls_pm_getSystemWakeupTick() - clock_time())) >
80 * CLOCK_SYS_CLOCK_1MS){
bls_pm_setWakeupSource(PM_WAKEUP_PAD);
}
DA
一是连续 60S 没有任何事件会进入 deepsleep,这里的事件包括按键被按下,
所以此时不会有 drive pin 高电平导致 deepsleep 无法进入;
rN
二是卡键 60S 后进入 deepsleep,这时候虽然有 drive pin 上的高电平,SDK
会将卡键的 drive pin 唤醒电平极性取反,设为低电平唤醒,同样避免了这个
问题(参考按键扫描章节的卡键处理)。
de
un
blt_pm_proc 函数低功耗管理配置几个要点总结如下:
1) 某些任务需要关闭 sleep mode 时,如语音(ui_mic_enable)、红外等任务运行
时,设置 bltm.suspend_mask 为 SUSPEND_DISABLE。
DA
Conn state slave role 时要进入 deepsleep,先调用 bls_ll_terminateConnection
向 master 发送一个 TERMINATE 命令,等到这个命令被 ack 后(此时会触发
BLT_EV_FLAG_TERMINATE 事件回调函数)再进入 deepsleep。这样做是为了
rN
确保 master 收到 salve 主动断连的请求后立刻断开。如果 slave 没有发送断
连请求就进入 deepsleep,master 仍然处于连接状态并一直尝试去和 slave 同
步,直到 connection timeout 触发。这个 connection timeout 时间可能很大(比
de
如 20S),如果在 20S connection timeout 之前 slave 被唤醒并发广播尝试和
master 建立连接,由于 master 还处于上一次的连接状态中,会导致无法立
un
刻和 slave 建立连接。应用上的体验就是回连速度很慢。
ed
4.5 应用层定时唤醒
DA
应用层定时唤醒发生时,执行 bls_pm_registerAppWakeupLowPowerCb 注册
的回调函数,其原型和 API 如下:
typedef
void
rN
void (*pm_appWakeupLowPower_callback_t)(int);
bls_pm_registerAppWakeupLowPowerCb(
de
pm_appWakeupLowPower_callback_t cb);
un
T_brx T_wakeup
app_wakeup_tick
UI task sleep
brx
event
Conn interval
5 低电检测
5.1 低电检测的重要性
使用电池供电的产品,由于电池电量会逐渐下降,当电压低到一定的值后会
DA
引起很多问题:
1) 8x5x 工作电压的范围为 1.8V~3.6V。当电压低于 1.8V 时,8x5x 已经无法保证
稳定的工作。 rN
2) 当电池电压较低时,由于电源的不稳定,Flash 的“write”和“erase”操作可能有
出错的风险,造成 program firmware 和用户数据被异常修改,最终导致产品
de
失效。根据以往的量产经验,我们将这个可能出风险的低压阀值设定为 2.0V。
un
根据上面的描述,使用电池供电的产品,必须设定一个安全电压值(secure
voltage),只有当电压高于这个安全电压的时候才允许 MCU 继续工作;一旦电
ed
为低压报警。产品使用者看到低压报警的行为后,了解到当前电池已经处于低电
状态,可以对电池进行充电或更换。
安全电压也称为报警电压,这个电压值的选取,目前 SDK 默认使用 2.0V。
如果 user 在硬件电路中出现了不合理的设计,导致电源网络稳定性的恶化,安
全电压值还需要继续提高,比如 2.1V、2.2V 等。
5.2 低电检测的实现
5.2.1 低电检测的注意事项
DA
低电检测是一个基本的 ADC 采样任务,在实现 ADC 采样电源电压时,有一
些需要注意的问题,说明如下。
rN
5.2.1.1 必须使用 GPIO 输入通道
de
VBAT,
}ADC_InputPchTypeDef;
DA
电平来测量。8x5x 内部电路结构设计可以保证 GPIO 输出高电平的电压值和
电源电压值永远相等。
rN
那么 GPIO 输出的高电平可以作为电源电压,通过该 GPIO input channel 进行
ADC 采样。
de
#define PB7_INPUT_ENABLE 0
#define ADC_INPUT_PCHN B7P
需要进行ADC采样时,PB7输出高电平:
gpio_set_output_en(GPIO_VBAT_DETECT, 1);
gpio_write(GPIO_VBAT_DETECT, 1);
5.2.1.2 只能使用差分模式
虽然 8x5x ADC input mode 同时支持单端模式(Single Ended Mode)和差分模
式(Differential Mode),但由于某些特定的原因,Telink 规定:只能使用差分模
式,单端模式不允许使用。
DA
同的功能设置,“#if 1”只是为了让 code 运行更快以节省时间。可以通过看“#else”
来理解,adc_set_ain_channel_differential_mode API 中选择了 PB7 作为 positive
adc_set_ain_chn_misc(ADC_INPUT_PCHN, GND);
#else
////set misc channel use differential_mode,
ed
adc_set_ain_channel_differential_mode(ADC_MISC_CHN,
ADC_INPUT_PCHN, GND);
ar
#endif
5.2.2 低电检测单独使用
DA
义为 0(关闭 Audio 所有功能),就可以获得 ADC 只被低压检测使用的 demo。
或者直接参考“8258_module”的低压检测 demo。
rN
5.2.2.1 低电检测初始化
de
参考 adc_vbat_detect_init 函数的实现。
un
void adc_vbat_detect_init(void)
{
ar
DA
if(!en){
adc_hw_initialized = 0; //need initialized again
}
} rN
de
使用了 adc_hw_initialized 的设计可以实现的功能有:
1) 与其他 ADC 任务(“ADC other task”)的切换
un
在 main_loop 里实现。
第一次执行 app_battery_power_check 函数时,adc_vbat_detect_init 被执行,
Sh
且后面不会被反复执行。
一旦“ADC other task”需要执行时,将抢走 ADC 的使用权,确保“ADC other task”
初始化时必须调用 battery_set_detect_enable(0),此时会将 adc_hw_initialized
清 0。
等“ADC other task”完成后,交出 ADC 的使用权。app_battery_power_check 再
次执行,由于 adc_hw_initialized 值为 0,必须再次执行 adc_vbat_detect_init,
这样就保证了低电检测每次切回来时都会重新初始化。
DA
如果 MCU 进入 deepsleep retention mode,醒来后 adc_hw_initialized 为 0,必
须重新执行 adc_vbat_detect_init,ADC 相关的 register 状态需要被重新配置。
rN
adc_vbat_detect_init 函数中设定 register 的状态可以在 suspend 期间保持不
掉电。
de
参考文档“低功耗管理”部分对 suspend mode 的说明可知,Dfifo 相关的寄
un
adc_config_misc_channel_buf((u16 *)adc_dat_buf,ADC_SAMPLE_NUM<<2);
dfifo_enable_dfifo2();
ar
Sh
5.2.2.2 低电检测处理
DA
{
return lowBattDet_enable;
}
rN
de
if(battery_get_detect_enable() &&
clock_time_exceed(lowBattDet_tick, 500000) ){
un
lowBattDet_tick = clock_time();
app_battery_power_check(VBAT_ALRAM_THRES_MV);
}
ed
lowBattDet_enable默认值为1,低电检测是默认允许的,MCU上电后立刻可
ar
以开始低电检测。该变量需要设置成retention_data,确保deepsleep retention不
能修改它的状态。
Sh
只有在其他ADC任务需要抢占ADC使用权时,才能改变lowBattDet_enable的
值:当其他ADC任务开始时,调用battery_set_detect_enable(0),此时main_loop
中不会再调用app_battery_power_check函数;在其他ADC任务结束后,调用
battery_set_detect_enable(1),交出ADC使用权,此时main_loop中又可以调用
app_battery_power_check函数。
通过变量lowBattDet_tick来控制低电检测的频率,Demo中为每500mS执行
一次低电检测。User可以根据自己的需求来修改这个时间值。
app_battery_power_check函数的具体实现看起来比较繁琐,涉及到低电检测
的初始化、Dfifo的准备、数据的获取、数据的处理、低电报警的处理等等。
由于ADC的使用比较复杂,加上硬件电路上有一些特殊的限制,user很难理
解所有的细节。这部分的处理流程上每个细节(本文档不会把每个细节都介绍清
楚)的处理都是有讲究的,所以user不要尝试去修改,尽量使用原始的demo code。
只有少量一些可以修改的地方,本文档会很明确的指出来;凡是文档里没有明确
指出可以修改的地方,user都不能有任何改动。
ADC采样数据的获取使用了Dfifo mode,Dfifo默认采样8笔数据,去掉最大最
小值后计算平均值。adc_vbat_detect_init函数里可以看到每个adc采样的周期为
10.4uS,所以获取数据过程大概83us。
可以看到Demo中宏“ ADC_SAMPLE_NUM”可以被修改为4,缩短ADC采样时间到
41uS。推荐使用8笔数据的方法,计算结果会更加准确。
DA
#define ADC_SAMPLE_NUM 8
#endif
5.2.2.3 低压报警
DA
analog_write(DEEP_ANA_REG2, LOW_BATT_FLG); //mark
cpu_sleep_wakeup(DEEPSLEEP_MODE, PM_WAKEUP_PAD, 0);
}
rN
de
“8258_ble_remote”被 shutdown 后,进入可被唤醒的 deepsleep mode。此时
如果发生按键唤醒,SDK 会在 user initialization 的时候先快速做一次低电检测,
un
DA
#define DBG_ADC_SAMPLE_DAT 0
按照低电检测单独使用的方式,程序开始运行后,默认低电检测先开启。当
Amic Audio 被触发时,做以下两件事:
1) 关闭低电检测
调用 battery_set_detect_enable(0),告知低电检测模块 ADC 资源已被抢占。
DA
rN
de
un
ed
ar
Sh
6 Audio
DA
参考本文档对“低电检测”的介绍可知,Amic Audio 和低电检测使用 ADC 模块
时,必须对 ADC 进行切换使用。
rN
同理,如果应用上同时出现 Amic Audio 和其他 ADC 任务,这 2 个任务也需
要对 ADC 进行切换使用。如果同时出现 Amic Audio、低电检测、其他 ADC 任务,
这 3 个任务也需要对 ADC 进行切换使用。
de
Telink 上一代 826x 系列 IC 上 Amic 可以在 user initialization 时设置,而 8x5x
Amic 需要在 Audio 任务开启时设置,这样才能实现低电检测和 Amic 对 ADC 模块
un
的切换使用。
ed
if(en){ //audio on
audio_config_mic_buf ( buffer_mic, TL_MIC_BUFFER_SIZE);
audio_amic_init(AUDIO_16K);
}
else{ //audio off
adc_power_on_sar_adc(0); //power off sar adc
}
#if (BATT_CHECK_ENABLE)
battery_set_detect_enable(!en);
#endif
}
上面ui_enable_mic函数中,en=1对应Audio任务的开启,en=0对应Audio任务
的结束。
Audio开始时,GPIO_AMIC_BIAS需要输出高电平来驱动Amic;Audio结束后,
GPIO_AMIC_BIAS需要关闭,防止这个管脚在sleep mode漏电。
Amic初始化设置为
audio_config_mic_buf ( buffer_mic, TL_MIC_BUFFER_SIZE);
DA
audio_amic_init(AUDIO_16K);
Audio在工作过程中,使用指定的Dfifo将数据源源不断拷贝到Sram上。
audio_config_mic_buf用于配置该Dfifo在Sram上的起始地址和长度。
rN
de
Dfifo 的配置在 ui_enable_mic 函数中处理,相当于每次 Audio 开始都要重新
做一遍,原因是 Dfifo 控制 register 在 suspend 时会掉电丢失。
un
Audio任务结束的时候,必须关闭SAR ADC,防止在suspend时漏电:
ed
adc_power_on_sar_adc(0);
ar
加 battery_set_detect_enable(!en),用于关闭和开启低电检测,请参考本文档低
电检测部分的介绍。
#endif
待补充。
DA
128 bytes 的数据,在 L2cap 层上发送给 master,会分成 5 个 packet 上进行,
因为每个包最大长度是 27,第一个包必须带 7 个 bytes 的 l2cap 的说明信息:
rN
l2caplen: 2 bytes,chanid:2 bytes,opcode:1 byte,AttHandle:2 bytes
de
下图所示为空中抓到的 RF 数据,可以看到第一个包中有 7 个额外的信息,
后面紧跟 20 bytes 的 audio 数据,后面的包 27 bytes 全是 audio 数据。第一个包
un
结合前面 BLE 模块 ATT & GATT 部分对 Exchange MTU size 部分的说明可知,
这里 audio 数据属于 128 byte 的长包在 slave 端进行了分包处理,如果希望 peer
device(对端设备)收到这些包后能够重新拼装成功,就一定要通过 Exchange MTU
size 确定对方 peer device 的最大 ClientRxMTU,只有当 ClientRxMTU 大于等于 128
时,slave 端的这个 128byte 长包才能被 peer device 正确处理。
所以当 audio 任务开启,需要发送 128 byte 长包时,会调用
blc_att_requestMtuSizeExchange 进行 Exchange MTU size。
void voice_press_proc(void)
{
key_voice_press = 0;
ui_enable_mic (1);
DA
if(ui_mtu_size_exchange_req &&
blc_ll_getCurrentState() == BLS_LINK_STATE_CONN){
ui_mtu_size_exchange_req = 0;
rN
blc_att_requestMtuSizeExchange(BLS_CONN_HANDLE, 0x009e);
de
}
}
un
DA
硬件控制数据填充到 buffer_mic 的机制说明如下:
Amic 采样的数据按照 16K 的速度匀速放入从 buffer_mic 地址开始的内存,向
后移动,并且最大长度为 992,一旦到最大长度,重新回到 buffer_mic 地址开始
放数据。这个过程不对内存上的数据进行任何是否已经被读走的判断,直接覆盖
rN
老的数据。向 RAM 放数据的过程中,维护一个写指针用于记录当前最新的 audio
de
数据已经到 RAM 的哪个地址了。
un
buffer_mic buffer_mic_enc
Sh
读指针 读指针
写指针 128 byte
248 读指针新
sample 压缩处理 的位置
128 byte
图 6-3 数据压缩处理
上图所示为数据压缩处理的方法。
buffer_mic 自动维护一个硬件写指针,同时在软件上维护一个读指针。
当软件上检测到写指针与读指针中间的差值已经满足 248 个 sample 时,就
开始调用压缩处理函数,从读指针开始取出 248 个 sample 的数据压缩为 128 个
bytes,同时将读指针移到图上新的位置,表示最新的未读的数据从新的位置开
始。如此循环往复,不断检测是否是有足够的 248 个 sample 的数据,只要达到
这个数据量,就开始做压缩处理。
由于 248 个 sample 的产生时间为 15.5ms,需要保证程序至少 15.5 ms 才查
询一次。由前面的介绍可知,程序在每个 main_loop 只执行一次 task_audio,那
么 main_loop 的时间必须小于 15.5 ms 才能保证音频数据不丢。在连接状态,
main_loop 的时间等于 connection interval,所以有音频任务的应用,connection
interval 一定要小于 15.5 ms。实际应用中推荐 10 mS。
buffer_mic_enc 在软件上维护写指针和读指针,当 248 sample 数据压缩为 128
bytes 后,将这个 128 个 bytes 拷贝到写指针开始的地方,拷贝完之后检查一下
DA
这个 buffer 是否溢出。若溢出,将最老的一笔数据放弃(将读指针向后移动 128
bytes 即可)。
将压缩后的数据拷贝到 BLE RF 数据发送缓冲区的方法为: rN
检查 buffer_mic_enc 是否为非空(写指针和读指针相等时为空,不等为非
de
空)。若非空,从读指针开始的地址拿出 128 bytes 拷贝到 BLE RF 数据发送缓冲
区,然后将读指针移到图上所示新的位置。
un
6.3 压缩与解压缩算法
ar
压缩算法调用的函数为:
Sh
void mic_to_adpcm_split (signed short *ps, int len, signed short *pds, int start);
ps 指向压缩前数据内存的首地址,对应图 6-3 中 buffer_mic 的读指针的位置。
len 取 TL_MIC_ADPCM_UNIT_SIZE(248),表示 248 个 sample。
pds 指向压缩后数据内存的首地址,对应图 6-3 中 buffer_mic_enc 写指针的
位置。
0
1 predict
2 predict_idx
3 124 audio data len
4
1/4压缩
248 sample
124 bytes
496 bytes
127
图 6-4 压缩算法对应数据
DA
如上图所示:压缩后的数据内存的前两个 bytes 存 predict;第三个 byte 存
rN
predict_idx;第 4 个 byte 为当前 adpcm 格式的 audio 数据的有效数据量,也就是
124;后面的 124 个 bytes 由 496 bytes 的原声数据 1/4 压缩而来,压缩的具体算
法不介绍,只要能根据这个方法对应解压缩即可。
de
解压缩算法对应函数为:
un
void adpcm_to_pcm (signed short *ps, signed short *pd, int len);
ps 指向需要解压缩的数据内存开始的地址,也就是指向 128 bytes 的 adpcm
格式数据,这个地址需要 user 定义 buffer,从 BLE RF 收到的 128 bytes 数据拷贝
ed
到该 buffer;
pd 指向解压缩后还原的 496 bytes pcm 格式音频数据内存开始的地址,这个
ar
7 OTA
DA
使用启动地址 0x20000 时,SDK 编译出来的 firmware size 应不大于 128K,即
flash 的 0~0x20000 之间的区域存储 firmware,但是由于一些特殊的原因,如果使
用启动地址为 0 和 0x20000 交替 OTA 升级,其 firmware size 不得超过 124K;如
果超过 124K 必须使用启动地位 0 和 0x40000 交替升级,此时最大 firmware size
rN
不得超过 252K。
de
0x80000 0x80000
un
ed
New_firmware
or
Firmware_1.bin
Ota_master.bin New_firmware Ota_master.bin
storage area
DA
7.1.2 OTA 更新流程
rN
以上面的 FLASH 存储结构为基础,详细说明 OTA 程序更新的过程。
de
首先介绍一下多地址启动机制(只介绍前两个启动地址 0x00000 和
0x20000):MCU 上电后,默认从 0 地址启动,首先去读 flash 0x8 的内容,若该
un
5) OTA master 获取了 slave OTA service 数据 Attribute 的 Attribute Handle 值后,
获取当前 slave FLASH 程序的 firmware 版本号。
注:获取版本号并对比这一步 user 自己决定是否需要做和该怎么做。
DA
6) master 确定要做 OTA 更新后,先发一个 OTA_start 命令通知 slave 进入 OTA
模式。
rN
7) Slave 收到 OTA start 命令后,进入 OTA 模式,等待 master 发 OTA 数据。
de
DA
两个值和前文的描述一致。
//firmware_size_k must be 4k aligned
rN
void bls_ota_set_fwSize_and_fwBootAddr(int firmware_size_k,
int boot_addr);
de
firmware_size_k 的设置一定要 4K byte 对齐,比如 size 为 97K 时需要设为
100K。
un
置。
ar
{4,ATT_PERMISSIONS_READ, 2,16,(u8*)(&my_primaryServiceUUID),
(u8*)(&my_OtaServiceUUID), 0},
DA
{0,ATT_PERMISSIONS_READ, 2, 1,(u8*)(&my_characterUUID),
(u8*)(&PROP_READ_WRITE_NORSP), 0}, //prop
{0,ATT_PERMISSIONS_RDWR,16,sizeof(my_OtaData),(u8*)(&my_OtaUUID),
rN
(&my_OtaData), &otaWrite, &otaRead}, //value
{0,ATT_PERMISSIONS_READ, 2,sizeof (my_OtaName),(u8*)(&userdesc_UUID),
de
(u8*)(my_OtaName), 0},
0 1 2 OTA_data 17 18 19
adr_index firmware data:adr_index*16 - adr_index*16+15 CRC
DA
此命令作为一个预留的命令,user 可以选择使用。如果使用了这个命令,slave
端留出了相应的回调函数供 user 来完成 firmware 版本号的传送。
2) 0xff01 为 OTA_Start 命令。 rN
master 发这个命令给 slave,用来正式启动 OTA 更新。
de
3) 0xff02 为 OTA_end 命令。
当 master 确定所有的 OTA 数据都被 slave 正确接收后,发送 OTA end 命令。
un
4) 0xff03 ~0xff0f,待补充。
ar
DA
若 user 采用事先约定好的方式,直接定义该值;
若没有事先约定好,采用 Read By Type Request 的方式获得这个 Attribute
Handle 值。 rN
Telink 所有 BLE SDK 的 OTA data 的 UUID 都是 16bytes,且永远都是下面这个
值:
de
#define TELINK_SPP_DATA_OTA
un
{0x12,0x2B,0x0d,0x0c,0x0b,0x0a,0x09,0x08,0x07,0x06,0x05,0x04,
0x03,0x02,0x01,0x00} //!< TELINK_SPP data for ota
slave 端回复的 Read By Type Rsp 中可以查到 OTA UUID 所在的这个 Attribute
Handle,如下图所示,master 可以查到 Attribute Handle 的值为 0x0031。
ar
Sh
DA
如下图所示的 8258_remote.bin,0x18 ~0x1b 内容为 0x00005a98,所以大小
为 0x5a98 = 23192 bytes,从 0x0000 到 0x5a97。
rN
de
un
DA
0x00 0x00 0x0e 0x80 ....省略 12 个 bytes..... 0x88 0x00 0xZW 0xXY
第二笔数据:
0x01 0x00 0x5e 0x80
rN
....省略 12 个 bytes..... 0x00 0x00 0xJK 0xHI
第三笔数据:
de
0x02 0x00 0x25 0x08 ....省略 12 个 bytes..... 0xfa 0x87 0xNO 0xLM
........
un
倒数第二笔数据:
0xa8 0x05 0x02 0x04 ....省略 12 个 bytes..... 0x00 0x00 0xST 0xPQ
ed
最后一笔数据:
ar
0xa9 0x05 0x44 0x58 0x00 0x00 0x01 0x00 0x00 0x00
0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xff 0xWX 0xUV
Sh
8 个 0xff 为补齐的数据。
0xa9 ~0xff 共 18 个 bytes 的 CRC 计算结果为 0xUVWX。
上面的数据如下图所示:
DA
OTA end 的 packet 有效字节设为 6 个,前两个为 0xff02,中间的两个 bytes
为新的 firmware 最大的 adr_index 值(这个是为了让 slave 端再次确认没有丢
掉最后一条或几条 OTA 数据),最后两个 bytes 为中间最大的 adr_index 值
的取反,相当于一个简单的校验。OTA end 不需要 CRC 校验。 rN
以上图所示的 8258_remote.bin 为例,最大的 adr_index 为 0x05a9,其取反值
为 0xfa56,最终的 OTA end 包如上图所示。
de
CRC_16 计算函数见本文档后面的附录。
ar
DA
若用户使用 bls_ota_registerStartCmdCb 函数注册了 OTA start 时的回调函数,
则执行此函数,这个函数的目的是让用户在进入 OTA 模式后,修改一些参数
状态等,比如将 PM 关掉(使得 OTA 数据传输更加稳定)。
rN
另外 slave 启动并维护一个 slave_adr_index,初值为-1,记录最近一次正确
OTA data 的 adr_index,用于判断整个 OTA 过程中是否有丢包。一旦丢包,认
de
为 OTA 失败,退出 OTA,MCU 重启,master 端由于收不到 slave 的 ack 包,
也会由于 OTA 任务超时使得软件发现 OTA 失败。
un
DA
可以用来做 double check。double check 的时候,
判断 slave 之前收到的 master
的数据 index 最大值与该包中的 adr_max 是否相等。若相等,认为 OTA 成功,
若不等,认为丢掉了最后一笔或几笔数据,OTA 不完整。 rN
当 OTA 成功的时候,slave 将老的 firmware 所在地址的 flash 启动标志设为 0,
将新的 firmware 所在地址的 flash 启动标志设为 0x4b,将 MCU reboot。
de
un
发该函数。
以下是相关 code:
typedef void (*ota_resIndicateCb_t)(int result);
enum{
OTA_SUCCESS = 0, //success
OTA_PACKET_LOSS, //lost one or more OTA PDU
OTA_DATA_CRC_ERR, //data CRC err
OTA_WRITE_FLASH_ERR, //write OTA data to flash ERR
OTA_DATA_UNCOMPLETE, //lost last one or more OTA PDU
OTA_TIMEOUT, //
};
void bls_ota_registerResultIndicateCb
(ota_resIndicateCb_t cb);
DA
rN
de
un
ed
ar
Sh
8 按键扫描
8.1 键盘矩阵
DA
VCC VCC VCC VCC VCC VCC
10 10 10 10 10 10
K K K K K
Row0
K
rN 100 K
de
Drive Row1 100 K
Row2
Pin 100 K
un
Row3 100 K
ed
Row4 100 K
CoL0
ar
CoL1
Scan
Sh
CoL2
Pin CoL3
CoL4
CoL5
图 8-1 行列式键盘结构
DA
定义行列式扫描中,drive pin 输出低电平时 scan pin 扫描到的有效电平。
#define KB_LINE_HIGH_VALID 0
当 MCU 进入 sleep mode 时,需要设置 PAD GPIO 唤醒。设置 drive pin 高电平
唤醒,按下按键时,drive pin 读到 100K 和 10K 的分压电平,为 10/11 VCC 的高
电平。需要打开 drive pin 的 ie 读取其电平状态:
#define PD5_INPUT_ENABLE 1
#define PD2_INPUT_ENABLE 1
#define PD4_INPUT_ENABLE 1
#define PD6_INPUT_ENABLE 1
#define PD7_INPUT_ENABLE 1
8.2 Keyscan、keymap
8.2.1 Keyscan
DA
按照上面的配置完成后,在 main_loop 中调用下面函数完成 keyscan。
u32 kb_scan_key (int numlock_status, int read_key)
rN
第一个参数 numlock_status 在 main_loop 中调用时设为 0 即可;只有在
deepsleep 醒来的快速扫描按键时才会将其设为
KB_NUMLOCK_STATUS_POWERON,后面的快速扫键中介绍(对应
de
DEEPBACK_FAST_KEYSCAN_ENABLE)。
第二个参数 read_key 是 keyscan 函数按键的缓存处理,这个一般用不到,一
un
返回 1;无变化时,返回 0。
ar
上面所说的最新按键状态指的是矩阵上所有按键的按下或松开的状态的集
合。上电时,默认的第一次矩阵按键状态为所有按键都是松开的。只要经过防抖
动滤波处理后的矩阵按键的状态发生任何变化,返回值都会为 1,否则返回 0。
如:按下一个按键返回一个变化;松开一个按键返回一个变化;按下一个键时再
按下第二个键返回一个变化;按下两个键时再按下第三个键返回一个变化;按下
两个键时松开其中一个键返回一个变化……
DA
typedef struct{
u8 cnt;
u8 ctrl_key;
u8 keycode[KB_RETURN_KEY_MAX]; rN
}kb_data_t;
de
kb_data_t kb_event;
un
个值)。
keycode[6]用于最多存储当前 6 个被按下按键的 keycode(如果实际按下的键
Sh
超过 6 个,只有前 6 个能反应出来)。
DA
忽略不看。
rN
3) kb_event.cnt = 2,可能上次 kb_event.cnt = 0,变化是两个键同时按下;可能
上次 kb_event.cnt = 1,一个键被按下时另一个键被按下;可能上次
de
kb_event.cnt = 3,三个键被按下时,其中一个被释放;其他可能性等等。
此时 kb_event.keycode[0]和 kb_event.keycode[1]表示当前被按下的两个键的
un
这个示例只是简单的处理单个按键按下的情况,所以当
kb_event.keycode[0]非 0 时,就认为是一个按键被按下,并不去判断是否两个
Sh
键同时按下或者两个键中的一个释放等复杂的情况。
kb_event.keycode[0] = 0;//clear keycode[0]
int det_key = kb_scan_key (0, 1);
if (det_key)
{
key_not_released = 1;
u8 key0 = kb_event.keycode[0];
if (kb_event.cnt == 2) //two key press, do not process
{
}
else if(kb_event.cnt == 1)
{
key_buf[2] = key0;
AN-19011501-C5 264 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
8.3 Keyscan flow
能读到有效的电平,并记录哪一列上读到了有效电平(用 scan_pin_need 标
记有效的列号)。
若不使用第一次全矩阵通扫,直接逐行扫的话,至少要进行所有行的扫描,
ed
即使没有按键按下也要每次都逐行扫描,比较耗时间。加入了第一次全矩阵
通扫后,若没发现任何列上有按键按下,就可以直接退出 keyscan,在没有
ar
按键按下时会节省很多时间。
Sh
2) 根据全矩阵通扫的结果,逐行扫描。
全矩阵通扫发现有按键按下时,开始逐行扫描,从 ROW0 ~ ROW4 逐行输出
有效 drive 电平,读取列上的电平值,找出按键按下的位置。
对应的代码为:
u32 pressed_matrix[ARRAY_SIZE(drive_pins)] = {0};
DA
}
在做逐行扫描时使用了一些方法来优化代码执行时间: rN
一是当某行 drive 时,并不需要读取全部的列 CoL0 ~ CoL5,根据之前通扫的
scan_pin_need 可以知道哪些列上能够读到有效电平,此时只读取已经被标
de
记的列即可。
un
3) 对 pressed_matrix[]进行防抖动滤波处理
对应代码为:
unsigned int key_debounce_filter( u32 mtrx_cur[], u32 filt_en );
当 deepsleep 醒来后快速按键检测时,numlock_status =
KB_NUMLOCK_STATUS_POWERON,此时 filt_en = 0,不进行滤波,是为了最
快速的获取键值。
其他情况下,filt_en = 1,需要滤波处理。滤波处理的思路是:最近的连续两
次 pressed_matrix[]一致,且和上一次有效的 pressed_matrix[]不一样,才认为
是按键矩阵发生了有效的变化,key_changed = 1。
AN-19011501-C5 266 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
4) 对 pressed_matrix[]进行缓存处理
将 pressed_matrix[]存入到缓冲区,当 kb_scan_key (int numlock_status, int
read_key)中的 read_key 为 1 时,立刻读出缓冲区的数据,当 read_key 为 0
时,缓冲区数据保存起来,不通知上层,只有等到 read_key 为 1 时,才能读
出之前缓存的数据。
由于我们的 read_key 永远为 1,这部分可以忽略不计,相当于缓冲区没有起
到作用。具体代码不介绍。
5) 根据 pressed_matrix[],查表 KB_MAP_NORMAL,返回键值。
对应的函数为 kb_remap_key_code 和 kb_remap_key_row,不具体介绍,user
自行理解。
DA
rN
8.4 Deepsleep 唤醒快速扫键(wake_up fast keyscan)
de
当 Slave 设备在连接状态时进入 deepsleep 后,被按键唤醒。唤醒后,程序
从头开始跑,要经过 user_init 后在 main_loop 中先发广播包,等到连上后才能将
un
可能会导致这个按键的丢失。
缓存数据是因为如果在广播态扫描到了有效的按键数据,push 到 BLE TX fifo
后,进入连接态数据会被重新清掉。
相关的 code 在 app_config.h 中的宏 DEEPBACK_FAST_KEYSCAN_ENABLE 控制。
#define DEEPBACK_FAST_KEYSCAN_ENABLE 1
void deep_wakeup_proc(void)
{
#if(DEEPBACK_FAST_KEYSCAN_ENABLE)
if(analog_read(DEEP_ANA_REG0) == CONN_DEEP_FLG){
if(kb_scan_key (KB_NUMLOCK_STATUS_POWERON,1) && kb_event.cnt){
deepback_key_state = DEEPBACK_KEY_CACHE;
key_not_released = 1;
memcpy(&kb_event_cache,&kb_event,sizeof(kb_event));
}
}
#endif
}
DA
void proc_keyboard (u8 e, u8 *p)
{
kb_event.keycode[0] = 0;
int det_key = kb_scan_key (0, 1);
rN
de
#if(DEEPBACK_FAST_KEYSCAN_ENABLE)
if(deepback_key_state != DEEPBACK_KEY_IDLE){
un
deepback_pre_proc(&det_key);
}
#endif
ed
if (det_key){
ar
key_change_proc();
}
Sh
#if(DEEPBACK_FAST_KEYSCAN_ENABLE)
if(deepback_key_state != DEEPBACK_KEY_IDLE){
deepback_post_proc();
}
#endif
}
需要注意一下按键释放的处理:手动给这个按键值时,判断一下当前矩阵按
键的状态是否还有按键按着。若有键按着,不用后面加手动的 release,因为实
际的按键释放时会产生一个释放动作;若当前按键已经释放了,需要标记一下后
面需要给一个手动的 release,否则有可能会出现缓存的按键事件一直有效,无
法释放。
deepback_post_proc 处理就是根据 deepback_pre_proc 中是否留下手动
release 事件,来决定要不要往 ble TX fifo 里放一个按键 release 事件。
DA
着时,需要定时发送一个按键值。
加入 repeat key 处理,在 app_config.h 中配置相关的宏如下。
KB_REPEAT_KEY_ENABLE 用来打开或关闭 repeat key 功能,默认这个功能是关
rN
闭的。
//repeat key
de
#define KB_REPEAT_KEY_ENABLE 0
#defineKB_REPEAT_KEY_INTERVAL_MS 200
un
#define KB_REPEAT_KEY_NUM 1
1) KB_REPEAT_KEY_ENABLE
ar
2) KB_REPEAT_KEY_INTERVAL_MS
定义 repeat key 的 repeat 时间。
若设为 200ms,表示当一个键被一直按着时,每过 200 ms,kb_key_scan 会
返回一个变化,并且在 kb_event 里面给出当前这个按键状态。
3) KB_REPEAT_KEY_NUM 和 KB_MAP_REPEAT
定义当前需要 repeat 的键值。
KB_REPEAT_KEY_NUM 定义数量;KB_MAP_REPEAT 定义一个 map,给出所有
需要 repeat 的 keycode,
注意这个 map 中 keycode 一定要是 KB_MAP_NORMAL
中的值。
AN-19011501-C5 269 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
应用举例:
如下所示的一个 6*6 的矩阵按键,四个宏定义实现的功能是:8 个按键 UP、
DOWN、LEFT、RIGHT、V+、V-、CHN+、CHN-支持 repeat,每 100ms repeat 一次;
其他的按键都不支持 repeat key。
DA
repeat key 代码的实现这里不介绍,user 自行理解,只要在工程上搜索以上
四个宏就可以找到所有代码了。
rN
de
8.6 卡键处理
户不小心用一些东西把其中一个键或多个键压住了,比如家里的茶杯/烟灰缸等
压住了遥控器。此时正常的 keyscan 会发现一直有一些按键被按着没有释放,
code 上若不做相应的卡键处理,就会一直认为是按键按着没有释放,永远进不
ed
了 deepsleep 或其他低功耗状态。
ar
app_config.h 中相关的两个宏为
//stuck key
Sh
#define STUCK_KEY_PROCESS_ENABLE 0
#endif
if (det_key){
#if (STUCK_KEY_PROCESS_ENABLE)
if(kb_event.cnt){ //key press
stuckKey_keyPressTime = clock_time();
}
#endif
DA
.......
}
rN
对于每一个按键状态的变化,当发现是按键按下时(kb_event.cnt 非 0),记
de
录这个最近的按键按下状态的时间 stuckKey_keyPressTime。
然后在blt_pm_proc中处理如下:
un
#if (STUCK_KEY_PROCESS_ENABLE)
if(key_not_released && clock_time_exceed(stuckKey_keyPressTime,
ed
STUCK_KEY_ENTERDEEP_TIME*1000000)){
u32 pin[] = KB_DRIVE_PINS;
ar
extern u8 stuckKeyPress[];
if(stuckKeyPress[i]){
cpu_set_gpio_wakeup (pin[i],0,1); //reverse stuck
key pad wakeup level
}
}
cpu_sleep_wakeup(1, PM_WAKEUP_PAD, 0); //deepsleep
}
#endif
判断最近的一次按键被按下的时间是否已经连续超过 60s。若超过,则认为
是发生了卡键处理,根据底层的 stuckKeyPress[]获取发生卡键的所有行号,将这
些行原来高电平 PAD 唤醒 deepsleep 改为低电平 PAD 唤醒 deepsleep。
修改的原因是本来按键按下时,对应的行上 drive pin 读到的是 10/11 VCC 高
电平,此时是无法进入 deepsleep 的,因为已经是高电平了,只要进入 deepsleep
就会立刻被这个高电平唤醒;修改为低电平唤醒后,可以正常进入 deepsleep,
且按键被释放时,行上的 drive pin 的电平变为 100K 下拉的低电平,释放按键可
以唤醒整个 MCU。
DA
rN
de
un
ed
ar
Sh
9 LED 管理
DA
在初始化的时候使用 device_led_init(u32 gpio,u8 polarity)设置当前 LED 对应的
GPIO 和极性。极性设为 1,表示 gpio 输出高电平点亮 LED;极性设为 0,表示低
电平点亮 LED。 rN
在 main_loop 的 UI Entry 部分添加 device_led_process 函数,该函数每次检查
是否有 LED 任务没有完成(DEVICE_LED_BUSY),若有任务,去执行相应的操作。
de
un
typedef struct{
Sh
} led_cfg_t;
DA
led_cfg_t led_event4 = {0, 100, 0xff, 0x00, };
5) 亮 3 秒后熄灭:onTime_ms 为 1000, offTime_ms 为 0,repeatCount 为 0x3
rN
led_cfg_t led_event5 = {1000, 0, 3, 0x00, };
de
调用 device_led_setup 将一个 led_event 送给 led 任务管理:
un
device_led_setup(led_event1);
ed
DA
user 可以参考当前 8258 ble remote 工程里代码 LED 相关的处理方法。
rN
de
un
ed
ar
Sh
DA
要求不是特别高的情况。
blt soft timer 的最大特点是不仅在 main_loop 中会被查询,也能确保在进入
suspend 后能够及时唤醒并执行 timer 的任务,该设计是基于低功耗唤醒部分介
绍的“应用层定时唤醒”实现的。
rN
de
现更多的或者更少的 timer:
#define MAX_TIMER_NUM 4 //timer max number
ed
DA
rN
de
#define CALLBACK_ENTRY 1
ar
blt_soft_timer_process 的具体实现比较复杂,基本思路如下:
Sh
}
AN-19011501-C5 277 Ver1.4.0
泰凌 Kite BLE SDK 开发指南
DA
rN
这里面可以看到对 timer 任务函数的处理:若该函数返回值小于 0,这个 timer
de
任务会被删掉,后面不再响应;若返回值为 0,保持上一次的定时值;若返
回值大于 0,则以该返回值作为新的定时周期(单位 us)。
un
if(change_flg){
blt_soft_timer_sort();
ar
}
Sh
10.3 添加定时器任务
DA
3) 返回值大于 0,则使用该返回值做为新的定时周期,单位 us。
rN
de
un
ed
ar
代码实现中,可以看到当定时器数量超过最大值时,添加失败。每添加一个
Sh
新的 timer 任务,必须重新做一下排序,以确保定时器任务在任何时候都是按照
时间排序的,时间上最近的那个 timer 任务对应的 index 为 0。
10.4 删除定时器任务
10.5 Demo
int gpio_test0(void)
{
DBG_CHN0_TOGGLE; //gpio 0 toggle to see the effect
return 0;
}
int gpio_test1(void)
{
DA
DBG_CHN1_TOGGLE; //gpio 1 toggle to see the effect
static u8 flg = 0;
flg = !flg;
rN
if(flg){
de
return 7000;
}
un
else{
return 17000;
}
ed
}
ar
int gpio_test2(void)
{
Sh
return 0;
}
int gpio_test3(void)
{
//gpio 3 toggle to see the effect
DBG_CHN3_TOGGLE;
return 0;
初始化:
blt_soft_timer_init();
blt_soft_timer_add(&gpio_test0, 23000);
blt_soft_timer_add(&gpio_test1, 7000);
blt_soft_timer_add(&gpio_test2, 13000);
blt_soft_timer_add(&gpio_test3, 27000);
DA
3) gpio_test2 在 5S 后将自己删掉。
代码中有两种方式都可以实现这个功能:
rN
一是调用 blt_soft_timer_delete(&gpio_test2);二是 return -1。
4) gpio_test3 每 27ms toggle 一次。
de
un
ed
ar
Sh
11 IR
DA
驱动上定义了 6 路 PWM 为:
typedef enum {
PWM0_ID = 0,
rN
PWM1_ID,
de
PWM2_ID,
PWM3_ID,
un
PWM4_ID,
PWM5_ID,
}pwm_id;
ed
ar
typedef enum{
……
AS_PWM0 = 20,
AS_PWM1 = 21,
AS_PWM2 = 22,
AS_PWM3 = 23,
AS_PWM4 = 24,
DA
AS_PWM5 = 25,
AS_PWM0_N = 26,
AS_PWM1_N = 27,
AS_PWM2_N = 28,
AS_PWM3_N = 29,
rN
AS_PWM4_N = 30,
de
AS_PWM5_N = 31,
}GPIO_FuncTypeDef;
un
11.1.2 PWM 时钟
Sh
DA
void pwm_set_cmp(pwm_id id, unsigned short cmp_tick) 用于设置 PWM
cmp,单位为 PWM clock 的个数。
PWM
clock
1 2 3 4 5
PWM
cmp
waveform
cycle
对于 PWM0 ~ PWM5,硬件上会自动将高电平放前面,低电平放后面。如果
想要低电平在前的话,有以下几种方法:
DA
或
pwm_set_cycle_and_duty(PWM0_ID, 16000, 8000);
rN
11.1.4 PWM 波形取反
de
API void pwm_revert(pwm_id id)用于将 PWM0 ~ PWM5 波形取反。
API void pwm_n_revert(pwm_id id)用于将 PWM0_N ~ PWM5_N 波形取反。
un
11.1.6 PWM 模式
DA
11.1.7 PWM 脉冲数(pulse number)
11.1.8 PWM 中断
ed
中断“status”是由硬件的特定动作(也就是中断动作)产生的状态标记位,它
不依赖于软件中任何设定,不管中断“mask”是否打开,只要中断动作发生,
Sh
DA
};
IR task IR task
(pulse number = 3) (pulse number = 3)
ed
Signal Frame Signal Frame Signal Frame Signal Frame Signal Frame Signal Frame
ar
Sh
IRQ_PWM0_FRAME IRQ_PWM0_FRAME
IRQ_PWM0_PNUM IRQ_PWM0_PNUM
1) 第一类:IRQ_PWMn_FRAME(n=0,1,2,3,4,5)
中断源后面的 6 个,是同一种中断,分别在 PWM0~PWM5 上产生。
如图所示,IRQ_PWMn_FRAME 是每个 PWM Signal Frame 结束后都会产生的
中断。PWM 5 种模式里,Signal Frame 都是 PWM 波形的基本单位。所以不
管哪种 PWM 模式,IRQ_PWMn_FRAME 都会出现。
2) 第二类:IRQ_PWM0_PNUM
IRQ_PWM0_PNUM 是一组 Signal Frame(个数由 API pwm_set_pulse_num 决
定)结束时产生的中断。图中每 3 个 Signal Frame 后产生一个
IRQ_PWM0_PNUM。
PWM 的 Counting mode、IR mode 会用到 API pwm_set_pulse_num。所以,只
有 PWM0 的 Counting mode、IR mode 才会产生 IRQ_PWM0_PNUM。
3) 第三类:IRQ_PWM0_IR_DMA_FIFO_DONE
PWM0 工作在 IR DMA FIFO mode 时,当 DMA 上所有配置好的 PWM 波形全
部发送完毕后,触发 IRQ_PWM0_IR_DMA_FIFO_DONE。
DA
上面说到所有相关中断“mask”同时被打开时,才会触发中断响应,对于 PWM
中断,以 FLD_IRQ_PWM0_PNUM 为例,共有 3 层“mask”需要打开:
1) FLD_IRQ_PWM0_PNUM 的“mask” rN
即 core_7b0 对应的“mask”,打开方法为:
de
reg_pwm_irq_mask |= FLD_IRQ_PWM0_PNUM;
un
reg_pwm_irq_sta = FLD_IRQ_PWM0_PNUM;
ed
ar
即 core_640 的 BIT<14>。
#define reg_irq_mask REG_ADDR32(0x640)
enum{
……
FLD_IRQ_SW_PWM_EN = BIT(14), //irq_software | irq_pwm
……
};
打开方法为:
reg_irq_mask |= FLD_IRQ_SW_PWM_EN;
下面介绍 IR DMA FIFO mode 专用的几个 API。User 可结合 SDK 上 PWM demo
code 来理解。
DA
DMA length ……
1 2 n
FIFO 1 FIFO 2 FIFO n
rN
DMA FIFO buffer
de
}Pwm0Pulse_SelectDef;
DA
“pulse”为 PWM0_PULSE_NORMAL 时,Signal Frame 来自 API
rN
pwm_set_cycle_and_duty 的配置;“pulse”为 PWM0_PULSE_SHADOW 时,Signal
Frame 来自 PWM shadow mode 的配置。
de
PWM shadow mode 是为了增加一组 Signal Frame 的配置,从而为 IR DMA FIFO
mode 的 PWM waveform 配置增加更多的灵活性。配置 API 如下,方法和 API
pwm_set_cycle_and_duty 完全一致。
un
DMA 模块。
void pwm_set_dma_address(void * pdat);
11.2 IR Demo
DA
相关的中断先来了,这个中断会占用 MCU 的时间,可能会导致 PWM 输出的切
换时间被延迟,IR 发生错误。
所以 IR 不能使用 PWM Normal mode。 rN
de
Telink 上一代 826x 系列 BLE SDK 上,使用 PWM IR mode 来实现 IR,user 可以
参考“826x BLE SDK handbook”的介绍。
un
险。
ar
11.2.2 Demo IR 协议
110 mS 110 mS
Data
DA
Red : Start
Data format:Address + Addrss + Command + Command Blue: Data
Green : Repeat
rN
图 11-4 demo IR 协议
de
un
11.2.3 IR 时序设计
点,我们得到下面图示的 IR 时序。
IR DMA FIFO mode 一个完整的任务定义为 FifoTask。先按照 SDK demo 中对 IR
ar
#define ADD_REPEAT_ONE_BY_ONE 1
IR dma fifo done IR dma fifo done IR dma fifo done IR dma fifo done
IR start interrupt interrupt interrupt interrupt
图 11-5 IR timing 1
当一个按键按下触发 IR 发送开始后,将 IR 拆分成图上的 FifoTask。
1) IR start 后,运行 FifoTask_data,发送有效数据。FifoTask_data 持续时间为
T_data,由于数据的不确定性,T_data 也不确定。FifoTask_data 结束后,触
发中断 IRQ_PWM0_IR_DMA_FIFO_DONE。
2) 在 IRQ_PWM0_IR_DMA_FIFO_DONE 中断函数里,开启 FifoTask_idle,这个阶
段发送无载波信号,时间为 110ms – T_data。FifoTask_idle 存在的意义是为了
控制第 1 个 FifoTask_repeat 时间点正好在 IR start 后的 110mS。FifoTask_idle
结束后,触发中断 IRQ_PWM0_IR_DMA_FIFO_DONE。
3) 在 IRQ_PWM0_IR_DMA_FIFO_DONE 中断函数里,开启第 1 个 FifoTask_repeat。
每个 FifoTask_repeat 的持续时间都是 110mS,只要在其对应的
IRQ_PWM0_IR_DMA_FIFO_DONE 中断函数继续添加下一个 FifoTask_repeat,
DA
就可以控制 IR repeat 信号的持续发送。
4) IR stop 的时间点是不确定的,取决于按键 release 的时间。应用层检测到按
键 release 后,在确保 FifoTask_data 正确完成的前提下,手动停止 IR DMA FIFO rN
mode 即可结束 IR 的发送。
de
对上面时序设计进行一些优化。优化的步骤包括:
un
续做优化。
2) 在步骤 1 优化基础上,将 FifoTask_ilde 和第一个 FifoTask_repeat*n 合一起,
Sh
组成 FifoTask_idle_repeat*n。
优化后的 IR 时序如下图所示:
FifoTask_data FifoTask_idle_repeat*n FifoTask_repeat*n
repeat
repeat
repeat
data …… ……
1
……
2,3... n 1
……
2,3... n
IR start
IR dma fifo done IR dma fifo done IR dma fifo done
interrupt interrupt interrupt
图 11-6 IR timing 2
DA
ir_send_ctrl.sending_start_time = clock_time();
pwm_start_dma_ir_sending();
}
rN
void rc_ir_irq_prc(void)
de
{
if(reg_pwm_irq_sta & FLD_IRQ_PWM0_IR_DMA_FIFO_DONE)
{
un
reg_pwm_irq_sta = FLD_IRQ_PWM0_IR_DMA_FIFO_DONE;
ed
if(ir_send_ctrl.repeat_enable){
if(ir_send_ctrl.is_sending == IR_SENDING_DATA){
ir_send_ctrl.is_sending = IR_SENDING_REPEAT;
ar
pwm_start_dma_ir_sending();
}
else if(ir_send_ctrl.is_sending == IR_SENDING_REPEAT){
//Add FifoTask_repeat*n to Dma
pwm_start_dma_ir_sending();
}
}
else{
ir_send_release();
}
}
}
11.2.4 IR 初始化
11.2.4.1 rc_ir_init
IR的初始化没有放到user initilialization里面进行,而是在IR按键触发时,按如
下调用。
if(!ir_hw_initialed){
ir_hw_initialed = 1;
rc_ir_init();
DA
}
wake_up后必须重新设置。
二是Sram逻辑变量的初始化(如waveform_logic_0_1st)。这些变量如果在
ed
“data/bss”段上,必须每次都重新设置;如果在“retention_data”段上,只要设
置一次就可以。
ar
SDK demo上将这些变量放在“data/bss”段上了,所以需要每次都重新设置。
User在做功耗优化时,可以根据Sram retention area的使用情况,决定是否将
Sh
它们放到“retention_data”段。
变量ir_hw_initialed必须放在“data/bss”段,不能放在“retention_data”段,以确
保每次deepsleep retention wake_up后都要重新执行rc_ir_init函数。
11.2.4.2 IR 硬件配置
Demo code如下。
pwm_n_revert(PWM0_ID);
gpio_set_func(GPIO_PB3, AS_PWM0_N);
pwm_set_mode(PWM0_ID, PWM_IR_DMA_FIFO_MODE);
pwm_set_phase(PWM0_ID, 0); //no phase at pwm beginning
pwm_set_cycle_and_duty(PWM0_ID, PWM_CARRIER_CYCLE_TICK,
PWM_CARRIER_HIGH_TICK );
pwm_set_dma_address(&T_dmaData_buf);
reg_irq_mask |= FLD_IRQ_SW_PWM_EN;
reg_pwm_irq_sta = FLD_IRQ_PWM0_IR_DMA_FIFO_DONE;
DA
11.2.4.3 IR 变量初始化 rN
对应 SDK demo 中变量 waveform_start_bit_1st、waveform_start_bit_2nd 等。
de
结合 IR 时序设计的介绍,需要配置出 FifoTask_data、FifoTask_repeat。
Start 信号是 9mS 的载波信号+4.5ms 无载波信号,对应的两个 DMA FIFO 数
un
11.2.5.1 FifoTask_data
DA
//waveform for start bit
rN
T_dmaData_buf.data[T_dmaData_buf.data_num ++]= waveform_start_bit_1st;
T_dmaData_buf.data[T_dmaData_buf.data_num ++]= waveform_start_bit_2nd;
de
//add data
u32 data = (~cmd)<<24 | cmd<<16 | addr2<<8 | addr1;
un
for(int i=0;i<32;i++){
if(data & BIT(i)){
//waveform for logic_1
ed
T_dmaData_buf.data[T_dmaData_buf.data_num ++] =
waveform_logic_1_1st;
ar
T_dmaData_buf.data[T_dmaData_buf.data_num ++] =
waveform_logic_1_2nd;
Sh
}
else{
//waveform for logic_0
T_dmaData_buf.data[T_dmaData_buf.data_num ++] =
waveform_logic_0_1st;
T_dmaData_buf.data[T_dmaData_buf.data_num ++] =
waveform_logic_0_2nd;
}
}
T_dmaData_buf.dma_len = T_dmaData_buf.data_num * 2;
……
pwm_start_dma_ir_sending();
11.2.5.2 FifoTask_idle
DA
对应code如下:
rN
u32 tick_2_repeat_sysClockTimer16M = 110*CLOCK_16M_SYS_TIMER_CLK_1MS –
(clock_time() - ir_send_ctrl.sending_start_time);
u32 tick_2_repeat_sysTimer =
de
(tick_2_repeat_sysClockTimer16M*CLOCK_SYS_CLOCK_1US>>4);
un
这里要注意两个时间单位的切换问题。参考本文档“时钟模块”的介绍可知,
软件计时使用的 System Timer 频率是固定的 16M。而 PWM clock 的来源是 system
clock,需要考虑当 system clock 频率为非 16M(24M、32M)时候的情况。
ed
ar
现。
waveform_wait_to_repeat = pwm_config_dma_fifo_waveform(0,
PWM0_PULSE_NORMAL, tick_2_repeat_sysTimer/PWM_CARRIER_CYCLE_TICK);
11.2.5.3 FifoTask_repeat
DA
CLOCK_SYS_CLOCK_1US/PWM_CARRIER_CYCLE_TICK);
if( ir_send_ctrl.is_sending)
{
bls_pm_setSuspendMask(SUSPEND_DISABLE);
}
12 其他模块
DA
rN
de
un
ed
ar
DA
void blc_pm_select_external_32k_crystal(void);
12.3 PA
un
首先打开下面的宏,默认是关闭的。
#ifndef PA_ENABLE
ar
#define PA_ENABLE 0
#endif
Sh
在系统初始化的时候,调用 PA 的初始化。
void rf_pa_init(void);
#ifndef PA_RXEN_PIN
#define PA_RXEN_PIN GPIO_PB3
#endif
User只需要调用上面的rf_pa_init,app_rf_pa_handler被注册到底层的回调,
BLE在各种状态时,都会自动调用app_rf_pa_handler的处理。
12.4 PhyTest
DA
PhyTest 即 PHY test,是指对 BLE controller RF 性能的测试。
详情请参照《Core_v5.0》(Vol 2/Part E/7.8.28~7.8.30)和《Core_v5.0》(Vol 6/Part
F “Direct Test Mode” )。 rN
de
12.4.1 PhyTest API
PhyTest 的源码被封装在 library 文件中,提供相关 API 供 user 使用,请参考
un
stack/ble/phy/ble_test.h 文件。
ed
void blc_phy_initPhyTest_module(void);
bool blc_phy_isPhyTestEnable(void);
Sh
DA
phy_test_2_wire_rx_from_uart 实现上位机下发的 cmd 的解析和执行,
rN
phy_test_2_wire_tx_to_uart 实现将相应的结果和数据反馈给上位机。
de
12.4.2 PhyTest demo
12.4.2.1 Demo1: 8258_feature_test
un
根据物理接口和测试命令格式的不同,PhyTest 可分为三种测试模式,如下
所示,“PHYTEST_MODE_DISABLE”表示 PhyTest 禁用。
Sh
#ifndef PHYTEST_MODE_DISABLE
#define PHYTEST_MODE_DISABLE 0
#endif
#ifndef PHYTEST_MODE_THROUGH_2_WIRE_UART
#define PHYTEST_MODE_THROUGH_2_WIRE_UART 1
#endif
#ifndef PHYTEST_MODE_OVER_HCI_WITH_USB
#define PHYTEST_MODE_OVER_HCI_WITH_USB 2
#endif
#ifndef PHYTEST_MODE_OVER_HCI_WITH_UART
#define PHYTEST_MODE_OVER_HCI_WITH_UART 3
#endif
选择 PhyTest 的测试模式:
#if (FEATURE_TEST_MODE == TEST_BLE_PHY)
#define BLE_PHYTEST_MODE PHYTEST_MODE_THROUGH_2_WIRE_UART
#endif
DA
#define BLE_PHYTEST_MODE PHYTEST_MODE_OVER_HCI_WITH_UART
12.4.2.2 Demo2:8258_ble_remote
ed
DA
{
blc_phy_initPhyTest_module();
blc_phy_preamble_length_set(11);
……
rN
de
}
un
12.5 EMI
ar
Sh
DA
write_reg8(0x840006,run);//run 命令 1:开始测试项,0:结束测试项
write_reg8(0x840007,cmd_now);//cmd:测试项设置
rN
write_reg8(0x840008,power_level);//power_level:发送power初始化
write_reg8(0x840009,chn);//chn:RF channel 初始化
write_reg8(0x84000a,mode);//mode:RF 模式初始化,
de
//在BLE SDK中暂只支持BLE 1M模式
write_reg8(0x840004,0); // 4bytes RSSI统计平均值初始化为0
un
DA
12.5.1.3 Emi Carrier Only(单载波)
介绍的设置方法来设置即可。
ed
12.5.1.4 emi_con_prbs9
数据由 rf_continue_mode_loop()函数来更新,以此来保证载波上的数据是一
系列随机数。
Sh
DA
rf_chn);
12.5.1.6 EMI RX
Sh
void rf_emi_rx_loop(void);
12.5.1.7 上位机配置参数设置
Run:
Cmd:
1 CarrierOnly 2 ContinuePRBS9 3 RX
DA
0 Reserve 1 Ble_1M
rN
这些参数的默认上电状态为(mode=1; power=0; channel=2; cmd=1),即在
ble_1M 模式、2402MHz 下以 10.4dbm 的发射功率发射单载波。
de
un
ed
ar
Sh
DA
rN
de
un
ed
第一步:选择芯片型号
Sh
图 12-3 选择芯片型号
图 12-4 选择数据总线
DA
rN
de
un
ed
ar
DA
图 12-6 set channel
rN
第四步:通过下拉框可以选择不同的 power level 及 ble mode,选择完后,
de
点击右侧 set 按钮(“Set_Power”/“Set_RF_Mode”)即可完成设置。
un
ed
ar
Sh
图 12-7 选择 RF 模式
DA
rN
de
un
ed
ar
Sh
图 12-9 选择测试模式
DA
rN
de
un
ed
ar
Sh
13 附录
13.1 附录 1:crc16 算法
unsigned shortcrc16 (unsigned char *pD, int len)
int i,j;
DA
for(j=len; j>0; j--)
ds = ds >> 1;
ed
}
ar
Sh
return crc;
系统时钟
Tick Rate
#define configTICK_RATE_HZ 1000
DA
Tick Rate 用户可以自行设置,一般是 100 – 1000。不能大于 1000
14.2 系统初始化 rN
Main.c 初始化代码尽量保持不变。例如以下两句请保留
de
un
14.3 创建任务
创建协议栈任务如下
tProto 任务是必须运行的,不能删除。协议栈任务优先级一般为最高优先级
UI 任务在每次 wakeup 唤醒都会调用一次,所以可以将一些跟唤醒周期相关的操
作放在 ui_task 里面