杰理芯片SDK-详细讲解AC695N/AC696N芯片软件启动流程
本章对AC695N/AC696N芯片的SDK软件包启动流程进行详细的讲解,方便大家对杰理SDK程序有一个深度的了解
前言
现在为止也开发了许多杰理TWS蓝牙耳机、音响项目SDK的案子,在调试案子时不断的向前辈们学习到了很多关于蓝牙音响、蓝牙TWS耳机专业的知识。想在这里做一个学习汇总,方便各位同行和对杰理芯片SDK感兴趣的小伙伴们学习;
本章详细介绍杰理AC695N/AC696N芯片软件启动流程的讲解
在我们进行杰理蓝牙耳机、蓝牙音响等蓝牙产品软件开发时,会对一个SDK软件代码包进行开发,可是往往一个SDK包里面的代码内容是复杂繁多的。所以本人在这里对AC695N/AC696N芯片的SDK软件包进行详细的讲解,方便大家对杰理SDK有一个深度的了解;
程序的入口:int main( )函数
所有C语言程序的执行都从main函数开始。在嵌入式系统中,它负责完成从硬件复位到操作系统启动的全部初始化工作。在杰理SDK中“ init.c ”文件中可以找到int main函数如图所示:

代码讲解:
/**
* @brief 程序的主入口函数
* @note 所有C语言程序的执行都从main函数开始。在嵌入式系统中,它负责完成从硬件复位到操作系统启动的全部初始化工作。
*/
int main() //程序的入口
{
// CONFIG_CPU_BR25只有当这个宏被定义(值为真)时,下面的代码块才会被编译进最终的程序中。
// 这表明此代码是专门为 BR25 这款CPU内核编写的。
#if(CONFIG_CPU_BR25)
// 这是一个嵌套的条件编译,用于根据不同的功能宏来决定系统时钟(SFC - System Frequency Clock?)的最高频率。
// 这种做法是为了在性能和功耗之间取得平衡。如果开启了很多耗资源的功能,就需要更高的时钟频率来保证流畅运行。
// 检查是否启用了以下任一高级功能:
// TCFG_DEC2TWS_ENABLE: TWS(真无线立体声)解码功能
// RECORDER_MIX_EN: 录音混音功能
// TCFG_DRC_ENABLE: 动态范围压缩(音频处理)
// TCFG_USER_BLE_ENABLE: 用户自定义的BLE(低功耗蓝牙)功能
// TCFG_DEC_APE_ENABLE: APE无损音频解码
// TCFG_DEC_FLAC_ENABLE: FLAC无损音频解码
// TCFG_DEC_DTS_ENABLE: DTS音频解码
// TCFG_USER_EMITTER_ENABLE: 用户自定义发射器功能
// 如果以上任何一个功能被启用,就将系统时钟设置为100MHz。
#if (TCFG_DEC2TWS_ENABLE ||RECORDER_MIX_EN || TCFG_DRC_ENABLE || TCFG_USER_BLE_ENABLE || TCFG_DEC_APE_ENABLE || TCFG_DEC_FLAC_ENABLE || TCFG_DEC_DTS_ENABLE || TCFG_USER_EMITTER_ENABLE)
clock_set_sfc_max_freq(100 * 1000000, 100 * 1000000);
#else
// 如果上述高级功能都未启用,则进入更细分的判断。
// 检查是否同时启用了AEC(回声消除)和TWS(真无线)功能。
// AEC和TWS组合对计算能力要求很高,因此也需要100MHz的高时钟。
#if ((TCFG_AEC_ENABLE) && (TCFG_USER_TWS_ENABLE))
clock_set_sfc_max_freq(100 * 1000000, 100 * 1000000);
#else
// 如果连AEC+TWS都没有启用,说明是相对基础的功能配置,对性能要求较低。
// 为了节省功耗,将系统时钟设置为较低的 64 MHz。
clock_set_sfc_max_freq(64 * 1000000, 64 * 1000000);
#endif
#endif // 结束对高级功能的判断
#endif // 结束对 CONFIG_CPU_BR25 的判断
// 关闭看门狗定时器(Watchdog Timer)。
// 在系统初始化阶段,为了防止因程序卡死或初始化时间过长而导致看门狗复位,通常会先关闭它。
// 在后续的程序中,可能会在适当的时候重新开启它,以监控系统是否正常运行。
wdt_close();
// 初始化操作系统内核(OS Kernel)。
// 这个函数会创建空闲任务、初始化任务调度器、创建系统节拍定时器(SysTick)等。
// 这是从“裸机”模式切换到“多任务操作系统”模式的关键一步。
os_init();
// 初始化芯片架构相关的硬件。
// 这是一个非常底层的初始化函数,通常由SDK提供。
// 它主要负责:
// 1. 内存初始化(如清零BSS段、拷贝数据段)。
// 2. 看门狗的初始配置。
// 3. 系统时钟源的配置(如选择外部晶振或内部RC振荡器)。
// 4. 初始GPIO引脚状态设置。
// 5. 系统滴答定时器(SysTick)的配置。
// 简单来说,就是为程序的运行准备好最基本的硬件环境。
setup_arch();
// 电路板级别的早期初始化。
// 这个函数通常用于初始化与具体硬件电路板相关的外设。
// 例如:初始化用于调试的串口(UART)、配置电源管理芯片(PMU)、初始化LED或按键等。
// 它在操作系统启动前被调用,确保一些基础外设可以尽早工作。
board_early_init();
// 创建应用程序的主任务(或称核心任务)。
// task_create 是操作系统提供的API,用于创建一个新的任务(线程)。
// 参数1: app_task_handler - 任务的入口函数,即任务需要执行的代码。
// 参数2: NULL - 传递给任务入口函数的参数,这里没有参数。
// 参数3: "app_core" - 任务的名字,用于调试和识别。
// 这个任务创建后,并不会立即执行,而是进入就绪状态,等待操作系统调度器分配CPU时间片
//模式任务 app_core 创建,所有的 APP 模式在此任务里执行处理
task_create(app_task_handler, NULL, "app_core");
// 启动操作系统调度器。
// 这个函数会启动SysTick定时器,并开始进行任务调度。
// 它会选择一个最高优先级的就绪任务来运行(在这里就是刚刚创建的app_task_handler)。
// **重要**:os_start() 函数通常是一个“死循环”或者会触发第一次任务切换,它本身不会返回。
// 一旦调度器启动,CPU的控制权就交给了操作系统,由它来决定哪个任务何时运行。
os_start();
// 启用本地中断(Local Interrupts)。
// 这条指令通常在os_start()之后,意味着在操作系统完全启动后,才全面开启中断系统。
// 在此之前,中断可能是被禁用的,以保证初始化过程的原子性,防止被打断。
// 对于某些架构,这可能是开启总中断或特定优先级的中断。
local_irq_enable();
// 主函数的最后部分,一个无限循环。
// 理论上,程序执行到 os_start() 后就不应该再返回到这里。
// 这个循环的存在主要有两个目的:
// 1. 安全性:作为一种保障,万一os_start()意外返回,程序不会跑飞,而是停在这里。
// 2. 功耗管理:在没有任何任务需要运行时(例如所有任务都在等待事件或睡眠),CPU可以执行 "idle" 指令进入低功耗模式,等待中断唤醒。
while (1) {
// "idle" 是一条汇编指令,它告诉CPU当前没有工作可做,可以进入空闲/睡眠状态以节省功耗。
// 当有中断(如定时器、外设事件)发生时,CPU会被唤醒并跳转到相应的中断服务程序处理。
asm("idle");
}
return 0;
}
小笔记:
在SDK中所能跳转到的最早最底层函数就是setup_arch()函数了
在int mian函数中,主要是进行一些底层的初始化和最主要的是模式任务 app_core 创建,所有的 APP 模式在此任务里执行处理
应用核心任务处理函数:static void app_task_handler(void *p)
跳转到应用核心任务处理函数中如图所示:

