Tonos de llamada Atmega8. Sirenas de sonido simples en MK AVR. Usando el módulo de sonido

El artículo describe los principios de la síntesis musical en un AVR. El software incluido le permite convertir cualquier archivo midi en fuente en C para microcontroladores AVR para añadir reproducción de fragmentos musicales a desarrollos ya preparados. Se considera un ejemplo del uso de software en una caja de música.

Primero, un breve vídeo de cómo funciona todo:

Lo que permite el software

El software para PC le permite obtener una fuente C para CodeVision AVR, que reproduce el archivo midi seleccionado:

1. Conecte common\hxMidiPlayer.h, common\hxMidiPlayer.c a su proyecto. Copie las plantillas ATMega8Example\melody.h, ATMega8Example\melody.c, ATMega8Example\hxMidiPlayer_config.h y conéctese.
2. Inicie MidiToC.exe
3. Cargue el archivo Midi.
4. Configure el reproductor: frecuencia de muestreo, número de canales, forma de onda, etc. El software reproduce la melodía de la misma manera que lo hará el AVR.
5. Haga clic en "Crear configuración del reproductor" y pegue la fuente en hxMidiPlayer_config.h.
6. Haga clic en "Crear código de melodía" y pegue la fuente en melody.c
7. En nuestro proyecto, implementamos el método Player_Output() para emitir sonido a través de PWM o un DAC externo.
8. Configure el temporizador en la frecuencia de muestreo y llame a Player_TimerFunc() desde la interrupción.
9. Llame a Player_StartMelody(&s_melody, 0).

La melodía se reproduce desde la interrupción del temporizador. Esto significa que durante la reproducción el microcontrolador también puede realizar un trabajo útil.

Cómo funciona

En el resto del artículo intentaré explicar brevemente cómo se implementa todo esto. Desgraciadamente no será muy breve: hay mucho material. Si no está interesado, puede ir inmediatamente a las secciones "Descripción del software" y "API del reproductor".

Qué es música

La música es una secuencia de sonidos de diferentes frecuencias y duraciones. La frecuencia del armónico fundamental de un sonido debe corresponder a la frecuencia de una determinada nota. Si la frecuencia de vibración de los sonidos difiere de la frecuencia de las notas, nos parece que el músico está "desafinado".

Mesa. Frecuencias de notas, Hz.

Todas las notas se dividen en octavas, 7 notas en cada una + 5 semitonos (teclas negras del piano). Las frecuencias de las notas en octavas adyacentes difieren exactamente 2 veces.

El reproductor de música más sencillo contiene una tabla con la secuencia de notas (nota + duración) de una melodía y una tabla con las frecuencias de las notas. Para sintetizar el sonido se utiliza uno de los canales del temporizador, que forma un meandro:

Desafortunadamente, un intérprete tan primitivo tiene una forma de onda fija (onda cuadrada), que no es muy similar a los instrumentos musicales reales, y sólo puede tocar una nota a la vez.

Una melodía real contiene al menos dos partes (solo + bajo), y cuando se toca en el piano, la nota anterior sigue sonando cuando comienza la siguiente. Esto es fácil de entender si recordamos la estructura de un piano: cada nota corresponde a una cuerda separada. Podemos hacer sonar varias cuerdas al mismo tiempo pasando las manos por las teclas.

Algunos microcontroladores tienen múltiples canales de temporizador que se pueden usar para tocar varias notas simultáneamente. Pero normalmente estos canales son un recurso valioso y no es recomendable utilizarlos todos. A menos, por supuesto, que simplemente estemos haciendo una caja de música.
En total, para obtener polifonía y varios sonidos de instrumentos musicales, es necesario utilizar la síntesis de sonido.

Síntesis de sonido en AVR

hxMidiPlayer utiliza síntesis de audio y puede reproducir polifonía con diferentes formas de onda. El reproductor calcula la amplitud de la señal de salida en el controlador de interrupciones del temporizador con una frecuencia de 8-22 KHz (cuánta potencia del procesador es suficiente; también depende de la forma de onda y el número de canales).

El principio de la síntesis del sonido se puede explicar utilizando el ejemplo de la síntesis sinusoide.

Tomemos una tabla de tamaño 64, en cada celda de la cual se escriben los valores de la amplitud del seno en los puntos índice * 2 * PI / 64 (un período):

Estático constante flash uint8_t s_sineTable[ 64 ] = ( 0x80, 0x82, 0x84, 0x86, 0x88, 0x8A, 0x8C, 0x8D, 0x8F, 0x90, 0x91, 0x93, 0x93, 0x94, 0x95, 0x95, 0x9 5, 0x95, 0x95, 0x94 , 0x93, 0x93, 0x91, 0x90, 0x8F, 0x8D, 0x8C, 0x8A, 0x88, 0x86, 0x84, 0x82, 0x80, 0x7E, 0x7C, 0x7A, 0x78, 0x76, 0x74, 0x73, 0 x7 1, 0x70, 0x6F, 0x6D, 0x6D, 0x6C, 0x6B, 0x6B, 0x6B, 0x6B, 0x6B, 0x6C, 0x6D, 0x6D, 0x6F, 0x70, 0x71, 0x73, 0x74, 0x76, 0x78, 0x7A, 0x7C, 0x7E);

128 (0x80) corresponde a cero, 255 (0xff) al punto positivo más grande, 0 al punto negativo más grande.

Ahora digamos que enviaremos los valores de la tabla a un DAC externo en una interrupción del temporizador llamada a una frecuencia de 1000 Hz:

Estático uint8_t s_index = 0; // Comparación de salida de Timer1 Una interrupción de rutina de servicio void timer1_compa_isr(void) ( SetDac(s_sineTable[ s_index]); if (s_index == 63) ( s_index = 0; ) else ( s_index++; ) )

¿Qué obtendremos como resultado? Obtendremos oscilaciones sinusoidales con una frecuencia de 1000/64 Hz.

Ahora aumentemos el índice en la interrupción no en 1, sino en dos.
Obviamente, la frecuencia de oscilación de salida ya será 1000/64 * 2 Hz.

En general, para obtener la frecuencia F, es necesario aumentar el índice en la tabla de la siguiente manera:
sumar = F / 1000 * 64

Este número puede ser fraccionario, pero para obtener alta velocidad se utiliza la aritmética de punto fijo.

