0%

杰理科技SDK开发笔记

绪论

本文从新人角度,暂且以jl701n_soundbox_release_v1.4.2/sdk为例,讲述对杰理SDK的入门与开发理解。

杰理系列芯片介绍

  • br23 br25 br28等,与芯片型号的关联,芯片丝印与SDK包命名的关系 等等需要理清

  • 不同内核、不同型号的芯片,具体的业务方案应用场景,为何做这样的区分,概述

此部分内容暂略…

杰理SDK软件架构解析

SDK框架总体概览

SDK框架总体概览

SDK文件目录概览

以SDK包为根,主要有以下子目录

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//SDK


├── apps
│ │
│ ├── common # 通用中间层文件,如音频、蓝牙、全局通用配置、设备管理、文件系统等
│ │
│ └── soundbox # 应用层的业务逻辑代码

├── cpu/br25 # 内核、芯片外设层的相关代码
│ │
│ ├── audio_common/
│ │
│ ├── tools/ # 工具集,包含有配置工具、烧录脚本等
│ │
│ └── ... #


└── include_lib # 封装好,以供调用或者链接的库,包含OS、FS、外设底层驱动等

开发思路流程概览

//apps/soundbox/board/brxx/board_config.h进行板级的宏定义配置选择

其包含了所有板级配置.h文件,通过配置宏定义选择性编译 #ifdef xxx #endif从而决定采用何种板级配置


板级宏定义配置决定了采用何种//apps/soundbox/board/br23/board_xxx_xxx/board_xxx_cfg.h文件,其业务功能实现则位于相应的//apps/soundbox/board/br28/board_xxx_xxx/

SDK级宏定义配置

外设驱动抽象层宏配置

SDK之初始化流程概述

上电初始化至main

main函数之前的源码不可见

猜测是通过指定链接相应的.a库或者段 到上电入口地址,完成必要的上电初始化后,再跳转至main函数执行用户级的初始化

这部分用户不可见的工作可能包括如下:

  1. 芯片上电从0地址,或者是所映射的其它原始入口地址,开始执行
  2. 进行CPU寄存器比如栈指针、CPU内核等芯片内核初始化
  3. C语言运行环境的初始化,包括栈指针设置、可读写数据区、堆区、栈区的初始化,具体代码实现取决于cpu内核架构
  4. 原始时钟系统、中断向量、内部Flash的读写等初始化
  5. 程序执行流->跳转至声明的main函数,正式执行用户级代码,并在main函数里面初始化及启动OS,开启线程调度

sdk.map文件可知,芯片代码段起始地址为 0x06000100,并说明了全局符号text_rodata_begin的地址即为 0x06000100,该全局符号的链接地址由sdk_ld.csdk.ld链接脚本确定。


1
2
3
4
5
6
7
8
9
10
11
//cpu/br28/tools/sdk.map
.text 0x06000100 0xc2610
[!provide] PROVIDE (text_rodata_begin, .)
*(.startup.text)
.startup.text 0x06000100 0x8e cpu/br28/liba/cpu.a(startup.S.o)
0x06000100 _start
*(.text)
.text 0x0600018e 0x11efe cpu/br28/tools/sdk.elf.o
0x060014ae update_result_get
0x060019fc __errno
0x06001cd4 main

通过以上节选map文件推测可知,芯片上电后,会首先执行_start汇编初始化函数,其实现位于cpu/br28/liba/cpu.a(startup.S.o),而后即跳转至main函数

main函数

用户入口int main()位于//SDK/apps/soundbox/common/init.c/main(),实现以及解释如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
int main()
{
wdt_close(); // 关看门狗

/* 初始化`FreeRTOS`或其它RTOS的任务调度器链表、
以及定时器任务链表、消息队列链表、事件链表等必要内核组件数据结构 */
os_init();

/*
架构级设置,路径位于 //cpu/br28/setup.c/setup_arch(),里面所作设置如下:
1. 开启DSP核的bpu配置,暂没了解
2. 定义芯片复位引脚的复位电平,以及长置该电平复位的时间
3. 调用memory_init,应为 用户动态内存堆的初始化
4. p11_init(),电源管理相关API,源码不可见
5. 初始化看门狗复位时间
6. efuse_init() ,推测为芯片flash相关的初始化
7. clk_voltage_init,配置时钟模式及系统电压初始化
8. xosc_hcs_trim(),外部晶振相关调整设置
9. clk_early_init 时钟源的早期配置,晶振频率、时钟频率、系统时钟源等
10. tick_timer_init OS节拍定时器初始化,实现链接可见于cpu/br28/liba/system.a(os_cpu_c.c.o)
11. port_init() 所有IO的必要初始化,或者复位
12. debug_uart_init 日志输出串口初始化,默认为uart0,实现位于//apps/soundbox/board/br28/board_xxx_xxx/board_xxx_xxx.c。
(该文件体现了不同板级间的配置差异)
13. log_early_init(1024);
14. 一些模块的dump
15. 调用中断注册接口,进行必要中断的注册设置
16. sys_timer_init();
17. debug_init(); 异常检测模块初始化,推测可能会初始化一个任务,检测cpu或者os任务控制块相关数据情况
18. __crc16_mutex_init();
*/
setup_arch();

// 运行操作系统前之必要的板级初始化,本文件内为弱函数,具体所链接的函数实现位于.a库中,不可见(当然,.a库也可能没有该实现)
board_early_init();

/* 创建一个OS任务,入口函数为 app_task_handler,入参为 NULL,name为 app_core
注意该任务:该任务包含了大部分的模式业务逻辑,其内实现了一个简易切换模式枚举的设计
*/
task_create(app_task_handler, NULL, "app_core");

// 使能OS内核的任务调度接口
os_start();

// 使能全局中断,当中断使能后,系统节拍定时器中断会立即切入,然后调用任务调度器切入优先级最高的任务执行
local_irq_enable();

while (1) {
asm("idle");
}

return 0;
}

