跳转至

低延时麦克风SDK


快速上手指南

概述

文档目的

完整描述SDK常用的配置、 使用及调试方法,供用户快速上手,并解决实际开发过程中所遇到的绝大多数问题。

术语说明

TBD

硬件相关

硬件框图

硬件框图

EVB开发板

EVB开发板 1

EVB开发板 2

EVB开发板 3

EVB开发板具体细节可参考“Telink TLSR9518A Generic Starter Kit Hardware Guide_internal_V1.0.pdf”文档。

(1) Line-in输入

有左右两路linein输入,可单端或者差分输入,EVB默认为差分接法。

单左路linein:

短接

AIPL1_LINEIN(J34_32)--TL_AIPL1(J34_31)
AINL1_LINEIN(J34_34)--TL_AINL1(J34_33)

断开

AIPL1_MIC(J34_28)--TL_AIPL1(J34_27)
AINL1_MIC(J34_30)--TL_AINL1(J34_29)
MICBIAS_AIL1(J34_36)--TL_MICBIAS(J34_35)

单右路linein:

短接

AIPR1_LINEIN(J20_32)--TL_AIPR1(J20_31)
AIPR1_LINEIN(J20_34)--TL_AINL1(J20_33)

断开

AIPR1_MIC(J20_28)--TL_AIPR1(J20_27)
AINR1_MIC(J20_30)--TL_AINR1(J20_29)
MICBIAS_AIR1(J20_36)--TL_MICBIAS(J20_35)

(2) AMIC输入

有左右两路amic输入,可单端或者差分输入,EVB默认为差分接法。

单左路amic:

短接

AIPL1_MIC(J34_28)--TL_AIPL1(J34_27)
AINL1_MIC(J34_30)--TL_AINL1(J34_29)
MICBIAS_AIL1(J34_36)--TL_MICBIAS(J34_35)

断开

AIPL1_LINEIN(J34_32)--TL_AIPL1(J34_31)
AINL1_LINEIN(J34_34)--TL_AINL1(J34_33)

单右路amic:

短接

AIPR1_MIC(J20_28)--TL_AIPR1(J20_27)
AINR1_MIC(J20_30)--TL_AINR1(J20_29)
MICBIAS_AIR1(J20_36)--TL_MICBIAS(J20_35)

断开

AIPR1_LINEIN(J20_32)--TL_AIPR1(J20_31)
AIPR1_LINEIN(J20_34)--TL_AINL1(J20_33)

(3) DMIC输入

dmic只需要2根信号线data和clk,clk频率固定为3M。

双DMIC有1根信号线data和2根clk,双dmic共用data,2路clk时序是一样的,在clk上沿采集一个DMIC数据,在clk下沿采集另一个dmic数据。

短接

左右mic共用数据:DMICDAT(J20-10)-- TL_PD4_DMICDAT(J20-9)

左mic:DMICCK1(J20-8)--TL_PD6_DMICCLK1(J20-7)

右mic:DMICCK2(J20-6)--TL_PD6_DMICCLK2(J20-5)

(4) USB接口

短接

DM(J34-2)--TL_PA5_DM (J34-1)
DP(J34-4)--TL_PA6_DP (J34-3)

(5) I2S接口

支持I2S主从模式,但建议TRLS9XXX I2S做Master,做Master不需要做异步重采样,减少算力消耗。

I2S接口

运行示例

硬件准备

需要三块EVB板,三根USB线。

硬件准备

说明:

配对按键:短按进入配对状态,双击退出配对状态。

连接状态指示灯:快闪为配对状态、慢闪为回连状态、常亮为连接状态。

如上图,使用的L Linein输入和Lineout输出接口,若使用模拟mic作为输入,则Tx端J34需按照下图红框所示进行短接。

Tx端J34短接

软件烧录

两个EVB作为TX,烧录_img_proj_ultra_ll_mic_tx_.bin,一个EVB作为RX,烧录_img_proj_ultra_ll_mic_rx_.bin(固件在release_bin目录),烧录步骤如下:

Step 1: 准备Telink烧录器,按照下图所示接法。

准备Telink烧录器

Step 2: 打开Telink BDT.exe 程序。

打开Telink BDT.exe

Step 3: 点击Activate。

点击Activate

Step 4: 点击File,在下拉菜单里点击Open。

点击File->Open

选择待烧录的固件,然后点击Download,如下图:

选择固件并下载