代码讲解:
/**
* @brief 应用核心任务处理函数
* @note 这是由RTOS调度器管理的最高优先级的应用任务。
* 所有与用户交互、业务逻辑、模式切换等核心功能都在这里实现。
* 该函数在main函数中通过task_create()创建,并由os_start()启动。
* @param p: 任务创建时传递的参数,此处未使用。
*/
static void app_task_handler(void *p)
{
// 1. 应用初始化阶段
// 调用app_init()函数,完成应用开始前的所有一次性准备工作。
// 这包括硬件外设(如解码器、I2C/SPI总线)、软件模块(如蓝牙协议栈、文件系统)
// 以及系统状态(如开机模式、音量、播放列表)的初始化。
app_init();
// 2. 应用主循环/调度阶段
// 调用app_main()函数,进入应用的主循环。这个函数通常是一个死循环,
// 负责根据系统当前的状态(模式)来调度和处理各种事件。
app_main();
}
应用核心任务的初始化函数:app_ini()函数讲解
这个函数是应用程序的“构造函数”,在任务开始时仅执行一次,用于完成所有硬件和软件的初始化。

代码讲解:
/**
* @brief 应用核心任务的初始化函数
* @note 此函数在app_task_handler中被调用,仅执行一次。
* 它负责初始化所有硬件外设、软件模块、系统状态和处理特殊开机逻辑(如充电开机、固件更新等)。
* 这是应用程序运行前的“总装车间”。
*/
static void app_init()
{
int update; // 用于标记是否需要进行固件更新处理
// --- 分层初始化调用 ---
// 这些是杰理SDK中常见的分层初始化机制,类似于Linux内核的initcall机制。
// 它将不同模块的初始化按照“早、中、晚”的顺序解耦,便于管理。
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()) {
update = update_result_deal();
}
// --- 开机逻辑与状态设置 ---
app_var.play_poweron_tone = 1; // 默认设置:开机后播放提示音
// 如果当前不是在充电状态下
if (!get_charge_online_flag()) {
#if !TCFG_AD_BATTERY_CHAKE_EN
check_power_on_voltage(); // 检查开机电压,防止电压过低导致工作不稳定
#endif
#if TCFG_POWER_ON_NEED_KEY // 判断是否配置了“需要按键才能开机”
/*充电拔出,CPU软件复位, 不检测按键,直接开机*/
#if TCFG_CHARGE_OFF_POWERON_NE // 判断是否配置了“拔出充电线自动开机”
// 如果不是因为更新而开机,并且是软件复位(如看门狗复位、低电压复位)
// 或者是由LDO5V(通常是USB或充电线)唤醒的
if ((!update && cpu_reset_by_soft()) || is_ldo5v_wakeup()) {
#else
// 如果不是因为更新而开机,并且是软件复位
if (!update && cpu_reset_by_soft()) {
#endif
app_var.play_poweron_tone = 0; // 满足上述条件,则不播放开机提示音(实现“来电自启”或“掉电重启”不发声
} else {
check_power_on_key(); // 否则,正常检测开机按键
}
#endif
}
#if (TCFG_MC_BIAS_AUTO_ADJUST == MC_BIAS_ADJUST_POWER_ON)
extern u8 power_reset_src;
u8 por_flag = 0;
u8 cur_por_flag = 0;
/*
*1.update
*2.power_on_reset(BIT0:上电复位)
*3.pin reset(BIT4:长按复位)
*/
// 判断复位原因:1.固件更新 2.上电复位 3.引脚复位(长按复位键)
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;
}
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
sd_set_power(1);
#if(TCFG_CHARGE_BOX_ENABLE)
/* clock_add_set(CHARGE_BOX_CLK); */ // 可能是给充电盒的时钟使能,被注释掉了
chgbox_init_app(); // 初始化充电盒相关的应用功能
#endif
}
应用主函数:app_main()函数讲解
这个函数是应用程序的“导演”,在初始化完成后被调用,负责设置初始任务状态,然后进入一个由app_task_loop()驱动的无限循环,根据不同的任务模式进行调度。如图所示:

