
Esencia
Ya he creado varios dispositivos electrónicos para pasatiempos diferentes, y tengo una característica extraña: si hay un emisor de sonido piezoeléctrico (zumbador) en el tablero, después de terminar el trabajo principal del proyecto, empiezo a sufrir tonterías y hago que toque varias melodías (tanto como sea posible) ) Es especialmente útil incluir una melodía al final de un largo proceso para llamar la atención. Por ejemplo, lo usé cuando construí una cámara de exposición improvisada para iluminar la fotorresistencia, etc.
Pero cuando comencé a buscar ejemplos de generación de frecuencia para AVR en la red, por alguna razón me encontré con proyectos monstruosos o insuficientemente concisos que implementan la generación de frecuencia de sonido de una manera puramente programática. Y luego decidí resolverlo yo mismo ...
Digresión lírica
Mi hobby incluye la creación de varios dispositivos en microcontroladores, porque esto no se cruza con mi profesor. actividad (desarrollo de software), me considero un autodidacta absoluto, y en electrónica no es demasiado fuerte. De hecho, prefiero los microcontroladores PIC, pero sucedió que acumulé una cierta cantidad de microcontroladores Atmel AVR (ahora Microchip). Inmediatamente haga una reserva de que nunca tuve AVR en mis manos, es decir Este es mi primer proyecto en Atmel MCU, a saber, Atmega48pa. El proyecto en sí lleva a cabo una carga útil, pero aquí describiré solo una parte de ella relacionada con la generación de frecuencias de sonido. La prueba para generar frecuencias la llamé "buzic", como abreviatura de la música del timbre. Sí, casi lo olvido: en Habr hay un usuario con el apodo
buzic , quería advertir de inmediato que este memo no se aplica a él de ninguna manera, y por si acaso, inmediatamente me disculpo por usar la combinación de letras "Buzic".
Así que vamos
Conocí una gran cantidad de ejemplos de la red, todos ellos se basan en el ciclo más simple en el cuerpo principal del firmware o en la interrupción del temporizador. Pero todos usan el mismo enfoque para generar frecuencia:
- alimentar un alto nivel al pie del microcontrolador
- hacer un retraso
- alimentarse al pie del microcontrolador
Cambiando los retrasos y la configuración del temporizador: ajuste la frecuencia.
Este enfoque no me convenía mucho, porque No deseaba escribir código para el control manual del pie del microcontrolador. Me gustaría que la "piedra" genere la frecuencia de sonido para mí, y simplemente configuro los valores de ciertos registros, y así la cambio (frecuencia).
Al estudiar la hoja de datos (en lo sucesivo, DS), todavía encontré el modo de temporizador que necesitaba, y este modo, como habrás adivinado, es el modo CTC (Clear Timer on Compare Match). Como la función de reproducir música es, por decirlo suavemente, no es la funcionalidad principal, preferí seleccionar el temporizador 2 para ello (párrafo 22 de la SD).
Todo el mundo sabe que prácticamente cualquier microcontrolador tiene un modo de generación de señal PWM que se implementa en temporizadores y es completamente hardware. Pero en esta tarea, PWM no es adecuado porque solo se generará una frecuencia en el hardware. Por lo tanto, necesitamos PFM (modulación de frecuencia de pulso). Alguna similitud de PFM es el modo de temporizador CTC (cláusula 22.7.2 LH).
Modo CTC
El temporizador 2 en el microcontrolador Atmega48pa es de 8 bits, es decir, "marca" de 0 a 255 y luego va en un círculo. Por cierto, el temporizador puede ir en una dirección diferente, pero no en nuestro caso. El siguiente componente requerido es la Unidad de comparación. Hablando muy crudamente, este módulo es el iniciador de cualquier evento relacionado con el temporizador. Los eventos pueden ser diferentes, como interrupciones, cambios en el nivel de ciertas patas del microcontrolador, etc. (Obviamente, estamos interesados en el segundo). Como puede suponer, el módulo de comparación no solo se llama, sino que compara un valor específico seleccionado por el desarrollador del firmware con el valor actual del temporizador. Si el valor del temporizador alcanza el valor que establecemos, se produce un evento. Los eventos también pueden ocurrir cuando el temporizador se desborda o durante un reinicio.
Bien, hemos llegado a la conclusión de que es conveniente para nosotros en ciertos momentos que el temporizador, junto con el módulo de comparación, cambie independientemente el nivel en el pie del microcontrolador al contrario, generando pulsos.La segunda tarea es establecer los intervalos entre estos pulsos, es decir controlar la frecuencia de generación. Toda la singularidad del modo CTC radica en el hecho de que en este modo el temporizador no llega al final (255), sino que se restablece cuando se alcanza el valor establecido. En consecuencia, al cambiar este valor, podemos controlar la frecuencia. Por ejemplo, si establecemos el valor del módulo de comparación en 10, entonces el cambio de nivel en el pie del microcontrolador ocurrirá 20 veces más a menudo que si lo establecemos (el valor del módulo de comparación) en 200. ¡
Ahora podemos controlar la frecuencia!
Hierro

