RISC-V内核中科蓝讯BT8922开发

BT8922

GPIO配置

官方说明部分

GPIOAFEN      //0:当作通用GPIO使用   //1:当作其它功能性IO,如串口/SPI..
GPIOADE       //数字IO使能: 0为模拟IO, 1 为数字IO,    //如作为AUX/MIC输入的IO口就需要设置成模拟IO, 减少数字IO对AUX的干扰.
GPIOADIR      //控制IO的方向:  0为输出, 1为输入.
GPIOA         //IO方向为输入时,读此寄存器的值即得到引脚的高低状态  //IO为输出时, 设置此寄存器的高低即设置对应引脚的高低输出.
GPIOADRV      //0 输出驱动为8mA   //1 输出驱动为32 mA   //IO方向为输出时有效
  
//以下寄存器控制上拉或下拉, 注意只有当IO设置为输入时, 上下拉才有效.  //IO方向为输出时上下拉自动无效  
GPIOAPU          //10K上拉使能
GPIOAPD          //10K下拉使能
GPIOAPU200K      //200K上拉使能
GPIOAPD200K      //200K下拉使能
GPIOAPU300       //300欧上拉使能
GPIOAPD300       //300欧下拉使能

//作为输出时,输出电平的高低也可以通过设置GPIOASET/GPIOACLR来快速实现,注意GPIOASET/GPIOACLR只对写1的位有效.
GPIOASET = BIT(0);    //PA0输出高, 等效于 GPIOA |= BIT(0);其它位状态不变.
GPIOACLR = BIT(0);    //PA0输出低, 等效于GPIOA &= ~BIT(0);其它位状态不变.   

常见输入输出使用示例: 1. PA0输出高低(点LED灯)示例:

int main(void)
{
    //示例开始
    GPIOAFEN &= ~BIT(0);   //PA0作为GPIO使用
    GPIOADE  |= BIT(0);    //PA0设置为数字IO
    GPIOADIR &= ~BIT(0);   //PA0方向设置为输出
    WDT_DIS();          //测试程序关看门狗,防止看门狗复位
    while(1) {
        GPIOA |= BIT(0);       //PA0输出高, 等效于GPIOASET = BIT(0);
        delay_ms(100);
        GPIOA &= ~BIT(0);      //PA0输出低, 等效于GPIOACLR = BIT(0);
        delay_ms(100);
}

配置得到PA0引脚状态(作用输入)示例:

int main(void)
{
    //示例开始
    GPIOAFEN &= ~BIT(0);   //PA0作为GPIO使用
    GPIOADE  |= BIT(0);    //PA0设置为数字IO
    GPIOADIR |= BIT(0);    //PA0方向设置为输入
    GPIOAPU  |= BIT(0);    //作为输入,为了有稳定的输入状态(1或0),在外部没上拉或下拉时,需要根据实际情况开内部上拉或下拉
    WDT_DIS();             //测试程序关看门狗,防止看门狗复位
    while(1) {
        delay_ms(100);
        printf("GPIOA0 = 0x%X\n",GPIOA & BIT(0));    //打印读到的PA0状态
}

关于 GPIOASET/ GPIOACLR: 只对写入为1的位有效, 写入为0的位则不影响它以前的状态的. 如PA0输出高低

GPIOASET = BIT(0);    //PA0输出高, 等效于 GPIOA |= BIT(0);
GPIOACLR = BIT(0);    //PA0输出低, 等效于GPIOA &= ~BIT(0);   

GPIO输出控制优化加速

此两个寄存器, 主要是优化IO输出高低电平而设置. 如设置PA0为高, 为了不影响GPIOA上的其它IO的操作, 以前程序一般这样写GPIOA |= BIT(0); 这里实际上有三个步骤:

1) 先读出GPIOA
2) 再把GPIOA | BIT(0)
3) 把或后的结果再写回GPIOA

而使用GPIOASET效优化后, 直接一步GPIOASET = BIT(0);
即达到 上面三步才能达到的效.
GPIOADE  &= ~BIT(7);//模拟:0 or 数字:1

/* 功能选择 */
GPIOAFEN &= ~BIT(7);//普通IO:0 or 复用IO:1

/* 上下拉控制 */
GPIOAPU  &= ~BIT(7);//10Kohm 上拉使能(输入有效) 0不用 1使能
GPIOAPU200K  &= ~BIT(7);//200Kohm 上拉使能(输入有效) 0不用 1使能
GPIOAPU300  &= ~BIT(7);//300ohm 上拉使能(输入有效) 0不用 1使能
GPIOAPD  &= ~BIT(7);//10Kohm 下拉使能(输入有效) 0不用 1使能
GPIOAPD300  &= ~BIT(7);//300ohm 下拉使能(输入有效) 0不用 1使能
GPIOAPD200K  &= ~BIT(7);//200Kohm 下拉使能(输入有效) 0不用 1使能

/* 输入输出控制 */
GPIOADIR |= BIT(7);//方向选择 0输出 1输入

/* 驱动能力控制 */
GPIOADRV |= BIT(7);//驱动电流 写0 8 or 写1 32mA

/* 端口功能映射控制 */
FUNCMCON0 =31:28】UART1 RX mapping
0000: no affect
0001: map to G1
0010: map to G2
0011: map to TX pin by UT1TXMAP select
1111: Clear these bits
【27:24】UART1 TX mapping
0000: no affect
0001: map to G1
0010: map to G2
1111: Clear these bits
【15:12】UART0 RX mapping
0000: no affect
0001: map to G1
0010: map to G2
0011: map to G3
0100: map to G4
0101: map to G5
0110: map to G6
0111: map to TX pin by UT0TXMAP select
1111: Clear these bits
【11:8】UART0 TX mapping  
0000: no affect
0001: map to G1
0010: map to G2
0011: map to G3
0100: map to G4
0101: map to G5
0110: map to G6
0111: map to G7
1111: Clear these bits
【7:4】SPI0 mapping
0000: no affect
0001: map to G1
0010: map to G2
0011: map to G3
1111: Clear these bits
【3:0】SD0 mapping
0000: no affect
0001: map to G1
0010: map to G2
0011: map to G3
0100: map to G4
0101: map to G5
0110: map to G6
1111: Clear these bits
 
FUNCMCON1 =11:8】UART2 RX mapping 
0000: no affect
0001: map to G1
0010: map to G2
0011: map to TX pin by UT2TXMAP select
1111: Clear these bits
【7:4】UART2 TX mapping
0000: no affect
0001: map to G1
0010: map to G2
1111: Clear these bits
  
FUNCMCON2 = /* 设置定时器 */
  
FUNCMCON2 = /* 设置MPDM interface mapping 和 PDM interface mapping */  

对于G1这种IO组合名称定义,查阅config_define.h,以及在Datasheet文件中找到相关定义
在这里插入图片描述

配置一个端口为串口

GPIOADE  |= BIT(7);//数字
GPIOAPU  |= BIT(7);//上拉10Kohm
GPIOADIR |= BIT(7);//方向选择1输入
GPIOAFEN |= BIT(7);//功能复用IO
GPIOADRV |= BIT(7);//驱动电流32mA
#define URX0MAP_TX	(7<<12) //0111 0000 0000 0000
#define UTX0MAP_PA7 (1 << 8)//0000 0001 0000 0000
FUNCMCON0 = URX0MAP_TX | UTX0MAP_PA7;//RX0 Map To TX0, TX0 Map to G1

通过对照手册,设定UART1的相关寄存器设置,在config_define.h中

PS:数据手册中对UART1的RX脚没有G2相关描述,不过控制寄存器中存在G2组,这是datasheet无法与用户手册对应的地方!!!

/*****************************************************************************
 * Module    : uart1 Mapping选择列表 用户手册与数据手册冲突,以数据手册为准
 *****************************************************************************/
#define UTX1MAP_PA7     (1 << 24)        //G1 uart1 tx: PA7
#define UTX1MAP_VUSB    (3 << 24)        //G3 uart1 tx: VUSB

#define URX1MAP_PA6     (1 << 28)        //G1 uart1 rx: PA6

/*****************************************************************************
 * Module    : uart2 Mapping选择列表 以数据手册为准
 *****************************************************************************/
#define UTX2MAP_PB2     (2 << 4)        //G2 uart2 tx: PB2
#define UTX2MAP_VUSB    (3 << 4)        //G3 uart2 tx: VUSB
#define UTX2MAP_CLR     (0xF << 4)      //清除映射

#define URX2MAP_PB1     (2 << 8)        //G2 uart2 rx: PB1
#define URX2MAP_CLR     (0xF << 8)      //清除映射

配置为普通GPIO

/*****************************************************************************
* Module    : GPIO list
*****************************************************************************/
#define IO_NONE             0
#define IO_PA0              1
#define IO_PA1              2
#define IO_PA2              3
#define IO_PA3              4
#define IO_PA4              5
#define IO_PA5              6
#define IO_PA6              7
#define IO_PA7              8
#define IO_PB0              9
#define IO_PB1              10
#define IO_PB2              11
#define IO_PB3              12
#define IO_PB4              13
#define IO_PE0              14
#define IO_PE1              15
#define IO_PE2              16
#define IO_PE3              17
#define IO_PE4              18
#define IO_PE5              19
#define IO_PE6              20
#define IO_PE7              21
#define IO_PF0              22
#define IO_PF1              23
#define IO_PF2              24
#define IO_PF3              25
#define IO_PB5              26
#define IO_MAX_NUM          26

#define IO_MUX_SDCLK        27
#define IO_MUX_SDCMD        28
#define IO_MUX_PWRKEY       29
#define IO_MUX_MICL         30

typedef struct {
    psfr_t sfr;             //GPIO SFR ADDR
    u8 num;
    u8 type;                //type = 1,高压IO,没有300R的强上下拉电阻。 type = 0, 普通IO, 有内部300R上下拉电阻。
} gpio_t;

/* 配置IO,io_num就是上表中数字 */
gpio_cfg_init(gpio_t *g, u8 io_num);

简化普通GPIO配置

/* GPIO基础配置 */
#define GET_GPIO_DE_REG(port)             GPIO##port##DE
#define GET_GPIO_FEN_REG(port)            GPIO##port##FEN
#define GET_GPIO_DIR_REG(port)            GPIO##port##DIR
#define GET_GPIO_DRV_REG(port)            GPIO##port##DRV
#define GET_GPIO_DATA_REG(port)           GPIO##port
#define GET_GPIO_SET_REG(port)            GPIO##port##SET
#define GET_GPIO_CLR_REG(port)            GPIO##port##CLR

/* 上拉控制 */
#define GET_GPIO_10K_PUP_REG_ADDR(port)   GPIO##port##PU
#define GET_GPIO_200K_PUP_REG_ADDR(port)  GPIO##port##PU200K
#define GET_GPIO_300_PUP_REG_ADDR(port)   GPIO##port##PU300
#define GET_GPIO_10K_PUP_REG_ADDR(port)   GPIO##port##PU

/* 下拉控制 */
#define GET_GPIO_10K_PD_REG_ADDR(port)    GPIO##port##PD
#define GET_GPIO_200K_PD_REG_ADDR(port)   GPIO##port##PD200K
#define GET_GPIO_300_PD_REG_ADDR(port)    GPIO##port##PD300
#define GET_GPIO_10K_PD_REG_ADDR(port)    GPIO##port##PD