代码讲解:
/**
* @brief 应用主函数,负责设置初始任务并进入主循环
* @note 此函数在app_init()之后被调用,用于决定开机后进入哪个初始模式(如开机模式、充电模式等),
* 然后调用app_task_loop()进入任务处理循环。它本身不包含循环,但它调用的app_task_loop()包含。
*/
void app_main()
{
log_info("app_main\n"); // 打印日志,标记app_main函数开始执行
app_var.start_time = timer_get_ms(); // 记录系统启动时间,用于后续的延时或超时判断
// --- 核心:根据开机状态决定初始任务 ---
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 // 如果没有定义“PC端软件控制开机充电”
app_curr_task = APP_IDLE_TASK; // 默认进入空闲任务(充电模式)
#else
app_curr_task = APP_POWERON_TASK; // 否则,进入开机任务
#endif
} else { // 如果没有连接充电线,正常开机
#if SOUNDCARD_ENABLE // 如果使能了声卡功能(如USB声卡)
soundcard_peripheral_init(); // 初始化声卡外设
#endif
#if TCFG_HOST_AUDIO_ENABLE // 如果使能了USB主机音频功能
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);
#endif
/* endless_loop_debug_int(); */ //=======================================//
ui_update_status(STATUS_POWERON); // 更新UI显示为“开机中”状态 //
app_curr_task = APP_POWERON_TASK; // 设置当前任务为“开机任务” //======================================//
}
#if TCFG_CHARGE_BOX_ENABLE // 如果使能了充电盒功能
app_curr_task = APP_IDLE_TASK; // 强制将当前任务设置为空闲任务(充电盒模式优先)
#endif
#if (TCFG_USB_CDC_BACKGROUND_RUN && (!TCFG_PC_ENABLE))
usb_cdc_background_run();
#endif
// --- 进入主循环 ---
// 这是应用程序的“心跳”。app_task_loop()内部是一个无限循环,
// 它会根据app_curr_task的值,通过一个大的switch-case结构,
// 不断地调用和执行对应模式(如APP_POWERON_TASK, APP_IDLE_TASK, APP_BT_TASK等)的处理函数。
// 当有事件发生(如按键、蓝牙连接、USB插入)时,会改变app_curr_task的值,从而实现模式切换。
app_task_loop(); //当开始通过app_main( )函数设置首任务或进行任务模式切换时,便会执行app_task_loop( )判定并开始相应模式的任务,当发生任务模式更改时,会根据变量app_curr_task跳转执行:
}
app_task_loop其实就是软件开发的主循环任务,app_task_loop()内部是一个无限循环;它会根据app_curr_task的值,通过一个大的switch-case结构,不断地调用和执行对应模式(如APP_POWERON_TASK, APP_IDLE_TASK, APP_BT_TASK等)的处理函数。
这些代码清晰地展示了一个典型的嵌入式RTOS应用的启动流程:
app_init(): “静态初始化”阶段。在任务开始时,一次性地、自底向上地初始化所有硬件、软件模块和系统状态。它处理各种复杂的开机场景(如充电开机、更新后开机、复位后开机),确保系统在进入主循环前处于一个已知的、稳定的状态。
app_main():“动态启动”阶段。它在app_init()之后运行,负责根据当前的硬件状态(是否充电)来决定系统的初始工作模式(app_curr_task),然后将控制权交给app_task_loop()。app_task_loop()是真正的“无限循环”和“任务调度器”,它驱动着整个应用程序的运行和模式切换。
任务调度器:app_task_loop()函数讲解
根据上面app_main()函数的介绍知道在app_init()之后运行,负责根据当前的硬件状态(是否充电)来决定系统的初始工作模式(app_curr_task),然后将控制权交给app_task_loop()。在app_main()函数中可以观察到,系统的初始工作模式已经设置为开机任务APP_POWERON_TASK;