app_init 用户级初始化

main函数创建app_task_handler()的app任务入口函数后,即开始调度至该入口函数执行,如下:

1
2
3
4
5
static void app_task_handler(void *p)
{
app_init();
app_main();
}

app_init()函数路径位于//apps/soundbox/common/init.c/app_init(),实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
static void app_init()
{
int update;

// do...call 皆为自动初始化,指向指定的目标段,执行其首尾之间的所有函数指针,从而完成必要的一些初始化动作
do_early_initcall();
do_platform_initcall();

// 板级初始化
board_init();

do_initcall();

do_module_initcall();
do_late_initcall();

// 初始化音频编码器和解码器
audio_enc_init();
audio_dec_init();

// 如果是配置了有升级功能,则进行处理
if (!UPDATE_SUPPORT_DEV_IS_NULL()) {
// 升级后,在此进行结果处理:
// 无更新,则直接返回;更新成功,则以一半的音量播报5次?更新失败,则以最大音量播报同一句语音?.......
update = update_result_deal(); // apps/common/update/update.c
}

app_var.play_poweron_tone = 1;

// 如果没有插入充电,则检查开机电压
if (!get_charge_online_flag()) {
check_power_on_voltage(); // 循环判断指定次数电池电压,如果正常则开机,电压过低则设置软关机

#if TCFG_POWER_ON_NEED_KEY // 是否需要按键才能开机
/*充电拔出,CPU软件复位, 不检测按键,直接开机*/
extern u8 get_alarm_wkup_flag(void);
#if TCFG_CHARGE_OFF_POWERON_NE
if ((!update && cpu_reset_by_soft()) || is_ldo5v_wakeup() || get_alarm_wkup_flag()) {
#else
if ((!update && cpu_reset_by_soft()) || get_alarm_wkup_flag()) {
#endif
app_var.play_poweron_tone = 0;
} else {
check_power_on_key();
}
#endif
}

#if TCFG_SHARE_OSC_EN
extern void share_osc_pull_down_io();
share_osc_pull_down_io();
#endif

#if TCFG_UART_1T2_EN
extern void uart_1t2_pull_down_io();
uart_1t2_pull_down_io();
#endif

#if (TCFG_MC_BIAS_AUTO_ADJUST == MC_BIAS_ADJUST_POWER_ON)
u8 por_flag = 0;
u8 cur_por_flag = 0;
#if (defined(CONFIG_CPU_BR23) || defined(CONFIG_CPU_BR25))
extern u8 power_reset_src;
/*
*1.update
*2.power_on_reset(BIT0:上电复位)
*3.pin reset(BIT4:长按复位)
*/
if (update || (power_reset_src & BIT(0)) || (power_reset_src & BIT(4))) {
//log_info("reset_flag:0x%x",power_reset_src);
cur_por_flag = 0xA5;
}
#else /* #if defined(CONFIG_CPU_BR23) */
u32 reset_src = is_reset_source(BIT(MSYS_POWER_RETURN) | BIT(P33_PPINR_RST));
if (update || reset_src) {
cur_por_flag = 0xA5;
}
#endif /* #if defined(CONFIG_CPU_BR23) */

int ret = syscfg_read(CFG_POR_FLAG, &por_flag, 1);
if ((cur_por_flag == 0xA5) && (por_flag != cur_por_flag)) {
//log_info("update POR flag");
ret = syscfg_write(CFG_POR_FLAG, &cur_por_flag, 1);
}
#endif

#if (TCFG_CHARGE_ENABLE && TCFG_CHARGE_POWERON_ENABLE)
if (is_ldo5v_wakeup()) { //LDO5V唤醒
extern u8 get_charge_online_flag(void);
if (get_charge_online_flag()) { //关机时,充电插入

} else { //关机时,充电拔出
power_set_soft_poweroff();
}
}
#endif

#if(TCFG_CHARGE_BOX_ENABLE)
/* clock_add_set(CHARGE_BOX_CLK); */
chgbox_init_app();
#endif
}

上述app_init代码暂作部分解释,其余后续补充

板级初始化 board_init()

board_init()app_init函数中被调用,其路径位于//apps/soundbox/board/br28/board_jl701n_demo/board_jl701n_demo.c/board_init(),实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
void board_init()
{
board_power_init(); // 实现位于当前模块内,包括电源管理,如唤醒源IO配置等功能
//adc_vbg_init();
adc_init(); // 实现位于 //cpu/br28/adc_api.c

// 解析蓝牙配置文件,实现位于 //apps/soundbox/common/user_cfg_new.c
// 蓝牙配置,如名字、Mac地址等,在此函数内进行结构体设置
cfg_file_parse(0);
/* devices_init(); */

#if TCFG_CHARGE_ENABLE // 是否支持芯片内置充电
extern int charge_init(const struct dev_node *node, void *arg);
charge_init(NULL, (void *)&charge_data);
#else
CHARGE_EN(0);
CHGGO_EN(0);
gpio_longpress_pin1_reset_config(IO_LDOIN_DET, 0, 0);
#endif

/* if (!get_charge_online_flag()) { */
/* check_power_on_voltage(); */
/* } */

#if (TCFG_SD0_ENABLE || TCFG_SD1_ENABLE)
sdpg_config(4);
#endif

#if TCFG_FM_ENABLE
fm_dev_init(&fm_dev_data);
#endif

#if TCFG_NOR_REC
nor_fs_ops_init();
#endif

#if TCFG_NOR_FS
init_norsdfile_hdl();
#endif

#if FLASH_INSIDE_REC_ENABLE
sdfile_rec_ops_init();
#endif

dev_manager_init();
dev_manager_set_valid_by_logo("res_nor", 0);///将设备设置为无效设备

// 设备驱动层初始化,比如按键驱动在此内进行注册
board_devices_init();

#if TCFG_CHARGE_ENABLE
if(get_charge_online_flag())
#else
if (0)
#endif
{
power_set_mode(PWR_LDO15);
}else{
power_set_mode(TCFG_LOWPOWER_POWER_SEL);
}

#if TCFG_UART0_ENABLE // 如果使能日志打印,则设置该串口的rx为普通io?
if (uart0_data.rx_pin < IO_MAX_NUM) {
gpio_set_die(uart0_data.rx_pin, 1);
}
#endif

#if TCFG_SMART_VOICE_ENABLE
int audio_smart_voice_detect_init(struct vad_mic_platform_data *mic_data);
audio_smart_voice_detect_init((struct vad_mic_platform_data *)&vad_mic_data);
#endif /* #if TCFG_SMART_VOICE_ENABLE */

#if defined(AUDIO_PCM_DEBUG)
void uartSendInit();
uartSendInit();
#endif

#if TCFG_RTC_ENABLE
alarm_init();
#endif

}

app_main 用户级初始化

app_main()函数路径位于//apps/soundbox/app_main.c/app_main(),实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
void app_main()
{
log_info("app_main \n");

#if TCFG_UNISOUND_ENABLE
unisound_init();
#endif

// app_var是个结构体,包含了音量、音量效果等的参数
app_var.start_time = timer_get_ms(); // 获取OS当前时间,作为app的开始时间

// 如果有充电插入
if (get_charge_online_flag()) {

app_var.poweron_charge = 1;

#if (TCFG_SYS_LVD_EN == 1)
vbat_check_init(); // 如果电量检测使能,则调用,其内会初始化电池检测定时器用于检测电压
#endif

#ifndef PC_POWER_ON_CHARGE // 没有定义此项
app_curr_task = APP_IDLE_TASK; // 直接进入此处
#else
app_curr_task = APP_POWERON_TASK;
#endif
}
#if TCFG_RTC_ENABLE
// 如果没插充电,且使能了实时时钟,则进入此处
else if (alarm_active_flag_get()) {
app_curr_task = APP_RTC_TASK;
}
#endif
else {
#if 0 //SOUNDCARD_ENABLE
soundcard_peripheral_init();
#endif

#if TCFG_HOST_AUDIO_ENABLE
/* void usb_host_audio_init(int (*put_buf)(void *ptr, u32 len), int *(*get_buf)(void *ptr, u32 len)); */
/* usb_host_audio_init(usb_audio_play_put_buf, usb_audio_record_get_buf); */
uac_host_init();
#endif

/* endless_loop_debug_int(); */
// 更新led的灯效设置状态,实现位于 //apps/soundbox/ui/led/pwm_led_api.c。如果没有配置,则是空实现
ui_update_status(STATUS_POWERON);

app_curr_task = APP_POWERON_TASK; // 非插充电下,无RTC使能,app while 1 轮询该模式业务
}

#if TCFG_CHARGE_BOX_ENABLE
app_curr_task = APP_IDLE_TASK;
#endif

#if TCFG_CHARGE_ENABLE
set_charge_event_flag(1);
#endif

app_task_loop(); // app任务进入 while 1 轮询
}

app_task_loop之APP顶层模式业务

app_task_loop 代码节选如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void app_task_loop()
{
while (1) {
switch (app_curr_task) {
case APP_POWERON_TASK:
log_info("APP_POWERON_TASK \n"); // 开机临时模式
app_poweron_task(); // 检测到任何的公共消息事件,则将 app_next_task 设置相应的模式枚举,并退出其内的 while1 等等
app_task_exit_ok(); // if (app_next_task) { app_curr_task = app_next_task; app_next_task = 0; task_exitting = 0;}
break;
case APP_POWEROFF_TASK:
log_info("APP_POWEROFF_TASK \n");
app_poweroff_task();
app_task_exit_ok();
break;
case APP_BT_TASK:
log_info("APP_BT_TASK \n");
app_bt_task();
app_task_exit_ok();
break;

......

case APP_LIVE_IIS_TASK:
log_info("APP_LIVE_IIS_ACTION_TASK \n");
app_live_iis_task();
app_task_exit_ok();
break;
}
app_task_clear_key_msg();//清理按键消息
//检查整理VM
vm_check_all(0);
}
}

app 各模式业务设计解析

结合代码,概述顶层应用的模式业务逻辑

APP_POWERON_TASK
APP_POWEROFF_TASK
APP_BT_TASK
APP_MUSIC_TASK
APP_LINEIN_TASK
APP_PC_TASK
APP_IDLE_TASK
APP_SLEEP_TASK

板子基本功能及系统级API简述

板子通用基本功能概述

系统级API应用开发简述

系统任务创建

创建新任务,调用OS API

另外,要在//apps/soundbox/common/task_table.c表增加相应的任务参数配置。为何这样设计,结合代码分析

其关联调用关系是怎样的

具体调用逻辑,后续补充

系统定时器API

sys_timer:软件定时器,定时到达后,发送事件到相应线程响应。属于同步接口

usr_timer:定时到达之后,直接在硬件中断服务函数执行 回调函数。属于异步接口

time与timeout: timeout只会被执行一次即结束

事件上报及处理流程

各模块业务功能开发

APP_TASK框架设计解析

等等

日志打印配置

在板级配置文件,如//apps/soundbox/board/br28/board_jl701n_demo/board_jl701n_demo_cfg.hs,配置使能串口打印,如下:

1
2
3
4
5
6
7
8
#define TCFG_UART0_ENABLE			ENABLE_THIS_MOUDLE //串口打印模块使能
#define TCFG_UART0_RX_PORT NO_CONFIG_PORT //串口接收脚配置(用于打印可以选择NO_CONFIG_PORT)
#if USER_CDC_TEST_ENABLE
#define TCFG_UART0_TX_PORT IO_PORTA_06//IO_PORTA_06//IO_PORT_DP //串口发送脚配置
#else
#define TCFG_UART0_TX_PORT IO_PORT_DP//IO_PORTA_06//IO_PORT_DP //串口发送脚配置
#endif
#define TCFG_UART0_BAUDRATE 1000000 //串口波特率配置

//apps/soundbox/include/app_config.h

1
2
3
4
#define CONFIG_DEBUG_ENABLE       1

//打开断言
const int config_asser = 1;
1
2
3
4
5
//打开整个库打印
#define LIB_DEBUG 1

///打印是否时间打印信息
const int config_printf_time = 1;

按键模块功能

系统上电初始化时,调用key_driver_init()进行按键驱动初始化

  • 进行iokey、adkey等key的IO初始化,此部分IO配置句柄(包括获取键值函数、定义消抖、长按等参数)实现位于apps/soundbox/board/br28/board_jl701n_demo/board_jl701n_demo.c中,句柄的配置宏定义则位于相应的板级配置.h文件中
  • 将按键句柄、配置参数、按键扫描函数添加到usr_timer_add硬件定时器中(可进入低功耗)

按键定时扫描->触发上报->按键-事件-任务表映射->发送打包后的消息

  • 当按键扫描函数判断到有相应按键触发后,则触发通知
    (事件结构体指明:事件类型为SYS_KEY_EVENT、事件参数为DEVICE_EVENT_FROM_KEY、按键类型、按键事件如单击、长按等)
  • key_event_remap(&e)->app_key_event_remap()将触发的事件进行映射:判断当前app_curr_task,再转换成不同task下的 任务-按键事件-枚举表
    (总映射管理位于apps/soundbox/task_manager/task_key.c
  • 调用sys_event_notify(struct sys_event *e)向系统发送事件通知
  • 事件API会给sys_event再封包,加上包头APP_MSG_SYS_EVENT,作为app消息发送至系统中

由当前app_task接收msg->解析按键事件->case相应事件枚举->执行相应操作

  • 每个app任务下有个while1,通过调用app_task_get_msg(msg, ARRAY_SIZE(msg), 1);接收到系统app消息
  • 进行包头消息判断,如case APP_MSG_SYS_EVENT,符合则进入私有任务的事件处理函数中
  • 在私有任务的事件处理中,case SYS_KEY_EVENT则表示进入按键事件处理;再往下,则可以根据不同的按键事件进行相应处理,如下,表示音量增加。
    1
    2
    3
    4
    case  KEY_VOLUP_HOLD:
    log_info(" KEY_VOLUP_HOLD \n");
    bt_key_vol_up();
    break;
  • 在私有任务事件进行处理后,则可以return 1表示成功处理;否则,调用app_default_event_deal((struct sys_event *)(&msg[1]));将消息缓存由通用事件处理函数进行处理

另外,每个app任务在退出切换时,会清除按键事件缓存,如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
u8 app_task_exitting()//
{
struct sys_event clear_key_event = {.type = SYS_KEY_EVENT, .arg = (void *)DEVICE_EVENT_FROM_KEY};
if (app_next_task && !task_exitting) {
/* app_curr_task = app_next_task; */
/* app_next_task = 0; */
task_exitting = 1;
sys_key_event_disable();
sys_event_clear(&clear_key_event);
return 1;
}
return 0;
}

UI-LED模块

灯随音动的实现

设备管理功能

dev_manager提供了设备上下线、设备状态查询等api操作接口,可以支持U盘/SD等设备挂载、查找、激活等操作,接口汇总概括如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(1) 支持设备上下线(添加/删除)
(2) 支持设备查找
1) 查找第一个设备
2) 查找最后一个设备
3) 查找当前设备上一个设备
4) 查找当前设备下一个设备
5) 查找最后活动设备
6) 查找指定设备
7) 查找指定序号的设备
(3) 支持设置设备信息
1) 设置设备有效/无效
2) 设置设备为活动设备
(4) 支持获取设备信息
1) 获取设备逻辑盘符
2) 获取音乐设备逻辑盘符
3) 获取录音播放设备逻辑盘符
4) 获取设备根目录
5) 获取设备文件浏览根目录
(5) 支持设备在线检查
(6) 其他
1) 设备扫盘及扫盘释放