El número de entradas en la tabla y la frecuencia del temporizador afectan la calidad del sonido sintetizado. En nuestro caso, 64 entradas en la tabla por período son suficientes y la frecuencia del temporizador es de 12 kHz. La frecuencia mínima aceptable del temporizador es 8 kHz, la ideal es 44 kHz.

Evidentemente, con una frecuencia de temporizador de 12 kHz, podemos generar un máximo de ondas cuadradas de 6 kHz, ya que necesitamos realizar al menos dos cambios por período. Sin embargo, las frecuencias más altas aún serán reconocibles si el estado de salida se calcula correctamente en cada tic del temporizador.

Puede ingresar valores para el período de oscilaciones no sinusoidales en la tabla y obtener un sonido diferente.

Atenuación

Si un instrumento musical se basa en cuerdas (por ejemplo, un piano), después de presionar una tecla el sonido se desvanece suavemente. Para obtener un sonido de sintetizador más natural, es necesario reducir suavemente la amplitud de las vibraciones después del inicio de la nota (“envolver” las vibraciones en una forma de atenuación – “envolvente”).

El reproductor contiene una tabla de caída que utiliza para reducir la amplitud de la onda sinusoidal (u otra forma de onda) desde el momento en que comienza la nota.
"Sine" "envuelto" en tal caparazón se asemeja al sonido de una caja de música mecánica.

Síntesis de meandro

La forma especial de la onda meandro permite simplificar significativamente la síntesis. En este caso no se utilizan tablas. Es suficiente calcular qué estado (1 o 0) debe tener la salida a una frecuencia determinada en el tic actual del temporizador. Esto se hace usando aritmética de enteros y funciona muy rápidamente, lo que explica la popularidad del uso de una onda cuadrada para reproducir melodías en decodificadores de 8 bits.

Ejemplo: declarar un contador:

Estático uint16_t s_counter = 0;

el cual aumentaremos en 0x8000 en cada interrupción del temporizador, y el bit más significativo del contador se enviará al puerto:

// Comparación de salida del Timer1 Una interrupción de rutina de servicio void timer1_compa_isr(void) ( PORTA.0 = (s_counter >> 15) & 1; s_counter += 0x8000; )

Dado que 0x8000 + 0x8000 = 0x10000, la variable s_counter se desborda, el bit 17 se descarta y se escribe 0x0000 en la variable.
Así, con una frecuencia de temporizador de 8KHz, la salida será una onda cuadrada de 4KHz.
Si aumenta el contador en 0x4000, obtendrá una onda cuadrada de 2 KHz.

En general, puedes obtener la frecuencia F sumando:
sumar = F/8000 * 0x10000

Por ejemplo, para obtener una onda cuadrada con una frecuencia de 1234 Hz, debe agregar 0x277C. La frecuencia real diferirá ligeramente de la dada, porque estamos redondeando el término a un número entero. Esto es aceptable en un sintetizador.

Síntesis de sonidos de instrumentos reales.

Puedes digitalizar el sonido de la nota delante del piano (usando un ADC para almacenar los valores de amplitud del sonido en la memoria a intervalos regulares):
y luego reproduzca el sonido (usando el DAC para emitir los valores grabados a intervalos regulares).

En general, para sintetizar baterías, es necesario grabar sonidos de batería y reproducirlos en el momento adecuado. En las consolas de 8 bits se utiliza “ruido blanco” en lugar de sonidos de batería. Los valores de amplitud del “ruido blanco” se obtienen mediante un generador números al azar. Los costos de memoria son mínimos.
hxMidiPlayer utiliza “ruido blanco” para la síntesis de batería.

Mezcla de canales

La amplitud del sonido en un determinado tic del temporizador se calcula para cada canal por separado. Para obtener el valor de amplitud final, debe sumar los valores de todos los canales. Correctamente, es necesario ajustar la suma, ya que el volumen percibido obedece a una dependencia logarítmica, pero en un sintetizador tan simple tendrás que conformarte con una simple suma. Por tanto, la amplitud máxima de cada canal es 255/N.

Salida de sonido desde AVR

Después de realizar todos los cálculos necesarios, el reproductor recibe el nivel de señal que debe convertirse a analógica. Para estos fines, puede utilizar un DAC o PWM externo.
Cabe señalar aquí que en ambos casos es aconsejable filtrar la señal recibida para eliminar el ruido de alta frecuencia que surge debido a la baja frecuencia de síntesis y redondeo.

Salida a DAC paralelo externo

Dado que no tiene sentido utilizar chips DAC precisos, estos proyectos suelen conformarse con una matriz R2R:

Con este esquema, simplemente enviamos la amplitud calculada al puerto:

PORTB = muestra;

Defectos:
1) la salida de la matriz R2R es demasiado señal débil, es obligatorio el uso de un amplificador analógico;
2) es necesario utilizar al menos 5 pines (y preferiblemente 8);
Este método se justifica sólo cuando no hay canales PWM libres.

(Para guardar pines, puede usar un ADC externo con una interfaz SPI).

PWM

Si hay un canal PWM libre, la forma más sencilla es utilizar este método.

Inicialización PWM (ATMega8):

// Inicialización del temporizador/contador 2 // Fuente del reloj: Reloj del sistema // Valor del reloj: 20000.000 kHz // Modo: PWM rápido top=0xFF // Salida OC2: PWM no invertido ASSR=0x00; TCCR2=0x69; TCNT2=0x00; OCR2=0x00; Y el resultado de muestra: void Player_Output(uint8_t sample) (OC2 = sample.)

La práctica común para usar PWM implica suavizar la señal de salida usando un filtro RC:

Desafortunadamente, después del filtrado la señal se debilita demasiado, por lo que hay que fabricar un amplificador analógico para conectar el altavoz.

Para simplificar el circuito, es mejor permanecer "digital" hasta el propio altavoz. Dado que un altavoz barato todavía no puede reproducir frecuencias superiores a 30 kHz, no es necesario filtrarlas. El propio difusor “filtrará” las altas frecuencias PWM.

Si necesita obtener más corriente, puede utilizar un amplificador de transistores. Se selecciona R1 para proporcionar la corriente requerida al altavoz.

Así es como puedes conectar pequeños parlantes de juguetes:

Para altavoces más grandes, es mejor ensamblar el variador con 2 transistores e instalar un filtro LC para eliminar el ruido:

El condensador C1 sirve para limitar la corriente a través del altavoz cuando el PWM no está funcionando. Además, debido a la inclusión de un condensador en serie, una señal simétrica con respecto a cero llega al altavoz. Por lo tanto, el cono del altavoz se moverá con respecto a la posición central "relajada", lo que tiene un efecto positivo en la calidad del sonido.
En este caso, los transistores funcionan en modo de conmutación, por lo que no es necesario compensar el desplazamiento de la base.

PWM, conexión de dos pines

La desventaja de los dos primeros circuitos es que el altavoz recibe corriente en una dirección. Si cambiamos la dirección de la corriente, el volumen se puede aumentar 2 veces sin exceder la potencia permitida. Para hacer esto, el altavoz se conecta a dos pines del microcontrolador: invertido y no invertido, por ejemplo OC1A y /OC1A. Si no hay salida no invertida, puedes utilizar el segundo canal en modo invertido (OC1B):

// Inicialización del temporizador/contador 1 // Fuente del reloj: Reloj del sistema // Valor del reloj: 24500.000 kHz // Modo: PWM rápido top=0x00FF // Salida OC1A: No Inv. // Salida OC1B: Invertida // Cancelador de ruido: Desactivado // Captura de entrada en flanco descendente // Interrupción de desbordamiento del temporizador 1: Desactivado // Interrupción de captura de entrada: Desactivado // Interrupción de coincidencia de comparación A: Desactivado // Interrupción de coincidencia de comparación B: Desactivado TCCR1A =0xB1; TCCR1B=0x09; TCNT1H=0x00; TCNT1L=0x00; ICR1H=0x00; ICR1L=0x00; OCR1AH=0x00; OCR1AL=0x00; OCR1BH=0x00; OCR1BL=0x00; void Player_Output (muestra uint8_t) (OCR1A = muestra; OCR1B = muestra;)

PWM, dos pines, amplificador clase D

La desventaja de los circuitos propuestos es el consumo de corriente durante el silencio.
Para nosotros, "silencio" corresponde a un nivel de señal de 128, es decir, PWM con un 50% de llenado: ¡la corriente siempre fluye a través del altavoz!

Al cambiar ligeramente el software, puede obtener un amplificador de clase D de software y hardware bastante potente:

Void Player_Output(muestra uint8_t) ( if (muestra >= 128) ( TCCR2=0x21; //normal, borrar al comparar TCCR2=0x21 | 0x80; //BORRAR OC2 PORTC.0 = 0; TCCR2=0x69; //non -invertir PWM OCR2 = (muestra-128) * 2; ) else // si (muestra< 128) { TCCR2=0x31; //normal, set on compare match TCCR2=0x31 | 0x80; //SET OC2 PORTC.0 = 1; TCCR2=0x79; //inverting PWM OCR2 = (128-sample) *2; } }

En este caso, un par de transistores está conectado a la salida PWM, el segundo, a una salida digital normal.

Como puede ver en el código, consideramos una señal por encima de 128 como una corriente dirigida en una dirección y una señal por debajo de 128 como una corriente dirigida en la otra dirección. En 128, ambos pines de altavoz están conectados al mismo pin de fuente de alimentación y no hay corriente. Al desviarse del nivel 128, el llenado PWM aumenta y una corriente de la polaridad correspondiente fluye a través del altavoz.

Un punto importante en la implementación es la conmutación forzada de la salida PWM al estado deseado en el momento de cambiar el segundo pin (digital normal) (PORTC.0). La escritura en el registro OCR2 se almacena en un búfer para eliminar fallos de PWM. Necesitamos cambiar la salida PWM inmediatamente, sin esperar al final del período.

Este último circuito es, en mi humilde opinión, la mejor opción en términos de simplicidad, ahorro de energía y potencia de salida.

Salida de sonido con forma de onda SquareWave

Al sintetizar un meandro, se utilizan algoritmos simplificados.

Cada canal (incluida la batería) emite 0 o 1. Por lo tanto, un tocadiscos de 3 canales emite valores en el rango 0..3. Por lo tanto, cuando se utiliza PWM, el procedimiento de salida es el siguiente:

Void Player_Output(muestra uint8_t) ( OCR2 = muestra * (255 / HXMIDIPLAYER_CHANNELS_COUNT); )

Si no utiliza PWM, para generar una melodía de 3 canales, son suficientes dos salidas digitales normales y una matriz R2R de 2 bits.

formato midi

Si observa el código de melodía resultante, puede ver fácilmente que la matriz utiliza números repetidos de un rango pequeño. Esto es comprensible: la melodía utiliza un número limitado de notas dentro de 1-2 octavas, el tempo de la melodía es fijo - retrasos iguales, el número de canales está en el rango de 0...15.
Todo esto significa que la matriz resultante se puede reducir significativamente aplicando algún tipo de algoritmo de compresión.
Algoritmos como ZIP proporcionan una buena compresión, pero también requieren mucha memoria para funcionar (diccionario ZIP - 64 Kb). Podemos utilizar un método de compresión muy simple que prácticamente no requiere memoria, cuya esencia es la siguiente.

En un byte, todos los números están distribuidos uniformemente en el rango de 0 a 255 y cada número está representado por 8 bits. En nuestro caso, algunos números son mucho más comunes que otros. Si codifica los números que aparecen con frecuencia con menos bits y los que ocurren con menos frecuencia con más, puede obtener una ganancia de memoria.

Elegimos un método de codificación fijo: las combinaciones de bits 000,001 y 010 (longitud - 3 bits) representarán los 3 números que aparecen con más frecuencia. Combinaciones de bits 0110, 0111 (longitud – 4 bits): los 2 siguientes números más comunes, etc.:

//000..010 - 0..2 //011 x 3..4 //100 xx 5..8 //101 xxx 9..16 //110 xxx 17..24 //111 inmediato

La combinación que comienza con 111 (longitud – 11 bits) codificará todos los demás números.
El método de codificación de bits puede ser diferente. Probé varios métodos y elegí este porque ofrecía los mejores resultados con dichos datos.

El procedimiento de compresión se ve así:
1. Calcule el número total de números X en la secuencia para X = .
2. Ordene por frecuencia decreciente de aparición en la transmisión.
3. Toma los primeros 25 números. Se codificarán en menos bits.
4. Codifique el flujo de entrada.

La salida es una matriz de los 25 números que ocurren con más frecuencia y un flujo de bits.
Esta compresión le permite lograr una compresión del 50% con costos mínimos de memoria y rendimiento. Desafortunadamente, esto aumenta el código del reproductor, por lo que no se recomienda la compresión para melodías cortas.

Almacenamiento de frecuencias de notas

Es bastante caro almacenar las frecuencias de todas las notas en una tabla desde la memoria. De hecho, existe una fórmula para determinar la frecuencia de una nota por su número midi:

F = 2^((N - 69)/12) * 440, Hz

Pero calcular la potencia fraccionaria es bastante difícil. En cambio, el reproductor almacena 12 frecuencias de notas en la octava superior. Las frecuencias de las notas en octavas inferiores se determinan disminuyendo la frecuencia 2^Y veces más, donde Y es el número de octavas hacia abajo.

Mayor desarrollo de la compresión.

La melodía contiene a menudo fragmentos repetidos (“estribillos”, “versos”). Al encontrar fragmentos repetidos y presentar la melodía en forma de fragmentos, puede reducir la melodía en otro 50%, casi sin perder tiempo. RAM y productividad. No implementé dicho algoritmo para no complicar el proyecto.

Descripción del programa

Ventana principal del programa convertidor:

El botón Cargar Midi le permite cargar un archivo midi. El programa inmediatamente comienza a reproducir el archivo con los parámetros seleccionados actualmente, simulando el sonido que habrá en el hardware.

La ventana de información (4) muestra:
– Duración: duración del fragmento de melodía seleccionado en ms;
– Máx. canales de sintetizador activos: número máximo de canales de sintetizador activos simultáneamente;
– Máximo de canales de batería activos: el número máximo de canales de sintetizador activos simultáneamente que reproducen “batería”;
– Notas estéreo activas máximas: número máximo de canales que reproducen la misma nota (ver más abajo);
– Tamaño estimado, bytes – Tamaño de la melodía en bytes. En el modo “Muestra personalizada”, el tamaño se muestra como A+B, donde A es el tamaño de la melodía y B es el tamaño de la muestra. El tamaño del código del reproductor no se especifica aquí.

La ventana de progreso muestra la posición de reproducción actual.
Puede hacer clic en la barra de progreso para iniciar la reproducción desde el punto especificado.
Los cuadros de entrada a la izquierda y a la derecha le permiten especificar el principio y el final del fragmento de melodía en ms.

La etiqueta "No hay suficientes canales para reproducir la melodía" en rojo indica que no hay suficientes canales de sintetizador para reproducir la melodía con la configuración actual. Si el reproductor no encuentra un canal libre, apaga la nota más antigua. En muchos casos esto funcionará bien. Sólo tiene sentido aumentar el número de canales cuando la melodía suena incorrecta al oído.

La configuración se puede dividir en configuración del reproductor y configuración de procesamiento de archivos midi. El reproductor podrá reproducir el código de melodía resultante si la configuración del reproductor y el código de melodía se crearon con la misma configuración del reproductor. Además, el reproductor podrá reproducir una melodía cuyo código fue creado para un reproductor con un número menor (pero no mayor) de canales.

La configuración del hardware del reproductor incluye:

– Frecuencia de muestreo – frecuencia de síntesis. La frecuencia máxima de fusión se determina experimentalmente. Basado en Atmega 16MHz, puede comenzar en 12000Hz para un reproductor con 6 canales y aumentarlo según desee hasta que la distorsión de la melodía se note de oído en el reproductor de hardware. La frecuencia máxima depende del número de canales, la forma de onda y la complejidad de la melodía misma.

– Forma de onda – forma de onda:
– Onda cuadrada – meandro;
– Seno – seno;
– Sinusoidal + Envolvente – sinusoidal con atenuación;
– Forma de onda * + Envolvente – varias opciones para ondas no sinusoidales con y sin atenuación;
– Muestra personalizada: utiliza una muestra del instrumento.

El botón "Cargar muestra" le permite cargar una muestra desde un archivo WAV. El archivo WAV debe estar en PCM mono de 8 bits, 4173 Hz, C-5. Sugerencia: puedes aumentar la frecuencia y bajar la nota, pero cambia el tono en la configuración del reproductor. No hay comprobaciones de formato: si el formato es diferente, el sonido no se reproducirá correctamente.
Tono: le permite cambiar el tono del sonido. Por ejemplo, para tocar 1 octava más arriba, necesitas configurar Pitch +12.

Utilice compresión: utilice compresión de melodía.
Habilitar sintetizador de batería: habilita el sintetizador de batería.

Canales del reproductor: el número de canales del sintetizador (el número máximo de notas que sonarán simultáneamente).

La configuración de procesamiento de archivos midi incluye:

Normalmente, este ajuste fino no es necesario. Estas configuraciones se pueden dejar como predeterminadas.

API del jugador

La implementación del reproductor se encuentra en los archivos Common\hxMidiPlayer.c y Common\hxMidiPlayer.h. Estos archivos deben incluirse en el proyecto. También necesita crear un archivo hxMidiPlayer_config.h, en el que debe colocar la configuración.
El reproductor está escrito en C sin inserciones de ensamblaje, lo que facilita su portabilidad a otros microcontroladores.

void externo Player_StartMelody(const flash TMelody* _pMelody, uint16_t _delay);

Empiece a tocar la melodía. _delay establece el retraso inicial antes de la reproducción, 255 unidades = 1 segundo.

Anular Player_Stop();

Deja de tocar la melodía.

bool externo Player_IsPlaying();

Devuelve false si la melodía ha terminado de sonar.

void externo Player_WaitFinish();

Espere hasta que termine de reproducirse la melodía.

Vacío externo Player_TimerFunc();

Esta función debe llamarse en una interrupción del temporizador a la frecuencia de muestreo especificada en la configuración. Cuando la melodía haya terminado de sonar, no tendrás que hacer ninguna llamada.

Player_Output nulo externo (muestra uint8_t);

Debe ser implementado por el usuario. Lo llama el reproductor cuando es necesario generar la siguiente muestra.

void externo Player_Started();

Debe ser implementado por el usuario. Se llama cuando el reproductor comienza a tocar una melodía. Se puede utilizar para configurar interrupciones del temporizador.

void externo Player_Finished();

Debe ser implementado por el usuario. Se llama cuando el jugador ha terminado de tocar la melodía. Se puede utilizar para desactivar las interrupciones del temporizador o comenzar a reproducir otra melodía.

//#definir NOTAS_TO_EEPROM //#definir SINETABLE_TO_EEPROM //#definir ENVELOPE_TO_EEPROM

Estas líneas deben descomentarse en el archivo hxMidiPlayer_config.h si la tabla de notas, la tabla de senos y la tabla de atenuación deben ubicarse en la eeprom.

Proyectos de ejemplo

ATMega644Ejemplo: proyecto para ATMega644, 25 MHz, salida PWM en PB3.

Requisitos de memoria

Mesa. Tamaño del reproductor y melodías en flash.

*al agregar un reproductor a un proyecto existente que no esté vacío, el tamaño del código será menor

**no hay suficientes canales para la reproducción normal de melodías

Melodía 1: bach_minuet_in_g.mid, 35 seg
Melodía 2: yiruma-river_flows_in_you.mid, 165 seg
Melodía 3: Franz Schubert – Serenade.mid, 217 seg

Como puede ver en la tabla, en la configuración mínima puede exprimir una melodía bastante larga incluso en el ATTiny2313. La compresión puede reducir la melodía más de dos veces, pero el tamaño del código del reproductor aumenta en ~600 bytes.

Las tablas de notas sinusoidales y de caída se pueden colocar en EEPROM, ahorrando aproximadamente 16, 50 y 100 bytes de memoria flash respectivamente.

Cuando utilice una muestra de un archivo wav, deberá agregar el tamaño de la muestra en bytes al tamaño del código del reproductor.

Ejemplo de uso

Como ejemplo del uso de un reproductor, consideremos el proceso de creación de una caja de música.

Tomamos una caja de MDF confeccionada:

Como microcontrolador, tomamos ATTiny85 en paquete SO-8 como el más barato con una cantidad de memoria suficientemente grande. Lo overclockearemos a 27MHz para conseguir una frecuencia de síntesis de 18KHz con 4 canales Sine+Envelope.

El amplificador será clase D con 4 transistores para ahorrar baterías.

Los transistores funcionan en modo de conmutación y pueden ser de cualquier tipo. El inductor L1 y el condensador C6 se seleccionan según el gusto para obtener un sonido sin ruido de alta frecuencia. R1 y R2 se pueden elevar hasta 2K para bajar el volumen y reducir el rebote de los altavoces.

El interruptor de límite de la unidad de disco encaja perfectamente, como si hubiera sido creado específicamente para la caja (funciona para abrirse; cuando abre la tapa, se suministra energía a la placa):

Las fuentes del firmware se encuentran en el directorio ATTiny85MusicBox.

8Kb cabe:
1) reproductor: 18000Hz, 4 canales, Sine+Envelope, Pitch+12, compresión, reproduce melodías una por una (la última se almacena en EEPROM)
2) Yiruma – El río fluye en ti
3) Franz Schubert – Serenata
4) PI Tchaikovsky “Octubre”