Step 5: 烧录MAC地址,打开Memory Access,按下图数字顺序操作和填入MAC地址,若要烧录MAC地址为: 0xa4c138000000,则填入: 00 00 00 38 c1 a4,如下图,填完后,按Enter键烧录。(BDT工具的更多操作,可访问:http://wiki.telink-semi.cn/wiki/IDE-and-Tools/Burning-and-Debugging-Tools-for-all-Series/)

烧录MAC地址

注意,当烧录器出现activate失败情况时,需要对烧录器进行固件升级,如下图所示,点击Help,选择Upgrade,载入烧录软件路径下的固件,最后重新拔插一下烧录器。

固件升级

运行

重新上电后,分别短按TX0和RX上的SW2按键来配对TX0,再分别按TX1和RX上的SW2按键来配对TX2,配对成功后TX0和TX1蓝灯常亮的同时,会分别亮起表示通道的绿灯和白灯,而Rx则会同时亮起绿灯和白灯,如图所示:

配对成功显示

最后,可通过Lineout接口监听音频。

注意:

  • Tx端的Lineout输出的是本地AMIC拾音编码后再解码输出的音频,而Rx端的Lineout输出的,分别是远端的Tx0和Tx1发送过来的编码音频数据解码后输出的音频。
  • 其中亮绿灯的Tx0为左声道,亮白灯的Tx1为右声道。

编译SDK

导入

首次打开Andesight IDE,会弹出选择workspace的对话框,你需要Browser选择一个目录作为你的workspace。可以选择任意的目录作为workspace目录。

选择Workspace

选择File -> Import,如下图所示:

选择File->Import

在弹出的窗口中,点击General,选择Existing Projects into Workspace。

选择Existing Projects into Workspace

在Select root directory框中输入SDK所在路径,或直接点击Browser,选择SDK所在的目录,接着勾选SDK,点击Finish,即可导入工程(注:建议IDE工作路径和SDK源码所在路径做区分,便于多个工程打开)。

导入工程

编译

编译后需要烧录的bin为合并固件(带_img_*头),包括:

(1) 带bootloader的boot工程:用于程序跳转以及ota。(proj_boot*)

(2) app工程:实际运行软件。(proj_ultra_ll_mic*)

IDE脚本会自行将上述两个工程合并为带_img_头的固件并保存到app工程生成目录下如:

b91m_ultra_ll_mic_sdk\proj_ultra_ll_mic_tx\output\img_proj_ultra_ll_mic_tx.bin

因此首次编译app工程时,需先编译_proj_boot_device_和_proj_boot_dongle_两个工程,用于后续生成合并固件。

注意:

首次编译时,必须先编译两个boot工程,再编译项目工程,否则会报错。

编译两个boot工程

接着再编译3(接收端16bit)4(接收端24bit)或5(发射端16bit)6(发射端24bit)工程。

编译3、4或5、6工程

常用工具

Telink BDT工具全称Telink Burning and Debugging Tool,具体功能可参考Tool User guide文档,下面列出几个常见的应用场景。

(1) 多bin文件下载工具

Telink BDT基于TWS,实现了多个bin文件烧录的功能,同时适用于本SDK。使用方式如下:

Step 1: 点击Tool,在下拉菜单中选择TWS Tool。

选择TWS Tool

Step 2: 选择Download Tool页面,分别勾选Bin File1、Bin File2以及BT MAC,并选择对应的项目固件、提示音文件以及填入MAC地址,点击Download即可同时烧录程序、提示音、以及MAC地址。

同时烧录程序、提示音、以及MAC地址

(2) 提示音独立烧录

SDK默认支持的提示音功能,需要在指定地址烧录提示音文件,具体步骤如下所示:

Step 1: 打开Telink BDT,点击Setting,在弹出窗口的Download Addr处输入0x80000。

输入指定地址

Step 2: 选取48k的ADPCM提示音文件。

选取提示音文件

Step 3: 点击Download烧录提示音。

烧录提示音

(3) MAC地址独立读写

当两个Tx端的Mac地址一样时可能会出现干扰的现象,因此可通过Telink BDT读写mac地址。

Step 1: 打开Telink BDT,点击Acitve,在Activate OK情况下,点击Tool,选择Memory Access,在弹出的窗口中内存类型选择Flash,大小输入6,addr输入FF000。

输入Flash地址

接着按下tab键,即可读取到该地址存储的mac地址:e0 33 8e 8b 70 2b。

读取MAC地址

Step 2: 在data一栏输入需要修改的mac地址:如e0 33 8e 8b 70 2c,并按下回车键,即可写入。

修改MAC地址

再次按下tab键,读取该地址数据,观察发现确实写入成功:

成功写入

(4) 给TX端烧写指定的usb_id

2对1模式下,TX端默认起始地址为0x120(通过FLASH_SYS_USER_USBID_ADDR所在的0xFFFF0地址进行判断)。

void user_init(void)
{
    ......
#if (APP_BUILD == APP_MIC_TX)
    uint16_t usb_id = 0x120;
    uint8_t flag = 0;
    app_flash_read(FLASH_SYS_USER_USBID_ADDR, sizeof(flag), (u8 *)&flag); //FLASH_SYS_USER_USBID_ADD - 0x7FFF0
    if (flag != 0xFF) {
        usb_id = 0x100 + flag;
    }
    ......
}

如果需要查看多个TX的log,则需要修改不同TX的usb_id所在地址进行区分,从而和risc_v_tdb工具的pid配置一致。

修改usb_id地址

然后在risc_v_tdb工具的pid配置中同步对应的值即可。

同步配置

risc_v_tdb

(1) USB打印和查看

USB打印log可使用下面函数,其参数str为你要打印的字符串,ph为你要打印的数据,n代表字节长度。如果只需要打印字符串或者数据,可以把不需要的参数写0.

void usb_send_str_data (char *str, u8 *ph, int n);

该函数有如下常用的封装:

void usb_send_str_data (char *str, u8 *ph, int n)
int usb_send_str_int (char *str,int w);
void usb_send_str_u32s (char *str, u32 d0, u32 d1, u32 d2, u32 d3);
#define my_dump_str_data(en,s,p,n)
#define my_dump_str_u32s(en,s,d0,d1,d2,d3)

开发者可灵活调用。

要支持USB打印,首先需要在app_config.h文件中将APP_MODE配置为APP_MODE_DEBUG_ONLY或APP_MODE_DEBUG_HID。(APP_MODE_AUDIO_AUTO模式下无法使用risc_v_tdb工具查看log,需要使用USB转串口工具)

......
/////   4 different mode //////////

#if (APP_BUILD == APP_MIC_RX)
    #define     APP_MODE               APP_MODE_DEBUG_ONLY    //APP_MODE_AUDIO_AUTO
#else
    #define     APP_MODE               APP_MODE_DEBUG_ONLY    // APP_MODE_DEBUG_ONLY or APP_MODE_DEBUG_HID
#endif

//////////////////////////// APP Mode: setting //////////////////////////////
......

接着根据不同的pid打开不同的配置,其中Tx端默认pid为0x1209218,Rx端默认pid为0x1239218,由usb_id以及pid两部分组成。

(a) Tx端usb_id

void user_init(void)
{
    ......
#if (APP_BUILD == APP_MIC_TX)
    uint16_t usb_id = 0x120;
    uint8_t flag = 0;
    app_flash_read(FLASH_SYS_USER_USBID_ADDR, sizeof(flag), (u8 *)&flag); //FLASH_SYS_USER_USBID_ADD - 0x7FFF0
    if (flag != 0xFF) {
        usb_id = 0x100 + flag;
    }
    ......
}

由于Tx在2对1模式下分为Tx0和Tx1,因此可进行usb_id定义。

(b) Rx端usb_id

void user_init(void)
{
    ......
#elif(APP_BUILD ==APP_MIC_RX)
    uint16_t usb_id = 0x123;
#endif
    ......
}

在app_usb_desc.c文件中定义完整的pid。

#if (APP_MODE & FLAG_PRINTER_DEBUG)
    const USB_Descriptor_Device_t my_device_desc_audio  = MYDESC_DEVICE(0x0110, 0x248a, 0x9218);

接着根据不同的pid创建不同的配置,分别是Tx0、Tx1以及Rx。

配置Tx0

配置Tx1

配置Rx

(2) Flash读写

(a) risc_v_tdb支持在线写入flash的功能,从而实现调试过程中直接在线升级程序的需求:

Step 1: 选取对应的配置,连接成功后,点击In File,选取待升级的固件。 (注:写入固件之前,可以通过修改MAC选项框同步修改MAC地址)

选取待升级固件

Step 2: 点击File_to_Flash写入固件。

写入固件

(b) 除此之外,risc_v_tdb还支持读取flash并保存为文件的功能:

Step 1: 选择支持读取flash并保存为文件的配置。

选择配置

Step 2: 点击Out File,选择待写入flash的bin文件。

选择bin文件

Step 3: 输入待读取的flash起始地址以及读取长度。

输入flash起始地址和读取长度

Step 4: 点击Flash_to_File,即可保存读取的flash到bin文件中。

保存flash到bin文件中

(3) 发送命令

risc_v_tdb支持发送指令控制程序的功能:基于USB Print模式,用户可在my_usb_audio_debug接口中增加项目的控制功能,目前支持的功能有:配对测试控制、EMI测试控制、提示音测试控制、数据传输测试控制、降噪功能测试控制。

my_usb_audio_debug接口

具体控制方式如下:

Step 1: risc_v_tdb连接板子,打开功能配置对应的宏如:EMI_TEST_ENABLE、TTONE_EN、TRANSMIT_DATA_EN、APP_NS_ENABLE等。

Step 2: 在命令框输入对应的命令,如11 00 20,并键入回车。

具体控制方式

其中11为命令头,00为配对测试控制对应值,20为配对测试传入参数。其他测试控制同上。

(4) VCD波形

VCD基础知识:

Value Change Dump (VCD),是一种基于ASCII的格式,用于由EDA逻辑仿真工具生成的转储文件。1996年,IEEE标准1364-1995与Verilog硬件描述语言一起定义了标准的四值VCD格式。六年后,IEEE标准1364-2001中定义的扩展VCD格式支持信号强度和方向性的记录。VCD格式的简单而紧凑的结构使它的使用变得无处不在并扩展到非Verilog工具中,例如VHDL模拟器GHDL和各种内核跟踪器。格式的一个限制是它不能在存储器中记录数值。

VCD文件内容分为标题段、变量定义段、变量初始化段、value change段。标题部分包括时间戳、模拟器版本号和时间刻度,它将值更改部分中列出的时间增量映射到模拟时间单位。变量定义部分包含范围信息以及在给定范围内实例化的信号列表。初始化部分包含所有转储变量的初始值。value change部分包含给定仿真模型中信号的一系列按时间顺序的值变化。

需要得到VCD文件,主要是对value change进行编写。

VCD函数说明:

(a) 打印事件,此函数会反转两次变量的值,及形成一个脉冲。此函数ID必须为event类型变量,其函数只会输出值变化的信息,不会输出时间信息,它的值变化会跟在最近的一个时间戳之后。

log_event(en,id)
e.g. log_event(1, SLET_timestamp)  //会反转两次SLEV_reserved的值,0->1,1->0.

(b) 打印事件和时间,此函数会反转两次变量的值,及形成一个脉冲。此函数ID必须为event with tick类型变量,其函数会输出值变化和时间的信息。

log_tick(en,id)  
e.g. log_tick(1, SLET_timestamp)  

(c) 打印数据,下面三个函数分别为打印1/8/16bit数据。en为使能,id为变量名(上一小节的信号量,需要使用1/8/16bit data类型的变量名),b为改变的值。它会把变量的值变化和当前的时间写入VCD文件。

log_task(en,id,b) //print 1bit data
e.g. log_task(1, SL01_TX_EN,1)   //把SLET_timestamp的值在当前时间点改为1
log_task(1, SL01_TX_EN,0)   //把SLET_timestamp的值在当前时间点改为0

log_b8(en,id,d)   //print 8bit data
e.g. log_ b8 (1, SL08_lmp_rx_code,1)
log_b16(en,id,d)  //print 16bit data
e.g. log_ b16 (1, SL16_bt_CLKN_HS,1)

risc_v_tdb生成VCD文件查看时序:

APP_MODE配置为APP_MODE_DEBUG_ONLY或APP_MODE_DEBUG_HID时,risc_v_tdb基于usb print支持Telink自有协议的vcd波形查看功能,能查看各个函数执行情况,具体查看步骤如下:

Step 1: 在连接情况下,点击Def,选择common\usb_dbg路径下的log_def_stack.h文件。

选择log_def_stack.h文件

Step 2: 点击In File,选择在firmware\_proj_ultra_ll_mic_tx_\output路径下生成的固件(这里以_proj_ultra_ll_mic_tx_为例)。

选择固件

Step 3: 点击VCD进行采集:

点击VCD

采集到一定量后点击Stop,vcd文件会自动保存到工程对应的output文件夹中。

vcd文件保存

然后点击View后跳转到波形查看工具。

跳转到波形查看工具

Step 4: 在弹出的工具中,点击File,选择Read Save File。

选择Read Save File

选中mic-i1.gtkw文件(文档中编者直接存储在riscv-tool根目录下,该文件用于将需要查看的波形添加到Waves窗口中,用户也可手动拖拽所需信号查看)。

选择mic-i1.gtkw文件

即可看到如图所示的波形。

查看波形

整个gtkwave工具页面如下:

第1个窗口点击下EVENT;

第2个窗口显示抓取的信号;

第3个窗口输入字符可以方便查找信号;

第4个窗口为显示的信号(在第2个窗口双击需要显示的信号即可);

第5个窗口就是整个信号在时序上的显示.

串口VCD助手生成VCD文件:

当APP_MODE配置为APP_MODE_AUDIO_AUTO时,无法使用risc_v_tdb工具生成vcd波形文件,这时需要使用串口VCD助手才能生成VCD波形文件。

首先需要使能app_config.h文件中的UART_VCD_EN宏。

SDK会在user_init中调用uart_vcd_init接口,初始化生成vcd波形的对应串口参数,波特率为3000000,默认TX引脚为PD2。

void user_init(void)
{
    ......
#if UART_VCD_EN
    uart_vcd_init(UART0, 3000000, UART0_TX_PD2, UART0_RX_PD3, DMA7);
#endif
    ......
}

接着将工具文件夹UART_VCD_TOOL需要与波形查看工具gtkw文件夹放在同级目录(编者这里将UART_VCD_TOOL文件夹直接放到riscv文件夹中)。

串口VCD路径

使用USB转串口工具连接板子和PC,打开串口VCD助手risc_v_tdb.exe,点击扫描,选择对应串口,点击打开即可开始生成VCD波形文件。

串口VCD

其中点击VCD为开始/停止抓取波形。

Wave为查看VCD波形(若log打印..gtkwave.exe Not Found,则由于UART_VCD_TOOL和gtkw不在同级目录下造成的错误)。

Header为VCD波形抓取头文件。

Output为生成的VCD波形文件。

通用串口查看log

当APP_MODE配置为APP_MODE_AUDIO_AUTO时,无法使用risc_v_tdb工具查看log,这个时候需要使用usb转串口工具才能查看。

SDK调用app_set_usb_desc_task接口,初始化uart1,波特率为1000000,默认引脚TX-PE0,RX-PE2。

void app_set_usb_desc_task (int reset)
{
    if (reset)
    {
        .......
        uart1_init(1000000);
        uart_set_tx_dma_config(UART1, DMA5);
        return;
        ......
    }
    ......
}

使用USB转串口工具连接板子和PC,打开usb转串口工具TL_UART_Log_Tool,点击扫描,选择对应串口,点击打开即可查看log。SDK里默认是PE0输出log。

选择串口查看log

OTA工具

(1) OTA简介

OTA功能实现在BootLoader里,芯片上电后,首先执行BootLoader程序,BootLoader程序主要流程如下:

(a) 读取模拟寄存器0x3c的值(设为:areg_v_0x3c,断电后上电,areg_v_0x3c的值为0x00)。

(b) 判断areg_v_0x3c是否等于0x4b。若不等,则校验APP code,校验成功,跳转执行APP code。

(c) 初始化USB、RF等功能模块,运行BootLoader功能。

(2) 支持OTA的2种方式

方式一:RX端有USB接口,TX端没有USB接口。可以Dongle+RX, Device+TX,通过按键或USB HID或者其他接口,发出指令,让TX、RX同时进入BootLoader,RX通过USB连接电脑。上位机可以通过USB和RX通信、升级RX,并可以通过RX给TX升级。

方式二:TX、RX都没有USB口接出来。可以Device+RX, Device+TX,另外做个升级板(可用EVB板),烧录Dongle固件。Dongle上电,TX或RX可通过按键/串口等方式接收指令,跳转到BootLoader(Device),Dongle会自动连接到Device,通过上位机,分别给TX、RX做OTA升级。

(3) Dongle和Device设备切换方法

SDK默认RX作为Dongle,TX作为Device。可通过修改SDK工程设置来改变,如下图所示,RX端作为Dongle,若要让RX作为Device设备,只需要就将下图红框处改为“_proj_boot_device_\output\_proj_boot_device_”即可。Tx端同理。

设备切换方法

(4) OTA操作步骤

(a) 编译_proj_boot_device_和_proj_boot_dongle_工程。

(b) 打开vendor/_proj_low_latency_mic/app_ui_EVB/app_ui.h文件,找到TRANSMIT_DATA_EN宏(没有就创建一个),设为1,分别编译和烧录TX和RX。

(c) 打开2个RISC-V-TDB工具,分别选择对应的ini配置文件,若没有对应的ini文件,可选择一个ini文件后,点击INI按钮,修改TX、RX的prnid和vcdid。TX的prnid=01209218,vcdid=11209218;RX的prnid=01239218,vcdid=11239218。设置好后,重新选择ini文件,标题栏出现RISC-V-TDB -- Found,并出现如下图log,说明设置正确。

设置ini配置文件

(d) 同时按TX0、RX的SW2,开启配对,配对成功后,TX0、TX1、RX的绿灯或白灯会同时亮起,说明连接成功。

(e) 如上图,在RX对应的RISC-V-TDB工具底部,TX、RX若是绿灯亮,输入指令11 05 00升级TX0,若是白灯亮,则输入指令11 05 01,升级TX1,按回车发送命令,1秒后TX、RX分别进入BootLoader。

(f) 打开usb_ota.exe工具,工具标题栏显示 DFU Found,表示Dongle被成功识别。如下图所示,出现“Remote Device Connected”,表示Dongle和Device建立连接成功。

设备连接成功

(g) 通过USB接口给Dongle升级:

a) 点击USB File按钮,在弹出的文件选择框里,选择给Dongle对应的要升级的固件。 b) 点击右侧对应的DL按钮,出现如下log,表示Dongle升级成功。

Dongle升级成功

(h) 通过USB接口给Device(Tx0)升级:

a) 点击Remote按钮,在弹出的文件选择框里,选择给Device对应的要升级的固件。 b) 点击Remote按钮右边对应的DL0按钮,出现如下log,表示Device升级成功。Device升级成功后,Dongle和Device都会重启。

Device升级成功

(i) 要给另一个TX升级(TX1):

a) 烧录固件到TX1,并复位。 b) 重新执行步骤4

EMI工具

EMI测试工具简介:基于USB HID,需要将APP_MODE配置为APP_MODE_DEBUG_HID(编者这里采用该模式,从而结合log进行讲解)或APP_MODE_AUDIO_AUTO,同时开启EMI_TEST_ENABLE宏,以使能EMI测试功能。

Step 1: 打开emi_test_hid_tool工具,显示USB Connected即说明连接成功。

打开emi_test_hid_tool

默认pid为9218,若待测设备的pid有变化,可在pid下拉框中选择对应的pid并点击Set Pid。

Step 2: 配置EMI参数,点击Enter EMI,如图所示在RISC-V TDB工具中会打印EMI测试已进入,并打印对应的配置参数。

打印配置参数

Step 3: 点击Exit EMI即可退出EMI模式(注:修改配置,需要点击Exit EMI先退出EMI模式,再点击Enter EMI重新进入EMI,从而实现配置的修改)。

退出EMI模式

版本号工具

SDK在编译过程中会基于版本号脚本给生成的BIN添加版本号信息,开发人员可搭配版本号工具查看对应BIN的版本号。

(1) tl_version.h介绍

MAKE_VER(major, minor, revision) //用于配置24位的版本号,如0.0.0

TL_VS_SDK_NAME(str) //用于指代SDK,必须引用,否则报错。

TL_VS_VER(name, value) //用于给模块配置版本号,可选,一般用于算法模块,编解码模块版本号的定义。

TL_VS_STRING(name, str) //用于配置字符串,可选。

TL_VS_INT(name, value) //用于配置十进制变量,可选。

TL_VS_HEX(name, value) //用于配置十六进制变量,可选。

TL_VS_BYTE_ARRAY(name, ...) //用于配置数组变量,用逗号分隔,可选。

(2) 应用

任意工程中的main.c文件中添加下列代码:

TL_VS_SDK_NAME("telink_b91m_ll_mic_sdk");

TL_VS_VER(sdk, MAKE_VER(1, 1, 0));

TL_VS_VER(lc3_plus, MAKE_VER(1, 4, 2));

TL_VS_VER(lc3_a, MAKE_VER(1, 4, 2));

TL_VS_VER(plc, MAKE_VER(1, 2, 1));

TL_VS_VER(asrc, MAKE_VER(1, 0, 0));

TL_VS_STRING(Serial_Number, "cky24-q8qrh-x3kmr-c6bcy-t847y ll_mic 2022");

TL_VS_INT(Rx_Num, 3);

TL_VS_HEX(Tx_Type, 15);

TL_VS_BYTE_ARRAY(rf, 1,2,3,4,5,6,7,8,9);

编译后,可在编译选项框显示版本相关信息:

版本相关信息

(3) 示例

打开生成的bin文件如_img_proj_ultra_ll_mic_rx_.bin,hex视图的尾部地址如下,会发现版本相关信息都存储到bin文件的尾部:

其bin文件的Hex显示

打开版本号工具VersionViewerTool.exe,点击OPEN选择bin文件或直接拖拽bin文件到工具框中(出现的两个Info中第一个为boot工程的版本号信息,第二个为对应工程的版本号信息):

版本输出

提示音工具

SDK支持提示音功能,采用ADPCM解码,因此需要通过提示音工具将自定提示音转换为ADPCM格式文件。