文件系统功能

文件系统接口模块提供包含文件浏览,文件删除,打开,创建,读写,等接口

音乐,录音,解码等模式会使用到

音频模块相关功能

解码任务,用于串联所有音频流处理。

相应地有打开通道和关闭通道,相当于打开在解码任务中的 串行流 通道。 解码流都是走的解码线程

(录音编码流也有对应的 编码线程、录音线程、mix_buffer、pcm_buf 等等)

init.c -> cpu\br28\audio_dec\audio_dec.c\audio_dec_init

1
2
3
4
5
6
7
8
9
int audio_dec_init()
{
int err;

printf("audio_dec_init\n");

// 创建解码任务
err = audio_decoder_task_create(&decode_task, "audio_dec");
}

蓝牙A2DP音频处理

何处发送该事件:

  1. 底层协议栈自动发送
  2. bt_switch_fun.c a2dp_media_packet_user_handler->a2dp_media_packet_play_start,后台模式下检测到有音频流,则切换至APP_BT_TASK并发送BT_STATUS_A2DP_MEDIA_START事件到APP_BT_TASK接收进行打开音频流

bt.c->
app_bt_task while1-> 接收到事件
bt_sys_event_handler->
bt_sys_event_office->
bt_connction_status_event_handler
-> case BT_STATUS_A2DP_MEDIA_START 事件

