绪论本文从新人角度,暂且以jl701n_soundbox_release_v1.4.2/sdk为例,讲述对杰理SDK的入门与开发理解。
杰理系列芯片介绍
br23 br25 br28等,与芯片型号的关联,芯片丝印与SDK包命名的关系 等等需要理清
不同内核、不同型号的芯片,具体的业务方案应用场景,为何做这样的区分,概述
此部分内容暂略…
杰理SDK软件架构解析SDK框架总体概览
点击跳转:SDK框架总体概览
SDK文件目录概览以SDK包为根,主要有以下子目录
1234567891011121314151617181920//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之初始化流程概述上电初始化至mainmain函数之前的源码不可见
猜测是通过指定链接相应的.a库或者段 到上电入口地址,完成必要的上电初始化后,再跳转至main函数执行用户级的初始化
这部分用户不可见的工作可能包括如下:
芯片上电从0地址,或者是所映射的其它原始入口地址,开始执行
进行CPU寄存器比如栈指针、CPU内核等芯片内核初始化
C语言运行环境的初始化,包括栈指针设置、可读写数据区、堆区、栈区的初始化,具体代码实现取决于cpu内核架构
原始时钟系统、中断向量、内部Flash的读写等初始化
程序执行流->跳转至声明的main函数,正式执行用户级代码,并在main函数里面初始化及启动OS,开启线程调度
从sdk.map文件可知,芯片代码段起始地址为 0x06000100,并说明了全局符号text_rodata_begin的地址即为 0x06000100,该全局符号的链接地址由sdk_ld.c或sdk.ld链接脚本确定。
1234567891011//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(),实现以及解释如下:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152int 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任务入口函数后,即开始调度至该入口函数执行,如下:
12345static void app_task_handler(void *p){ app_init(); app_main();}
app_init()函数路径位于//apps/soundbox/common/init.c/app_init(),实现如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102static 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(),实现如下:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182void 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(),实现如下:
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960void 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 代码节选如下:
123456789101112131415161718192021222324252627282930313233void 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表增加相应的任务参数配置。为何这样设计,结合代码分析
其关联调用关系是怎样的
具体调用逻辑,后续补充
系统定时器APIsys_timer:软件定时器,定时到达后,发送事件到相应线程响应。属于同步接口
usr_timer:定时到达之后,直接在硬件中断服务函数执行 回调函数。属于异步接口
time与timeout: timeout只会被执行一次即结束
事件上报及处理流程各模块业务功能开发APP_TASK框架设计解析等等
日志打印配置在板级配置文件,如//apps/soundbox/board/br28/board_jl701n_demo/board_jl701n_demo_cfg.hs,配置使能串口打印,如下:
12345678#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有
1234#define CONFIG_DEBUG_ENABLE 1//打开断言const int config_asser = 1;
12345//打开整个库打印#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则表示进入按键事件处理;再往下,则可以根据不同的按键事件进行相应处理,如下,表示音量增加。1234case 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任务在退出切换时,会清除按键事件缓存,如下:
12345678910111213u8 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等设备挂载、查找、激活等操作,接口汇总概括如下:
123456789101112131415161718192021(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
123456789int audio_dec_init(){ int err; printf("audio_dec_init\n"); // 创建解码任务 err = audio_decoder_task_create(&decode_task, "audio_dec");}
蓝牙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.cbt_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数据流的音量变化处理,并实现将当前音量平滑至目标音量的效果。
1234567891011121314// 结构体定义如下: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配置工具->进行提示音配置
打开//sdk/cpu/br28/tools/*配置入口.jlxproj配置工具,选择提示音配置,可配置相应提示音的文件、格式,最终点击保存即将提示音文件转化写入到cfg_tool.bin中
表格的提示音名字对应//apps/soundbox/common/tone_table.c的语音枚举,节选如下:123456const 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,代码节选及解析如下1234567891011121314151617181920212223242526272829extern 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_pathtone_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调用逻辑:
123456789// 当前为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调用逻辑:
1234567891011121314// 各自模式下的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,如下:
123456789101112typedef 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,结构体配置如下:
123456789101112BT_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”没有被调用)会重新覆盖初始化设置该配置结构体参数,包括蓝牙名称,如下代码所示:
123456789101112131415ret = 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
杰理音频芯片系列通用外设研读杰理工程音频技术应用文档,依照其资料大纲,按照个人理解方式,重述外设驱动应用开发部分。
普通外设普通外设概览如下:
12345678910111213RESET:可配置作复位、唤醒、低功耗唤醒脚TIMER:UART:SPI:内部Flash:IIC:GPIO:PWM:SD&USB:时钟外设(RTC、省晶振):GPCNT:IOMAP(IO重映射):复用应用:
音频外设12345AUX/音频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等
设置系统库及其库头文件搜索路径等123windows下为SYS_LIB_DIR := C:/JL/pi32/pi32v2-lib/r3-largeSYS_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 固件下载升级时用到的配置文件?
1234567pre_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,规则、编译、依赖文件包含等12345all依赖于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.ccpu/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 下载带有相关代理商Key的固件到目标芯片。
空片则对应无Key下载,带Key的芯片则要带相应Key参数进行下载升级。