软件调试方法
📎 原始文档:https://www.kdocs.cn/l/crfPObuH9i3x 软件调试方法
一. 串口调试信息
打开打印
#define CONFIG_DEBUG_ENABLE//注释掉就是关打印

打开断言打印
//打开断言
const int config_asser = 1;
生成反汇编文件
其他内容详见:
五. 死机原因
一. 遇到死机调试方法
开机复位源的打印
//打开复位源
const char log_tag_const_v_PMU AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(FALSE);
const char log_tag_const_i_PMU AT(.LOG_TAG_CONST) = 1;//CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_d_PMU AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(FALSE);
const char log_tag_const_w_PMU AT(.LOG_TAG_CONST) = 1;//CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_e_PMU AT(.LOG_TAG_CONST) = 1;//CONFIG_DEBUG_LIB(TRUE);
库打印
//打开整个库打印
#define LIB_DEBUG 1
打开LOG打印
#ifdef CONFIG_RELEASE_ENABLE
#undef __LOG_LEVEL
#define __LOG_LEVEL 0
#endif
打印函数所在位置的打印
printf("%s %s %d \\r\\n" ,__FILE__, __func__ , __LINE__) ;
打印系统时间(一般需要打开库打印)
///打印是否时间打印信息
const int config_printf_time = 1;
蓝牙协议的打印

u8 l2cap_debug_enable = 0xf0;
u8 rfcomm_debug_enable = 0xf;
u8 profile_debug_enable = 0xff;
u8 ble_debug_enable = 0xff;
u8 btstack_tws_debug_enable = 0xf;
内存的打印
函数 mem_stats(); 需打开库打印(库打印打开参考上面修改)
[HEAP_MEM]Current free heap 124904 bytes, minimum ever free heap 124700 bytes, physics memory size 100480 bytes
physics memory size %d bytes ,这个表示现在剩下可用的物理内存
打印历史剩余的可用RAM最小值
extern void mem_unfree_dynamic_max_dump(void);
mem_unfree_dynamic_max_dump(); // 调用此句
// SDK\apps\soundbox\log_config\lib_system_config.c, 配置
const u32 CONFIG_HEAP_MEMORY_TRACE = 1;