->
bt_event_func.c
bt_status_a2dp_media_start

在文件audio_dec_bt.c
a2dp_dec_open->audio_decoder_task_add_wait(a2dp_wait_res_handler)->a2dp_dec_start

其中audio_decoder_task_add_wait接口实现不可见

重点 对a2dp_dec_start函数的实现解析,以充分理解音频流的处理流程

文件 audio_dec_bt.c->int zero_entry_handle(void *priv, struct audio_data_frame *in)用于对PCM原始音频数据帧的处理

文件 audio_digital_vol.c audio_digital_vol.h 包含了音量初始化及处理的相关接口:

通过int audio_digital_vol_run(dvol_handle *dvol, void *data, u32 len)函数实现直接对PCM数据流的音量变化处理,并实现将当前音量平滑至目标音量的效果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 结构体定义如下:
typedef struct {
u8 toggle; /*数字音量开关*/
u8 fade; /*淡入淡出标志*/
u8 vol; /*淡入淡出当前音量(level)*/
u8 vol_max; /*淡入淡出最大音量(level)*/
s16 vol_fade; /*淡入淡出对应的起始音量*/
#if BG_DVOL_FADE_ENABLE
s16 vol_bk; /*后台自动淡出前音量值*/
struct list_head entry;
#endif
volatile s16 vol_target; /*淡入淡出对应的目标音量*/
volatile u16 fade_step; /*淡入淡出的步进*/
} dvol_handle;

