Skip to main content
欢迎来到PAWPAW技术文档网站了解更多信息

USB Audio Debugging

本文记录了基于XMOS平台上进行USB 音频开发中遇到的问题与解决方式。

音量调节

lib_xua 3.5.1中,我们可以将mixer与音量调节解耦,简单地通过宏定义,控制是否要打开XMOS的音量调节。

在windows平台,音量调节对应系统的音量控制,调整系统音量将会控制XMOS设备的音量大小。

解决方式:

在Makefile中添加以下编译选项:

-DIN_VOLUME_IN_MIXER=0
-DOUT_VOLUME_IN_MIXER=0

该宏定义会使得音量控制无需启用Mixer。

并口GPIO控制

由于60-pin封装的XMOS芯片能够利用的引脚有限,开发人员会经常需要独立地使用4-bit或8-bit port,并分别控制它们其中的一个或几个引脚,同时不影响其他引脚的功能。

XMOS设计并口的初衷是加快数据的I/O,多路数据并行使得在一个时钟周期内可以读入4-bit/8-bit/16-bit/32-bit的外部数据(例如QSPI,LPDDR)。

然而,用户并不总是需要并行地使用这些端口。为了最大化资源的利用,并保持对其他应用的兼容性,XMOS提供了一种使用汇编和锁的方式,以独立地读取/写入一组并口中的某几个端口。

解决方式:

gpio_access.c 提供了上面描述中的功能,用于控制GPIO(通用输入输出)端口的访问和锁定,以确保在多核环境下的线程安全。代码使用了软件锁(swlock_t类型的变量gpo_swlock)来同步对GPIO端口的访问。

你需要在其他文件中定义端口p_gpio,我们通常建议在audiohw.xc中定义它。

完成定义后,你可以通过包含gpio_access.h文件,通过set_gpio()函数,以向指定的端口输出数据。

开启打印后,XCORE输出的时序不稳定

在使用XCORE处理器进行音频处理时,部分用户报告在启用打印输出(例如使用printf)后,出现了时序不稳定的问题。这通常发生在尝试打印大量音频样本或在时序关键的驱动代码中添加打印语句时。

  1. 将大量打印语句(如printf)添加到音频处理循环或中断服务例程中。
  2. 运行代码并观察音频输出或时序相关的功能。

用户可能会观察到音频质量下降或时序相关功能出现异常行为。

原因分析

  1. JTAG打印性能限制printf等打印函数可能通过JTAG接口输出,该接口的速度相对较慢,可能导致处理器中断,从而影响时序。
  2. 时序敏感性:音频处理和其他时间关键的任务对于处理器的时序非常敏感。打印操作由于其非确定性的执行时间,会引入不可预测的延迟,从而影响整体的时序稳定性。

解决方案

  • 短期解决方案 使用XSCOPE:XSCOPE是一个调试工具,设计用于提供最小化干扰的实时数据监控和控制。使用XSCOPE代替JTAG打印可以减少对时序的影响。

  • 长期解决方案

    避免在时序敏感代码中打印:在开发阶段,应尽量避免在时序关键部分添加打印语句。如果需要进行调试,可以考虑以下方法:

    • 状态指示:使用LED或其他状态指示器来代替打印,以表明程序状态。

    • 条件编译:使用宏或条件编译指令来控制打印语句的编译,以便在发布版本中去除这些打印语句。

调试建议

  • 分段调试:将代码分段,并逐一验证每个段落,以便定位引起时序问题的具体代码块。

  • 性能分析:使用性能分析工具来确定代码中的瓶颈,特别是那些可能影响时序的部分。

  • 测试与验证:在更改代码(尤其是涉及时序的代码)后,进行充分的测试以确保时序的准确性和稳定性。

打印输出在开发过程中是一个有用的调试工具,但在处理时序敏感的应用时,需要谨慎使用。通过使用XSCOPE、避免在关键代码中打印、以及采用其他调试策略,可以有效地减少打印对时序稳定性的影响。在产品化阶段,应确保所有的调试打印语句都已被移除或禁用,以保证最终产品的性能和稳定性。

输入输出采样率随时可调整

USB Audio默认需将输入输出的采样率需要保持一致,以保证录播正常;

