Bonjour. Nous continuons le cours, comprendre les microcontrôleurs. Aujourd'hui nous allons parler de la gestion du temps. Ce qui est très inhabituel, alors que nous avons beaucoup parlé des entrées sorties des microcontrôleurs, nous n'allons parler dans ce module que de sorties. Vous pouvez d'ailleurs voir le programme, nous allons étudier la durée d'une instruction, nous allons voir comment nous pouvons créer des attentes actives. Nous allons aussi apprendre à programmer des séquenceurs. Nous allons, également, toucher un domaine un peu mystérieux qui s'appelle le multi-tâches et même faire une allusion au temps absolu. Allons-y. Vous le savez déjà , certainement, le processeur exécute en permanence des instructions. En général, il ne s'arrête pas, il exécute des instructions. Vous savez, également, que la durée d'une instruction est quelque chose d'extrêmement court, un petit peu trop court pour nous puissions bien réaliser ce que c'est. Et pourtant c'est une durée parfaitement réelle. Cette durée va donc pouvoir être utilisée pour gérer le temps qui passe. On avait mentionné que les microcontrôleurs ont une horloge, par exemple sur les processeurs AVR de la société Atmel, on trouve une horloge interne qui est généralement à 8 Mégahertz. Il existe un registre de calibrage, et le calibrage est fait d'usine de telle manière qu'on obtienne une précision relativement bonne, disons environ aussi bonne que le pourcent. Il est possible sur ces processeurs de mettre des quartz, assez souvent jusqu'à 20 Mégahertz, ce qui permet d'aller un petit peu plus vite que les 8 Mégahertz d'origine. Je signale aussi que les Arduino ont généralement un quartz à 16 Mégahertz, et je rappelle qu'un quartz permet d'avoir une précision qui est beaucoup plus grande que ce 1% dont je parlais tout à l'heure. Sur les AVR il est également possible de diviser la fréquence par 8, c'est programmé dans un fanion qui n'est pas perdu lorsqu'on coupe le courant Ça permet de choisir une fréquence de, par exemple, de 1 Mégahertz au lieu de 8 Mégahertz. Sur les processeurs MSP430 de Texas Instruments, il y a une horloge interne à 16 Mégahertz, la particularité, c'est qu'on peut choisir relativement librement la fréquence dans le cours du programme, et il y a deux valeurs qui sont calibrées d'usine, à 1 Mégahertz et à 16 Mégahertz. Lorsqu'on doit gérer un temps plus précis, on utilise généralement un quartz à 32 kilohertz, donc un quartz horloger qui est plus coûteux. Je ne vais évidemment pas entrer dans les détails de ces horloges, ça dépasserait le cadre de ce cours. Est-ce qu'il est possible de prévoir la durée d'une instruction? Alors, en assembleur, c'est possible. Une instruction donnée va prendre un ou plusieurs cycles de l'horloge. Par exemple, sur les AVR, la quasi totalité des instructions s'exécute en un seul coup d'horloge, ce qui signifie qu'on a 125 nanosecondes pour l'exécution d'une instruction. Par contre, nous avons l'habitude de programmer en C, et là , on ne peut absolument pas dire le temps d'exécution d'une ligne d'une instruction en C, il faudrait regarder quelles sont les instructions assembleur que le compilateur a générées au moment de son travail de compilation. Par contre, étant donné que le temps d'exécution d'une instruction est répétitif, il est possible d'exécuter des boucles dont la durée va aussi être répétitive. Et c'est comme ça qu'on va faire des boucles d'attente active qui vont vous permettre de gérer un petit peu le temps. Essayons de regarder ce programme. On voit marqué qu'il y a, tout à l'intérieur, une première boucle, cette boucle va s'exécuter un certain nombre de fois, ici la valeur a été définie. À noter que cette variable a été mise en volatile, c'est très important, de telle manière que l'exécution d'une boucle vide ne soit pas remplacée par rien du tout par le compilateur qui essaie toujours d'optimiser. Cette boucle for est incluse dans une première boucle, qui elle, va s'exécuter un certain nombre de fois lié à ce paramètre d'entrée, donc nous avons une procédure que nous avons appelée attente millisecondes, on lui passe le paramètre de durée, c'est cette durée qui est utilisée pour faire l'exécution d'un certain nombre de fois cette boucle, on comprend bien, donc, que cette procédure va faire une attente, dont la durée est plus ou moins proportionnelle à ce paramètre. Il est clair que si on met 1 ou si on met 2, on n'aura pas une linéarité, par contre si on met 200, si on met 300, on aura une bonne linéarité. Mais étant donné qu'on ne connaît rien sur le temps d'exécution des instructions, la seule solution pour avoir une procédure qui soit correcte du point de vue du temps, c'est de la calibrer soi-même. Alors je vous propose un petit quizz. Avec quel appareil de mesure pourra-t-on calibrer notre boucle d'attente? Peut-on utiliser un fréquencemètre? Un chronomètre? Un oscilloscope? Une balance? Alors on voit bien qu'une balance n'est pas tout à fait l'outil idéal pour ce genre de travail, par contre un fréquencemètre convient parfaitement bien. Un oscilloscope, qui permet de mesurer une période, convient aussi parfaitement bien. Mais un chronomètre peut tout à fait être aussi utilisé. Je dois vous dire que j'ai pensé aux nombreux étudiants africains qui vont suivre ce cours en posant cette question. On se plaint souvent là -bas, il n'y a pas de matériel dans les laboratoires, les laboratoires ne sont pas très accessibles aux étudiants, et bien vous voyez qu'avec un simple chronomètre, comme celui que vous trouvez dans la plupart des téléphones portables, vous allez pouvoir calibrer votre procédure. Regardons comment on va faire ce calibrage. Utilisons notre procédure AttenteMs, qui permet d'attendre un certain nombre de millisecondes, ou qui est censée attendre un certain nombre de millisecondes. Donnons-lui le paramètre dix mille, qui va correspondre à dix secondes, faisons changer l'état de la LED après 10 secondes, il suffira ensuite d'ajuster la constante qu'on avait appelée BaseTempsMs jusqu'à obtenir exactement les dix secondes au chronomètre, et ce sera assez répétitif, parfaitement répétitif si on a un quartz, répétitif avec une précision meilleure qu'1% si on a simplement un oscillateur comme on les trouve dans les microcontrôleurs. Je précise que cette précision limitée est suffisante dans beaucoup de cas. Par contre, pour gérer la date et l'heure, il existe des circuits spécialisés, et nous aurons l'occasion d'en reparler dans la suite de ce cours. Une fois de plus, l'environnement Arduino, et également Energia, offrent une procédure similaire à celle qu'on a développée tout à l'heure. Elle s'appelle delay et on lui passe un paramètre qui est exprimé en millisecondes. Elle semble donc être faite exactement comme celle que nous venons de faire, et vous vous souvenez, c'est ce qui nous avait permis d'écrire notre premier programme, qui était un clignotant, où on faisait alterner des digitalWrite HIGH et des delay, puis LOW et le même delay pour faire clignoter. Alors il faut bien se rappeler, cet appel delay est un appel bloquant. Si on lance un delay, il ne se passe rien pendant cette période. Nous avons maintenant tout ce qu'il faut pour réaliser des séquenceurs. Nous allons faire succéder des assignations de sorties, chose que nous savons parfaitement faire, et des attentes que nous venons d'apprendre à effectuer. Les entrées ne sont pas impliquées dans ce type de programme, mais il en existe bel et bien, un certain nombre d'applications, plus qu'on imagine peut-être, qui n'utilisent que les sorties du microcontrôleur sans utiliser les entrées. Je cite quelques exemples ici : un feu tricolore cyclique n'a pas besoin d'entrées, une boîte à musique non plus. Une enseigne lumineuse animée cyclique ou pseudo-aléatoire n'a pas besoin d'entrées. C'est également le cas d'un journal lumineux, ces deux derniers sujets, d'ailleurs, seront étudiés plus en détail dans ce cours un peu plus tard. Et il y a certainement d'autres applications. Prenons l'exemple du feu tricolore cyclique. Nous avons deux axes sur lesquels peuvent circuler les voitures dans les deux sens, et nous avons simplement deux feux pour chaque direction, pour chaque axe, ils sont utilisés en même temps, ici pour l'axe numéro 2. Le programme est un séquenceur. Avant d'écrire le programme proprement dit, donnons-nous quelques outils pour le rendre plus lisible. D'abord, l'utilisation de enum pour déclarer les couleurs vert, orange et rouge, et ensuite deux procédures qui allument la bonne couleur sur l'axe 1, la bonne couleur sur l'axe numéro 2. Alors chaque fois il faut éteindre les trois feux, et puis ensuite il faut allumer le feu de la bonne couleur, soit le rouge, soit l'orange, soit le vert. Voilà maintenant la boucle principale de ce programme, on voit que c'est effectivement un séquenceur, on a successivement des assignations de sorties puis des attentes, chacune de ces lignes se présente de la même manière. Alors il y a trois étapes pour chacun des axes. Passer l'autre axe au rouge, passer l'axe en question en vert, et attendre le temps que les voitures passent, puis passer à l'orange sur cet axe, puis passer au rouge sur cet axe, chaque fois avec de petites attentes, et on fait la même chose sur le deuxième axe. On peut se poser une question : est-il possible de gérer plusieurs tâches avec un microcontrôleur? Je rappelle que les attentes, nous l'avons vu, sont bloquantes. Nous avons très facilement pu faire clignoter une LED à 2 Hertz, mais c'est pas si facile que ça d'ajouter une seconde LED qui clignoterait à 3 Hertz, par exemple. Alors voilà une idée pour résoudre ce problème. Essayer d'utiliser une boucle principale dont la durée principale soit constante. Ça signifie qu'il ne faut pas utiliser d'autres boucles à l'intérieur. Voilà donc une solution pour ce problème du clignotant double à fréquences inégales. On va utiliser deux variables compteur1 et compteur2. La boucle principale, c'est le seul endroit où on trouve un while. Ici, il n'y a aucun while. On ne trouve que des if, qui ne sont pas bloquants. On utilise l'incrémentation des compteurs à chaque cycle et on utilise l'attente de une milliseconde pour que cette boucle principale tourne avec une durée à peu près constante. Et alors, en début de chaque cycle, c'est-à -dire lorsque le compteur est à 0, on va inverser une des LED ou l'autre LED, et lorsque le compteur aura atteint sa valeur maximale, ici 250 pour avoir un quart de seconde, donc pour avoir une fréquence de 2 Hertz, on va donc remettre le compteur1 à 0, et ici de la même manière, après 166 millisecondes, on met le compteur2 à 0. Cette technique, qui est donc l'utilisation d'une boucle principale à durée constante est très intéressante et nous aurons plusieurs fois l'occasion de l'utiliser dans la suite de ce cours. Jusqu'à maintenant, nous avons toujours utilisé des temps relatifs, il est aussi possible d'avoir un temps absolu, entre guillemets, c'est en fait le temps écoulé depuis le début du programme. On peut le réaliser tout simplement par cette structure-là . Dans la boucle principale à durée constante, on incrémente une variable qui est initialisée à 0. Évidemment, ce serait un peu dommage qu'on arrive déjà à un retour à 0 après environ 65000 millisecondes, c'est-à -dire à peine plus qu'une minute, c'est la raison pour laquelle on utilisera plutôt une variable unsigned long qui correspond à plus d'un mois avant le retour à la valeur 0. Voilà donc une autre version de notre double clignotant qui semble plus simple, le programme est beaucoup plus court, il n'y a qu'une seule variable, notre variable qui gère les millisecondes qui passent, et on utilise ici une division, plus exactement le reste de la division, pour savoir si on est arrivé à 150 ou 166 millisecondes écoulées, et nous pouvons à ce moment-là inverser l'état de chacune des diodes. Mais au fond, est-ce vraiment plus simple? Voilà le programme que nous avions. Certes, il est plus long en C, mais est-ce qu'il est plus long une fois qu'il est compilé? Peut-être pas. Ici, nous avons des comparaisons, et comme seul calcul mathématique, calcul arithmétique, nous avons deux incrémentations. Ici nous avons deux divisions, qui sont deux opérations qui prennent évidemment beaucoup plus de temps et beaucoup plus de place en mémoire, davantage d'instructions assembleur. Il faut donc être vigilant, la solution qui semble la plus simple en C n'est pas toujours la plus simple en assembleur, et donc pas toujours la plus efficace. De nouveau, l'environnement Arduino ainsi qu'Energia offrent une procédure similaire, il est possible d'appeler cette procédure millis, qui donne le nombre de millisecondes depuis le début du programme. Ce qui est étonnant, c'est qu'Arduino offre aussi la procédure micros, et cette fois ce n'est plus en millisecondes mais en microsecondes, et là l'explication que je vous ai donnée semble ne pas convenir, parce qu'alors l'exécution de quelques instructions en assembleur risque de venir sérieusement influencer des valeurs qui sont exprimées en microsecondes. En millisecondes, on avait de la marge, en microsecondes, on est tout près du temps d'exécution d'une instruction. Ils ont donc un truc. Il s'agit de l'utilisation des timers et des interruptions, sujet que nous allons prochainement voir dans ce cours. Je profite de rappeler, il ne faut pas oublier que ces deux appels ne sont pas bloquants, n'attendent pas mais donnent simplement l'horloge, la valeur, dans le fond, de l'heure, c'est comme regarder une horloge, au contraire de l'appel delay, qui lui était une attente, qui correspond donc plutôt à une minuterie. Nous avons donc vu, une instruction dure un certain temps, il est possible de préparer des boucles d'attente active, il est possible avec cet outil de faire des séquenceurs qui agissent sur les sorties. Nous avons également vu qu'il était possible de faire du multi-tâches, c'est-à -dire d'exécuter plusieurs processus en quelque sorte en même temps. Nous avons aussi parlé du temps absolu, et nous avons regardé les appels Arduino qui correspondaient à ces différentes fonctions. Dans un prochain module, nous allons alors intégrer les entrées et pas seulement les sorties, et également les gérer en fonction du temps.