/**
 * @brief 配置32mA电流输出端口
 * @param port 端口
 * @param pin  引脚号PAx
 * 
 */
#define GPIO_CONFIG_OUTPUT_MODE(port, pin) \
  do{ \
    GET_GPIO_DE_REG(port) |= BIT(pin); \
    GET_GPIO_FEN_REG(port) &= ~BIT(pin); \
    GET_GPIO_DIR_REG(port) &= ~BIT(pin); \
    GET_GPIO_DRV_REG(port) |= BIT(pin); \
    GET_GPIO_CLR_REG(port) = BIT(pin); \
  }while(0)

/**
 * @brief 配置8mA低电流驱动输出
 * @param port 端口
 * @param pin  引脚号PAx
 * 
 */
#define GPIO_CONFIG_SAMALL_OUTPUT_MODE(port, pin) \
  do{ \
    GET_GPIO_DE_REG(port) |= BIT(pin); \
    GET_GPIO_FEN_REG(port) &= ~BIT(pin); \
    GET_GPIO_DIR_REG(port) &= ~BIT(pin); \
    GET_GPIO_DRV_REG(port) &= ~BIT(pin); \
    GET_GPIO_CLR_REG(port) = BIT(pin); \
  }while(0)

/**
 * @brief 配置引脚输入模式,默认10K上拉
 * @param port 端口
 * @param pin  引脚号PAx
 * 
 */
#define GPIO_CONFIG_INPUT_MODE(port, pin) \
  do{ \
    GET_GPIO_DE_REG(port) |= BIT(pin); \
    GET_GPIO_FEN_REG(port) &= ~BIT(pin); \
    GET_GPIO_DIR_REG(port) |= BIT(pin); \
    GET_GPIO_DRV_REG(port) |= BIT(pin); \
    GET_GPIO_10K_PUP_REG_ADDR(port) |= BIT(pin); \
  }while(0)

/**
 * @brief 设置引脚高电平
 * @param port 端口
 * @param pin  引脚号PAx
 * 
 */
#define SET_GPIO_TO_HIGH(port, pin) \
  do{ \
    GET_GPIO_SET_REG(port) = BIT(pin); \
  }while(0)

/**
 * @brief 设置引脚低电平
 * @param port 端口
 * @param pin  引脚号PAx
 * 
 */
#define SET_GPIO_TO_LOW(port, pin) \
  do{ \
    GET_GPIO_CLR_REG(port) = BIT(pin); \
  }while(0)

/**
 * @brief 获取引脚电平
 * @param port 端口
 * @param pin  引脚号PAx
 * 
 */
#define GET_GPIO_DATA(port, pin) \
  (GET_GPIO_DATA_REG(port) & BIT(pin))

  
static void Test_GPIO_Init(void)
{
  // /* 使能数字GPIO PE5 */
  // GPIOEDE |= BIT(5);

  // /* 普通IO口功能 */
  // GPIOEFEN &= ~BIT(5);

  // /* 设置为输出 */
  // GPIOEDIR &= ~BIT(5);

  // /* 设置高电流驱动 */
  // GPIOEDRV |= BIT(5);

  // /* 设置为低电平 */
  // GPIOE &= ~BIT(5);
  GPIO_CONFIG_OUTPUT_MODE(E, 5);
}

static void Test_GPIO_Out(uint8_t GPIO_State)
{
  if(GPIO_State)
  {
    // GPIOE |= BIT(5);
    SET_GPIO_TO_HIGH(E, 5);
  }
  else
  {
    // GPIOE &= ~BIT(5);
    SET_GPIO_TO_LOW(E, 5);
  }
}

简化后的GPIO配置自定义LED

蓝讯有配置LED闪烁方式的工具(download),默认高电平点亮,内部配置IO看不到,所以在自己的电路中如果灯是低电平点亮,就无法使用工具进行配置LED,需要我们做下修改(配置IO、设置高低电平的接口由我们自己代码定义,闪烁周期方式由配置工具定义
并不推荐这么做,功能由sdk实现的就尽量不动里面的代码,对后续升级sdk方便一点
1、在port_led.c文件中增加如下接口
在这里插入图片描述
我们在bsp_led.c中实现这些接口:注意代码放置在.com区域

#define USER_BLUE_LED_PORT              B
#define USER_BLUE_LED_PIN               4
#define USER_GREEN_LED_PORT             B
#define USER_GREEN_LED_PIN              3
#define USER_RED_LED_PORT               A
#define USER_RED_LED_PIN                6
#define USER_SET_LED_OFF_LEVEL          1   //1是高电平灯灭

AT(.com_text.led_disp)
void user_led_cfg_port_init(gpio_t *g)
{
  if(&bled_gpio == g)
  {
    GPIO_CONFIG_OUTPUT_MODE(USER_BLUE_LED_PORT, USER_BLUE_LED_PIN);
  }

  if(&rled_gpio == g)
  {
    GPIO_CONFIG_OUTPUT_MODE(USER_RED_LED_PORT, USER_RED_LED_PIN);
  }
}


AT(.com_text.led_disp)
void user_led_cfg_set_on(gpio_t *g)
{
  if(&bled_gpio == g)
  {
    #if USER_SET_LED_OFF_LEVEL == 0
      SET_GPIO_TO_HIGH(USER_BLUE_LED_PORT, USER_BLUE_LED_PIN);
    #else
      SET_GPIO_TO_LOW(USER_BLUE_LED_PORT, USER_BLUE_LED_PIN);
    #endif
  }

  if(&rled_gpio == g)
  {
    #if USER_SET_LED_OFF_LEVEL == 0
      SET_GPIO_TO_HIGH(USER_RED_LED_PORT, USER_RED_LED_PIN);
    #else
      SET_GPIO_TO_LOW(USER_RED_LED_PORT, USER_RED_LED_PIN);
    #endif
  }
}

AT(.com_text.led_disp)
void user_led_cfg_set_off(gpio_t *g)
{
  if(&bled_gpio == g)
  {
    #if USER_SET_LED_OFF_LEVEL == 1
      SET_GPIO_TO_HIGH(USER_BLUE_LED_PORT, USER_BLUE_LED_PIN);
    #else
      SET_GPIO_TO_LOW(USER_BLUE_LED_PORT, USER_BLUE_LED_PIN);
    #endif
  }

  if(&rled_gpio == g)
  {
    #if USER_SET_LED_OFF_LEVEL == 1
      SET_GPIO_TO_HIGH(USER_RED_LED_PORT, USER_RED_LED_PIN);
    #else
      SET_GPIO_TO_LOW(USER_RED_LED_PORT, USER_RED_LED_PIN);
    #endif
  }
}

xm配置文件脚本解释

# 添加一个子项控件,左侧标题,控件描述(底部提示)
config(SUB, "MIC参数", "蓝牙和ANC的MIC参数");

# 下拉控件,控件标题,底部提示,变量名,下拉列表数目,数目选项,默认0
config(LIST, "提示音语言选择", "选择系统的语言", LANG_ID, 4, "英文", "中文", "中英文(出厂默认英文)", "中英文(出厂默认中文)", 0);

# 开关控件,控件标题,控件注释(底部提示),变量名(实际小写),默认值0
config(CHECK, "MICR功能", "是否打开MICR功能", MICR_EN, 0);

# 列表控件,控件标题,控件注释(底部提示),变量名,BIT(固定语法), 占位大小3,默认值5,列表数目3
config(LISTVAL, "MICR通路选择", "选择MICR通路", MICR_SEL, BIT, 3, 5, 3, ("MICNR_PF2", 4), ("MICPR_PF3", 5), ("MICR_VUSB", 6),MICR_EN);

# 输入控件,控件标题,控件注释(底部提示),变量名,占位大小4,数值范围低0,数值范围高15,默认值3,依赖MICR_EN开关状态
config(BIT, "MICR内部偏置电阻大小", "MICR内部偏置电阻配置", MICR_BIAS_RESISTOR, 4, 0, 15, 3, MICR_EN);

# 输入控件,控件标题,控件注释(底部提示),变量名,数值范围低0,数值范围高13,默认值6,依赖MICR_EN开关状态 ps:此控件单独占一个字节
config(BYTE, "MICL模拟增益", "MICL模拟增益配置(3~42DB), Step 3DB", MICL_ANL_GAIN, 0, 13, 6, MICL_EN);

当增加一行配置项目,需要rebuild工程

config(BIT, "MICL内部偏置电阻xx大小", "MICL内部偏置电阻配置xx", MICL_BIAS_RESISTORx, 10, 1, 16, 4, MICL_EN);

downloader工具重新读取配置文件,才会增加新的配置项目,同时会自动更改以下文件。

  • 配置更新

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BIPFZddI-1659060313069)(BT8892B_Aids.image/image-20211027181040250.png)]

  • xcfg配置数据结构更新

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-OL8jsi3t-1659060313069)(BT8892B_Aids.image/image-20211027180909048.png)]

音频文件更新替换

当增加或替换音乐资源文件时,需要rebuild工程

音频文件目录位于:app\projects\earphone\Output\bin\res

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hMrIImgc-1659060313069)(BT8892B_Aids.image\image-20211028105239539.png)][外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EgcyKW5Z-1659060313070)(BT8892B_Aids.image\image-20211028111745573.png)]

app\projects\earphone\plugin\multi_lang.h

enum {
    IDX_LANGUAGE_MP3,
    IDX_LOW_BATTERY_MP3,
    IDX_POWERON_MP3,
    IDX_POWEROFF_MP3,
    IDX_MAX_VOL_MP3,
    IDX_SDCARD_MODE_MP3,
    IDX_USB_MODE_MP3,
    IDX_AUX_MODE_MP3,
    IDX_CLOCK_MODE_MP3,
    IDX_FM_MODE_MP3,
    IDX_SPK_MODE_MP3,
    IDX_PC_MODE_MP3,
    IDX_BT_MODE_MP3,
    IDX_CAMERA_MODE_MP3,
    IDX_LEFT_CH_MP3,
    IDX_RIGHT_CH_MP3,
    IDX_PAIRING_MP3,
    IDX_WAIT4CONN_MP3,
    IDX_CONNECTED_MP3,
    IDX_DISCONNECT_MP3,
    IDX_CAMERA_ON_MP3,
    IDX_CAMERA_OFF_MP3,
    IDX_CALL_HANGUP_MP3,
    IDX_CALL_REJECT_MP3,
    IDX_REDIALING_MP3,
    IDX_NUM_0_MP3,
    IDX_NUM_1_MP3,
    IDX_NUM_2_MP3,
    IDX_NUM_3_MP3,
    IDX_NUM_4_MP3,
    IDX_NUM_5_MP3,
    IDX_NUM_6_MP3,
    IDX_NUM_7_MP3,
    IDX_NUM_8_MP3,
    IDX_NUM_9_MP3,
    IDX_MUSIC_MODE_MP3,
    IDX_GAME_MODE_MP3,
    IDX_MAX_MP3,
};

typedef struct {
    u32 *ptr;
    u32 *len;
} res_addr_t;

const res_addr_t *res_get_ring_num(u8 index);

#if (LANG_SELECT == LANG_EN_ZH)
void multi_lang_init(uint lang_id);