用户只需将WAV格式的自定提示音文件放置到tone_tool目录下,执行pcm2adpcm.exe程序,即可在同级目录下生成提示音文件tone_adpcm.bin

SDK简介

概述

麦克风SDK根据功能一般分为收发两端,下面简称为TX、RX。通常TX为信号采样端,信号采集之后经过压缩/编码,通过无线2.4GHz信号进行数据发送,以超低延时SDK为例,对应的工程为_proj_ultra_ll_mic_tx和_proj_ultra_ll_mic_240_tx。RX是通过2.4GHz信号接收TX端发送过来的数据,对数据解码/解压,通过USB Audio或Line-out输出音频,以超低延时SDK为例,对应的工程为_proj_ultra_ll_mic_rx和_proj_ultra_ll_mic_240_rx。

软件组织架构

在IDE中导⼊SDK⼯程后,显⽰的⽂件组织结构如下图所⽰。顶层⽂件夹有10个:algorithm、application、boot、common、drivers、incs、proj_lib、stack、vendor。

⽂件组织结构

algorithm: 算法相关程序,aes_ccm,crypto,ecc。

application: 提供一些通用的应用处理程序,如print、keyboard等。

boot: 提供芯片的software bootloader,即MCU上电启动或deepsleep唤醒后的汇编处理过程,为后面C语言程序的运行搭建好环境。

codec: 编解码相关算法,例如filter、resample等。

common: 提供一些通用的跨平台的处理函数,如内存处理函数、字符串处理函数等。

drivers: 提供与MCU紧密相关的硬件设置和外设驱动程序,如clock、flash、I2C、USB、GPIO、UART等。

incs: 公共头文件目录。

proj_lib: 存放SDK运行所必需的库文件(如 libB91_driver_i2.a)。BLE协议栈、RF驱动、PM驱动等文件,被封装在库文件里,用户无法看到源文件。

stack: 存放BLE/BT协议栈相关的头文件。源文件被编译到库文件里面,对于用户是不可见的。

vendor: 用于存放用户应用层代码。

boot: 存放cstartup_9518.S文件。 cstartup_9518.S: 完成retention_reset、aes_data、retention_data、ramcode、data、sbss、bss、stack的初始化和搬移拷贝,flash初始化,中断向量的入口,一些需要写成汇编的函数。

bootlink: 是内存布局文件,里面描述了不同代码section,放在Flash、I-SRAM、D-SRAM里的情况。

boot.link: _proj_boot_xx项目对应的bootlink。

boot-36k.link: vendor下的项目对应bootlink差异是SRAM和对应flash的地址偏移了0x9000(36k)。

main.c

包括 main 函数⼊⼝,系统初始化的相关函数,以及⽆限循环 while(1) 的写法,建议不要对此⽂件进⾏任何修改,直接使⽤固有写法(已省略部分不重要的代码)。

_attribute_ram_code_ int main(void)
{
    sys_init(DCDC_1P4_LDO_1P8, VBAT_MAX_VALUE_GREATER_THAN_3V6);
    user_read_flash_value_calib();
    clock_init(PLL_CLK_192M, PAD_PLL_DIV, PLL_DIV2_TO_CCLK, CCLK_DIV2_TO_HCLK, HCLK_DIV2_TO_PCLK, PLL_DIV4_TO_MSPI_CLK);

#if WATCHDOG_ENABLE
    wd_set_interval_ms(15*1000);
    wd_start();
#endif

    clock_cal_24m_rc();

    clock_32k_init(CLK_32K_RC);
    clock_cal_32k_rc(); //6.68ms


    user_init();
    while(1)
    {

        main_loop();
#if WATCHDOG_ENABLE
        wd_clear_cnt();
#endif
    }
    return 0;
}

配置文件

(1) app_config.h

应用功能配置⽂件,⽤于对整个系统的应用功能参数进⾏配置,包括2.4GHz相关功能、USB相关功能、音频输入功能、算法相关功能的配置。

(2) app_audio_config.h

音频功能配置文件,用于对音频功能参数进行配置,包括编码器、帧长、增益等相关功能的配置。

(3) app_ui.h

UI功能配置文件,用于对UI功能参数进行配置,包括key、led、电量检测、提示音等相关功能的配置。

后⾯介绍各个模块时会对配置文件中的各个参数的含义进⾏详细说明。

BootLoader介绍

包括TX和RX的BootLoader工程,分别是_proj_boot_device_和_proj_boot_dongle_,上电后会检测APP固件完整性并跳转到APP执行,在APP固件里也可以通过调用函数进入BootLoader来进行OTA操作。OTA的操作方法可查看OTA工具小节。

BootLoader介绍

SDK概述

Ultra Low Latency MIC支持2对1/1对1两种形态,具有延时低,抗干扰强等特点,有效使用距离和产品形态、传输次数有关,需根据具体产品实际测试。典型的2对1音频性能如下:

模拟输入输出:延迟低至8ms(SBC编解码)和12ms(LC3+编解码),频响20 ~ 20kHz,THD+N低至0.5%,SNR达到88dB。

IIS输入输出:延时低至7ms(SBC编解码)和9ms(LC3+编解码),频响20 ~ 20kHz,THD+N低至0.04%,SNR达到96dB。

SDK给用户提供了两个工程示例,分别是:_proj_ultra_ll_mic_和_proj_ultra_ll_mic_24bits_,如下图:

SDK工程示例

_proj_ultra_ll_mic_和_proj_ultra_ll_mic_24bits_两个工程示例不仅存在16bits和24bits的区别,一些算法的消耗以及功能也存在一些差异,具体如下:

项目 _proj_ultra_ll_mic_ _proj_ultra_ll_mic_24bits_
LC3+编码耗时 852微秒 1037微秒
LC3+解码耗时 963微秒 1110微秒
USB功能 16bits上下行都支持 24bits只支持上行

16bits AP指标如下:

16bits SDK AP

24bits AP指标如下(其中蓝字dual_4db为开启MIC_DUAL_INPUT_EN功能的指标):

24bits SDK AP

SDK功能介绍

TX端具体的功能如下:

(1) 支持的应用相关功能:

a) USB模块:USB HID、USB Debug;

b) 看门狗模块;

c) 算法模块:包括EQ、降噪算法、重采样算法、DRC算法;

d) OTA模块;

e) EMI模块:支持EMI测试指令;

f) 2.4GHz模块:RF相关:2.4GHz Tx、发射功率自适应、跳频;无线相关:手动配对、回连、自动配对、配对ID过滤。

(2) 支持的音频相关功能:

a) 编码:SBC、LC3+;

b) 音频输入:AMIC、DMIC/AMIC/Line-in或I2S。音频输出:I2S或Line-out

(3) 支持的UI相关功能:

a) KEY:短按、长按、双击;

b) LED;

c) TTONE:提示音;

d) 电量检测。

RX端UI功能同TX端,其他功能如下:

(1) 支持的应用相关功能:

a) USB模块:USB Audio上下行、USB HID、USB Debug;

b) 看门狗模块;

c) OTA模块:OTA升级;

d) EMI模块:支持EMI定频测试;

e) 2.4GHz模块:RF相关:2.4GHz Rx、跳频、收包纠错;无线相关:手动配对、回连、自动配对、一对一、一对二、配对ID过滤。

(2) 支持的音频相关功能:

a) 编码:SBC、LC3+;

b) 音频输入:DMIC/AMIC/Line-in或I2S。音频输出:I2S或Lineout、USB Audio。

关键宏介绍

SDK很多功能都是通过宏开关控制。下面简单介绍app_config.h、app_audio_config.h、app_ui.h。

(1) 应用相关配置

MIC_TX_NUM:

默认值:2;

作用说明:控制Tx数量;

备注:默认2对1模式,可修改为1对1模式,时隙变短,Tx重发次数也会相应增加。

AUTO_PAIR_EN:

默认值:0;

作用说明:自动配对。

TRANSMIT_DATA_EN:

默认值:0;

作用说明:传输命令;

备注:传输bootloader命令。

ADAPTIVE_RF_POWER_EN:

默认值:0;

作用说明:发射功率自适应调整功能。

RF_ERROR_CORRECTION_EN:

默认值:0;

作用说明:包纠错功能。

APP_MODE:

默认值:APP_MODE_DEBUG_ONLY;

作用说明:USB模式选取;

备注:默认PRINTER调试模式,可调整为USB HID模式以及USB Audio。

USB_SPEAKER_ENABLE:

默认值:1;

作用说明:USB Audio下行使能;

备注:默认开启,可单独关闭。

USB_MIC_ENABLE:

默认值:1;

作用说明:USB Audio上行使能;

备注:默认开启,可单独关闭。

WATCHDOG_ENABLE:

默认值:1;

作用说明:看门狗使能。

EMI_TEST_ENABLE:

默认值:0;

作用说明:EMI功能使能。

APP_NS_ENABLE:

默认值:0;

作用说明:降噪功能使能。

APP_WB_NS_ENABLE:

默认值:FS_16K_NS_EN;

作用说明:降噪功能采用默认模式,可更改为LD_16K_NS_EN长距离降噪模式。

APP_NS_MODE:

默认值:1;

作用说明:降噪功能采用webrtc算法。

APP_EQ_ENABLE:

默认值:0;

作用说明:EQ功能使能。

ADC_DRC_EN:

默认值:0;

作用说明:DRC功能使能。

PAIR_VPID_FILTER_EN:

默认值:1;

作用说明:配对ID过滤功能使能,配对过程中,可增加厂商ID和产品ID,防止不同ID的产品进行配对。

(2) UI相关配置

LED_ENABLE:

默认值:1;

作用说明:LED使能;

备注:分别定义了LED_BLUE、LED_GREEN、LED_WHITE、LED_RED。

KEY_ENABLE:

默认值:1;

作用说明:KEY使能。

TTONE_EN:

默认值:1;

作用说明:提示音使能;

备注:播放提示音,需要烧录提示音bin文件。

ENABLE_BATT_CHECK:

默认值:0;

作用说明:电量检测使能;

备注:默认使用PB3引脚,可在bat_check_init接口初始化处进行修改。

(3) 音频相关配置

MIC_DUAL_INPUT_EN:

默认值:0;

作用说明:开启后,会将左右mic的PCM数据进行融合处理,可获得更好的动态输入、SNR、底噪等参数

备注:需要在硬件上将左mic的输入并到右mic,实现两路输入。

CODEC_DAC_MONO_MODE:

默认值:1;

作用说明:1:单声道,0:立体声;

备注:Tx端的Lineout用于监听输入音频,默认开启,可关闭。

CODEC_ALGORITHM_SEL:

默认值:CODEC_ALGORITHM_LC3A;

作用说明:编解码选取;

备注:默认选择LC3+编码,可选取SBC编码。

LC3A_BIT_RATE:

默认值:96000(72000);

作用说明:LC3+比特率;

备注:LC3+在120 Samples采用96k比特率(proj_ultra_ll_mic_240工程为72k)。

MIC_SAMPLING_RATE:

默认值:48000;

作用说明:mic采样率;

备注:默认48k。

MIC_SAMPLES_PER_PACKET:

默认值:120 (proj_ultra_ll_mic_240工程为240);

作用说明:mic采样sample;

备注:默认采样2.5ms(5ms),因此mic一个采样Samples = 2.5(5)*48000/1000= 120(240)。

MIC_PACKET_NUM:

默认值:8(14);

作用说明:Tx发送音频数据包次数;

备注:1对2情况下(1对1另行讨论),Tx端每包音频数据重发8/2 = 4次,具体数值受时隙及编码影响。

AUDIO_INTERFACE:

默认值:AUDIO_I2S_CODEC;

作用说明:CODEC音频源选取;

备注:默认为内部codec输入输出,若改为AUDIO_I2S_GPIO,则切换为I2S输入输出。

AUDIO_IN_MODE:

默认值:LINE_INPUT;

作用说明:内部codec输入源选取;

备注:当AUDIO_INTERFACE配置为AUDIO_I2S_CODEC,可通过该宏进行内部codec输入源选取,支持AMIC、DMIC、Linein。

AUDIO_INTERFACE_ROLE:

默认值:AUDIO_I2S_AS_MASTER;

作用说明:外部引脚输入源主从机选取;

备注:当AUDIO_INTERFACE配置为AUDIO_I2S_GPIO,可通过该宏配置I2S角色为主机/从机。

MIC_AB_EN:

默认值:0;

作用说明:A麦、B麦功能使能。

MIX_ADC_AUDIO_EN:

默认值:0;

作用说明:Rx端混合本地mic功能使能;

备注:Rx端。

CODEC_IN_A_GAIN:

默认值:CODEC_IN_A_GAIN_0_DB;

作用说明:模拟输入增益;

备注:用于配置Tx端和Rx端模拟输入增益。

CODEC_IN_D_GAIN:

默认值:CODEC_IN_D_GAIN_0_DB;

作用说明:数字输入增益;

备注:用于配置Tx端和Rx端数字输入增益。

CODEC_OUT_A_GAIN:

默认值:CODEC_OUT_A_GAIN_0_DB;

作用说明:模拟输出增益;

备注:用于配置Tx端和Rx端模拟输出增益。

CODEC_OUT_D_GAIN:

默认值:CODEC_OUT_D_GAIN_0_DB;

作用说明:数字输出增益;

备注:用于配置Tx端和Rx端数字输出增益。

音频相关

采样音频基本概念

