Skip to content

B91 BT/Low Latency Concurrent General SDK Developer Handbook


Overview

This document describes the implementation of the TWS earbuds solution and details the modules of concern to customers for secondary development, so that customers can quickly understand the SDK and accelerate the time cycle from project kickoff to mass production.

Typical applications of the BT/low latency concurrent(CC for short) general SDK include: traditional single Bluetooth(BT) mode TWS, 2.4GHz low latency and BT dual-mode concurrent TWS, and 2.4GHz dongle working with TWS. Specifically, the following applications can be realized: connecting to PC or cell phone via BT only for phone calls and music; connecting to dongle via 2.4GHz low latency private protocol only for calls or low latency music playback; mixing and playing via BT and 2.4GHz dongle in two ways.

Main supported features:

  • Supports TWS mode with 2.4GHz low latency and BT dual-mode concurrent
  • Supports dongle function with dual-mode concurrent TWS mode
  • Supports BT-only TWS mode
  • Supports 2.4GHz low latency private protocol
  • Supports BT HFP-HF, A2DP-SINK, AVRCP, SPP, GATT protocols
  • Supports BT SPP/GATT based wireless upgrade function
  • Supports AAC SBC format decoding for BT music
  • Supports MSBC CVSD encoding and decoding for BT call
  • Supports LC3 encoding and decoding by 2.4GHz low latency audio
  • Supports audio EQ, 9 steps music mode, 4 steps for uplink call and 4 steps for downlink call
  • Supports packet loss compensation (PLC) mechanism, MSBC and CVSD encoding format for BT call
  • Supports ADPCM8 format of tone playback
  • Supports MIC automatic gain control (AGC)
  • Supports audio signal dynamic range compression (DRC)
  • Supports automatic echo cancellation (AEC)
  • Supports noise suppression (NS)
  • Supports BF beamforming

Quick Start

Description of Code Structure

The structure of the root directory under src is as follows:

Root directory structure

  • application: audio path and USB audio.
  • boot: boot file.
  • codec: codec.
  • common: compiling and debugging related code.
  • drivers: driver.
  • proj_lib: algorithm library.
  • stack: BT and async stack related code.
  • tlkapi: api layer code.
  • vendor: store the build project code, and currently contains four project compilation options:_proj_boot_device_, _proj_bt_tws_ , _proj_cc_tws_, _proj_le_ll_dongle_.

Software Compilation Environment

The software compilation environment uses Telink_RDS IDE, which can be downloaded and installed from the following address: http://wiki.telink-semi.cn/wiki/IDE-and-Tools/RISC-V_IDE_for_TLSR9_Chips/

Establishment of the Project

Click File->import at the menu to call out the following interface:

Import interface

Select "Existing Projects into Workspace", then click "Browse" to select the directory where the code is located:

Select directory

Click "Finish" to complete the import of the project:

Complete the import

Project Compilation

Click  in the menu bar, and then select the option to be compiled in the drop-down menu to start compiling:

Start compiling

The bin file generated after compilation is located in the code directory: \src\_proj_cc_tws_\output.

bin file path

Hardware Environment

At present, the hardware environment of the BT/low latency concurrent general SDK includes the general development board environment (B91 development board) and the B91 TWS kit environment.

General Development Board Environment

The general development board environment uses two B91 development boards for the left earbud and right earbud of the TWS earbuds, and the other B91 development board as the dongle to work together.

General development board environment

B91 TWS Kit Environment

The B91 TWS kit environment is shown below. The screen acts as a board for the charging case, which can simulate charging case related actions such as powering on and off, entering and exiting the charging case, charging and discharging, etc. The dongle that cooperates with the work is still the general development board.

B91 TWS kit environment

Software Burning and Debugging

Burning

The burning software is the Telink BDT tool, and the download address is as follows:

Telink BDT Tool

The BDT tool interface after download and installation is as follows:

BDT tool interface

When downloading select Tool-> TWS Tool and the following interface will pop up:

TWS Tool interface

Download the bin file of the earbuds:

Download the bin file of the earbuds to location 0x00000000, the tone is downloaded to location 0x000d0000, 0x001dc000 is the earbuds name and 0x001ff100 is the earbuds address.

Download dongle bin file:

Download the dongle bin file to location 0x00000000.

Example of bin file download

Trace Printing

(1) Trace Configuration

The trace configuration for all modules of the SDK is defined in the enumeration: src\tlkapi\tlk_debug.h.

Define traces for all modules of the SDK

The sub-module trace of each module is defined in the following enumeration structure:

Define each sub-module trace

Add the enable structure defined by each module trace to the scTlkDebugInfo:

Add the enable structure to the scTlkDebugInfo

For example, the following two are the log enable definition for system and btc module:

system and btc module log enable definition

  • 1: The main switch that enables trace and VCD in this module.
  • 2: Sub module name
  • 3: Whether submodule trace is enabled or not
  • 4: Whether submodule vcd log is enabled or not
  • 5: Define this submodule trace print level, which can be configured as follows:

Define the sub-module trace print level

Dongle mode is a USB sound card mode, there will be no trace printing. If it is needed to enable trace mode, define APP_MODE as APP_MODE_DEBUG_ONLY in app_config.h in the dongle project to enable trace mode.

(2) Trace Printing Tool

Click the BDT tool menu Tool->RISC-V Tool, it pops up the following trace printing tool interface, when the left earbud and right earbud of TWS need to print log at the same time, first separate power on the right earbud, at this time the trace can be printed out normally, then write the position 0xcfff0 to 0x21 at position 3, fill in the content as shown in the red box in the figure, and press Enter to take effect.

trace printing tool interface

Then find the "tl_template.ini" file (1 in the figure below) in the BDT path: BDT5-7-2\BDT\config\riscv\tool, make a copy of it in the same directory and name it "tl_template21.ini", then open the "tl_template21.ini" file and change the content of position 2 to the content of the red box. At this time, click the BDT tool menu Tool->RISC-V Tool again to pop up the second trace printing interface, and select the file with the name "tl_template21.ini" at the position of 3. After the left earbud is powered on, the test trace will be displayed in the first tool interface, and the right earbud will be displayed in the second tool interface.

Select the file

VCD Log Printing

Select 3 in the trace tool interface and select the vcd configuration file in the pop-up box, the path is \src\common\usb_dbg\log_def_stack.h. Then during the earbuds test, click 1 to start getting the vcd information, the result will be automatically saved in the download path as .vcd. Clicking 2 will automatically open the vcd file for analysis.

VCD log printing

TWS Earbuds Solution Basic Function Operation Instruction

Power on/off Operation

In the general development board environment, after the earbuds is updated and downloaded or powered off, press the reset button to power on the earbuds. During use, you can enter "11 0c" in the command line window of RISC-V TDB to trigger the power off, or press and hold the k2 button for more than 3 seconds before releasing it to trigger the power off.

In the B91 TWS kit environment, after the earbuds is updated and downloaded, the earbuds is powered on by unplugging and plugging the USB port. During use, with the earbuds in the charging case, short press the green button on the earbuds base board to power on and double click the green button on the earbuds base board to trigger power off.

Earbuds Entering and Exiting the Charging Case

In the B91 TWS kit environment, the earbuds and the charging case board are in the power-on state, pressing the white button on the earbuds base board to enter the charging case state, and pop it up to exit the charging case state. After the earbuds update program or power down and then on, you need to press the green button on the base board to trigger the earbuds to recognize entry and exit state of the charging case while remaining exited the charging case state.

Earbuds Grouping and Pairing Mode

In the general development board environment, for the left and right earbuds, simultaneously double click the K2 button to trigger the earbuds into pairing mode, which can be paired with dongle or BT devices, triple click the K2 button triggers the left and right earbuds to restore factory settings, perform the earbuds pairing action, and then enter the pairing mode.

In the B91 TWS kit environment, while the earbuds are in the charging case, long press the green button on the earbuds base board for 3 seconds, the earbuds enters pairing mode, maintain long press to 10 seconds, the earbuds will restore factory settings, perform the earbuds pairing action, and then enter the pairing mode.

Earbuds Establish Connection with BT Master Device and Dongle

When the earbuds is in pairing mode, a BT master device such as mobile phone can search for and pair with the headset, and double-click the k1 button on the B91 development board as dongle to pair with the earbuds. The earbuds that have been paired and connected to the phone or dongle can be automatically reconnected to the last connected phone and dongle when it is powered off and on again.

Lighting Effects of Earbuds and Dongle in Each Mode

Dongle: Red light flashing fast in pairing mode, red light always on when connected, red light breathing effect in reconnection mode.

Earbuds (in general development board environment): Red light flashing fast in pairing mode, red light breathing when connected, red light flashing slow in reconnection mode.

Earbuds (in B91 TWS kit environment): Double light flashing fast in pairing mode, double light breathing when connected, double light flashing slow in reconnection mode;

System Architecture Description

Overall Architecture

The TWS earbuds can be connected separately to a dongle via a private 2.4GHz low latency private protocol or to a main device through Bluetooth for calls or music playback. They can also be dual-connected to enable one path of phone call or music while the other is active. When both paths are in use, users may opt to enable mixing mode for simultaneous music playback or mixed mode for one path of Bluetooth phone call and one path of dongle phone or music (audio only in the downstream). When using dongle-only music mode, the 48kHz LC3 encoding is implemented; when using dongle-only phone mode, the format is 48kHz LC3 in the downstream direction and 16kHz mSBC in the upstream direction. Both modes can achieve a latency of less than 30ms. For Bluetooth phone calls, the CVSD and mSBC encoding formats are supported; for Bluetooth music, AAC with 127kHz bitrate and SBC with 53 bitpool are both supported.

Earbuds Software Architecture

Earbuds software architecture

The earbuds software architecture is shown in the figure above, and each module is introduced as follows:

  • PHY_RF/Driver: BT/2.4GHz RF physical layer and chip driver related logic.
  • BT_controller: BT controller underlying logic.
  • Async_controller: 2.4GHz low latency private protocol controller related logic.
  • BT_Host/Profile: BT host layer (HCI/RFCOMM/L2CAP), BT Profile (A2DP/HFP/AVRCP/SPP, etc.).
  • BT_API and Async_API: The middle layer related to the interaction between the APP layer and the BT host layer, the APP layer and the Aasync controller layer.
  • SYS_APP: Handle system application layer related logic.
  • UI: Handle all UI judgments and actions in the system.
  • Audio_Path: Audio path related logic.
  • Audio_Algorithm: All audio algorithm related logic processing.
  • Codec: Audio player data acquisition and processing.

Dongle Software Architecture

Dongle software architecture

The Dongle software architecture is shown above, and each module is introduced as follows:

  • PHY_RF/Driver: 2.4GHz RF physical layer and chip driver.
  • Async_controller: 2.4GHz low latency private protocol controller related logic.
  • Async_API: The middle layer related to the interaction between the APP layer and the Aasync controller layer.
  • SYS_APP: Handle system application layer related logic.
  • UI: Handle all UI judgments and actions in the system.
  • Audio_Path: Audio path related logic.
  • Audio_Algorithm: All audio algorithm related logic processing.
  • USB: USB audio related logic.

Software Module Description

Earbuds BT API Layer

The API layer is used to receive events from the host layer, record the relevant data and provide the corresponding interface calls to the APP layer, which is located in the tlkapi layer directory of the source code.

When the host layer reports an event to the API layer, it will record the relevant state in the API layer for the APP layer to use, and if the APP layer registers the relevant event, the event is reported to the APP layer. The APP layer can also obtain the data values recorded in the API layer through the relevant interface.

For example, the phone answering process: when a phone calls, the host layer will parse the corresponding hfp instructions, and if it is correct then report the call status of the phone to the api_hfphf_call_status_changed_callback function in the API layer, this function will record the call_status of the phone for the APP to use api_bt_get_hfphf_status function for querying. At the same time, if the APP layer registers this event, the API will report this event to the APP layer for processing.

The interface for the APP layer to handle these events is mostly implemented in app_bt.c.

Location of API layer files

tlkapi_bth

The tlkapi_bth_base_evt stores the basic events from the host layer regarding ACL or SCO connection establishment, such as ACL establishment, disconnection, linkkey acquisition, etc. The corresponding events can also be registered to the APP layer for processing via the registration function in tlkapi_bth_event_user.

tlkapi_bth

Interface description:

(1) tlkapi_bth_base_evt

Data structures:

Define the API layer BT host event data structure:

  • acl_handle: Record acl handle
  • btaddr: Device address of the other party
  • dev_class: Device type of the other party
  • cur_role: Current role
  • positive: Active connection or passive connection
  • cur_mode: Current mode
  • link_key: Current linkkey
  • encry_enable_flag: Encryption enable flag
  • link_type: Link type
  • sco_handle: Record sco handle
typedef struct {
    uint16_t  acl_handle;
    uint8_t   btaddr[6];
    uint32_t  dev_class;
    uint8_t   cur_role;
    uint8_t   positive;
    uint8_t   cur_mode;
    uint8_t   link_key[16];
    uint8_t   encry_enable_flag;
} api_bth_acl_base_item_t;

typedef struct {
    uint32_t   dev_class;
    uint8_t    link_type;          
    uint16_t   sco_handle;
    uint16_t   acl_handle;
    uint8_t    btaddr[6];
} api_bth_sco_base_item_t;

typedef struct {
    api_bth_acl_base_item_t acl_item[TLK_STK_BTACl_NUMB];
    api_bth_sco_base_item_t sco_item[TLK_STK_BTSCO_NUMB];
} api_bth_base_event_info_t;

Interface definition:

Get the acl item structure data for the API layer corresponding handle:

api_bth_acl_base_item_t *api_bth_evt_get_acl_item_by_handle(uint16_t handle, api_bth_base_event_info_t *p_buff)

Get the acl item structure data for the API layer corresponding address:

api_bth_acl_base_item_t *api_bth_evt_get_acl_item_by_addr(uint8_t *addr, api_bth_base_event_info_t *p_buff)

Get the acl item structure data that is not used by the API layer corresponding handle:

api_bth_acl_base_item_t *api_bth_evt_get_acl_item_unused(api_bth_base_event_info_t *p_buff)

Get the sco item structure data for the API layer corresponding handle:

api_bth_sco_base_item_t *api_bth_evt_get_sco_item_by_handle(uint16_t scoHandle, api_bth_base_event_info_t *p_buff)

Get the sco item structure data for the API layer corresponding address:

api_bth_sco_base_item_t *api_bth_evt_get_sco_item_by_addr(uint8_t *addr, api_bth_base_event_info_t *p_buff)

Get the sco item structure data that is not used by the API layer corresponding handle:

api_bth_sco_base_item_t *api_bth_evt_get_sco_item_unused(api_bth_base_event_info_t *p_buff)

host callback event: ACL connect request

int api_bth_evtid_aclconn_request_callback(uint8_t *p_data, uint16_t data_len)

host callback event: SCO connect request

int api_bth_evtid_scoconn_request_callback(uint8_t *p_data, uint16_t data_len)

host callback event: ACL connect complete

int api_bth_evtid_aclconn_complete_callback(uint8_t *p_data, uint16_t data_len)

host callback event: SCO connect complete

int api_bth_evtid_scoconn_complete_callback(uint8_t *p_data, uint16_t data_len)

host callback event: ACL disconnect complete

int api_bth_evtid_acldisc_complete_callback(uint8_t *p_data, uint16_t data_len)

host callback event: SCO disconnect complete

int api_bth_evtid_scodisc_complete_callback(uint8_t *p_data, uint16_t data_len)

host callback event: authen complete

int api_bth_evtid_authen_complete_callback(uint8_t *p_data, uint16_t data_len)

host callback event: encrypt complete

int api_bth_evtid_encrypt_complete_callback(uint8_t *p_data, uint16_t data_len)

host callback event: sco codec changed

int api_bth_evtid_scocodec_changed_callback(uint8_t *p_data, uint16_t data_len)

host callback event: role changed

int api_bth_evtid_role_changed_callback(uint8_t *p_data, uint16_t data_len)

host callback event: mode changed

int api_bth_evtid_mode_changed_callback(uint8_t *p_data, uint16_t data_len)

host callback event: pincode request

int api_bth_evtid_pincode_request_callback(uint8_t *p_data, uint16_t data_len)

host callback event: linkkey request

int api_bth_evtid_linkkey_request_callback(uint8_t *p_data, uint16_t data_len)

host callback event: linkkey notify

int api_bth_evtid_linkkey_notify_callback(uint8_t *p_data, uint16_t data_len)

host callback event: ACL establish

int api_bth_evtid_acl_establish_callback(uint8_t *p_data, uint16_t data_len)

Register the event of the APP layer reading linkkey to the API layer, which is used to read the linkkey information saved by the APP layer. Description of the return value for APP layer events: if the return value is 0, reading linkkey is successful, otherwise it fails:

void api_bth_linkkey_request_register(bth_request_linkkey_func func)