Resultado en vídeo:

Mayor desarrollo

En principio, el reproductor se puede desarrollar aún más, llevándolo a un reproductor MIDI o MOD completo. Personalmente creo que para obtener una melodía de alta calidad sería más fácil conectar una tarjeta SD y reproducir cualquier archivo WAV con mucho más mejor calidad de lo que generalmente es posible obtener mediante síntesis de software. Y un reproductor de este tipo es mucho más sencillo en cuanto a software y hardware. El nicho de hxMidiPlayer es agregar buen sonido a proyectos ya preparados cuando quedan un par de patas y un poco de espacio en el flash. Ya en su forma actual hace frente a esta tarea “excelentemente”.

Creo que esto puede solucionar el problema de crear todo tipo de cajas de música/campanas en AVR :)

La continuación de la lección tomó mucho tiempo, lo cual es comprensible; tuve que dominar el trabajo con tarjetas de memoria y archivos. sistema de grasa. Pero aún así sucedió, la lección está lista; de hecho, un milagro de Año Nuevo.

Para no sobrecargar el artículo con información, no describiré la estructura del formato del archivo wav, hay información más que suficiente en los motores de búsqueda. Baste decir que si abre un archivo con algún tipo de editor hexadecimal, los primeros 44 bytes contienen toda la información sobre el tipo de archivo, frecuencia de muestreo, número de canales, etc. Si necesita analizar el archivo, lea esto encabezado y serás feliz.

Los datos de carga útil comienzan en 44 bytes y contienen esencialmente los niveles de voltaje que componen el sonido. Ya hablamos de los niveles de voltaje en la última parte de la lección. Por lo tanto, todo es simple, debe enviar estos pasos al altavoz en la frecuencia de muestreo del archivo.

¿Cómo hacer temblar físicamente un altavoz? Debe generar estos niveles de voltaje usando PWM o usar R2R. En cualquier caso, es muy sencillo de usar, lee el número, ponlo en OCR o PORTx. Luego, después de cierto tiempo, sustituí el siguiente valor y así hasta el final del archivo.

Ejemplo, un determinado archivo wav, los datos provienen del byte 44=0x2C, ahí está escrito el número 0x80, reproducimos el sonido, por ejemplo, por PWM del primer temporizador, escribimos OCR1A=0x80; Digamos que la frecuencia de muestreo de la muestra es de 8 kHz, por lo que la interrupción debe configurarse en la misma frecuencia. En la interrupción, sustituya el siguiente valor 0x85 después de 1/8000 = 125 µs.

¿Cómo configurar la interrupción a 8 kHz? Recordemos que si el temporizador funciona a una frecuencia de 250 kHz, entonces el registro de comparación de interrupciones debe sustituirse por (250/8)-1=31-1 o 0x1E. Con PWM también todo es sencillo: cuanto mayor sea la frecuencia a la que funcione, mejor.

Para que el firmware funcione, aceptaremos que la unidad flash esté formateada en FAT32, utilizando la biblioteca PetitFat de la lección 23.2. El archivo está en formato wav, ya sea 8kHz o 22.050kHz, mono. Nombre del archivo 1.wav. Analicemos el firmware.