采样: 对模拟信号隔一定的时间间隔取一个点。

量化: 给纵坐标加刻度,根据近似取整数值,使采样得到的点的值都是整数。

编码: 对量化取得的整数值按二进制进行编码。

数字信号: 把编码得到的0和1的序列变为高低电平的信号。

脉冲编码调制(PulseCodeModulation),简称PCM。由上面的模数转换可知,PCM格式文件存储的内容实际上就是编码得到的序列

音频采样

采样频率: 所谓采样就是在时间轴上对模拟信号进行数字化,根据奈奎斯特定理(采样定理),按照比声音最高频率2倍以上的频率进行采样(AD转换)。频率在20Hz ~ 20kHz之间的声音是可以被人耳识别的。所以采样频率一般为40kHz左右,常用的音乐为44.1kHz(44100次/s采样)、48kHz等,电话的采样率为8K。

采样位数: 每个采样点能够表示的数据范围。采样位数通常有8bits或16bits两种,采样位数越大,所能记录声音的变化度就越细腻,相应的数据量就越大。16bit是最常见的采样精度。

声道数: 声道数是指能支持不同发声的音响的个数,常用的声道数有单声道,立体声(左声道和右声道)。

PCM格式

PCM是采样量化后的未压缩音频数据,由模拟信号经过采样、量化、编码转换成的标准的数字音频数据。如果是单声道的音频文件,采样数据按时间的先后顺序依次存入(如果是双声道的话就按照LRLR的方式存储),存储的时候还和机器的大小端有关。小端模式如下图所示。

PCM小端模式

编码和解码

支持的编解码:SBC、LC3Plus、ADPCM。

其中SBC、LC3Plus用于空中传输的音频的压缩和解压,ADPCM解码速度快,用于播放提示音。

MCU/SDK相关功能

Flash地址空间分配

Flash存储信息以一个sector的大小(4K byte)为基本的单位,因为Flash的擦除是以sector为单位的(擦除函数为flash_erase_sector),理论上同一种类的信息需要存储在一个sector里面,不同种类的信息需要在不同的sector(防止擦除信息时将其他类的信息误擦除)。所以建议user在使用Flash存储定制信息时遵循“不同类信息放在不同sector”的原则。

芯片默认支持1MB的Flash作为程序存储空间,SDK在文件boot.link的最后定义“FLASH_SIZE”为1MB,并且对“BIN_SIZE” <= “FLASH_SIZE” 做了限制的判断,如果用户使用大于1MB的Flash,描述需要做对应的修改。

Flash按照功能分为Locked区和Unlocked区,分别存储不需要更改的数据以及可以随时更改的数据。如配对信息等会更改的数据存储在Unlocked区域,而固件、版本信息、USB_ID、提示音、DRC参数、EQ参数等不会更改的数据存储在Locked区域。

(1) 0xFF000 ~ 0xFFFFF这个sector存储MAC地址,实际上MAC address 6个bytes存储在0xFF000 ~ 0xFF005,高byte的地址存放在0xFF005,低byte地址存放在0xFF000。比如Flash 0xFF000到0xFF005的内容依次为0x11 0x22 0x33 0x44 0x55 0x66,那么MAC address为0x665544332211。

泰凌的量产治具系统会将实际产品的MAC地址烧写到0xFF000这个地址,和SDK相对应。如果user需要修改这个地址,请确保治具系统烧写的地址也作了相应的修改。SDK中在user_init函数里会从Flash的CFG_ADR_MAC_1M_FLASH地址处读取MAC地址,这个宏在/vendor/common/blt_common.h里面修改即可。

#ifndef             CFG_ADR_MAC_1M_FLASH
#define             CFG_ADR_MAC_1M_FLASH              0xFF000
#endif

(2) 0xEA000和0xEB000两个sector被2.4GHz协议栈占用,用来存储配对信息。

(3) 0x00000 ~ 0x3FFFF 256KB空间默认作为程序空间。0x00000 ~ 0x3FFFF共256KB为Firmware存储空间,0x40000 ~ 0x7FFFF 256KB为OTA更新时存储新Firmware的空间,即支持的Firmware空间理论上为 256KB,由于某些特殊原因,0x40000 ~ 0x7FFFF高地址的空间实际只能使用254KB,最后4KB不能使用。

注意:

实际上所有高地址空间最后的4KB都不能使用。

(4) 0xDF000为量产的MAC地址,MAC地址默认使用该地址,如果该地址未存储数据,才会读取0xFF000所在地址。

(5) 0xDC000为EQ数据存储地址。

(6) 0xDA000为USB PID VID存储地址。

(7) 0xD9000为DRC算法数据存储地址。

(8) 0x80000为提示音存储地址。

(9) 0xFFFF0为USB_ID存储地址。

(10) 剩余的Flash空间全部作为user的数据存储空间。

用户Flash管理功能

当用户需开发频繁读写的Flash相关功能,如音量、关机时间等(SDK配对信息),建议采用sdk提供的Flash管理功能,该功能可在指定地址开辟一段专门用于用户自定义Flash内容管理的区域,能实现以下优点:

(1) Flash管理:在用户指定区域地址,从头写入,检测到Flash尾部时,会擦除该区域,继续从头写入,循环往复,增加Flash使用寿命以及利用率,减少Flash擦除动作。

(2) 相同数据不写入:调用Flash管理模块的写Flash接口时,会对Flash中已写入内容进行比对,相同则不写入。

(3) 数据校验:写入数据前会对数据进行校验,校验不通过则不写入,防止写入错误数据。

(4) 数据备份功能:当FLASH_STORAGE_BACKUP_EN开启时(默认关闭),会将指定Flash的一半区域用于备份,防止Flash擦除后,准备写入时,掉电导致的数据丢失问题。

Flash管理功能主要函数介绍

flash_storage_init (flash_storage_t *fs, u32 addr, u8 *item_ptr, u16 item_size):flash读写功能的初始化函数。除了初始化flash读写功能之外,还会将flash的数据读到自定义的数据结构体中。第一个参数是与这个功能相关的一个结构体地址,第二参数要读取和写入的flash地址,第三个参数是自定义的数据结构体地址,第四个参数是自定义的数据结构体的长度。

flash_storage_write (flash_storage_t *fs):写flash的函数接口。调用后即可将数据从自定义结构体写入flash中。参数是与这个功能相关的一个结构体地址。

flash_storage_read (flash_storage_t *fs):读flash的函数接口。由于flash_storage_init调用时会调用一次这个函数,即初始化的时候就会将flash的数据读到自定义的数据结构体中。所以一般不用调用这个函数。

Flash管理功能的操作流程

上面是对flash读写功能的相关函数介绍,接下来以SDK配对信息为例介绍如何操作。

(1) 首先需要定义一个配对信息flash_storage_t结构体(全局变量)fs_pair_info。接着,定义配对信息pair_info_s结构体(分TX和RX)pair_info,如下:

typedef struct {
    u32 access_code;
#if APP_BUILD == APP_MIC_RX
    u32 slave_ids[REMOTE_DEVICE_NUM_MAX];                     //<! for RX
    u8  remote_mac[REMOTE_DEVICE_NUM_MAX][LOCAL_MAC_LEN];     //<! for RX
    u8  lp_dev_id;                                            //<! for RX, last paired dev id.
#else
    u8  remote_mac[LOCAL_MAC_LEN];                            // for TX
    u8  dev_id;
#endif
    u8  connect_chn;
    u8  valid;
} pair_info_s;

pair_info_s pair_info;
flash_storage_t fs_pair_info;

(2) 在app_user_data_init接口中调用flash_storage_init(四字节对齐)进行fs_pair_info初始化及pair_info绑定,如下所示:

void app_user_data_init(void)
{
    ......
    flash_storage_init(&fs_pair_info, FLASH_SYS_USER_BASE, (u8 *)&pair_info, sizeof(pair_info));
    if (!pair_info.valid) {
        tmemset(&pair_info, 0, sizeof(pair_info));
    }
    ......
}

这一步会将Flash中FLASH_SYS_USER_BASE对应地址存储的配对信息读取到pair_info中(首次初始化时,Flash中内容全为FF,由于配对信息pair_info_s结构体中的valid值不合法,因此将pair_info中的所有值置0)。

(3) 当TX和RX完成配对流程后,不管是TX还是RX,pair_info都会相应的更新,最终在app_user_data_update接口中调用flash_storage_write即可以把更新初值后的pair_info的数据加上flash_storage_header_s头后,再写入到Flash中,如下所示:

typedef struct {
    u16 version;                                         /**< Version of data. */
    u16 length;                                          /**< Length of data, exclude header(version, length, verify). */
    u32 verify;                                          /**< Verify of data. */
} flash_storage_header_s;

void app_user_data_update(void)  // user data information changed, call this func write to flash
{
    pair_info.valid = PAIR_INFO_VALID_BYTE;
    flash_storage_write(&fs_pair_info);     /* push buffer into flash */
}

(4) 以TX0(mac地址:22 11 22 33 44 55)、TX1(mac地址:11 11 22 33 44 55)和RX(mac地址:33 11 22 33 44 55)的配对流程为例:

TX0和RX进行首次配对后,通过risc-v工具分别读取0xEB000地址内容,如下所示:

TX0和RX配对信息

TX0配对信息中:

  • 0~1字节:flash_storage_header_s中的version,指代数据版本号,目前未定义,默认为0x0000。
  • 2~3字节:flash_storage_header_s中的length,指代TX0存储的配对信息长度为0x0010,为16字节。
  • 4~7字节:flash_storage_header_s中的4字节verify,指代整个存储信息的校验值为0x000003ab。
  • 8~11字节:access_code值为0x4f4e2cfb。
  • 12~17字节:remote_mac,指代配对的RX设备对应的mac地址为33 11 22 33 44 55。
  • 18字节:dev_id值为0x00,指代TX0当前设备id。
  • 19字节:connect_chn值为0x08,指代配对通道对应的序列值。
  • 20字节:valid值为0xad,合法。
  • 21~23字节:用于四字节对齐。

RX配对信息中:

  • 0~1字节:flash_storage_header_s中的version,指代数据版本号,目前未定义,默认为0x0000。
  • 2~3字节:flash_storage_header_s中的length,指代Rx存储的配对信息长度为0x001c,为28字节。
  • 4~7字节:flash_storage_header_s中的4字节verify,指代整个存储信息的校验值为0x00000466。
  • 8~11字节:access_code,值为0x4f4e2cfb。
  • 12~15字节:TX0的slave_ids,指代配对的TX0设备对应的slave id为0x44662200。
  • 16~19字节:TX1的slave_ids,指代配对的TX1设备对应的slave id,由于未配对,因此值为0x00000000。
  • 20~25字节:TX0的remote_mac,指代配对的TX0设备对应的mac地址为22 11 22 33 44 55。
  • 26~31字节:TX1的remote_mac,指代配对的TX1设备对应的mac地址,由于未配对,因此值为0x00000000。
  • 32字节:lp_dev_id,指代上一次配对的设备id为0x00(这里存储的是TX0的设备id)。。
  • 33字节:connect_chn值为0x08,指代配对通道对应的序列值。
  • 34字节:valid值为0xad,合法。
  • 35字节:用于四字节对齐。

在TX0和RX已配对的基础上,进行TX1和RX的配对流程,RX配对信息pair_info由于增加了Tx1相关信息,因此会进行Flash中配对信息的更新,即在上一个TX0对应的配对信息基础上,往下一个地址进行偏移,通过risc-v工具分别读取0xEB000地址内容,如下所示:

TX1和RX配对信息

TX1配对信息中:

  • 0~1字节:version,默认为0x0000。
  • 2~3字节:length,指代TX1存储的配对信息长度为0x0010,为16字节。
  • 4~7字节:verify,为0x000003ac。
  • 8~11字节:access_code值为0x4f4e2cfb。
  • 12~17字节:remote_mac,指代配对的RX设备对应的mac地址为33 11 22 33 44 55。
  • 18字节:dev_id值为0x01,指代TX1当前设备id。
  • 19字节:connect_chn值为0x08,指代配对通道对应的序列值。
  • 20字节:valid值为0xad,合法。
  • 21~23字节:用于四字节对齐。

RX配对信息中:

  • 36~37字节:version,默认为0x0000。
  • 38~39字节:length,指代Rx存储的配对信息长度为0x001c,为28字节。
  • 40~43字节:verify,指代整个存储信息的校验值为0x00000665。
  • 44~47字节:access_code,值为0x4f4e2cfb。
  • 48~51字节:TX0的slave_ids,指代配对的TX0设备对应的slave id为0x44662200。
  • 52~55字节:TX1的slave_ids,指代配对的TX1设备对应的slave id为0x44552233
  • 56~61字节:TX0的remote_mac,指代配对的TX0设备对应的mac地址为22 11 22 33 44 55。
  • 62~67字节:TX1的remote_mac,指代配对的TX1设备对应的mac地址为11 11 22 33 44 55。
  • 68字节:lp_dev_id,指代上一次配对的设备id为0x01(这里存储的是TX1的设备id)。
  • 69字节:connect_chn值为0x08,指代配对通道对应的序列值。
  • 70字节:valid值为0xad,合法。
  • 71字节:用于四字节对齐。

注意:

flash_storage_t结构体fs_pair_info经过初始化后就与配对信息结构体pair_info绑定。对数据进行操作的是pair_info,且仅在pair_info变化后(对比Flash中存储的信息),调用flash_storage_write接口时,flash_storage_t结构体fs_pair_info才会进行地址偏移,并最终将更新后的pair_info写入Flash中偏移后的地址。

