【问题标题】:I'm designing a guitar tuner through ATmega16p and CodeVisionAVR and i just can't get my code to run我正在通过 ATmega16p 和 CodeVisionAVR 设计吉他调音器,但我无法让我的代码运行
【发布时间】:2019-05-20 20:44:42
【问题描述】:

我正在为我大学的第二个项目设计一个通过 atmel mega16 处理器和 CodeVisionAVR 的吉他调音器。我已将单声道插孔连接到处理器的 PINA.7(ADC 转换器)和 GND。我有 7 个 LED (PORTB.0..6),它们应该根据信号基频的频率通过一系列 if/elseif 来打开。

我正在通过 800 个样本的 DFT(我知道有更快的 FT,但我们的大学告诉我们应该使用 DFT,他们知道原因)来获取信号的基本原理。在选择的 800 个样本中,它计算频谱。然后用next for来计算每个频率的绝对值,取最大的一个,可以作为吉他调音师的一个很好的参考点。

Momentairly,我在主函数中只包含了一个大频率条件,以查看 LED 是否亮起,但它没有。

我尝试在整个代码中将 LED 从 0 切换到 6,它似乎停止在 F = computeDft();,所以我删除了变量,让 computeDft(); 运行,但下一个 LED 没有亮起。函数永远不会被调用吗?我已经在 Visual Studio 中使用生成的余弦函数尝试了该函数,并且效果很好。它总是检测基本面。为什么它在 CVAVR 中不起作用?

#define M_PI 3.1415926f
#define N 800

unsigned char read_adc(void)
{
ADCSRA |= 0x40;  //start conversion;
while (ADCSRA&(0x40)); //wait conversion end
return (float)ADCH;
}

typedef struct 
{
    float re;
    float im;
} Complex;

float computeDft()
{      
    unsigned char x[N] = {0};
    float max = 0;   
    float maxi = 0;
    float magnitude = 0; 
    Complex X1[N] = {0};
    int n = N;
    int k;       
    for (n = 0; n < N; ++n)
    {
        for (k = 0; k < n; k++)
        {       
            x[k] = read_adc();            
            X1[n].re += x[k] * cos(n * k * M_PI / N);
            X1[n].im -= x[k] * sin(n * k * M_PI / N);
        }
    }                     
    for (k = 0; k < n; k++)  
    {
        magnitude = sqrt(X1[k].re * X1[k].re +  X1[k].im * X1[k].im);
        if (magnitude > maxi) 
        {
        maxi = magnitude;
        max = k;   
        }
    }                                             
    return max;   
}


/*
 * main function of program
 */
void main (void)
{          
    float F = 0;
    Init_initController();  // this must be the first "init" action/call!
    #asm("sei")             // enable interrupts
    LED1 = 1;               // initial state, will be changed by timer 1 
    L0 = 0;
    L1 = 0;
    L2 = 0;
    L3 = 0;
    L4 = 0;
    L5 = 0;
    L6 = 0;
    ADMUX = 0b10100111; // set ADC0
    ADCSRA = 0b10000111; //set ADEN, precale by 128

    while(TRUE)
    {
        wdogtrig();         // call often else processor will reset ;        
        F = computeDft();  
        if (F > 50 && F < 200)
        {
            L3 = 1;
        }
    } 


}// end main loop 

我试图实现的结果是来自电话或计算机的信号(可能是一个人调整吉他的 YouTube 视频)通过插孔发送到 AD 转换器 (PINA.7) 中的处理器。主函数调用computeDft; 函数,它将要求read_adc(); 将通过电缆发送的电压值添加到x[k],然后计算它的Dft。然后相同的函数选择基波的频率(具有最高绝对值的频率),然后返回它。在 main 函数内部,一个变量将被分配基频的值,并通过一系列 if 将其值与标准吉他弦频率 82.6、110 等进行比较......

【问题讨论】:

  • computeDft 可能需要 7K+ 的堆栈。你有多少堆栈空间?
  • 1016 字节,我意识到这是问题之一,所以我删除了向量的初始化,现在它占用了 5K,但仍然太多了。我不知道如何制作一个只有 1016 字节的 DFT 调谐器

标签: c avr atmega16 tuner codevisionavr


【解决方案1】:

1. 首先:在 DFT 中只选择较大的谐波,作为调音器不好,因为根据所演奏的乐器,泛音可能具有较大的幅度。体面的调谐器可以通过使用例如自相关算法。