#incluir #include "diskio.h" #include "pff.h" búfer de caracteres sin firmar [512]; /* buffer en el que se copia la información desde la unidad flash */ recuento de int volátil sin signo; //contador de datos copiados interrumpir [TIM2_COMP] void timer2_comp_isr(void) //interrupción en la que se sustituyen valores(OCR1A = búfer[recuento]; // salida de sonido al altavoz si (++ cuenta >= 512) //aumentar el contador contar = 0; //si se reinicia 512) void main(void) ( unsigned int br; /* contador de lectura/escritura de archivos */ char buf sin firmar = 0; //variable que define qué parte del buffer se lee FATFSfs; /* Espacio de trabajo (objeto del sistema de archivos) para unidades lógicas */ PUERTOB= 0x00 ; DDRB= 0x02 ; //salta la cuña ocr1a // Inicialización del temporizador/contador 1// Fuente del reloj: Reloj del sistema // Valor del reloj: 8000.000 kHz // Modo: PWM rápido top=0x00FF // Salida OC1A: No Inv. TCCR1A= 0x81; TCCR1B= 0x09; TCNT1= 0x00 ; OCR1A= 0x00 ; // Inicialización del temporizador/contador 2// Fuente del reloj: Reloj del sistema // Valor del reloj: 250.000 kHz // Modo: CTC top=OCR2 TCCR2= 0x0B ; TCNT2= 0x00 ; //OCR2=0x1E; //configurando el registro de comparación para 8kHz OCR2= 0xA ; //para 22kHz #asm("sei") // Inicialización de temporizador(es)/contador(es) de interrupción(es) si (disk_initialize() == 0) //inicializa la unidad flash( pf_mount(&fs) ; //montar sistema de archivos pf_open("1.wav"); //abrir un hilo pf_lseek(44); //mueve el puntero a 44 pf_read(búfer, 512,& br); //por primera vez tragamos 512 bytes a la vez TIMSK= 0x80 ; //encender la música mientras (1) ( if (! buf && count> 255 ) //si se reproducen más de 255 bytes,( pf_read(& buffer[ 0 ], 256 ,& br); //luego leemos la información de la unidad flash en la primera mitad del búfer buf= 1 ; si (br< 256 ) //si el buffer no contiene 256 valores, significa el final del archivo romper ; ) si (buf && cuenta< 256 ) { pf_read(& buffer[ 256 ] , 256 ,& br) ; // lee la segunda parte del buffer desde la unidad flash bufo = 0; si (br< 256 ) break ; } } TIMSK = 0x00 ; //глушим все pf_mount(0x00 ) ; //desmantelar el velo) mientras (1 ) ( ) )

#incluir #incluye "diskio.h" #incluye "pff.h" búfer de caracteres sin firmar; /* búfer en el que se copia la información desde la unidad flash */ volatile unsigned int count; //interrupción del contador de datos copiados void timer2_comp_isr(void) //interrupción en la que se sustituyen los valores (OCR1A = buffer; //envía sonido al altavoz if (++count >= 512) //aumenta el contador = 0; //if 512 reset ) void main(void) ( unsigned int br; /* contador de lectura/escritura de archivos */ unsigned char buf = 0; //variable que define qué parte del buffer se lee FATFS fs; /* Trabajando área (objeto del sistema de archivos) para unidades lógicas */ PORTB=0x00; DDRB=0x02; //salte el shim ocr1a // Inicialización del temporizador/contador 1 // Fuente del reloj: Reloj del sistema // Valor del reloj: 8000,000 kHz // Modo: Fast PWM top=0x00FF // Salida OC1A: No Inv. TCCR1A=0x81; TCCR1B=0x09; TCNT1=0x00; OCR1A=0x00; // Inicialización del temporizador/contador 2 // Fuente del reloj: Reloj del sistema // Valor del reloj : 250,000 kHz // Modo: CTC top= OCR2 TCCR2=0x0B; TCNT2=0x00; //OCR2=0x1E; //configurando el registro de comparación para 8kHz OCR2=0xA; //para 22kHz #asm("sei") // Temporizador(es)/Contador(es) Interrupción(es) inicialización if(disk_initialize()==0) //inicializa la unidad flash ( pf_mount(&fs); //montar el sistema de archivos pf_open("1.wav"); //abre la rama pf_lseek(44); //mueve el puntero a 44 pf_read(buffer, 512,&br); //por primera vez tragamos 512 bytes a la vez TIMSK=0x80; //enciende la música mientras(1) ( if(!buf && count>255) //si se han reproducido más de 255 bytes, ( pf_read(&buffer, 256,&br);//luego lee la información del flash conducir a la primera mitad del buffer buf=1 ; si (br< 256) //если буфер не содержит 256 значений значит конец файла break; } if(buf && count<256) { pf_read(&buffer, 256,&br); // читаем во вторую часть буфера с флешки buf = 0; if (br < 256) break; } } TIMSK = 0x00; //глушим все pf_mount(0x00); //демонтируем фат } while (1) { } }

Para comprobarlo, conectamos un altavoz al pin OCR1A mediante un condensador de 100uF, "+" al pin del microcontrolador, "-" al altavoz. “-” altavoz a tierra, “+” al condensador.

No espere una señal fuerte en la salida; necesita un amplificador para que suene fuerte. Esto se ve claramente en el vídeo. Para la prueba cargué el gallo con 8 kHz y la pista con 22 kHz.

Quien lo desee puede aumentar con seguridad la frecuencia del timer2 para reproducir archivos de 44 kHz; los experimentos muestran que se puede conseguir una calidad de sonido bastante buena. En el video, el sonido es débil y la calidad es mala, pero en realidad esto se debe a que lo filmé con una cámara.

También estoy publicando materiales amablemente proporcionados por Apparatchik: el código fuente de GCC, a partir del cual se escribió el firmware para CAVR.

Y vídeo con reproducción de 44kHz.

Aprovecho para felicitar a todos por el Año Nuevo, deseo que todo el firmware y dispositivos funcionen para ustedes :)

proyecto de reproductor wav en Atmega8

Escribí un módulo de software que permite agregar la función de reproducir melodías o secuencias de sonidos a casi cualquier proyecto en el microcontrolador AVR.

Características del módulo:

Fácil integración con un proyecto ya preparado

Sólo se utiliza el temporizador t2 de 8 bits, aunque sigue siendo posible utilizarlo para sondear o formar intervalos de tiempo.

El módulo es ajustable a casi cualquier frecuencia del generador de reloj.

El tono de las notas se especifica como constantes simbólicas (C0, A2, etc.) o en Hertz.

Las duraciones se especifican en forma estándar (cuartos, octavos, etc.) o en milisegundos.

Es posible establecer el tempo de reproducción de la melodía y el número de repeticiones.

Durante la reproducción, la melodía se puede pausar.