El pinout del microcontrolador muestra que necesitamos conectar nuestro zumbador a la pata de PB3 (OC2A) o la pata de PD3 (OC2B), porque OC2A y OC2B significan exactamente que en estas patas, el temporizador 2 puede generar señales.
El esquema que suelo usar para conectar el timbre es:
Y así armamos el dispositivo.Registros
En el párrafo anterior, decidimos la elección de la pierna: esta es PB3 (OC2A), trabajaremos con ella. Si necesita PD3, para ella todo será igual, lo que será claramente visible en la historia.
Configuraremos nuestro temporizador 2 cambiando 3 registros:
- TCCR2A - configuración de modo y selección de comportamiento
- TCCR2B - ajustes de modo y divisor de frecuencia del temporizador (también bits FOC - no los usamos)
- OCR2A (OCR2B para el caso de pierna PD3) - valor del módulo de comparación
Considere primero los registros TCCR2A y TCCR2B

Como puede ver, tenemos 3 grupos de bits que son importantes para nosotros: estos son bits de las series COM2xx, WGM2x y CS2x
Lo primero que necesitamos cambiar es WGM2x, esto es lo principal para elegir el modo de generación, estos bits se utilizan para seleccionar nuestro modo CTC.
nota: obviamente en LH el error tipográfico en "Actualización de OCR0x en" debería ser OCR2xEs decir el código será así:
TCCR2A = _BV(WGM21) ;
Como puede ver, TCCR2B aún no se utiliza. WGM22 debería ser cero, pero ya es cero.
El siguiente paso es configurar los bits COM2xx, más precisamente COM2Ax, porque trabajamos con la pata PB3 (para PD3 COM2Bx se usan de la misma manera). Lo que sucederá con nuestra pierna PB3 depende de ellos.
Los bits COM2xx dependen del modo que seleccionamos con los bits WGM2x, por lo que tendremos que encontrar la sección correspondiente en la LH. Porque tenemos el modo CTC, es decir no PWM, entonces estamos buscando una placa "Comparar modo de salida, no PWM", aquí está:

Aquí debe seleccionar "Alternar", de modo que el nivel en el tramo cambie al opuesto cuando el temporizador alcance el valor establecido. Cambio de nivel constante e implementa la generación de la frecuencia que necesitamos.
Porque los bits COM2xx también están en el registro TCCR2A, solo que cambia:
TCCR2A = _BV(COM2A0) | _BV(WGM21) ;
Naturalmente, también debe seleccionar el divisor de frecuencia con bits CS2x y, por supuesto, ajustar el pie PB3 a la salida ... pero aún no lo haremos para que cuando activemos el MK, no obtengamos un chirrido penetrante a una frecuencia incomprensible, pero cuando hagamos todos los demás ajustes y Gire el pie para salir - se describirá a continuación.
Así que llevemos nuestra inicialización a un aspecto completo:
#include <avr/io.h> //set bit - using bitwise OR operator #define sbi(x,y) x |= _BV(y) //clear bit - using bitwise AND operator #define cbi(x,y) x &= ~(_BV(y)) #define BUZ_PIN PB3 void timer2_buzzer_init() { // PB3 cbi(PORTB, BUZ_PIN); // PB3 , cbi(DDRB, BUZ_PIN); // TCCR2A = _BV(COM2A0) | _BV(WGM21) ; // ( ) OCR2A = 0; }
Usé las macros cbi y sbi (espiadas en algún lugar de la red) para configurar bits individuales, y lo dejé así. Estas macros, por supuesto, se han colocado en el archivo de encabezado, pero para mayor claridad, las puse aquí.
Cálculo de la frecuencia y duración de las notas.
Ahora llegamos a la esencia misma del problema. Hace algún tiempo, conocidos de músicos intentaron introducir una cierta cantidad de información sobre un equipo musical en el cerebro de mi programador, mi cerebro casi hervía, pero aún así saqué un grano útil de estas conversaciones.
Te advierto de inmediato: son posibles grandes inexactitudes.
- cada medida consta de 4 cuartos
- Cada melodía tiene un tempo, es decir la cantidad de esos cuartos por minuto
- Cada nota se puede tocar como una medida completa, así como su parte 1/2, 1/3, 1/4, etc.
- Cada nota, por supuesto, tiene una cierta frecuencia
Examinamos el caso más común, de hecho, todo es más complicado allí, al menos para mí, por lo que no discutiré este tema en el marco de esta historia.
Bueno, está bien, trabajaremos con lo que tenemos. Lo más importante para nosotros es obtener la frecuencia de la nota (de hecho, el valor del registro OCR2A) y su duración, por ejemplo, en milisegundos. En consecuencia, es necesario hacer algunos cálculos.
Porque estamos dentro del marco de un lenguaje de programación, las melodías son más fáciles de almacenar en una matriz. La forma más lógica de establecer cada elemento de la matriz en el formato es nota + duración. Es necesario calcular el tamaño del elemento en bytes, porque escribimos debajo del microcontrolador y con recursos aquí es escaso; eso significa que el tamaño del elemento en bytes debe ser adecuado.
Frecuencia
Comencemos con la frecuencia. Porque tenemos el temporizador 2 de 8 bits, el registro de comparación OCR2A también es de 8 bits. Es decir, nuestro elemento de la matriz de melodías ya será de al menos 2 bytes, porque aún necesita guardar la duración. De hecho, 2 bytes es el límite para este tipo de embarcaciones. Todavía no obtenemos un buen sonido, por decirlo suavemente, y gastar más bytes no es razonable.
Entonces, nos detuvimos en 2 bytes.Al contar la frecuencia, de hecho, surge otro gran problema.Si observa las frecuencias de las notas, veremos que están divididas en octavas.