无线音频传输

在SDK应用中,采用的LE 2M Phy,该物理层传输速率为2M bit/s,即传输一个byte时间为4us。理解LinkLayer用户需要对telink的RF driver有一定了解,由于RF外设内容较多,本章只概述sdk中用到的RF接口函数,其他的用户可通过telink的wiki参考driver handbook。

音频流传输过程

整个音频数据流为广播式机制,TX会不断发送音频包,RX收到后无需回ack,不同配置不同模式分别会采用2 / 2.5 / 5ms为一帧(frame)数据,在每帧数据里再划分N个slot间隔进行收发包,不同工程示例配置N值不同,如_proj_ultra_ll_mic_工程采用LC3+编码配置的N为9(slot 0 ~ slot 8)。编解码时,RX会在固定slot对前面收到音频数据包进行解码,TX在固定slot对当前的音频数据包进行编码,这样可以确保整个音频数据流的传输延迟不变。

(1) 2.5ms帧长,LC3+,2对1模式

1 frame为2.5ms,在每frame中再划分9个slot间隔进行收发包。

其中slot 0为同步通道,用于RX传输TX跳频表、配对/回连、其他控制命令等信息。

slot 1 ~ slot 4为TX0发包slot。

slot 5 ~ slot 8 为TX1发包slot。

2对1模式

如图所示, 在1个frame中,TX0在slot 0之前编码完成,然后从slot 1开始到slot 4,连续发送4次编码后的音频包,RX连续收包4次,接着在slot 5进行解码。

而TX1在slot 4之前编码完成,然后从slot 5开始到slot 8,连续发送4次编码后的音频包,RX连续收包4次,接着在下一frame的slot 0进行解码。

(2) 2.5ms帧长,LC3+,1对1模式

1 frame为2.5ms,在每frame中再划分9个slot间隔进行收发包。

其中slot 0同2对1模式。

slot 1 ~ slot 8为TX发包slot。

1对1模式

如图所示, 在1个frame中,TX在slot 0之前编码完成,然后从slot 1开始到slot 8,连续发送8次编码后的音频包,RX连续收包8次,接着在下一frame的slot 0进行解码。

注意:

相比2对1模式,在1对1模式下,由于只有1个发射端,TX重传次数多了4次,但解码位置调整到2对1模式中TX1的解码位置,因此传输距离变远的同时,延时也增加了约1.25ms。

RF主要函数说明

async_init: 初始化system timer、初始化RF、跳频、时序参数和各个中断;

async_stx_start: RF发包函数;

async_srx_start: RF收包函数;

async_irq_rf: RF中断处理函数。

扫描连接流程

扫描连接流程

配对流程

配对流程

低功耗管理

MCU正常执行程序时处于working mode,此时工作电流在3 ~ 7mA之间。如果需要省功耗需要进入低功耗模式。

低功耗模式(low power mode)又称sleep mode,包括3种:suspend mode、deepsleep mode和deepsleep retention mode。

deepsleep mode

SDK目前支持deepsleep mode,此时程序停止运行,MCU绝大部分的硬件模块都断电,PM硬件模块维持工作。在deepsleep mode下IC电流小于1uA。如果内置flash的standby电流处于1uA左右,可能导致测量到的deepsleep电流为1 ~ 2uA。Deepsleep mode wake_up时,MCU将重新启动,类似于上电的效果,程序会重新开始进行初始化。

Deepsleep mode下,除了analog register上有少数几个register能保存状态,其他所有Sram、digital register、analog register全部掉电丢失状态。

PM模块特殊的不掉电analog regsiter,drivers/B91/pm.h文件中的DEEP_ANA_REG,如下code所示:

#define PM_ANA_REG_POWER_ON_CLR_BUF0    0x39 // initial value 0x00. [Bit0][Bit1] is already occupied. The customer cannot change!
#define PM_ANA_REG_POWER_ON_CLR_BUF1    0x3a // initial value 0x00
#define PM_ANA_REG_POWER_ON_CLR_BUF2    0x3b // initial value 0x00
#define PM_ANA_REG_POWER_ON_CLR_BUF3    0x3c // initial value 0x00
#define PM_ANA_REG_POWER_ON_CLR_BUF4    0x3d // initial value 0x00
#define PM_ANA_REG_POWER_ON_CLR_BUF5    0x3e // initial value 0x00
#define PM_ANA_REG_POWER_ON_CLR_BUF6    0x3f // initial value 0x0f
#define PM_ANA_REG_POWER_ON_CLR_BUF7    0x38 //initial value =0xff

0x3f以上寄存器只有在掉电时才会恢复初始值,需要注意的是,客户不允许使用ana_39,该模拟寄存器留给底层stack使用,如果应用层代码有用到该寄存器,需要修改为ana_3a ~ ana_3f。因为不掉电模拟寄存器数量比较少,建议客户使用其每一个bit指示不同的状态位信息。

0x38寄存器,在硬件/软件reset、掉电和watchdog三种情况下会被初始化,需要注意的是bit0位已经被stack使用,使用者在使用时需要避免该bit位。

使用者可以在sys_init(power_mode_e power_mode)后使用API pm_get_mcu_status(void)的返回值来判断cpu是从哪种状态返回,返回值如下:

typedef enum{
MCU_STATUS_POWER_ON = BIT(0),
MCU_STATUS_REBOOT_BACK = BIT(2), //the user will not see the reboot status.
MCU_STATUS_DEEPRET_BACK = BIT(3),
MCU_STATUS_DEEP_BACK = BIT(4),
MCU_STATUS_REBOOT_DEEP_BACK = BIT(5), //reboot + deep
}pm_mcu_status;

低功耗唤醒源

suspend/deepsleep/deepsleep retention都可以被GPIO PAD和timer唤醒。本SDK只关注2种唤醒源,如下所示(注意code中PM_TIM_RECOVER_START和PM_TIM_RECOVER_END两个定义不是唤醒源):

typedef enum {
Telink B91 BLE Single Connection SDK Developer Handbook
AN-20111001-C2 217 Ver.0.2.0
PM_WAKEUP_PAD = BIT(3),
PM_WAKEUP_TIMER = BIT(5),
}SleepWakeupSrc_TypeDef;

低功耗唤醒源

如上图所示,deepsleep mode在硬件上有2个唤醒源:TIMER、GPIO PAD。

唤醒源PM_WAKEUP_TIMER来自硬件32k timer(32k RC timer or 32k Crystal timer)。32k timer在SDK中已经被正确初始化,user在使用时不需要任何配置,只需要在cpu_sleep_wakeup()中设置该唤醒源即可。

唤醒源PM_WAKEUP_PAD来自GPIO模块,除MSPI 4个管脚外所有的GPIO(PAx/PBx/PCx/PDx/PEx)的高低电平都具有唤醒功能。

配置GPIO PAD唤醒sleep mode的API:

typedef enum{
    WAKEUP_LEVEL_LOW        = 0,
    WAKEUP_LEVEL_HIGH       = 1,
}pm_gpio_wakeup_level_e;
void pm_set_gpio_wakeup (gpio_pin_e pin, pm_gpio_wakeup_Level_e pol, int en);
#define cpu_set_gpio_wakeup pm_set_gpio_wakeup

pin为GPIO定义。

pol为唤醒极性定义:Level_High表示高电平唤醒,Level_Low表示低电平唤醒。

en: 1表示enable,0表示disable。

举例说明:

cpu_set_gpio_wakeup (GPIO_PC2, Level_High, 1); //GPIO_PC2 PAD 唤醒打开, 高电平唤醒
cpu_set_gpio_wakeup (GPIO_PC2, Level_High, 0); //GPIO_PC2 PAD 唤醒关闭
cpu_set_gpio_wakeup (GPIO_PB5, Level_Low, 1);  //GPIO_PB5 PAD 唤醒打开, 低电平唤醒
cpu_set_gpio_wakeup (GPIO_PB5, Level_Low, 0);  //GPIO_PB5 PAD 唤醒关闭

而在EVB的ui定义中,在app_key.c文件的app_key_scan_init接口中,调用pm_set_gpio_wakeup,将所有的按键对应pin脚都配置为唤醒打开, 高电平唤醒:

void app_key_scan_init(void)
{
    ......
    for(int i=0;i<MATRIX_ROW_COUNT;i++) {
        gpio_pin_e pin = matrix_row_pins[i];
        gpio_set_input_en(pin, 1);
        gpio_set_output_en(pin, 0);
        gpio_setup_up_down_resistor(pin, PM_PIN_PULLDOWN_100K);
    pm_set_gpio_wakeup(pin, WAKEUP_LEVEL_HIGH, 1);
    }
    ......
}

低功耗模式的进入和唤醒

设置MCU进入睡眠和唤醒的API为:

typedef int (*cpu_pm_handler_t)(SleepMode_TypeDef sleep_mode, SleepWakeupSrc_TypeDef wakeup_src, unsigned int
wakeup_tick);
cpu_pm_handler_t cpu_sleep_wakeup;

第一个参数sleep_mode:设置sleep mode,有以下4个选择,分别表示suspend mode、deepsleep mode、deepsleep retention 32K Sram、 deepsleep retention 64K Sram。

typedef enum {
//available mode for customer
SUSPEND_MODE = 0x00,
DEEPSLEEP_MODE = 0x30,
DEEPSLEEP_MODE_RET_SRAM_LOW32K = 0x21, //for boot from sram
DEEPSLEEP_MODE_RET_SRAM_LOW64K = 0x03, //for boot from sram
DEEPSLEEP_MODE_RET_SRAM = 0x21,
//not available mode
DEEPSLEEP_RETENTION_FLAG = 0x0F,
}SleepMode_TypeDef;

第二个参数wakeup_src:设置当前的suspend/deepsleep的唤醒源,参数只能是PM_WAKEUP_PAD、PM_WAKEUP_TIMER中的一个或者多个。如果wakeup_src为0,那么进入低功耗sleep mode后,无法被唤醒。

第三个参数wakeup_tick:当wakeup_src中设置了PM_WAKEUP_TIMER时,需要设置wakeup_tick来决定timer在何时将MCU唤醒。如果没有设置PM_WAKEUP_TIMER唤醒,该参数无意义。wakeup_tick的值是一个绝对值,按照本文档前面介绍的System Timer tick来设置,当System Timer tick的值达到这个设定的wakeup_tick后,sleep mode被唤醒。wakeup_tick的值需要根据当前的System Timer tick的值,加上由需要睡眠的时间换算成的绝对时间,才可以有效地控制睡眠时间。如果没有考虑当前的System Timer tick,直接对wakeup_tick进行设置,唤醒的时间点就无法控制。由于wakeup_tick是绝对时间,必须在32bit的System Timer tick能表示的范围之内,所以这个API能表示的最大睡眠时间是有限的。目前的设计是最大睡眠时间为32bit能表示的最大System Timer tick对应时间的7/8。System Timer tick最大能表示大概268s,那么最长sleep时间为268*7/8=234s,即下面delta_Tick不能超过234s,若需要更长的睡眠时间,user可以调用长睡眠函数。

cpu_sleep_wakeup(SUSPEND_MODE, PM_WAKEUP_TIMER, clock_time() + delta_tick);

返回值为当前sleep mode的唤醒源的集合,该返回值各bit对应表示的唤醒源为:

typedef enum {
WAKEUP_STATUS_COMPARATOR = BIT(0),
WAKEUP_STATUS_TIMER = BIT(1),
WAKEUP_STATUS_CORE = BIT(2),
WAKEUP_STATUS_PAD = BIT(3),
WAKEUP_STATUS_MDEC = BIT(4),
STATUS_GPIO_ERR_NO_ENTER_PM = BIT(7),
STATUS_ENTER_SUSPEND = BIT(30),
}pm_wakeup_status_e;

a) WAKEUP_STATUS_TIMER这个bit为1,说明当前sleep mode是被Timer唤醒。

b) WAKEUP_STATUS_PAD这个bit为1,说明当前sleep mode是被GPIO PAD唤醒。

c) WAKEUP_STATUS_TIMER和WAKEUP_STATUS_PAD同时为1时,表示Timer和GPIO PAD两个唤醒源同时生效了。

d) STATUS_GPIO_ERR_NO_ENTER_PM是一个比较特殊的状态,表示当前发生了GPIO唤醒错误:比如当设置了某个GPIO PAD高电平唤醒,而在这个GPIO为高电平的时候尝试调用cpu_sleep_wakeup进入suspend,且设置了PM_WAKEUP_PAD唤醒源。此时会出现无法进入suspend,MCU立刻退出cpu_sleep_wakeup函数,返回值STATUS_GPIO_ERR_NO_ENTER_PM。

一般采用如下的形式来控制睡眠时间:

cpu_sleep_wakeup (SUSPEND_MODE , PM_WAKEUP_TIMER, clock_time() + delta_Tick);

delta_Tick是一个相对的时间(比如100* CLOCK_16M_SYS_TIMER_CLK_1MS),加上当前的clock_time()就变成了绝对时间。

举例说明cpu_sleep_wakeup的用法:

cpu_sleep_wakeup (SUSPEND_MODE , PM_WAKEUP_PAD, 0);

程序执行该函数时进入suspend mode,只能被GPIO PAD唤醒。

cpu_sleep_wakeup (SUSPEND_MODE , PM_WAKEUP_TIMER, clock_time() + 10* CLOCK_16M_SYS_TIMER_CLK_1MS)