extern const res_addr_t mul_lang_tbl[2][IDX_MAX_MP3];
#define MUTIL_LANG_TBL_EN           1
#define RES_BUF_LANGUAGE_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_LANGUAGE_MP3   ].ptr
#define RES_LEN_LANGUAGE_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_LANGUAGE_MP3   ].len
#define RES_BUF_LOW_BATTERY_MP3     *mul_lang_tbl[sys_cb.lang_id][IDX_LOW_BATTERY_MP3].ptr
#define RES_LEN_LOW_BATTERY_MP3     *mul_lang_tbl[sys_cb.lang_id][IDX_LOW_BATTERY_MP3].len
#define RES_BUF_POWERON_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_POWERON_MP3    ].ptr
#define RES_LEN_POWERON_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_POWERON_MP3    ].len
#define RES_BUF_POWEROFF_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_POWEROFF_MP3   ].ptr
#define RES_LEN_POWEROFF_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_POWEROFF_MP3   ].len
#define RES_BUF_SDCARD_MODE_MP3     *mul_lang_tbl[sys_cb.lang_id][IDX_SDCARD_MODE_MP3].ptr
#define RES_LEN_SDCARD_MODE_MP3     *mul_lang_tbl[sys_cb.lang_id][IDX_SDCARD_MODE_MP3].len
#define RES_BUF_USB_MODE_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_USB_MODE_MP3   ].ptr
#define RES_LEN_USB_MODE_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_USB_MODE_MP3   ].len
#define RES_BUF_AUX_MODE_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_AUX_MODE_MP3   ].ptr
#define RES_LEN_AUX_MODE_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_AUX_MODE_MP3   ].len
#define RES_BUF_CLOCK_MODE_MP3      *mul_lang_tbl[sys_cb.lang_id][IDX_CLOCK_MODE_MP3 ].ptr
#define RES_LEN_CLOCK_MODE_MP3      *mul_lang_tbl[sys_cb.lang_id][IDX_CLOCK_MODE_MP3 ].len
#define RES_BUF_FM_MODE_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_FM_MODE_MP3    ].ptr
#define RES_LEN_FM_MODE_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_FM_MODE_MP3    ].len
#define RES_BUF_SPK_MODE_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_SPK_MODE_MP3   ].ptr
#define RES_LEN_SPK_MODE_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_SPK_MODE_MP3   ].len
#define RES_BUF_PC_MODE_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_PC_MODE_MP3    ].ptr
#define RES_LEN_PC_MODE_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_PC_MODE_MP3    ].len
#define RES_BUF_MAX_VOL_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_MAX_VOL_MP3    ].ptr
#define RES_LEN_MAX_VOL_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_MAX_VOL_MP3    ].len
#define RES_BUF_BT_MODE_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_BT_MODE_MP3    ].ptr
#define RES_LEN_BT_MODE_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_BT_MODE_MP3    ].len
#define RES_BUF_CAMERA_MODE_MP3     *mul_lang_tbl[sys_cb.lang_id][IDX_CAMERA_MODE_MP3].ptr
#define RES_LEN_CAMERA_MODE_MP3     *mul_lang_tbl[sys_cb.lang_id][IDX_CAMERA_MODE_MP3].len
#define RES_BUF_LEFT_CH_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_LEFT_CH_MP3    ].ptr
#define RES_LEN_LEFT_CH_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_LEFT_CH_MP3    ].len
#define RES_BUF_RIGHT_CH_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_RIGHT_CH_MP3   ].ptr
#define RES_LEN_RIGHT_CH_MP3        *mul_lang_tbl[sys_cb.lang_id][IDX_RIGHT_CH_MP3   ].len
#define RES_BUF_PAIRING_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_PAIRING_MP3    ].ptr
#define RES_LEN_PAIRING_MP3         *mul_lang_tbl[sys_cb.lang_id][IDX_PAIRING_MP3    ].len
#define RES_BUF_WAIT4CONN_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_WAIT4CONN_MP3  ].ptr
#define RES_LEN_WAIT4CONN_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_WAIT4CONN_MP3  ].len
#define RES_BUF_CONNECTED_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_CONNECTED_MP3  ].ptr
#define RES_LEN_CONNECTED_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_CONNECTED_MP3  ].len
#define RES_BUF_DISCONNECT_MP3      *mul_lang_tbl[sys_cb.lang_id][IDX_DISCONNECT_MP3 ].ptr
#define RES_LEN_DISCONNECT_MP3      *mul_lang_tbl[sys_cb.lang_id][IDX_DISCONNECT_MP3 ].len
#define RES_BUF_CAMERA_ON_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_CAMERA_ON_MP3  ].ptr
#define RES_LEN_CAMERA_ON_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_CAMERA_ON_MP3  ].len
#define RES_BUF_CAMERA_OFF_MP3      *mul_lang_tbl[sys_cb.lang_id][IDX_CAMERA_OFF_MP3 ].ptr
#define RES_LEN_CAMERA_OFF_MP3      *mul_lang_tbl[sys_cb.lang_id][IDX_CAMERA_OFF_MP3 ].len
#define RES_BUF_CALL_HANGUP_MP3     *mul_lang_tbl[sys_cb.lang_id][IDX_CALL_HANGUP_MP3].ptr
#define RES_LEN_CALL_HANGUP_MP3     *mul_lang_tbl[sys_cb.lang_id][IDX_CALL_HANGUP_MP3].len
#define RES_BUF_CALL_REJECT_MP3     *mul_lang_tbl[sys_cb.lang_id][IDX_CALL_REJECT_MP3].ptr
#define RES_LEN_CALL_REJECT_MP3     *mul_lang_tbl[sys_cb.lang_id][IDX_CALL_REJECT_MP3].len
#define RES_BUF_REDIALING_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_REDIALING_MP3  ].ptr
#define RES_LEN_REDIALING_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_REDIALING_MP3  ].len
#define RES_BUF_NUM_0_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_0_MP3      ].ptr
#define RES_LEN_NUM_0_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_0_MP3      ].len
#define RES_BUF_NUM_1_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_1_MP3      ].ptr
#define RES_LEN_NUM_1_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_1_MP3      ].len
#define RES_BUF_NUM_2_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_2_MP3      ].ptr
#define RES_LEN_NUM_2_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_2_MP3      ].len
#define RES_BUF_NUM_3_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_3_MP3      ].ptr
#define RES_LEN_NUM_3_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_3_MP3      ].len
#define RES_BUF_NUM_4_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_4_MP3      ].ptr
#define RES_LEN_NUM_4_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_4_MP3      ].len
#define RES_BUF_NUM_5_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_5_MP3      ].ptr
#define RES_LEN_NUM_5_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_5_MP3      ].len
#define RES_BUF_NUM_6_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_6_MP3      ].ptr
#define RES_LEN_NUM_6_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_6_MP3      ].len
#define RES_BUF_NUM_7_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_7_MP3      ].ptr
#define RES_LEN_NUM_7_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_7_MP3      ].len
#define RES_BUF_NUM_8_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_8_MP3      ].ptr
#define RES_LEN_NUM_8_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_8_MP3      ].len
#define RES_BUF_NUM_9_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_9_MP3      ].ptr
#define RES_LEN_NUM_9_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_9_MP3      ].len
#define RES_BUF_MUSIC_MODE_MP3      *mul_lang_tbl[sys_cb.lang_id][IDX_MUSIC_MODE_MP3 ].ptr
#define RES_LEN_MUSIC_MODE_MP3      *mul_lang_tbl[sys_cb.lang_id][IDX_MUSIC_MODE_MP3 ].len
#define RES_BUF_GAME_MODE_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_GAME_MODE_MP3  ].ptr
#define RES_LEN_GAME_MODE_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_GAME_MODE_MP3  ].len

定义MP3音频文件的代码,主要由IDX_MAX_MP3的枚举变量定义每个MP3文件的index。

res_addr_t.ptr 指定了每个MP3文件的存储地址

res_addr_t.len 指定了每个MP3文件的长度

如何增加新的音频文件

1、添加MP3文件

2、添加文件的宏定义

1)在res目录中增加MP3文件,本例中在zh目录中添加了num_10.mp3。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PAFI7aot-1659060313070)(BT8892B_Aids.image\image-20211028170130394.png)]

app\platform\bsp\bsp_music.h