Para la mayoría de las melodías simples, 3 octavas son suficientes, pero decidí esquivar e implementar 6: grande, pequeño y los siguientes 4.
Ahora pasemos de la música y volvamos al mundo de la programación de microcontroladores.
Cualquier temporizador en el AVR (y la gran mayoría de otros MK) está vinculado a la frecuencia del MK en sí. La frecuencia del cuarzo en mi circuito es de 16Mhz. En mi caso, la "definición" de F_CPU determina que la misma frecuencia es igual a 16000000. En el registro TCCR2B, podemos seleccionar divisores de frecuencia para que nuestro temporizador 2 no "marque" a una velocidad frenética de 16000000 veces por segundo, sino un poco más lento. El divisor de frecuencia se selecciona por bits CS2x, como se mencionó anteriormente.
nota: obviamente en LH un error tipográfico en lugar de "CA2x" debería ser CS2xSurge la pregunta: ¿cómo configurar el divisor?
Para hacer esto, comprenda cómo calcular los valores para el registro OCR2A. Y calcularlo es bastante simple:
OCR2A = F_CPU / (divisor de frecuencia de cuarzo * 2) / frecuencia de notaPor ejemplo, tome la nota ANTES de la primera octava y el divisor 256 (CS22 = 1, CS21 = 1, CS20 = 0):
OCR2A = 16000000 / (256 * 2) / 261 = 119
Explicaré de inmediato de dónde vino la multiplicación por 2. El hecho es que seleccionamos el modo "Alternar" con los registros COM2Ax, lo que significa que el cambio de niveles en el pie de menor a mayor (o viceversa) y viceversa ocurrirá en 2 pases del temporizador: primero el temporizador alcanza el valor de OCR2A y cambia el pie del microcontrolador, por ejemplo, de 1 a 0, se reinicia y solo en la segunda vuelta cambia 0 de nuevo a 1. Por lo tanto, 2 vueltas del temporizador van para cada onda completa, respectivamente, el divisor debe multiplicarse por 2, de lo contrario solo obtendremos la mitad de la frecuencia de nuestra nota
De ahí la desgracia antes mencionada ...
Si tomamos la nota ANTES de la gran octava y dejamos el divisor 256:
¡OCR2A = 16000000 / (256 * 2) / 65 = 480!
480: este número es claramente más de 255 y no cabe físicamente en el registro OCR2A de 8 bits.Que hacer Obviamente cambiando el divisor, pero si ponemos el divisor 1024, entonces con una octava grande, todo estará bien. Los problemas comenzarán con las octavas superiores:
LA 4ta octava - OCR2A = 16000000 / (1024 * 2) / 3520 = 4
Una cuarta octava aguda - OCR2A = 16000000 / (1024 * 2) / 3729 = 4
Los valores de OCR2A ya no son diferentes, lo que significa que el sonido también dejará de ser diferente.Solo hay una salida: para la frecuencia de las notas, necesita almacenar no solo los valores del registro OCR2A, sino también los bits del divisor de frecuencia de cuarzo. Porque ¡para diferentes octavas habrá un valor diferente del divisor de frecuencia de cuarzo, que tendremos que establecer en el registro TCCR2B!Ahora todo cae en su lugar, y finalmente expliqué por qué no podíamos completar de inmediato el valor del divisor en la función timer2_buzzer_init ().
Desafortunadamente, el divisor de frecuencia es de 3 bits más. Y deberán tomarse en el segundo byte del elemento del conjunto de melodías.
Larga vida a las macros #define DIV_MASK (_BV(CS20) | _BV(CS21) | _BV(CS22)) #define DIV_1024 (_BV(CS20) | _BV(CS21) | _BV(CS22)) #define DIV_256 (_BV(CS21) | _BV(CS22)) #define DIV_128 (_BV(CS20) | _BV(CS22)) #define DIV_64 _BV(CS22) #define DIV_32 (_BV(CS20) | _BV(CS21)) #define NOTE_1024( x ) ((F_CPU / (1024 * 2) / x) | (DIV_1024 << 8)) #define NOTE_256( x ) ((F_CPU / (256 * 2) / x) | (DIV_256 << 8)) #define NOTE_128( x ) ((F_CPU / (128 * 2) / x) | (DIV_128 << 8)) #define NOTE_64( x ) ((F_CPU / (64 * 2) / x) | (DIV_64 << 8)) #define NOTE_32( x ) ((F_CPU / (32 * 2) / x) | (DIV_32 << 8))
Y para la duración de la nota, solo nos quedan 5 bits, así que calculemos la duración.
Duración
Primero necesita traducir el valor del tempo en unidades temporales (por ejemplo, en milisegundos). Lo hice así:
Duración de una medida musical en ms = (60,000 ms * 4 trimestres) / valor de tempo.En consecuencia, si estamos hablando de partes de ritmo, entonces este valor debe dividirse, y al principio pensé que el desplazamiento a la izquierda habitual para los divisores sería suficiente. Es decir el código era este:
uint16_t calc_note_delay(uint16_t precalced_tempo, uint16_t note) { return (precalced_tempo / _BV((note >> 11) & 0b00111)); }
Es decir Usé 3 bits (de los 5 restantes) y obtuve partes del ritmo musical desde grados 2 hasta 1/128. Pero cuando le di a un amigo que me pedía que escribiera algún tipo de tono de llamada para mi pieza de hierro, surgieron preguntas sobre por qué no hay 1/3 o 1/6 y comencé a pensar ...
Al final, hice un sistema complicado para obtener tales duraciones. Un poco del 2x restante: gasté en el signo de multiplicación por 3 para el divisor de reloj obtenido después del turno. Y el último bit es indicar si es necesario restar 1. Esto es difícil de describir, es más fácil ver el código:
uint16_t calc_note_delay(uint16_t precalced_tempo, uint16_t note) { note >>= 11; uint8_t divider = _BV(note & 0b00111); note >>= 3; divider *= ((note & 0b01) ? 3 : 1); divider -= (note >> 1); return (precalced_tempo / divider); }
Luego "definí" todas las notas posibles (excepto las que son menores de 1/128).
Aqui estan #define DEL_MINUS_1 0b10000 #define DEL_MUL_3 0b01000 #define DEL_1 0 #define DEL_1N2 1 #define DEL_1N3 (2 | DEL_MINUS_1) #define DEL_1N4 2 #define DEL_1N5 (1 | DEL_MINUS_1 | DEL_MUL_3) #define DEL_1N6 (1 | DEL_MUL_3) #define DEL_1N7 (3 | DEL_MINUS_1) #define DEL_1N8 3 #define DEL_1N11 (2 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N12 (2 | DEL_MUL_3) #define DEL_1N15 (4 | DEL_MINUS_1) #define DEL_1N16 4 #define DEL_1N23 (3 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N24 (3 | DEL_MUL_3) #define DEL_1N31 (5 | DEL_MINUS_1) #define DEL_1N32 5 #define DEL_1N47 (4 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N48 (4 | DEL_MUL_3) #define DEL_1N63 (6 | DEL_MINUS_1) #define DEL_1N64 6 #define DEL_1N95 (5 | DEL_MUL_3 | DEL_MINUS_1) #define DEL_1N96 (5 | DEL_MUL_3) #define DEL_1N127 (7 | DEL_MINUS_1) #define DEL_1N128 7
Poniendo todo junto
Total, tenemos el siguiente formato para el elemento de nuestra matriz de tonos de llamada.
- 1 bit: divisor de retraso - 1
- 1 bit: divisor de retraso * 3
- 3 bits: demora el cambio del divisor
- 3 bits: divisor de reloj de la CPU
- 8 bits: valor OCR2A
Solo 16 bits.
Estimado lector, si lo desea, puede soñar con el formato usted mismo, tal vez nazca algo más espacioso que el mío.
Olvidamos agregar una nota vacía, es decir silencio Y finalmente, expliqué por qué al principio, en la función timer2_buzzer_init (), configuramos especialmente el tramo PB3 a la entrada y no a la salida. Cambiando el registro DDRB, activaremos y desactivaremos la reproducción de "silencio" o la composición en su conjunto. Porque no podemos tener notas con un valor de 0, será una nota "vacía".
Defina las macros faltantes y la función para habilitar la generación de sonido:
#define EMPTY_NOTE 0 #define NOTE(delay, note) (uint16_t)((delay << 11) | note) ........ ........ ........ void play_music_note(uint16_t note) { if (note) { TCCR2B = (note >> 8) & DIV_MASK; OCR2A = note & 0xff; sbi(DDRB, BUZ_PIN); } else cbi(DDRB, BUZ_PIN); }
Ahora te mostraré cómo se ve un tono de llamada escrito de acuerdo con este principio:
const uint16_t king[] PROGMEM = { NOTE(DEL_1N4, MI3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N2, SI3), NOTE(DEL_1N4, LA_3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, LA_3), NOTE(DEL_1N4, EMPTY_NOTE), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, FA3), NOTE(DEL_1N2, LA3), NOTE(DEL_1N4, MI3), NOTE(DEL_1N4, FA_3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, LA3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, MI4), NOTE(DEL_1N4, RE4), NOTE(DEL_1N4, SI3), NOTE(DEL_1N4, SOL3), NOTE(DEL_1N4, SI3), NOTE(DEL_1N2, RE4), NOTE(DEL_1N2, EMPTY_NOTE), };
Tocando tono de llamada
Todavía tenemos una tarea: tocar la melodía. Para hacer esto, necesitamos "ejecutar" a través de la matriz de tonos de llamada, resistir las pausas apropiadas y cambiar las frecuencias de las notas. Obviamente, necesitamos otro temporizador, que, por cierto, puede usarse para otras tareas generales, como suelo hacer. Además, puede cambiar entre elementos de la matriz en la interrupción de este temporizador o en el bucle principal, y usar el temporizador para calcular el tiempo. En este ejemplo, utilicé la segunda opción.
Como sabes, el cuerpo de cualquier programa para MK incluye un bucle infinito:
int main(void) { for(;;) {
En ella "correremos" a lo largo de nuestra matriz. Pero necesitamos una función similar a GetTickCount de WinApi, que devuelve el número de milisegundos en los sistemas operativos Windows. Pero, naturalmente, en el mundo de MK no existen tales funciones "fuera de la caja", por lo que debemos escribirlo nosotros mismos.
Temporizador 1
Para calcular los intervalos de tiempo (intencionalmente no escribo milisegundos, más tarde comprenderá por qué) utilicé el temporizador 1 junto con el modo CTC ya conocido. El temporizador 1 es un temporizador de 16 bits, lo que significa que el valor del módulo de comparación ya está indicado por 2 registros de 8 bits OCR1AH y OCR1AL, para los bytes alto y bajo, respectivamente. No quiero describir en detalle el trabajo con el temporizador 1, ya que esto no se aplica al tema principal de este memo. Por lo tanto, te diré solo en 2 palabras.
En realidad necesitamos 3 funciones:
- Inicialización del temporizador.
- Manejador de interrupción de temporizador
- función que devuelve el número de intervalos de tiempo.
Archivo de código C #include <avr/io.h> #include <avr/interrupt.h> #include <util/atomic.h> #include "timer1_ticks.h" volatile unsigned long timer1_ticks; // ISR (TIMER1_COMPA_vect) { timer1_ticks++; } void timer1_ticks_init() { // // CTC , 8 TCCR1B |= (1 << WGM12) | (1 << CS11); // OCR1AH = (uint8_t)(CTC_MATCH_OVERFLOW >> 8); OCR1AL = (uint8_t) CTC_MATCH_OVERFLOW; // TIMSK1 |= (1 << OCIE1A); } unsigned long ticks() { unsigned long ticks_return; // , ticks_return // ATOMIC_BLOCK(ATOMIC_FORCEON) { ticks_return = timer1_ticks; } return ticks_return; }
Antes de mostrar el archivo de encabezado con una cierta constante CTC_MATCH_OVERFLOW, necesitamos retroceder en el tiempo a la
sección "Duración" y determinar la macro más importante para la melodía, que calcula el tempo de la melodía. Esperé mucho tiempo para determinarlo, ya que está conectado directamente al reproductor y, por lo tanto, con el temporizador 1.
En una primera aproximación, se veía así (ver cálculos en la sección "Duración"):
#define TEMPO( x ) (60000 * 4 / x)
El valor que obtenemos en la salida posteriormente debemos sustituir el primer argumento en la función
calc_note_delay . Ahora observe de cerca la función calc_note_delay, es decir, la línea:
return (precalced_tempo / divider);
Vemos que el valor obtenido al calcular la macro TEMPO se divide por un cierto divisor. Recuerde que el divisor máximo que hemos definido es
DEL_1N128 , es decir el divisor será 128.
Ahora tomemos el valor de tempo común igual a 240 y hagamos algunos cálculos simples:
60000 * 4/240 = 1000¡Oh horror! Obtuvimos solo 1000, ya que este valor aún se dividirá entre 128, corremos el riesgo de caer a 0, a tasas altas.
Este es el segundo problema de duración.¿Cómo solucionarlo? Obviamente, para expandir el rango de valores de tempo, de alguna manera necesitamos aumentar el número obtenido al calcular la macro TEMPO. Esto se puede hacer de una sola manera: alejarse de milisegundos y contar el tiempo en ciertos intervalos de tiempo. Ahora entiendes por qué todo este tiempo evité mencionar "milisegundos" en la historia. Definamos otra macro:
#define MS_DIVIDER 4
Deje que sea nuestro divisor del milisegundo: divida el milisegundo, por ejemplo, entre 4 (250 μs).
Entonces necesita cambiar la macro TEMPO:
#define TEMPO( x ) (60000 * MS_DIVIDER * 4 / x)
Ahora, con la conciencia tranquila, le daré el archivo de encabezado para trabajar con el temporizador 1:
#ifndef TIMER1_TICKS_H_INCLUDED #define TIMER1_TICKS_H_INCLUDED #define MS_DIVIDER 4 #define CTC_MATCH_OVERFLOW ((F_CPU / 1000) / (8 * MS_DIVIDER)) void timer1_ticks_init(); unsigned long ticks(); #endif
Ahora podemos, cambiando MS_DIVIDER, ajustar el rango de nuestras tareas, tengo 4 en mi código, esto fue suficiente para mis tareas.
Atención: si todavía tiene alguna tarea "vinculada" al temporizador 1, no olvide multiplicar / dividir los valores de control de tiempo para ellos por MS_DIVIDER.Placa giratoria
Ahora escribamos a nuestro jugador. Creo que todo quedará claro a partir del código y los comentarios.
int main(void) { timer1_ticks_init();
Conclusión
Espero que este memo sea útil para un lector respetado y para mí, para no olvidar todos los matices de tocar melodías, en caso de que vuelva a tomar los microcontroladores AVR.
Bueno, tradicionalmente el video y el código fuente (lo desarrollé en el entorno de Code Blocks, así que no tengas miedo de los archivos oscuros):
Código fuente