程序执行该函数时进入suspend mode,只能被Timer唤醒,唤醒时间为当前时间加上10ms,所以suspend时间为10ms。

cpu_sleep_wakeup (SUSPEND_MODE , PM_WAKEUP_PAD | PM_WAKEUP_TIMER,clock_time() + 50* CLOCK_16M_SYS_TIMER_CLK_1MS);

程序执行该函数时进入suspend模式,可被GPIO PAD和Timer唤醒,Timer唤醒的时间设置为50ms。如果在50ms结束之前触发了GPIO的唤醒动作,MCU会被GPIO PAD唤醒;如果50ms内无GPIO动作,MCU会被Timer唤醒。

cpu_sleep_wakeup (DEEPSLEEP_MODE, PM_WAKEUP_PAD, 0);

程序执行该函数时进入deepsleep mode,可被GPIO PAD唤醒。

cpu_sleep_wakeup (DEEPSLEEP_MODE_RET_SRAM_LOW32K , PM_WAKEUP_TIMER, clock_time() + 8*
CLOCK_16M_SYS_TIMER_CLK_1S);

程序执行该函数时进入deepsleep retention 32K Sram mode,可被Timer唤醒,唤醒时间为执行该函数的8s后。

cpu_sleep_wakeup (DEEPSLEEP_MODE_RET_SRAM_LOW32K , PM_WAKEUP_PAD | PM_WAKEUP_TIMER,clock_time() + 10*
CLOCK_16M_SYS_TIMER_CLK_1S);

程序执行该函数时进入deepsleep retention 32K Sram mode,可被GPIO PAD和Timer唤醒,Timer唤醒时间为执行该函数的10s后。如果在10s结束之前触发了GPIO动作,MCU会被GPIO PAD唤醒;如果10s内无GPIO动作,MCU会被Timer唤醒。

低功耗唤醒后运行流程

当user调用API cpu_sleep_wakeup后,MCU进入sleep mode;当唤醒源触发MCU唤醒后,对于不同的sleep mode,MCU的软件运行流程不一致。

下面详细介绍deepsleep被唤醒后的MCU运行流程。请参考下图。

MCU运行流程

MCU上电(Power on)之后,各流程的介绍:

(1) 运行硬件bootloader(Run hardware bootloader)

MCU硬件上执行一些固定的动作,这些动作固化在硬件上,软件无法修改。

举几个例子说明一下这些动作,比如:芯片上电/deep回来:通过读取flash的boot启动标记“TLNK”,判断当前应该运行的firmware存储地址(偏移地址0x00000/0x20000/0x40000/0x80000),然后跳转到Flash的对应地址(基础地址0x20000000 + 偏移地址0x00000/0x20000/0x40000/0x80000)开始执行软件bootloader。

(2) 运行软件bootloader(Run software bootloader)

hardware bootloader运行结束之后,MCU开始运行software bootloader。Software bootloader就是前面介绍过的vector端(对应cstartup_B91.S里面的汇编程序)。

Software bootloader是为了给后面C语言程序的运行设置好内存环境,可以理解为整个内存的初始化。

(3) 系统初始化(System initialization)

System initialization对应main函数中sys_init到user_init之前各硬件模块初始化(包括sys_init、rf_drv_init、gpio_init、clock_init),设置各硬件模块的数字/模拟寄存器状态。

(4) 用户初始化(User initialization)

User initialization对应SDK中函数user_init。

(5) main_loop

User initialization完成后,进入while(1)控制的main_loop。main_loop中进入sleep mode之前的一系列操作称为"Operation Set A”,sleep唤醒之后一系列操作称为"Operation Set B”。

如果调用cpu_sleep_wakeup函数进入deepsleep mode,当deepsleep被唤醒后,MCU会重新回到Run hardware bootloader。

可以看出,deepsleep wake_up跟Power on的流程是几乎一致的,所有的软硬件初始化都得重新做。

MCU进入deepsleep后,所有的Sram和数字/模拟寄存器(只有几个模拟寄存器例外)都会掉电,所以功耗很低,MCU电流小于1uA。

低电检测

本文档统一以“低电检测(low battery detect)”这个名称进行说明。

低电检测的重要性

使用电池供电的产品,由于电池电量会逐渐下降,当电压低到一定的值后会引起很多问题:

(1) B91工作电压的范围为1.8V ~ 4.3V。当电压低于1.8V时,已经无法保证稳定的工作。

(2) 当电池电压较低时,由于电源的不稳定,Flash的“write”和“erase”操作可能有出错的风险,造成program firmware和用户数据被异常修改,最终导致产品失效。根据以往的量产经验,我们将这个可能出风险的低压阀值设定为2.0V。

根据上面的描述,使用电池供电的产品,必须设定一个安全电压值(secure voltage),只有当电压高于这个安全电压的时候才允许MCU继续工作;一旦电压低于安全电压,MCU停止运行,需要立刻被 shutdown(SDK 上使用进入deepsleep mode来实现)。

安全电压也称为报警电压,这个电压值的选取,目前SDK默认使用2.2V。如果user在硬件电路中出现了不合理的设计,导致电源网络稳定性的恶化,安全电压值还需要继续提高,比如2.3V、2.4V等。

对于开发实现的产品,只要使用了电池供电,低电检测都必须是该产品整个生命周期实时运行的任务,以保证产品的稳定性。

低电检测的实现

低电检测需要使用ADC对电源电压进行测量。user请参考文档Driver SDK Developer Handbook相关ADC章节,先对ADC模块进行必要的了解。

低电检测的实现,结合SDK工程示例 “proj_ultra_ll_mic_tx”给出的实现来说明,参考文件bat.c和bat.h。

必须确保app_config.h文件中宏“ENABLE_BATT_CHECK”是被打开的,这个宏默认是关闭的,user使用低电检测功能时需要注意。

#define ENABLE_BATT_CHECK              1

低电检测的注意事项

低电检测是一个基本的ADC采样任务,在实现ADC采样电源电压时,有一些需要注意的问题,说明如下。

(1) 建议使用GPIO输入通道

采样方式可采用Vbat或GPIO模拟信号输入的方式进行采样,但Vbat通道采样精度较差,对采样精度要求高的场合建议通过外部GPIO方式采样。

可用的GPIO输入通道为PB0~PB7、PD0、PD1对应的input channel。

typedef enum{
    ADC_GPIO_PB0 = GPIO_PB0 | (0x1<<12),
    ADC_GPIO_PB1 = GPIO_PB1 | (0x2<<12),
    ADC_GPIO_PB2 = GPIO_PB2 | (0x3<<12),
    ADC_GPIO_PB3 = GPIO_PB3 | (0x4<<12),
    ADC_GPIO_PB4 = GPIO_PB4 | (0x5<<12),
    ADC_GPIO_PB5 = GPIO_PB5 | (0x6<<12),
    ADC_GPIO_PB6 = GPIO_PB6 | (0x7<<12),
    ADC_GPIO_PB7 = GPIO_PB7 | (0x8<<12),
    ADC_GPIO_PD0 = GPIO_PD0 | (0x9<<12),
    ADC_GPIO_PD1 = GPIO_PD1 | (0xa<<12),
}adc_input_pin_def_e;

使用GPIO input channel对电源电压进行ADC采样,其具体使用方式如下:在硬件电路设计上,将电源直接和GPIO input channel连接。ADC初始化时,将GPIO设为高阻态(ie、oe、output全部设0),此时GPIO上的电压等于电源电压,直接进行ADC采样即可。

初始化时所有状态(ie、oe、output)使用默认状态即可,不做特殊修改,工程示例中默认选择PB3为GPIO input channel。

(2) 只能使用差分模式

虽然ADC input mode同时支持单端模式(Single Ended Mode)和差分模式(Differential Mode),但由于某些特定的原因,Telink 规定:只能使用差分模式,单端模式不允许使用。

差分模式的input channel分为positive input channel(正端输入通道)和negative input channel(负端输入通道),被测量的电压值为positive input channel电压减去negative input channel电压得到的电压差。

如果ADC采样的input channel只有1个,使用差分模式时,将当前input channel设置为positive input channel,将GND设为negative input channel。这样二者的电压差和positive input channel电压相等。

SDK中低压检测使用了差分模式,函数接口如下:

adc_set_diff_input(pin >> 12, GND);

(3) 不同的ADC任务需要切换

低压检测作为一种最基本的ADC采样,使用的是Misc channel。User如果需要除低压检测外其他的ADC的任务,也需要使用Misc channel。低压检测无法与其他ADC任务同时运行,必须采用切换的方式来实现。

低电检测的使用

在SDK工程示例中,proj_ultra_ll_mic和proj_ultra_ll_mic_240工程中均实现了低电检测功能,user需要在app_ui.h中开启低电检测的功能进行使用。

(1) 低电检测初始化

SDK中默认的初始化为:

bat_check_init(ADC_GPIO_PB3, 0, 1, 2200, 2300);

第一个参数pin:设置低电检测的引脚,SDK默认PB3。

第二个参数res_up和第三个参数res_down:电量检测分压系数,由于低电检测基于GPIO,当检测值超过3.5V时,数值会开始产生偏差,因此如检测电压超过3.5V时,用户需自行配置分压系数,SDK默认无分压,即res_up值为0,res_down值为1,具体计算位置在bat.c文件bat_get_voltage_mvJ接口中:

int bat_get_voltage_mv(void)
{
    ......
    adc_buf[adc_buf_index++] = vol *
            (bat_check_info.res_up + bat_check_info.res_down) / bat_check_info.res_down;
    ......
}

第四个参数bat_low_mv:安全电压,SDK默认2200mv。

第五个参数bat_nor_mv:正常电压,SDK默认2300mv,其中bat_low_mv和bat_nor_mv之间是过渡阶段,防止低电和正常状态频繁切换。

另外bat_check_info变量为低电检测结构体。

typedef struct {
    adc_input_pin_def_e pin;
    int res_up;
    int res_down;
    int bat_low_mv;
    int bat_nor_mv;

    u32 tick_debounce;
    u32 debounce_time;

    u32 tick_check;
    u32 check_interval;

    int bat_state_curr;
    int bat_state_last;

    int bat_low;
    int bat_vol_mv;
} bat_check_info_s;

前五个变量为低电检测初始化入参。

第六个变量tick_debounce:低电状态切换定时器,默认值为1000,单位微秒,用于周期切换低电状态。

第七个变量debounce_time:低电状态切换定时值,默认为1000,单位微秒。

第八个变量tick_check:bat_check_task任务执行定时器,用于周期执行低电检测任务。

第九个变量check_interval:bat_check_task任务执行定时值,默认为1000,单位微秒。

第十个变量bat_state_curr:当前低电状态。

第十一个变量bat_state_last:上一次低电状态。通过与当前低电状态进行判断,若当前低电状态与上一次低电状态不一致时,开始低电状态变量的更新操作。

第十二个变量bat_low:低电状态变量,0为非低电状态,1为低电状态,user可通过获取该变量状态从而判断是否为低电状态。

第十三个变量bat_vol_mv:当前电量,单位mv。

(2) 低电检测处理

在ui_loop中,调用bat_check_task(),即可实现低电检测处理。

通过变量tick_check来控制低电检测任务执行的频率,工程示例中为每20ms执行一次低电检测。开发者可以根据自己的需求来修改这个时间值。当工作电压从大于2300mv变到低于2200mv时,bat_state_last值为0,而bat_state_curr变为1,当前电量与上一次电量不一致,开始执行低电状态变量更新,将bat_state_curr的值1赋给bat_low,从而实现低电状态的切换。

USB接口

USB Audio相关宏说明

APP_MODE:设置USB功能,支持如下三个设置项:

(1) APP_MODE_DEBUG_ONLY: 支持USB Log、VCD、指令输入;

(2) APP_MODE_DEBUG_HID:支持USB Log、VCD、指令输入、HID通信;

(3) APP_MODE_AUDIO_AUTO:支持USB Speaker、USB MIC、HID通信。

当APP_MODE == APP_MODE_AUDIO_AUTO时,开启USB Audio功能,有如下宏可设置:

a) USB_SPEAKER_ENABLE开启USB Speaker(USB 下行)

b) USB_MIC_ENABLE开启USB MIC(USB 上行)

c) USB_MIC_CHANNEL:1:USB MIC/上行为单声道;2:USB MIC/上行为立体声

其中,USB Speaker和USB MIC可独立开启,也可以同时开启;USB Speaker仅支持立体声。

USB Audio描述符名称初始化

USB Audio描述符名称初始化配置在app_config.h文件中:

......
#define STRING_PRODUCT                     L"Low Latency MIC"
#else
#define STRING_PRODUCT                     L"USB Speacker"
#endif
......

默认值是在default_config.h文件中,建议在app_config.h里修改。

......
#ifndef STRING_VENDOR
#define STRING_VENDOR       L"Telink"
#endif
#ifndef STRING_PRODUCT
#define STRING_PRODUCT      L"2.4G Wireless Audio"
#endif
#if (MCU_CORE_TYPE == MCU_CORE_9518)
    #ifndef STRING_SERIAL
    #define STRING_SERIAL       L"TLSR9518"
    #endif
#else
    #ifndef STRING_SERIAL
    #define STRING_SERIAL       L"TLSR9518"
    #endif
#endif
......

算法

SDK支持多种算法

其中发射端音频流如下

TxAudioStream

接收端音频流如下

RxAudioStream

