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

公共代码

在深入研究特定阶段的代码之前,值得简要了解一些公共代码。

所有部分和阶段共享的代码可以在 src/common/ 目录中找到。然而,请注意,每个阶段仅使用 src/common/filters/ 目录中的一个源文件。

请注意,本教程的每个阶段都假设用户已经阅读了前面的部分。这将有助于避免重复信息。

main.xc

main.xc 是固件应用程序的入口点。main() 在一个 XC 文件中定义,而不是 C 文件,以利用 XC 语言方便的语法来分配通道资源和启动在每个 tile 上运行的线程。

src/common/main.xc
int main(){
// 用于在tile[0]和tile[1]之间传输音频数据的通道。
chan c_audio_data;
// 用于在信号处理完成后从tile[1]报告定时信息的通道。
chan c_timing;

par {
// 一个线程在tile[0]上运行
on tile[0]:
{
// 调用xscope_config_io(XSCOPE_IO_BASIC)以便使用xscope进行打印,而不是使用JTAG。
xscope_config_io(XSCOPE_IO_BASIC);

printf("Running Application: %s\n", APP_NAME);

// 这是应用程序将花费所有时间的地方。
wav_io_task(c_audio_data,
c_timing,
INPUT_WAV, // 这三个宏在CMake项目中针对每个目标进行定义。
OUTPUT_WAV,
OUTPUT_JSON);

// 一旦wav_io_task()返回,我们就完成了。
_Exit(0);
}

// 在tile[1]上有两个线程。
// 一个简单的任务,等待将定时信息报告回tile[0]。
on tile[1]: timer_report_task(c_timing);
// 执行信号处理的线程。
on tile[1]: filter_task(c_audio_data);
}
return 0;
}

tile[0] 上生成一个线程 wav_io_task(),负责处理所有文件 IO,并将输入和输出信号分解成音频帧。

在第二个 tile,tile[1] 上生成两个额外的线程。filter_task() 是滤波的实际执行点,它与 wav_io_task 之间使用一个通道资源进行通信。

另外,在 tile[1] 上还有一个 timer_report_task()。这是一个简单的线程,它等待应用程序即将终止,然后将一些定时信息(由 filter_task 收集)报告回 wav_io_task,以便性能信息可以写入文本文件。

APP_NAMEINPUT_WAVOUTPUT_WAVOUTPUT_JSON 这些宏都在特定阶段的 CMakeLists.txt 中定义。

common.h

common.h 头文件包含了几个其他的样板头文件,并定义了大多数阶段所需的几个宏。

common.h 中:

#define TAP_COUNT     (1024)
#define FRAME_SIZE (256)
#define HISTORY_SIZE (TAP_COUNT + FRAME_SIZE)

TAP_COUNT 是正在实现的数字 FIR 滤波器的阶数。它是滤波器系数的数量。从算术角度来说,计算滤波器输出所需的乘法和加法的数量。它还是为了计算特定时间步长的滤波器输出所需的最小输入样本历史记录大小。

filter_task 不是逐个接收输入样本,而是以帧的形式接收新的输入样本。每个输入样本帧将包含 FRAME_SIZE 个新的输入样本值。

由于滤波线程以 FRAME_SIZE 的批量接收输入样本并以 FRAME_SIZE 的批量发送输出样本,因此将它们作为一批进行处理是合理的。此外,在处理一批数据时,将样本历史记录存储在单个线性缓冲区中更加方便和高效。该缓冲区的长度必须为 HISTORY_SIZE

misc_func.h

misc_func.h 头文件包含了几个简单的内联标量函数,它们在整个教程的各个地方都需要使用。lib_xcore_math 的未来版本可能会包含这些函数的实现,但目前我们的应用程序必须定义它们。

filters/

src/common/filters/ 目录包含了 4 个源文件,每个文件以不同的格式定义相同的滤波器系数。每个阶段的固件仅使用这些滤波器中的一个。

filter_coef_double.c

filter_coef_double.c 将系数以 double 元素的数组形式存储:

const double filter_coef[TAP_COUNT] = {...};

filter_coef_float.c

filter_coef_float.c 将系数以 float 元素的数组形式存储:

const float filter_coef[TAP_COUNT] = {...};

filter_coef_q2_30.c

filter_coef_q2_30.c 将系数以 q2_30 元素的数组形式存储。q2_30 是在 lib_xcore_math 中定义的固定点类型。稍后将对其进行更详细的描述。

const q2_30 filter_coef[TAP_COUNT] = {...};

filter_coef_q4_28.c

filter_coef_q4_28.c 将系数以 q4_28 元素的数组形式存储。q4_28 是在 lib_xcore_math 中定义的固定点类型。稍后将对其进行更详细的描述。

const q4_28 filter_coef[TAP_COUNT] = {...};

计时器

通过 timing.h 中的计时模块来测量滤波器的时间性能。timing.c 的实现并不重要。在每个阶段的实现中,你将找到以下四个调用:

timer_start(TIMING_SAMPLE);
timer_stop(TIMING_SAMPLE);
timer_start(TIMING_FRAME);
timer_stop(TIMING_FRAME);

你会发现一对 timer_start(TIMING_SAMPLE)timer_stop(TIMING_SAMPLE) 包围着对 filter_sample() 的调用(关于 filter_sample() 的更多信息稍后会提到)。

它们是应用程序(以及所有后续阶段)用来测量每个样本的滤波性能的方法。设备的 100 MHz 参考时钟用于在计算新的输出样本时(使用 TIMING_SAMPLE)捕获时间戳。这告诉我们在计算输出样本时经过了多少个 100 MHz 时钟周期。timing.c 中的代码最终只是计算所有输出样本的平均时间。

由于每个样本的计时会错过许多所需的处理过程,在每个阶段中,你还会在 rx_frame()tx_frame() 中找到对 timer_start(TIMING_FRAME)timer_stop(TIMING_FRAME) 的调用。这对(使用 TIMING_FRAME)测量处理整个帧所需的时间。