enum {
    TWS_RES_INVALID,
    TWS_RES_NUM_0,
    TWS_RES_NUM_1,
    TWS_RES_NUM_2,
    TWS_RES_NUM_3,
    TWS_RES_NUM_4,
    TWS_RES_NUM_5,
    TWS_RES_NUM_6,
    TWS_RES_NUM_7,
    TWS_RES_NUM_8,
    TWS_RES_NUM_9,
    TWS_RES_NUM_10,//添加新文件的宏定义
...

app\platform\bsp\bsp_music.c

void tws_res_get_addr(u32 index, u32 *addr, u32 *len) {
    switch(index) {
    case TWS_RES_NUM_0:
        *addr = RES_BUF_NUM_0_MP3;
        *len = RES_LEN_NUM_0_MP3;
        break;

 ...

    case TWS_RES_NUM_9:
        *addr = RES_BUF_NUM_9_MP3;
        *len = RES_LEN_NUM_9_MP3;
        break;

    case TWS_RES_NUM_10://添加新文件的宏定义
        *addr = RES_BUF_NUM_10_MP3;
        *len = RES_LEN_NUM_10_MP3;
        break;

app\projects\earphone\plugin\multi_lang.c

const res_addr_t mul_lang_tbl[2][IDX_MAX_MP3] = {
    {
...
        [IDX_NUM_0_MP3      ] = {&RES_BUF_ZH_NUM_0_MP3,        &RES_LEN_ZH_NUM_0_MP3},
        [IDX_NUM_1_MP3      ] = {&RES_BUF_ZH_NUM_1_MP3,        &RES_LEN_ZH_NUM_1_MP3},
        [IDX_NUM_2_MP3      ] = {&RES_BUF_ZH_NUM_2_MP3,        &RES_LEN_ZH_NUM_2_MP3},
        [IDX_NUM_3_MP3      ] = {&RES_BUF_ZH_NUM_3_MP3,        &RES_LEN_ZH_NUM_3_MP3},
        [IDX_NUM_4_MP3      ] = {&RES_BUF_ZH_NUM_4_MP3,        &RES_LEN_ZH_NUM_4_MP3},
        [IDX_NUM_5_MP3      ] = {&RES_BUF_ZH_NUM_5_MP3,        &RES_LEN_ZH_NUM_5_MP3},
        [IDX_NUM_6_MP3      ] = {&RES_BUF_ZH_NUM_6_MP3,        &RES_LEN_ZH_NUM_6_MP3},
        [IDX_NUM_7_MP3      ] = {&RES_BUF_ZH_NUM_7_MP3,        &RES_LEN_ZH_NUM_7_MP3},
        [IDX_NUM_8_MP3      ] = {&RES_BUF_ZH_NUM_8_MP3,        &RES_LEN_ZH_NUM_8_MP3},
        [IDX_NUM_9_MP3      ] = {&RES_BUF_ZH_NUM_9_MP3,        &RES_LEN_ZH_NUM_9_MP3},
        [IDX_NUM_10_MP3     ] = {&RES_BUF_ZH_NUM_10_MP3,       &RES_LEN_ZH_NUM_10_MP3},//添加新文件的宏定义
        [IDX_MUSIC_MODE_MP3 ] = {&RES_BUF_ZH_MUSIC_MODE_MP3,   &RES_LEN_ZH_MUSIC_MODE_MP3},
        [IDX_GAME_MODE_MP3  ] = {&RES_BUF_ZH_GAME_MODE_MP3,    &RES_LEN_ZH_GAME_MODE_MP3},
    },
};

app\projects\earphone\plugin\multi_lang.h

enum {
    IDX_LANGUAGE_MP3,
    IDX_LOW_BATTERY_MP3,

...
        
    IDX_NUM_0_MP3,
    IDX_NUM_1_MP3,
    IDX_NUM_2_MP3,
    IDX_NUM_3_MP3,
    IDX_NUM_4_MP3,
    IDX_NUM_5_MP3,
    IDX_NUM_6_MP3,
    IDX_NUM_7_MP3,
    IDX_NUM_8_MP3,
    IDX_NUM_9_MP3,
    IDX_NUM_10_MP3,//添加新文件的宏定义
    IDX_MUSIC_MODE_MP3,
    IDX_GAME_MODE_MP3,
    IDX_MAX_MP3,
};

typedef struct {
    u32 *ptr;
    u32 *len;
} res_addr_t;

const res_addr_t *res_get_ring_num(u8 index);

#if (LANG_SELECT == LANG_EN_ZH)
void multi_lang_init(uint lang_id);

extern const res_addr_t mul_lang_tbl[2][IDX_MAX_MP3];
#define MUTIL_LANG_TBL_EN           1
...
#define RES_BUF_NUM_0_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_0_MP3      ].ptr
#define RES_LEN_NUM_0_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_0_MP3      ].len
#define RES_BUF_NUM_1_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_1_MP3      ].ptr
#define RES_LEN_NUM_1_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_1_MP3      ].len
#define RES_BUF_NUM_2_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_2_MP3      ].ptr
#define RES_LEN_NUM_2_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_2_MP3      ].len
#define RES_BUF_NUM_3_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_3_MP3      ].ptr
#define RES_LEN_NUM_3_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_3_MP3      ].len
#define RES_BUF_NUM_4_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_4_MP3      ].ptr
#define RES_LEN_NUM_4_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_4_MP3      ].len
#define RES_BUF_NUM_5_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_5_MP3      ].ptr
#define RES_LEN_NUM_5_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_5_MP3      ].len
#define RES_BUF_NUM_6_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_6_MP3      ].ptr
#define RES_LEN_NUM_6_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_6_MP3      ].len
#define RES_BUF_NUM_7_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_7_MP3      ].ptr
#define RES_LEN_NUM_7_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_7_MP3      ].len
#define RES_BUF_NUM_8_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_8_MP3      ].ptr
#define RES_LEN_NUM_8_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_8_MP3      ].len
#define RES_BUF_NUM_9_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_9_MP3      ].ptr
#define RES_LEN_NUM_9_MP3           *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_9_MP3      ].len
#define RES_BUF_NUM_10_MP3          *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_10_MP3     ].ptr//添加新文件的宏定义
#define RES_LEN_NUM_10_MP3          *mul_lang_tbl[sys_cb.lang_id][IDX_NUM_10_MP3     ].len//添加新文件的宏定义
#define RES_BUF_MUSIC_MODE_MP3      *mul_lang_tbl[sys_cb.lang_id][IDX_MUSIC_MODE_MP3 ].ptr
#define RES_LEN_MUSIC_MODE_MP3      *mul_lang_tbl[sys_cb.lang_id][IDX_MUSIC_MODE_MP3 ].len
#define RES_BUF_GAME_MODE_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_GAME_MODE_MP3  ].ptr
#define RES_LEN_GAME_MODE_MP3       *mul_lang_tbl[sys_cb.lang_id][IDX_GAME_MODE_MP3  ].len

...

#define RES_BUF_NUM_0_MP3               RES_BUF_ZH_NUM_0_MP3
#define RES_LEN_NUM_0_MP3               RES_LEN_ZH_NUM_0_MP3
#define RES_BUF_NUM_1_MP3               RES_BUF_ZH_NUM_1_MP3
#define RES_LEN_NUM_1_MP3               RES_LEN_ZH_NUM_1_MP3
#define RES_BUF_NUM_2_MP3               RES_BUF_ZH_NUM_2_MP3
#define RES_LEN_NUM_2_MP3               RES_LEN_ZH_NUM_2_MP3
#define RES_BUF_NUM_3_MP3               RES_BUF_ZH_NUM_3_MP3
#define RES_LEN_NUM_3_MP3               RES_LEN_ZH_NUM_3_MP3
#define RES_BUF_NUM_4_MP3               RES_BUF_ZH_NUM_4_MP3
#define RES_LEN_NUM_4_MP3               RES_LEN_ZH_NUM_4_MP3
#define RES_BUF_NUM_5_MP3               RES_BUF_ZH_NUM_5_MP3
#define RES_LEN_NUM_5_MP3               RES_LEN_ZH_NUM_5_MP3
#define RES_BUF_NUM_6_MP3               RES_BUF_ZH_NUM_6_MP3
#define RES_LEN_NUM_6_MP3               RES_LEN_ZH_NUM_6_MP3
#define RES_BUF_NUM_7_MP3               RES_BUF_ZH_NUM_7_MP3
#define RES_LEN_NUM_7_MP3               RES_LEN_ZH_NUM_7_MP3
#define RES_BUF_NUM_8_MP3               RES_BUF_ZH_NUM_8_MP3
#define RES_LEN_NUM_8_MP3               RES_LEN_ZH_NUM_8_MP3
#define RES_BUF_NUM_9_MP3               RES_BUF_ZH_NUM_9_MP3
#define RES_LEN_NUM_9_MP3               RES_LEN_ZH_NUM_9_MP3
#define RES_BUF_NUM_10_MP3              RES_BUF_ZH_NUM_10_MP3//添加新文件的宏定义
#define RES_LEN_NUM_10_MP3              RES_LEN_ZH_NUM_10_MP3//添加新文件的宏定义

以上修改完成后,执行Rebuild,会自动生成app\projects\earphone\res.h。RES_BUF_ZH_NUM_10_MP3RES_LEN_ZH_NUM_10_MP3的宏定义也会自动生成。如下所示:

#define RES_BUF_ZH_NUM_0_MP3                        (*(u32 *)0x11000cb8)
#define RES_LEN_ZH_NUM_0_MP3                        (*(u32 *)0x11000cbc)

#define RES_BUF_ZH_NUM_1_MP3                        (*(u32 *)0x11000cd8)
#define RES_LEN_ZH_NUM_1_MP3                        (*(u32 *)0x11000cdc)

#define RES_BUF_ZH_NUM_10_MP3                       (*(u32 *)0x11000cf8)
#define RES_LEN_ZH_NUM_10_MP3                       (*(u32 *)0x11000cfc)

最后可通过如下代码测试,是否能播放新添加的音频文件。

 bsp_tws_res_music_play(TWS_RES_NUM_10);

软件开关机

大致流程:

  • 关机:判断设备进入关机状态后,进入到此段代码,执行关闭外设,修改时钟,降低一切功耗,配置Powerkey脚为复位脚,死等
  • 开机:当Powerkey按下,设备执行复位,正常运行代码检测开关机状态power_on_check();,当为关机状态时检测按下时长进行开机,否则再次进入到关机状态
void sfunc_pwrdown_do(u8 vusb_wakeup_en)
{
#if USER_TKEY
    u32 tkey_wakeup_en = sys_cb.tkey_pwrdwn_en;
#endif

    printf("pwrdwn: %d\n", vusb_wakeup_en);

    RTCCON3 &= ~BIT(8);                             //rtc alarm wakeup disable
    RTCCON8 &= ~BIT(15);                            //RI_EN_VUSBDIV = 0
    RTCCON9 = 0x3ff;                                //Clr pending
#if USER_TKEY_LOWPWR_WAKEUP_DIS                     //电池无保护板且有内置触摸开关机功能方案,需要打开此宏
    if (sys_cb.vbat < 3050) {
        tkey_wakeup_en = 0;                         //低电关机, 关掉触模唤醒
    }
    if (tkey_wakeup_en) {
        RTCALM = RTCCNT + 300;                      //定时5分钟唤醒检查电池电量
        RTCCON3 |= BIT(8);
    }
#endif
    sfunc_power_save_enter();
    if (!vusb_wakeup_en) {
        RTCCON8 = (RTCCON8 & ~BIT(6)) | BIT(1);     //disable charger function
#if !VUSB_SMART_VBAT_HOUSE_EN
        vusb_wakeup_en = sfunc_pwrdown_w4_vusb_offline();
        if (xcfg_cb.ch_box_type_sel == 3) {         //5V完全掉电的仓
            RTCCON3 &= ~BIT(12);                    //RTCCON3[12], INBOX Wakeup disable
        }
#endif
    }

    RTCCON11 = (RTCCON11 & ~0x03) | BIT(2);         //WK PIN filter select 8ms
    uint rtccon3 = RTCCON3 & ~BIT(11);
    uint rtccon13 = RTCCON13 & ~BIT(11);
#if CHARGE_EN
    if ((xcfg_cb.charge_en) && (vusb_wakeup_en)) {
        rtccon3 |= BIT(11);                         //VUSB wakeup enable
    }
#endif
    RTCCON3 = rtccon3 & ~(BIT(10) | BIT(14));       //关WK PIN,再打开,以清除Pending
    PWRCON1 &= ~(0x1F<<14);                         //disable Flash Power Gate
    PWRCON1 |= BIT(18);                             //pdown flash power gate
#if USER_TKEY
    rtccon3 = RTCCON3;
    rtccon3 |=  BIT(3);                             //VDDCORE AON enable
    rtccon3 &= ~BIT(5);                             //VDDCORE short disable
    RTCCON3 = rtccon3;
    delay_us(3);
    if (tkey_wakeup_en) {
        RTCCON3 |= BIT(14);                         //Touch key long press wakeup
    }
#else
    RTCCON0 &= ~BIT(0);                             //RC2M_RTC Disable
#endif
    RTCCON0 &= ~BIT(4);                             //TKITF_EN disable
	RTCCON0 &= ~BIT(13);

    rtccon3 = RTCCON3 & ~0x17;                      //Disable VDDCORE VDDIO VDDBUCK, VDDXOEN
    rtccon3 |= BIT(6);                              //Core power down enable, VDDCORE short disable
    rtccon3 |= BIT(7);                              //RI_EN_VDDIO_AON   RTC 2.9V LDO enable
#if USER_PWRKEY
    if (sys_cb.wko_pwrkey_en) {
        rtccon3 |= BIT(10);                         //WK pin wake up enable
		#if POWER_KEY_USE_HIGHLEVEL
		rtccon13 &=~BIT(4);
		rtccon13 |= BIT(0) | BIT(8) | BIT(12)| BIT(16);
		#else
		rtccon13 |= BIT(0) | BIT(4) | BIT(12);      //wk pin0 wakeup, input, pullup10k enable
		#endif
    }
#endif // USER_PWRKEY

#if !USER_TKEY
    //保持电源,保证tkey通道能顺利关闭
    RTCCON3 |=  BIT(3);                             //VDDCORE AON enable
    RTCCON3 &= ~BIT(5);                             //VDDCORE short disable
    RTCCON0 |=  BIT(17);
    TKACON0 &= ~(BIT(23) | BIT(11));                //disable TK channel
    TKACON1 &= ~(BIT(23) | BIT(11));
#endif

    RTCCON |= BIT(5);                               //PowerDown Reset,如果有Pending,则马上Reset
    RTCCON13 = rtccon13;
    RTCCON3 = rtccon3;
    LPMCON |= BIT(0);
    asm("nop");asm("nop");asm("nop");
    while (1);
}

串口功能

所有串口波特率支持1.5Mbps

高速串口配置

配置工具,配置引脚

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Spl6c0cw-1659060313071)(README.image/image-20220215110243846.png)]

接收数据:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-D8HOdoPh-1659060313072)(README.image/image-20220215110331916.png)]

