Основные параметры:

  • Источник питания: 5 В постоянного тока;
  • Потребление электроэнергии: около 170 мА;
  • Максимальная амплитуда аналогового входного сигнала: 2,5 В (5 В пик-пик);
  • Анализируемые полосы частот: 200 Гц, 400 Гц, 1 кГц, 2 кГц, 4 кГц, 6 кГц, 8 кГц, 12 кГц;
  • Количество способов представления спектра сигнала: 5.

Принципиальная схема устройства показана на рисунке 1. Как видите, была разработана очень простая микропроцессорная система, сердцем которой является небольшой микроконтроллер от Microchip (ранее Atmel) с маркировкой ATmega48, работающий от внутреннего высокостабильного тактового генератора частотой 8 МГц. Более опытные проектировщики могут быть удивлены такой низкой тактовой частотой микроконтроллера с учетом типа задач, которые ему предстоит выполнять (цифровая обработка сигнала). Поверьте мне, когда я говорю, что этой частоты вполне достаточно. Кстати, мы избавляемся от дополнительных элементов, которые должны использоваться для увеличения тактовой частоты сверх значений, имеющихся внутри структуры нашей системы (речь идет о внешнем кварцевом резонаторе).

Spectra - анализатор спектра звукового сигнала - схема

Рисунок 1. Схема устройства.

Основная задача нашего микроконтроллера — выборка аудиосигнала, подаваемого на вывод ADC2(PC2), что делается с помощью встроенного АЦП, работающего в 8-битном режиме (фактически в 10-битном, но из-за выравнивания результата преобразования влево, легче прочитать 8-битное значение, являющееся результатом обработки) с тактовым сигналом частотой 1 МГц, что обеспечивает достаточно малое время обработки сигнала (около 15,5 мкс). Это достаточно короткое время преобразования обусловлено довольно высокой частотой дискретизации входного аудиосигнала, а именно 25600 выборок в секунду. Это, в свою очередь, вытекает из желаемой полосы пропускания обрабатываемого сигнала, которая в данном случае по теореме Найквиста составит 12,8 кГц.

Однако до того, как звуковой сигнал попадет на вход АЦП он проходит через простой ФНЧ первого порядка с частотой среза около 12,8 кГц, построенный с использованием операционного усилителя в виде интегральной схемы U2 (MCP6231) и нескольких пассивных элементов. Коэффициент усиления в схеме фильтра был установлен равным 1, но его можно увеличить, изменив отношение сопротивлений R12/R15, но все же помня о частоте среза, определяемой по формуле:

fc = 1/(2π*R12*C6)

Фильтр в такой конфигурации инвертирует фазу сигнала, но из-за создания на неинвертирующем входе операционного усилителя смещения напряжения, равному Uпит/2 и наличия конденсатора С7 инверсия фазы не имеет значения для дальнейшего DSP-анализа, так как сигналы, обрабатываемые АЦП, будут в диапазоне напряжений 0…5В.

Для справки добавлю, что триггером преобразования аналогового сигнала является Timer0, работающий в режиме СТС, для которого параметры подобраны таким образом, что сравнение происходит 25600 раз в секунду.

Блок-схема системы Spectra представлена на рисунке 2. Здесь также стоит отметить, что производитель микроконтроллера, компания Microchip, предоставляет в соответствующей документации достаточно неоднозначную или, по крайней мере, неполную информацию, касающуюся АЦП. С одной стороны, рекомендуется, чтобы тактовая частота преобразователя АЦП была не выше 200 кГц, а с другой стороны, говорится, что если мы не заботимся о полном разрешении АЦП (10 бит), мы можем увеличить эту частоту до 1 МГц (значения, в противном случае взяты из еще одного раздела документации).

Однако нет никаких формул или сведений о том, как связать ожидаемое разрешение преобразователя с допустимой тактовой частотой этого периферийного устройства. К счастью, нам на помощь приходит интернет и крайне интересная практическая статья, размещенная по адресу, где эмпирически были доказаны ограничения этой конструкции. Я искренне рекомендую прочитать эту статью, потому что она очень просто объясняет сложные механизмы, влияющие на процесс сбора данных.

Блок-схема системы сбора данных устроства

Рисунок 2. Блок-схема системы сбора данных устроства.

