В настоящее время я реализую генератор синусоиды на ATmega328p (16 МГц). Мой проект в основном основан на этой статье https://makezine.com/projects/make-35/advanced-arduino-sound-synchronous/ .
Короче говоря, у меня есть два таймера. Первый (pwm_timer) считает от 0 до 255 и устанавливает выходной контакт на основе значения в OCR2A
регистре, который создает сигнал PWM. Второй таймер (sample_timer) использует прерывание (ISR) для изменения значения OCR2A
. ISR происходит, когда значение таймера совпадает со значением в OCR1A
регистре, после чего таймер обнуляется.
Итак, это мой код:
#include <avr/io.h>
#include <avr/interrupt.h>
#include <math.h>
/******** Sine wave parameters ********/
#define PI2 6.283185 // 2*PI saves calculation later
#define AMP 127 // Scaling factor for sine wave
#define OFFSET 128 // Offset shifts wave to all >0 values
/******** Lookup table ********/
#define LENGTH 256 // Length of the wave lookup table
uint8_t wave[LENGTH]; // Storage for waveform
static uint8_t index = 0; // Points to each table entry
/******** Functions ********/
static inline void populate_lookup_table(void);
static inline void setup_pwm_timer(void);
static inline void setup_sample_timer(void);
int main(void)
{
populate_lookup_table();
setup_pwm_timer();
setup_sample_timer();
while(1)
{
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");
asm("NOP");
}
}
ISR(TIMER1_COMPA_vect) // Called when TCNT1 == OCR1A
{
OCR2A = wave[++index]; // Update the PWM output
}
static inline void setup_pwm_timer()
{
TCCR2A = 0;
TCCR2B = 0;
TCCR2A |= _BV(WGM21) | _BV(WGM20); // Set fast PWM mode
TCCR2A |= _BV(COM2A1); // Clear OC2A on Compare Match, set at BOTTOM
TCCR2B |= _BV(CS20); // No prescaling
OCR2A = 127; // Initial value
DDRB |= _BV(PB3); // OC2A as output (pin 11)
}
static inline void setup_sample_timer()
{
TCCR1A = 0;
TCCR1B = 0;
TIMSK1 = 0;
TCCR1B |= _BV(WGM12); // Set up in count-till-clear mode (CTC)
TCCR1B |= _BV(CS10); // No prescaling
TIMSK1 |= _BV(OCIE1A); // Enable output-compare interrupt
OCR1A = 40; // Set frequency
sei(); // Set global interrupt flag
}
static inline void populate_lookup_table()
{
// Populate the waveform table with a sine wave
int i;
for (i=0; i<LENGTH; i++) // Step across wave table
{
float v = (AMP*sin((PI2/LENGTH)*i)); // Compute value
wave[i] = (int)(v+OFFSET); // Store value as integer
}
}
Теоретически частота выходного сигнала должна быть равна этой формуле 16Mhz / (LOOKUP_TABLE_LENGTH * OCR1A)
. Итак, для OCR1A = 100
мы должны получить 625Hz
синусоиду. И это верно до тех пор ~1200Hz (OCR1A = 52)
. После этого вне зависимости от значения OCR1A
выходная частота остается неизменной. Вопрос в том, почему?
Думаю проблема кроется во времени выполнения ISR. Есть ли способ ускорить его, может быть, оптимизировать код? Может быть, я должен написать это на ассемблере?
Я знаю, что могу увеличить частоту, уменьшив длину справочной таблицы, но я действительно хочу остаться с 256 образцами.
Примечание. Я понимаю, что добавление некоторых asm(“NOP”)
в основной цикл немного увеличивает частоту (1250 Гц). Может быть, этот while(1) тоже виноват?
Фотография показывает, что частота дискретизации правильная (16000000 / 256 = 62500).
Мой микроконтроллер "Arduino Nano3". IDE — Atmel Studio.
Спасибо за ваше время.
Обновления:
MOV
инструкция. 40: {
1f.92 PUSH R1 Push register on stack
0f.92 PUSH R0 Push register on stack
0f.b6 IN R0,0x3F In from I/O location
0f.92 PUSH R0 Push register on stack
11.24 CLR R1 Clear Register
8f.93 PUSH R24 Push register on stack
ef.93 PUSH R30 Push register on stack
ff.93 PUSH R31 Push register on stack
41: OCR2A = wave[++index]; // Update the PWM output
e0.91.00.01 LDS R30,0x0100 Load direct from data space
ef.5f SUBI R30,0xFF Subtract immediate
e0.93.00.01 STS 0x0100,R30 Store direct to data space
f0.e0 LDI R31,0x00 Load immediate
ef.5f SUBI R30,0xFF Subtract immediate
fe.4f SBCI R31,0xFE Subtract immediate with carry
80.81 LDD R24,Z+0 Load indirect with displacement
80.93.b3.00 STS 0x00B3,R24 Store direct to data space
42: }
ff.91 POP R31 Pop register from stack
ef.91 POP R30 Pop register from stack
8f.91 POP R24 Pop register from stack
0f.90 POP R0 Pop register from stack
0f.be OUT 0x3F,R0 Out to I/O location
0f.90 POP R0 Pop register from stack
1f.90 POP R1 Pop register from stack
18.95 RETI Interrupt return
Для 1200 Гц и таблицы поиска 256 у вас есть 16000000/(256*1200) = 52 цикла между прерываниями.
Если вы считаете шаги в ASM-коде прерывания, вы находитесь на самом дне, если не ниже.
В основном цикле есть прыжок, для которого требуется два цикла, если вы добавите nop, прыжок будет происходить реже, поэтому у вас есть небольшое улучшение.
Вы можете переместить код прерывания в основной цикл, чтобы сэкономить несколько циклов (в три раза меньше), потому что PUSH и POP медленнее. Затем используйте nop для получения желаемой частоты. Отключите любое прерывание.
Есть также одно большое ограничение, которое все еще существует: как вы можете обновить 256-шаговый ШИМ всего за 52 цикла? Даже если вы не хотите уменьшать длину справочной таблицы, многие записи в PWM фактически игнорируются.
Поскольку вы ничего не можете сделать, кроме обновления значения, вы можете импровизировать резисторный ЦАП на цифровом порту.
Помимо того, что говорит @Dorian, обратите внимание, что вы используете таймер PWM и таймер выборки на одной частоте. У вас есть один цикл PWM каждые 256 циклов ЦП. Если вы меняете рабочий цикл ШИМ чаще, чем каждые 256 циклов ЦП, в быстром режиме ШИМ вы получите сбои/искажения на выходе.
Чтобы смягчить проблемы, на первом этапе вы можете добавить фильтр нижних частот (RC) на выходе ШИМ, чтобы создать синусоидальный сигнал с частотой x Гц из 50% ШИМ с частотой x Гц, обходя справочную таблицу. Или используйте низкочастотный фильтр с более высокой частотой и уменьшите таблицу поиска, скажем, до 4 или 8 записей, уменьшив частоту ISR до 4 или 8 раз больше выходной частоты (вместо 256 раз) и позволив фильтру сгладить переходы между шагами.
В качестве альтернативы вы можете рассмотреть микросхемы ATtiny2/4/85, которые предлагают «настоящую» быструю ШИМ с таймером, работающим на частоте до 64 МГц.
яский
Батави
Батави
пользователь98663
трубка