需要注意的是,数据接收代码需放在(AT)如下区域,不可调用printf打印输出

/**
 * @brief 高速串口数据接收
 *
 * @param data 数据
 * @param len  数据长度
 */
AT(.com_huart.text)
void func_huart_aids_put_data(const uint8_t *data, uint16_t len)
{
  CQ_putData(&CircularBuffer_Serial, data, len);
}

可以在外部循环中,加入如下代码,调试输出

uint32_t Len = CQ_getLength(CQ_Handle);  
if(Len > 0)
{
  CQ_ManualGetData(CQ_Handle, Temp_Buf, Len);
  printf("REC:\n");
  for(int i = 0; i < Len; i++)
  {
    printf("%02x ", Temp_Buf[i]);
  }
}

发送数据:

需要注意的是,发送缓冲区需要定义在如下区域:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4MSm91Ku-1659060313072)(README.image/image-20220215110850661.png)]

static uint8_t Send_Buf[20] AT(.aids_debug.cache);
huart_putcs(Send_Buf, 7);

UART0调试端口修改

修改位于config.h文件,第39行,设置想要的引脚输出

#define UART0_PRINTF_SEL                PRINTF_PA7//PRINTF_NONE             //选择UART打印信息输出IO,或关闭打印信息输出

音频导出

音频导出利用的是HUART外设,此外设使用DMA发送,所以发送区域buf需要全局变量或者静态。

HUART波特率最高支持1.5Mbps,串口协议为一个停止位和起始位+8bit数据即每次10个bit

每秒最大发送:1500000 / 10 = 150000Bytes

每秒最大发送音频数据/16bit位宽:150000 / 2 = 75000点音频 相当于最高支持 75K的采样率无校验导出!

16K采样率的音频16bit位宽,传输为16000点/s == 32000Bytes/s,分成64点每包发出(64 * 2Bytes),需要250包,每包加上协议字段6个字节,就是增加250*6=1500Bytes,传输1s时长的16K数据需要32000 + 1500 = 33500Bytes,以1.5Mbps可以:150000 / 35000 ~= 4.4通道数据

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wOWLr1nQ-1659060313077)(README.image/image-20211116125609827.png)]

1、增加导出代码

static const uint8_t auchCRCHi[] = {
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
  0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
  0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
  0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
  0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
  0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
  0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
  0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
  0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
  0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
  0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
  0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
  0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0,
  0x80, 0x41, 0x00, 0xC1, 0x81, 0x40, 0x00, 0xC1, 0x81, 0x40,
  0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0, 0x80, 0x41, 0x00, 0xC1,
  0x81, 0x40, 0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41,
  0x00, 0xC1, 0x81, 0x40, 0x01, 0xC0, 0x80, 0x41, 0x01, 0xC0,
  0x80, 0x41, 0x00, 0xC1, 0x81, 0x40
};

static const uint8_t auchCRCLo[] = {
  0x00, 0xC0, 0xC1, 0x01, 0xC3, 0x03, 0x02, 0xC2, 0xC6, 0x06,
  0x07, 0xC7, 0x05, 0xC5, 0xC4, 0x04, 0xCC, 0x0C, 0x0D, 0xCD,
  0x0F, 0xCF, 0xCE, 0x0E, 0x0A, 0xCA, 0xCB, 0x0B, 0xC9, 0x09,
  0x08, 0xC8, 0xD8, 0x18, 0x19, 0xD9, 0x1B, 0xDB, 0xDA, 0x1A,
  0x1E, 0xDE, 0xDF, 0x1F, 0xDD, 0x1D, 0x1C, 0xDC, 0x14, 0xD4,
  0xD5, 0x15, 0xD7, 0x17, 0x16, 0xD6, 0xD2, 0x12, 0x13, 0xD3,
  0x11, 0xD1, 0xD0, 0x10, 0xF0, 0x30, 0x31, 0xF1, 0x33, 0xF3,
  0xF2, 0x32, 0x36, 0xF6, 0xF7, 0x37, 0xF5, 0x35, 0x34, 0xF4,
  0x3C, 0xFC, 0xFD, 0x3D, 0xFF, 0x3F, 0x3E, 0xFE, 0xFA, 0x3A,
  0x3B, 0xFB, 0x39, 0xF9, 0xF8, 0x38, 0x28, 0xE8, 0xE9, 0x29,
  0xEB, 0x2B, 0x2A, 0xEA, 0xEE, 0x2E, 0x2F, 0xEF, 0x2D, 0xED,
  0xEC, 0x2C, 0xE4, 0x24, 0x25, 0xE5, 0x27, 0xE7, 0xE6, 0x26,
  0x22, 0xE2, 0xE3, 0x23, 0xE1, 0x21, 0x20, 0xE0, 0xA0, 0x60,
  0x61, 0xA1, 0x63, 0xA3, 0xA2, 0x62, 0x66, 0xA6, 0xA7, 0x67,
  0xA5, 0x65, 0x64, 0xA4, 0x6C, 0xAC, 0xAD, 0x6D, 0xAF, 0x6F,
  0x6E, 0xAE, 0xAA, 0x6A, 0x6B, 0xAB, 0x69, 0xA9, 0xA8, 0x68,
  0x78, 0xB8, 0xB9, 0x79, 0xBB, 0x7B, 0x7A, 0xBA, 0xBE, 0x7E,
  0x7F, 0xBF, 0x7D, 0xBD, 0xBC, 0x7C, 0xB4, 0x74, 0x75, 0xB5,
  0x77, 0xB7, 0xB6, 0x76, 0x72, 0xB2, 0xB3, 0x73, 0xB1, 0x71,
  0x70, 0xB0, 0x50, 0x90, 0x91, 0x51, 0x93, 0x53, 0x52, 0x92,
  0x96, 0x56, 0x57, 0x97, 0x55, 0x95, 0x94, 0x54, 0x9C, 0x5C,
  0x5D, 0x9D, 0x5F, 0x9F, 0x9E, 0x5E, 0x5A, 0x9A, 0x9B, 0x5B,
  0x99, 0x59, 0x58, 0x98, 0x88, 0x48, 0x49, 0x89, 0x4B, 0x8B,
  0x8A, 0x4A, 0x4E, 0x8E, 0x8F, 0x4F, 0x8D, 0x4D, 0x4C, 0x8C,
  0x44, 0x84, 0x85, 0x45, 0x87, 0x47, 0x46, 0x86, 0x82, 0x42,
  0x43, 0x83, 0x41, 0x81, 0x80, 0x40
};
/**
 * @brief modbusCRC calc with table
 *
 * @param data data
 * @param data_len data len
 * @return uint16_t
 */
uint16_t modbus_crc_return_with_table(uint8_t *data, uint16_t data_len)
{
  uint8_t ucCRCHi = 0xFF;
  uint8_t ucCRCLo = 0xFF;
  int iIndex;

  while(data_len--)
  {
    iIndex = ucCRCLo ^ *(data++);
    //
    //
    ucCRCLo = (uint8_t)(ucCRCHi ^ auchCRCHi[iIndex]);
    ucCRCHi = auchCRCLo[iIndex];
  }
  return (uint16_t)( ucCRCHi << 8 | ucCRCLo );
}

/**
  ******************************************************************
  * @brief   音频导出接口加入音频
  * @param   [in]Source_Audio_Data 原始数据.
  * @param   [in]Result_Audio_Data 处理后数据.
  * @return  None.
  * @author  aron566
  * @version V1.0
  * @date    2021-10-29
  ******************************************************************
  */
#define ENBALE_EXPORT_CRC 1

#define AUDIO_CHANNEL_NUM 2
#define MONO_FRAME_SIZE 	128

#define ENBALE_EXPORT_CRC 1

static int16_t Send_Buf[AUDIO_CHANNEL_NUM * MONO_FRAME_SIZE + 1 + 1 + 1] AT(.alg.cache);
AT(.com_text.alg)
void Audio_Export_Start(const int16_t *Source_Audio_Data, const int16_t *Result_Audio_Data)
{
  Send_Buf[0] = 0x6605;//帧头
#if ENBALE_EXPORT_CRC
  int16_t index = 2;
  Send_Buf[1] = AUDIO_CHANNEL_NUM * MONO_FRAME_SIZE * sizeof(int16_t);
  for(int16_t i = 0; i < MONO_FRAME_SIZE; i++)
  {
    Send_Buf[index] = Source_Audio_Data[i];
    Send_Buf[index+1] = Result_Audio_Data[i];
    index += 2;
  }

  /* 计算CRC */
  uint16_t crc_val = modbus_crc_return_with_table((uint8_t *)Send_Buf, index*sizeof(int16_t));
  memcpy(&Send_Buf[index], &crc_val, sizeof(uint16_t));
  huart_putcs(Send_Buf, AUDIO_CHANNEL_NUM * MONO_FRAME_SIZE * sizeof(int16_t) + 6);
#else
  int16_t index = 0;
  for(int16_t i = 0; i < MONO_FRAME_SIZE; i++)
  {
    Send_Buf[index] = Source_Audio_Data[i];
    Send_Buf[index+1] = Result_Audio_Data[i];
    index += 2;
  }
  huart_putcs(Send_Buf, AUDIO_CHANNEL_NUM * MONO_FRAME_SIZE * sizeof(int16_t));
#endif
}

2、调用处理

  Algorithm_Port_Start((const int16_t *)ptr, &Result_CH1_Ptr, &Result_CH2_Ptr);

#if USE_GPIO_TEST_ALGORITHM
  SET_GPIO_TO_LOW(TEST_ALGORITHM_GPIO_PORT, TEST_ALGORITHM_GPIO_PIN);
#endif

#if ENABLE_EXPORT_FUNC
  /* 音频导出 */
  if(xcfg_cb.huart_en)
  {
    Audio_Export_Start((const int16_t *)ptr, (const int16_t *)Result_CH2_Ptr);
  }
#endif

  /* output DAC */
  obuf1_put_mono_samples(Result_CH1_Ptr, Result_CH2_Ptr, samples);

3、打开音频生成工具,配置如下

  • 工具已开源(github)
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jK0vgfik-1659060313078)(README.image/image-20211118134839568.png)]

音频测试

注意事项:

1、杜绝USB连接工频220~交流电源,防止工频干扰,造成底噪问题!

  • 示波器
  • USB电源
  • USB串口工具

2、测试算法时间,通常改变如下变量:

  • 单帧处理点数

  • 主频改变

源码修改调整

音频路径调整

文件bsp_audio.c

//AUX analog gain -42DB~+3DB
//MIC analog gain: 0~13(共14级), step 3DB (3db ~ +42db)
//adadc digital gain: 0~63, step 0.5 DB, 保存在gain的低6bit
const sdadc_cfg_t rec_cfg_tbl[] = {
/*   通道,             增益,        采样率,      通路控制,    样点数,   回调函数*/
    {AUX_CHANNEL_CFG,  (2 << 6),    SPR_44100,   ADC2DAC_EN,    256,    aux_sdadc_callback},            /* AUX     */
    {MIC_CHANNEL_CFG,  (8 << 6),    SPR_44100,   ADC2DAC_EN,    256,    speaker_sdadc_callback},        /* SPEAKER */
    {MIC_CHANNEL_CFG,  (12 << 6),   SPR_8000,    ADC2DAC_EN,    128,    bt_sdadc_callback},             /* BTMIC   */
    {MIC_CHANNEL_CFG,  (12 << 6),   SPR_48000,   ADC2DAC_EN,    128,    usbmic_sdadc_callback},         /* USBMIC  */
    {MIC_CHANNEL_CFG,  (12 << 6),   SPR_44100,   ADC2SRC_EN,    256,    karaok_sdadc_callback},         /* KARAOK  */
    {MIC_CHANNEL_CFG,  (12 << 6),   SPR_16000,   ADC2DAC_EN,    256,    opus_sdadc_callback},           /* opus  */
    {MIC_CHANNEL_CFG,  (6 << 6),    SPR_44100,   ADC2SRC_EN,    256,    ttp_sdadc_callback},            /* TRANSPARENCY  */
};

void audio_path_init(u8 path_idx)
{
    sdadc_cfg_t cfg;
    memcpy(&cfg, &rec_cfg_tbl[path_idx], sizeof(sdadc_cfg_t));
        
#if FUNC_AUX_EN
    if (path_idx == AUDIO_PATH_AUX) {
        cfg.channel =  (xcfg_cb.auxr_sel << 4) | xcfg_cb.auxl_sel;
        cfg.gain =     ((u16)xcfg_cb.aux_anl_gain << 6) | xcfg_cb.aux_dig_gain;
    }
#endif // FUNC_AUX_EN

    if (path_idx == AUDIO_PATH_BTMIC || path_idx == AUDIO_PATH_KARAOK || path_idx == AUDIO_PATH_BTVOICE || path_idx == AUDIO_PATH_OPUS || path_idx == AUDIO_PATH_TTP) {
        if (path_idx == AUDIO_PATH_BTMIC) {
            if (sys_cb.hfp_karaok_en) {
                memcpy(&cfg, &rec_cfg_tbl[AUDIO_PATH_KARAOK], sizeof(sdadc_cfg_t));
                cfg.sample_rate = SPR_48000;
            } else {
        #if BT_AEC_EN || BT_ALC_EN || BT_NLMS_AEC_EN
                if (xcfg_cb.bt_aec_en) {
                    cfg.callback = bt_aec_process;
                } else if (xcfg_cb.bt_alc_en) {
                    cfg.callback = bt_alc_process;
                } else {
                    cfg.callback = bt_sco_tx_process;
                }
        #endif
            }
        }
        cfg.channel = bt_mic_channel_get();
        cfg.gain = ((u16)BT_ANL_GAIN << 6) | BT_DIG_GAIN;
    }

    if (bt_mic_sel == 2) {
        cfg.channel = CH_VUSBL;
        cfg.gain = ((u16)BT_ANL_GAIN << 6) | BT_DIG_GAIN;
    }else if (bt_mic_sel == 6) {
        cfg.channel = CH_VUSBR;
        cfg.gain = ((u16)BT_ANL_GAIN << 6) | BT_DIG_GAIN;
    }

#if SDADC_DRC_EN
    sdadc_drc_init((u8 *)RES_BUF_EQ_SDADC_DRC, RES_LEN_EQ_SDADC_DRC);
#endif

    sdadc_init(&cfg);
}

在不改变原有功能上增加自己的音频路径

增加路径表后:

/* 音频路径号 */
#define AUDIO_PATH_AIDS_MIC_TEST   9

/* 接收处理 */
void audio_export_process(u8 *ptr, u32 samples, int ch_mode);

#if FUNC_AUDIO_EXPORT_EN
    #define audio_export_callback audio_export_process
#endif

const sdadc_cfg_t rec_cfg_tbl[] = {
/*   通道,             增益,        采样率,      通路控制,    样点数,   回调函数*/
    {AUX_CHANNEL_CFG,  (2 << 6),    SPR_44100,   ADC2DAC_EN,    256,    aux_sdadc_callback},            /* AUX     */
    {MIC_CHANNEL_CFG,  (8 << 6),    SPR_44100,   ADC2DAC_EN,    256,    speaker_sdadc_callback},        /* SPEAKER */
    {MIC_CHANNEL_CFG,  (12 << 6),   SPR_8000,    ADC2DAC_EN,    128,    bt_sdadc_callback},             /* BTMIC   */
    {MIC_CHANNEL_CFG,  (12 << 6),   SPR_48000,   ADC2DAC_EN,    128,    usbmic_sdadc_callback},         /* USBMIC  */
    {MIC_CHANNEL_CFG,  (12 << 6),   SPR_44100,   ADC2SRC_EN,    256,    karaok_sdadc_callback},         /* KARAOK  */
    {MIC_CHANNEL_CFG,  (12 << 6),   SPR_16000,   ADC2DAC_EN,    256,    opus_sdadc_callback},           /* opus  */
    {MIC_CHANNEL_CFG,  (6 << 6),    SPR_44100,   ADC2SRC_EN,    256,    ttp_sdadc_callback},            /* TRANSPARENCY  */
    {MIC_CHANNEL_CFG,  (8 << 6),    SPR_44100,   ADC2DAC_EN,    256,    audio_export_callback}          /* AUDIO EXPORT*/
};

初始化路径

/* 等待淡出完成 */
dac_fade_wait();

/* 初始化音频路径配置 */
audio_path_init(7);

/* 启动音频路径 */
audio_path_start(7);

/* 淡入 */
dac_fade_in();

音频处理

/* 定义音频接收回调处理 */
void audio_export_process(u8 *ptr, u32 samples, int ch_mode)
{
  /* 放大音频 */
  uint16_t *rptr16 = (uint16_t *)ptr;
  uint32_t *rptr32 = (uint32_t *)ptr;

  if(ch_mode == 0)
  {
    for(uint32_t i = 0; i < samples; i++)
    {
      rptr16[i] <<= 6;
    }
  }
  else
  {
    for(uint32_t i = 0; i < samples; i++)
    {
      rptr32[i] <<= 4;
    }
  }

  /* DAC输出 */
  sdadc_pcm_2_dac(ptr, samples, ch_mode);
}

MIC设置

配置工具设置如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TfBzMETp-1659060313073)(README.image/image-20211207151832019.png)]

PE7供电代表MIC_bias使用的是PE7脚输出供电!!!

DAC输出通道设置

DAC的输出通道由变量xcfg_cb.dac_sel控制,所以此配置由donwloader工具调整:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nFRYYWG5-1659060313077)(BT8892B_Aids.image/image-20211103105030929.png)]

/**
 * @brief 输出音频到DAC
 *
 * @param buf_CH1
 * @param buf_CH2
 * @param samples
 */
AT(.com_text.dac)
static void obuf1_put_mono_samples(void *buf_CH1, void *buf_CH2, u32 samples)
{
  u16 *ptr_CH1 = buf_CH1;
  u16 *ptr_CH2 = buf_CH2;
  u32 sample = 0;
  while(samples--)
  {
#if USE_HW_EQ //music接口调节EQ
    if(AUBUFCON & BIT(8))
    {
      break;
    }
    sample = *ptr_CH2++;
    sample <<= 16;
    sample |= *ptr_CH1++;

    AUBUFDATA = sample;
#else
    if(AUBUF1CON & BIT(8))
    {
      break;
    }
    sample = *ptr_CH2++;
    sample <<= 16;
    sample |= *ptr_CH1++;

    AUBUF1DATA = sample;
#endif
  }
}

代码量统计

添加宏

/**
 * @brief 获取指定段地址
 * @param section_nam 段名
 * @param save_val 保存变量名
 */
#define GET_SECTION_ADDR(section_nam, save_val) extern const uint32_t section_nam; \
                                                save_val = (uint32_t)(&section_nam)

设置段ld文件中,变量存储段

在这里插入图片描述

__AIDS_Start = .;
*(.aids.*)
__AIDS_End = .;

设置代码中的变量地址

/* 算法选择 */
static ALGORITHM_FUNCTION_Typdef_t Algorithm_Func_Select AT(.aids.cache);

/* 两帧音频数据 */
static int16_t Algorithm_2FRAME_Data[MONO_FRAME_SIZE * 2] AT(.aids.cache);

/* 算法输出存储区 */
static int16_t Algorithm_Result_Data[MONO_FRAME_SIZE] AT(.aids.cache);

添加打印,输出变量占用大小

  uint32_t save_val_s, save_val_e;
  GET_SECTION_ADDR(__AIDS_Start, save_val_s);
  GET_SECTION_ADDR(__AIDS_End, save_val_e);

  printf("s 0x%08X, e 0x%08X size %u=>%fKB\n", save_val_s, save_val_e, save_val_e - save_val_s, (float)(save_val_e - save_val_s)/1024.f);

设置代码占用地址
在这里插入图片描述

__AIDS_CODE_Start = .;
*(.text.aidis*)
__AIDS_CODE_End = .;
/* 代码指定 */
AT(.text.aidis.wdrc)
static void WDRC_Cal(const int16_t *Src, int16_t *pOut)
{
  /* 代码 */
}

/* 常量指定 */
static const uint8_t auchCRCHi[] AT(.text.aidis.ro) = {0x00};

打印代码及常量占用Flash大小,单位字节数

  uint32_t save_val_s, save_val_e;
  GET_SECTION_ADDR(__AIDS_CODE_Start, save_val_s);
  GET_SECTION_ADDR(__AIDS_CODE_End, save_val_e);

  printf("s 0x%08X, e 0x%08X size %u=>%fKB\n", save_val_s, save_val_e, save_val_e - save_val_s, (float)(save_val_e - save_val_s)/1024.f);

充电检测

bsp_charge.c