Вернемся к схеме нашего устройства. Микроконтроллер также управляет светодиодным индикатором, состоящего из восьми 10-точечных линейных шкал, соединенных по схеме с общим катодом, и работой кнопки MODE, предназначенной для изменения способа представления спектра сигнала. Светодиодный дисплей управляется с помощью механизма мультиплексирования, за который отвечает таймер/счетчик 1. Он настроен генерировать прерывание по сравнению (режим CTC) 480 раз в секунду или 60 раз на каждую линейную шкалу. В таком прерывании в первую очередь выключаются все общие катоды светодиодных дисплеев (порты столбцов), затем в порты PD0…PD7 и PC4 и PC5 (порты рядов) поступают значения, соответствующие следующему диапазон частот. Затем включается один общий управляемый катод с портов PB0…PB7 микроконтроллера (через управляющие транзисторы Т1…Т8). Это типичное решение используемого во многих микропроцессорных системах механизма мультиплексирования, благодаря которому можно управлять большим количеством светодиодов при ограниченном количестве портов микроконтроллера. Благодаря этому мы также ограничиваем общий ток, потребляемый светодиодным дисплеем, который в данном случае составляет максимум 10-16 мА.

Настройки Fusebit (более важные):
CKSEL3…0: 0010
SUT1…0: 10
CKDIV8: 1
EESAVE: 1

Программа управления

Пришло время показать фрагменты кода, связанного с системой сбора данных. Код, отвечающий за инициализацию схемы таймера-счетчика Timer0, которая является триггером сбора данных, и конфигурация преобразователя АЦП показаны в примере 1. Кроме того, в примере 2 показан код, отвечающий за чтения данных АЦП. Как видите, чтение полной порции данных сопровождается остановкой сбора данных (отключением таймера Timer0) и установкой флага ADCdataReady, благодаря чему возможна обработка данных основной программой.

Пример 1. Код, отвечающий за инициализацию периферии
void initADC(void)
{
// Таймер 0 в режиме CTC, прерывание 25600 раз в секунду
TCCR0A = (1 << WGM01); // Режим CTC
TCCR0B = (1 << CS01); // Предделитель = 8 (1MHz)
OCR0A = 38;
//Vref = AVcc (5V), выравнивание результата влево (8-битное разрешение), MUX = ADC2 (PC2)
ADMUX = (1 << REFS0)|(1 << ADLAR)|(1 << MUX1);
// Включение АЦП, начало преобразования по выбранному сигналу, разрешено прерывание АЦП, предделитель = 8 (1MHz)
ADCSRA = (1 << ADEN)|(1 << ADATE)|(1 << ADIE)|(1 << ADPS1)|(1 << ADPS0);
// Выбор источника запуска преобразования АЦП: Timer/counter0 compare match A
ADCSRB = (1 << ADTS1)|(1 << ADTS0);
// Цифровой вход отключен на выводе ADC2 (PC2)
DIDR0 = (1 << ADC2D);
}

Обозначение ADCdata[Idx++] = ADCH - 128 требует нескольких слов внимания. Вычитание значения 128 из результата обработки АЦП (ADCH) происходит из-за того, что вход ADC2 преобразователя был смещен половиной напряжения питания, а следовательно, и опорного напряжения, чтобы система могла обрабатывать реальные звуковые сигналы, которые, как известно, также принимают значения ниже 0.

Пример 2. Код, отвечающий за сбор данных АЦП
ISR(ADC_vect)
{
static uint8_t Idx;
ADCdata[Idx++] = ADCH - 128;
if(Idx == SAMPLES){
Idx = 0;
ADCdataReady = 1;
STOP_ACQUISITION;
}
// Сбрасываем флаг по совпадению  Timer/counter0
// чтобы заново начать чтение АЦП
TIFR0 = (1 << OCF0A);
}

Функция, показанная в примере 3, отвечает за обработку собранных данных, т.е. за вычисление дискретного преобразования Фурье из отсчетов сигналов, собранных в таблице ADCdata[]. Введена возможность настройки «чувствительности» функции DFT, благодаря преобладанию низкочастотных тонов в реальном сигнале и необходимостью снижения «чувствительности» функции в связи с этим. Работа функции DFT() заключается в расчете мощности отдельных частотных диапазонов (задается значением аргумента i). И именно эта задача является основной вычислительной проблемой, упомянутой в начале статьи. Это связано с количеством вычислений с фиксированной точкой, выполненных в цикле, из которого состоит функция. Как видите, количество этих вычислений напрямую зависит от количества точек преобразования Фурье (SAMPLES),

В нашем случае BIN = 200*(25,6 кГц/128), что означает, что последовательные значения частоты, для которых рассчитывается мощность сигнала, кратны 200 Гц. Это значение, в свою очередь, является компромиссом между разрешающей способностью мощности (BIN) и временем, необходимым для выполнения функции DFT() с предполагаемым количеством точек преобразования (SAMPLES). В нашем случае это время составляет около 1,45 мс * 8 (поскольку мы вычисляем DFT только для выбранных 8 точек). Это определяет частоту обновления графика спектра (так называемую частоту кадров), которая в данном случае составляет около 80 Гц, что для наших нужд очень много. Дальнейшее увеличение количества точек преобразования (SAMPLES), хотя и желательно, уменьшило бы частоту обновления графика спектра до неприемлемых и практически бесполезных значений.

