简介:这是一份面向信号处理学习者和嵌入式开发者的MUSIC算法完整实现资源包,涵盖从理论推导到工程落地的关键环节。资源以MATLAB仿真与C语言移植为主线,系统讲解算法原理、子空间分解、谱函数构造与信号源方向估计步骤,并给出从MATLAB到C代码的移植要点。包内共263个文件,核心为C源代码与头文件,配合MATLAB脚本和数据文件便于验证算法;另含编译生成的mexw64动态库及工程配置文件,可在常见环境中直接运行。资源包整体约4.86MB,已有1269人学习。对于需要快速上手MUSIC算法、或在资源受限硬件上实现DOA估计的读者,这份资源提供了可读性良好的源码和清晰的项目结构,能显著节省从理论到实现的调试时间。
1. MUSIC算法C语言+MATLAB:一套从仿真到嵌入式都能跑的DOA估计方案
一个8阵元的均匀线阵,两个同频信号从-20°和35°方向同时打过来,常规波束形成的主瓣宽到分不清谁是谁,MUSIC算法的空间谱却能画出两根锐利的谱峰。这就是高分辨DOA估计里最常被提起的方法。MUSIC的核心逻辑很简洁:把接收数据的协方差矩阵做特征分解,用噪声子空间构造空间谱,谱峰对应的角度就是来波方向。而这个标题把C和MATLAB放在一起,本质是在解决一件事——MATLAB负责把算法流程和参数验证跑通,C负责把复数矩阵运算、特征分解搬进没有科学计算环境的嵌入式平台。这篇文章面向要做声源定位、雷达测向、无线电测向的工程师和在校生,从原理讲到可复现代码,再讲参数怎么调、哪些地方容易翻车。
2. MUSIC算法原理里的两个关键点:为什么导向矢量能筛出方向
2.1 从阵列接收模型到协方差矩阵:MUSIC在算什么
先建立接收模型。一个M阵元的均匀线阵,阵元间距为d,有K个远场窄带信号从θ₁, θ₂, …, θ_K方向入射,某时刻t的接收向量x(t)可以写成:
x(t) = A(θ) s(t) + n(t)
其中A是M×K的方向矩阵,第k列是导向矢量a(θ_k) = [1, e^(-j2πd·sinθ_k/λ), …, e^(-j2π(M-1)d·sinθ_k/λ)]^T,s(t)是信号幅度向量,n(t)是噪声。这里每一个导向矢量都对应一个“从某个方向来的单位幅度信号在各阵元上形成的相位差”。MUSIC要做的,就是反着用这个相位差:从接收数据里反推出到底有哪些导向矢量存在于混合信号中。
再看协方差矩阵R = E[x(t) x^H(t)]。把接收模型代进去,展开后得到:
R = A R_s A^H + σ² I
其中R_s是信号的协方差矩阵,σ²是噪声功率,I是单位阵。这个式子的结构很关键:前半部分A R_s A^H是信号贡献的,它的秩最多是K(因为A是M×K,R_s是K×K);后半部分σ²I是均匀分布在所有M个维度上的。也就是说,R包含了K个“特征值变大了”的方向和一个均匀的噪声基底。顺带说一句选型理由:常规波束形成的分辨率受瑞利限限制,两个角度差小于阵列波束宽度的信号是糊在一起的;MUSIC因为用子空间分解把信号和噪声分开,理论上能做到远超瑞利限的高分辨,这也是它在DOA估计里长期是首选的原因。ESPRIT虽然不需要谱搜索,但要求阵列有旋转不变结构,均匀线阵之外的自由度受限,不像MUSIC对阵列形式那么宽容。
这一模型里有两个隐含前提需要记牢:信源数K必须小于阵元数M;信号之间不能完全相干。后面第5章会看到,这两个前提一旦被破坏,MUSIC的表现会非常难看。
2.2 特征分解与空间谱:为什么谱峰就是来波方向
对R做特征分解,得到R = E Λ E^H,其中Λ是对角矩阵,对角线上是特征值λ₁ ≥ λ₂ ≥ … ≥ λ_M。理想情况下,前K个特征值对应信号子空间,它们显著大于σ²;后M-K个特征值全部等于σ²,对应噪声子空间。特征值可不可靠,决定整条链路的结果——这是MUSIC算法里最需要敬畏的一步,因为它直接决定你取的是哪个子空间。
噪声子空间的物理意义是:它张成的空间与所有信号导向矢量正交。也就是说,如果某个假想角度θ是真实的来波方向,那么导向矢量a(θ)投影到噪声子空间U_n上应该几乎是零。反过来,如果θ不是来波方向,投影就会有明显的非零值。MUSIC就利用这个正交性构造空间谱:
P(θ) = 1 / [a^H(θ) U_n U_n^H a(θ)]
分母是导向矢量在噪声子空间上投影的模方。当θ等于真实来波方向时,分母趋近于零,P(θ)会出现一个尖锐的峰。实际计算里因为噪声和有限快拍,分母不会严格为零,而是变成一个很小的数,所以谱图上会出现明显的尖峰。谱峰搜索就是在角度网格上把P(θ)一个个算出来,然后找局部极大值。
整个流程排下来是:构造协方差矩阵 → 特征分解 → 确定信号数K → 取后M-K列特征向量作为U_n → 角度网格上算空间谱 → 找峰。步骤不多,但每一步都有参数和边界条件要卡。第3章先用MATLAB把这条链路跑通,后面C语言只是把同一个链路换一种实现方式,概念上不增加新东西。
3. MATLAB先行验证:把MUSIC算法跑通并摸清参数敏感度
3.1 用MATLAB跑通MUSIC最小仿真:关键脚本骨架
在做C语言移植之前,我习惯先写一份尽量小的MATLAB脚本,把算法链路验证一遍。这个脚本的目的不是功能丰富,而是参数清晰、可复现,方便后续逐行翻译成C。下面这份骨架脚本是均匀线阵加MUSIC谱估计的最小可用版本。
% DOA_MUSIC_demo.m % 最小可见示例:均匀线阵 + MUSIC 空间谱估计 % 参数区 M = 8; % 阵元数 d = 0.5; % 阵元间距,以波长为单位,0.5 即半波长 K = 2; % 信源数(假设已知,实测需估计) N = 200; % 快拍数 SNR_dB = 10; % 信噪比 theta_true = [-20, 35]; % 真实来波方向(度) % 生成阵列接收数据 lambda = 1; % 归一化波长 array_pos = (0:M-1)' * d * lambda; A = exp(-1j * 2*pi * array_pos * sind(theta_true)); S = (randn(K, N) + 1j*randn(K, N)) / sqrt(2); X = A * S; noise = (randn(M, N) + 1j*randn(M, N)) / sqrt(2); X = X + 10^(-SNR_dB/20) * noise; % 协方差矩阵与特征分解 R = X * X' / N; [E, D] = eig(R); [~, idx] = sort(diag(D), 'ascend'); E = E(:, idx); Un = E(:, 1:M-K); % 噪声子空间:前 M-K 个小特征值对应列 % 空间谱搜索 theta_grid = -90:0.1:90; spec = zeros(size(theta_grid)); for i = 1:numel(theta_grid) a = exp(-1j * 2*pi * array_pos * sind(theta_grid(i))); spec(i) = 1 / (a' * (Un * Un') * a); end spec = 10*log10(spec / max(spec)); plot(theta_grid, spec); grid on; xlabel('角度 (deg)'); ylabel('归一化谱 (dB)'); title('MUSIC 空间谱');逻辑说明:方向矩阵A用sind,保证角度单位是度而不是弧度,这是MATLAB里最容易出错的地方;S和noise用复高斯分布,功率归一化到1,加噪时用10^(-SNR_dB/20)把噪声调整到对应信噪比;eig返回的特征值按升序排列,特征向量列顺序和特征值一一对应,所以排序后直接取前M-K列就是噪声子空间;谱函数里a'是共轭转置,对应理论公式中的a^H。
参数说明:M=8是常见的折中值,8阵元能分辨的角度间隔大约在十几度以内,M太小则谱峰变胖;d=0.5是半波长间距,这个值在大多数线性阵列里是最优选择,后面会详细说;N=200表示用了200个时间快拍,快拍越多协方差估计越接近真实R;K=2表示信源数已知,实际场景里这个值要用信息论准则或特征值阈值去估,不能拍脑袋写死。
3.2 参数怎么调:阵元数、快拍数、信源数估计与谱峰插值
脚本能跑通之后,真正的工作是调参数。下面这个表格是我做模拟项目X时总结出来的经验区间,写在这里给读者一个起点,不是死规矩。
| 参数 | 常用范围 | 经验说明 |
|---|---|---|
| 阵元数M | 8~16 | M越大分辨率越高,运算量和硬件成本也越高;低于6时MUSIC优势不明显 |
| 阵元间距d | 0.4λ~0.6λ | 半波长最优;小于0.4λ有效孔径变小,大于0.5λ会出现栅瓣 |
| 快拍数N | 100~500 | 低于50时协方差估计噪声大,谱峰会抖动;高于500收益递减 |
| 信源数K | 特征值阈值/AIC/MDL估计 | K偏大把噪声当信号,谱峰消失;K偏小丢信号,出现假峰 |
| 搜索步长 | 0.1°~0.5° | 步长太粗会量化误差;太细纯增加计算量,可配合插值获得亚网格精度 |
先说信源数估计。实测时K是未知的,我一般先看特征值分布:把协方差矩阵的特征值从大到小排列,如果出现明显的“悬崖”——比如前两个是几十,后面全是1左右——那K就取悬崖前的数量。更严谨的做法是用AIC或MDL准则,MATLAB里自己写也不难,就是加一个对数似然项再加惩罚项。但需要注意的是:低信噪比时特征值悬崖不明显,K的估计会摇摆,这时候宁可把K估小一点,再配合谱峰数量人工确认,也不要估大。
再说一个能直接提升角度精度的技巧:抛物线插值。搜索步长0.1°已经挺细,但真实来波方向几乎不可能恰好落在网格点上,峰顶附近三个点的形状近似抛物线,用这三个点拟合顶点位置,可以把角度精度从网格量化水平提升到远小于步长的水平。代码就是把谱峰位置idx附近的三个值取出来,套顶点公式:
% 假设 idx 是谱峰所在网格索引,step 是搜索步长 y1 = spec(idx-1); y2 = spec(idx); y3 = spec(idx+1); offset = 0.5 * step * (y1 - y3) / (y1 - 2*y2 + y3); theta_est = theta_grid(idx) + offset; % 插值后的来波方向这一段做完,MATLAB这边的验证闭环就成立了。无论是改阵元数、间距还是信噪比,都能在这个脚本里快速看到谱图和估计角度的变化。这套流程跑熟之后,再去写C代码就不会盲目——你已经知道每一步应该产出什么形状的数据。另外,如果仿真里发现两个相干信号时MUSIC失效,需要在MATLAB里加空间平滑再做特征分解,这个坑放在第5章详细讲。
4. C语言重写核心:复数运算、Jacobi特征分解与谱搜索的落地
4.1 C里没有eig():Jacobi特征分解的移植思路
到了C语言这一步,最大的鸿沟是:MATLAB一行eig(R)搞定的事,C里没有现成等价物。解决思路通常有三条:集成LAPACK这类线性代数库、用MATLAB Coder生成C代码、手写特征分解。前两种在特定平台可用,但很多DSP裸机环境和资源受限的嵌入式平台上,LAPACK太重,MATLAB Coder生成的代码又依赖运行时库,所以手写一个轻量Jacobi是常见兜底方案。Jacobi方法比QR实现简单,适合中小规模实数/复数对称矩阵,M=8~16这个规模下收敛速度完全够用。
先看复数运算的基础设施。MUSIC全程都在复数域工作,协方差矩阵是Hermitian的,所以复数结构体和基本运算是绕不开的地基。
typedef struct { float re; float im; } cmplx; static inline cmplx cplx_mul(cmplx a, cmplx b) { cmplx r; r.re = a.re * b.re - a.im * b.im; r.im = a.re * b.im + a.im * b.re; return r; } static inline cmplx cplx_conj(cmplx a) { cmplx r = { a.re, -a.im }; return r; } static inline cmplx cplx_add(cmplx a, cmplx b) { cmplx r = { a.re + b.re, a.im + b.im }; return r; }逻辑说明:三个函数分别是复数乘法、共轭、加法,全部内联以省掉函数调用开销;乘法按复数运算法则展开,共轭只把虚部取负。这些函数在协方差构建和谱搜索里会被高频调用,保持内联很关键。
参数说明:这里用float而不是double,是考虑到嵌入式平台浮点性能;但要注意float在特征分解里可能不够稳,第5章会专门讲这一点。如果目标平台硬件浮点较弱,可以改成Q格式定点,最后第6章补充这条路。
接下来是Jacobi特征分解的核心骨架。下面的代码以实对称矩阵为例,展示找主元、算旋转角、收敛判断的逻辑;Hermitian复数矩阵要做的事情一样,只是把实数运算换成复数运算、转置换成共轭转置。
/* Jacobi 主循环骨架:以实对称矩阵为例 */ #define M_MAX 16 #define MAX_ITER 50 #define EPS 1e-6f void jacobi_eigen(float A[M_MAX][M_MAX], int n, float V[M_MAX][M_MAX], float eigs[M_MAX]) { // 初始化 V 为单位阵 for (int i = 0; i < n; i++) for (int j = 0; j < n; j++) V[i][j] = (i == j) ? 1.0f : 0.0f; for (int iter = 0; iter < MAX_ITER; iter++) { // 1. 找非对角元最大绝对值,作为本次旋转的目标 p、q float max_off = 0.0f; int p = 0, q = 1; for (int i = 0; i < n; i++) for (int j = i + 1; j < n; j++) if (fabsf(A[i][j]) > max_off) { max_off = fabsf(A[i][j]); p = i; q = j; } if (max_off < EPS) break; // 收敛 // 2. 计算旋转角 float theta = 0.5f * atan2f(2.0f * A[p][q], A[q][q] - A[p][p]); float c = cosf(theta), s = sinf(theta); // 3. 更新 A,只涉及 p、q 行与 p、q 列 for (int k = 0; k < n; k++) { if (k == p || k == q) continue; float a_kp = A[k][p]; float a_kq = A[k][q]; A[k][p] = c * a_kp + s * a_kq; A[k][q] = -s * a_kp + c * a_kq; A[p][k] = A[k][p]; A[q][k] = A[k][q]; } float a_pp = A[p][p], a_qq = A[q][q], a_pq = A[p][q]; A[p][p] = c*c*a_pp + 2*s*c*a_pq + s*s*a_qq; A[q][q] = s*s*a_pp - 2*s*c*a_pq + c*c*a_qq; A[p][q] = 0.0f; A[q][p] = 0.0f; // 4. 累乘到特征向量矩阵 V:同样只更新 p、q 两列 for (int i = 0; i < n; i++) { float v_ip = V[i][p]; float v_iq = V[i][q]; V[i][p] = c*v_ip + s*v_iq; V[i][q] = -s*v_ip + c*v_iq; } } for (int i = 0; i < n; i++) eigs[i] = A[i][i]; }逻辑说明:每次迭代挑当前矩阵里绝对值最大的非对角元,用Givens旋转把它消掉,同时把旋转累乘到V矩阵上;重复这个过程直到所有非对角元小于阈值。这里展示的是实对称版本,处理协方差矩阵R时,因为R是Hermitian复数矩阵,直接用这套代码是不行的——需要处理复数旋转,或者把复数Hermitian矩阵映射成等价的实对称矩阵来套这套骨架。
参数说明:MAX_ITER=50对M≤16的矩阵一般够用,但低信噪比时协方差矩阵病态,可能到上限还没收敛,所以迭代结束最好加一个检查:重新算一下非对角元最大值,如果还在EPS之上,要么加大迭代次数,要么考虑用double重算。这一章的真实目标是让你有能力把MATLAB里eig的行为在C里复刻出来,而不是直接造一个通用线性代数库。
4.2 协方差矩阵构建与谱搜索:C语言版的关键循环
特征分解之后,还有两个核心循环要写:协方差矩阵构建和空间谱搜索。协方差构建这一步藏着很多新手翻车点,所以我直接给出利用共轭对称性只算上三角的写法,既省一半计算量,又能保证矩阵是严格Hermitian的。
/* 构造协方差矩阵 R = X * X^H / N,X 维度 M*N */ cmplx R[M_MAX][M_MAX]; for (int i = 0; i < M; i++) { for (int j = i; j < M; j++) { // 只算上三角 cmplx acc = {0.0f, 0.0f}; for (int n = 0; n < N; n++) acc = cplx_add(acc, cplx_mul(X[i][n], cplx_conj(X[j][n]))); acc.re /= N; acc.im /= N; R[i][j] = acc; R[j][i] = (cmplx){ acc.re, -acc.im }; // 共轭填充下三角 } }逻辑说明:外层循环让j从i开始,只计算上三角和下三角共轭填充,既保证Hermitian对称性,又让计算量减半;最内层是X[i][n]乘以X[j][n]的共轭,对应公式里的x_i x_j^*,注意共轭位置不能反。
参数说明:这个实现把N个快拍做累加,最后再除N。如果N很大,acc用float累加会有舍入问题,建议这里用double做累加器,算完再存回cmplx的float字段;就算矩阵元素后续用float存储,累积过程保持double能明显减少误差。M_MAX和N要提前用宏定义好,比如N_MAX=512、M_MAX=16,嵌入式上静态二维数组比malloc安全得多。
谱搜索的C代码是整个算法里运算量最集中的部分。一个优化点是先把Un * Un^H算成一个M×M投影矩阵P,这样每个角度网格点就不用反复做矩阵乘,而是只做一个二次型a^H P a,计算量从O(M²(M-K))降到了O(M²)。
/* 事先计算投影矩阵 P = Un * Un^H,维度 M*M */ cmplx P[M_MAX][M_MAX]; for (int i = 0; i < M; i++) for (int j = 0; j < M; j++) { cmplx sum = {0.0f, 0.0f}; for (int k = 0; k < M - K; k++) sum = cplx_add(sum, cplx_mul(Un[i][k], cplx_conj(Un[j][k]))); P[i][j] = sum; } /* 空间谱搜索,角度范围 -90° ~ 90°,步长 0.1° */ #define DEG2RAD 0.01745329252f float peak_val = -1.0f; float peak_angle = 0.0f; for (int g = 0; g <= 1800; g++) { float theta = -90.0f + 0.1f * g; float phase = 2.0f * PI * d * sinf(theta * DEG2RAD); cmplx a[M_MAX]; for (int m = 0; m < M; m++) { a[m].re = cosf(-phase * m); a[m].im = sinf(-phase * m); } cmplx v = {0.0f, 0.0f}; for (int i = 0; i < M; i++) for (int j = 0; j < M; j++) v = cplx_add(v, cplx_mul(cplx_conj(a[i]), cplx_mul(P[i][j], a[j]))); float power = 1.0f / (v.re * v.re + v.im * v.im); if (power > peak_val) { peak_val = power; peak_angle = theta; } } printf("DOA estimate: %.2f deg\n", peak_angle);逻辑说明:导向矢量在C里直接用cos/sin展开复指数,避免写复杂的指数函数;投影矩阵P先算好后,谱搜索每个角度只剩一个M×M的二次型累计;v.rev.re+v.imv.im就是|a^H P a|²,理论上P是Hermitian的,v的虚部应该接近于零,实际会有浮点残余,取模方最稳妥。
参数说明:d=0.5表示半波长间距,phase里的d是相对波长比例而不是绝对长度,这点和MATLAB脚本保持一致;1801是-90°到90°以0.1°为步长的网格点数;这里的M、K、Un都来自特征分解的输出,如果上一步特征向量列顺序没排对,这一整段谱搜索就全废了。C代码跑出来后,建议先把P矩阵导出来和MATLAB里的Un*Un'对比一下,最大误差在1e-4以内再接谱搜索,这能省掉大量联调时间。
5. MUSIC算法C+MATLAB联调的5个常见坑与排查思路
5.1 特征值排序错位:谱峰直接消失
现象:同一份数据在MATLAB里跑出的MUSIC谱有两个尖锐峰,C实现画出来什么也没有,或者谱乱成一团。 原因:MATLAB的eig()默认把特征值升序排列,特征向量列一一对应;手写Jacobi只保证把矩阵推向对角化,特征值在最终对角线上的顺序完全取决于迭代过程中的旋转顺序。如果C代码取前M-K列当噪声子空间,而Jacobi输出的特征值不是升序,你取到的就可能是信号子空间的一部分,谱当然废了。 解决:在C里对特征值做一次选择排序,同时记录原始索引,把特征向量列也按同样的顺序重排。排序后加一个自检:打印最小M-K个特征值,量级应该接近噪声方差;如果发现有几个特征值明显偏大,那排序逻辑大概率有问题。
5.2 谱峰有网格量化误差:角度差半度以上
现象:真实来波方向是10.2°,仿真谱峰落在10.5°网格上,步长从0.5°改到0.1°也只是变成10.1°或10.3°,始终对不齐。 原因:两层误差叠加。第一层是搜索网格本身的量化误差;第二层更重要,是有限快拍下样本协方差与真实协方差的偏差,导致MUSIC估计本身有方差。只改步长解决不了第二层。 解决:快拍加到200以上,信噪比低于0dB时加到500;搜索步长取0.1°即可,不必更细;谱峰附近三个点做抛物线插值,把峰顶位置算到亚网格精度。这两招配合下来,模拟项目X里10.2°的来波方向通常能估到10.18°~10.25°以内。
5.3 协方差矩阵不对称:空间谱出现直流基底
现象:C里把R打印出来检查,发现R[1][2]和R[2][1]不满足共轭关系;谱图整体抬高,谱峰相对高度被压低。 原因:最常见是把共轭转置写丢了,用了X[i][n] * X[j][n]而不是X[i][n] * conj(X[j][n]);其次是float累加误差累积,让本应相等的共轭对出现偏差。 解决:按第4章给的做法只算上三角,再共轭填充下三角,天然保证Hermitian对称;累加器用double,最后存回float。这一步是最容易排查的,只要写一个断言式的自检,检查max(|R[i][j] - conj(R[j][i])|),超阈值直接报错,就能把问题挡在特征分解之前。
5.4 两个相干信号让MUSIC只出一个峰
现象:两个信号频率相同、相位锁定,MUSIC谱上只有一个峰,或者两个峰粘连扭曲,怎么调参数都没用。 原因:相干信号让信号协方差矩阵R_s秩亏,本应有K个大特征值,实际只剩下不到K个,噪声子空间维度变大,某个真实信号方向也被算进了噪声子空间。 解决:采用前后向空间平滑。把M阵元的阵列划分成L个重叠子阵,每个子阵有m_sub个阵元,用每个子阵的数据单独算协方差再取平均;前向平滑能恢复信号秩,后向平滑再翻倍利用数据。代价是有效阵元数从M降到m_sub,分辨率略有下降。这是实测里最常见的“翻车点”,做声学阵列时尤其多,两块同频喇叭就容易让MUSIC失灵。
5.5 float跑Jacobi不收敛:谱全是毛刺
现象:同一份数据,double版本C代码正常,float版本谱图到处是尖刺;或者Jacobi在50次迭代内不收敛,特征向量不正交。 原因:float只有约7位有效数字,协方差矩阵大特征值和小特征值动态范围大,Jacobi旋转变换的舍入误差在迭代中累积,导致收敛变慢甚至不收敛。 解决:特征分解和协方差矩阵构建用double,最后只有谱搜索阶段保持float,这样省下来的时间都花在刀刃上;如果目标平台必须全程float,加迭代上限到100,并在迭代结束后检查V^H V与单位阵的偏差,偏差过大就做一次Gram-Schmidt正交化修正。这个坑我最早是在某DSP平台上遇到的,一开始以为是代码写错了,后来发现就是精度问题,double版一跑立刻正常。
6. 从仿真到嵌入式:DSP上跑MUSIC的定点化与性能验证
把C代码从PC搬到DSP平台,声音往往会变:浮点运算太慢。硬件浮点单元不存在时,float代码在定点DSP上会被编译器翻译成一堆软浮点库调用,一个谱搜索跑出几十万周期很正常。我一般会做两件事:先剖热点再定点化。先用性能计数器测,通常热点集中在谱搜索的a^H P a二次型,特征分解反而只占总时间的10%~20%。所以先优化谱搜索,收益最大。
第一招是查表法替代sin/cos。导向矢量里的exp(-j·2πd·sinθ·m)在-90°到90°的网格上被反复调用,可以先建一个正弦表,步长0.1°对应1801个点,表长1801个float,驻留内存没问题。查表加线性插值,精度损失远小于定点化本身。第二招是把投影矩阵P提前算好并转成定点。常见做法是Q15格式,把P的每个元素缩放到[-1,1)区间并乘32767存成short;谱搜索算二次型时,按定点乘累加,最后再缩放到浮点域做谱峰比较。注意累加器要用32位或40位宽,否则中间结果会溢出。
性能验证不能只测运行时间。我的习惯是:先把float版C代码的结果导出,和MATLAB谱做逐点对比,最大误差在1e-3以内可以认为移植正确;再把定点版谱和float版谱对比,谱峰角度误差控制在±1°以内就达标。如果角度差超过这个量,优先检查投影矩阵的定点缩放比例是不是把数值压得太小,其次查查表插值步长。
最后分享一个我自己的教训:第一次把MUSIC往DSP上搬时,我在特征分解和谱搜索上花了两周优化编译器选项,后来发现最大的速度瓶颈是我在谱搜索内层循环里重复计算了导向矢量,把导向矢量预先算好存表之后,整个搜索时间直接砍了七成。MUSIC这类算法的优化顺序一定是先砍重复计算,再谈定点化和查表。希望帮到你。
本文还有配套的精品资源,点击获取