Conexión de un módulo de sonido

1. Copie todos los archivos del módulo (tone.h, sound.h, sound.c) en la carpeta del proyecto.

2. Conecte el archivo sound.c al proyecto.

Para IAR `a: haga clic derecho en la ventana del espacio de trabajo y seleccione Agregar > Agregar archivos…

Para WINAVR es más o menos lo mismo, sólo es necesario agregar sound.c al archivo MAKE:

SRC = $(TARGET).c sonido.c

3. Incluya el archivo de encabezado sound.h en el módulo correspondiente. Por ejemplo, en principal.c

#incluir "sonido.h"

4. Establezca la configuración del módulo en el archivo sound.h.

//si comentas, la duración de las notas será

//calculado a partir del BPM especificado en la melodía

//si queda, entonces desde el valor especificado a continuación

//#definir SONIDO_BPM 24

//frecuencia de reloj μ

#definir SOUND_F_CPU 16U

//salida del microcontrolador en el que se generará el sonido

#definir PORT_SOUND PORTB

#definir PINX_SOUND 0

//número de melodías especificadas.

#definir SOUND_AMOUNT_MELODY 4

5. Agregue sus melodías a sound.c y escriba los nombres de las melodías en la matriz de melodías.

Agregar tonos de llamada

La melodía es una matriz de números de 16 bits y tiene la siguiente estructura

BPM (negras por minuto) es una constante que se utiliza para calcular la duración de las notas y determina la velocidad a la que se reproduce la melodía.

Los BPM pueden oscilar entre 1 y 24, lo que corresponde a 10 y 240 negras por minuto, respectivamente.

Si la duración de las notas/sonidos se especifica en milisegundos, entonces el BPM escrito en la matriz debe ser igual a 1.

Si la constante SOUND_BPM está comentada en el archivo de encabezado sound.h, entonces la duración de las notas se calcula durante la ejecución del programa de acuerdo con los BPM especificados en la matriz. Si SOUND_BPM no está comentado, la duración de las notas se calcula en la etapa de compilación, en función del valor de esta constante, y todas las melodías se reproducirán al mismo tempo. Esto limita la funcionalidad, pero ahorra algunos bytes de código.

Número de repeticiones. Puede tomar valores 1...254 y LOOP (255). LOOP: significa que la melodía se repetirá sin cesar hasta que se dé el comando SOUND_STOP o SOUND_PAUSE.

Duración de la nota– el tiempo durante el cual se genera un determinado tono sonoro o se mantiene una pausa. Se puede especificar en ms, utilizando la macro ms(x), o como valores de notas estándar: corcheas, semicorcheas, etc. A continuación se muestra una lista de duraciones admitidas. Si necesitas algunas duraciones exóticas, siempre puedes agregarlas en el archivo tone.h

n1 - nota completa

n2 - media nota

n4 - cuarto

n8 - octavo

n3 - octavo triplete

n16 - decimosexto

n6 - sextol

n32 - treinta segundos

tono de nota se especifica utilizando constantes simbólicas descritas en el archivo tone.h, por ejemplo C2, A1, etc. Además, el tono de las notas se puede especificar en Hertz utilizando la macro f(x).

¡El programa tiene restricciones en la frecuencia de sonido mínima y máxima!

Marcador de fin de melodía. El valor del último elemento de la matriz debe ser cero.

Usando el módulo de sonido

Al comienzo de main, debes llamar a la función SOUND_Init(). Esta función establece el pin de salida del microcontrolador, configura el temporizador T2 e inicializa las variables del módulo.

Luego, debe configurar el indicador de habilitación de interrupciones: __enable_interrupt(), porque el módulo usa desbordamiento del temporizador T2 e interrupciones coincidentes.

Después de esto, puedes empezar a tocar melodías.

Por ejemplo, así:

SONIDO_SetSong(2);

SONIDO_Com(SONIDO_REPRODUCCIÓN); //reproducir melodía

//colocamos el puntero en la segunda melodía

//e iniciar la reproducción

SOUND_PlaySong(2);

La reproducción de la melodía se puede detener en cualquier momento emitiendo el comando SOUND_STOP.
También puedes pausar la melodía usando el comando SOUND_PAUSE. La emisión posterior del comando SOUND_PLAY reanuda la reproducción de la melodía desde el punto donde se detuvo.

En principio, esta funcionalidad no es particularmente necesaria (la acabo de inventar) y cuando se trabaja con el módulo, la función SOUND_PlaySong(unsigned char numSong) es suficiente;

Archivos

Puede descargar ejemplos de uso del módulo de sonido desde los enlaces siguientes. No dibujé un diagrama porque allí todo es sencillo. conectado al pin PB0, el botón de inicio de melodías está conectado al pin PD3. Hay 4 melodías definidas en los proyectos. Al presionar el botón se inicia una nueva melodía cada vez. Se utiliza el microcontrolador atmega8535. Inicialmente quería molestarme con un proyecto con cuatro botones: REPRODUCIR, DETENER, PAUSA y SIGUIENTE, pero luego pensé que era innecesario.

PD: El módulo no ha sido sometido a pruebas exhaustivas y se proporciona "tal cual". Si hay alguna propuesta racional, finalicémosla.

En este artículo, veremos cómo tocar tonos y aprenderemos a tocar una melodía monofónica.

Preparándose para el trabajo

El programa declara dos matrices. Matriz con notas notas contiene una lista simple de notas. Estas notas coinciden con la duración del sonido en la matriz. late. La duración en la música está determinada por el divisor de una nota en relación con la nota completa. El valor tomado como nota entera es 255 . Dividiendo este número se obtienen mitades, cuartos y octavos.
Tenga en cuenta que la duración de la primera nota no se obtiene dividiendo 255 por una potencia de dos. Aquí tendrás que pasar a la teoría musical. Se pueden visualizar las notas de la melodía original. Estas notas se combinan en tresillos. Cuando se combinan de esta manera, tres corcheas suenan igual que una negra. Por tanto su duración relativa es 21.
El usuario también debe especificar explícitamente el número de notas en la secuencia con la directiva:

# definir SEQU_SIZE 19

En el programa principal, en primer lugar, se recalculan las matrices de frecuencia y la duración en períodos de señales y la duración de las notas.
Con períodos de señal (matriz período_señal) todo es sencillo. Para obtener la duración del período en microsegundos, simplemente divida 1.000.000 por la frecuencia de la señal.
Para calcular la duración absoluta de las notas se debe especificar el tempo de la obra musical. Esto se hace por directiva.

# definir TEMPO 108

El tempo en la música es el número de negras por minuto. En línea

# definir WHOLE_NOTE_DUR 240000 / TEMPO

Se calcula la duración de una nota completa en milisegundos. Ahora basta con recalcular los valores relativos de la matriz usando la fórmula late a matriz absoluta nota_duración.
En el bucle principal, la variable tiempo transcurrido se incrementa después de cada período de la señal reproducida por la duración de este período hasta que excede la duración de la nota. Vale la pena prestar atención a esta entrada:

mientras(tiempo_transcurrido< 1000 * ((uint32_t) note_duration[ i] ) )

Variable tiempo transcurrido 32 bits y los elementos de la matriz. notas_duración 16 bits. Si un número de 16 bits se multiplica por 1000, se garantiza un desbordamiento y la variable tiempo transcurrido será comparado con la basura. Modificador (uint32_t) convierte un elemento de matriz notas_duración[i] en un número de 32 bits no hay desbordamiento.
Puedes ver otra característica en el bucle de audio. No será posible utilizar la función. _delay_us(), ya que su argumento no puede ser una variable.
Para crear tales retrasos, utilice la función VarDelay_us(). En él, un bucle con un retraso de 1 μs se desplaza un número específico de veces.

void VarDelay_us(uint32_t takt) ( while (takt- - ) ( _delay_us(1 ); ) )

Al tocar una melodía, se utilizan dos retrasos más. Si las notas se tocan sin pausas, se fusionarán en una sola. Para ello se inserta entre ellos un retardo de 1ms, especificado por la directiva:

# definir NOTAS_PAUSE 1

Después de cada ciclo completo de reproducción de la melodía, el programa se detiene durante 1 segundo y comienza a reproducirse nuevamente.
Como resultado, recibimos un código en el que es fácil cambiar el tempo, ajustar la duración o reescribir completamente la melodía. Para ello bastará con transformar únicamente la parte del programa con directivas y declaraciones de variables.

Tareas individuales

  1. En la melodía propuesta, intenta cambiar el tempo y haz una pausa de 5 segundos entre repeticiones.
  2. elementos de matriz late solo acepte valores de 0 a 255. Cambie el ancho de bits de los elementos de la matriz y observe la salida del compilador para ver cómo esto afecta la cantidad de memoria ocupada por el programa.
  3. Ahora intenta cambiar la melodía tú mismo. Por ejemplo, aquí está “Marcha Imperial” de la misma película: int notas = ( A4, R, A4, R, A4, R, F4, R, C5, R, A4, R, F4, R, C5, R, A4, R, E5, R, E5, R, E5, R, F5, R, C5, R, G5, R, F5, R, C5, R, A4, R); int tiempos = (50, 20, 50, 20, 50, 20, 40, 5, 20, 5, 60, 10, 40, 5, 20, 5, 60, 80, 50, 20, 50, 20, 50, 20, 40, 5, 20

    Si su automóvil no tiene una sirena sonora instalada y aún no puede decidir cuál comprar e instalar, entonces este artículo es solo para usted. ¿Por qué comprar alarmas caras si puedes montarlas todas tú mismo de una forma bastante sencilla?

    les presento dos de estos circuitos simples en los microcontroladores AVR ATmega8 y Attiny2313, o más bien simplemente se implementa el mismo circuito para funcionar en estos dos microcontroladores. Por cierto, en el archivo encontrará dos versiones de firmware para el microcontrolador Atmega8, de las cuales la primera reproduce un sonido similar a alarma de carro, y el segundo sonido es similar a la alarma de seguridad de un edificio (señal rápida y nítida).

    Puedes descargar todo el firmware a continuación en el archivo (todos están firmados), en el archivo también encontrarás una simulación de circuitos en Proteus, lo que significa que después de escuchar todas las canciones podrás elegir de la lista la que más te guste. .

    A continuación se muestra el diagrama de señalización para Atmega8.

    Lista de componentes de radio utilizados en el circuito Atmega8

    U1- microcontrolador AVR ATmega8-16PU de 8 bits, cant. 1,
    R1- Resistencia de valor nominal 47 Ohmios, núm. 1,
    R2, R3 - Resistencia con un valor nominal de 270 ohmios, no. 2,
    D2,D3-LED, no. 2,
    Altavoz LS1, no. 1,
    Sensor S1.

    Y en el circuito de señalización de Attiny2313, solo se cambió MK.
    U1- Microcontrolador AVR 8 bits ATtiny2313-20PU, cantidad. 1.

    placa de circuito impreso para Atmega8 se ve así:

    Como puedes ver el circuito es muy sencillo, solo hay un microcontrolador, 3 resistencias, 2 LED y un altavoz más. En lugar de un botón, puede utilizar un interruptor de láminas u otro contacto.

    El principio de funcionamiento es el siguiente. En cuanto aplicamos energía, el LED (en el circuito D3) inmediatamente se enciende o comienza a parpadear (según el firmware), y si no tocamos el sensor, la alarma se silenciará. Ahora, si se activa el sensor, la sirena también funcionará, el LED también parpadeará, pero D2.

    Si desea que los faros del automóvil parpadeen cuando la alarma está funcionando, para ello debe conectar el pin del microcontrolador 24 PC1 al relé a través de un transistor y el relé mismo a los faros. Para apagar la sirena, debe apagar y encender nuevamente el dispositivo, o simplemente presionar el botón. Para operar el microcontrolador, necesita un oscilador interno de 8 MHz,

    Si quieres mejorar de alguna manera el sonido de la alarma, puedes montar un amplificador con transistores y conectarlo al circuito. Eso es exactamente lo que hice, pero no lo representé en este diagrama.

    Pasemos al circuito del Attiny 2313, como dije antes tiene los mismos detalles y el mismo principio de funcionamiento, solo se le ha cambiado el MK y como resultado los pines conectados, este microcontrolador opera desde un Oscilador interno de 4 MHz, aunque se puede flashear a 1 MHz.

    A continuación se muestra el diagrama de conexión que ya está en Attiny2313.

    Para este MK escribí solo una versión del firmware, ensamblé todo en la placa de circuito, lo revisé y todo funciona bien.
    Y los fusibles deben configurarse como se muestra a continuación:





Arriba