CR.md 17.3 KB

Tutorat de microprocesseurs - Sujet 8 - Thermostat

DJERABA Taky - HUBERT Thomas - PREUD'HOMME Geoffrey

Sujet

Le projet consiste à réaliser un thermostat pour la commande du chauffage d'une maison individuelle. Le dispositif devra d'abord afficher alternativement la température et l'heure chaque 10s, sur des afficheurs 7-segments. Notre thermostat devra aussi permettre le réglage de la température pour chaque heure de la journée, selon deux modes : jour et nuit, dont les températures seront réglé par l'utilisateur. Enfin, la commande permettant l'allumage de la chaudière se fera en fonction de la température mesurée. Si celle-ci est 0.5°C inférieure à la consigne fixée, la chaudière s'allume. Si la température mesurée dépasse la consigne de 0.5°C, la chaudière s'eteint.

De plus nous nous sommes fixés un objectif supplémentaire afin de faire programme qui consomme un minimum d'énergie. Bien que ce genre de système est branché en permanence au secteur et la consommation du micro-contrôleur est négligeable par rapport à celle de la chaudière, on considère que programmer de manière optimisée est une bonne pratique à adopter. On s'arrêtera dans cet objectif à l'optimisation des instructions assembleurs.

Enfin, afin de vérifier la validité de notre algorithme et de nos configurations, nous avons recréé une simulation du système avec une Arduino Mega 2560, qui est la carte que nous avons utilisé pour les TP de microprocesseurs, et que nous avons programmé en utilisant les mêmes outils utilisés pendant lesdits TP.

Architecture matérielle

Schéma de la modélisation

Entrées

On utilisera 4 boutons : pour valider, revenir en arrière, incrémenter et décrémenter. Ils sont branchés sur les ports d'interruption INT0 à INT3 respectivement pour éviter une scrutation afin d'économiser de l'énergie. <!-- TODO Pourquoi résistances et capa -->

Le capteur de température est connecté sur le convertisseur analogique-numérique numéro 0 (ADC0). Pour la simulation, nous avons utilisé un potentiomètre afin de régler la température d'entrée du thermostat.

Sorties

