在欠采样中使用 Goertzel 算法

信息处理 傅里叶变换 信号分析 自由度 采样 阶段
2022-02-07 22:07:16

我计划使用 Goertzel 算法计算信号的相位。我有 2 个信号进入微控制器的 ADC。需要测量它们之间的相位差。信号为 15MHz 正弦波。采样率约为 1MHz 或更低。是否可以使用 Goertzel 进行欠采样?这比计算两个信号的 FFT,然后搜索最高光束并计算它们的相位要容易得多。有人试过吗?这是关于 Goertzel 的好文章:

Banks K. - Goertzel 算法

这是来自“listing1”的代码:

清单 1:Goertzel 实现

#include <stdio.h>
#include <math.h>

#define FLOATING    float
#define SAMPLE  unsigned char

#define SAMPLING_RATE   8000.0  //8kHz
#define TARGET_FREQUENCY    941.0   //941 Hz
#define N   205 //Block size

FLOATING coeff;
FLOATING Q1;
FLOATING Q2;
FLOATING sine;
FLOATING cosine;

SAMPLE testData[N];

/* Call this routine before every "block" (size=N) of samples. */
void ResetGoertzel(void)
{
  Q2 = 0;
  Q1 = 0;
}

/* Call this once, to precompute the constants. */
void InitGoertzel(void)
{
  int   k;
  FLOATING  floatN;
  FLOATING  omega;

  floatN = (FLOATING) N;
  k = (int) (0.5 + ((floatN * TARGET_FREQUENCY) / SAMPLING_RATE));
  omega = (2.0 * PI * k) / floatN;
  sine = sin(omega);
  cosine = cos(omega);
  coeff = 2.0 * cosine;

  printf("For SAMPLING_RATE = %f", SAMPLING_RATE);
  printf(" N = %d", N);
  printf(" and FREQUENCY = %f,\n", TARGET_FREQUENCY);
  printf("k = %d and coeff = %f\n\n", k, coeff);

  ResetGoertzel();
}

/* Call this routine for every sample. */
void ProcessSample(SAMPLE sample)
{
  FLOATING Q0;
  Q0 = coeff * Q1 - Q2 + (FLOATING) sample;
  Q2 = Q1;
  Q1 = Q0;
}


/* Basic Goertzel */
/* Call this routine after every block to get the complex result. */
void GetRealImag(FLOATING *realPart, FLOATING *imagPart)
{
  *realPart = (Q1 - Q2 * cosine);
  *imagPart = (Q2 * sine);
}

/* Optimized Goertzel */
/* Call this after every block to get the RELATIVE magnitude squared. */
FLOATING GetMagnitudeSquared(void)
{
  FLOATING result;

  result = Q1 * Q1 + Q2 * Q2 - Q1 * Q2 * coeff;
  return result;
}

/*** End of Goertzel-specific code, the remainder is test code. */

/* Synthesize some test data at a given frequency. */
void Generate(FLOATING frequency)
{
  int   index;
  FLOATING  step;

  step = frequency * ((2.0 * PI) / SAMPLING_RATE);

  /* Generate the test data */
  for (index = 0; index < N; index++)
  {
    testData[index] = (SAMPLE) (100.0 * sin(index * step) + 100.0);
  }
}

/* Demo 1 */
void GenerateAndTest(FLOATING frequency)
{
  int   index;

  FLOATING  magnitudeSquared;
  FLOATING  magnitude;
  FLOATING  real;
  FLOATING  imag;

  printf("For test frequency %f:\n", frequency);
  Generate(frequency);

  /* Process the samples */
  for (index = 0; index < N; index++)
  {
    ProcessSample(testData[index]);
  }

  /* Do the "basic Goertzel" processing. */
  GetRealImag(&real, &imag);

  printf("real = %f imag = %f\n", real, imag);

  magnitudeSquared = real*real + imag*imag;
  printf("Relative magnitude squared = %f\n", magnitudeSquared);
  magnitude = sqrt(magnitudeSquared);
  printf("Relative magnitude = %f\n", magnitude);

  /* Do the "optimized Goertzel" processing */
  magnitudeSquared = GetMagnitudeSquared();
  printf("Relative magnitude squared = %f\n", magnitudeSquared);
  magnitude = sqrt(magnitudeSquared);
  printf("Relative magnitude = %f\n\n", magnitude);

  ResetGoertzel();
}

/* Demo 2 */
void GenerateAndTest2(FLOATING frequency)
{
  int   index;

  FLOATING  magnitudeSquared;
  FLOATING  magnitude;
  FLOATING  real;
  FLOATING  imag;

  printf("Freq=%7.1f   ", frequency);
  Generate(frequency);

  /* Process the samples. */
  for (index = 0; index < N; index++)
  {
    ProcessSample(testData[index]);
  }

  /* Do the "standard Goertzel" processing. */
  GetRealImag(&real, &imag);

  magnitudeSquared = real*real + imag*imag;
  printf("rel mag^2=%16.5f   ", magnitudeSquared);
  magnitude = sqrt(magnitudeSquared);
  printf("rel mag=%12.5f\n", magnitude);

  ResetGoertzel();
}

int main(void)
{
  FLOATING freq;

  InitGoertzel();

  /* Demo 1 */
  GenerateAndTest(TARGET_FREQUENCY - 250);
  GenerateAndTest(TARGET_FREQUENCY);
  GenerateAndTest(TARGET_FREQUENCY + 250);

  /* Demo 2 */
  for (freq = TARGET_FREQUENCY - 300; freq <= TARGET_FREQUENCY + 300; freq += 15)
  {
    GenerateAndTest2(freq);
  }

  return 0;
}

我应该在代码中进行哪些更改才能很好地使用欠采样?

谢谢!马吕什

2个回答

Goertzel 算法源自一个频率仓的 DFT 定义:

  • X(k)=m=0N1x(n)ej2πnk/N ,

也可以重写:

  • X(k)=m=0N1x(n)ej2πk(Nn)/N,因为WNkN=1

从递归方程上面的性质可以推导出来(我想在这里展示所有的结论是没有意义的):

  • yr(k)=WNk(s(r)+yr1(k)) ,

其中是输入信号的样本。还证明 for因此,经过次迭代后,您将获得准确的 DFT bin 值s(r)yr(k)=X(k)r=N1NX(k)

另一方面,FFT 还计算精确的 DFT 箱。这两种算法都利用原点的 DFT 方程和 DFT 属性来计算频率区间,没有近似值。因此,对于这两种算法,您将获得相同的结果(当然,假设相同的输入信号)。唯一的差异可能是由于实施了 Goertzel 算法核心中的 2d 阶 IIR 滤波器。

希望这可以帮助。

如果您对信号的大致频率足够了解以知道它将最终进入哪个峰值 FFT 结果 bin,则在该 bin 的频率上与 FFT 长度相同的复数输出 Goertzel 滤波器将为您提供与该 bin 相同的结果(加上或减去轻微的数值差异)。