Actualmente estoy implementando un generador de ondas sinusoidales en ATmega328p (16MHz). Mi proyecto se basa principalmente en este artículo https://makezine.com/projects/make-35/advanced-arduino-sound-synthesis/ .
En resumen, tengo dos temporizadores. El primero (pwm_timer) cuenta de 0 a 255 y establece el pin de salida basado en el valor en OCR2A
registro, que está creando la señal PWM. El segundo temporizador (sample_timer) utiliza la interrupción (ISR) para cambiar el valor de OCR2A
. El ISR se produce cuando el valor del temporizador es el mismo que el valor en OCR1A
registro, después de eso, el temporizador se pone a cero.
Así que este es mi código:
#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
}
}
En teoría, la frecuencia de la señal de salida debería ser igual a esta fórmula 16Mhz / (LOOKUP_TABLE_LENGTH * OCR1A)
. Así que para OCR1A = 100
deberíamos obtener 625Hz
onda sinusoidal. Y esto es cierto hasta que ~1200Hz (OCR1A = 52)
. Después, independientemente del valor de OCR1A
- la frecuencia de salida se mantiene igual. La pregunta es ¿por qué?
Creo que el problema radica en el tiempo de ejecución del ISR. ¿Hay alguna manera de acelerarlo, tal vez optimizar el código? ¿Tal vez debería escribirlo en ensamblador?
Sé que puedo aumentar la frecuencia disminuyendo la longitud de la tabla de búsqueda, pero realmente quiero quedarme con 256 muestras.
Nota al margen. Me doy cuenta de que añadir algo de asm(“NOP”)
en el bucle principal aumentar un poco la frecuencia (1250Hz). ¿Quizás este while(1) también es culpable?
Resultado del código anterior.
Foto que muestra que la frecuencia de muestreo es correcta (16000000 / 256 = 62500).
Mi microcontrolador es "Arduino Nano3". IDE - Atmel Studio.
Gracias por su tiempo.
Actualizaciones:
- Compruebo la frecuencia utilizando el DSO Shell y el altavoz con el analizador de espectro.
- La disminución del valor de la resistencia y la capacitancia agrega algunos hertzios, pero es insignificante.
-
El desmontaje revela que el ISR no es simplemente
MOV
instrucción.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
0 votos
Disminuye ese condensador a 10n y/o disminuye la resistencia. El RC de paso bajo podría ser el culpable.
0 votos
Post actualizado @Wossname
0 votos
Post actualizado @JanDorniak
0 votos
Gracias por la actualización. No creo que la culpa sea del ISR. Como ISRs ir, que es un muy ligero.
0 votos
Me sigue sorprendiendo que el ISR esté tan hinchado, pero no tengo suficiente experiencia en AVR para juzgar si es necesario.