声卡功能应用

app_var.music_volume

printf(“common vol+: %d”, app_audio_get_volume(APP_AUDIO_CURRENT_STATE));

app_audio_set_volume(__this->state, volume, 1);

提示音模块功能

通过SDK_config配置工具->进行提示音配置

  1. 打开//sdk/cpu/br28/tools/*配置入口.jlxproj配置工具,选择提示音配置,可配置相应提示音的文件、格式,最终点击保存即将提示音文件转化写入到cfg_tool.bin
  2. 表格的提示音名字对应//apps/soundbox/common/tone_table.c的语音枚举,节选如下:
    1
    2
    3
    4
    5
    6
    const char *tone_table[] = {
    [IDEX_TONE_NUM_0] = TONE_RES_ROOT_PATH"tone/0.*",
    [IDEX_TONE_NUM_1] = TONE_RES_ROOT_PATH"tone/1.*",
    [IDEX_TONE_NUM_2] = TONE_RES_ROOT_PATH"tone/2.*",
    // ......
    };

当然,提示音的存放路径也可以放到外部存储介质中,则需要在源文件额外配置路径等。


提示音控制接口应用

  • 提示音接口API声明位于//apps/soundbox/include/tone_player.h,代码节选及解析如下
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    extern const char *tone_table[];
    int tone_play_open_with_callback_base(const char **list, u8 follow, u8 preemption, void (*evt_handler)(void *priv, int flag), void *evt_priv, int sync_confirm_time);
    int tone_play(const char *name, u8 preemption);
    int tone_play_index(u8 index, u8 preemption);
    int tone_play_index_with_callback(u8 index, u8 preemption, void (*user_evt_handler)(void *priv), void *priv);
    int tone_file_list_play(const char **list, u8 preemption);
    int tone_play_stop(void);
    int tone_play_stop_by_index(u8 index);
    int tone_play_stop_by_path(char *path);
    int tone_get_status();
    int tone_play_by_path(const char *name, u8 preemption);

    int tone_dec_wait_stop(u32 timeout_ms);
    int tone_play_pp(void);

    // 按名字播放提示音,播放完毕后回调evt_handler
    // 示例场景1:从后台返回蓝牙模式,播放蓝牙提示音后,回调启动播放器
    int tone_play_with_callback_by_name(char *name, // 带有路径的文件名
    u8 preemption, // 打断标记
    void (*evt_handler)(void *priv, int flag), // 事件回调接口 //flag: 0正常关闭,1被打断关闭
    void *evt_priv // 事件回调私有句柄
    );

    // 按列表播放提示音
    int tone_play_with_callback_by_list(const char **list, // 文件名列表
    u8 preemption, // 打断标记
    void (*evt_handler)(void *priv, int flag), // 事件回调接口 //flag: 0正常关闭,1被打断关闭
    void *evt_priv // 事件回调私有句柄
    );