Пример 3. Код функции, отвечающей за вычисление дискретного преобразования Фурье
#define MULF 64 // Коэффициент умножения, не более 128
// Время выполнения 1,45 мс
uint16_t DFT(uint8_t i, uint8_t dftSensivity)
{
uint16_t a, b, Power;
int32_t Re, Im;
// Рассчитываем мощность сигнала
// для искомой полосы частот i < (SAMPLES/2)+1
Re = Im = a = 0;
b = 3*SAMPLES/4;
for (uint8_t j = 0; j < SAMPLES; ++j){
Re += (ADCdata[j] * Twiddle[a % SAMPLES])/MULF;
Im -= (ADCdata[j] * Twiddle[b % SAMPLES])/MULF;
a += i;
b += i;
}
if(dftSensivity == SENSIVITY_HI){
Power = (Re*Re + Im*Im)/
2048UL; else Power = (Re*Re + Im*Im)/
16384UL;
}
return Power;
}

Это основное программное ограничение нашей концепции, о котором упоминалось ранее и вытекает в основном из 8-битной архитектуры микроконтроллера ATmega, а также максимально доступной тактовой частоты преобразователя АЦП (1 МГц). Кроме того, недаром в качестве числа точек преобразования было выбрано значение 128, которое является степенью двойки. Это произошло из-за операции деления, которое выполняется в вышеупомянутом цикле, а, как мы знаем, деление на число, являющееся целой степенью двойки, происходит на порядок быстрее, чем деление на любое другое число, потому что на самом деле оно просто сдвигает разделенное значение вправо.

Так много для ограничений реализации. Итак, вернемся к нашей функции DFT(). Как видите, и как упоминалось в начале статьи, функция использует таблицу коэффициентов Twiddle[] вращающегося вектора, которая определяется функцией calculateTwiddleFactors(), показанной в примере 4. Конечно, функция из примера какая-то избыточная, ведь мы могли бы определить такие коэффициенты в массиве и сохранить их постоянно в памяти программы, что уменьшило бы оперативную память микроконтроллера и код программы. Я решил установить коэффициенты во время работы программы, потому что, во-первых, это быстрее, а во-вторых, что не менее важно, наш микроконтроллер оснащен большим объемом оперативной памяти, которая не используется.

Пример 4. Код функции, отвечающей за определение коэффициентов вращающегося вектора
#define PI2 6.2832 //2*Pi
void calculateTwiddleFactors(void){
for(uint8_t i = 0; i < SAMPLES; ++i)
Twiddle[i] = (int8_t)(MULF*cos(i*PI2/SAMPLES));
}

Стоит отметить, что перед выполнением функции DFT() собранная таблица входных данных ADCdata[] подвергается оконной обработке, целью которой является уменьшение так называемой утечки спектра сигнала, что само по себе оказывает негативное влияние на результирующий спектр сигнала. Используемая оконная функция относится к типу Hanna (Hanninga), а определение соответствующих оконных коэффициентов (Window[]) выполняется функцией calculateWindowFactors(), тело которой показано в примере 5.

Пример 5. Код функции, отвечающей за определение коэффициентов окна Ханна(Hanninga)
void calculateWindowFactors(void){ // Окно Ханна
for(uint8_t i = 0; i < SAMPLES; ++i)
Window[i] = (int8_t)(MULF*(0.5-0.5*cos(i*PI2/(SAMPLES-1))));
}

Дискретный входной сигнал, собранный в массиве ADCdata[], умножается оконной функцией Window[] перед выполнением преобразования Фурье в функции DFT(). В самом конце, после выполнения функции DFT(), и перед отображением спектра мощности сигнала соответствующие мощности пересчитываются в логарифмическую шкалу (дБ), что в основном связано с высокой динамикой сигнала Power[] и необходимо отображать его на ограниченном вертикальном разрешении светодиодного индикатора (10 светодиодов) сигнальной шкалы. Рассматриваемое преобразование выполняется согласно следующему соотношению: Power[i] = 3,333·log10(Power[i]).

Вот вам и обработка сигналов DSP. Наконец, я кратко расскажу о соответствующих функциях механизма мультиплексирования, отвечающего за отображение данных нашего светодиодного экрана. Начнем с заголовочного файла, содержимое которого показано в примере 6. Далее в примере 7 показана функция инициализации механизма мультиплексирования, которая устанавливает соответствующие логические состояния на общих катодных и анодных портах светодиодного дисплея и инициализирует соответствующий аппаратный таймер.

