Я проектирую гитарный тюнер через ATmega16p и CodeVisionAVR, и я просто не могу заставить свой код работать - PullRequest
0 голосов
/ 20 мая 2019

Я проектирую гитарный тюнер с процессором Atmel Mega16 и CodeVisionAVR для второго проекта моего университета.Я подключил монофонический разъем к процессору PINA.7 (АЦП) и GND.У меня есть 7 светодиодов (PORTB.0..6), которые должны включаться через серию if / elseif в зависимости от частоты основного сигнала.

Я принимаю основную часть сигнала через DFT (я знаю, что есть более быстрые FT, но наш университет сказал нам, что мы должны использовать DFT, они знают почему) из 800 выборок.Из 800 выбранных выборок рассчитывается частотный спектр.Затем следующее значение используется для вычисления абсолютного значения каждой частоты и выбирает наибольшую, так что это может быть хорошей точкой отсчета для гитарного тюнера.

В данный момент я включил в основную функцию простоусловие большой частоты, чтобы увидеть, горит ли светодиод, но он не горит.

Я попытался включить светодиоды от 0 до 6 по всему коду, и кажется, что он останавливается на F = computeDft();, поэтому я удалилпеременная, и просто запустите computeDft();, но следующие светодиоды не загорелись.Функция никогда не вызывается?Я попробовал функцию в 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.Затем та же функция выбирает частоту основной частоты (с наибольшим абсолютным значением), а затем возвращает ее.Внутри основной функции переменной будет присвоено значение основного значения, и через серию ifs она будет сравнивать свое значение со стандартными частотами гитарных струн 82,6, 110 и т. Д.

1 Ответ

1 голос
/ 21 мая 2019

1. Прежде всего: просто выбрать большую гармонику в DFT, это плохо для тюнера, поскольку, в зависимости от используемого инструмента, обертоны могут иметь большую амплитуду.Приличный тюнер можно сделать, используя, например, алгоритм автокорреляции.

2. Я вижу эту строку в вашем проекте:

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

Зачем вам нужен сторожевой таймерна первом месте?Где это настроено?На какой тайм-аут он установлен?Как вы думаете, сколько времени потребуется для выполнения обоих вложенных циклов в computeDft()?С большим количеством операций с плавающей запятой внутри, включая вычисление синуса и косинуса на каждом шаге?На 16-МГц 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).Т.е. вы можете предварительно рассчитать все 2 * N возможных значений и поместить их в виде постоянной таблицы во флэш-память.

4. Посмотрите на вложенные циклы в computeDft()

Внутри внутреннего цикла вы каждый раз вызываете read_adc() !

Вы хотите выбрать сигнал в буфер один раз, а затем выполнить DFT над сохраненным сигналом.Т.е. сначала вы читаете значения АЦП в массиве 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].re и X1[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;   
    }
}

Вот и все!Вы сэкономили 6 КБ, удалив бессмысленный массив Complex X1[N]

6. В коде инициализации произошла ошибка:

ADMUX = 0b10100111; // set ADC0

Я не знаю, чтоэто «ATmega16P», я предполагаю, что он работает так же, как «ATmega16».Таким образом, наиболее значимые биты этого регистра, называется REFS1 и REFS0 используется для выбора опорного напряжения.Возможные значения:

  • 00 - внешнее напряжение с контакта AREF;
  • 01 - напряжение AVCC, взятое в качестве эталона
  • 11 - внутренний регулятор (2,56 В для ATmega16,1,1 В для ATmega168PA)

10 - неверное значение.

7. Выходной сигнал гитары - это слабый сигнал, возможно, несколько десятков милливольт.Кроме того, это сигнал переменного тока, который может быть как положительным, так и отрицательным.Поэтому, прежде чем подавать сигнал на вход MCU, необходимо сместить его (в противном случае вы увидите только положительную полуволну) и усилить его.

Т.е. недостаточно просто подключить штекер к GND и ADC.вход, вам нужно несколько схем, которые сделают сигнал соответствующего уровня.

Вы можете Google для этого.Например это: enter image description here (из Этот проект )

...