当前位置: 首页 > news >正文

基于矢量水听器的潜标探测系统——信号处理部分

一、项目概述

基于矢量水听器的潜标探测系统_带水听器潜标-CSDN博客

前篇如上。

依旧是毕设项目,依旧是基于AD采集信号,将信号搬运到PS端的DDR3上,在ARM端裸机开发进行信号处理(FIR、FFT、矢量测向),将处理后的结果发送到上位机,并可以存储到SD卡内。

调了小一周,终于调好了。(累!)

硬件选择依旧是ZYNQ7010+AD7606并行采集。

二、PL端设计调整

基于矢量水听器的潜标探测系统_带水听器潜标-CSDN博客

根据前篇,我设计的是AD输出四路通道,后面接了四路FIFO,然后再接了四路DMA,最终通过HP接口将数据传到PS端的DDR3中。

周四我们组的硬件老师来了,我顺便问了一下,老师说就算是四个DMA,最终也是从一个HP接口传输数据,存到DDR3中反倒不合适,不如从一开始AD7606就交织输出,用一个FIFO缓存,再通过一个DMA搬运到DDR3中。

老师也建议,可以保留四路FIFO,但水声信号的数据量并不是很大,FIFO的深度可以减小一些,节省一些逻辑资源。同时,老师建议,ZYNQ7010的资源比较少,在PL端做多做到FIR,其余的挪到PS端来做。

2.1问题1:

我根据老师的建议做了一下,也就是AD7606→FIFO转AXI Stream→FIR→DMA IP核→DDR3,但在测试阶段,出现了解析错误,经查询是设置FIR通道的问题,如果我使用FIR多通道,例如我设置了四个通道的FIR,那如果我只输入三通道的实际数据,会发生数据错位。

原因:FIR IP核内部是严格按照TDM(时分复用)来处理交织数据的,如果只输入三路通道,从第四个时钟开始,CH0的数据会被IP核误判成CH3,会造成所有通道的滤波完全乱序,输出波形毫无规律,会造成数据的交叉污染。

所以我思来想去,还是决定在ARM端做FIR、FFT与矢量测向,虽然没有办法利用PL端的数据加速,但相较于在PL端做,也更加的方便。

2.2问题2:

还有一处修改:之前我灵机一动,在FIFO转AXI Stream模块中将数据拼接成了高16位全是0,低16位是数据,也就是在内存中是这个样子的:

内存: [0x0000_CH1] [0x0000_CH2] [0x0000_CH3] [0x0000_CH4] ...

在实际对信号进行信号处理过程中,这部分的解交织代码总是写不好,于是我决定保留最初的“两个16bit样本打包成一个32bit字”的数据格式。

原因:原始FIFO是16bit写、32读。AD7606每采一个16bit样本写进FIFO,FIFO内部每攒够2个16bit样本,就拼接成一个32字输出。也就是dout是32bit,里面同时包含了两个16bit AD样本。

assign M_AXIS_TDATA = {dout[15:0], dout[31:16]};

{dout[15:0], dout[31:16]}是半字交换(调整字节序),确保DMA写到DDR后,PS端解交织时拿到的通道顺序是对的。

故,我现在的PL端的BD图如下所示:

具体内容没怎么变,大家参考之前发的两篇文章就可以捏!

三、PS端信号处理代码实现

在之前的文章里,我已经在PS端利用lwIP实现TCP数据传输,以及SD卡的读写,这一次主要实现信号处理算法。

3.1解交织

想要在ARM端进行信号处理,要先对存入DDR3的数据进行解交织,故代码逻辑为:

uint32_t word = src[word_idx + w]; uint16_t sample_hi = (uint16_t)(word >> 16); // CH1, CH3... uint16_t sample_lo = (uint16_t)(word & 0xFFFF); // CH2, CH4...

解交织后,4路数据被拆分到独立地址:

  • CH1 →0x02000000

  • CH2 →0x02100000

  • CH3 →0x02200000

  • CH4 →0x02300000

3.2信号处理部分

在DMA传输完成后,主循环依次调用:

SignalProcess_ProcessAllChannels(actual_fs); // 4路FFT VectorProcessing_Execute(actual_fs, result); // 互谱+DOA send_spectrum_data(); // 发送频谱 VectorProcessing_SendResult(); // 发送方位结果

首先,我们要逐通道进行FFT处理,主要使用CMSIS-DSP的实数FFT来处理。

3.2.1初始化

先对FFT进行初始化:

arm_status status = arm_rfft_fast_init_f32(&S, FFT_SIZE);

作用是预计算旋转因子,填充结构体S内部查找表,这部分只执行一次,之后所有的1024点FFT复用该实例,避免重读计算cos/sin。

再对FIR滤波器进行初始化:

arm_fir_init_f32(&S_fir[i], FIR_NUM_TAPS, fir_coeffs, fir_state[i], FFT_SIZE);

我设计的FIR滤波器参数如下(仅作为参考,请大家结合自己的实际来配置参数):

