我发现一种有用的方法是生成两个相隔 90 度的参考波(我称它们为“正弦”和“余弦”),然后在相当短的时间内将输入波形与这些参考波的点积(比如 1/60 秒)输入的延伸。这将为您提供一个有点嘈杂的指标,表明您有多少输入频率与您的参考波同相或异相(使用两个参考波生成的值的平方和的平方根将是幅度)。如果窗口尺寸较小,您会注意到输出相当嘈杂,但如果您使用简单的 FIR 或 IIR 滤波器等过滤输出,您可能会得到相当合理的结果。
一个很好的技巧是生成两个幅度数:对于第一个,通过两轮滤波运行正弦和余弦幅度,然后计算平方和。第二,通过一轮滤波运行幅度,然后计算平方和,然后通过另一轮滤波运行。
两个幅度测量都会经历相同的延迟,但第一个比第二个更具选择性;因此,您可以非常清楚地判断频率是“正确开启”还是有点偏离。使用这种方法,可以快速检测 DTMF 音调,同时拒绝甚至偏离几赫兹的音调(偏离音调的音调在“松散”检测器上的接收比在紧密检测器上更强)。
示例代码:
双正弦相位,正弦频率;
void process_some_waves(double *input, int16 len,
双*正弦相位,双正弦频率,
双 *sine_result, 双 *cosine_result)
{
诠释我;
双相,sin_tot,cos_tot;
相位 = *正弦相位;
sin_tot = cos_tot = 0;
对于 (i=0; len > i; i++)
{
sin_tot += 输入[i] * sin(相位);
cos_tot += 输入[i] * cos(相位);
相位 += 正弦频率;
}
*sine_result = sin_tot;
*cosine_result = cos_tot;
*sine_phase = 相位;
}
/* 获取缓冲区中的第一个元素,并使用简单的高斯 resp 将其“涂抹”通过缓冲区。 */
void simple_fir_filter(double *buff, int buffsize)
{
诠释我;
对于 (i=buffsize-1; i>=2; i--)
buff[i] = (buff[i-1] + buff[i-2])/2;
}
#define FILTER_SIZE1 10
#define FILTER_SIZE2 8
#define SECTION_LENGTH 128
#define FREQ 随便
双 sine_buff1[FILTER_SIZE1], sine_buff2[FILTER_SIZE2];
双 cos_buff1[FILTER_SIZE1], cos_buff2[FILTER_SIZE2];
双组合_buff[FILTER_SIZE2];
双紧振幅,松振幅;
双参考相位;
无效句柄_some_data(双*输入)
{
/* 将结果放入过滤器缓冲区的第一个元素 */
process_some_waves(输入, SECTION_LENGTH, &ref_phase, FREQ, sine_buff1, cos_buff1);
/* 运行第一阶段过滤 */
simple_fir_filter(sine_buff1, FILTER_SIZE1);
simple_fir_filter(cosine_buff1, FILTER_SIZE1);
/* 每个数组的最后一个元素将保存过滤结果。 */
/* 现在做第二阶段 */
sine_buff2[0] = sine_buff1[FILTER_SIZE1-1];
cosine_buff2[0] = cosine_buff1[FILTER_SIZE1-1];
组合_buff[0] = sine_buff2[0]*sine_buff2[0] + cosine_buff2[0]*cosine_buff2[0];
simple_fir_filter(sine_buff2, FILTER_SIZE2);
simple_fir_filter(cosine_buff2, FILTER_SIZE2);
simple_fir_filter(combined_buff, FILTER_SIZE2);
紧振幅 = sine_buff2[FILTER_SIZE2-1]*sine_buff2[FILTER_SIZE2-1] +
cosine_buff2[FILTER_SIZE2-1]*cosine_buff2[FILTER_SIZE2-1];
松散幅度=组合_buff2 [过滤器尺寸2-1];
}
这里的代码对除数组下标之外的所有数学运算都使用“double”。在实践中,用整数数学代替一些数学几乎肯定会更快。在具有浮点的机器上,我希望最好的方法是将相位保持为 32 位整数并使用约 4096 个“单”正弦值的表(RAM 中的表大小越小,缓存一致性越好表现)。我在定点(整数)DSP上使用了非常类似于上面的代码,并取得了巨大的成功; process_some_waves 中的正弦和余弦计算是在单独的“循环”中完成的,每个“循环”都被实现为带有“重复”前缀的单个指令。