实际的使用过程中,会遇见以下情况:

  • 输入采样率变灰色,不可调

    1. 在Windows系统的声卡界面,将输入输出的采样率设定为一致
    2. 调整输入采样率,此时会发现变成灰色,无法调整

    这时需调整输出采样率,然后才能调整输入采样率

    image-20240109232036197

解决方式:

此项单独调整主要解决"输入采样率变灰,不可调"的现象。注意这仅仅是控制显示,实际使用时依然遵循以下要求:

输入输出的采样率需要保持一致,以保证录播正常

要达到输入输出采样率随时可调整得目标,主要修改两个文件,分别是xua_ep0_descriptors.hxua_ep0_uacreqs.xc

在endpoint0信息中是存在OUT的,所以主要是在原有的基础上,添加IN的采样率显示的调整,可以理解为将IN采样率分离出来;由于修改较多,具体修改项可下载文件后对比

16/32bit位深输入数据异常

USB Audio需要多位深采样率支持时,将INPUT_FORMAT_COUNT设定2或3,以支持16/32bit时,会出现数据异常丢包的情况;

这是由于默认输入设置只支持24bit位深,在lib_xua/lib_xua/api/xua_conf_default.h搜索Useful for dropping lower。

修改输入部分的宏定义,主要包括以下几点:

  1. 支持多种输入格式:原来的宏定义只考虑了输入格式1的分辨率和位字节。修改后的宏定义增加了对输入格式2和输入格式3的支持。

  2. 条件判断更加复杂:新增的宏定义中,对于32位分辨率的使用,不仅检查了输入格式1,还检查了输入格式2和输入格式3,并且根据INPUT_FORMAT_COUNT变量判断是否考虑这些格式。

  3. 灵活配置:通过修改后的宏定义,可以根据不同的输入格式和位字节来决定是否使用32位分辨率以及位的配置,使得系统能够更灵活地适应不同的处理需求。

修改后的宏定义代码
/* Useful for dropping lower part of macs in volume processing... */
#if (FS_STREAM_FORMAT_INPUT_1_RESOLUTION_BITS > 24) || (HS_STREAM_FORMAT_INPUT_1_RESOLUTION_BITS > 24) ||\
(((FS_STREAM_FORMAT_INPUT_2_RESOLUTION_BITS > 24) || (HS_STREAM_FORMAT_INPUT_2_RESOLUTION_BITS > 24)) && (INPUT_FORMAT_COUNT > 1)) || \
(((FS_STREAM_FORMAT_INPUT_3_RESOLUTION_BITS > 24) || (HS_STREAM_FORMAT_INPUT_3_RESOLUTION_BITS > 24)) && (INPUT_FORMAT_COUNT > 2))
#define STREAM_FORMAT_INPUT_RESOLUTION_32BIT_USED 1
#else
#define STREAM_FORMAT_INPUT_RESOLUTION_32BIT_USED 0
#endif

#if(FS_STREAM_FORMAT_INPUT_1_SUBSLOT_BYTES == 4) || (FS_STREAM_FORMAT_INPUT_2_SUBSLOT_BYTES == 4) ||\
(FS_STREAM_FORMAT_INPUT_3_SUBSLOT_BYTES == 4) || (HS_STREAM_FORMAT_INPUT_1_SUBSLOT_BYTES == 4)||\
(HS_STREAM_FORMAT_INPUT_2_SUBSLOT_BYTES == 4) || (HS_STREAM_FORMAT_INPUT_3_SUBSLOT_BYTES == 4)
#define STREAM_FORMAT_INPUT_SUBSLOT_4_USED 1
#else
#define STREAM_FORMAT_INPUT_SUBSLOT_4_USED 0
#endif

#if(FS_STREAM_FORMAT_INPUT_1_SUBSLOT_BYTES == 3) || (FS_STREAM_FORMAT_INPUT_2_SUBSLOT_BYTES == 3) ||\
(FS_STREAM_FORMAT_INPUT_3_SUBSLOT_BYTES == 3) || (HS_STREAM_FORMAT_INPUT_1_SUBSLOT_BYTES == 3)|| \
(HS_STREAM_FORMAT_INPUT_2_SUBSLOT_BYTES == 3) || (HS_STREAM_FORMAT_INPUT_3_SUBSLOT_BYTES == 3)
#define STREAM_FORMAT_INPUT_SUBSLOT_3_USED 1
#else
#define STREAM_FORMAT_INPUT_SUBSLOT_3_USED 0
#endif