20ksps四通道带通FIR滤波器: 参数设计: 响应类型:带通 设计方法:FIR等波纹 采样率:20000HZ 通带下边界:50HZ,通带上边界:5000HZ 阻带下边界:20HZ或30HZ,阻带上边界:5500HZ 通带纹波:1dB 阻带衰减:60dB 阶数:128阶

这么设计滤波器的好处在于:1)50HZ以下的噪声能压掉,2)5KHZ内有效噪声基本保留,3)四通道一致性比较好

其中,fir_state是延迟线缓冲区,大小 =FIR_NUM_TAPS + FFT_SIZE = 129 + 1024 = 1153。

3.2.2单通道FFT

在单通道FFT中,第一步,要将ADC原始值转换成电压。

int16_t raw = (int16_t)adc_data[i]; // 16bit 有符号 fft_input[i] = ((float32_t)raw / 32768.0f) * 5.0f;

AD7606是±5V、16bit双极性,故+32767对应+5.0V,-32768对应-5.0V,故应该按照代码的方式进行转换。

再进行FIR滤波:

arm_fir_f32(&S_fir[ch_idx], fft_input, filtered_input, FFT_SIZE);

再进行RMS计算:

for (int i = 0; i < FFT_SIZE; i++) { sum_sq += filtered_input[i] * filtered_input[i]; } arm_sqrt_f32(sum_sq / FFT_SIZE, &result->rms_value);

这一步必须在FFT之前,我将这部分挪到FFT之后做,输出的值与真实值不符合,后来才知道原因:因为arm_rfft_fast_f32会原地破坏输入缓冲区的filtered_input,把它当成临时工作区。

再执行FFT:

arm_rfft_fast_f32(&S, filtered_input, fft_output, 0);

参数0代表着正变换(也就是时域→频域),输入1024点实数时域信号,输出打包数据fft_output[1024]。

其中,arm_rfft_fast_f32对1024点实数输入的输出格式如下:

RFFT 输出格式
数组索引内容物理意义
fft_output[0]R0DC 分量(实部)
fft_output[1]R512Nyquist 点(实部)
fft_output[2]R1频点 1 实部
fft_output[3]I1频点 1 虚部
fft_output[4]R2频点 2 实部
fft_output[5]I2频点 2 虚部
.........
fft_output[1022]R511频点 511 实部
fft_output[1023]I511频点 511 虚部

DC和Nyquist两个纯实数点被CMSIS塞到了最前面,[0]是DC,[1]是Nyquist,从[2]开始才是正常的(R1,I1)交错。

再计算幅度谱,将其从双边谱转换为单边谱:

// DC 分量 fft_magnitude[0] = fabsf(fft_output[0]) / FFT_SIZE; // 中间频点(1 ~ 511) for (int i = 1; i < FFT_SIZE/2; i++) { float32_t real = fft_output[2*i]; float32_t imag = fft_output[2*i + 1]; fft_magnitude[i] = (sqrtf(real*real + imag*imag) * 2.0f) / FFT_SIZE; } // Nyquist 点 fft_magnitude[FFT_SIZE/2] = fabsf(fft_output[1]) / FFT_SIZE;

计算其频率分辨率:

float32_t freq_resolution = sample_rate / FFT_SIZE;

假设采样率Fs = 2000 Hz,则分辨率为2000/1024 ≈ 1.953 Hz/ bin,峰值频率为peak_idx × 1.953 Hz。

对峰值进行检测

arm_max_f32(&fft_magnitude[1], FFT_SIZE/2 - 1, &peak_mag_temp, &peak_idx); peak_idx += 1;

这一步跳过了DC,避免0HZ直流干扰。

互谱计算的代码如下:

// 互谱实部:Re{ P*(f) · V(f) } = p_real*v_real + p_imag*v_imag float32_t cross_real = p_real * v_real + p_imag * v_imag;

这一步是矢量水听器DOA估计的数学基础,p是声压通道(标量),V是振速通道(矢量,分为Vx/Vy/Vz),P* · V的实部正比于声强的流向,Ix = Re{P* · Vx}表示X方向的声能流,Iy = Re{P* · Vy}表示Y方向的声能流。

DOA估计代码:

azimuth_rad = atan2f(Iy_val, Ix_val);

声能流再水平面的投影方向就是声源方位角。

总结下来,FFT的数据流如下所示:

ADC原始值(uint16_t)
↓ 强制转 int16_t,映射到 ±5V
时域电压(float32_t[1024])
↓ arm_fir_f32 滤波
滤波后信号(float32_t[1024])
↓ arm_rfft_fast_f32 (正变换)
打包频域数据(float32_t[1024])
├─ [0] = DC (R0)
├─ [1] = Nyquist (R512) ← 你代码里复数提取漏了这步
└─ [2..1023] = (R1,I1)...(R511,I511)
↓ 解析 + 幅度计算
单边幅度谱(float32_t[513])
├─ [0] = DC 幅度
├─ [1..511] = 频点1~511幅度 (×2归一化)
└─ [512] = Nyquist 幅度
↓ arm_max_f32 搜峰
峰值频率/幅度 + RMS
↓ 存入 ch_results[4]
TCP发送 / 互谱计算 / SD存储