tone_play_by_path
tone_play_by_path(tone_table[IDEX_TONE_PAIRING], 1);

delay_play_conn_tone_status = sys_timeout_add(NULL,delay_play_conn_tone,1500);

tone_get_status() == TONE_START

录音功能应用

jl701n_soundbox_sdk142为例,在//apps/soundbox/task_manager/record/record.c有如下录音API调用逻辑:

1
2
3
4
5
6
7
8
9
// 当前为app_record_task
// app_record_task->record_sys_event_handler->record_key_event_opr->switch (key_event)
case KEY_ENC_START:
log_i(" KEY_ENC_START \n");
record_key_pp();
return true;

// record_key_pp->recorder_mix_start
// record_key_pp->record_mic_start

//apps/soundbox/task_manager/app_common.c文件有如下API调用逻辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 各自模式下的app_task未处理完的事件,会调用 app_default_event_deal 进行公共事件统一处理
// app_default_event_deal->app_common_key_msg_deal->case KEY_ENC_START:
// 如按键录音事件枚举为 KEY_ENC_START ,可见公共事件中只会在 混合录音使能时,进行录音处理
case KEY_ENC_START:
#if (RECORDER_MIX_EN)
if (recorder_mix_get_status()) {
g_printf("recorder_encode_stop\n");
recorder_mix_stop();
} else {
g_printf("recorder_encode_start\n");
recorder_mix_start();
}
#endif/*RECORDER_MIX_EN*/
break;

如果音频流频率跟MIC采样频率不一样,则进行 SRC 变采样处理

常规MIC录音

record_mic_start->recorder_encode_start->pcm2file_enc_open->audio_encoder_open->......

混合录音

//cpu/br28/audio_enc/audio_recorder_mix.c/下有recorder_mix_start->__recorder_mix_start->recorder_encode_start->pcm2file_enc_open

音频流以及MIC输入流,混合输入编码到PCM,再存储到文件中

行与不行,核心关注://cpu/br28/audio_enc/audio_enc_file.c/pcm2file_enc_open

关注//cpu/br28/audio_enc/audio_enc_file.c/pcm2file_enc_pcm_get,有没有进来

通常问题原因有:无法创建文件、

电源管理模块

//apps/soundbox/power_manage/app_power_manage.c 提供了电源管理相关接口,如读取电池电压电量百分比、初始化接口

具体涉及到哪些功能

电池电压电量检测?通过ADC检测并滤波会存在耗时吗?可以配置DMA-ADC吗?

充电管理?

蓝牙模块功能

btstack_main.c->ble_profile_init->app_ble_profile_init()->att_server_init(multi_profile_data, att_read_callback, att_write_callback);

实际中打印发现:

cpu/br28/liba/btstack.a.llvm.1119858.spp_update_profile.c,mutil_handle_data_deal

BLE收到透传数据时,会自动调用mutil_handle_data_deal()->soundcore_spp_data_receive_cb()

蓝牙相关功能部分概述

蓝牙相关部分具体会涉及到哪些内容、哪些业务功能

蓝牙模块初始化配置

结构体类型定义位于//include_lib/system/user_config.h,如下:

1
2
3
4
5
6
7
8
9
10
11
12
typedef struct __BT_CONFIG {
u8 edr_name[LOCAL_NAME_LEN]; //经典蓝牙名
u8 mac_addr[6]; //蓝牙MAC地址
u8 rf_power; //发射功率
u8 dac_analog_gain; //通话DAC模拟增益
u8 mic_analog_gain; //通话MIC增益
u16 tws_device_indicate; /*设置对箱搜索标识,inquiry时候用,搜索到相应的标识才允许连接*/
u8 tws_local_addr[6];
u8 ble_name[LOCAL_NAME_LEN]; //ble蓝牙名
u8 ble_mac_addr[6]; //ble蓝牙MAC地址
u8 ble_rf_power; //ble发射功率
} _GNU_PACKED_ BT_CONFIG;