重采样算法

(1) 重采样算法初始化

在app_audio.c文件的app_audio_init接口中调用“my_resample48to16_init”和“my_resample16to48_init”接口分别对应下采样(48k-16k)和上采样(16k-48k)的初始化。

void app_audio_init (void)
{
    ......
    #if RESAMPLE_48_TO_16_EN
    my_resample48to16_init();
    #endif

    #if RESAMPLE_16_TO_48_EN
    my_resample16to48_init();
    #endif
    .....
}

(2) 重采样算法处理

当前重采样算法处理主要适用于降噪算法处理工程中的采样率调整问题。

(a) 在默认降噪模式中

Tx端降噪前先下采样,降噪后上采样

Rx端则无重采样处理。

(b) 在长距离降噪模式中

Tx端降噪前先下采样,然后编码。

Rx端解码后,先上采样。

EQ

使能APP_EQ_ENABLE宏即可开启EQ。

(1) EQ初始化

#if(APP_BUILD == APP_MIC_TX) && APP_EQ_ENABLE
    eq_inf_load(0x20000000 + FLASH_USER_EQ_BASE);
    myudb_register_hci_eq_cb(my_debug_eq);
    audio_codec_flag_set(CODEC_FLAG_EQ_VOICE_MIC_EN, 1);
#endif

eq_inf_load(0x20000000 + FLASH_USER_EQ_BASE): 用于读取eq数据,如果该地址未存储eq数据,则使用eq.c文件中的默认eq参数:

float32_t coeff_eq_voice_mic_left[5 * NSTAGE_EQ_VOICE_MIC_MAX]={
    0.983146317605274,-1.896277815330497,0.925516368712182,1.896277815330497,-0.908662686317456,//800Hz

    1.018304336102112,-1.823726756975888,0.907922907437080,1.823726756975888,-0.926227243539192,//2300Hz

    1.382406919700922,-0.529089646178078,0.000170953890723,0.529089646178078,-0.382577873591645,//3000Hz

    1.018304336102112,-1.823726756975888,0.907922907437080,1.823726756975888,-0.926227243539192,//2300Hz
};

myudb_register_hci_eq_cb(my_debug_eq): 用于注册EQ在线调试接口。

audio_codec_flag_set(CODEC_FLAG_EQ_VOICE_MIC_EN, 1): 用于使能codec的EQ。

(2) EQ处理

在app_audio_task接口中执行EQ处理:

_attribute_ram_code_ void app_audio_task ()
{
    ......
#if APP_EQ_ENABLE
    if (audio_codec_flag_get(CODEC_FLAG_EQ_VOICE_MIC_EN))
    {
        g_eq_para.eq_type        = EQ_TYPE_VOICE_MIC;
        g_eq_para.eq_sample_rate = EQ_SAMPLE_RATE_48K;

        g_eq_para.eq_channel = EQ_CHANNEL_LEFT;
        g_eq_para.eq_nstage = instance_eq_voice_mic_left.nstage;
        eq_proc(g_eq_para, pcm, pcm, samples, 0);
    }
#endif
    ......
}

通过执行eq_proc函数进行EQ处理。

(3) EQ工具

EQ Tool是泰凌用于调试EQ的工具,下面介绍EQ工具:

首先打开Telink BDT工具,点击Tool,在下拉菜单中,选择TWS Tool。

BDT工具

选择EQ Tool即为EQ工具,其中分为5大区域,分别是:

(a) 可视化区域:用于将EQ参数可视化,便于调试。

(b) 配置区域:用于配置EQ初始化配置。其中,

a) FreqSmp:采样率,SDK默认使用48k;

b) Channel:支持左、右以及立体声,SDK默认使用左声道;

c) Stages:频点数,默认支持4个频点;

d) Mode:EQ模式,SDK默认使用Speech Mic;

e) Gain:降低所有频点的增益,范围是-10 ~ 0。

(c) 调试区域:用于调试EQ参数,其中,

a) Type:滤波类型;

b) Q:斜率;

c) Fc(Hz):频点;

d) dB:增益。

(d) 操作区域:用于在线更新EQ参数、保存EQ参数bin文件,以及生成EQ参数代码。

(e) 参数生成区域:用于显示log以及生成EQ参数。

5大区域

接下来,展示如何更新EQ参数:

(a) USB在线更新:APP_MODE宏配置为APP_MODE_DEBUG_ONLY或APP_MODE_DEBUG_HID,保证设备与PC能打印log,在操作区域选择USB,点击Download,可以在risc-v TDB工具中看到参数更新的log。

USB在线更新

(b) 生成参数更新:点击Get Paramter,会在生成参数区域,生成对应的EQ参数代码,将该代码替换上文提到的默认的eq参数代码所在位置即可。

参数更新

(c) 生成文件更新:点击Save as bin file,在指定路径生成eq的bin文件,再使用Telink TDB的Download Tool工具将bin文件烧录到0xDC000所在地址。

文件更新

降噪算法

APP_NS_ENABLE宏使能降噪功能。

APP_WB_NS_ENABLE宏使能则采用webrtc算法(效果更佳),否则采用speex算法。

APP_NS_MODE宏选择降噪模式:

FS_16K_NS_EN,默认降噪模式,处理过程 - Tx端将1frame 48K数据重采样为16K -> NS -> 再重采样为48K,编码,发送N次,Rx收包N次,解码,播放。

LD_16K_NS_EN,长距离降噪模式,处理过程 - Tx端将3frame 48K数据重采样为16k -> NS 编码,发送3N次,Rx收包3N次,解码,重采样为48K,播放。

注意:

长距离降噪模式仅支持LC3+编码,SBC编码不支持。

(1) 降噪算法初始化

在void app_audio_init()接口中进行初始化

void app_audio_init ()
{
    ......
    #if APP_WB_NS_ENABLE
        app_w_ns_init();
    #else
        app_ns_init();
    #endif
    ......
}

(a) speex算法

void app_ns_init(void)
{
    nsParas.noise_suppress_default = -15;
    nsParas.echo_suppress_default = -55;
    nsParas.echo_suppress_active_default = -45;
    nsParas.low_shelf_enable = 1;
    nsParas.ns_smoothness = 27853;      // QCONST16(0.85f,15)
    nsParas.ns_threshold_low = 100000.0f;

#if 0
    int nsSize = ns_get_size();
    my_dump_str_data(1, "NS Buffer Size", &nsSize, 4);   // 0x234E
    den = (SpeexPreprocessState*)malloc(nsSize);
#else
    static u32 ns_buffer[0x234E/4+256];
    den = (SpeexPreprocessState*)ns_buffer;
#endif
    ns_init(den, &nsParas, 16000 * MIC_SAMPLES_PER_PACKET / MIC_SAMPLING_RATE, SPEEX_SAMPLERATE);
}

noise_suppress_default: 调节噪声衰减幅度,单位为db,-15含义为衰减15db。

low_shelf_enable: 低频衰减的滤波器,使能后100Hz以下有明显衰减,整体信号能量也会减小。

注意:

其他参数建议不做修改。

其中ns_init接口的入参frame_size最大支持120。

(b) webrtc算法

void app_w_ns_init(void)
{
    w_ns_cfg_param.frame_size = W_NS_FRAME_SIZE;
    w_ns_cfg_param.sampleRate = 16000;
    w_ns_cfg_param.target_level = k12dB;   
    w_ns_cfg_param.lowShelf_En = 0;
    //int s = w_ns_get_size();
    //my_dump_str_data(1, "buffer size: %d\n", &s,4);

    w_ns_init(ns_buffer_st, w_ns_cfg_param);
}

其中app_w_ns_initns_in接口的入参W_NS_FRAME_SIZEframe_si最大支持80,帧长超过80的情况下会进行组包处理。

(2) 降噪算法处理

降噪处理过程中,除需关注webrtc算法和speex算法以外的不同,最需关注的是降噪模式的区别。

(a) 默认降噪模式

Tx端处理过程:

_attribute_ram_code_ void app_audio_task (void)
{
    ......
    #if APP_NS_MODE == FS_16K_NS_EN
        if (async.aec_enable) {
            s16 pcm16k[samples/3];
            my_resample48to16_data((int *)pcm, samples, (int *)pcm16k, 0);
            log_task (SL_AUDIO_EN, SL01_task_tws_arbiter_en, 1);
            app_ns_process_frame(pcm16k, samples/3);
            log_task (SL_AUDIO_EN, SL01_task_tws_arbiter_en, 0);
            my_resample16to48_data((int *)pcm16k, samples/3, (int *)pcm, 0);
        }
    ......
        ps->cmd = ASYNC_CMD_AUDIO_TAG;
        #if CODEC_ALGORITHM_SEL == CODEC_ALGORITHM_SBC
            mic_audio_encode(pcm, samples, enc);
            tmemcpy (ps->data, enc, ASYNC_AUDIO_DATA_LEN);
        #elif CODEC_ALGORITHM_SEL == CODEC_ALGORITHM_LC3A
            __UNUSED int leni = my_llenc (0, pcm, samples, enc);
            tmemcpy (ps->data, enc, ASYNC_AUDIO_DATA_LEN);
        #endif
    #endif
    ......
}

Line 7:将1frame 48K数据重采样为16K

Line 9:NS

Line 11:再重采样为48K,

Line 14 ~ 21: SBC/LC3+编码,发送N次,

Rx端处理过程:收包N次,解码,播放。

(b) 长距离降噪模式

Tx端处理过程:

_attribute_ram_code_ void app_audio_task (void)
{
    ......
    #elif APP_NS_MODE == LD_16K_NS_EN
        if (async.aec_enable) {
            app_resample48to16_data_mono(ld_48k_pcm[mic_16k_cnt], samples, ld_16k_pcm[mic_16k_cnt]);
            app_ns_process_frame(ld_16k_pcm[mic_16k_cnt], samples/3);
            app_resample16to48_data_mono(ld_16k_pcm[mic_16k_cnt], samples/3, pcm); //这里降采样用于本地回环输出,与无线音频链路无关。
        }
    #endif
    ......
    #if APP_NS_ENABLE && (APP_NS_MODE == LD_16K_NS_EN)
        if (async.aec_enable) {
            if (mic_16k_cnt == 2){
            ps->cmd = ASYNC_CMD_AUDIO_TAG_NS;
            #if CODEC_ALGORITHM_SEL == CODEC_ALGORITHM_SBC
                mic_audio_encode((s16 *)ld_16k_pcm, samples, enc);
                tmemcpy (ps->data, enc, ASYNC_AUDIO_DATA_LEN);
            #elif CODEC_ALGORITHM_SEL == CODEC_ALGORITHM_LC3A
                __UNUSED int leni = my_llenc (0, (int16_t *)ld_16k_pcm, samples, enc);
                tmemcpy (ps->data, enc, ASYNC_AUDIO_DATA_LEN);
            #endif
            }
        }
    #endif
    ......
}

Line 6:将第1 / 2 / 3 frame 48K数据重采样为16K.

Line 7:NS。

Line 12 ~ 25: mic_16k_cnt为2时,将3frame数据进行LC3+编码,发送3N次,

Rx端处理过程:

_attribute_ram_code_ void app_audio_task (void)
{
    ......
    #if (APP_NS_MODE == LD_16K_NS_EN)
    ......
        if (mic_16k_cnt == 0) {
        ......
            my_lldec (mic_idx, ps, ASYNC_AUDIO_DATA_LEN, ld_16k_dec_pcm[mic_idx], 0);
            app_resample16to48_data_mono(mic_idx, ld_16k_dec_pcm[mic_idx] + 0 * (MIC_SAMPLES_PER_PACKET / 3), MIC_SAMPLES_PER_PACKET / 3, pcm_dec_mic[mic_idx]);
        }
        else if (mic_16k_cnt == 1)
        {
            app_resample16to48_data_mono(mic_idx, ld_16k_dec_pcm[mic_idx] + 1 * (MIC_SAMPLES_PER_PACKET / 3), MIC_SAMPLES_PER_PACKET / 3, pcm_dec_mic[mic_idx]);
        }
        else if (mic_16k_cnt == 2)
        {
            app_resample16to48_data_mono(mic_idx, ld_16k_dec_pcm[mic_idx] + 2 * (MIC_SAMPLES_PER_PACKET / 3), MIC_SAMPLES_PER_PACKET / 3, pcm_dec_mic[mic_idx]);
        }
    ......
}

Line 6 ~ 10:mic_16k_cnt为0时,将收到的3frame数据先解码,然后将第1frame 16K数据重采样为48K。

Line 11 ~ 14:mic_16k_cnt为1时 ,将第2frame 16K数据重采样为48K。

Line 15 ~ 18:mic_16k_cnt为2时 ,将第3frame 16K数据重采样为48K。

DRC算法

Dynamic Range Control(DRC)动态范围控制提供压缩和放大能力,可以使声音听起来更柔和或更大声,即一种信号幅度调节方式。DRC广泛应用于音频信号处理领域,例如助听器中最常见的宽动态范围压缩方法(Wide Dynamic Range Compression,WDRC)、音频信号处理中最常用的自动增益控制(Automatic Gain Control,AGC)方法等。动态范围控制,顾名思义,是将输入音频信号的动态范围映射到指定的动态范围。通常映射后的动态范围小于映射前的动态范围,因此称之为动态范围压缩。音频信号可以进行整体的动态范围控制;也可以划分为若干子带分别进行动态范围控制。具体原理可参考:DRC原理