///兼容某些维持电压的仓: 耳机入仓, 电池仓的电压不会自动升到5V, 需要手动按仓的按键。
AT(.com_text.bsp.charge)
void bsp_charge_inbox_process(void)
{
#if USE_RELEASE_VERSION == 0
  static u8 Last_Charge_State = 5;
  static u8 Last_InBox = 5;
  static u8 Last_InBoxCheck = 5;
  static u8 Last_SysCharge_State = 5;
  if(Last_Charge_State != CHARGE_DC_IN() ||
    Last_InBox != CHARGE_INBOX() ||
    Last_InBoxCheck != bsp_charge_inbox_check() ||
    Last_SysCharge_State != sys_cb.charge_sta)
  {
    Last_Charge_State = CHARGE_DC_IN();//
    Last_InBox = CHARGE_INBOX();
    Last_InBoxCheck = bsp_charge_inbox_check();
    Last_SysCharge_State = sys_cb.charge_sta;
    /* 检测VUSB状态 */
    printf("VUSB = %u.\n", Last_Charge_State);
    printf("IN VOX = %u.\n", Last_InBox);
    printf("Last_InBoxCheck = %d %d.\n", Last_InBoxCheck, chbox_cb.inbox_rtt_check);
    printf("Last_SysCharge_State = %d.\n", Last_SysCharge_State);
    switch(Last_Charge_State)
    {
      case 2:
        printf("2 Charge Full\n");
      break;
      case 1:
        printf("1 Charge Begin\n");
      break;
      case 0:
        printf("0 Charge Off\n");
      break;
    }
  }
#endif
  /* 充电关机 */
  if (sys_cb.charge_sta && (xcfg_cb.chg_inbox_pwrdwn_en))
  {
    msg_enqueue(EVT_CHARGE_INBOX);
  }

    static u32 inbox_tick = 0;
    u8 sta;
    if ((chbox_cb.inbox_rtt_check) && (!CHARGE_DC_IN())) {
        if (!tick_check_expire(inbox_tick, 10)) {
            return;
        }
        inbox_tick = tick_get();
        sta = bsp_charge_inbox_check();
#if VUSB_SMART_VBAT_HOUSE_EN
        bsp_vhouse_inbox_sta(sta);
#endif
        if ((2 == sta) && (xcfg_cb.chg_inbox_pwrdwn_en)) {
            msg_enqueue(EVT_CHARGE_INBOX);
        }
    }
}

入仓关机功能

如果软件开关机功能被关闭,入仓关机功能受到影响,可能无法关机,需要做如下修改
在这里插入图片描述

BLE开发指南

BLE使能

1、代码部分

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3DcHEaUg-1659060313079)(README.image/image-20211118155634026.png)]

2、配置参数,BLE使能

在这里插入图片描述

3、接收数据入口

加入以下代码测试,手机连接BLE,使用服务UID为0xFF13

printf("REC: \n");
for(u8 i = 0; i < len; i++) {
  printf("%02x ", ptr[i]);
}

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

可以看到收到了如下打印信息:

在这里插入图片描述

4、发送数据入口,手机端监听UUID为0xFF14的服务

加入以下代码:

在这里插入图片描述

ble_tx_notify(gatts_app_notify_base.att_index, ptr, len);

我们使用UUID为0xFF13对BLE从机发送数据,可以看到收到如下的通知:

在这里插入图片描述

调试打印信息:

在这里插入图片描述

在这里插入图片描述

FOAT功能

1、代码部分

SPP协议

在这里插入图片描述

BLE协议

在这里插入图片描述

2、配置参数

在这里插入图片描述

3、转换bin文件为fota的升级文件
在这里插入图片描述

在这里插入图片描述

4、手机蓝牙发送固件即可完成升级

对于BLE下的FOAT,手机需要通过UUID为0xFF15提供的读写特征服务下发数据。

BT蓝牙名称及配置修改

目标:修改蓝牙名称增加随机后缀。

修改配置工具,增加名称修改使能开关,参考MAC地址随机功能

在这里插入图片描述

config(SUB, "蓝牙配置", "蓝牙的相关配置");
config(CHECK, "配置蓝牙名", "是否配置蓝牙名", CONFIG_BT_NAME_EN, 0);
config(TEXT, "蓝牙名称", "手机上可以看到的蓝牙名称", BT_NAME, 32, "Bluetrum-TWS", CONFIG_BT_NAME_EN);
config(MAC, "蓝牙后缀", "蓝牙名后缀", BT_SUFFIX, 6, 00:00:00:00:00:00, FF:FF:FF:FF:FF:FF, 00:00:00:00:00:01, CONFIG_BT_NAME_EN);
config(MAC, "蓝牙地址", "蓝牙的MAC地址", BT_ADDR, 6, 41:42:00:00:00:00, 41:42:FF:FF:FF:FF, 41:42:00:00:00:01);
config(LISTVAL, "长按几秒开机进配对", "是否支持长按N秒开机直接进入配对状态", BT_PWRKEY_NSEC_DISCOVER, BIT, 3, 0, 8, ("不支持", 0), ("2秒", 1), ("3秒", 2), ("4秒", 3), ("5秒", 4), ("6秒", 5), ("7秒", 6), ("8秒", 7));
config(CHECK, "配对提示音", "是否播放配对提示音", WARNING_BT_PAIR, 1);
config(CHECK, "连接两部手机功能", "是否支持连接两部手机功能(打开<TWS功能>后无效)", BT_2ACL_EN, 0);
config(CHECK, "音乐播放功能", "是否支持蓝牙音乐播放功能", BT_A2DP_EN, 1);
config(CHECK, "音乐音量同步", "是否支持A2DP音量与手机同步", BT_A2DP_VOL_CTRL_EN, 0, BT_A2DP_EN);
config(CHECK, "通话功能", "是否支持蓝牙通话的功能", BT_SCO_EN, 1);
config(CHECK, "私密接听功能", "是否使用手动私密接听(切换到手机端接听)", BT_HFP_PRIVATE_EN, 1, BT_SCO_EN);
config(CHECK, "来电报号功能", "是否支持来电报号功能", BT_HFP_RING_NUMBER_EN, 1, BT_SCO_EN);
config(CHECK, "串口功能", "是否支持蓝牙串口的功能", BT_SPP_EN, 0);
config(CHECK, "拍照功能", "是否支持蓝牙HID拍照的功能", BT_HID_EN, 0);
config(CHECK, "按键HID连接/断开功能", "是否支持蓝牙HID服务,按键手动连接/断开", BT_HID_MANU_EN, 0, BT_HID_EN);
config(CHECK, "HID默认不连接", "HID服务默认不连接,需要按键手动连接", BT_HID_DISCON_DEFAULT_EN, 0, BT_HID_EN);
config(LEVEL, 0x00);
config(CHECK, "HID独立自拍器模式", "是否支持独立自拍器模式功能", FUNC_BTHID_EN, 0);
config(TEXT, "HID蓝牙名称", "手机上可以看到的独立自拍器模式蓝牙名称", BTHID_NAME, 32, "BT-Photo", FUNC_BTHID_EN);
config(LEVEL, 0x03);
config(CHECK, "BLE控制功能", "是否支持BLE音乐控制的功能", BLE_EN, 0);
config(TEXT, "BLE名称", "手机上可以看到的BLE蓝牙名称", LE_NAME, 32, "LE-Remoter", CONFIG_BT_NAME_EN);
config(CHECK, "快速测试使能", "快速测试使能", QTEST_EN, 0);
config(LIST, "耳机拿起后状态", "设置耳机拿起后的状态,以测试盒设置为准",  QTEST_PICKUP_STATUS, 4, "不操作", "拿起关机", "拿起复位", "断开蓝牙连接后关机" 0,QTEST_EN);
config(U32, "快测配对ID选择", "测试盒可根据ID选择是否可进行快速配对", QTEST_TWS_PAIR_ID, 0, 0x7fffffff,  0x38383930,  QTEST_EN);

重新编译后出现如下开关标签:

在这里插入图片描述

SDK修改:

修改经典蓝牙名

在这里插入图片描述

const char *bt_get_local_name(void)
{
#if 1
#if IODM_TEST_MODE
    bt_get_new_name(xcfg_cb.bt_name);
#endif // IODM_TEST_MODE

#if FUNC_BTHID_EN
    if (is_bthid_mode()) {
        return xcfg_cb.bthid_name;
    }
#endif // FUNC_BTHID_EN

    /* 更改蓝牙名称 */
    char name[32];
    if(xcfg_cb.config_bt_name_en)
    {
      sprintf(name, "%s-%02X%02X", xcfg_cb.bt_name, xcfg_cb.bt_suffix[4], xcfg_cb.bt_suffix[5]);
    }
    else
    {
      sprintf(name, "%s-%02X%02X", bt_local_name, xcfg_cb.bt_suffix[4], xcfg_cb.bt_suffix[5]);
    }
    snprintf(xcfg_cb.bt_name, 32, "%s", name);
    return xcfg_cb.bt_name;
#else
    return bt_local_name;
#endif // 1
}

修改BLE蓝牙名:

在这里插入图片描述

u32 ble_get_scan_data(u8 *scan_buf, u32 buf_size)
{
    memset(scan_buf, 0, buf_size);
    u32 data_len = sizeof(scan_data_const);
    memcpy(scan_buf, scan_data_const, data_len);

    //读取BLE配置的蓝牙名称
    /* 更改蓝牙名称 */
    char name[32];
    if(xcfg_cb.config_bt_name_en)
    {
      sprintf(name, "%s-%02X%02X", xcfg_cb.le_name, xcfg_cb.bt_suffix[4], xcfg_cb.bt_suffix[5]);
    }
    else
    {
      sprintf(name, "%s-%02X%02X", BT_NAME_DEFAULT, xcfg_cb.bt_suffix[4], xcfg_cb.bt_suffix[5]);
    }
    snprintf(xcfg_cb.le_name, 32, "%s", name);


    int len;
    len = strlen(xcfg_cb.le_name);
    if (len > 0) {
        memcpy(&scan_buf[2], xcfg_cb.le_name, len);
        data_len = 2 + len;
        scan_buf[0] = len + 1;
    }
    return data_len;
}

u32 ble_get_adv_data(u8 *adv_buf, u32 buf_size)
{
    memset(adv_buf, 0, buf_size);
    u32 data_len = sizeof(adv_data_const);
    memcpy(adv_buf, adv_data_const, data_len);
    //读取BLE配置的蓝牙名称
    /* 更改蓝牙名称 */
    char name[32];
    if(xcfg_cb.config_bt_name_en)
    {
      sprintf(name, "%s-%02X%02X", xcfg_cb.le_name, xcfg_cb.bt_suffix[4], xcfg_cb.bt_suffix[5]);
    }
    else
    {
      sprintf(name, "%s-%02X%02X", BT_NAME_DEFAULT, xcfg_cb.bt_suffix[4], xcfg_cb.bt_suffix[5]);
    }
    snprintf(xcfg_cb.le_name, 32, "%s", name);

    int len;
    len = strlen(xcfg_cb.le_name);
    if (len > 0) {
        memcpy(&adv_buf[9], xcfg_cb.le_name, len);
        adv_buf[7] = len + 1;
        data_len = 9 + len;
    }
    return data_len;
}

硬件算法支持

FFT变换

FFT缓冲区不可为局部变量!

/* 参数说明 */
typedef enum {
    RDFT_128 = 0,
    RDFT_256,
    RDFT_512,
} RDFT_LEN;

typedef struct {
    void *in_addr;          //输入地址
    void *pwr_addr;         //功率谱输出地址,为NULL则关闭功率谱输出
    void *out_addr;         //输出地址
    u8 fft_shft;            //功率谱打开后,FFT输出放大倍数
    RDFT_LEN size;          //size:0(128), 1(256), 2(512)
    u8 window_en        :1; //只有fft 512有效(不对客户开放使用)
    u8 input_type       :1; //input type:0,half word; 1,word
    u8 isr_en           :1; //是否打开中断(不对客户开放使用)
} fft_cfg_t;