蓝牙配置初始化代码位于//apps/soundbox/common/user_cfg_new.c,结构体配置如下:

1
2
3
4
5
6
7
8
9
10
11
12
BT_CONFIG bt_cfg = {
.edr_name = {'j', 'l', '_', 's', 'o', 'u', 'n', 'd', 'b', 'o', 'x', '_', '1'},
.mac_addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
.tws_local_addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
.rf_power = 10,
.dac_analog_gain = 25,
.mic_analog_gain = 7,
.tws_device_indicate = 0x6688,
.ble_name = {'j', 'l', '_', 's', 'o', 'u', 'n', 'd', 'b', 'o', 'x', '_', 'b', 'l', 'e'},
.ble_mac_addr = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
.ble_rf_power = 10,
};

当然,上述的配置信息仅作结构体展示用,并没有实际设置至程序中。

在当前文件下的cfg_file_parse(u8 idx)函数(入参“idx”没有被调用)会重新覆盖初始化设置该配置结构体参数,包括蓝牙名称,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ret = syscfg_read(CFG_BT_NAME, tmp, 32);
if (ret < 0) {
log_info("read bt name err\n");
} else if (ret >= LOCAL_NAME_LEN) {
memset(bt_cfg.edr_name, 0x00, LOCAL_NAME_LEN);
memcpy(bt_cfg.edr_name, tmp, LOCAL_NAME_LEN);
bt_cfg.edr_name[LOCAL_NAME_LEN - 1] = 0;
log_info("read new cfg bt name config:%s\n", tmp);
} else {
memset(bt_cfg.edr_name, 0x00, LOCAL_NAME_LEN);
memcpy(bt_cfg.edr_name, tmp, ret);
log_info("read new cfg bt name config:%s\n", tmp);
}
/* g_printf("bt name config:%s\n", bt_cfg.edr_name); */
log_info("bt name config:%s\n", bt_cfg.edr_name);

芯片外设配置开发

时钟系统配置

低速外设时钟源 LSB

时钟源打印 clock_dump

杰理音频芯片系列通用外设

研读杰理工程音频技术应用文档,依照其资料大纲,按照个人理解方式,重述外设驱动应用开发部分。

普通外设

普通外设概览如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
RESET:可配置作复位、唤醒、低功耗唤醒脚
TIMER:
UART:
SPI:
内部Flash:
IIC:
GPIO:
PWM:
SD&USB:
时钟外设(RTC、省晶振):
GPCNT:
IOMAP(IO重映射):
复用应用:

音频外设

1
2
3
4
5
AUX/音频ADC:
IIS:
HDMI(ARC):
SPDIF:
PDM LINK:

外设IO功能宏定义配置

宏定义配置路径位于//apps/soundbox/board/br28/board_jl791n_demo/board_jl701n_demo_cfg.h

杰理SDK编译、构建、链接、下载流程概述

bootloader、app、备份区、数据区等

SDK顶层Makefile结构解析

编译链、环境路径、特定工具设置

  • 根据$(OS)变量判断当前是Windows还是Linux,分别设置各系统下的编译工具链路径
  • 设置工具链下的具体可执行程序路径,如CC、LD、AD等;设置一些通用CMD命令,如mkdir、rm -rf等
  • 设置系统库及其库头文件搜索路径等
    1
    2
    3
    windows下为
    SYS_LIB_DIR := C:/JL/pi32/pi32v2-lib/r3-large
    SYS_INC_DIR := C:/JL/pi32/pi32v2-include
  • 将工具链路径export到环境变量中(特别地 CC CXX LD AR这四个变量被强制使用指定工具链路径下的可执行程序)
  • 设置输出文件和编译生成路径

编译参数、宏定义设置

头文件搜索路径、源文件路径设置

链接参数设置

预构建流程

特别地,其中在实际编译和链接之前的pre_build过程需要注意:

  1. cpu/br28/sdk_used_list.c -o cpu/br28/sdk_used_list.used 链接选项用到
  2. cpu/br28/sdk_ld.c -o cpu/br28/sdk.ld 链接选项用到
  3. cpu/br28/tools/download.c -o $(POST_SCRIPT) 编译链接完成后执行此脚本
  4. $(FIXBAT) $(POST_SCRIPT) 格式转换
  5. cpu/br28/tools/isd_config_rule.c -o cpu/br28/tools/isd_config.ini 固件下载升级时用到的配置文件?