The APP layer delete linkkey event is registered to the API layer to delete the linkkey information of the APP layer when the authentication fails:

void api_bth_clear_last_dev_register(bth_clear_last_dev_func func)

Clear the information related to acl or sco at the API layer:

int api_bth_evt_info_clear(void *p_data, uint16_t size)

Register API layer acl and sco data:

int api_bth_evt_base_info_register(api_bth_base_event_info_t *p_data)

Remove API layer acl and sco data:

int api_bth_evt_base_info_remove(void)

API layer BT host related initialization:

int api_bth_evt_init(void)

(2) tlkapi_bth_event_user

Interface definition:

API layer callback event execution interface. After the APP layer registers related events, this function is called at the API layer:

void api_bth_event_user_exec(uint16_t evtID, uint8_t *pData, uint16_t dataLen)

Provide an interface for the APP layer to remove API layer callback events:

void api_bth_event_user_remove(uint16_t evtID)

Provide an interface for the APP layer to clear all API layer callback events:

void api_bth_event_user_clear_all(void)

Provide an interface for the APP layer to register API layer callback events:

void api_bth_event_user_register(uint16_t evtID, bth_event_func func)

tlkapi_btmisc

The tlkapi_btmisc stores files related to BT, including BT reconnection, scan management, and API layer data acquisition.

The API layer is implemented in tlkapi_bt_get_value to obtain the data recorded by the host layer to report event status, such as a2dp codec, hfp phone status, etc.

The tlkapi_bt_recon implements the processing of the reconnection mechanism.

The tlkapi_bt_scan implements the processing of scan management.

The tlkapi_btmisc_event_user can register the corresponding events to the APP layer.

The usage methods of all functions are temporarily referred to the calls in the APP layer.

tlkapi_btmisc

Interface description:

(1) tlkapi_bt_get_value

Interface definition:

Register API layer related variables:

void api_bt_ctrl_all_value_init(void)

Get ACL handle by address:

uint16_t api_bt_get_acl_handle(uint8_t addr[6])

Get address by ACL handle:

uint8_t api_bt_get_addr_by_handle(uint8_t out_addr[6], uint16_t handle)

Disconnect the ACL link for the corresponding address:

void api_bt_acl_disconnect(uint8_t addr[6])

Cancel the ACL link connection for the corresponding address:

void api_bt_acl_cancel_connect(uint8_t addr[6])

Get ACL device type:

uint32_t api_bt_get_acl_dev_class(uint8_t addr[6])

Get ACL current role:

uint8_t api_bt_get_acl_curr_role(uint8_t addr[6])

Get ACL positive:

uint8_t api_bt_get_acl_positive(uint8_t addr[6])

Get ACL current mode:

uint8_t api_bt_get_acl_curr_mode(uint8_t addr[6])

Get the current linkkey value of the API record:

uint8_t *api_bt_get_linkkey(uint8_t addr[6])

Get SCO device class:

uint32_t api_bt_get_sco_dev_class(uint8_t addr[6])

Get SCO link type:

uint8_t api_bt_get_sco_link_type(uint8_t addr[6])

Get A2DP sink status:

BTP_A2DP_CHN_MODE_ENUM api_bt_get_a2dpsnk_status(uint8_t addr[6])

Get A2DP sink channel mode:

BTP_A2DP_CHN_MODE_ENUM api_bt_get_a2dpsnk_chn_mode(uint8_t addr[6])

Get A2DP sink codec:

uint8_t api_bt_get_a2dpsnk_codec(void)

Get avrcp volume:

uint8_t api_bt_get_avrcp_volume(uint8_t addr[6])

Get the current avrcp handle:

uint16_t api_bt_get_avrcp_handle(void)

Get hf status:

void api_bt_get_hfphf_status(uint8_t addr[6], api_hfp_call_status_t *out_buff)

Get whether the hf of the corresponding address is on the call:

uint8_t api_bt_get_hfphf_has_voice(uint8_t addr[6])

Get the profile connection flag for API layer records:

uint16 api_bt_get_profile_connflag(uint8_t addr[6])

Get the corresponding ACL item for the API layer:

api_bth_acl_base_item_t *api_bt_get_acl_item(uint8_t addr[6])

Get unused ACL items for the API layer:

api_bth_acl_base_item_t *api_bt_get_unused_acl_item(void)

Get the corresponding SCO item for the API layer:

uint8_t api_bt_get_has_sco_item(uint8_t addr[6])

Get whether A2DP sink is playing or not:

uint8_t api_bt_get_a2dpsnk_is_playing(uint8_t addr[6])

Get whether the current hf is on the call:

uint8_t api_bt_get_currernt_has_voice(void)

Get whether the current connection has a sco link:

uint8_t api_bt_get_currernt_has_sco(void)

(2) tlkapi_bt_recon

Macro definition:

Define reconnection ACL timeout:

#define RECON_ACL_TIMEOUT        5000

Define the default timeout time for the reconnection process:

#define API_BTREC_DEFAULT_TIME    30

Defines the default number of timeouts for the reconnection process:

#define API_BTREC_DEFAULT_COUNTS  2

Data structures:

Define the enumeration types for each state during the reconnection process:

typedef enum {
    BTRECON_STATE_NONE      =0,
    BTRECON_STATE_CONNETING =1,
    BTRECON_STATE_SUCCESS   =2, BTRECON_STATE_CANCEL_BYLOCAL=3,
    BTRECON_STATE_WAIT_MASTER      =4,
    BTRECON_STATE_TIMEOUT   =5,
    BTRECON_STATE_COUNTS_MAX=6,
    BTRECON_STATE_RETRY      =7,
    BTRECON_STATE_WAITCONN_PROFILE =8, 
    BTRECON_STATE_NEW_DEV          =9,
    BTRECON_STATE_POSITIVE_FALSE   =10, 
    BTRECON_STATE_PAGE_SUCCESS     =11,
    BTRECON_STATE_MAX
} btrecon_state_e;

Define the configuration reconnection parameter structure type:

  • con_addr[6]: Reconnection address

  • remote_service: Profile that needs to be reconnected

typedef struct {
    uint8_t   con_addr[6]; 
    uint16_t  remote_service;  
} api_btrecon_config_t;

Define the reconnection item structure type:

  • en: Whether or not to enable the reconnection
  • state: Reconnection state
  • recon_info: Reconnection configuration information
  • cur_con_cnt: Current number of reconnection
  • start_1s_flag: Reconnection process 1s timing flag
  • ticks: Reconnection ticks
  • set_con_cnt: Set the number of reconnection
  • set_con_time_s: Set the reconnection time
  • hf_channel: hfp channel
  • try_conn_profile: Profile that have tried to reconnect
typedef struct {
    bool   en        :1;
    btrecon_state_e   state;         
    api_btrecon_config_t  recon_info;      
    uint8_t     cur_con_cnt;
    uint16_t    start_1s_flag;   
    uint32_t    ticks;           
    uint8_t     set_con_cnt;     
    uint16_t    set_con_time_s;
    uint8_t     hf_channel;
    uint16_t    try_conn_profile;
} api_btrecon_item_t;

Interface definition:

API layer reconnection: profile connect processing function

void api_btrec_profile_conn_func(uint16_t acl_handle, uint8_t status, uint8_t prof_type)

API layer reconnection: profile disconnect processing function

void api_btrec_profile_disconn_func(uint16_t acl_handle, uint8_t prof_type)

API layer reconnection: get hf channel processing function

void api_btrec_sdp_set_hf_channel_func(uint16_t acl_handle, uint8_t hf_channel)

API layer reconnection: encryption completion processing function

void api_btrec_encrypt_complete_func(uint16_t acl_handle)

API layer reconnection: ACL complete processing function

void api_btrec_aclconn_complete_func(uint8_t addr[6], uint8_t status, uint8_t positive)

API layer reconnection: ACL disconnect processing function

void api_btrec_acldisc_complete_func(uint8_t addr[6], uint8_t reason)

API layer reconnection: authentication completion processing function

void api_btrec_authen_complete_func(uint8_t addr[6], uint8_t reason)

API layer reconnection: get hf channel function

uint8_t api_btrec_get_hf_channel(void)

Setting the reconnection state:

void api_btrec_set_state(btrecon_state_e data)

Get the reconnection state:

btrecon_state_e api_btrec_get_state(void)

Start reconnection:

void api_btrec_start(api_btrecon_config_t data)

Stop reconnection:

void api_btrec_stop(void)

Clear the reconnection information:

void api_btrec_clear(void)

Set the reconnection timeout time:

void api_btrec_set_con_time(uint16_t timeout_s)

Get the reconnection timeout time:

uint16_t api_btrec_get_con_time(void)

Get the remaining time of reconnection:

uint16_t api_btrec_get_con_remaining_time(void)

Set the number of reconnection timeouts:

void api_btrec_set_con_counts(uint8_t try_ticks)

Get the number of reconnection timeouts:

uint8_t api_btrec_get_con_counts(void)

Get the remaining number of reconnection:

uint16_t api_btrec_get_con_remaining_counts(void)

Reconnection process:

void api_btrec_process(void)

(3) tlkapi_bt_scan

Macro definition:

Define the time when scan is always on:

#define    API_BTSCAN_FOREVER_TIME  0xFFFF

Define the default start scan time锛�

#define    API_BTSCAN_DEFAULT_TIME  (2*60)

Data structures:

Define the structure types for each state of the scan process:

typedef enum {
    BTSCAN_STATE_NONE =0,
    BTSCAN_STATE_SCANNING =1,
BTSCAN_STATE_CANCEL_BYLOCAL=2,
    BTSCAN_STATE_TIMEOUT =3,
BTSCAN_STATE_MAX
} btscan_state_e;

Define the scan item structure type:

  • inqscan_en: Whether inquiry scan is enabled or not
  • pagescan_en: Whether page scan is enabled or not
  • inq_state: Inquiry scan state
  • page_state: Page scan state
  • set_inqscan_time_s: Timeout set by inquiry scan
  • set_pagescan_time_s: Timeout set by page scan
  • start_inqscan_1s_flag: Running time of inquiry scan
  • start_pagescan_1s_flag: Running time of page scan
  • ticks: Ticks flag
typedef struct {
    bool     inqscan_en    :1;
    bool     pagescan_en   :1;
    btscan_state_e     inq_state; 
    btscan_state_e     page_state;
    uint16_t  set_inqscan_time_s; 
    uint16_t  set_pagescan_time_s;
    uint16_t  start_inqscan_1s_flag; 
    uint16_t  start_pagescan_1s_flag;
    uint32_t  ticks;
} api_btscan_item_t;

Interface definition:

API layer scan: ACL connect completion processing function:

void api_btscan_aclconn_complete_func(uint8_t addr[6], uint8_t status, uint8_t positive)

Set the inquiry scan state:

void api_btscan_set_inq_state(btscan_state_e data)

Get the inquiry scan state:

btscan_state_e api_btscan_get_inq_state(void)

Set the inquiry scan timeout:

void api_btscan_set_inq_time(uint16_t timeout_s)

Get the inquiry scan timeout:

uint16_t api_btscan_get_inq_time(void)

Refresh inquiry scan running time:

void api_btscan_refresh_inq_start_tick(void)

Get the remaining time of inquiry scan:

uint16_t api_btscan_get_inq_remaining_time(void)

Set the page scan state:

void api_btscan_set_page_state(btscan_state_e data)

Get the page scan state:

btscan_state_e api_btscan_get_page_state(void)

Set the page scan timeout:

void api_btscan_set_page_time(uint16_t timeout_s)

Get the page scan timeout:

uint16_t api_btscan_get_page_time(void)

Refresh page scan running time:

void api_btscan_refresh_page_start_tick(void)

Get the remaining time of page scan:

uint16_t api_btscan_get_page_remaining_time(void)

Enable or disable scan:

void api_btscan_enable(bb_scan_en_t en)

API layer scan process:

void api_btscan_process(void)

(4) tlkapi_btmisc_event_user

Data structures:

Define the bt_misc corresponding event ID enumeration type for the app layer to call:

typedef enum {
BTMISC_EVENTID_RECON_SUCCESS= 0,
BTMISC_EVENTID_RECON_CANCEL_BYLOCAL, 
BTMISC_EVENTID_RECON_WAIT_MASTER, 
BTMISC_EVENTID_RECON_TIMEOUT,
BTMISC_EVENTID_RECON_COUNTS_MAX,
BTMISC_EVENTID_RECON_RETRY,  
BTMISC_EVENTID_RECON_WAITECONN_PROFILE,               
BTMISC_EVENTID_RECON_NEW_DEVICE,
BTMISC_EVENTID_RECON_POSITIVE_FALSE,
BTMISC_EVENTID_RECON_PAGE_SUCCESS,
BTMISC_EVENTID_SCAN_INQ_CANCEL_BYLOCAL,
BTMISC_EVENTID_SCAN_PAGE_CANCEL_BYLOCAL,
BTMISC_EVENTID_SCAN_INQ_TIMEOUT,
BTMISC_EVENTID_SCAN_PAGE_TIMEOUT,
BTMISC_EVENTID_MAX
}btmisc_eventid_e;

Interface definition:

API layer callback event execution interface. After the APP layer registers related events, this function is called at the API layer:

void api_btmisc_event_user_exec(btmisc_eventid_e evtID, uint8_t *pData, uint16_t dataLen)

Provide an interface for the APP layer to remove API layer callback events:

void api_btmisc_event_user_remove(btmisc_eventid_e evtID)

Provide an interface for the APP layer to clear all API layer callback events:

void api_btmisc_event_user_clear_all(void)

Provide an interface for the APP layer to register API layer callback events:

void api_btmisc_event_user_register(btmisc_eventid_e evtID, btmisc_event_func func)

tlkapi_btp

The tlkapi_btp stores events about profiles from the host layer, which can be registered to the APP layer for processing through the registration function in tlkapi_btp_event_user.

Among them, tlkapi_profile_evt stores the profile connection, disconnection and other events. According to the type of profile, the processing functions of different profile events are defined in different files, which are represented by file name. For example, in the hand-free protocol, the processing functions of the phone are defined in the tlkapi_hfp file. When the volume of the phone changes (as the hf role), the host event will call the api_hfphf_volume_changed_callback function in this file for processing.

tlkapi_btp

Interface description:

(1) tlkapi_a2dp

Data structures:

Define the A2DP parameter structure type:

  • acl_handle: acl handle
  • status: A2DP status
  • chn_mode: channel mode
  • frequence: A2DP sampling rate
  • aac_obj_type: AAC object type
  • aac_bit_rate: bit rate
typedef struct {
    uint16_t               acl_handle;
    BTP_A2DP_STATUS_ENUM   status;
BTP_A2DP_CHN_MODE_ENUM chn_mode;

    BTP_A2DP_CODEC_ENUM  codec;
    uint16_t               frequence;
    uint8_t                aac_obj_type;
    uint32_t               aac_bit_rate; 
} api_a2dp_base_item_t;

typedef struct {
    api_a2dp_base_item_t item[TLK_BT_A2DP_MAX_NUMB];
} api_a2dp_base_info_t;

Interface definition:

Get a2dp item by handle:

api_a2dp_base_item_t *api_a2dp_get_item_by_handle(uint16_t handle, api_a2dp_base_info_t *p_buff)

a2dp event callback: source codec changed

int api_a2dpsrc_codec_changed_callback(uint8_t *p_data, uint16_t data_len)

a2dp event callback: sink codec changed

int api_a2dpsnk_codec_changed_callback(uint8_t *p_data, uint16_t data_len)

a2dp event callback: source status changed

int api_a2dpsrc_status_changed_callback(uint8_t *p_data, uint16_t data_len)

a2dp event callback: sink status changed

int api_a2dpsnk_status_changed_callback(uint8_t *p_data, uint16_t data_len)

Clear a2dp item structure data:

int api_a2dp_base_info_clear(void *p_data, uint16_t size)

Register a2dp item interface:

int api_a2dp_base_info_register(api_a2dp_base_info_t *src, api_a2dp_base_info_t *snk)

Remove a2dp item interface:

int api_a2dp_base_info_remove(bool src, bool snk)

a2dp initialisation function:

int api_btp_a2dp_evt_init(void)

(2) tlkapi_avrcp

Data structures:

Define the AVRCP parameter structure type:

  • acl_handle: ACL handle
  • cur_volume: Current volume
typedef struct {
    uint16_t acl_handle;
    uint8_t  cur_volume;
}api_avrcp_base_item_t;

typedef struct{
    api_avrcp_base_item_t item[TLK_BT_AVRCP_MAX_NUMB];
}api_avrcp_base_info_t;

Interface definition:

Get avrcp item by handle:

api_avrcp_base_item_t *api_avrcp_get_item_by_handle(uint16_t handle, api_avrcp_base_info_t *p_buff)

avrcp event callback: volume change