En sortie, nous avons utilisés 4 afficheurs 7-segments, lesquels permetront l'affichage de l'heure, de la température, et du mode de fonctionnement. Les afficheurs devront aussi permettre à l'utilisateur de régler le thermostat en faisant défiler les options de réglages (heure, date, réglage de la température en fonction de l'heure de la journée). Pour la simulation, nous avons utilisé un quadruple affichage 7-segments (et point) de 12 broches, que nous avons branché sur les ports parallèles A (pour la représentation) et C (pour la sélection de l'affichage).

Enfin, le signal de contrôle de la chaudière est branchée sur un port parallèle, un non utilisé par l'afficheur 7 segments pour simplifier éviter de faire des opérations d'entrée/sortie avec des masques. Pour la simulation, nous avons utilisé une LED branchée sur le port G (PG0).

Étude du fonctionnement

Pour pouvoir afficher et modifier les différentes valeurs dont l'utilisateur a accès, on a créé un menu, dont voici sa hiérarchie :

  • Veille
    • Association mode ↔ heure de la semaine
      • Lundi
      • Mardi
      • Mercredi
      • Jeudi
      • Vendredi
      • Samedi
      • Dimanche
    • Température du mode jour
    • Témpérature du mode nuit
    • Réglage de la date et heure
      • Jour de la semaine
      • Heures
      • Minutes

On peut passer d'un menu à son menu hiérarchiquement supérieur (parent) avec le bouton Retour. On passe d'un menu à un autre de niveau identique mais de même parent (frère) avec les boutons Incrémenter et Décrémenter. Lorsque l'on appuie sur le bouton Valider, deux choses peuvent se produire : si le menu possède des sous-menus (enfants), il passe au premier enfant, sinon, il permettra l'édition d'une valeur que l'on poura incrémenter ou décrémenter avec les boutons du même nom, et qui seront sauvegardées à l'aide du bouton retour. Même si ce menu peut sembler simple à en voir sa vue d'ensemble, il est beaucoup plus difficile à comprendre lorsqu'il est réduit à 4 afficheurs 7 segments. On oubliera pas de mettre ce genre d'information dans un guide si jamais ce système doit être utilisé par quelqu'un d'autre.

Contrôle de la température

Concernant la régulation de la température, il a été prévu que celle-ci se fasse chaque 10 secondes, car la température n'est pas un paramètre qui change très rapidement. Puisque l'allumage de la chaudière est asservi en température, notre dispositif utilise un capteur de température, lequel fournit une tension variant de 0 à 5V, pour une plage de température allant de 5 à 30.6°C. Pour une précision de 0.1°C, cette plage peut se coder sur 8bits car elle contient 257 valeurs (on fera abstraction de la dernière valeur).

La température mesurée (et convertie via le convertisseur ADC), est comparée aux valeurs $\text{température de référence} - 0.5\degree C$ et $\text{température de référence} + 0.5\degree C$ afin de savoir si il faut activer la chaudière ou non. Pour éviter un dépassement de capacité lors de la comparaison, on oblige l'utilisateur à choisir une température entre 5.5°C et 30.0°C.

Stockage des données

Les accès à la RAM nécessitant plus d'instructions et donc étant plus gourmands en énergie, on stockera le maximum d'informations dans les registres, de préférences les données étant lues/écrites les plus fréquemment. On y stockera donc :

  • 4 registres : les 4 digits de l'afficheur 7 segments en cours d'affichage. Ces informations devant être récupérées 4 fois toutes les quelques millisecondes, on ne calculera leur représentation que lors de leur changement et on les stockera dans ces registres.
  • 1 registre (dont 4 bits inutilisés) : afficheur 7 segment actuellement en cours d'affichage. Cela permet d'alterner les 4 dans l'ordre. Pour économiser un registre d'utilisation générale, on utilisera le fait que le port C est considéré comme un registre et sauvegarde les états qui lui sont envoyés.
  • 3 registres : Stockage du temps. Le compteur de temps le plus grand mais à la fois diviseur de la période de changement de mode veille et d'une heure sur l'Atmega 2560 est deux secondes. On comptera le temps de la semaine avec cette période de deux secondes. Il y a alors $\frac{7 \times 24 \times 60 \times 60 }{ 2 }$ $=302400$ valeurs possibles pour ce compteur de temps. On a donc besoin de 3 registres $\left( 2{8 \times 2} = 65536 {8 \times 3} \right)$ pour stocker cette valeur. Afin de simplifier, sur le premier registre on utilisera 3 bits pour stocker le jour de la semaine, 5 bits pour l'heure du jour, on utilisera le deuxième registre pour stocker la minute de l'heure, et le troisième registre pour stocker le nombres de fois qu'il y a eu 2 secondes dans la minute actuelle. Cela permet de simplifier les calculs et d'éviter de faire des opérations sur des entiers de 24 bits (on aurait pu utiliser un registre supplémentaire pour stocker le jour de la semaine et éviter des opérations, mais on voulait s'amuser avec des masques et des divisions).

TODO Il manque des registres

Les associations de chaque heure de la semaine à son mode (jour/nuit) seront stockées dans la RAM. Bien qu'en théorie nous n'avons besoin que de $\frac{ \text{nb jours/semaine} \times \text{nb heures/jour} \times \text{nb bits pour stocker le mode} }{ \text{nb bits stockables sur une adresse} }$ $=\frac{7 \times 24 \times 1 }{ 8 }$ $=\frac{168}{8}$ $=21$ adresses pour stocker ces informations, on préfèrera utiliser une adresse par heure de la semaine, soit $168$ adresses. Bien que l'on perd en espace mémoire disponible, on garde en rapidité d'éxecution (et donc en énergie), en effet il n'est pas nécessaire d'utiliser des masques pour récupérer la valeur des bits individuels. Dans notre cas le microprocesseur n'abritera aucun autre programme avec qui il devra partager la RAM, et la consommation énergétique ne change pas selon le nombre d'adresses utilisées, il n'y a donc que des avantages à utiliser cette technique.

Configuration des interfaces

Interruptions externes

Nos boutons sont branchés sur les ports d'interruptions INT0 à INT3. On active donc les interruption au niveau du micro-contrôleur avec la directive SEI (qui servira aussi pour les autres interfaces mais on ne le rappelera pas). Ensuite, on active les interruptions sur front montant

TODO Blabla rebond etc.

Analogic to Digital Converter (ADC)

Le thermostat est branché sur l'entrée analgique ADC0. Pour éviter d'utiliser de l'énergie inutilement, on désactivera les autres convertisseur analogique-numérique avec les directives DIR0 et DIR2.

On configurera les registres concernant l'ADC de manière à mettre les 8 bits de points forts sur un seul registre (ADCH). En effet, les valeurs de témpératures allant de 5,0° à 30,6°, en choisissant une précision de 0,1° on obient 256 valeurs possibles, ce qui peut se stocker sur un seul régistre. Une précision supplémentaire ne semble pas nécessaire.

Il sera de plus configuré pour s'activer sur demande et activera une interruption dès que la conversion est terminée afin d'éviter toute scrutation.

Watchdog

TODO

Timer

TODO

Algorithme

Voici la fonction principale du programme :

boucle:
    sleep
    jmp boucle

En effet, nous avons dit précédemment que pour économiser l'énergie, toutes les actions seraient gérées par des interruptions, le reste du temps le micro-contrôleur ne fait que dormir. On peut se le permettre car les actions sont très courtes, et il n'y a donc peu de risque de conflit au cas où deux interruptions se déclenchent en même temps.

L'algorithme étant assez imposant (~620 lignes) et redondant, on ne présentera que les principes clefs et quelques exemples d'astuces utilisés. Le code complet (et les outils utilisés pour l'ensemble du projet) sont disponibles à l'adresse suivante : https://archives.plil.fr/gbontoux/s6-mp-tutorat (Fies > principal.txt).

Ce fonctionnement peut être implémenté de deux manières différentes. La première, étant que à chaque élément du menu correspond un emplacement dans le code assembleur, et le programme passe le plus clair de son temps à scruter les boutons. La deuxième, étant que le programme reste sur une instruction sleep, et que les boutons provoquent des interruptions qui gèrent modifient l'affichage et les valeurs nécessaires en fonction du bouton qui a été appuyé et de l'état précédent du menu. D'un point de vue énergétique, la deuxième solution est clairement gagnante. Il faut alors stocker dans quel état est le menu en mémoire. On utilisera pour cela un registre d'état, qui prendra les valeurs suivantes :

  • 0→1 : Veille (Heure / Température)
  • 2 : Menu Association mode ↔ Heure de la semaine
  • 3→9 : Menu jours (Lundi - Dimanche)
  • 10→16 : Paramétrage des jours (Lundi - Dimanche)
  • 17 : Menu température du mode jour
  • 18 : Paramétrage température du mode jour
  • 19 : Menu température du mode nuit
  • 20 : Paramétrage température du mode nuit
  • 21 : Menu réglage de la date et heure
  • 22→24 : Menu réglage du jour / heure / minute
  • 25→27 : Paramétrage du jour / heure / minute

On parle de menu quand l'écran affiche le nom de l'élément que l'utilisateur s'apprête à modifier, et de paramétrage lorsque l'utilisateur voit la valeur qu'il est en train de modifier. Pour les paramètrages, un registre nommé compteur accompagne le registre etat, et permet de stocker la valeur qui est en cours de modification. À chaque appui sur un bouton, une interruption est émise et le code associé effectue une tâche qui varie selon la valeur d'etat. L'interruption peut changer certaines variables du système (la température du mode jour par exemple, ou l'heure actuelle), ou bien l'état lui-même.

Pour chaque état on a défini plusieurs fonctions qui sont appelés par les différents boutons. Pour un état donné Demo, on aura alors :

  • etatDemoU qui permet de changer l'affichage pour l'état courant, éventuellement en fonction de la valeur de compteur dans le cas d'un paramètrage. Cette fonction est appelé à la fin de toutes les autres fonctions que l'on définira par la suite.
  • etatDemo est l'action éxecutée lorsque l'utilisateur arrive sur l'état : la valeur du registre etat est changée, et la valeur de compteur est chargée avec la variable du système correspondante dans le cas d'un paramètrage.
  • etatDemoC (paramètrages seulement) est appelé lorsque la valeur de compteur est changée par les boutons Incrementer ou Decrementer. Elle fait appel aux fonctions boundType qui permettent de remettre dans les bornes la valeur si elle passe hors de l'intervalle de définition (par exemple si une minute est incrémentée à 60 elle repasse à 0). Ensuite elle sauvegarde dans le bon endroit de la mémoire (registres ou RAM) la valeur de compteur.

Pour certains états, on aura aussi un comportement plus spécifique. Par exemple, lorsqu'on fait Retour depuis les états 10 à 16, la valeur d'etat est décrémentée de 7. On créera donc deux fonctions : etatMenuJours qui est utilisé quand on arrive d'un niveau parent (on passe donc à l'état 3, Lundi par défaut), et etatMenuJoursR qui est utilisé quand on arrive d'un niveau enfant (on passe donc à l'état du menu correspondant au jour que l'on venait de modifier).

Voici un exemple, pour le menu et le paramètrage de la sélection de la température du mode jour :

; Appelé depuis l'état  2 après un appui sur le  bouton Incrémenter
;                      19                               Décrémenter
;                      18                               Valider
;                      18                               Retour
etatMenuTJour:
    etat <- 17
    d3 <- 0x3c ; "JOUR"
    d2 <- 0x5c
    d1 <- 0x1c
    d0 <- 0x44
    reti

; Appelé depuis l'état 17 après un appui sur Valider
etatParaTJour:
    etat <- 18
    compteur <- tempJour ; Lecture depuis la RAM
etatParaTJourU:
    call afficherTemperature
    reti

; Appelé depuis l'état 18 après un appui sur Incrementer ou Decrementer
etatParaTJourC:
    call boundTemperature ; Remet la température dans les bornes
    tempJour <- compteur ; Écriture vers la RAM
    call agirHeure ; Au cas où on change le mode de l'heure actuelle, on recharge la température de référence
    jmp etatParaTJourU

On remarque l'utilisation de reti et non de ret. Étant donné que ces fonctions sont les dernières appelées dans les interruptions des boutons, on fait de la sorte pour simplifier le code.

Fonctions usuelles

Pour clarifier le code et pouvoir le réutiliser, on définit un certain nombre de fonctions. Généralement, elles utilisent la valeur de compteur dans son contexte, et la modifie si besoin.

On y trouve les fonctions d'affichage, qui considèrent compteur comme une certaine donnée (un jour, une température, une minute) et l'affiche dans un format approprié. Par exemple, voici la fonction afficherTemperature, utiliser dans l'état de veille et pour paramétrer les températures des deux modes :

afficherTemperature:
    ; Considère le registre compteur comme une température et l'affiche
    d0 <- 0x63 ; Sigle degré
    d1 <- afficheur@ROM[compteur%10] ; Décimale
    temp <- compteur / 10 + 5 ; On stocke temporairement la partie entière pour simplifier
    d2 <- afficheur@ROM[temp%10] | 0b10000000 ; Chiffre des unités, auquel on ajoute le point
    d3 <- 0x00 ; On affiche le chiffre des dizaines si la température est > 10
    si temp > 10 alors d3 <- afficheur@ROM[temp/10]
    ret

Les boutons d'incrémentation et de décrémentation changent la valeur de compteur indépendamment du type de données qu'il contient. Il faut donc remettre à posteriori les valeurs dans leur intervalle de définition. Voici un exemple pour utilisé pour la modification de l'heure actuelle :

boundHeur:
    si compteur =  255 alors compteur <- 23
    si compteur >= 24  alors compteur <- 0
    ret

On utilise le fait qu'une décrémentation depuis la valeur 0 effectue un dépassement et remet le registre à 255. On aurait pas pu utiliser le flag indiquant qu'un dépassement a eu lieu car l'incrémentation / la décrémentation de compteur s'effectue beaucoup plus tôt que la remise en borne. Pour faciliter la vie de l'utilisateur, lorsque l'on dépasse une des deux bornes, la valeur prend la borne opposée.

Fonction d'agissement

TODO

Interruptions

TODO

Timer

Watchdog

ADC

Boutons

Conclusion

Pour ce projet de thermostat, nous avons réalisé son architecture matérielle, son algorithme, et même réussi à en faire une simulation sur du matériel réel, ce qui a nécessite l'écriture de l'algorithme complet. Bien qu'un sujet similaire était proposé dans un des TP, on s'est rendu compte que l'ajout de certaines fonctions paraissant anodines (telles que la gestion du temps, ou un menu) nécessitent un gros travail pour tout coordonner. La réalisation de la simulation, bien que non nécessaire, a été utile pour se rendre compte que certains chemins envisagés au débuts n'étaient pas réalisables, et a permis de pouvoir tester notre algorithme. Au final