绪论
本文从新人角度,暂且以jl701n_soundbox_release_v1.4.2/sdk
为例,讲述对杰理SDK的入门与开发理解。
杰理系列芯片介绍
br23 br25 br28等,与芯片型号的关联,芯片丝印与SDK包命名的关系 等等需要理清
不同内核、不同型号的芯片,具体的业务方案应用场景,为何做这样的区分,概述
此部分内容暂略…
杰理SDK软件架构解析
SDK框架总体概览
SDK文件目录概览
以SDK包为根,主要有以下子目录
1 | //SDK |
开发思路流程概览
在//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
函数执行用户级的初始化
这部分用户不可见的工作可能包括如下:
- 芯片上电从0地址,或者是所映射的其它原始入口地址,开始执行
- 进行CPU寄存器比如栈指针、CPU内核等芯片内核初始化
- C语言运行环境的初始化,包括栈指针设置、可读写数据区、堆区、栈区的初始化,具体代码实现取决于cpu内核架构
- 原始时钟系统、中断向量、内部Flash的读写等初始化
- 程序执行流->跳转至声明的
main
函数,正式执行用户级代码,并在main
函数里面初始化及启动OS,开启线程调度
从sdk.map
文件可知,芯片代码段起始地址为 0x06000100,并说明了全局符号text_rodata_begin
的地址即为 0x06000100,该全局符号的链接地址由sdk_ld.c
或sdk.ld
链接脚本确定。
1 | //cpu/br28/tools/sdk.map |
通过以上节选map文件推测可知,芯片上电后,会首先执行_start
汇编初始化函数,其实现位于cpu/br28/liba/cpu.a(startup.S.o)
,而后即跳转至main
函数
main函数
用户入口int main()
位于//SDK/apps/soundbox/common/init.c/main()
,实现以及解释如下:
1 | int main() |
app_init 用户级初始化
在main
函数创建app_task_handler()
的app任务入口函数后,即开始调度至该入口函数执行,如下:
1 | static void app_task_handler(void *p) |
app_init()
函数路径位于//apps/soundbox/common/init.c/app_init()
,实现如下:
1 | static void app_init() |
上述app_init
代码暂作部分解释,其余后续补充
板级初始化 board_init()
board_init()
在app_init
函数中被调用,其路径位于//apps/soundbox/board/br28/board_jl701n_demo/board_jl701n_demo.c/board_init()
,实现如下:
1 | void board_init() |
app_main 用户级初始化
app_main()
函数路径位于//apps/soundbox/app_main.c/app_main()
,实现如下:
1 | void app_main() |
app_task_loop之APP顶层模式业务
app_task_loop 代码节选如下:
1 | void app_task_loop() |
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 |
在//apps/soundbox/include/app_config.h
有
1 |
|
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
4case 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 | u8 app_task_exitting()// |
UI-LED模块
灯随音动的实现
设备管理功能
dev_manager提供了设备上下线、设备状态查询等api操作接口,可以支持U盘/SD等设备挂载、查找、激活等操作,接口汇总概括如下:
1 | (1) 支持设备上下线(添加/删除) |
文件系统功能
文件系统接口模块提供包含文件浏览,文件删除,打开,创建,读写,等接口
音乐,录音,解码等模式会使用到
音频模块相关功能
解码任务,用于串联所有音频流处理。
相应地有打开通道和关闭通道,相当于打开在解码任务中的 串行流 通道。 解码流都是走的解码线程
(录音编码流也有对应的 编码线程、录音线程、mix_buffer、pcm_buf 等等)
init.c -> cpu\br28\audio_dec\audio_dec.c\audio_dec_init
1 | int audio_dec_init() |
蓝牙A2DP音频处理
何处发送该事件:
- 底层协议栈自动发送
- 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 | // 结构体定义如下: |
声卡功能应用
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配置工具->进行提示音配置
- 打开
//sdk/cpu/br28/tools/*配置入口.jlxproj
配置工具,选择提示音配置
,可配置相应提示音的文件、格式,最终点击保存即将提示音文件转化写入到cfg_tool.bin
中 - 表格的提示音名字对应
//apps/soundbox/common/tone_table.c
的语音枚举,节选如下:1
2
3
4
5
6const 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
29extern 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 | // 当前为app_record_task |
在//apps/soundbox/task_manager/app_common.c
文件有如下API调用逻辑:
1 | // 各自模式下的app_task未处理完的事件,会调用 app_default_event_deal 进行公共事件统一处理 |
如果音频流频率跟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 | typedef struct __BT_CONFIG { |
蓝牙配置初始化代码位于//apps/soundbox/common/user_cfg_new.c
,结构体配置如下:
1 | BT_CONFIG bt_cfg = { |
当然,上述的配置信息仅作结构体展示用,并没有实际设置至程序中。
在当前文件下的cfg_file_parse(u8 idx)
函数(入参“idx”没有被调用)会重新覆盖初始化设置该配置结构体参数,包括蓝牙名称,如下代码所示:
1 | ret = syscfg_read(CFG_BT_NAME, tmp, 32); |
芯片外设配置开发
时钟系统配置
低速外设时钟源 LSB
时钟源打印 clock_dump
杰理音频芯片系列通用外设
研读杰理工程音频技术应用文档
,依照其资料大纲,按照个人理解方式,重述外设驱动应用开发部分。
普通外设
普通外设概览如下:
1 | RESET:可配置作复位、唤醒、低功耗唤醒脚 |
音频外设
1 | AUX/音频ADC: |
外设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
3windows下为
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
过程需要注意:
- cpu/br28/sdk_used_list.c -o cpu/br28/sdk_used_list.used 链接选项用到
- cpu/br28/sdk_ld.c -o cpu/br28/sdk.ld 链接选项用到
- cpu/br28/tools/download.c -o $(POST_SCRIPT) 编译链接完成后执行此脚本
- $(FIXBAT) $(POST_SCRIPT) 格式转换
- cpu/br28/tools/isd_config_rule.c -o cpu/br28/tools/isd_config.ini 固件下载升级时用到的配置文件?
1 | pre_build: |
上述预构建过程解析如下:
-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
5all依赖于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参数进行下载升级。