Пример 6. Заголовочный файл механизма мультиплексирования
#define ROW_LSB_PORT PORTD
#define ROW_LSB_DDR DDRD
#define ROW_MSB_PORT PORTC
#define ROW_MSB_DDR DDRC
#define ROW_MSB_0 PC5
#define ROW_MSB_1 PC4
#define COLUMN_PORT PORTB
#define COLUMN_DDR DDRB
#define COLUMN_0 PB0
#define COLUMN_1 PB5
#define COLUMN_2 PB4
#define COLUMN_3 PB3
#define COLUMN_4 PB2
#define COLUMN_5 PB1
#define COLUMN_6 PB7
#define COLUMN_7 PB6
#define COLUMN_BLANK COLUMN_PORT = 0x00
//Переменная, в которой хранится значение, отображаемое на последовательных барах дисплея
extern uint16_t Led[8];

Пример 7. Функция инициализации механизма мультиплексирования
void initMultiplex(void){
// Порты управляющие строками дисплея с неактивным состоянием "0"
ROW_LSB_DDR = 0xFF;
ROW_MSB_DDR |= (1 << ROW_MSB_1)|(1 << ROW_MSB_0);
// Порты управляющие столбцами дисплея с неактивным состоянием "0"
COLUMN_DDR = 0xFF;
// Настраиваем прерывание от Timer1
// для работы функции мультиплексирования дисплея(480 Гц)
TCCR1B = (1 <<WGM12)|(1 << CS12); // Режим CTC, Предделитель = 256 @ 8MHz
// 480 Hz (прерывание 480 раз в секунду,
// 60 раз в секунду для каждой шкалы
OCR1A = 64;
// Запуск прерывания по сравнению
TIMSK1 = (1 << OCIE1A);
}

Еще одной представленной функцией является функция обслуживания прерываний, реализующая механизм мультиплексирования, содержание которой показано в примере 8. Упомянутая функция использует объявление переменной, хранящейся во флэш-памяти микроконтроллера: Введение этой переменной упрощает доступ к портам столбцов.

const uint8_t Columns[8] PROGMEM = {
(1 << COLUMN_0), (1 << COLUMN_1),
(1 << COLUMN_2), (1 << COLUMN_3),
(1 << COLUMN_4), (1 << COLUMN_5),
(1 << COLUMN_6), (1 << COLUMN_7)};

Пример 8. Функция обработки прерываний, реализующая механизм мультиплексирования
// Переменная, в которой хранится значение, отображаемое на последовательных барах дисплея
uint16_t Led[8];
ISR(TIMER1_COMPA_vect){
// Номер следующего бара для отображения
static uint8_t Nr;
// Погасить все столбцы
COLUMN_BLANK;
// Отображение правильных состояний на портах строк
ROW_LSB_PORT = Led[Nr] & 0xFF;
if(Led[Nr] & 0b100000000)
ROW_MSB_PORT |= (1 << ROW_MSB_0);
else
ROW_MSB_PORT &= ~(1 << ROW_MSB_0);
if(Led[Nr] & 0b1000000000)
ROW_MSB_PORT |= (1 << ROW_MSB_1);
else
ROW_MSB_PORT &= ~(1 << ROW_MSB_1);
// Включить соответствующий столбец
COLUMN_PORT = pgm_read_byte(&Columns[Nr]);
// Выбор следующего столбца
Nr = (Nr+1) & 0x07;
}

С программированием покончено, давайте перейдем к вопросам сборки.

Монтаж и наладка

Схема сборки устройства Spectra показана на рисунке 3. Очень компактная печатная плата была разработана с использованием только выводных компонентов. Сборку прибора начинаем с припаивания всех резисторов, затем припаиваем дроссель L1, конденсаторы, затем интегральные микросхемы (которые должны быть снабжены соответствующими гнёздами) и, наконец, дисплеи LED1...LED8 и переключатель РЕЖИМ.

Рисунок 3. Макет печатной платы с расположением элементов.

Правильно собранная плата не требует никаких регулировок и должна работать сразу после включения питания. Для любознательных стоит отметить, что в прикладной программе устройства Spectra предусмотрено 5 режимов отображения информации о спектре звукового сигнала, примеры которых представлены на рисунке 4. Что стоит подчеркнуть, третий режим (Bar max) объединяет дополнительный функционал в виде показа пикового значения в каждой из полос частот.

Рисунок 4. Визуализации, показывающие 5 режимов отображения информации о спектре аудиосигнала.

Выводы

Итоговые тесты устройства с использованием генератора сигналов произвольной формы показали очень высокую избирательность системы в отношении отклика на выбранные частоты, анализируемые нашим прибором, и несколько меньший «отклик» устройства в случае «обычного» звука. сигналов (особенно с высокими частотами), что побудило меня повысить чувствительность функции, вычисляющей дискретное преобразование Фурье, и изменить результирующий масштаб этой функции на логарифмический, чтобы правильно отображать очень высокую динамику сигнал.


Файлы к статье "Spectra - анализатор спектра звукового сигнала на ATmega48"
Описание:

Файл прошивки микроконтроллера

Размер файла: 4.13 KB Количество загрузок: 92 Скачать