#if(FS_STREAM_FORMAT_INPUT_1_SUBSLOT_BYTES == 2) || (FS_STREAM_FORMAT_INPUT_2_SUBSLOT_BYTES == 2) ||\
(FS_STREAM_FORMAT_INPUT_3_SUBSLOT_BYTES == 2) || (HS_STREAM_FORMAT_INPUT_1_SUBSLOT_BYTES == 2)|| \
(HS_STREAM_FORMAT_INPUT_2_SUBSLOT_BYTES == 2) || (HS_STREAM_FORMAT_INPUT_3_SUBSLOT_BYTES == 2)
#define STREAM_FORMAT_INPUT_SUBSLOT_2_USED 1
#else
#define STREAM_FORMAT_INPUT_SUBSLOT_2_USED 0
#endif

DFU占用线程

使用lib_xua时,当 USB和Audio 不在同一个Tile时,DFUHandler()会单独占用一个线程。

使用一个Core仅实现DFUHandler(),这对Core利用率是不高的;应将Core使用更加合理一些,特别是当Core不够用时,更应注意Core的分配

在lib_xua/lib_xua/src/core/main.xc,有以下代码,说明此时DFUHandler()是单独运行的,可将其更改为Combined

#if XUA_USB_EN
#if (XUD_TILE != 0 ) && (AUDIO_IO_TILE != 0) && (XUA_DFU_EN == 1)
/* Run flash code on its own - hope it gets combined */
//#warning Running DFU flash code on its own
on stdcore[0]: DFUHandler(dfuInterface, null);
#endif
#endif

将DFUHandler()执行部分更改为Combined

#if XUA_USB_EN
#if (XUD_TILE != 0 ) && (AUDIO_IO_TILE != 0) && (XUA_DFU_EN == 1)
/* Run flash code on its own - hope it gets combined */
//#warning Running DFU flash code on its own
on stdcore[0].code[0]: DFUHandler(dfuInterface, null);
#endif
#endif

调整IO的驱动能力

XMOS第三代芯片XCORE.AI支持调整I/O驱动能力,在芯片Datasheet中有如下说明

I/O AC特性,具有5pf负载,默认4mA驱动强度;I/O驱动电流可设定为2mA、4mA、8mA 和 12mA。

MODE_SETP ADCTRL

Mode bits 0x0006. Sets the pad options according to the valueof bits 23..18.

Bits 19 and 18 set the pull resistor (00 for none; 01 for weak pull-up; 10 for weak pull-down; or 11 for weak bus-keep.).

Bits 21 and 20 set the drive strength (00 for 2mA; 01 for 4mA; 10 for 8mA; or 11 for 12mA).

Bit 22 enables slew-ratecontrol. Bit 23 enables the Schmitt-Trigger.


如何修改I/O驱动

以下是lib_xua/src/core/ports/audioports.xc 设定IO驱动为8mA,可作参考

    #ifdef __XS3A__
/* Increase drive strength of clock ports to 8mA */
asm volatile ("setc res[%0], %1" :: "r" (p_bclk), "r" (0x200006));
asm volatile ("setc res[%0], %1" :: "r" (p_lrclk), "r" (0x200006));
#endif

示例:为p_bclk设定不同的驱动能力

asm volatile ("setc res[%0], %1" :: "r" (p_bclk), "r" (0x000006));  /* Set drive strength 2mA */
asm volatile ("setc res[%0], %1" :: "r" (p_bclk), "r" (0x100006)); /* Set drive strength 4mA */
asm volatile ("setc res[%0], %1" :: "r" (p_bclk), "r" (0x200006)); /* Set drive strength 8mA */
asm volatile ("setc res[%0], %1" :: "r" (p_bclk), "r" (0x300006)); /* Set drive strength 12mA */

附:audioports.xc文件跳转链接GitHub lib_xua