void api_avrcp_change_volume_callback(uint16_t acl_handle, uint8_t volume)

Set current volume to the host layer:

void api_avrcp_set_host_note_cur_music_vol(uint16_t handle, uint8_t vol_perc)

Clear avrcp item structure data:

int api_avrcp_baseInfo_clear(void *p_data, uint16_t size)

Register avrcp item interface:

int api_avrcp_baseInfo_register(api_avrcp_base_info_t *avrcp_info)

Remove avrcp item interface:

int api_avrcp_baseInfo_remove(void)

avrcp initialisation function:

int api_btp_avrcp_evt_init(void)

(3) tlkapi_btp_event_user

Interface definition:

API layer callback event execution interface. After the APP layer registers related events, this function is called at the API layer:

void api_btp_event_user_exec(uint16_t evtID, uint8_t *pData, uint16_t dataLen)

Provide an interface for the APP layer to remove API layer callback events:

void api_btp_event_user_remove(uint16_t evtID)

Provide an interface for the APP layer to clear all API layer callback events:

void api_btp_event_user_clear_all(void)

Provide an interface for the APP layer to register API layer callback events:

void api_btp_event_user_register(uint16_t evtID, btp_event_func func)

(4) tlkapi_hfp

Data structures:

Define call setup enumeration type:

typedef enum _hshf_callsetup_enum{
    CALL_SETUP_NONE          = 0,
    CALL_SETUP_INCOMING     = 1,
    CALL_SETUP_OUTGOING     = 2,
    CALL_SETUP_REMOTE_ALERT = 3,
    CALL_SETUP_ESTABLISHED   = 4
}hfp_callsetup_status_e;

Define call status enumeration type:

typedef enum _hshf_call_enum{
CALL_NONE                = 0,
CALL_ESTABLISHED         = 1
}hfp_call_status_e;

Define call status structure type:

  • status: call status
  • status_setup: call setup
  • status_held: call held
typedef struct {
    uint8_t  status;
    uint8_t  status_setup;
    uint8_t  status_held;
}api_hfp_call_status_t;

Define the HFP parameter structure type:

  • acl_handle: ACL handle
  • codec: hfp codec
  • call: call status
typedef struct {
    uint16_t acl_handle;
    uint8_t  codec;
    api_hfp_call_status_t call;

}api_hfp_base_item_t;

typedef struct{
    api_hfp_base_item_t item[TLK_BT_HFP_MAX_NUMB];
}api_hfp_base_info_t;

Interface definition:

Get hfp item by handle:

api_hfp_base_item_t *api_hf_get_item_by_handle(uint8_t handle, api_hfp_base_info_t *p_buff)

hfp event callback: hf codec changed

int api_hfphf_codec_changed_callback(uint8_t *p_data, uint16_t data_len)

hfp event callback: hf status changed

int api_hfphf_status_changed_callback(uint8_t *p_data, uint16_t data_len)

hfp event callback: hf call status changed

int api_hfphf_call_status_changed_callback(uint8_t *p_data, uint16_t data_len)

hfp event callback: hf volume changed

int api_hfphf_volume_changed_callback(uint8_t *p_data, uint16_t data_len)

Send battery level interface:

int api_hfphf_send_battery_level(uint8_t batt_level)

Clear hfp item structure data:

int api_hfp_baseInfo_clear(void *p_data, uint16_t size)

Register hfp item interface:

int api_hfp_baseInfo_register(api_hfp_base_info_t *hf, api_hfp_base_info_t *ag)

Remove hfp item interface:

int api_hfp_baseInfo_remove(bool hf, bool ag)

hfp initialisation function:

int api_btp_hfp_evt_init(void)

Register spp receive data callback:

void api_btp_spp_RecvDataCB_register(api_btp_spp_rx_event_t func)

(5) tlkapi_profile_evt

Data structures:

Define the profile connection parameter structure type:

  • acl_handle:ACL handle
  • btaddr[6]: Connection address
  • conn_flag: Connected profile protocol
  • remote_services: Remote services
typedef struct {
    uint16_t  acl_handle;
    uint8_t   btaddr[6];
    uint16_t  conn_flag;
    uint8_t   remote_services;
}api_profile_item_t;

typedef struct{
    api_profile_item_t item[TLK_STK_BTACl_NUMB];
}api_profile_base_info_t;

Interface definition:

Get profile item by handle:

api_profile_item_t *api_profile_get_item_by_handle(uint16_t handle, api_profile_base_info_t *p_buff)

Get unused profile item:

api_profile_item_t *api_profile_get_item_unused(api_profile_base_info_t *p_buff)

profile event callback: profile request

int api_btp_evtid_profile_request_callback(uint8_t *p_data, uint16_t data_len)

profile event callback: profile connect

int api_btp_evtid_profile_connect_callback(uint8_t *p_data, uint16_t data_len)

profile event callback: profile disconnect

int api_btp_evtid_profile_disconn_callback(uint8_t *p_data, uint16_t data_len)

profile event callback: profile service

int api_btp_evtid_profile_service_callback(uint8_t *p_data, uint16_t data_len)

profile event callback: profile channel

int api_btp_evtid_profile_channel_callback(uint8_t *p_data, uint16_t data_len)

Clear profile item structure data:

int api_profile_baseInfo_clear(void)

Register profile item interface:

int api_profile_baseInfo_register(api_profile_base_info_t *profile_info)

Remove profile item interface:

int api_profile_baseInfo_remove(void)

profile initialisation function:

int api_btp_profile_evt_init(void)  

tlkapi_flash

The tlkapi_nvds store functions related to flash initialization, read/write, delete, and other functions. The flash intervals are divided, and specific usage can be found in the APP layer's app_flash.c and other files.

tlkapi_flash

(1) tlkapi_nvds

Macro definition:

Define default flash page size:

#ifndef FLASH_PAGE_SIZE
#define FLASH_PAGE_SIZE  256
#endif

Define default flash sector size:

#ifndef FLASH_SECTOR_SIZE
#define FLASH_SECTOR_SIZE    4096
#endif

Define the two flash sector addresses used by default:

#ifndef FLASH_SEC_0_ADDR
#define FLASH_SEC_0_ADDR    0xea000
#endif
#ifndef FLASH_SEC_1_ADDR
#define FLASH_SEC_1_ADDR    0xeb000
#endif

Define the NVDS storage unit size:

#ifndef NVDS_STORE_UINT_SIZE
#define NVDS_STORE_UINT_SIZE  64
#endif

Define the maximum number of BT devices that can be connected by default:

#ifndef MAX_PAIRED_DEVICE_NUM
#define MAX_PAIRED_DEVICE_NUM  5
#endif

Define the maximum number of dongle devices that can be connected by default:

#ifndef MAX_PAIRED_DONGLE_NUM
#define MAX_PAIRED_DONGLE_NUM  1
#endif

Define the number of NVDS storage blocks:

#define NVDS_STORE_NUMBE (FLASH_SECTOR_SIZE / NVDS_STORE_UINT_SIZE)

Define the NVDS valid frame header:

#define NVDS_SECTOR_VALID_MAGIC  0xcafecafe

Define NVDS offset:

#define TAG_OFFSET_INVALID   0

Data structures:

Define the return value of NVDS error code:

typedef enum
{
    NVDS_ERR_SUCCESS = 0,
    NVDS_ERR_INVALID_STATE,
    NVDS_ERR_INVALID_PARA,
    NVDS_ERR_INVALID_LEN,
    NVDS_ERR_DATA_CORRUPT,
    NVDS_ERR_INVALID_SECTION,
}nvds_err_e;

Define NVDS storage data TAG ID flag:

typedef enum
{
#if !PROJ_CC_DONGLE
    NVDS_TAG_BT_GLOBAL = 0,
    NVDS_TAG_DFU_IMG_INFO,
    NVDS_TAG_BT_BONDED_0,
    NVDS_TAG_BT_BONDED_MAX_NUM = NVDS_TAG_BT_BONDED_0 + (MAX_PAIRED_DEVICE_NUM - 1),

    NVDS_TAG_2P4G_GLOBAL_CFG,
    NVDS_TAG_DONGLE_0,
    NVDS_TAG_DONGLE_MAX_NUM = NVDS_TAG_DONGLE_0 +  (MAX_PAIRED_DONGLE_NUM - 1),

    NVDS_TAG_APP_CFG,
    NVDS_TAG_MAX,
#else //dongle
    NVDS_TAG_DFU_IMG_INFO = 0,
    NVDS_TAG_CONIFG_NAME,
    NVDS_TAG_MAX,
#endif
}nvds_tag_id_t;

Define the NVDS header type:

typedef struct
{
    uint32_t magic;
    uint32_t erase_cnt;
}__attribute__((packed)) sector_header_t;

Define the NVDS tag header type:

typedef struct
{
    uint8_t  empty      : 1;
    uint8_t  valid       : 1;
    uint8_t  rsv_bits     : 6;
    uint8_t  tag;
    uint8_t  crc;
    uint8_t  len;
}__attribute__((packed)) tag_header_t;

Define the NVDS structure type:

typedef struct
{
    uint32_t chip_id;
    uint32_t sector_addr;
    uint32_t erase_cnt;
    uint8_t  next_store_unit;
    uint8_t  tag_offset[NVDS_TAG_MAX];
}nvds_t;

Define flash protection structure type:

typedef struct
{
    uint32_t addr;
    uint8_t  bp;
    uint8_t  cmp;
}flash_protect_setting_t;

Interface definition:

Get flash capacity id:

uint8_t capacity_id(void)

NVDS data reset:

void api_nvds_reset(void)   

NVDS initialization:

nvds_err_e api_nvds_init(void)  

Remove sector data:

nvds_err_e api_nvds_sector_move(void)   

NVDS write data:

nvds_err_e api_nvds_write(nvds_tag_id_t tag_id, void* data, uint16_t size)  

NVDS read data:

nvds_err_e api_nvds_read(nvds_tag_id_t tag_id, void* data, uint16_t size)   

NVDS delete related data:

nvds_err_e api_nvds_delete(nvds_tag_id_t tag_id)    

Get flash size:

uint32_t api_nvds_flash_size(void)  

Enable and disable flash protection interface:

nvds_err_e api_nvds_flash_protect(uint32_t size)    

tlkapi_key

The tlkapi_key stores functions related to key, such as key addition and deletion, key scanning, processing functions, mode mechanism, table parameter table registration, etc. The specific functions can refer to the declaration in the header file, and the usage method is described in the introduction of the APP layer in section 3.

The tlkapi_key_bt_func stores functions related to BT music or phone control, such as the previous song, the next song, answer, hang up and other function interfaces. This function will be secondary encapsulated at the APP layer, that is, the APP layer judges the relevant state, so as to choose which method to call.

tlkapi_key

Interface description:

(1) tlkapi_key

Macro definition:

Define the default number of keys:

#ifndef API_KEY_NUM_MAX
#define API_KEY_NUM_MAX       1
#endif

Define the default key valid voltage level:

#ifndef KEY_VALID_LEVEL
#define KEY_VALID_LEVEL       1
#endif

Define the minimum long press time (in 10ms):

#define  MIN_LONG_CNT         100

Define the minimum hold time (in 10ms):

#define  MIN_HOLD_CNT         100

Define the minimum extra-long press time (in 10ms):

#define  MIN_LONG_LONG_CNT  400

Define the minimum interval for determining non-consecutive clicks (in 10ms):

#define  MIN_INTERVEL         10

Data structures:

Define the key state enumeration type:

typedef enum {
    KEY_DOWN = 0, 
    KEY_HOLD = 1, 
    KEY_LONG = 2, 
    KEY_RELEASE = 3
} key_state_e;

Define key events:

typedef enum {
    KEY_SHORT_EVT    = 0, 
    KEY_DCLICK_EVT   = 1, 
    KEY_TCLICK_EVT   = 2, 
    KEY_HOLD_EVT     = 3,
    KEY_LONG_EVT     = 4,
    KEY_LONGLONG_EVT = 5,
    KEY_EVT_FLAG_MAX
} key_evt_flag_e;

Define the key event flag:

typedef union {
    uint32_t _flg; /**< 32bit */

    struct {
        /**< byte 0 */
        uint8_t _short    :1; /**< 0 */
        uint8_t dclick    :1; /**< 1 */
        uint8_t tclick     :1; /**< 2 */
        uint8_t hold      :1; /**< 3 */
        uint8_t _long     :1; /**< 4 */
        uint8_t long_long  :1; /**< 5 */
        uint8_t res       :2;
    /**< byte 1~3 */
    };
} key_event_flg_u;

Define key event class:

typedef struct {
    uint8_t  _short;
    uint8_t  dclick; 
    uint8_t  tclick; 
    uint8_t  hold; 
    uint8_t  _long;
    uint8_t  longlong; 
} key_event_class_t;

Define the event processing structure type:

typedef struct {
    key_event_flg_u   evt_occurr;
    key_event_class_t  mode;
} key_event_t;

Define the key configuration structure type:

typedef struct {
    API_GPIO_TYPE_DEF key_pin;
    API_GPIO_TYPE_DEF key_out_pin; 
    uint8_t       key_down_level;
    uint32_t      key_hold_cnt; 
    uint32_t      key_long_cnt; 
    uint32_t      key_long_long_cnt;
    uint32_t      key_intervel_cnt; 
} key_config_t;

Define the key mode event table:

typedef struct {
    uint16_t  table_count;
    key_func *p_key_table;
} key_event_table_t;

Define collection of event callbacks:

typedef struct {
    key_func  _short;
    key_func  dclick;
    key_func  tclick; 
    key_func  hold; 
    key_func  _long; 
    key_func  longlong; 
} key_event_extra_t;

Define the key control information type:

typedef struct {
    uint8_t       key_id;
    uint32_t      key_down_cnt;
    uint32_t      key_up_cnt; 
    uint32_t      key_click_cnt;
    key_state_e   key_state;
    key_config_t  key_info; 
} key_ctl_info_t;

Interface definition:

Get key ID by GPIO:

uint8_t api_get_key_id_by_gpio(API_GPIO_TYPE_DEF gpio, API_GPIO_TYPE_DEF out_gpio)

Add key:

uint8_t api_key_add(key_config_t *p)

Remove key:

uint8_t api_key_remove(uint8_t key_id)

Register key mode event table:

uint8_t api_key_evt_table_register(const key_func *table, uint16_t tbl_mode_num)

Remove key mode event table:

uint8_t api_key_evt_table_remove(void)

Register event callback:

uint8_t api_key_evt_happen_register(uint8_t key_id, key_event_extra_t *p_event)

Remove event callback:

uint8_t api_key_evt_happen_remove(uint8_t key_id)

Determine whether to register the key scan function:

uint8_t api_key_scan_not_exec_cb_register(key_func p_event)

Get event callback by mode:

uint8_t api_get_key_evt_by_mode(uint16_t mode, key_func* out_func)

Execute the corresponding event callback for mode:

uint8_t api_key_evt_func_execute(uint16_t mode_index)

Set the key current mode:

uint8_t api_set_key_curr_evt_mode(uint8_t key_id, key_event_class_t *new_mode)

Get the key current mode:

uint8_t api_get_key_curr_evt_mode(uint8_t key_id, key_event_class_t *out_mode)

Key scan function:

void api_key_scan(void)

Key process:

void api_key_process(void)

(2) tlkapi_key_bt_func

Interface definition:

BT call answer:

void api_key_func_bt_call_answer(void)

BT call hang up:

void api_key_func_bt_call_end(void)

BT call rejection:

void api_key_func_bt_reject_call(void)

BT accepts incoming calls from third-party and holds up existing calls:

void api_key_func_accept_wait_hold_active(void)

BT third-party call hangs up and wakes up waiting:

void api_key_func_end_active_resume_hold(void)

BT music next song:

void api_key_func_bt_music_forward(void)

BT music previous song:

void api_key_func_bt_music_backward(void)

BT music pauses playback:

void api_key_func_bt_music_play_pause(void)

BT voice assistant program:

void api_key_func_trigger_voice_assistant(void)

BT siri processing function:

void api_key_hfp_iphone_siri_handler(void)

BT third-party calls reject and keep existing calls:

void api_key_func_bt_reject_wait_keep_active(void)

tlkapi_led

The tlkapi_led stores functions related to LED light effect, such as light addition and deletion, light effect setting and insertion, etc. The specific functions can refer to the declaration in the header file, and the usage method is described in the introduction of the APP layer in section 3.

tlkapi_led

Interface description:

(1) tlkapi_led

Macro definition:

Define the maximum time for LED light effect:

#define TIME_FOREVER    0xffff

Define the minimum time for LED light effect:

#define TIME_NO_TIME    0x0000

Define the LED always flashing:

#define FLASH_FOREVER   0xffff

Define unset colour flag:

#define LED_COLOR_NOT_SET   0xffff

Define maximum colour duty ratio:

