Я проектирую гитарный тюнер через ATmega16p и CodeVisionAVR, и я просто не могу заставить свой код работать
Я проектирую гитарный тюнер с процессором 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. Прежде всего: просто выбрать большую гармонику в 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 и АЦП, вам нужны схемы, которые подадут сигнал соответствующего уровня.
Вы можете Google для этого. Например это: (из этого проекта)