代码讲解:
应用任务主循环(任务调度器): 这是一个无限循环,根据全局变量 app_curr_task 的值,通过 switch-case结构调用不同的模式处理函数。它是整个应用程序的“心跳”,负责在不同的工作模式(如开机、蓝牙、音乐、空闲等)之间进行切换和调度。该函数在 app_main() 中被调用,一旦进入便不再返回。
app_task_clear_key_msg();防止按键消息堆积,或者在任务切换时清除旧的按键状态,避免误触发
vm_check_all(0);垃圾回收、或者将缓存的数据写入Flash。参数0可能代表“轻度检查”或“后台整理”。
APP模式值在“ app_task.h ” 文件中如图所示

APP_POWERON_TASK 开机模式
APP_POWEROFF_TASK 关机模式
APP_BT_TASK 蓝牙模式(核心模式)
APP_MUSIC_TASK 本地音乐模式(U盘/TF卡模式)
APP_FM_TASK 收音电台模式
APP_RECPRD_TASK 录音模式
APP_LINEIN_TASK AUX线输入模式
APP_RTC_TASK 时钟模式
APP_SLEEP_TASK 睡眠模式
APP_IDLE_TASK 空闲/待机模式
APP_PC_TASK PC电脑模式
APP_SPDIF_TASK SPDIF输出模式(高端音响)
……
制作不易!喜欢的小伙伴给个小赞赞!喜欢我的小伙伴点个关注!有不懂的地方和需要的资源随时问我哟!
更多推荐
所有评论(0)