SDK中的DRC分为ADC DRC和DAC DRC分别通过ADC_DRC_EN宏和DAC_DRC_EN宏控制。

下面以ADC DRC为示例。

(1) DRC初始化

使能ADC_DRC_EN宏即可开启DRC算法。

void app_drc_init(drc_nodes_s *drc_nodes, u32 flash_addr)
{
    app_flash_read(flash_addr, sizeof(drc_paras_set_num), (u8 *)&drc_paras_set_num);
    if (drc_paras_set_num > 0 && drc_paras_set_num <= DRC_NODE_MAX) {
        u32 addr = FLASH_DRC_PARA_BASE + sizeof(drc_paras_set_num);
        app_flash_read(addr, drc_paras_set_num * sizeof(drc_node_para_s), (u8 *)drc_paras_set);
        drc_nodes_init_paras(drc_nodes, drc_paras_set, drc_paras_set_num);
        drc_paras_set_num = 0;
    }
    else{
        #if DAC_DRC_EN
            drc_nodes_init_paras(drc_nodes, dac_drc_node_paras_default, ARRAY_LEN(dac_drc_node_paras_default));
        #elif  ADC_DRC_EN
            drc_nodes_init_paras(drc_nodes, adc_drc_node_paras_default, ARRAY_LEN(adc_drc_node_paras_default));
        #endif
    }
    drc_nodes_update(drc_nodes);
}

初始化DRC算法的参数优先从Flash所在地址读取,若未存储DRC参数到指定地址,则使用默认的DRC参数作进行初始化。

drc_node_para_s adc_drc_node_paras_default[] =
{
    {
        .type = DRC_NOISE_GATE,
        .noise_gate = {
                -50.0f, 4.0f, 0.05f, 0.1f, 0.05f, MIC_SAMPLING_RATE, VALUE_0dBFS, 1
        },
    },
    {
        .type = DRC_EXPANDER,
        .expander = {
                -70.0f, (float)20, (float)4, 0.01f, 0.05f, 0.000f, (float)MIC_SAMPLING_RATE, 0, VALUE_0dBFS, 48*2
        },
    },
    {
        .type = DRC_COMPRESSOR,
        .compressor = {
                -6.0f, 3.0f, 4.0f, 0.05f, 1.0f, MIC_SAMPLING_RATE, 0, VALUE_0dBFS, 48
        },
    },
    {
        .type = DRC_LIMITER,
        .limiter = {
                -3.0f, 1.0f,  0.01f, 0.1f, MIC_SAMPLING_RATE, 0, VALUE_0dBFS, 48
        },
    },
};

(2) DRC处理

调用drc_process_frame接口进行DRC处理。

static inline PCM_MONO_INT drc_gain_process_single_point(PCM_MONO_INT pcm, float gain)
{
    float v = (float)pcm * gain;
    for(int j=0; j<__drc_nodes->drc_cnt; j++) {
        v = drc_calc_output(&__drc_nodes->drc[j], v);
    }
    v += 0.5f;
    return SATURATE_PCM(v);
}
_attribute_ram_code_ int drc_gain_process_frame(drc_nodes_s *drc_nodes, PCM_MONO_INT *pcm, int len, float gain)
{
    __drc_nodes = drc_nodes;
    for(int i=0;i<len;i++) {
        pcm[i] = drc_gain_process_single_point(pcm[i], gain);
    }
    return len;
}

(3) DRC工具

DRC工具用于调试DRC以及生成对应的参数代码,界面如下:

DRC工具

(a) 噪声门(Noise gate):是扩展器的一种,可以限制低于给定阈值的信号。

a) Threshold:关门dB;

b) Attack:关门到开门时间;

c) Release:开门到关门时间;

d) Hold:开门状态保持时间;

e) Samples:计算点,默认96个点计算一次。

(b) 动态范围扩展器(Dynamic range expander):减弱低于给定阈值的小声信号的音量;可以使得小信号听起来更加小声。

a) Threshold:扩展dB;

b) Width:拐点过度平滑程度,用于平滑处理;

c) Ratio:斜率;

d) Makeup:增益;

e) 其他同上。

(c) 动态范围压缩器(Dynamic range compressor):减弱超过给定阈值的大声信号的音量;可以保护硬件,增加整体响度。

(d) 动态范围限幅器(Dynamic range limiter):是压缩器的一种,可以限制超过给定阈值的信号。

设置好DRC参数后,点击Get Code。

Make Code

就会生成如图所示的DRC参数代码:

DRC参数代码

将该参数替换默认的DRC参数变量即可实现DRC参数的导入。

或者使能CFG_COMMUNICATION_BY_USB宏并将APP_MODE配置为APP_MODE_DEBUG_HID或APP_MODE_AUDIO_AUTO开启在线调试。(编者这里采用APP_MODE_DEBUG_HID模式方便查看log)

当打印USB Connected说明连接成功

DRC连接成功

接着勾选需要的参数,点击Download即可下载参数

DRC参数下载

最后可在下位机log中查看上位机发送的DRC在线调试参数

DRC工具log

注意:

DRC在线调试会进行参数比对,如果发送的参数和当前采用的参数一致,则不进行处理。

Audio

Audio初始化

(1) TX音频初始化

Tx端Audio输入来源可以是I2S、lineIn、DMIC和AMIC。支持本地Lineout监听输入音频,可关闭,通过CODEC_DAC_MONO_MODE宏控制。

(a) LineIn/AMIC:开发板默认的音频输入通路,Linein即跳帽短接PIN 34/33。

PIN 34/33

若需要切换到模拟麦克风通路,PIN 34/33跳帽调整为PIN 30/29(默认使用Left_Mic)。

PIN 30/29

(b) DMIC:直接使⽤外围audio处理的芯⽚,将数字信号读到R9上,需要将“AUDIO_IN_MODE”宏设置为“DMIC_INPUT”。

void app_audio_init ()
{
    ......
#ifdef AUDIO_IN_MODE
    if (AUDIO_IN_MODE == DMIC_INPUT) {
    #ifdef DMIC_GPIO_SEL
        audio_set_dmic_input(DMIC_GPIO_SEL);
    #endif
    }
    else {
        audio_set_input_mode(AUDIO_IN_MODE);
    }
#endif
    ......
}

(c) I2S:默认为主机,需要将“AUDIO_INTERFACE”宏设置为“AUDIO_I2S_GPIO”。

void app_audio_init ()
{
    ......
#if(AUDIO_INTERFACE == AUDIO_I2S_GPIO)
    audio_set_interface(FLAG_INTERFACE_GPIO, 1);
    #if (AUDIO_INTERFACE_ROLE == AUDIO_I2S_AS_SLAVE)
        gpio_input_en(I2S_BCK_PC3|I2S_DAC_LR_PC6|I2S_DAC_DAT_PC7);
        audio_set_interface(FLAG_INTERFACE_ROLE_SLAVE, 1);
    #else
        audio_set_interface(FLAG_INTERFACE_ROLE_SLAVE, 0);
    #endif
#else
    ......
}

I2S初始化引脚分别是:PC3(BCK)、PC6(LR)、PC7(DAT_IN)。

(2) RX音频初始化

(a) Lineout:默认音频输出。若开启了USB Audio则增加USB MIC上行,同时支持USB Audio下行与Lineout输出的远端Tx音频进行混音处理。

(b) I2S:可选切换的音频输出,将AUDIO_INTERFACE宏设置为AUDIO_I2S_GPIO,初始化引脚分别是:PC3(BCK)、PC4(LR)、PC5(DAT_OUT)。若开启了USB Audio则增加USB MIC上行,同时支持USB Audio下行与I2S输出的远端Tx音频进行混音处理。

(3) 增益初始化

(a) Tx端:可调整AMIC、DMIC的输入增益以及Lineout增益,分别由CODEC_IN_A_GAIN(0 ~ 20dB)、CODEC_IN_D_GAIN(0 ~ 43dB)和CODEC_OUT_A_GAIN(-19 ~ 12dB)、CODEC_OUT_D_GAIN(-31 ~ 32dB)控制。

(b) Rx端:仅Lineout增益有效,由CODEC_OUT_A_GAIN、CODEC_OUT_D_GAIN控制,初始化接口同上。

void app_audio_init ()
{
    audio_set_codec_in_path_a_d_gain(CODEC_IN_D_GAIN, CODEC_IN_A_GAIN);    //mic sco
    audio_set_codec_out_path_a_d_gain(CODEC_OUT_D_GAIN, CODEC_OUT_A_GAIN); //mic sco
    .......
}

可通过app_ui.h文件直接对Tx / Rx增益进行配置。

    #if APP_BUILD == APP_MIC_TX
        #define CODEC_IN_A_GAIN               CODEC_IN_A_GAIN_0_DB
        #define CODEC_IN_D_GAIN               CODEC_IN_D_GAIN_0_DB
        #define CODEC_OUT_A_GAIN              CODEC_OUT_A_GAIN_0_DB
        #define CODEC_OUT_D_GAIN              CODEC_OUT_D_GAIN_0_DB
    #elif APP_BUILD == APP_MIC_RX
        #define CODEC_IN_A_GAIN               CODEC_IN_A_GAIN_0_DB
        #define CODEC_IN_D_GAIN               CODEC_IN_D_GAIN_0_DB
        #define CODEC_OUT_A_GAIN              CODEC_OUT_A_GAIN_0_DB
        #define CODEC_OUT_D_GAIN              CODEC_OUT_D_GAIN_0_DB
    #endif

编码

SDK中提供了SBC、LC3+编解码算法,而不同的需求也会对编码器进行不同的配置,主要通过以下几个关键宏:

(1) MIC_SAMPLING_RATE:用于配置麦克风采样率,默认48K。

(2) CODEC_ALGORITHM_SEL:用于配置编码算法,可选SBC和LC3+。

(3) MIC_SAMPLES_PER_PACKET:麦克风每Frame的采样数,不同模式不同采样率对应值也不同,如下:

a) SBC编码有:96、120、240。

b) LC3+有:120、240。

(4) SBC_BIT_POOL、SBC_BLOCK_NUM:SBC编码器配置项,根据MIC_SAMPLES_PER_PACKET的不同会有不同的配置:

a) 96 Frame:20 | 12;

b) 120 Frame:26 | 15;

c) 240 Frame:19 | 30。

(5) LC3A_BIT_RATE:LC3编码器配置项,单位比特率,根据MIC_SAMPLES_PER_PACKET不同会有不同配置,其中:

a) 120 Frame:96000,

b) 240 Frame:72000。

(6) ASYNC_AUDIO_DATA_LEN:编码后的包长度,单位bytes。

(7) MIC_PACKET_PER_TX:单个Tx发送次数。

a) 1对1模式下,MIC_PACKET_PER_TX即为MIC_PACKET_NUM Tx发送次数。

b) 2对1模式下,MIC_PACKET_PER_TX = MIC_PACKET_NUM / 2。

(8) MIC_PACKET_IN_TICK:发包时间点,单位微秒,会影响到最终的延时。

User可通过对以上宏的有序组合,配置出符合项目需求的配置。

以下为48K@16bits编码相关配置:

2对1模式48K采样率编码相关配置

Audio数据处理

根据配对模式、编码、采样Samples的不同,Audio的数据处理流程、延时、发包次数、以及传输距离都会不同。

算法 Frame 压缩率 采样时间(ms) 传输次数 延时(ms)
SBC 96 17.71% 2.0 3 7.980
SBC 120 22.08% 2.5 4 8.950
SBC 240 15.83% 5.0 5 13.700
LC3+ 120 12.50% 2.5 4 10.713
LC3+ 240 9.37% 5.0 7 15.525

配置1: 采样率为48k*16bit,每采样2.0ms,产生96个sample共192bytes的原始PCM数据。对这192 bytes进行SBC编码,其中BIT_POOL:20,BLOCK_NUM:12,得到:((20*12) + 7)/8 + 4 = 34 bytes数据。

配置2: 采样率为48k*16bit,每采样2.5ms,产生120个sample共240 bytes的原始PCM数据。对这240bytes进行SBC编码,其中BIT_POOL:26,BLOCK_NUM:15,得到:((26*15) + 7)/8 + 4 = 53 bytes数据。

配置3: 采样率为48k*16bit,每采样5.0ms,产生240个sample共480 bytes的原始PCM数据。对这480bytes进行SBC编码,其中BIT_POOL:19,BLOCK_NUM:30,得到:((19*30) + 7)/8 + 4 = 76 bytes数据。

配置4: 采样率为48k*16bit,每采样2.5ms,产生120个sample共240 bytes的原始PCM数据。对这240bytes进行LC3+编码,BIT_RATE:96000,得到:(96000*(120/120) + 3199)/3200 = 30 bytes数据。

Project example proj_ultra_ll_mic_24bits:采样率为48k*24bit,每采样2.5ms,产生120个sample共360 bytes的原始PCM数据。对这360bytes进行LC3+编码,BIT_RATE:96000,由于算法支持,依然是30 bytes数据。

配置5: 采样率为48k*16bit,每采样5.0ms,产生240个sample共480 bytes的原始PCM数据。对这480bytes进行LC3+编码,BIT_RATE:72000,得到:(72000*(240/120) + 3199)/3200 = 45 bytes数据。

Project example proj_ultra_ll_mic_24bits:采样率为48k*24bit,每采样5.0ms,产生240个sample共720 bytes的原始PCM数据。对这720bytes进行LC3+编码,BIT_RATE:72000,由于算法支持,依然是45 bytes数据。