1
2
3
4
5
6
7
pre_build:
$(info +PRE-BUILD)
$(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -D__LD__ -E -P cpu/br28/sdk_used_list.c -o cpu/br28/sdk_used_list.used
$(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -D__LD__ -E -P cpu/br28/sdk_ld.c -o cpu/br28/sdk.ld
$(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -D__LD__ -E -P cpu/br28/tools/download.c -o $(POST_SCRIPT)
$(QUITE) $(FIXBAT) $(POST_SCRIPT)
$(QUITE) $(CC) $(CFLAGS) $(DEFINES) $(INCLUDES) -D__LD__ -E -P cpu/br28/tools/isd_config_rule.c -o cpu/br28/tools/isd_config.ini

上述预构建过程解析如下:
-E: 这个选项告诉编译器只执行预处理阶段,不进行实际的编译。这意味着编译器将读取源代码,执行宏替换、文件包含等预处理操作,然后停止,不生成任何机器代码。

-P: 这个选项请求预处理器不生成传统的预处理输出,而是生成更易读的输出,这通常意味着移除行号和文件名标记,使得输出更适合进一步处理,比如用作脚本的一部分。

源文件构建流程

  • 使用通配符和转换规则,使c_OBJS, S_OBJS, s_OBJS, cpp_OBJS, cxx_OBJS, cc_OBJS分别映射包含所有源文件编译生成对应的 .o 目标文件
  • 定义OBJS包含所有类型的目标文件;定义DEP_FILES包含所有的.d依赖文件,用于增量编译判断用。
  • 修改 OBJS 和 DEP_FILES 的值,增加BUILD_DIR前缀,使得所有构建过程输出都位于BUILD_DIR目录下
  • VERBOSE控制编译过程输出详细日志,默认为0,可设置为1,如make VERBOSE=1; LINK_AT用于决定是否使用file函数
  • 定义构建伪目标all、pre_build、clean,规则、编译、依赖文件包含等
    1
    2
    3
    4
    5
    all依赖于pre_build和OUT_ELF(即sdk.elf)(层层目标依赖,直至源文件->目标文件的编译),直接输入make时,默认执行make all。其执行完编译链接流程后,最后调用脚本`cpu\br28\tools\download.bat`进行下载

    pre_build用于执行预处理步骤,如生成配置文件、链接脚本、下载脚本

    clean用于清理构建过程产生的中间文件,如rm -rf BUILD_DIR xxx

相关的配置、链接、下载脚本解析

cpu/br28/sdk_used_list.c

cpu/br28/sdk_ld.c

段分配、地址分配等

各模块的链接

cpu/br28/tools/download.c

生成了download.bat,其里调用了子目录download.bat

fw_add 固件打包流程

cpu/br28/tools/isd_config_rule.c

杰理程序烧录、下载、升级等概述

cpu/br28/tools/download/soundbox/download.bat的执行流程

检测到目标设备当前固件与下载固件一致,则不进行升级,此部分代码体现在何处?

USB DP/DM 通信传输代码实现?

升级实现

串口升级设计代码实现?

蓝牙升级部分代码实现?
蓝牙BLE主要走SPP串口透传
EDR蓝牙升级
U盘升级:.ufw文件
串口升级

简单思考及拓展

是如何通过宏定义进行不同板级选择的?不同配置的板级代码之间有何异同?

Makefile把所有源文件都包含了进去,通过 xxx_config.h 对相应的板级配置.h文件开启 条件编译,同样的,相应的 .c 实现文件也开启了条件编译。

那不同板级的配置差异体现在哪里?为何存在这些差异?他们的业务应用区别是?

杰理codeblock工程是如何进行构建编译的?与SDK根目录的Makefile构建方式的具体异同?效率有何差异?

下载脚本download.bat执行的详细链路解析?具体是如何通过USB DP/DM,以何种协议进行程序下载升级的?

不同芯片的SDK,其软件设计框架具体有何异同?为何?举实例分析与软件设计的相关因素?

杰理不同系列芯片SDK的适用业务方案范围大概是怎样的?概括一下?

在底层驱动代码或者库源码不可见的情况下,如何界定某些bug是出自于上层业务还是底层代码?通常的方法论是?举实用实例?

设备的各种提示音的内容定义、播放逻辑、业务流程、提示音文件打包下载 具体是如何做的?

不同业务模式,如耳机、蓝牙、音箱等的主要业务代码区别体现在何处?

如开机后进入POWERON_TASK模式,轮询检测各模块的消息从而决定进入哪个工作模式,当音频蓝牙连接时,app_task则切换至APP_BT_TASK

是如何实现声音播放淡入淡出的?具体代码原理分析?

通过对PCM数据流的处理,作渐进比例控制,从而实现声音淡入淡出效果

具体分别说下模式主任务app_task与各子模块的交互设计及切换流程?

各模块的任务检测到有事件发生后,通过消息队列发送至 app_task,app_task接收到消息后进行相应的处理

如 按键模块,代码实现:
蓝牙连接事件通知处理如:
等等

APP_TASK各模式处理接收的事件的具体流程?

如何具体基于release_SDK作增量/二次业务开发?

二次业务开发,通常是新起文件夹,新建文件,并将其加入Makefile中,然后编译链接即可

当然,其实也可以拷贝一份板级配置以及板级相关源文件,改为新的板级配置,在此基础上进行开发。

公版SDK是?与手上的SDK_Release有何区别?如何添加Key?

release即公版SDK

Key是一个文件,在实际的下载脚本中,添加-key <param>下载带有相关代理商Key的固件到目标芯片。

空片则对应无Key下载,带Key的芯片则要带相应Key参数进行下载升级。