共享全局变量

在XC语言中,可通过GET_SHARED_GLOBAL和SET_SHARED_GLOBAL,访问和修改同Tile的共享全局变量。

  • SET_SHARED_GLOBAL(g, v):用于将变量v的值写入共享内存中的全局变量g。

  • GET_SHARED_GLOBAL(x, g):用于从共享内存中读取全局变量g的值,并将其存储在变量x中。

需要在XC文件中,添加调用函数 #include "xc_ptr.h"

注意: 全局变量仅允许在同一个Tile中共享(它们使用同一块SRAM内存),不可以跨Tile共享内存,需要时使用通道传输变量。

共享全局变量的简单示例
#include <stdio.h>
#include "xc_ptr.h"

int g_val; /* 定义共享全局变量 */

int main() {

int local_val = 0;
int get_shared_to_buf = 0;

for (int i = 0; i < 5; i++)
{
local_val += i; /* 修改本地变量 */

SET_SHARED_GLOBAL(g_val, local_val); /* 写入本地变量到共享变量 */
printf("Writes the value of a local variable to a shared global variable: %d \n", g_val);

GET_SHARED_GLOBAL(get_shared_to_buf, g_val); /* 读取共享变量的值到缓冲 */
printf("Reads the value of a shared global variable: %d \n\n", get_shared_to_buf);
}
}

监测USB连接状态

lib_xud/lib_xud/src/core/XUD_User.c中,指示USB的连接状态,有以下函数

  • XUD_UserSuspend(),监测DP/DN状态,指示USB断开

  • XUD_UserResume(),监测DP/DN状态,指示USB连接

// XUD_UserSuspend函数的默认实现,标记为弱符号
// 该函数在USB设备挂起时被调用,用户可以根据需要提供具体实现
void XUD_UserSuspend(void) __attribute__ ((weak));
void XUD_UserSuspend()
{
// 默认实现为空,因为不是所有的USB应用都需要处理挂起事件
return;
}

// XUD_UserResume函数的默认实现,标记为弱符号
// 该函数在USB设备从挂起状态恢复时被调用,用户可以根据需要提供具体实现
void XUD_UserResume(void) __attribute__ ((weak));
void XUD_UserResume()
{
// 默认实现为空,因为不是所有的USB应用都需要处理恢复事件
return;
}

Windows音量控制

在Windows系统中,可以通过API函数来获取和设置控制代码xua_ep0_uacreqs.xc中的音量值 x。以下是关键的使用要点:

  • 输出音量控制(扬声器)

    • 使用 updateVol 函数结合 FU_USBOUT 标识符来更新输出音量。
    • 示例代码:
      x = longMul(master_vol, vol, 29) * !mutesOut[0] * !mutesOut[channel];
  • 输入音量控制(麦克风)

    • 使用 updateVol 函数结合 FU_USBIN 标识符来更新输入音量。
    • 示例代码:
      x = longMul(master_vol, vol, 29) * !mutesIn[0] * !mutesIn[channel];

MAC音量控制

在MAC系统中,虽然控制代码xua_ep0_uacreqs.xc中音量值 x 的使用方法与Windows相似,但可能需要使用不同的系统API或框架。以下是关键的使用要点:

  • 输出音量控制(扬声器)

    • 使用 updateMasterVol 函数结合 FU_USBOUT 标识符来更新输出音量。
    • 示例代码:
      int x = longMul(master_vol, vol, 29) * !mutesOut[0] * !mutesOut[i];
  • 输入音量控制(麦克风)

    • 使用 updateMasterVol 函数结合 FU_USBIN 标识符来更新输入音量。
    • 示例代码:
      int x = longMul(master_vol, vol, 29) * !mutesIn[0] * !mutesIn[i];

注意事项

  • 代码片段是在 xud_tile 中使用的。
  • 根据您的音频硬件和操作系统的具体情况,您可能需要调用不同的API函数或框架。
  • 在实施音量控制之前,请确保已经实现了所有必要的辅助函数和数据结构。

本文档提供了音量控制代码的基本理解和应用方法,但并未涉及具体的API调用示例。您需要根据自己的开发环境和需求,选择合适的方法来实现音量控制功能。