Bonjour, nous allons faire un peu de musique aujourd'hui. Nous allons le faire évidemment avec un microcontrôleur et il n'y aura pas besoin d'être virtuose pour pouvoir suivre cette leçon. C'est un exemple d'application que nous allons faire aujourd'hui. Certes, nous parlerons un petit peu de notes pour comprendre comment générer des notes correctes mais nous verrons surtout comment le faire avec un timer et avec des interruptions. C'est une jolie application de ces techniques que nous avons appris à maîtriser. Alors chacun sait qu'un son, c'est un phénomène périodique qui se produit dans des fréquences de l'ordre de 50 Hertz à 20 kHz si on est très jeune. On a donc une variation qui peut avoir différentes formes. Ici nous allons utiliser la sortie d'un microcontrôleur pour pouvoir mettre une valeur à 0 et à ce moment-là, le haut-parleur ne sera pas activé, ou une valeur à 1 et la membrane sera collée. Ici on a un montage plus astucieux où on va utiliser deux sorties. On va s'arranger de les utiliser en permanence en opposition, c'est-à-dire lorsqu'on a un 0, on aura un 1 ici de telle manière que le haut-parleur soit attiré dans un sens et tout au contraire, lorsqu'on aura un 0 ici et un 1, le haut-parleur sera collé dans l'autre sens donc on aura plus de sonorité en utilisant ce schéma. Vous n'êtes pas sans ignorer qu'en musique, on parle de notes, on parle d'une gamme avec un certain nombre de notes, do, ré, mi, fa, sol, la, si. Lorsqu'on retombe sur le do, on est à une octave de cette note-là et l'octave correspond au, à la multiplication de la fréquence par deux. Autre chose, le la, c'est la référence, il est à 440 Hz et c'est ce qu'on obtient avec un diapason, que vous avez peut-être vu utilisé chez des musiciens. Pour être un tout petit peu plus précis, on a des tons et des demis-tons qui séparent les notes. Par exemple, entre le do et le ré, il y a un ton par contre entre le mi et le fa, il y a un demi-ton. Et en tout, on a 12 demis-tons par octave. Or, c'est évidemment une progression géométrique et non pas une progression arithmétique, une progression linéaire. Donc chaque note doit être multipliée par un certain facteur pour passer à la note suivante, ou plus exactement, pour passer d'un demi-ton, on doit multiplier par un coefficient qui est fixe et qui est égal à racine douzième de 2. Pourquoi cette formule avec cette racine douzième de 2? Et bien par le fait que nous avons des demis-tons. Pour une octave, il y a 12 demis-tons. Or, une octave correspond à un facteur 2, comme on l'a vu tout à l'heure. Donc il faut bien utiliser un facteur qui est égal à racine douzième de 2. Ce chiffre, d'ailleurs, est égal, à peu près, à 1,06. Commençons la programmation de notre application qui va nous permettre de jouer une mélodie. Tout d'abord, j'ai énuméré des notes pour avoir des constantes associées à toutes les notes. Alors il y a do, ré, mi, fa, sol et cetera avec aussi les demis-tons jusqu'au si. Et puis il y a une partie de la première octave, la deuxième octave jusqu'ici, la troisième octave jusqu'ici et une partie de la quatrième. J'ai limité à 32 notes, vous verrez pourquoi tout à l'heure, on va utiliser l'astuce que ces 32 notes peuvent se coder avec 5 bits de la puissance 5. Maintenant, l'aspect essentiel, c'est de donner la période liée à chaque note. Vous voyez ici un tableau qui a été fait avec un tableur en mettant ce paramètre de la racine douzième de 2. On est parti du la et on a culculé des différentes fréquences. Puis on est passé aux périodes, la période étant l'inverse de la fréquence. Si vous regardez, si vous faites le calcul, 2272 microsecondes c'est égal à 1 sur 440 Hz. C'est donc bien ici le la du diapason qu'on trouve à cet endroit. C'est une chose de donner une fréquence correcte mais nous souhaitons avoir une mélodie avec des notes de durée régulière. Or, en fonction de la fréquence le nombre de notes pour une durée donnée va varier. Si la fréquence est plus basse, on aura moins de périodes. Si la fréquence est plus élevée, on aura davantage de périodes. Certes on aurait pu le calculer, ici on a également utilisé un tableau, un tableau de bytes, de caractères donc de mots de 8 bits, dans lequel on a mis ces valeurs qui ici vont augmenter puisque tout à l'heure on avait des valeurs qui diminuaient. Et c'est inversement proportionnel. En musique, les notes ont des durées qui ont des noms. La durée la plus standard, c'est la noire. La croche est deux fois plus courte. La blanche est deux fois plus longue que la noire. La ronde est quatre fois plus longue que la noire. On a ajouté la possibilité de séparer les notes avec un petit moment de silence et on va donner un paramètre qui est le pourcentage du temps de la note pendant laquelle la note cessera d'être jouée. C'est nettement plus agréable d'écouter une mélodie avec des notes un petit peu séparées que d'avoir une mélodie tout en continu, qui donne une impression très mécanique. Alors nous pouvons maintenant coder les notes dans notre programme. La croche vaut 1, la blanche vaut 2, 3, 4, on a même prévu une noire pointée. Mais pourquoi ce décallé de 5? C'est en fait une multiplication par 32. Et bien, c'est pour libérer la place des 5 premiers bits qui seront utilisés pour le codage de la note do, ré, mi, fa, sol alors que la durée croche, blanche et cetera, sera codée sur les trois bits suivants. Vous remarquez qu'il n'y a pas de noire puisque la noire, c'est la valeur par défaut, c'est le 0. Pour des raisons de lisibilité, j'ai également défini une version résumée avec juste deux lettres de telle manière qu'on puisse, ici, noter notre mélodie Frère Jacques, do, ré, mi, do, do, ré, mi et cetera avec, ici, une blanche sur le sol, avec des croches dans cette zone-là et finalement, une blanche qui termine le morceau et puis, ici, le terminateur qui permettra de dire que la mélodie est terminée. Alors vous pourriez me poser une question : pourquoi ne pas avoir écrit constante devant ce tableau comme devant tous les tableaux qu'on a vi, qu'on a vu jusqu'à maintenant? Alors, c'est vrai, ce serait mieux de le mettre mais malheureusement, le processeur que nous allons utiliser dans ce cas-là, un AVR, un ATMega168 ou 328, n'est pas capable d'utiliser ce terme, il n'est pas capable d'exploiter un tableau qui se trouve en mémoire morte. En fait, c'est un processeur qui n'a pas un bus unique d'adresse, il a deux bus, un pour le programme et un pour les données, et, par conséquent, il n'est pas possible simplement en ajoutant constante devant un tableau de faire qu'il soit gravé en mémoire. C'est le cas pour d'autres processeur, c'est le cas pour le MSP430, qui est un processeur dit de von Neumann, qui a un seul espace d'adressage, mais pour l'AVR, ça n'est pas utile. Dans ce cas-là, le compilateur ne pourra pas exploiter ce terme constante et utilisera malheureusement de la mémoire pour coder ces tables, qui quand même ne vont pas changer, qui sont permanentes. On arrive au programme principal, on souhaiterait qu'il soit extrêmement simple. Ici, simplement, demander de jouer la mélodie Frère Jacques et ce qu'on souhaite, c'est que, tout de suite, la main soit rendue pour que la suite du programme puisse s'exécuter et on va voir que tout le reste va être géré par des interruptions. Ici j'ai une variable globale qui permet de définir ce piqué, ce pourcentage de la note pendant laquelle elle est inactive à la fin, qui permet de donner une impression plus agréable de, à l'écoute. Alors regardons cette procédure JoueMelodie. Il y a un certain nombre de variables qu'on va utiliser. Et là, vous remarquez une syntaxe que nous n'avons encore jamais vue. Il s'agit de pointeurs. Alors, je ne veux pas vous faire une théorie sur les pointeurs mais on voit ici que ce paramètre mélodie C'est en fait l'adresse d'un tableau où va se trouver la mélodie. Et on verra plus tard, dans la routine d'interruption, comment on va chercher les informations à partir de ce pointeur, donc ici le début est modifié, on a également ici un pointeur qui sera le pointeur courant qui va suivre les différentes notes qui se jouent les unes après les autres. J'initialise encore cette variable qui est le nombre de périodes restantes de la note courante, comme il n'y a pas encore de note qui est chargée, on initialise à zéro. Et finalement j'ai séparé toute la partie matérielle, toute la partie initialisation matérielle, de telle manière que, si je veux réutiliser cette procédure sur un autre processeur, je n'aie, en principe, que cette partie à modifier. Et je signale qu'on revient tout de suite au programme appelant sans attendre, toute la mélodie va être jouée sous contrôle du mécanisme d'interruption. Alors regardons quand même cette initialisation matérielle, comme elle se présente pour un processeur AVR. Donc InitMelodie, il faut enclencher le haut-parleur, donc le mettre en sortie, ce sera un ddr qu'on devra activer. Il faut ensuite choisir la fréquence du timer, on va évidemment utiliser un timer, ici le timer c'est 16 bits, et vous voyez que dans ce registre de contrôle de ce timer 16 bits, j'ai mis une valeur qui s'appelle DivTimer8, et elle est décalée de CS10, et ce qui vous semble peut-être encore plus bizarre, c'est que j'ai déclaré cette valeur en binaire, et en plus avec un zéro non significatif, j'ai mis trois valeurs binaires. Alors vous allez comprendre pourquoi j'ai fait ces choix qui peuvent paraître un petit peu obscurs. Regardons ce que dit le fabricant de cette valeur qu'on doit mettre dans le registre. Alors il le définit comme étant trois bits, qui s'appellent CS02, 01, et 00, ou également 1 2, 1 1, 1 0, c'est celui qu'on a pris, c'est les mêmes pour le timer 0, le timer 1 et le timer 2, et on voit bien ici que si on choisit une division par 8, c'est ce qu'on a voulu prendre, on a la valeur 0, 1, 0. C'est cette valeur que vous retrouvez ici : 0, 1, 0. Et comme ces trois bits se trouvent à la position CS00, qui est égale à CS10 dans le registre TCCR1B, et bien j'ai pris cette valeur binaire et je l'ai décalée du rang de ce bit-là. Voilà une manière qui est, on l'espère, lisible, de représenter ces informations. De toute manière, il va falloir lire la documentation si on veut comprendre en détail ce qui se passe. Ici, on a enclenché un masque d'interruption, de telle manière qu'on ait le output capture du registre correspondant qui est activé, vous allez voir tout à l'heure comment est-ce qu'on va l'utiliser. Et bien sûr, il faut déclencher l'ensemble des interruptions pour que notre interruption puisse alors fonctionner. Je profite encore d'ajouter ces deux instructions dont on aura besoin, celle qu'on a déjà utilisée, qui permet de mettre le haut-parleur en sortie, c'est bien l'activation d'un bit sur un registre ddr, et ce que j'ai appelé TicHautParleur, qui permet donc de changer l'état du haut-parleur, donc générer un petit bruit. Il va falloir travailler sur un port, et il va falloir faire le ou exclusif, le toggle la version du bit, avec un des bits, ici on a choisi le PC5. Alors essayons d'entrer dans cette routine d'interruption, c'est la partie la plus compliquée de ce programme. Voilà encore quelques variables que nous allons utiliser. La période courante, ici la période pendant laquelle le son ne doit pas être produit à la fin de la note. Ici on a également la note courante, qui est mémorisée dans une variable, et ici un int dans lequel la valeur du Timer, au moment du début de l'interruption est mémorisée. Alors voilà la syntaxe ici pour l'interruption, interrupt sub routine, il s'agit de l'interruption du timer 1 pour la comparaison numéro 1, vect veut dire, donc, vecteur, il s'agit d'un vecteur d'interruption, et la première chose qu'on fait pour ne pas perdre une information temporelle, c'est de mémoriser, dans cette variable, la valeur actuelle du compteur, de ce compteur qui compte en permanence, donc du timer lui-même. Ensuite, on va se poser la question : est-ce qu'il reste des périodes dans la précédente note qu'on avait peut-être commencée? Si ça n'est pas le cas, alors il faut aller chercher la note courante en prenant la valeur qui se trouve à l'adresse du pointeur de la mélodie, on va profiter d'incrémenter ce pointeur pour qu'il aille ensuite chercher la prochaine note quand il s'agira de prendre la prochaine note. Je reprends cette même ligne et je continue le code. Est-ce que la note courante que je viens de lire, c'est le terminateur de la mélodie? Si oui, il va falloir terminer la procédure, non sans avoir oublié d'arrêter, de terminer de mettre à zéro le flag des interruptions pour qu'il n'y ait plus d'interruption qui vienne, puisque la mélodie est terminée. On regarde aussi si la note courante a la valeur Reprise, qui est une constante qui a été déclarée, et qui permet de faire la reprise de la mélodie au début. On aura donc, a priori, une mélodie qui se jouera sans fin, plus exactement jusqu'au moment où on réinitialise une nouvelle mélodie. Si c'est le cas, le pointeur de la mélodie reprend le début, on va chercher la note courante, comme tout à l'heure, et on pourra continuer. Maintenant s'il s'agit d'une note normale, et bien on va à partir de cette note courante, en ne s'occupant que de la partie note, donc en masquant les cinq premiers bits, on va chercher dans le tableau notre période, la période de la note courante. On va faire la même chose avec la durée, ça c'est le tableau de la durée, on lui donne de nouveau comme index la note courante masquée de la même manière. Petite astuce, le programme avait été développé pour un processeur tournant à huit mégahertz, et l'Arduino qu'on va utiliser aujourd'hui utilise un processeur à 16 mégahertz, c'est la raison pour laquelle j'ai multiplié par deux cette durée, de telle manière que ma note dure un temps correct. On doit encore s'occuper de la durée de la note, est-ce qu'il s'agit d'une noire, est-ce qu'il s'agit d'une blanche, et cetera. Donc on va masquer la note pour ne garder que la partie qui nous intéresse dans la note courante, et puis on va regarder si la valeur est nulle, une noire, ou bien si c'est une blanche, une ronde, et cetera. On va également s'occuper de cette partie de la période, donc de la durée de la note, où le son ne sera pas joué pour donner une impression musicale plus agréable, en reprenant ce paramètre Pique, vous vous souvenez peut-être qu'on avait initialisé à 20% dans le programme principal, si on met une valeur plus grande, on aura une impression de piqué plus importante, d'où le fait que j'ai appelé cette procédure Pique. Finalement, il ne me reste qu'à donner au registre de capture la nouvelle valeur de capture qui sera la valeur qu'on a lue, tout à l'heure, du timer, auquel j'ajoute la période courante, et il faudra évidemment faire un tic sur le haut-parleur sauf si on est dans la période où on ne doit pas donner de son. Donc le tic du haut-parleur va faire bouger, soit faire coller soit faire décoller le haut-parleur, et la succession de ces événements donnera un son. Et finalement, pour la note courante, la période restante, le nombre de périodes restantes va diminuer, de telle manière que finalement on aille jusqu'à la fin de la note. Je reconnais que ce programme est un peu compliqué. Il faudrait que vous puissiez l'avoir dans son ensemble sous les yeux pour le regarder. Mais de manière générale, lorsqu'on veut utiliser des timers ou des interruptions, il faut effectivement regarder la documentation du fabricant en détail, ou éventuellement s'inspirer de programmes qu'on a trouvés sur Internet, en particulier sur les sites des fabricants, ou dans d'autres ressources qui sont fournies autour de ces microcontrôleurs, et il y en a beaucoup. Est-ce que ce programme fonctionne? Et bien, écoutez. Je presse sur le petit bouton, il s'agit donc ici d'un Arduino, qui est un modèle particulier, le Diduino, qui est très pratique puisqu'il a la possibilité de placer des petits composants, j'ai mis des petits fils pour mettre la résistance, et le petit haut-parleur, le petit buzzer, et maintenant écoutez le résultat. Alors, si vous avez l'occasion de faire de la musique avec un microcontrôleur, même des mélodies simples, s'il vous plaît, essayez de faire des notes qui soient justes, pensez aux gens qui ont l'oreille musicale.