#define LED_MAX_COLOR       BRT_HIGHT_LIGHT_FULL_DUTY

Define the default color:

#define LED_DEFAULE_COLOR (LED_MAX_COLOR)

Data structures:

Define the source of parameters for setting the LED light effect:

typedef enum {
    DEFAULT_PATTERN = 0,
    USER_PATTERN = !DEFAULT_PATTERN
} led_patt_from_e

Define the parameter table index for the LED light effect:

typedef enum {
    LED_PATTERN_LED_OFF = 0,
    LED_PATTERN_LED_ON = 1,
    LED_PATTERN_LED_FLASH_SLOW = 2,
    LED_PATTERN_LED_FLASH_FAST = 3,
    LED_PATTERN_LED_FLASH_PAIR = 4,
    LED_PATTERN_LED_IDLE = 5,
    LED_PATTERN_LED_FAST_FLASH_1_TIME = 6,
    LED_PATTERN_LED_FAST_FLASH_2_TIMES = 7,
    LED_PATTERN_LED_SLOW_FLASH_2_TIMES = 8,
    LED_PATTERN_LED_FAST_FLASH_3_TIMES = 9,
    LED_PATTERN_LED_ON_500MS = 10,
    LED_PATTERN_LED_ON_1S = 11,
    LED_PATTERN_LED_SLOW_FLASH_3_TIMES = 12,
    LED_PATTERN_LED_BREATH_ON_OFF = 13,
    LED_PATTERN_LED_BREATH_ON = 14,
    LED_PATTERN_LED_BREATH_OFF = 15,
    LED_PATTERN_LED_BREATH_ONE_TIME = 16,
    LED_PATTERN_LED_BREATH_TWO_TIMES = 17,
    LED_PATTERN_LED_BREATH_THREE_TIMES = 18,
    LED_PATTERN_LED_BREATH = 19,
    LED_PATTERN_LED_BREATH_PAIR = 20,
    LED_PATTERN_LED_HIGH_LIGHT_FLASH_1_TIME = 21,
    LED_PATTERN_LED_MAX_STATE,
} led_pattern_index_e;

Define the LED light effect configuration structure type:

  • led_pin: LED gpio
  • led_on_level: LED valid level
  • pwmid: pwm id used
typedef struct {
    API_GPIO_TYPE_DEF  led_pin;
    uint8_t            led_on_level;
    pwm_id_e           pwmid;
} led_config_t;

Define the LED setting parameter type:

  • behavior: LED behavior, flashing or breathing lights
  • duty: duty ratio
  • latency: delay time
  • flash_times: flash times
  • keep_high: high keep time
  • keep_low: low keep time
typedef struct {
    uint16_t behavior; 
    uint16_t duty; 
    uint16_t latency; 
    uint16_t flash_times; 
    uint16_t keep_high; 
    uint16_t keep_low;
} led_pattern_t;

Define the LED state structure:

  • state: current running state
  • flags: led related flag
  • lum: current run step record
  • cnt: number of runs
  • ref: time count flag
typedef struct {
    uint16_t state;
    uint16_t flags;
    uint16_t lum;
    uint16_t cnt;
    uint32_t ref;
} lum_state_t;

Define LED parameter table structure type:

typedef struct {
    uint16_t      pattern_count;
    led_pattern_t  *p_pattern_table;
} led_pattern_table_t;

Define LED color related settings:

typedef struct {
    uint16_t color_value[API_LED_NUM_MAX];
} led_color_t;
typedef struct {
    uint16_t     color_count;
    led_color_t   *p_color_table;
} led_color_table_t;

Define LED behavior related structure:

typedef struct {
    led_pattern_t  *p_pattern;
    lum_state_t   lum_state;
    led_pattern_t  *p_insert_pattern; 
    lum_state_t   insert_lum_state;
} led_env_t;

Define LED control information:

typedef struct {
    uint8_t       led_id;
    uint16_t      color_duty;
    led_env_t     led_env;
    led_config_t   led_info; 
} led_ctl_info_t;

Interface definition:

Add LED:

uint8_t api_led_add(led_config_t *p)

Remove LED:

uint8_t api_led_remove(uint8_t led_id)

Get LED ID by GPIO:

uint8_t api_get_led_id_by_gpio(API_GPIO_TYPE_DEF gpio)

Register LED user parameters table:

uint8_t api_led_user_patterns_register(led_pattern_table_t *table, uint16_t tbl_mode_num)

Remove LED user parameters table:

uint8_t api_led_user_patterns_remove(void)

Set LED colour duty:

void api_set_led_color_duty(led_color_t *color)

Get LED colour duty:

void api_get_led_color_duty(led_color_t *out_color)

Get LED parameter duty:

uint16_t api_led_get_pattern_duty(int led_id)

Get the current lum value of LED:

uint16_t api_led_get_current_lum(int led_id)

Set LED light effect:

uint8_t api_led_pattern_set(int led_id, int patt, led_patt_from_e is_user_patt)

Insert LED light effect:

uint8_t api_led_pattern_insert(int led_id, int patt, led_patt_from_e is_user_patt)

Determine whether the LED is currently running with an inserted light effect:

uint8_t api_led_is_insert_pattern_running(int led_id)

LED refresh process:

void api_led_refresh_process(void)

Earbuds UI and APP Layer Function Description

LED Function

(1) LED Add and Remove

Add and remove LEDs by calling the API layer functions api_led_add and api_led_remove. Among them, the parameter of the added function is the structure pointer of type led_config_t *, and the type is defined as follows:

typedef struct {
    API_GPIO_TYPE_DEF led_pin;
    uint8_t     led_on_level; /**< 0 or 1    default:LED_VALID_LEVEL   */
    pwm_id_e  pwmid;      /**< io pwm */
} led_config_t;

The led_pin is the IO pin for the LED connection to the chip.

The led_on_level is the level at which the LED is lit, and can be set to high or low.

The pwmid is the pwm id used by this LED, and if the chip pin is corresponding to a fixed pwm id, there is no domain in the led_config_t structure of this chip.

In the demo program, there is a global variable named g_led_state, which has a led_id domain, this value is to facilitate the control of the lights at the APP layer, and the corresponding index can be stored with the corresponding ID when initializing. For example, the blue light index LED_INDEX_BLUE is defined as 1. g_led_state.led_id[LED_INDEX_BLUE] represents the ID of the light corresponding to the blue light, which can also be queried through the IO port using the api_get_led_id_by_gpio function of the API layer.

In addition, some of the LEDs provided by this SDK support the breathing light effect, so when using the LED add function in the API, the corresponding pins of the LED should support pwm, otherwise the initialisation may fail.

After the LEDs have been added, the api_led_refresh_process needs to be added to the main loop for the pwm refresh of the LEDs. The LED task in the APP layer is also added to the main loop for the state update.

(2) LED State Setting

The LED sets the state of the current light effect by calling the API layer function api_led_pattern_set. Use the api_led_pattern_insert function to insert a light effect, which has a higher execution priority than the api_led_pattern_set function, and returns to the previously set state when execution is complete. Therefore, when using insert, avoid using "forever" light effect parameters. Both functions at the API layer are valid for all initialized LEDs.

For example: change the LED state to always on, use api_led_pattern_set. Afterwards use api_led_pattern_insert to insert flash once again, at this time, the light effect will flash once and then return to the always on state.

In the demo program, three function interfaces are encapsulated:

uint8_t led_get_id(uint8_t index_flag);
void led_set_all_ctrl_pattern(int patt, led_patt_from_e is_user_patt);
void led_insert_all_ctrl_pattern(int patt, led_patt_from_e is_user_patt);

Among them, the first function is to get the ID added to g_led_state.led_id as mentioned in the previous subsection to facilitate the implementation of the APP layer's light effect control. The other two functions are to set the same state uniformly for the LEDs already added to g_led_state.led_id.

Both the functions of the two LED settings encapsulated in the API layer and the APP layer use the parameter type led_patt_from_e of the light effect, as described in the next section.

(3) LED Parameter Table Setting

In the two LED setting functions of the API layer (same for the APP layer):

uint8_t api_led_pattern_set(int led_id, int patt, led_patt_from_e is_user_patt);
uint8_t api_led_pattern_insert(int led_id,
                         int patt,
                         led_patt_from_e is_user_patt);

Both functions use the parameter patt (refer to 'led_pattern_t') and the parameter is_user_patt (refer to 'led_patt_from_e'), with the following two parameter types:

typedef struct {
    uint16_t behavior;  /**< zero is flash, non-zero is breath       */
    uint16_t duty;      /**< Duty for LED on                  */
    uint16_t latency;    /**< Off time                         */
    uint16_t flash_times; /**< Breath or flash counter              */
    uint16_t keep_high;  /**< unit is mS                        */
    uint16_t keep_low;  /**< unit is mS                        */
} led_pattern_t;

typedef enum {
    DEFAULT_PATTERN = 0, 
    USER_PATTERN = !DEFAULT_PATTERN
} led_patt_from_e;

When the value of the parameter is_user_patt is DEFAULT_PATTERN, the parameter patt is the index value of the parameter table provided by the API by default. When the value of the parameter is_user_patt is USER_PATTERN, the parameter patt is the index value of the parameter table registered by the user.

The parameter table is an array of type led_pattern_t, which uses api_led_user_patterns_register to register user-supplied parameters, and this function requires two parameters, namely the first address of the parameter table and the number of parameters. It allows the user to remove the user-registered parameter table using api_led_user_patterns_remove. Once the user written parameter table has been registered, the light effect can be set using USER_PATTERN.

The index values of the registry provided by the API are shown in "led_pattern_index_e", where the commonly used values are:

Light effect off: LED_PATTERN_LED_OFF

Light effect always on: LED_PATTERN_LED_ON

Breathing effect: LED_PATTERN_LED_BREATH

Light effect flashing fast: LED_PATTERN_LED_FLASH_FAST

Light effect flashing slow: LED_PATTERN_LED_FLASH_SLOW

(4) LED Synchronization

In the TWS demo program, when the master and slave earbuds are connected, the light effect of the master earbud changes and this state needs to be synchronized to the slave earbud, and the led_state_sync function is called in the task of the APP layer LED to synchronize the LED state at this time. At the same time, when the light effect of the master and slave earbuds is breathing light, due to the different execution efficiency of the master and slave earbuds main_loop, there may be a problem that the light effect of the two earbuds are not synchronized after a period of time, so the time of the last light effect change is detected in the led task, and if the light effect has not been synchronized for a long time, it will actively synchronize the light effect once, the function used is led_set_resync_auto_ticks(TRUE), and the final call of the light synchronization function is still led_state_sync.

(5) Demo Process

When powering on, there is a 1s light effect flashing, then if there is a code for low power detection, low power detection will be performed first, after the detection is complete the master earbud (or single earbud) will set the light effect according to the state and synchronize it to the slave earbud.

The specific process is as follows:

(a) Power on and initialize

After power on, call the initialisation function of the LED led_init() or led_evb_init() in the user_init() function. Configure the corresponding control IO, the effective level to control the light on, the corresponding pwmid used (if there is a pwmid domain) and call the API layer function api_led_add() to register and add, at this time add the corresponding api_led_refresh_process() function to the main loop to control LED flashing. The demo program adds the api_led_refresh_process() function to app_led_task() for polling execution.

In the initialization function, relevant programs can be added as needed to record the state. For example, in led_init(), the corresponding led_id is added to the global variable g_led_state.led_id for recording, which facilitates the subsequent call to the function led_set_all_ctrl_pattern() for unified control of all the LEDs.

(b) Set the power-on light effect

In the demo, at the end of the LED initialization, the led_set_power_on_state() is called to set the light effect of the 1s flashing at power on. In the app_led_task() function, there is the corresponding flag led_get_power_on_keep_flag(), which does not judge the system state to change the light effect.

(c) Low power light effect when power on

If the code enables the macro definition POWERON_CHECK_LOWPOWER for LED low power flashing, it will check whether the power is low at that time after the power on light effect has finished. If it is a low power state, the low power light effect will be flashed in priority and the master earbud in dual earbuds mode will wait for the detection process of the slave earbud.

(d) System state detection and synchronisation

In app_led_task(), after performing the above steps, the master earbud or single earbud will control the change of the light effect according to the state of the system, and when the master earbud changes the light effect, the current state will be sent to the slave earbud to achieve the synchronization of the master and slave earbuds. When the system state does not change for a long time, in order to synchronize the light effects on both sides of the master and slave earbuds, the master earbud will synchronize the light effect at regular intervals.

The current system has the following states:

SYS_STATE_CONNECTED: The default breathing light effect.

SYS_STATE_IDLE: The default slow flashing.

SYS_STATE_PAIR: The default fast flashing.

SYS_STATE_POWER_OFF: Power off state, no setting.

(e) Other light effects

Other light effects, such as enter charge case light effect, low power light effect, can call api_led_pattern_insert() or led_insert_all_ctrl_pattern() such inserted light effect, and restore to the corresponding light effect of the system state after execution.

Key Function

(1) Add and Remove key

The key is added and removed by calling the API layer functions api_key_add and api_key_remove. Among them, the parameter of the added function is the structure pointer of type key_config_t *, and the type is defined as follows :

typedef struct {
    API_GPIO_TYPE_DEF key_pin;
    API_GPIO_TYPE_DEF key_out_pin;
    unsigned char     key_down_level;
    unsigned int      key_hold_cnt;
    unsigned int      key_long_cnt;
    unsigned int      key_long_long_cnt;
    unsigned int      key_intervel_cnt;
} key_config_t;

key_pin: The IO pin for the key connection to the chip.

key_out_pin: If the connection is matrix scan, this value is the IO pin at the other end, otherwise it must be 0.

key_down_level: The level state when the key is pressed.

key_hold_cnt: Key hold event judgement time, unit 10 ms, minimum value MIN_HOLD_CNT.

key_long_cnt: Key long press event judgement time, unit 10 ms, minimum value MIN_LONG_CNT.

key_long_long_cnt: Key extra-long press time, unit 10 ms, minimum value MIN_LONG_LONG_CNT.

key_intervel_cnt: Judgement time for multiple key presses, unit 10 ms, minimum value MIN_INTERVEL.

Among them, the hold event does not coexist with the long press and extra-long press events, that is, the long press and extra-long press events will be triggered when the key_hold_cnt is 0. All times less than the minimum time are set to the default value.

After adding, the key scan function api_key_scan is added to the main loop for polling to detect key triggers, including click, double click, triple click, long press, extra-long press or hold event.

The key ID registered by the key can be obtained through the api_get_key_id_by_gpio function, the two parameters of this function are the key_pin and the key_out_pin.

(2) Key Event Handling Function

The API provides an interface for key event handling:

uint8_t api_key_evt_happen_register(uint8_t key_id, 
key_event_extra_t *p_event);
uint8_t api_key_evt_happen_remove(uint8_t key_id);

The register function is used to register, and key_id can be obtained with IO, p_event is the callback function corresponding to each event for this id, its type is key_event_extra_t:

typedef struct {
    key_func _short;   /**<  short key event extra                */
    key_func dclick;   /**<  double click event extra             */
    key_func tclick;   /**<  triple click event extra             */
    key_func hold;     /**<  hold key event extra                 */
    key_func _long;    /**<  long key event extra                 */
    key_func longlong; /**<  long long key event extra            */
} key_event_extra_t;

Once registration is complete using the registration function, put the api_key_process into the main loop for processing.

The api_key_evt_happen_remove function is used to remove the events for this id.

(3) Key Mode Mechanism

The key mode mechanism is designed to provide a flexible key response mode, the basic principle is that when the key event occurs, in its registered happen callback function, it performs different mode event processing according to its state to achieve a configurable key function. The API layer provides the registration and deletion of the parameter table for mode events, and the setting, acquisition, execution of the corresponding mode callback, as follows:

uint8_t api_key_evt_table_register(const key_func *table, uint16_t tbl_mode_num);
uint8_t api_key_evt_table_remove(void);
uint8_t api_set_key_curr_evt_mode(uint8_t key_id, key_event_class_t *new_mode);
uint8_t api_get_key_curr_evt_mode(uint8_t key_id, key_event_class_t *out_mode);
uint8_t api_key_evt_func_execute(uint16_t mode_index);

table: A collection of functions for each scene, such as the previous and next song in the demo program.

tbl_mode_num: The number of parameters of the table.

new_mode: The mode index value of each key set by the keyid.

out_mode: Get the mode index value of each key set by the Keyid.

mode_index: Get the key callback method corresponding to this index value.

In the demo, the table is s_key_evt_table and its corresponding index value is the enumeration key_evt_mode_e. It is registered through the function api_key_evt_table_register(s_key_evt_table, KEY_EVT_MODE_MAX), and the function key_set_default_key_mode is encapsulated at the app layer to initialise the mode of all keys, including single earbud mode and dual earbuds mode. Put the function key_refresh_key_mode_process into the main loop, and whenever the single and dual earbuds modes are switched, the mode will be refreshed, so as to realize different functions of key configuration in different modes.

