无标题
当前 LidarQ8App_0_1/lidar_q8_app_raw.c 的 Q8 处理流程,可以概括为:
读取 A55 发布的共享配置
↓
根据 Q8_CORE_ID 选择本 Q8 对应的 ADC/FFT 地址组
↓
初始化本 Q8 的 iDMA channel 0
↓
建立 FFT/ADC → 内部 DRAM 的 ping/pong descriptor
↓
循环搬运多个采集块
↓
内部 DRAM → A55 共享 SRAM buffer
↓
等待搬运完成
↓
通过 IPC channel 4 发送 Q8 buffer done Doorbell
↓
等待 MCU1 应答
↓
切换 ping/pong,继续下一批数据
下面按照代码实际执行顺序说明。
———
1. 每个 Q8 image 是独立编译、独立运行的
Q8 工程通过编译参数定义:
-DQ8_CORE_ID=0
-DQ8_CORE_ID=1
-DQ8_CORE_ID=2
-DQ8_CORE_ID=3
对应生成四个独立的 Q8 image:
q8_core_0.bin
q8_core_1.bin
q8_core_2.bin
q8_core_3.bin
代码中:
#ifndef Q8_CORE_ID
#define Q8_CORE_ID 0UL
#endif
因此,每个 Q8 image 内部的 Q8_CORE_ID 是固定常量。
四个 Q8 并不是在同一个程序中分别使用 iDMA channel 0、1、2、3。实际情况是:
Q8 core 0 程序:使用它自己的 IDMA channel 0
Q8 core 1 程序:使用它自己的 IDMA channel 0
Q8 core 2 程序:使用它自己的 IDMA channel 0
Q8 core 3 程序:使用它自己的 IDMA channel 0
这里的 IDMA_CHANNEL_0 是“当前 Q8 DSP 核内部的 iDMA channel 0”,不是四个 Q8 全局共享的一个 channel。
因此四个 Q8 同时运行时,实际硬件关系是:
Q8_0 的 IDMA channel 0
Q8_1 的 IDMA channel 0
Q8_2 的 IDMA channel 0
Q8_3 的 IDMA channel 0
它们分别属于不同的 Q8 DSP core,互不冲突。
———
2. 为什么只使用 IDMA_CHANNEL_0
代码顶部虽然定义了:
#define IDMA_CHANNEL_0 0
#define IDMA_CHANNEL_1 1
#define IDMA_CHANNEL_2 2
#define IDMA_CHANNEL_3 3
但当前工作模式只实际使用:
IDMA_CHANNEL_0
原因是当前设计中,一个 Q8 core 只处理一路 ADC/FFT 数据:
#define NUM_CHANNELS_ACQ 1
#define NUM_CHANNELS_FFT 1
每个 Q8 core 的职责是:
Q8_0 -> ADC/FFT group 0 -> Buffer0
Q8_1 -> ADC/FFT group 1 -> Buffer1
Q8_2 -> ADC/FFT group 2 -> Buffer2
Q8_3 -> ADC/FFT group 3 -> Buffer3
因此,一个 Q8 core 内部只需要一条 iDMA 数据搬运通道:
当前 Q8 对应的 FFT 输出区
↓
内部 DRAM ping/pong
↓
A55 共享输出 buffer
没有必要在同一个 Q8 core 内再启用 channel 1、2、3。
之前官方代码中的多 channel 版本,是另一种架构:
一个 Q8 core 内部处理四路 ADC
channel 0 -> ADC0
channel 1 -> ADC1
channel 2 -> ADC2
channel 3 -> ADC3
而当前代码已经改为:
四个 Q8 core 分摊四路 ADC
Q8_0 -> ADC0
Q8_1 -> ADC1
Q8_2 -> ADC2
Q8_3 -> ADC3
所以不能同时把“一个 Q8 内部四个 iDMA channel 的官方版本”和“现在四个 Q8 分工版本”混在一起。
当前代码中的这些对象:
IDMA_BUFFER_DEFINE(fft_desc_buffer_1, …);
IDMA_BUFFER_DEFINE(fft_desc_buffer_2, …);
IDMA_BUFFER_DEFINE(fft_desc_buffer_3, …);
是为了保留官方代码结构和后续扩展空间,但当前单 Q8 单 stream 方案中没有实际使用。真正使用的是:
fft_desc_buffer_0
以及:
acq_desc_buffer
———
3. Q8 如何选择自己的 ADC/FFT 地址
代码定义了 ADC group 的步长:
#define ADC_GROUP_STRIDE 0x00040000UL
#define ADC_GROUP_BASE (0x00600000UL + (Q8_CORE_ID * ADC_GROUP_STRIDE))
因此四个 Q8 的地址基准如下:
Q8 core Q8_CORE_ID ADC group base
━━━━━━━━━ ━━━━━━━━━━━━ ━━━━━━━━━━━━━━━━
Q8_0 0 0x00600000
───────── ──────────── ────────────────
Q8_1 1 0x00640000
───────── ──────────── ────────────────
Q8_2 2 0x00680000
───────── ──────────── ────────────────
Q8_3 3 0x006C0000
FFT 读取区地址由:
#define FFT_RD_BUF_OFFSET_0 0x00030000UL
计算:
fft_marker_0_0 =
(volatile int16_t *)(ADC_GROUP_BASE + FFT_RD_BUF_OFFSET_0);
所以各 Q8 实际读取地址分别是:
Q8_0: 0x00630000
Q8_1: 0x00670000
Q8_2: 0x006B0000
Q8_3: 0x006F0000
当前日志中 Q8_0 打印:
fft_buf=0x00630000
这就是 Q8_0 对应的 FFT 输出区。
注意,fft_marker_0_1、fft_marker_0_2、fft_marker_0_3 仍然定义在代码中:
volatile int16_t *fft_marker_0_1;
volatile int16_t *fft_marker_0_2;
volatile int16_t *fft_marker_0_3;
但在当前架构中它们没有被使用,因为它们代表同一个 ADC group 内的其他 FFT 通道,而不是另外三个 Q8 core。
———
4. 当前 4K FFT 的数据大小
当前 A55 配置:
ADCSampleSize: 4096
含义是:
每路 ADC 输入 4096 个实数采样
4096 点 FFT
IPC/UDP 传输 2048 个复数频点
每个复数由两个 int16 组成:
real : 2 bytes
imaginary : 2 bytes
所以每个复数占 4 字节:
#define XFER_SIZE (2 * 2 * 2048)
结果为:
2048 × 4 = 8192 bytes
这就是 Q8 每次从硬件 FFT 区搬运的数据量。
代码中的:
#define XFER_SIZE (222048)
可以拆成:
2:real/imag 两个分量
2:每个分量为 int16,占 2 字节
2048:IPC 传输的复数频点数量
———
5. Q8 启动阶段
main() 首先清零性能计数器:
*q8_0_dbg_pm0 = 0UL;
…
*q8_0_dbg_pm6 = 0UL;
然后设置软件 magic:
*q8_0_dbg_pm0 = SW_MAGIC_ID;
清空两个内部 DRAM 缓冲区:
memset((void*)dram0_ping, 0, sizeof(dram0_ping));
memset((void*)dram0_pong, 0, sizeof(dram0_pong));
内部 DRAM 定义为:
ALIGN_16 SECTION_DRAM0_PING char dram0_ping[1024 * 64];
ALIGN_16 SECTION_DRAM0_PONG char dram0_pong[1024 * 64];
也就是:
dram0_ping:内部 DRAM ping 缓冲区
dram0_pong:内部 DRAM pong 缓冲区
之后根据 A55 共享配置判断是否开启 FFT:
if ((LIDAR_MODE_FFT == pSharedData->LiDARMode) ||
(LIDAR_MODE_FMCW == pSharedData->LiDARMode)) {
fft_en = 1UL;
}
然后初始化 Q8 IPC 日志,读取当前 Q8 对应的输出 buffer:
uint32_t output_addr[4] = {
pSharedData->Buffer0Address,
pSharedData->Buffer1Address,
pSharedData->Buffer2Address,
pSharedData->Buffer3Address,
};
根据 Q8_CORE_ID 选择:
output_sram = (volatile int16_t *)output_addr[Q8_CORE_ID];
例如:
Q8_0 -> Buffer0 = 0x00094040
Q8_1 -> Buffer1 = 0x0009C040
Q8_2 -> Buffer2 = 0x000A4040
Q8_3 -> Buffer3 = 0x000AC040
每个 Q8 只有一个稳定的 A55 输出 buffer。
———
6. iDMA 初始化
初始化函数:
static void initIDMA()
{
idma_init(IDMA_CHANNEL_0,
0,
MAX_BLOCK_16,
16,
0,
0,
NULL);
}
这里初始化的是当前 Q8 core 的 iDMA channel 0。
随后注册完成和错误中断处理函数:
idma_register_interrupts(
IDMA_CHANNEL_0,
(void*)idma_task_done_intr_handler,
(void*)idma_task_err_intr_handler);
当前两个回调为空:
void idma_task_err_intr_handler(int ch) {}
void idma_task_done_intr_handler(int ch) {}
因为当前程序不是通过中断回调推进状态,而是主动轮询:
idma_buffer_status(IDMA_CHANNEL_0)
所以真正的数据流程由主循环中的状态轮询驱动。
———
7. FFT descriptor 的建立
FFT 模式下执行:
setup_adc_to_dram_desc();
函数内部:
idma_init_loop(
IDMA_CHANNEL_0,
fft_desc_buffer_0,
IDMA_1D_DESC,
NUM_DESCS_FFT,
NULL,
NULL);
当前:
#define NUM_DESCS_FFT 2
然后建立两个 descriptor:
idma_add_desc(
fft_desc_buffer_0,
(uint8_t *)dram0_ping,
fft_marker_0_0,
XFER_SIZE,
0);
idma_add_desc(
fft_desc_buffer_0,
(uint8_t *)dram0_pong,
fft_marker_0_0,
XFER_SIZE,
0);
因此 descriptor 链表为:
descriptor 0:
source = 硬件 FFT buffer
destination = dram0_ping
size = 8192 bytes
descriptor 1:
source = 硬件 FFT buffer
destination = dram0_pong
size = 8192 bytes
在下一批处理开始时,descriptor loop 会重新初始化。
———
8. 为什么 FFT 也需要 ping/pong
这里的 ping/pong 有两个作用。
8.1 满足官方 iDMA loop 的工作方式
当前版本使用:
#define NUM_DESCS_FFT 2
官方 FFT 路径也是建立两个 descriptor:
FFT -> dram0_ping
FFT -> dram0_pong
在当前芯片的 iDMA loop 实现中,单 descriptor loop 可能长期处于 busy 状态,导致:
while (idma_buffer_status(cur_chan) == num_descs) {
}
一直不退出。
之前单 descriptor 版本的日志是:
status=1 descs=1 chans=1
之后没有任何 Q8 done。
恢复两个 descriptor 后,日志变成:
status=2 descs=2 chans=1
并能够继续运行。
因此,两个 descriptor 不只是为了性能,也是当前硬件/iDMA loop 正常推进所需的形式。
8.2 避免当前批次搬运时覆盖正在使用的数据
如果只有一个内部 DRAM buffer,流程会变成:
FFT -> dram
dram -> A55 output
下一次 FFT -> 同一个 dram
这样下一次 FFT 可能在上一次 dram -> A55 output 尚未完成时覆盖内部 DRAM,造成数据竞争。
使用两个 buffer 后:
FFT -> dram0_ping
dram0_ping -> A55 output
同时或随后:
FFT -> dram0_pong
dram0_pong -> A55 output
当前代码通过 buf_type 选择最终搬运哪个内部 buffer:
int buf_type = BUF_PING;
每次完成一批之后:
buf_type = 1 - buf_type;
切换关系是:
第 1 批:使用 dram0_ping
第 2 批:使用 dram0_pong
第 3 批:使用 dram0_ping
第 4 批:使用 dram0_pong
…
———
9. 第一阶段:FFT/ADC → 内部 DRAM
主循环入口:
while (1) {
setup_adc_to_dram_desc();
num_acqs = 0;
cur_chan = 0;
FFT 模式下调度两个 descriptor:
idma_schedule_desc_fast(
IDMA_CHANNEL_0,
NUM_DESCS_FFT);
也就是