3.3矢量处理与DOA估计

首先统一目标频率,以Ix.peak_idx(互谱峰值频点)作为统一的目标频率bin,读取该频点处的互谱值:

float32_t Ix_val = Ix.cross_spectrum[target_bin]; float32_t Iy_val = Iy.cross_spectrum[target_bin]; float32_t Iz_val = Iz.cross_spectrum[target_bin];

根据公式tan(θ) = Iy / Ix来计算声能流在X-Y平面的夹角,也就是水平方向的方位角:

azimuth_rad = atan2f(Iy_val, Ix_val); azimuth_deg = azimuth_rad * 180.0f / PI; if (azimuth_deg < 0) azimuth_deg += 360.0f;

再根据垂直分量与水平合成幅度的夹角计算垂直方向的俯仰角:

horizontal_mag = sqrtf(Ix_val*Ix_val + Iy_val*Iy_val); elevation_rad = atan2f(fabsf(Iz_val), horizontal_mag);

3.3数据输出

3.3.1频谱数据发送

send_spectrum_data()为每个有效通道发送一帧:

  • 帧头:0x55 0xA5+ 命令字0x10+ 通道掩码

  • 通道头:0xAA+ 通道索引

  • 参数:peak_freq,peak_magnitude,rms_value(3 个 float)

  • 频谱数据:513 个 float(0~Nyquist 频率的幅度谱)

3.3.2矢量结果发送

VectorProcessing_SendResult()发送:

  • 帧头:命令字0x20

  • DOA 数据:azimuth_deg,elevation_deg,confidence,target_freq

  • 互谱峰值频率:Ix/Iy/Iz.peak_freq

  • 互谱数据:128 个 float(Ix.cross_spectrum的前 128 点)

4.总结

目前测试下来,这套代码是可以跑通的,且我利用信号发射器模拟信号发射输出,得到的结果也是符合预期的,今天太晚了先到这里,明天多用几种方案再来验证一下我的这套系统

还有一些在实现期间遇到的问题,等我有时间整理一下下......

累!

http://www.jsqmd.com/news/711419/

相关文章:

  • Go语言的上下文管理详解
  • DeepSeek V4大模型算法解析
  • Python 爬虫进阶技巧:Session 复用减少重复登录开销
  • LeetCode HOT100 - 寻找两个正序数组的中位数
  • ANI3DHUMAN:3D人体动画技术的自引导随机采样解析
  • 职场利器!OpenClaw 汉化版极简安装上手指南
  • 企业宣传短片,如何选对制作公司让品牌价值翻倍?
  • Windows AirPlay 2接收器终极方案:免费实现iOS设备投屏到Windows电脑
  • 2026年轻钢龙骨怎么选 实用干货帮你挑正规靠谱品牌
  • 5步掌握雀魂AI智能辅助工具:提升麻将水平的终极指南
  • YOLOv13涨点改进| WACV 2026 | 独家创新首发、Conv卷积改进篇 |引入SimConv相似卷积模块,实现自适应感受野调整,克服传统卷积固定卷积局限,助力小目标检测、图像分割等高效涨点
  • 基于非线性模型预测控制NMPC+QP求解器(qpOASES和qpDUNES)+ACADO工具包车辆自主导航、车道跟踪与避障控制(Matlab代码实现)
  • 《初学C语言》第三讲:printf函数和scanf函数
  • 2026年q2道路花箱选型技术推荐:不锈钢花箱,不锈钢镀锌板花箱,人行横道花箱,园林花箱,排行一览! - 优质品牌商家
  • 从Jupyter Notebook一键转生产沙箱:3步实现AI代码自动容器化+依赖锁定+网络策略注入(2026 Docker Desktop 4.32新功能深度拆解)
  • Trae入门
  • 软考高级系统架构设计师备考(二十三):软件工程—逆向工程、正向工程与需求工程
  • 2026浏览器TLS指纹与JA3/JA4协议指纹技术深度解析及实现方案
  • 人力资源咨询公司,人力资源改革,国企改革咨询,成都咨询公司,成都管理咨询公司,绩效咨询公司,优选指南! - 优质品牌商家
  • StitchFlow:基于AI的本地化UI生成工具,打通产品简报到可交付代码
  • 告别‘抓瞎’!用CAPL的RS232函数自动抓取MCU Log保姆级教程
  • 72W碳化硅SIC电源方案(24V3A,12V6A)LP8841SC+LP35118N全电压,过认证,六级能效( BOM,典型电路)
  • 机器学习参数与超参数:核心区别与调优实践
  • 3步快速解锁碧蓝航线全皮肤:Perseus补丁终极指南
  • 大语言模型在文档伪造检测中的创新应用与实践
  • G-Helper实战指南:华硕笔记本开源硬件控制与性能调优
  • 全国省市区 JSON数据
  • 拜读了顶会顶刊上这些论文,原来多模态特征融合是这么玩的
  • 大语言模型强化学习训练:BAPO算法解析与实践
  • 基于大模型的AI外呼系统:RAG与知识增强实践(三)