In the APP layer demo, refresh mode function key_refresh_key_mode_process has a variable g_key_state.is_ updata_mode, this value is to set the key mode in order to handle scenarios other than single and dual earbuds mode switching, the specific process can be designed by yourself.

(4) Demo Introduction

When powering on, the key initialization, configuration pins, event trigger time and event callback will be performed first.

The process of the demo program is as follows:

(a) Key scan initialization

After power on, call the key initialization function key_init() or key_evb_init() in the user_init() function. Configure the corresponding control IO, the valid level of key presses, the judgment time of various events, etc. This part can refer to the introduction of the previous sections, and call the API layer function api_key_add() to register and add, at this time the corresponding api_key_scan() function is added to the main loop, and the corresponding action of the key will be detected. The demo program adds the api_key_scan() function to app_key_task() for polling execution.

Similar to the initialization process of LED, in key_init(), the corresponding key_id is added to the global variable g_key_state.need_master_exec_id for recording, to facilitate the subsequent judgement of whether the corresponding key event from the slave earbud need to be sent to the master earbud for processing.

(b) Key event initialization

After completing step (1), only the action of the key can be detected, and the callback processing of the corresponding event needs to be registered. In the demo program, the api_key_evt_happen_register() function is provided for registration, just register the interface of the callback function and add the api_key_process() function to the main loop. The demo program adds the api_key_process() function to app_key_task() for polling execution.

When the key is initialized, the APP layer records the enable flag of the key through the key_set_enable_flag() function, which is convenient for subsequent judgment on whether to perform the corresponding key processing. At the same time, the corresponding key enable flag bit can be obtained by key_get_enable_flag().

(c) Key mode event registration

In the key event callback of the demo program, the handling mechanism of the mode event is added to facilitate subsequent changes to the key function through other control methods. For details, please refer to the content of the previous section.

In the initialisation function key_init() or key_evb_init(), the function api_key_evt_table_register() is called to register the relevant event table, and its events include controlling the pause and play of music, controlling the answering and hanging up of the phone, etc., and the corresponding event can be queried by their corresponding enumeration type key_evt_mode_e, and the meaning of the enumeration type is the position of the corresponding event in the registered callback array. After successful registration, the corresponding event can be executed through the api_key_evt_func_execute() function, and the APP layer will re-encapsulate the key execution function as tws_key_act().

In the demo, the default mode of the key is set by the key_set_default_key_mode() function, and the default function of the key can be adjusted by modifying the content of this function, which is executed when the initialization function is powered on. If the key function of the last run is retained by other methods (such as saving to flash), the location of calling the key_set_default_key_mode() function can be adjusted.

The APP layer provides a refresh mechanism for mode event, just add the key_refresh_key_mode_process() function to the main loop. The demo program adds the key_refresh_key_mode_process() function to app_key_task() for polling execution. In the demo program, there are two scenarios for its refresh method:

The key mode is set through key_set_key_mode(), and the mode in TWS mode or single earbud mode can be set.

When TWS mode and single earbud mode are switched to each other, they are automatically refreshed to the corresponding mode in the key_refresh_key_mode_process() function.

(d) The happen event handling function in demo

After the initialisation of the key is complete, when the key are executed, if it is a single earbud state, it will be processed directly. If it is the dual earbuds state, the slave earbud event needs to be sent to the master earbud for processing.

When executing the event handling function "happen", it will first check whether the current earbud is the slave earbud. Taking a long press event for key 1 as an example, at the beginning of the function, the variable "is_need_snd_master" is assigned a value (mark 1 in the following figure), if it is the slave earbud and needs to be sent to the master earbud, this value will be true and at mark 2 it will be sent to the master earbud for processing, as shown below. The mark 3 in the figure below is the handling of special events, if this key is used to control the next song when there is no call, and hangs up the call when there is an incoming call, then the state of the incoming call can be regarded as a special state, and a special mode can be assigned for processing. See the code logic for details.

happen event handling function

happen event handling function

(5) Demo Key Default Function

The default function configuration of the key can be queried in key_set_default_key_mode, which is divided into left and right earbuds functions, single and dual earbuds functions. The left and right earbuds are distinguished by the key_is_left() function. g_key_event_tws records the function for dual earbuds and g_key_event_mono records the function for single earbud. If using the general development board, the left and right earbuds are distinguished by the USBID at the time of burning, when the USBID is 0x20, it is the left earbud, otherwise it is the right earbud.

In addition, the double click and long press of key1 have special functions for answering the phone. The default double click is to answer, and the long press is to hang up or reject. See the corresponding happen callback functions key1_evt_dclick_happen and key1_evt_long_happen.

Its default functions are as follows: mono - single mode, TWS - dual mode.

Key Mode Click Double click Triple click Long press(2~5s)
key1 mono left earbud none music play/pause none voice assistant
mono right earbud none music play/pause none voice assistant
tws left earbud volume - next song none voice assistant
tws right earbud volume + music play/pause none voice assistant
key2 mono left earbud single earbud saves user area flash data single earbud enters pairing mode single earbud enters factory configuration recovery mode single earbud power off
mono right earbud single earbud saves user area flash data single earbud enters pairing mode single earbud enters factory configuration recovery mode single earbud power off
tws left earbud single earbud saves user area flash data single earbud enters pairing mode single earbud enters factory configuration recovery mode single earbud power off
tws right earbud single earbud saves user area flash data single earbud enters pairing mode single earbud enters factory configuration recovery mode single earbud power off
key3 mono left earbud volume + previous song voice assistant music play/pause
mono right earbud volume - next song voice assistant music play/pause
tws left earbud volume - next song voice assistant music play/pause
tws right earbud volume + previous song voice assistant music play/pause
key4 mono left earbud BT phone answer BT phone hangs up none BT phone rejection
mono right earbud LE phone answer/hang up LE phone answer/hang up none LE phone rejection
tws left earbud BT phone answer BT phone hangs up none BT phone rejection
tws right earbud LE phone answer/hang up LE phone answer/hang up none LE phone rejection

Flash User Data Module

(1) Module Introduction

At the API layer, starting from two blocks of FLASH_SEC_0_ADDR and FLASH_SEC_1_ADDR with a size of FLASH_SECTOR_SIZE bytes in the flash memory, they are defined as the user storage area. Each storage area is divided into sections of NVDS_STORE_UINT_SIZE bytes, which are designated as the areas for refreshing and storing data, each storage area has NVDS_STORE_NUMBER sections. Different tag_id (enumeration type nvds_tag_id_t) are assigned to different data, and the maximum length of data for each tag is the length of the section NVDS_STORE_UINT_SIZE. New data can be saved to flash by adding tag_id. When data is stored, each save will refresh the save address of flash, incremented by the section of the storage area. If the FLASH_SEC_0_ADDR has reached its maximum address, the contents of FLASH_SEC_1_ADDR will be erased, saved and accumulated from the first section. When FLASH_SEC_1_ADDR has reached its maximum address, the contents of FLASH_SEC_0_ADDR will be erased, and so on. Users may adjust the information related to flash storage by modifying the relevant macros in app_config.h. For more information, please refer to the code.

After the earbuds are powered on, the flash will be initialised by the api_nvds_init function, and the data in the user area will be read into the g_app_flash_user_data variable by app_nvds_load, such as the phone address connected when the last powered off. At the same time, the app_nvds_update function can be modified to save or delete the variables that need to be saved to the user area. This information will be automatically saved to flash when powered off.

If the general development board is used, the existing information cannot be saved when the power is directly off or the reset key is pressed, so the debug instruction "11 08" or the shutdown instruction can be used to save the flash when reset or power off.

In addition, this module saves data to the nvds data area according to the tag_id, and eventually also calls the flash read/write function, if the user calls flash read/write directly, please pay attention to the flash address allocation.

(2) Demo Process

(a) flash initialisation

After the earbuds are powered on, the flash initialisation function api_nvds_init() is called in the user_init() function to initialise the flash space.

(b) Data reading

After the flash initialization is completed, call app_nvds_load() to read the user data stored in the flash, and judge and assign the data according to the tag_id. Then modify the corresponding variables according to the running situation during the execution process. Currently for security reasons, flash will not be read and written in real time, and flash will be refreshed according to the relevant variables when shutting down.

(c) Data update

In the case of shutdown or other necessary saving of flash, the APP layer detects and updates the content of each tag_id by calling the app_nvds_update() function, in addition to the update interface, it also encapsulates data deletion, protection and other related interfaces, which can be called as required.

Tone Logic

(1) Software Module Introduction

The tone call function currently used is tws_tone_play, and the corresponding value of its parameter is e_type_tone_t enumeration class. Some tones need to be synchronized to dual earbuds to play at the same time, such as pairing tones. The tws_tone_play function will determine which tones need to be played synchronously and send it to the slave earbud so that both earbuds can play at the same time. If simultaneous playback is not required, the tone_play() function will be called to play the tone.

During debugging, you can use the debug command "11 0e + enumeration value" to test whether the tone file is burned correctly, such as the pairing tone "11 0e 00". Currently, the burning address of the tone is 0xd0000.

(2) Demo Process

There is no fixed initialisation process for using tone, just burn the created tone file to the appropriate address correctly and then play it normally by calling tws_tone_play() or tone_play().

In the demo program, the function tws_tone_play(), which has the function of judging whether the master and slave earbuds need to play synchronously, is used to play the tone. If another tone comes in while the tone is playing, the new tone will be suspended, and after the first tone is played, call tws_tone_play() again in the tws_tone_delay_play_check_task() function to play the new tone. If a new tone does not require synchronization, it will be suspended in the tone_play() function, and this tone will be played first when the first tone finishes playing.

In the scene where the tone needs to be played, the call of the BT connection tone CONNECTED_TONE is special. It is called in the app_btble_connect_status_check() function, and its principle is that the master earbud sends the ACL connection information to the slave earbud to play together after connecting to the BT, involving several variables: g_app_ctl.bt_conn_ready, g_app_ctl.bt_slave_ready, acl_link_tws.bt_ ready and the local variable conn of the current connection state.

Currently, the variables g_app_ctl.bt_conn_ready and g_app_ctl.bt_slave_ready changes as follows:

g_app_ctl.bt_conn_ready: The variable is set to 0 in the scenario of non-slave earbud ACL connection success or when the master and slave earbuds are each disconnected; when the master earbud sends information, the value is assigned to acl_link_tws.bt_ready and sent to the slave earbud; variable is set to 1 when the connection tone is played.

g_app_ctl.bt_slave_ready: The variable is set to 0 in the scenario of non-slave earbud ACL connection success or when the master and slave earbuds are each disconnected; variable is set to 1 after an ACL message is sent and the slave earbud replies; variable is set to 2 when the connection tone is playing; variable is set to 3 when the connection tone is played.

Power Off Process

(1) Module Introduction

In the demo program, if there is no connection for a certain period of time, or the power is too low (the general development board does not have this scenario temporarily), it will enter the power off state. It is handled in the app_poweroff_task function, and there are two kinds of power off flag bits mentioned: APP_SYS_FLAG_POWER_OFF_REQ and APP_SYS_FLAG_TIMEOUT_POWER_OFF_REQ. At the same time, when the power off, the user data will be saved.

The timeout power off time can be adjusted by modifying the value of APP_ATUO_POWER_OFF_TO_MINUTES.

If the general development board is used, it can enter the power off state through the debug command "11 0c".

(2) Demo Process

(a) Currently, there are the following scenarios for power off

Long time low power state, resulting in automatic power off when the battery power is low;

Automatic power off when the earbuds enter the charging case and charging case is closed;

No BT connection, no dongle connection and not in pairing mode, the timer exceeds the set time will trigger the timeout automatic power off;

Pairing is triggered when there is no connection, and it will automatically power off when the pairing timeout fails.

(b) app_poweroff_task introduction

The power off process is divided into two parts according to two flag bits, One is to use the app_sys_flag_set() function to set the power off flag and then power off directly, and this part will play the power off tone and the BT disconnection process before power off. Finally, call the app_system_poweroff() function for power off.

The timeout power off part is in the scenario where there is no connection and is not in pairing mode. When the time exceeds the set parameter APP_ATUO_POWER_OFF_TO_MINUTES, the timeout power off process will be triggered and the function interface app_tws_poweroff() will be called by the master earbud (no action will be taken when triggered by the slave earbud), this function is used for the master earbud and the slave earbud to power off synchronously, and then the app_system_poweroff() function is called at the same for power off.

Reconnection Logic

(1) Module Introduction

In the demo program, when the bt timeout is disconnected or powered on, In the demo program, when the BT timeout is disconnected or powered on, the flash is read to have a connection record and the APP layer sets the APP_SYS_FLAG_AUTO_RECON_REQ flag to true to perform a reconnection action. The APP layer reconnection procedure is handled in the app_bt_recon_task function, where the slave earbud does not perform a reconnection action, but waits for the master earbud to connect. In this state, the number or time of the reconnection is set (usually set time), the reconnection address and the profile to be connected (only care about hfp, a2dp and avrcp), and finally the api_btrec_start function is called to upload the parameters to the API layer for processing, while the api_btrec_process function needs to be executed in the main loop.

In the APP layer, the reconnection time can be modified via g_app_ctl.app_recon_time_s.

(2) Demo Process

In the reconnection task app_bt_recon_task(), the non-slave earbud will query the information of the last connected BT device based on the local connection record, and the query results are processed in three cases:

(a) There are BT connection records, whether there are dongle connection records is not concerned

At this time, api_btrec_start() will be called to enable the connection according to the device address and the profile involved in the reconnection, and the connection processing flow is executed in api_btrec_process().

(b) Only dongle connection records exist

When only the dongle connection is recorded, the program will assign an initial value to g_app_ctl.tick_delay_ bt_paring and if the dongle is not connected within two seconds, it will enter the BT pairing state.

(c) Neither BT nor dongle has a connection record

If neither BT nor dongle has a connection record, it will immediately enter the BT pairing state.

In the process of calling api_btrec_start(), the APP layer will judge the reconnection timeout time and the number of reconnection timeout (The number of ACL connection timeouts, the corresponding time in the current reconnection process is RECON_ACL_TIMEOUT) recorded by the API layer at this time. Only one of the two needs to be set, and this value will not be cleared at the end of the reconnection. At the next reconnection, if no new timeout time or number of timeouts are set, the reconnection will be performed according to the parameters recorded at that time. If the timeout time and the number of timeouts are set to 0 when calling api_btrec_start(), the default timeout time will be set to API_BTREC_DEFAULT_TIME. If both are set to non-zero values, the process will end according to the parameter that arrives first, such as if the timeout time is set to 10s and the number of timeouts is set to 10 times, when the reconnection exceeds 10s but the number of timeouts does not reach 10 times, the current reconnection process will still end and the reconnection timeout event BTMISC_EVENTID_RECON_TIMEOUT will be reported to the APP layer.

api_btrec_set_con_time(): Set the timeout time

api_btrec_set_con_counts(): Set the number of timeouts

api_btrec_get_con_time(): Get the timeout APItime

api_btrec_get_con_counts(): Get the number of timeouts

api_btrec_get_con_remaining_time(): Get the remaining time for the reconnection process

api_btrec_get_con_remaining_counts(): Get the number of times remaining for the reconnection process

The APP layer can directly call the relevant functions to determine the timeout time and the number of timeouts. The parameter used in the demo program is the timeout time, which is set for the first time during initialization. Then it will be judged by the global variable g_app_ctl.app_recon_time_s and change the reconnection time in the reconnection task. The purpose of using this solution is to ensure that when the slave earbud switches to the master earbud, the time recorded by g_app_ctl.slave_recon_time_s will continue to be used for reconnection process on the master earbud. After completion of the current reconnection process, the reconnection time can be reset according to the g_app_ctl.app_recon_time_s in the next reconnection. There are many details of this process, which can be referred to the procedure.

The API layer reconnection is designed for the processing function api_btrec_process().

api_btrec_process() processing function

The program is divided into two judgments: timeout and the number of timeouts. api_btrec_set_state is called to change the reconnection state, this function does not need to be called by the APP layer. In the APP layer, two functions api_btrec_start() and api_btrec_stop() can be used to start and stop reconnection.

api_btrec

When the API layer reconnection state is changed, the corresponding processing function will be called in api_btrec_process(), most of these functions report the relevant events to the APP layer, the event enumeration type is btmisc_eventid_e, and the reporting can be completed after registering at the APP layer using the registration interface api_btmisc_event_user_register().

BTMISC_EVENTID_RECON_SUCCESS: Reconnection success event

BTMISC_EVENTID_RECON_CANCEL_BYLOCAL: Cancel the reconnection event locally

BTMISC_EVENTID_RECON_WAIT_MASTER: Wait for the master earbud and cancel the reconnection