typedef struct {
    void *in_addr;
    void *out_addr;
    RDFT_LEN size;          //size:0(128), 1(256), 2(512)
    u8 window_en        :1; //只有ifft 512有效
    u8 output_type      :1; //output type:0,half word; 1,word
    u8 overlap_en       :1; //硬件overlap
    u8 isr_en           :1; //是否打开中断(不对客户开放使用)
} ifft_cfg_t;

/* 测试代码 */
/* 256点FFT/iFFT测试,输入为资源音频文件RES_BUF_INFILE_PCM,FFT输入为word,无overlap:*/
#define NFFT    256
static u32 fft_in[NFFT] AT(.fft_test);
static u32 fft_out[NFFT] AT(.fft_test);
static u32 ifft_out[NFFT] AT(.fft_test);

void sys_fft_test(void)
{
    WDT_DIS();
    fft_cfg_t fft_t;
    ifft_cfg_t ifft_t;

    memset(&fft_t, 0, sizeof(fft_cfg_t));
    fft_t.size = RDFT_256;
    fft_t.input_type = 1;
    fft_t.in_addr = fft_in;
    fft_t.out_addr = fft_out;

    memset(&ifft_t, 0, sizeof(ifft_cfg_t));
    ifft_t.size = RDFT_256;
    ifft_t.output_type = 1;
    ifft_t.in_addr = fft_out;
    ifft_t.out_addr = ifft_out;

    s16 *infile = (s16 *)RES_BUF_INFILE_PCM;
    for (int i = 0; i < 20; i++) {
        for (int j = 0; j < NFFT; j++) {
            fft_in[j] = infile[j];
        }
        infile += NFFT;

        printf("### INPUT:\n");
        print_r32(fft_in, NFFT);

        fft_hw(&fft_t);

        printf("### FFT:\n");
        print_r32(fft_out, NFFT);

        ifft_hw(&ifft_t);
    }
}

/* 256点FFT/iFFT测试,输入为资源音频文件RES_BUF_INFILE_PCM,FFT输入为word,有overlap: */
#define NFFT    256
static u32 fft_in[NFFT] AT(.fft_test);
static u32 fft_out[NFFT] AT(.fft_test);
static u32 ifft_out[NFFT] AT(.fft_test);

void sys_fft_test(void)
{
    WDT_DIS();
    fft_cfg_t fft_t;
    ifft_cfg_t ifft_t;

    memset(&fft_t, 0, sizeof(fft_cfg_t));
    fft_t.size = RDFT_256;
    fft_t.input_type = 1;
    fft_t.in_addr = fft_in;
    fft_t.out_addr = fft_out;

    memset(&ifft_t, 0, sizeof(ifft_cfg_t));
    ifft_t.size = RDFT_256;
    ifft_t.output_type = 1;
    ifft_t.in_addr = fft_out;
    ifft_t.out_addr = ifft_out;
    ifft_t.overlap_en = 1;

    s16 *infile = (s16 *)RES_BUF_INFILE_PCM;
    for (int i = 0; i < 20; i++) {
        for (int j = 0; j < NFFT/2; j++) {
            fft_in[NFFT/2 + j] = infile[j];
        }
        infile += NFFT / 2;

        printf("### INPUT:\n");
        print_r32(fft_in, NFFT);

        fft_hw(&fft_t);

        printf("### FFT:\n");
        print_r32(fft_out, NFFT);

        ifft_hw(&ifft_t);

        memcpy(fft_in, &fft_in[NFFT/2], sizeof(fft_in)/2);
    }
}

基础数学计算

void log2_hw(u32 X, u8 Q, math_out_t *output);
void log10_hw(u32 X, u8 Q, math_out_t *output);
void ln_hw(u32 X, u8 Q, math_out_t *output);
void pow2_hw(u32 X, u8 Q, math_out_t *output);
void powe_hw(u32 X, u8 Q, math_out_t *output);
void sqrt_hw(u32 X, u8 Q, math_out_t *output);
int sqrt64_hw(int hi, int lo);

void log10_hw(u32 X, u8 Q, math_out_t *output) == (实际公式为:Z = log10(Y))

参数说明
输入参数X,Q:Y = X / (2^Q);
输出参数math_out_t *output:
typedef struct {
    u32 outdat;     //A = outdat / 2^31
    u32 outexp;     //B = 2^outexp
} math_out_t;       //result = A * B
所以输出:Z = (outdat / 2^31) * (2 ^ outexp);
  
计算log10(0.123456), Q = 16, X = 0.123456 * 2 ^ 16 = 8090/* 示例代码 */
math_out_t output;
//计算log10(0.123456), Q = 16, X = 0.123456 * 2 ^ 16 = 8090
log10_hw(8090, 16, &output);
printf("log10(0.123456):%d %d\n", output.outdat, output.outexp);

输出:log10(0.123456):-487764073 2log10(0.123456) = -487764073 / (2 ^ 31) * (2 ^ 2) = -0.908531

  • Q值是左移位数,对小数放大
  • X值是放大后的数值
/* 测试代码 */
void Test_Math_HW(void)
{
  /* 计算log2(8) */
  log2_hw(8, 0, &out);
  DE_PRINTF("log2_hw(8, 0, &out);\r\n");
  DE_PRINTF(" A = 0x%08X = out.outdat / 2^31, B = %u = 2^out.outexp\r\n", out.outdat, out.outexp);
  float A = (float)out.outdat / (1U << 31);
  uint32_t res = A * (1U << out.outexp);
  DE_PRINTF("RES = A*B = %08X\r\n", res);
}

数组带入运算

/* 求2^(X(定点数)-1)次方和 exp固定为1*/
void MATH_FUNC_PORT(power2_q15)(
  const q15_t * pSrc,
        uint32_t blockSize,
        q63_t * pResult)
{
        uint32_t blkCnt;                               /* Loop counter */
        q63_t sum = 0;                                 /* Temporary result storage */
        q15_t in;                                      /* Temporary variable to store input value */
#if USE_BT8922X_CORE
  BASE_MATH_Typedef_t output1;
  BASE_MATH_Typedef_t output2;
  uint32_t exp;
  /* Initialize blkCnt with number of samples */
  blkCnt = blockSize;

  while (blkCnt > 0U)
  {
    /* C = 2^A[0] + 2^A[1] ... + 2^A[blockSize-1] */

    /* Compute Power and store result in a temporary variable, sum. */
    in = *pSrc++;
    pow2_hw(in, 15, (math_out_t *)output1);

    /* exp固定位1 */
    exp = output1.exp - 1;
    output1.data = << exp;

    in = *pSrc++;
    pow2_hw(in, 15, (math_out_t *)output2);
    /* exp固定位1 */
    exp = output2.exp - 1;
    output2.data = << exp;

    sum += (output1.data + output2.data)
    /* Decrement loop counter */
    blkCnt -= 2;
  }
  *pResult = sum; 
}

对于编译器报没有对应的软件浮点实现问题,需要连接对应的库

在这里插入图片描述

程序下载更新

参考:其他博主图片
在这里插入图片描述

注意事项

芯片框架简述

1、框架总述

LX蓝牙芯片采用最近比较流行的RISC-V(32位)开源内核架构 + 国产RT-Thread操作系统.不过从代码上来看, 操作系统代码已经被封装到库中, 一般用户可以不用涉及操作系统代码, 降低了开发难度.

LX芯片“冯·诺依曼结构”, 即代码与数据的统一编址. 框架结构大致如下:

在这里插入图片描述

芯片内部一般会封装一颗512K或1M SpiFlash, 用于存放代码及资源文件/参数记忆等. SpiFlash和芯片之间通过spi接口进行通信.

首先,代码不会直接在SpiFlash上运行, SpiFlash中所有程序及数据均需要先通过spi接口加载到芯片RAM中, CPU再从RAM中取指令或数据运行.

2、com区和bank区

基于上述的程序存储框架, LX芯片在程序编写时, 有两个重要的概念: com区(公共区) 和 bank区.

com区(公共区):

芯片上电, 一般从Mask程序区开始运行, 在进入main函数之前, 程序会先把com区程序从Flash加载到芯片内部Ram. 由于在程序的整个生命周期内, com区程序会一直保留在RAM中. CPU执行com区代码会很快.

但由于芯片RAM有限, com区一般分配在几十K以内.

bank区(也称为flash区):

Flash中的bank区(存储区)一般是几百K以上.

RAM中的bank程序运行区(类似cache), 一般从几K到几十K不等.

由于RAM中的bank区远远小于FLASH中的bank区, 所以CPU会根据需要不断把Flash(bank程序存储区)中的代码动态替换(加载)到 RAM中的bank程序运行区运行. 由于芯片与Flash之间通过spi进行通信, bank区代码执行速度相对比较慢.

3、开发时需要注意:

上所述, 开发时需要注意如下:

  • 1)中断函数(及其子函数)必须放入com区, 否则(放入bank区)会导致死机.

    中断响应需要非常及时, com区程序常驻于RAM中, CPU可以迅速响应中断函数.

    如果中断函数放入bank区, 中断响应时,可能该bank区还未加载到RAM中, 还需要先加载再运行,耗时相对较慢. 为了防止中断响应慢,芯片做了限定: 中断中加载bank区代码则直接死机.

    另外需要特别注意: 中断函数中不能有switch语句, swith语句编译后生成的跳转常量表会默认放到bank区, 引起中断函数访问bank区死机. 请用if-else语句代替switch语句.

    一些实时性要求比较高的代码,首选放入com区.

  • 2).函数前没有AT指定存放段的代码, 默认放入bank区, bank区代码自动加载(或替换), 不需要人工干预.

    由于bank区加载到RAM, 需要spi通信, IO翻转会有一定干扰, 在一些对干扰敏感的应用中, 如FM, 可以把所有相关程序放入同一个命名的bank, 这样bank加载时, 会大概率地把所有同一bank中的程序一次性加载到RAM中,减少程序运行时可能不断多次地加载引起的spi通信干扰.

4、函数放入com区的写法

在函数前面加入AT(.指定段名到com区) 即可.示例如 usr_tmr1ms_isr函数前面的AT(.com_text.timer)

AT(.com_text.timer)
 
void usr_tmr1ms_isr(void)
{
    gui_scan();                   
    .....
}

在map.text中可以看到, usr_tmr1ms_isr位于 0x20e4a.位于0x20000~ (0x20000+34K) 的com区内.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3t0fn9C9-1659060313100)(BT8892B_Aids.image/image-20211105212820603.png)]

另外有一点, printf参数中的字符串常量也是默认放在bank区中, 如果在中断中调用printf, 也需要把字符串常量放入com区.

如下:

AT(.com_text.str1)

const char str1[] = "Com String";

AT(.com_text.str2) / / 注意每个字符串前都需要增加 AT定位到com区去.

const char str2[] = "val = %d\n";

//调用printf时, 直接使用该字符串即可:

AT(.com_text.timer)

void usr_tmr5ms_isr(void)

{   printf(str1);

  printf(str2, test_val);

  ……

}
  • 9
    点赞
  • 53
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

aron566

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值