cpu占比的信息,堆栈
config_system_info = 1; //开启,若没有该变量可以不理会
void os_system_info_output(void); //输出信息,可以定时器5秒调用一次
void os_system_info_reset(void); //重置运行时间
// 可视化SDK,周期调用以下函数输出任务信息
// 具体可参考audio_debug.h audio_debug.c文件
void task_info_output(int size);
task_info_output(0);
注意:如果程序使用task_create 函数创建任务里面会默认把任务加进记录,输出能看到对应的任务输出
task_create(app_task_handler, NULL, "app_core");
如果使用 os_task_create 是没有把该任务加进记录的此时需要调用多一条函数 :
void task_info_add(TaskHandle_t task) 把任务加进记录
void task_info_add(TaskHandle_t task)
//demo
task_info_add(os_task_get_handle("my_task")); //my_task 为创建的任务
时钟的打印
函数:clock_dump();需打开库打印(库打印打开参考上面修改)
[00:03:28.408][Debug]: [CLOCK][-------------Clock Dump-----------]
[00:03:28.416][Debug]: [CLOCK]---CHIP Ver. : 0x5e01
[00:03:28.426][Debug]: [CLOCK]--Internal OSC CLK : 24000000
[00:03:28.434][Debug]: [CLOCK]--OSC CLK : 0
[00:03:28.444][Debug]: [CLOCK]--PLL SYS CLK : 96000000
[00:03:28.452][Debug]: [CLOCK]--PLL ALNK CLK : 11294117
[00:03:28.460][Debug]: [CLOCK]---SFC CLK : 96000000
[00:03:28.468][Debug]: [CLOCK]---SPI CLK : 96000000
[00:03:28.478][Debug]: [CLOCK]---HSB CLK : 6000000
[00:03:28.486][Debug]: [CLOCK]---LSB CLK : 6000000
[00:03:28.496][Debug]: [CLOCK]---P33 CLK : 6000000
[00:03:28.504][Debug]: [CLOCK]--USB CLK : 48000000
[00:03:28.512][Debug]: [CLOCK]--AUDIO CLK : 48000000
[00:03:28.518][Debug]: [CLOCK]--UART CLK : 48000000
[00:03:28.528][Debug]: [CLOCK]--BT CLK : 48000000
[00:03:28.536][Debug]: [CLOCK]---SYS DVDD : 7
[00:03:28.544][Debug]: [CLOCK]---VDC13 : 7
[00:03:28.552][Debug]: [CLOCK]---RANGE : 0
[00:03:28.560][Debug]: [CLOCK]---TRIM SYS DVDD : 3
[00:03:28.566][Debug]: [CLOCK]SFC_CON : 002802b5
[00:03:28.578][Debug]: [CLOCK]SFC_QCNT : 0000001f
[00:03:28.584][Debug]: [CLOCK]CACHE_CON : 7ff23120
二、放ram方法
AT(.volatile_ram_code)
三、低功耗调试方法
打开 以下打印 查看是否有线程卡住不让进低功耗
SDK\apps\soundbox\log_config\lib_driver_config.c
const char log_tag_const_v_PMU AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(FALSE);
const char log_tag_const_i_PMU AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_d_PMU AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_w_PMU AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_e_PMU AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
const char log_tag_const_c_PMU AT(.LOG_TAG_CONST) = CONFIG_DEBUG_LIB(TRUE);
3四、复位源
VDDIO POR 是首次上电,即芯片从没电到有电,从头跑
VDDIO LVD 是电池电量不够或者VDDIO被扯,导致VDDIO 的电压低于设定的阈值,复位了
WDT 是看门狗超时,复位了
VCM 是芯片vcom引脚,默认的短按就会复位,就是通常板子上的reset键就是用这个脚做的。
PPINR 是设置的长按复位脚,长按引起的复位
SOFT RESET 是P33系统管理的复位,一般是程序里执行了复位函数,如果代码异常了,异常中断里会执行复位函数来复位芯片,比如一些非法操作:除0异常,非法地址访问等等。
DVDD NO OK 是供电方式没有配置对,样机是DCDC供电的,但是配置成了LDO供电方式了
POWER RETURN 软关机唤醒
- 例子