BTMISC_EVENTID_RECON_TIMEOUT: Reconnection timeout event

BTMISC_EVENTID_RECON_COUNTS_MAX: Exceeds the number of reconnection event

BTMISC_EVENTID_RECON_RETRY: Retry the ACL connection

BTMISC_EVENTID_RECON_WAITECONN_PROFILE: Waiting for profile connection

BTMISC_EVENTID_RECON_NEW_DEVICE: Connect new device event

BTMISC_EVENTID_RECON_POSITIVE_FALSE: Positive is false when reconnected

BTMISC_EVENTID_RECON_PAGE_SUCCESS: Page success when reconnected

The demo program currently only cares about timeout and the number of timeouts events, other events are not registered.

api_btrec_xxx_func() function

Similar to the api_btrec_xxx_func() function in the reconnection project, the functions encapsulated for handling the relevant processes during the reconnection process. For example, when an ACL connection is completed, the ACL connection completion event reported by the host will process the api_btrec_aclconn_complete_func() function, which is necessary, otherwise the reconnection process may be wrong.

BT Pairing and Factory Reset

(1) Module Introduction

In the demo program, if the general development board is used, the dual earbuds can double-click the key2 at the same time or send the debug command "11 0b 00" to enter the pairing mode. If the earbuds with charging case is used, press and hold the charging case button for 3s after dual earbuds enter the charging case to enter pairing mode. Its pairing flag is APP_SYS_FLAG_PAIRING_MODE_REQ.

Similarly, if the general development board is used, the dual earbuds can be double clicked the debug command "11 0b 01" at the same time to enter dual earbuds grouping mode (factory reset). If the earbuds with charging case is used, press and hold the charging case button for 10s after dual earbuds enter the charging case to enter grouping mode. After the first power on or flash is completely erased, if the dual earbuds are not grouped, the dual earbuds can be grouped by this command. The dual earbuds grouping mode (factory reset) flag is APP_SYS_FLAG_FACTORY_RESET.

Both parts of the code are in app_bt_pairing_task, i.e:

When the APP_SYS_FLAG_PAIRING_MODE_REQ value is true and the APP_SYS_FLAG_FACTORY_RESET value is false, it is pairing mode.

When the APP_SYS_FLAG_PAIRING_MODE_REQ value is true and the APP_SYS_FLAG_FACTORY_RESET value is also true, it is the dual earbuds grouping mode (factory reset).

Note:

The dual earbuds grouping mode will clear the user area data.

(2) Demo Process

When flag APP_SYS_FLAG_PAIRING_MODE_REQ is set, the app_bt_pairing_task() function will be called for processing. First clear the current BT state, when the BT is successfully disconnected, if only the flag of pairing mode is set, it will call app_bt_enter_pairing() to start scan and play a pairing tone.

If flag APP_SYS_FLAG_FACTORY_RESET is set at the same time, the corresponding grouping process will be executed according to the dual earbuds grouping mode TWS_PAIRING_MODE_WIRELESS (wireless grouping or wired grouping). At the same time, clear the user data saved in the user area NVDS, clear the saved connection information, restore the default key operation and other processes, and then the configuration information that needs to be reset to default can be added here.

BT Scan Management

(1) Module Introduction

In the demo program, the scan management in the APP layer is in the app_bt_scan_task() function, which can be enabled or disenabled by the function app_bt_write_scan_enable(uint8_t en, uint8_t call_num). The first parameter "en" of this function can refer to the bb_scan_en_t type, and the second parameter "call_num" used for debugging and printing logs to determine the location of the function call.

In the APP layer, the scan time can be modified via g_app_ctl.app_inqscan_time_s and g_app_ctl.app_pagescan _time_s.

(2) Demo Process

app_bt_scan_task() function

The function app_bt_write_scan_enable() calls the api_btscan_enable() function in the API layer to enable the state. The scan process is similar to the reconnection process, adding api_btscan_process() to the scan task app_bt_scan_task() for execution. The first half of the app_bt_scan_task() function is the timeout time setting for scan, which is recorded using the global variables g_app_ctl.app_pagescan_time_s and g_app_ctl.app_inqscan_time_s, and the API layer function is called for the first assignment during initialization, and then when the global variable changes, it will be refreshed in app_bt_scan_task(). The function for setting time in the API layer is as follows:

api_btscan_set_inq_time(): Set inquiry scan timeout time

api_btscan_get_inq_time(): Get inquiry scan timeout time

api_btscan_refresh_inq_start_tick(): Retime inquiry scan

api_btscan_get_inq_remaining_time(): Get remaining time for inquiry scan

api_btscan_set_page_time(): Set page scan timeout time

api_btscan_get_page_time(): Get page scan timeout time

api_btscan_refresh_page_start_tick(): Retime page scan

api_btscan_get_page_remaining_time(): Get remaining time for page scan

The second half of the function app_bt_scan_task() is the logical handling of scan failure. For the specific process, please refer to the code.

API layer scan management is designed for the processing function api_btscan_process().

api_btscan_process() function

The above figure is the timing operation and timeout judgment of scan. Call api_btscan_set_xxx_state to change the scan state, and this function generally does not need to be called at the APP layer. Just focus on the use of the api_btscan_enable() function at the APP layer.

api_btscan

When the API layer scan state is changed, the corresponding processing function will be called in api_btscan_process(), and the function here reports the relevant events to the APP layer, the event enumeration type is btmisc_eventid_e, and the reporting can be completed after registering at the APP layer using the registration interface api_btmisc_event_user_register().

BTMISC_EVENTID_SCAN_INQ_CANCEL_BYLOCAL: Cancel the inq_scan process locally

BTMISC_EVENTID_SCAN_PAGE_CANCEL_BYLOCAL: Cancel the page_scan process locally

BTMISC_EVENTID_SCAN_INQ_TIMEOUT: inq_scan timeout process

BTMISC_EVENTID_SCAN_PAGE_TIMEOUT: page_scan timeout process

The demo program currently only cares about two timeout events, other events are not registered.

Similar to the reconnection process, when an ACL connection is completed, the ACL connection completion event reported by the host will process the api_btscan_aclconn_complete_func() function, which is necessary, otherwise the scan process may be wrong.

TWS Earbuds Grouping and Pairing Mode

TWS address description, there are three addresses in the SDK project, namely:

  • g_app_flash_user_data.const_addr: The address written during download, will not change (Flash 0x1DF000 location).
  • g_app_flash_user_data.new_alloc_addr: The address used after grouping will be set to the TWS working address.
  • g_app_flash_user_data.cur_used_addr: The address actually used when the earbuds are powered on and take effect (const_addr or new_alloc_addr).

Earbuds need to be grouped in two scenarios:

  • The TWS earbuds work in the form of two single earbuds grouped to a pair of earbuds when they are shipped out of the factory.
  • One of the grouped TWS earbuds was replaced.

Purpose of the group:

  • The purpose of the group is to determine that the Bluetooth address used by the dual earbuds is the address of the left earbud, and the new_alloc_addr of the dual earbuds will be assigned the const_addr of the left earbud.
  • Unify the async channel id and access code of dual earbuds so that the dual earbuds can then work synchronously as the master and slave earbuds in TWS mode.

Trigger for group action:

Triple clicking the k2 button on the general development board or holding down the green button on the B91 TWS kit for 10 seconds will trigger the APP_SYS_FLAG_PAIRING_MODE_REQ and APP_SYS_FLAG_FACTORY_RESET events. When grouping, the BT pairing history will be erased, as well as user information such as new_alloc_addr, async channel id, access code and key mode settings on dual earbuds, and eq parameters will also be restored to factory settings (if either earbud detects the other party's address has changed since the last group-up, the dongle's pairing information will also be erased).

(1) Wired Grouping

In the application scenario with a case, the wired grouping method is to exchange the addresses of the two earbuds through the case, and then each earbud saves the other's address, and assigns the left earbud address to the local new_alloc_addr, and calculate the async channel ID, access code that works between the two earbuds according to the left and right earbud addresses. If the address of the other party saved before in the local is different from that received this time, it is considered that a earbud is replaced, each will erase the pairing information of the dongle and update the new async channel ID and access code. See the uart_chn_ac_addr_gen() function implementation in the \src\vendor\_proj_cc_tws_\batt_uart.c file for details.