2. 我在您的项目中看到了这一行:

 wdogtrig();         // call often else processor will reset ; 

为什么首先需要看门狗?它是在哪里配置的?它设置的超时时间是多少?您认为,在computeDft() 中执行两个嵌套循环需要多长时间?里面有很多浮点运算,包括每一步计算正弦和余弦?在 16MHz 8 位 MCU 上?我认为这至少需要几秒钟,所以不要使用看门狗,或者更频繁地重置它。

3.

cos(n * k * M_PI / N);

(顺便问一下,你确定是cos(n * k * M_PI / N);而不是cos(n * k * 2 * M_PI / N);?)

由于 cos(x) = cos(x + 2 * M_PI),可以看到这个公式可以表示为cos((n * k * 2) % (2 * N) * M_PI / N)。 IE。您可以预先计算所有 2*N 个可能的值,并将它们作为常数表放入闪存中。

4.查看computeDft()中的嵌套循环

在内部循环中,您每次都在调用read_adc()

您希望一次将信号选取到缓冲区中,然后对保存的信号执行 DFT。 IE。首先将 ADC 值读入 x[k] 数组:

for (k = 0; k < N; k++)
{       
    x[k] = read_adc();            
}

然后您才对其执行 DFT 计算:

for (n = 0; n < N; ++n)
{
    for (k = 0; k < n; k++)
    {       
        X1[n].re += x[k] * cos(n * k * M_PI / N);
        X1[n].im -= x[k] * sin(n * k * M_PI / N);
    }
}   

5.仔细看两个周期:

for (n = 0; n < N; ++n)
     ..
        X1[n].re += x[k] * cos(n * k * M_PI / N);
        X1[n].im -= x[k] * sin(n * k * M_PI / N);
}

在每一步,您都在计算 X1[n] 的值,没有使用之前的 X1 值。

下面还有一个循环:

for (k = 0; k < n; k++)  
{
    magnitude = sqrt(X1[k].re * X1[k].re +  X1[k].im * X1[k].im);
    ...
}

在这里,您正在计算 X1[k] 的大小,并且没有使用 X1 的前一个值和下一个值。因此,您可以简单地将它们组合在一起:

for (n = 0; n < N; ++n)
{
    for (k = 0; k < n; k++)
    {       
        X1[n].re += x[k] * cos(n * k * M_PI / N);
        X1[n].im -= x[k] * sin(n * k * M_PI / N);
    }
    magnitude = sqrt(X1[n].re * X1[n].re +  X1[n].im * X1[n].im);
    if (magnitude > maxi) 
    {
    maxi = magnitude;
    max = k;   
    }
}

这里你可以清楚地看到,你没有理由将X1[n].reX1[n].im存储在任何数组中。干掉他们!

for (n = 0; n < N; ++n)
{
    float re = 0;
    float im = 0;
    for (k = 0; k < n; k++)
    {       
        re += x[k] * cos(n * k * M_PI / N);
        im -= x[k] * sin(n * k * M_PI / N);
    }
    magnitude = sqrt(re * re +  im * im);
    if (magnitude > maxi) 
    {
        maxi = magnitude;
        max = k;   
    }
}

就是这样!通过删除无意义的Complex X1[N] 数组,您节省了 6 KB

6.你的初始化代码有错误:

ADMUX = 0b10100111; // set ADC0

我不知道什么是“ATmega16P”,我认为它的工作原理与“ATmega16”相同。所以这个寄存器的最高有效位,称为REFS1REFS0 用于选择参考电压。可能的值是:

  • 00 - 来自 AREF 引脚的外部电压;
  • 01 - AVCC 电压作为参考
  • 11 - 内部稳压器(ATmega16 为 2.56V,ATmega168PA 为 1.1V)

10 的值不正确。

7.吉他输出是小信号,可能几十毫伏。此外,它是一个交流信号,可以是正的,也可以是负的。因此,在将信号放入 MCU 的输入之前,您必须对其进行移位(否则您将只能看到正半波)并对其进行放大。

即仅将插头连接到 GND 和 ADC 输入是不够的,您需要一些原理图来制作适当电平的信号。

你可以google一下。例如: (来自This project

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-08-20
    • 1970-01-01
    • 2017-07-25
    • 2012-10-02
    • 2016-05-13
    • 1970-01-01
    • 2011-05-29
    相关资源
    最近更新 更多