u8 power_reset_source_dump(void)
{
u8 sfr;
cpu_soft_reset = 0;
sfr = p33_rx_1byte(P3_PMU_CON1);
if((sfr&BIT(7))==0)
{
printf("power return\n");
return sfr;
}
p33_or_1byte(P3_PMU_CON1, BIT(6));
sfr = p33_rx_1byte(P3_RST_SRC);
prinft("--P3 Reset Source:0x%x\n", sfr);
if(sfr&BIT(0)){
printf("VDDIO por\n");
}
if(sfr&BIT(1)){
printf("VDDIO LVD\n");
}
if(sfr&BIT(2)){
printf("WDT\n");
}
if(sfr&BIT(3)){
printf("VCM\n");
}
if(sfr&BIT(4)){
printf("PPINR\n");
}
if(sfr&BIT(5)){
printf("SYS RESET\n");
}
if(sfr&BIT(6)){
printf("soft reset\n");
cpu_soft_reset = 1;
}
return sfr;
}
VCOM复位
当vcom 脚拉高的时候会复位,这是默认的vcom复位功能,一般不想要做8秒长按复位,可以用VCOM复位来做复位功能
当VCOM复位没有作用的时候,可以使用下面的接口来再次开启VCOM复位功能
VCM_DET_EN(1);
//P33_CON_SET(P3_ANA_CON2, 3, 1, en); //VCM detect enable bit
五. 死机原因
请仔细查看下面链接内容:
707_轻量级打印开启.otl
5.1.2. 系统异常信息
- 如何使用mpu来定位非法被修改的地址 问题:
当某些存在bug的代码产生野指针在合法范围之内,就不会触发 空白地址 或者 MMU非法地址 异常,但是会改掉其他模块的全局变量,就会给令开发者感到疑惑,某些全局变量被不明原因的修改了,当发生这种情况时,可以使用SDK提供的MPU内存保护接口将意外被修改的变量保护起来,保护后被意外修改时将会触发异常,SDK默认会使用相关MPU接口保护系统内部一些内存范围,如果用户流程写到该保护范围,将会触发异常。
并非所有芯片都支持mpu,查看是否是支持该功能可以查看,include_lib\driver\cpu\brxx\asm\debug.h路径是否包含mpu的接口。
/* ---------------------------------------------------------------------------- */
/**
* @brief Memory权限保护设置
*
* @param idx: 保护框索引, 范围: 0 ~ 7, 目前系统默认使用0和3, 用户可用1, 2, 4, 5, 6, 7
* @param begin: Memory开始地址
* @param end: Memory结束地址
* @param inv: 0: 保护框内, 1: 保护框外
* @param format: "Cxwr0rw1rw2rw3rw", CPU:外设0:外设1:外设2:外设3,
* @param ...: 外设ID号索引, 如: DBG_EQ, 见debug.h
* @example1: 设置内存保护框1, 保护地址为0x200 ~ 0x400 - 1, 令该地址只可以FFT和EQ设备访问:
mpu_set(1, 0x200, 0x400 - 1, 0, "0rw1rw", get_dev_id("DBG_FFT"), get_dev_id("DBG_EQ"));
* @example2: 设置内存保护框2, 保护地址为0x200 ~ 0x400 - 1, 令该地址只可以cpu读写和EQ设备访问:
mpu_set(2, 0x200, 0x400 - 1, 0, "Crw0rw", get_dev_id("DBG_EQ"));
*/
/* ---------------------------------------------------------------------------- */
void mpu_set(int idx, u32 begin, u32 end, u32 inv, const char *format, ...);
/* ---------------------------------------------------------------------------- */
/**
* @brief 取消指定框的mpu保护
*
* @param idx: 保护框索引号
*/
/* ---------------------------------------------------------------------------- */
void mpu_disable_by_index(u8 idx);
/* ---------------------------------------------------------------------------- */
/**
* @brief :取消所有保护框mpu保护
*/
/* ---------------------------------------------------------------------------- */
void mpu_diasble(void);
使用示例:
struct _TEST_RAM_PROTECT {
u8 test_buf[10];
u8 over_data;
}__attribute__((packed)) _test_data;
#include "debug.h"
static void mpu_set_enable()
{
// 其中"Cr"是表示此地址区域仅允许cpu只读,其它操作会触发断言
mpu_set(1,&_test_data.over_data,&_test_data.over_data + 1,0,"Cr");
//这边填写保护的范围就是u8大小的over_data,所以后面是+1,如果是int大小,则是+4
}
static void mpu_set_disable()
{
mpu_diasble();
}
void timer_test_func(void *arg)
{
// printf("%x %x %x",&_test_data.test_buf[9],&_test_data.test_buf[10],&_test_data.over_data);
static u8 cnt = 0;
if(cnt > 10) {
mpu_set_enable();//循环10次后开启保护
cnt = 0;
} else {
mpu_set_disable();
}
printf("before over data = %d",_test_data.over_data);
for(int i=0;i<11;i++)//这里写越界
{
_test_data.test_buf[i] = i;
}
printf("after over data = %d",_test_data.over_data);
_test_data.over_data = 33;//重新赋值,用于查看下次越界是否会覆写
cnt++;
}
//以下两行函数放在app_main中调用
mpu_set_enable();
sys_timer_add(NULL,timer_test_func,1000);
触发断言结果
测试SDK包:
mpu_debug_案例_701NsoundboxV142.zip
六.线程相关
- 防止打断线程开关方法
- 开关中断
extern void local_irq_disable();
extern void local_irq_enable();
local_irq_disable();
local_irq_enable();
2. 定义锁
DEFINE_SPINLOCK(clock_lock);
spin_lock(&clock_lock);
spin_unlock(&clock_lock);
////案例
void clock_add(u32 type)
{
u32 cur_clk ;
spin_lock(&clock_lock);
u8 resoult = clock_ext_push(type);
spin_unlock(&clock_lock);
if (!resoult) {
return;
}
}
2. 线程相关设置
文件: task_table.c
关键数组: const struct task_info task_info_table[]
prio优先级说明: 最低优先级: 0; 最高优先级: 6; 没有特殊要求不建议修改, 避免引起系统崩溃。
stack_size:堆栈大小
qsize:消息池大小
七、字节对齐
4字节对齐
__attribute__((aligned(4)));
八、695nSDK打补丁程序-可复制参考程序
【金山文档】 AC695N_soundbox_sdk_release_0.0.6_p2-未打任何补丁程序的公版SDK
https://kdocs.cn/l/cnuwOZ98olZd
【金山文档】 AC695N_soundbox_sdk_release_0.0.6_p2_打不开机补丁V3V4-打修复LVD失效问题补丁
.在695-006-p2SDK上添加了:
1.AC695N_芯片兼容性及不开机补丁v3_最新20210918
2.AC695N_不开机补丁v4
3.关于AC696N、AC695N软关机后LVD失效问题修改说明
https://kdocs.cn/l/coisojXZn8aq
FAQ
一. 遇到死机调试方法
异常信息打印说明
- 5.1. 串口调试 — JL Project Documentation (zh-jieli.com)
- 查看死机的原因
- 打开断言的打印
///异常中断,asser打印开启
const int config_asser = 1;
- 然后复现死机现象,触发异常会有以下打印,然后可以得到死机的原因
- 然后根据打印信息,生成汇编代码。 根据死机地址找到死机的地方
处理步骤参考文档:软件调试方法.otl - (可选)有条件可以打开完整的库打印
//打开整个库打印
#define LIB_DEBUG 1
完成以上步骤后,即可复现问题观察打印。
例子:
异常死机打印分析:
根据异常的打印找到以下地址程序最后运行时对应的 打开 前面生成的 sdk.lst 找到上图框住的地址
2. 如何观察函数是否放在flash 打开前面生成的sdk.lst找到对应函数的名称。(如果放在在flash的函数,一定能找到一般地址开头是1e开头的为放flash的代码)
- 文档 异常调试介绍.pdf
PS:
如果生成不了sdk.lst ,可以直接把%ELFFILE% 直接改为sdk.elf%OBJDUMP% -D -address-mask=0x1ffffff -print-dbg %ELFFILE% > %ELFFILE%.lst
二、烧录文件加KEY参考。
备注,加KEY后,重新编译或重新运行download.bat文件,重新生成的烧录文件就是加KEY后的文件。

三、升级失败调试方法
- 打开OTA打印(一定要替换)以及 uboot打印(可选)
- 打开isd_config.ini 里面的打印
//uboot and ota.bin串口tx
// *****************************
// 如果是USB口 设置为 USBDP/USBDM
// *****************************
UTTX=PA05;//uboot串口tx
UTBD=1000000;//uboot串口波特率
有些SDK是由isd_config_rule.c 去生成isd_config.ini 文件,则需要修改isd_config_rule.c 里面的配置
2. 更换对应文件 注意看实际的SDK使用是哪个ota.bin文件
可以看下载工程的download.bat(例如下图:使用的是...\ota.bin ->就是上两层目录下的ota.bin) 一般是tools文件目录下
将上面的ota.bin和uboot.bin 替换成带打印的bin
2. 提供对应的log
本文档为保密信息,未经授权,请勿转发或告知第三方。非常感谢!
This electronic documentation and any attachments are confidential and may be legally privileged or otherwise protected from disclosure. If you are not the intended recipient, please do not disclose the contents to anyone。
Thank you for your cooperation!
解析权归杰理所有!
The analysis right belongs to JieLi Technology Co., Ltd.