Then the left and right earbuds are connected with a fixed async channel ID and access code, and determine the master and slave earbuds (packets that are not each other's address will be filtered out to avoid multiple pairs of earbuds pairing up at the same time).

Wired grouping flow chart

(2) Wireless Grouping

In application scenarios without case, such as soundbar and demo general development boards, the SDK provides a wireless way to implement binaural grouping (set macro TWS_PAIRING_MODE_WIRELESS to 1 to enable). The difference is that after the two earbuds are connected with fixed async channel ID and access code to determine the master and slave earbuds, they interact with each other's addresses. The two earbuds assign the address of the left earbud to their respective new_alloc_addr, and calculate the async channel ID and access code for the two earbuds to complete the grouping. One disadvantage of wireless grouping is that if multiple pairs of earbuds are teamed at close range at the same time, the earbuds may not receive the air interaction packet sent by the earbuds expected to team up, resulting in crossover errors.

(3) Earbuds Pairing with New Devices

The TWS earbuds will open the BT page scan and inquiry scan after into pairing mode, which can be searched by the phone for pairing connection or paired connection by the dongle in pairing mode (dongle is also in pairing mode). However, pairing BT and dongle is one of the two choices, after pairing on one way, the other way will exit pairing mode.

The async channel ID and access code of the earbuds and dongle will be set to the same fixed parameters after into pairing mode so that they can communicate with each other. The two earbuds will first sync to determine the relationship between the master and slave earbuds. Then, in the process of pairing interaction with dongle, the master earbud will tell dongle the private async channel ID and access code that have been determined between the left and right earbuds so as to establish a connection later. After the pairing, the earbuds and dongle will exit the fixed async channel ID and access code, and enter the private async channel ID and access code to establish a connection. All dongle pairs to the earbud will drop and then reconnect.

(4) Earbuds Reconnect to the Dongle

The earbuds and dongle already know the async access code of each other after pairing, so as long as the earbuds are not in pairing mode, they can be reconnected by the last connected dongle.

TWS Earbuds Master-slave Switching

The TWS earbuds will trigger the master-slave mode switch when the master earbud is off, both earbuds are in the case at the same time and the slave earbud is out of the case, both earbuds are outside the case and the master earbud is in the case, during which the master earbud will synchronize to the slave earbud and continue to work. The purpose is to keep the original slave earbud connected to the phone or dongle when the master earbud is off, or to ensure that the MIC of the earbud outside the case is valid (only the master earbud MIC is valid). The master-slave switching action is triggered by the message TWS_FLAG_HANDOVER_START.

In the final stage of the master-slave switching, the master earbud calls the app_bt_handover_cfm() function to synchronize the state information related to the master earbud to the slave earbud, and the slave earbud calls btp_tws_handover() to assign the value to the corresponding variable. The content mainly contains the status of each profile, such as the current call status, the rfcomm layer sending and receiving data credits data, the last L2CAP layer CID, etc.

TWS Earbuds Communication Interface

Link virtualization, state synchronization and UI interaction between master and slave earbuds of TWS require data channels. SDK provides two interfaces for calling, app_push_cmd() and async_tws_push_cmd(). The former sends buffer length cannot exceed 37 bytes, the latter length is not limited, and the function will do unpacking internally. The receiver parses the data in the function tws_cmd_vendor_parse().

After the TWS earbuds are connected, the search, pair and connection of the phone is the interaction with the master earbud. The slave earbud must know the pairing encryption information and link information between the master earbud and the phone, and must draw the same link in order to properly receive the packets sent from the phone to the master earbud.

After connecting to the phone, the master earbud will call bt_ll_tws_flag_set(); to set the local connection flag, such as calling bt_ll_tws_flag_set(TWS_FLAG_ACL_CONNECTED, 1); to set the TWS_FLAG_ACL_CONNECTED flag after the ACL link is established, setting TWS_FLAG_ACL_ENCRYPTED after the link encryption is finished. Other Profile flags are defined as:

#define TWS_FLAG_A2DP_CONNECTED             (0x01UL << 1)
#define TWS_FLAG_AVRCP_CONNECTED            (0x01UL << 2)
#define TWS_FLAG_HFP_CONNECTED              (0x01UL << 3)
#define TWS_FLAG_SPPATT_CONNECTED           (0x01UL << 4)

The master earbud locally uses the variable tws_sync_link_flag.slave_link_flag to record the current link flag status of the slave earbud. The master earbud checks the difference between the current link and the slave earbud link information in the void app_system_sync_link_task(void) task function. After the ACL link is established and encrypted successfully, the master earbud will synchronize the corresponding link information to the slave earbud in the order of ACL, A2DP, AVRCP, HFP, GATT/SPP. After the slave earbud virtualizes the ACL link, it can receive the packets sent by the phone to the master earbud. If the profile is not virtualized, the corresponding packets will be dropped at the HOST layer and not processed.

The master earbud calls the tws_link_info_send() function to send the corresponding link information to the slave earbud, who parses it in the function app_tws_tsync_cmd_rcvd_callback() and calls the corresponding interface virtual link, see function \src\stack\bt\bt_tws\btp_tws_slave.c. After that, the TWS_CMD_SLAVE_SYNC_LINK_RSP message is sent to the master earbud to tell the current link information of the slave earbud (the master earbud updates tws_sync_link_flag.slave_link_flag with this).

Dongle UI and APP Layer Description

Dongle UI Description

The addition and registration of the dongle key and led the same as the earbuds. See the section LED Function and Key Function.

The SDK default configuration general development board only KEY1 button double click is valid, the corresponding action is to erase the pairing information with the earbuds, while entering the pairing mode. The LED is only defined that the red LED corresponding to GPIO_PB7 IO is valid, and the default configuration of light effect is described in section Lighting Effects of Earbuds and Dongle in Each Mode.

The dongle keys are only triggered into pairing, not involved in phone and music related behavior control. The dongle phone and media control are triggered by the earbud, and the key values are sent to dongle through the ASYNC_CMD_UI_MEDIA_KEY message. The values is parsed by the function tlk_app_cmd_rcvd() in app_ui.c of the dongle and converted into hid commands, which are sent to the master device connected to dongle through USB to execute the action.

Dongle User Area Save

Dongle user data structure is:

typedef struct _bt_ram_para { 
unsigned short record_crc; 
//above field will write in flash 
uint32_t ble_ac; 
uint8_t ble_ch; 
uint8_t ble_id; 
bd_addr_t addr_headset; 
bd_addr_t const_addr;//be writeed when down code 
uint8_t const_name[LOCAL_NAME_LEN_MAX];//be writeed when down code 
}__attribute__((packed)) bt_ram_para_t; 

After the Dongle is paired with the earbud, stack will send ASYNC_EVENT_PAIRING_SAVE message to trigger a real-time writing to flash at the 4k position starting from flash 0xE0000.

  • ble_ac dongle: The access code that the earbuds sent to dongle after pairing the earbuds when double-clicking key1.
  • ble_ch dongle: The channel ID that the earbuds sent to dongle after pairing the earbuds when double-clicking key1.
  • ble_id dongle: The code serial number (Currently the SDK only supports pairing one dongle, which will be 0). that the earbuds sent to dongle after pairing the earbuds when double-clicking key1.
  • addr_headset: The address used by the TWS that the earbuds sent to dongle after pairing the earbuds when double-clicking key1, i.e. the g_app_flash_user_data.new_alloc_addr value saved on the earbuds.
  • const_addr and const_name: The address and name assigned to dongle is not currently used by the SDK.

The dongle user data will be erased and written back to flash in real time when double-clicking key1, so dongle will enter pairing mode by default when it is powered down and powered up again in pairing mode.

Earbuds and Dongle Firmware Upgrade

OTA Upgrade

The SDK supports OTA wireless upgrade function, IOS device transmits OTA packets through BT GATT profile, android device transmits OTA packets through BT SPP profile. The new firmware data is temporarily stored in the chip internal flash fixed area (the earbud is 0x120000--0x1d7000, the dongle is 0x70000--0xe0000). After the upgrade, it is judged and copied to the execution area starting from 0x9000 in the BootLoader stage, and then rebooted to take effect.

The earbud flash allocation map:

Earbud flash allocation map

The OTA process is as follows (with an example of upgrading TWS earbuds) :

OTA flow chart

The OTA process is the same for Dongle, except that all data packets are forwarded through the master earbud as a relay. The APP sends OPC_REQ_VERSION to obtain the current firmware version number. If the new version number is greater than the current version number, the APP sends OPC_REQ_UPDATE to request an upgrade. The earbud judges whether to upgrade the earbud or upgrade dongle (the earbud is 11.XX, dongle is 0.1.XX) according to the version number code specification in the data packet. The function firmware_update_req_rsp() processes and records the file size, CRC and other contents of the current OTA, and sends OPC_RSP_UPDATE back to the APP side.

Then start the upgrade, each call firmware_page_request() to send OPC_REQ_DATA to APP for 256 bytes of firmware information, the request content includes the offset address of the current 256 bytes of data relative to the starting position of 0x9000. If it is a breakpoint to continue OTA, this offset address is the position before the last failure (saved in flash user area), then APP returns the requested 256 bytes of content via OPC_RSP_DATA. So loop until the end of the file read.

The APP reads from the target bin file 0x9000 position when OTA upgrade, the boot related code in front is not involved in the upgrade.

Key functions:

  • dfu_rx_callback(): The earbud receives the processing of upgrade related data from the APP or dongle, and the dongle receives the processing of data from the earbud.
  • rsp_data_relay(): The master earbud calls this function to unpack and forward data to the dongle side when upgrading the dongle.
  • flash_page_save(): The earbud and dongle call this function to write flash.
  • firmware_page_request(): The earbud and dongle call this function to send OPC_REQ_DATA to the APP for the next firmware packet.
  • update_app(): This function is called after the earbuds and dongle OTA are finished and the verification is successful to write the new firmware valid information to the flash DFU_NEW_FIRMWARE_INFO_ADDR (0x8800) position, to be checked and moved to the execution area to take effect after reboot.

Wired Upgrade in Boot Mode

In the B91 TWS kit environment, it supports wired single-wire upgrades (TWS earbuds only) through the case board. The purpose is to produce the production line first, and quickly upgrade the software version after iteration. The activation method is that in the user mode, with the two earbuds are powered on in the case state, short pressing the red button on the earbuds board of the B91 TWS kit to trigger. At this time the case will send {0xca, 0xfe, 0xa3, 0x85, 0x00} command sequence to the left and right earbuds respectively. The earbuds parse the command at the beginning of the function batt_box_cmd_handler(), and calls tlk_afh_boot_mode_set() to write 0xc5 to the register WORK_MODE_AREG, and then reboot the system. After the system reboots, the system enters the single-wire upgrade mode (serial_boot_task()) if it is judged that the register data is 0xc5. See the processing of user_init_normal() in src\vendor\_proj_boot_device_\app.c.

Before the single-wire upgrade, you need to download the new earbuds bin file to the 0x80000 location of the B91 TWS kit built-in chip flash and download the tone to the 0x150000 location of the B91 TWS kit built-in chip flash in advance. The beginning of the 0 address is the charging case's own firmware. See the figure following:

bin file download settings

After the single-wire upgrade starts, it will start from the 0x88000 location (the first 36k boot code of the earbud firmware is not involved in the upgrade) of the B91 TWS kit built-in chip flash, mirror copy to the beginning position of the earbud flash 0x8000, the copy length is 0x110000 (1088k), as the earbud tone needs to be stored at the 0xd0000 position of the earbud flash, so the tone needs to be downloaded to the 0x150000 position of the base flash in advance. After the copy is completed, the case will send CMD_CHECK_FLASH (0x91) command to trigger the earbud to check the crc and send the result back to the case. the case will send CMD_REBOOT (0x86) to trigger the earbud to reboot after judging correctly, and the reboot will check the integrity of the bin at the beginning position of 0x9000 based on the 0x8000 position information.

Unlike the OTA upgrade, the single-wire upgrade directly erases and writes the earbud execution area.

Boot Mode Introduction

The bin of Boot is generated by compiling the _proj_boot_device_ project with the path src\_proj_boot_device_ \output\_proj_boot_device_.bin, which should be copied to the src root directory when used, and the earbuds and dongle are shared. The earbuds are compiled to generate _proj_cc_tws_.bin, and then the compilation environment will run bin_ota.exe to package _proj_boot_device_.bin and _proj_cc_tws_.bin into _img_proj_cc_tws_.bin, _proj_boot_device_.bin will be placed at the location starting at address 0 (address 0 - 0x7fff in the range of 36k), _proj_cc_tws_.bin will be placed at the location starting at 0x9000. And the earbud version number (stored in src\ver.ini), the actual earbud bin start location, bin size, bin crc and compile date will be written to the location starting at 0x8000. The following is a screenshot of the corresponding Location of _img_proj_cc_tws_.bin:

_img_proj_cc_tws_.bin corresponding Location

In addition to the single-wire upgrade function in boot mode (see section Wired upgrade in Boot mode), there are the following two functions:

(1) Read the content of flash 0x8800 to determine whether there is a valid OTA bin to take effect. If there is, copy the new bin temporarily stored at the beginning of the 0x120000 (dongleshi 0x70000) location to the beginning of the 0x9000 location. After successful verification, erase the bin information before the start location of flash 0x8000, including ota image information, write the new bin related information (size, crc, start address) to the 0x8000 location, and reboot. see the implementation of the function new_firmware_apply() in src\vendor\_proj_boot_device_\app.c;

(2) If there is no new OTA bin to take effect, check the integrity of the bin starting at 0x9000, if it is normal, jump to the location where 0x9000 is executed and boot. See the implementation of the function check_image_integrity() in src\vendor\_proj_boot_device_\app.c.

There is no no function of USB printing log in boot mode. By defining macro UART_DEBUG_SWS_MODE as 1 to enable simulating UART printing through SWS line. See src\vendor\_proj_boot_device_\printf.c for detailed implementation.

Audio Path

Earbuds Audio Path

Audio path Block Diagram

Audio path contains modules: BT audio, Low latency audio, tone, codec interface, audio control. The block diagram is as follows:

BT audio is responsible for processing the logic of BT music and BT phone, including receiving A2DP data, sending and receiving SCO data, processing MIC uplink data, encoding and decoding audio data, BT audio synchronization mechanism, including synchronisation between master and slave earbuds and synchronisation between earbuds and phone.

Low latency audio is responsible for processing music and voice logic in 2.4GHz low latency private protocol mode, including functions: receiving music data, sending and receiving voice data, processing MIC uplink data, encoding and decoding audio data, and audio synchronization mechanism between master and slave earbuds.

The codec interface contains codec-related processing functions, such as putting PCM data into the codec playback buffer.

The audio control contains the interface between audio path module and BT application, the interface between audio path module and BT stack, the initialization logic of audio path and the logic of audio mode switching. Audio has two modes: AUDIO_MODE_LE and AUDIO_MODE_BT, AUDIO_MODE_LE means the current audio is in 2.4GHz low latency private protocol, AUDIO_MODE_BT means BT audio.

Audio path block diagram

BT Voice and Low Latency Audio Mix

BT voice and low latency audio mixing is enabled by default and can be disabled via the macro LE_AUDIO_BT_VOICE_MIX_ENABLE.

Mixing function is enabled by default

The volume of low latency audio in mixing mode is set to 150 and the range is 0~1024. Low latency audio in mixing mode sounds like background sound with a lighter volume, you can modify the volume of mixing mode with ll_audio_set_mix_vol.

Modify the volume of the mixing mode

Music in one-to-two Mode

The BT music and low latency audio also support mixing function, but this is disabled by default due to the limitations of RAM resources on the B91 platform.

Mixing function disabled by default

In the music one-to-two mode, the BT music priority is higher than the low latency audio by default when the mixing function is not enabled, and BT music will preempt low latency audio playback. The macro APP_DONGLE_MUISC_PRIORITY_HIGHT can be used to modify the one-to-two priority. The default value is 0, indicating that the BT music priority is higher than the low latency audio.

set priority

Tone Production

Tone source format: 16kHz mono

Tool: wave_to_tone_bin_tool_v1.0.1

Tone production steps:

According to the type defined by tone.h, rename the audio source as 1,2,3,4,5... Here is the corresponding relationship between type and audio source. After modifying the audio source name, use the pcm16to8_vol_1024.bat script to produce the tone files in ADPCM format.

Tone production steps

Tone playback steps:

Call the tone_play API to specify id, such as tone_play(CONNECTED_TONE), and the program will initialise the ADPCM.

Call tone_get_sample in the BT audio playback logic or low latency audio playback logic to obtain the PCM data.

Dongle Audio Path

Dongle Audio Path Working Mode

The dongle will be enumerated as a usb MIC device and a usb speaker device when plugged into the usb host. When dongle is working normally, it processes the audio from the earbuds and uploads it to the usb host through the usb MIC path, and processes the audio data from the usb speaker path and sent it to the earbuds.

Dongle Audio Path Process

Dongle audio path process

(1) Dongle receives a single-channel 16kHz sampling rate mSBC encoded mic packet from the earbuds, and the corresponding function interface is app_audio_receive_voice_data().

(2) In the mic task, it is mainly for decoding, data alignment, sampling rate conversion and volume adjustment of mic data, and the corresponding function is app_mic_task().

(3) In USB ISO IN processing, the data processed by step 鈶� is sent to the USB host, and the corresponding function is app_usb_iso_in_handler().

(4) In USB ISO OUT processing, the PCM format speaker data of 48kHz sampling rate from the USB host is received, and the corresponding function is app_usb_iso_out_handler().

(5) In the audio task, the speaker data is mainly adjusted by volume, data aligned, and encoded into LC3 format, and the corresponding function is app_audio_task().

(6) Send the LC3 encoded speaker packet to the earbuds.

Audio Algorithm Processing

Audio Algorithm Overview

This SDK mainly uses the following audio algorithms:

  • EQ (Equalizer)
  • SBC (Sub-band Coding)
  • mSBC (Modified Sub-band coding)
  • CVSD (Continuous Variable Delta Modulation)
  • AAC (Advanced Audio Coding)
  • LC3 (Low Complexity Communication Codec)
  • BF (Beamforming)
  • AEC (Acoustic Echo Canceling)
  • NS (Noise Suppression)
  • AGC (Automatic Gain Control)
  • PLC (Packet Lost Compensation)

Block Diagram of Algorithm Operation

According to the different operating states of the audio path, the audio algorithm is distinguished into three modes as shown in the following figure:

The voice uplink is the voice uplink path, where BT supports both mSBC and CVSD coding algorithms, and LL only supports mSBC. The voice algorithms include BF->AEC->S_NS and BF->W_NS->AGC paths, which are limited by hardware resources, choose one of these two paths to take effect.

The voice downlink is the voice downlink path, where BT supports mSBC and CVSD decoding algorithms, and LL only supports LC3 decoding algorithm.

The music downlink is the music downlink path, where BT supports both AAC and SBC decoding algorithms, and LL only supports LC3 decoding algorithm.

Block diagram of algorithm operation

EQ

Overview

The EQ execution logic as shown in the figure below, EQ supports online parameter change, to achieve the function of multiple sound switching. When the system starts up, it is necessary to load the latest parameters from the specified flash address, or not to use this function (the flash address is null), default use the default parameters defined in tlkalg_eq_interface_api.h. When the parameters are loaded, the coefficients required by the algorithm are calculated internally and stored in the internal global data for data processing.

After the parameters are loaded, the sampling rate is changed according to the use requirements, such as the negotiation results between the protocol stack and the mobile phone. The sampling rate change function determines internally whether changes need to be made, 48kHz is used by default, if the required sampling rate is different from the default sampling rate, the sampling rate is changed internally and the algorithm coefficient is calculated.

After the algorithm coefficients have been calculated according to the current usage environment, the data processing interface is called at the appropriate position in the audio data stream.

When the system is powered off, the latest EQ parameters are saved to the specified flash address, and the sound effect is restored when the system is powered on next time. When the flash address is null, the function does not take effect.

In music mode, 9th order filter is supported, and 9th order is used by default. Call uplink and downlink support 4th order filter, and 2nd order is used by default.

EQ is an optional function, the following macros can control whether to use the overall EQ function and the EQ function in voice mode:

#define EQ_ENABLE                   1
#define EQ_VOICE_ENABLE             1

EQ execution flow chart

Parameter Format

The default configuration parameters use a 9th order filter, and the specific parameters correspond to the filter type and main parameters, from left to right: gain (2B), center frequency (2B), Q value (1B), filter type (1B), reserved bit (1B). The header data is index (1B), maximum gain number (1B), order (1B), and total gain (1B), where the index is used to distinguish different EQ types, and the total gain ensures that the gain value of all frequency points is below 0dB.

Parameter format

Configuration Tool

The figure below shows the EQ parameter graphical configuration tool. Area 1 provides specific configuration for each order filter parameters, including filter type, Q value, center frequency and gain value. Area 2 is used to set the overall parameters, including sampling rate, channel type, filter order, EQ mode and total gain, where the total gain is automatically generated by the tool internal algorithm based on each filter parameter and does not support manual changes. Area 3 is used to display the real-time frequency response curve of changing the filter parameters in area 1.

Check "All Para" and then check "Get Parameter" in area 5, you can see the EQ parameter as shown in the figure below in area 4, which can be directly placed in the code. Area 5 synchronously supports parameter saving, which can save the currently set parameters for later viewing. The tool also supports online debugging function, in two ways: wired USB and wireless RF. In order to achieve this function, the device needs to synchronously support the corresponding communication mode, click "Download" to load the parameters displayed in area 4 to the device.

EQ parameter configuration tool

SBC

The algorithm execution process is shown in the figure below, which can be classified into three categories according to the data transmission direction and data type, namely SBC decoding applied to music downlink, mSBC decoding applied to voice downlink, and mSBC encoding applied to voice uplink.

When music is applied in the BT/low latency concurrent general SDK, the device is only used as sink, so SBC only needs to support decoding. SBC and AAC are mutually exclusive when decoding music, and AAC requires more memory space than SBC, so to save space for the algorithm, SBC shares the buffer size of AAC when decoding. When initializing, the allocated memory starting address and SBC related parameters (sampling rate, Bitpool depth, channel number, etc.) are passed to the initialization interface. According to the data type to be decoded (left channel, right channel, stereo), the corresponding decoding function is called to complete the decoding operation.

The mSBC codec and resampling algorithms share a buffer, which obtains the occupied space before initialization to facilitate address allocation for subsequent algorithms. The initialization operation of codec is similar to that of SBC, given the starting address and parameters, the coding algorithm needs to set the number of blocks and Bitpool depth after initialization. By calling the codec processing function at the voice data transmission position, the corresponding processing can be completed, an additional encoding interface with parameter pointer is encapsulated during encoding, which is used to encode another channel data during dual channel encoding.

SBC algorithm execution process

mSBC Packet Loss Compensation

The mSBC packet loss compensation algorithm is used to detect the packet loss state in the voice downlink path, and if packet loss occurs, packet compensation processing is performed in the time domain.

The algorithm operation process is shown in the following figure, the size of the memory space required for algorithm operation is obtained through the get_size function, and the corresponding space is allocated on the specified buffer, mSBC_PLC and CVSD_PLC are mutually exclusive, so selecting the larger of the two when allocating space can save some memory. When initializing the codec voice mode, the algorithm is initialized synchronously, and the above allocated memory starting address, sampling rate and frame length are transmitted to the algorithm initialization interface. Packet loss detection is performed in the voice downlink path, and the corresponding algorithm processing interface is called according to the packet loss situation.

mSBC algorithm execution process

CVSD

CVSD is used for data encoding and decoding in the BT voice path and packet loss compensation during voice downlink.

The CVSD and CVSD_PLC algorithm execution process is shown below. The SDK uses the mSBC codec by default in the voice path, and CVSD initialization is performed when the received packet information indicates that the CVSD codec format is currently used.

CVSD_PLC obtains the required memory space through get_size, and is mutually exclusive with mSBC, and chooses the larger space occupied by both, so as to save some RAM resources. The coding interface can be called directly when the voice uplink is encoded, and the decoding or packet compensation operation can be performed according to whether the packet loss occurs during the downlink decoding.

CVSD algorithm execution process

AAC

AAC is applied in SDK to decode music data in BT path.

The process of algorithm execution is shown in the figure below. AAC requires two parts of memory space to run, one is the exclusive space of the algorithm (pBuf), and the other is the space that can be shared with other algorithms (pScratchBuf), which are considered simultaneously when setting the starting address of the space. AAC and SBC will not be in effect at the same time, pBuf can share the same memory space as SBC, with the same starting address. AAC uses more space than SBC, and both algorithms allocate memory according to the size of AAC space. During initialization, the starting address and configuration parameters of the allocated two parts of memory are passed to the interface. The parameters have been defined as global variables in the algorithm file and do not need to be set externally. In the audio data downlink path, the corresponding decoding interface is called based on the data type. The BT/low latency concurrent general SDK is the application of TWS earbuds solution, and the left decoding interface is used by default. The system uses SBC decoding by default. During the A2DP data reception process, AAC algorithm initialization and decoding function registration are only carried out when the incoming data is recognized as AAC encoding.

AAC algorithm execution process

LC3

The LC3 is applied to the encoding and decoding of music data transmission between dongle and earbuds. The encoding process runs at dongle, while the decoding process runs at earbuds. This algorithm is also used for decoding voice downlink data.

The process of algorithm execution is shown in the figure below. The interface for obtaining the size of memory occupation is encapsulated as a comprehensive interface, and the corresponding memory size is returned by passing in the memory type. The memory type refers to the enumeration e_lc3_buff_type_e. The BT/low latency concurrent general SDK is designed with the function of mixing LE audio and BT audio, so the two sets of memory required for LC3 decoding are not shared with other algorithms, otherwise it will cause an exception when the mixing is turned on, and two global arrays are defined g_lc3_dec_buff, g_ll_lc3_dec_scratch_buff for LC3 decoding, and the encoding at the dongle also defines two sets of global arrays g_lc3_enc_buff and g_lc3_enc_scratch_buff. During initialization, two sets of memory starting addresses and configuration parameters (bit rate, number of channels, sampling rate, etc.) need to be passed in. The configuration parameters are defined as global variables inside the algorithm file, and do not need to be set for external use. After the above process is completed, the corresponding codec interface can be called in the data transmission path.

LC3 algorithm execution process

EC

This algorithm is a collection of several algorithms, including BF, AEC, NS, and AGC, and the appropriate algorithm is selected to run via macro configuration. The voice uplink path algorithm used in this SDK is a combination of BF, NS and AGC.

In the SDK voice uplink path, a dual MIC(microphone) scheme is used, where the voice MIC and noise MIC sample two channels of data and send them to the BF algorithm. The BF algorithm enhances the voice signal at a specific angle and attenuates the noise signal at other angles. The data processed by the BF algorithm has been transformed into mono data, and then sent to the NS algorithm module, which further processes the noise in the voice signal and adjusts the gain and output gain of the signal entering the algorithm. After NS processing, it enters the AGC module, which adjusts the gain of different voltage levels of the signal to ensure that the signal is not saturated. After AGC processing, it enters EQ to shape the signal.

Due to the limited system resources, this SDK has two main options to configure paths, one is BF, W_NS, AGC path, and the other is BF, AEC, and S_NS path.

BF

The basic call process of the BF algorithm is as follows:

(1) Initialize the BF algorithm. First, the version number of the algorithm is obtained by gsc_get_version() function, then the space required by the algorithm is obtained by gsc_get_size() function, space is allocated to the BF algorithm from the system, and the BF algorithm is initialised by gsc_state_init() function.

(2) Every frame of dual MIC data obtained is sent to the gsc_BeamFormer() algorithm to perform processing, and obtain a mono audio signal.

BF algorithm call process

In the SDK, the BF algorithm can be enabled through macros, which can control the relevant configurations of all BFs in the BF algorithm. When the total switch of the macro for this algorithm is 0, it indicates that the BF algorithm is disabled, and when it is 1, it indicates that the algorithm is enabled.

#define ENC_GSC_ENABLE                                  0

As the BF algorithm is strongly correlated with the hardware design and the sound cavity mould, different projects need to optimize BF algorithm and adjust BF parameters. In the SDK, the BF algorithm can be used to capture data and analyze and update parameters through BT SPP to further optimize BF performance.

AEC

The AEC algorithm is applied to eliminate the echo generated by the path between the speaker and the MIC from the input signal of the MIC. The current algorithm eliminates the echo by an adaptive filter and a non-linear processing module.

The basic call process of the algorithm is as follows:

(1) Initialize the AEC algorithm. First, the version number of the algorithm is obtained by tlka_aec_ns_get _version() function, then the space required by the algorithm is obtained by tlka_aec_get_size() function, space is allocated to the AEC algorithm from the system, and the AEC algorithm is initialised by tlka_aec_init() function.

(2) The mono MIC data or dual MIC processed data for each frame obtained is sent together with the mono speaker data to the tlka_aec_process_frame() algorithm to perform processing, and obtain a mono audio signal that removes the echo signal.

AEC algorithm call process

In the SDK, the AEC algorithm can be enabled through macros, which can control the relevant configurations of all AECs in the AEC algorithm. When the total switch of the macro for this algorithm is 0, it indicates that the AEC algorithm is disabled, and when it is 1, it indicates that the algorithm is enabled.

#define AEC_ENABLE                                  0

NS

In this SDK, two types of NS are supported: one is the basic performance S_NS algorithm, and the other is W_NS. W_NS is used by default in this SDK. This section will first introduce the S_NS algorithm.

(1) S_NS

The S_NS algorithm is applied to remove noise from the input signal to improve the clarity of the signal.

The basic call process of the algorithm is as follows:

(a) Initialize the S_NS algorithm. First, the version number of the algorithm is obtained by tlka_aec_ns_get _version() function, then the space required by the algorithm is obtained by tlka_ns_get_size() function, space is allocated to the S_NS algorithm from the system, and the S_NS algorithm is initialised by tlka_ns_init() function.

(b) Every frame of mono MIC data obtained is sent to the tlka_ns_process_frame() algorithm to perform processing, and obtain a mono audio signal that removes the noise signal.

S_NS algorithm call process

In the SDK, the S_NS algorithm can be enabled through macros, which can control the relevant configurations of all S_NS in the S_NS algorithm. When the total switch of the macro for this algorithm is 0, it indicates that the S_NS algorithm is disabled, and when it is 1, it indicates that the algorithm is enabled.

#define NS_ENABLE                                  0

(2) W_NS

The W_NS algorithm is applied to remove noise from the input signal to improve the clarity of the signal. At present, the noise reduction module can provide a noise reduction depth of 6 ~ 21db.

The basic call process of the algorithm is as follows:

(a) Initialize the W_NS algorithm. First, the version number of the algorithm is obtained by tlka_w_ns_get_version() function, then the space required by the algorithm is obtained by tlka_w_ns_get_size() function, space is allocated to the W_NS algorithm from the system, and the W_NS algorithm is initialised by tlka_w_ns_init() function.

(b) Every frame of mono MIC data obtained is sent to the tlka_w_ns_process_frame() algorithm to perform processing, and obtain a mono audio signal that removes the noise signal.

W_NS algorithm call process

In the SDK, the W_NS algorithm can be enabled through macros, which can control the relevant configurations of all W_NS in the W_NS algorithm. When the total switch of the macro for this algorithm is 0, it indicates that the W_NS algorithm is disabled, and when it is 1, it indicates that the algorithm is enabled.

#define  W_NS_EN                                  0

AGC

The AGC algorithm is applied to adjust the voltage level gain to ensure that the signal does not saturate.

The basic call process of the algorithm is as follows:

(1) Initialize the AGC algorithm. First, the version number of the algorithm is obtained by tlka_agc_get_version() function, then the space required by the algorithm is obtained by tlka_agc_get_size() function, space is allocated to the AGC algorithm from the system, and the AGC algorithm is initialised by tlka_agc_init() function.

(2) Every frame of mono MIC data obtained is sent to the tlka_agc_process() algorithm to perform processing, and obtain a mono audio signal processed by AGC.

AGC algorithm call process

In the SDK, the AGC algorithm can be enabled through macros, which can control the relevant configurations of all AGC in the AGC algorithm. When the total switch of the macro for this algorithm is 0, it indicates that the AGC algorithm is disabled, and when it is 1, it indicates that the algorithm is enabled.

#define SOFT_AGC_EN                                 0

System Crash Debugging Method

There are two ways to debug crash problems:

Method 1:

After the system crashed, it enters the MCU's trap exception interrupt. We determine the cause and location of the crash by printing out the MCU's PC pointer location, the LA jump return address, and the RSIC-V's MCAUSE/MVAL.

Method 2:

After the system crashed, if it does not enter the MCU's trap exception interrupt, or if it does not get valuable information after entering the trap exception interrupt, or if we know the cause of the trap exception but need to find the root cause, etc. Depending on the cause of the common crash problem, we can try to do the following:

(1) If the location is fixed when a crash occurs, we can use the process control method to determine which operation is causing the problem. For example, by defining a "volatile" variable and assigning values at different process locations, we can determine which process is causing the problem and then gradually narrow down the scope to determine the root cause of the problem.

(2) If a crash occurs due to some exception operation that has modified with an instruction, but the specific location cannot be determined by the process control method, then we can determine the problem location by checking the instructions at the specified location at the nodes of different processes. For example, if an instruction has been modified with at a specific address, then the trap exception (mcause=0x0b) can be triggered by the function check_target_address_code, then combined with PC and LA to check exactly which process has modified the instruction at this location.

(3) If we find that the IRAM (instruction ram) has been modified with, resulting in an instruction exception when executing the instruction, then we can perform a checksum operation on the iram area to determine if the iram has been modified with, and if it is modified with, trap exception (mcause=0x0b) will be triggered, and then combined with PC and LA to determine the specific root cause of the problem.

(4) Since memcpy and memset functions are often accompanied by some pointer operations, it is easy to have problems such as out-of-bounds, leading to instruction exceptions, so we have made some checking mechanisms in these two functions. If the parameters are abnormal, trap exception (mcause=0x0b) will be triggered, and then combined with PC and LA to determine the specific root cause of the problem.

TWS Charging Case

Charging Case, Earbuds and SY5500

Charging case, earbuds and SY5500

As shown in the figure, the SY5500 interacts with the earbuds main chip as the charging management chip, and is mainly responsible for managing and protecting the charging stat us of the earbuds. Both are configured and status queried through the I2C interface. The earbuds interacts with the charging case through the UART signaling; the charging case will send the mode switching signal of SY5500 to design the normal state of SY5500.

The corresponding GPIO resources are: I2C (GPIO_PB3, GPIO_PB2); UART (GPIO_PC7, GPIO_PC6); ADC (GPIO_PB4); control ADC sampling (GPIO_PC4); GPIO wake-up (GPIO_PC5).

The details of SY5500 chip can refer to the corresponding chip handbook.

Low Power Protection Flash Operation

It should be noted that when the power supply voltage is below a certain limit, flash operation will create an error risk, especially when the device is in OTA, so it is necessary to monitor the current voltage in real time. The macro VBAT_ALRAM_FLASH_THRES_MV corresponding to low power flash protection operation is currently set to 2.5V. The macros corresponding to low power flash detection are BATT_TEST_DEMO and VBAT_ALRAM_FLASH_CHECK.

Power Supervision and Charging

In the earbuds and charging case system, the power supervision, charging and discharging are in the batt part, mainly including batt.c, batt_UART.c and sy5500.c. The power management module can be enabled by macro HEADSET_UI_EN and BATTERY_CODE.

Charging logic: the earbuds is charged when it is in the charging case and the charging case is closed, otherwise it is not charged.

In the App layer, the power management module can be configured through the two interfaces batt_init() and batt_task().

Initialisation Process

The earbuds will be fully charged when battery reaches 4.2 ~ 4.35V, play the tone when battery drops to 3.5V, and shut down when battery drops below 3.3V. Considering the fluctuation of charging levels, there are two level arrays of charging and non-charging. The earbuds is charged when it is in the charging case and the charging case is closed, otherwise it is not charged.

Non-charged level arrays

Charged level arrays

The relevant configuration of the SY5500 chip is in interface void sy5500_set_default_state().

The earbuds is charged when it is in the charging case and the charging case is closed, otherwise it is not charged. After manually opening the charging case, it is in non-charging mode and the earbuds is powered on. The charging case sends a fixed waveform through the pogo pin to make SY5500 in TRX mode, i.e. UART signaling mode, and then sends the corresponding opening charging case command. During the power-on process, the initialization part will be entered first.

First, initialize the corresponding module and related parameters (mark 1 in the following figure), the first power-on needs to distinguish whether the current state is PCBA or not (i.e. the first power-on test of the product); the power supervision state will not be entered in PCBA mode, but the non-PCBA, i.e. normal state, will enter the charge detection and power supervision state (mark 2 in the following figure); after power on, the current power is judged, if the power is normal, the current power is saved and the default earbuds is in the charging case (mark 4 in the following figure); otherwise, if the power is too low, exit TRX mode and enter deep sleep mode (mark 3 in the following figure).

Power-on initialisation

Power-on initialisation

There are three main parts: the charge status check, the battery level check when power on and the low power handling module. Their interfaces correspond to: sy5500_charge_status_handler(), intbatt_power_on_level_check() and batt_deep_handler().

(1) When powering on to inquire the SY5500 status, the charging case will be out of the charging state when it is opened and powered on, except TRX mode as the normal interaction state between the charging case and the earbuds, all other cases are abnormal state, need to reset the relevant parameters and enter the deep sleep handling (mark 1 in the following figure). When the SY5500 is in the TRX state at power-on, the state is considered to be normal power-on, and the current power is quickly collected (mark 2 in the following figure).

Charge status check

Charge status check

This interface checks the relevant status of the SY5500 and will only be called when powered on.

(2) The first fast power collection at power-on is a delayed process that takes place every 200us and enough power values (16 times) need to be collected, which will update the current power value and save it (mark 1, 3 in the following figure); if the macro VBAT_ALRAM_FLASH_CHECK is enabled, the flash low-level warning detection function will be activated (mark 2 in the following figure); if it is TWS mode, that is, in the dual earbuds state, a lower power level will be compared as the current power (mark 4 in the following figure).

Battery level check when power on

(3) The interface in the figure below will be entered when certain conditions are met to enter deep sleep state; the default deep sleep wake-up source is pad wake-up, and the corresponding GPIO is GPIO_PC5 (mark 1 in the following figure); if the charging state is 5V at this time, the timing wake-up source is added. At the same time, if the wake-up period is 30s when it is fully charged, the wake-up period is 5s if it is not fully charged (mark 2, 3 in the following figure); turn off power collection and update the charging status in the non-charging state (mark 4 in the following figure); if it is in TRX mode or in standby mode and receives commands from the charging case, it will wake up after 100ms (mark 5 in the following figure).

Low power handling module

The complete power-on and power-off process can refer to the following logic analyser timing diagram:

The UART timing diagram at power on and normal state:

UART timing diagram

The UART timing diagram in power off and charging states:

UART timing diagram

As shown above, the TRX mode of the SY5500 will be exited when it is powered off, and it will count down to enter the forced shutdown; when the earbuds are powered off and charging in the charging case, the earbuds will wake up every 5s or so to detect the temperature and other relevant status during charging, in order to protect the SY5500 chip and circuit safety.

Real-time Power Collection, Entering and Exiting the Charging Case and Charging

Currently power collection and charge state supervision are handled in main_loop, the corresponding interface is batt_task(). The interface mainly includes: entry and exit charging case status management, power collection updates, low power handling, charging status handling, interaction with the SY5500 chip, and UART signaling interaction between the charging case, as shown in the figure below.

batt_task

When the master in TWS mode is scanning, it needs to synchronize the battery power with the slave, batt_tws_check_scan_state().

The corresponding interface for entering and exiting the charging case state is batt_in_out_box_check(). There are two main bases for the earbuds to enter the charging case state: one is the charging state; The second is that the received UART signaling is a valid value (mark 1 in the following figure); Exit the charging case is judged by the timeout mechanism (mark 3 in the following figure). The speaker of the earbuds in the charging case is silent and will only be activated when it is out of the charging case (mark 2 in the following figure).

The earbuds enter and exit the charging case state

The corresponding interface for power supervision is batt_level_supervise_task(), which collects power every 500ms, and then compares it with the last charge, and can only increase if it is a charging state, otherwise it can only decrease (mark 1 in the following figure). Once the power is updated, the master and slave earbuds power in the TWS state need to be synchronised (mark 2 in the following figure); if the power is too low at this time, the handover process will be performed to exit the TRX mode and start the shutdown countdown (mark 3 in the following figure).

Power collection and update

When the power level is below 3.5V and the earbuds is not yet in power off, the earbuds plays a low power tone, and flash the LED to start timing for 15min (mark 1 in the following figure). Then the earbuds plays a tone again, after the LED flashes and timing for 15 minutes, it exits the TRX mode and start the shutdown countdown (mark 2 in the following figure).

Low power handling

Low power handling

In TWS mode, when the earbuds is put back in the charging case, the earbuds will perform the handover process and start the shutdown countdown. The corresponding interface is batt_charge_reboot_handler().

The chip B91 interacts with the SY5500 through I2C. When the temperature of the SY5500 chip exceeds the operating range, it will enter the ship mode. The corresponding interface is batt_sy5500_task().

Operating state Temperature range Protection behavior
Charging phase T < 0°C Turn off charging
0°C < T < 10°C The charging current is reduced by half
10°C < T < 45°C Normal charging
45°C < T < 60°C The float charging voltage drops to 4.05V
60°C < T Turn off charging
Discharge phase T < -10°C Turn off discharge
-10°C < T < 60°C Normal discharge
60°C < T Turn off discharge

The earbuds communicates with the charging case through UART, and the signaling mainly includes the content of the following figure, once the earbuds receives a valid UART signaling from the charging case, it is considered that the earbuds is in the charging case.

UART signaling

For example, the UART timing information in the following figure:

Open charging case command: From box 0x5555

Earbuds status: From ear 0x55a51812

UART timing information