Posts Tagged ‘DIY’

Ok, trois articles sur le sujet ça peut paraître court, mais ça constitue déjà une bonne introduction, qui devrais vous permettre d’envisager la suite par vous-même. Nous allons aujourd’hui nous pencher sur un autre élément essentiel de la programmation avr :

Les interruptions

Imaginez que vous êtes en train de souder un circuit quand tout à coups la sonnette de votre porte d’entrée résonne. Vous pouvez arrêter ce que vous étiez en train de faire (mais rien ne vous y oblige), aller répondre, et revenir à vos soudures. Et c’est exactement comme ça que se passe une interruption dans le monde informatique, ici représentée par la sonnette.

Les interruptions peuvent être matérielles (changement d’état d’une broche, timer qui arrive à une certaine valeur), ou logicielles, et le nombre d’interruption disponibles dépend du modèle d’avr.
Les différentes interruptions disponibles sur votre microcontrolleur sont visibles sur la table des vecteurs d’interruptions de la datasheet (ici, je suis toujours sur l’Attiny85)

Vecteurs d’interruption

Lorsqu’une interruption se produit, l’avr stoppe ce qu’il était en train de faire pour exécuter  la fonction que vous souhaitiez rattacher à cette interruption. Pour ce faire, il utilise une table des vecteurs d’interruption, positionnée au début de sa mémoire flash, afin de faire la correspondance Interruption <–> fonction.

Afin d’utiliser une interruption sur notre avr, nous avons besoin de faire 3 choses :

  • Positionner le bit Enable Interrupt (en général avec la fonction sei(), set global interrupt, mais peut aussi être positionné à la main)
  • Positionner les bits de chaque interruption.
  • et enfin remplir la condition de l’interruption.

Comme d’habitude, nous allons voir ensemble un petit exemple. Ce programme servira à compter les impulsions reçues sur la pin 5 de l’Attiny85. Lorsqu’il arrivera à 200 ou plus, il allumera une led sur la pin 6. Les impulsions pourront être crées avec un bouton poussoir (attention au debounce) ou un générateur de signal.

#include <avr/io.h>
#include <avr/interrupt.h>

//	   ___
//  PB5  *|+  |*  VCC
//  PB3  *|   |*  PB2
//  PB4  *|   |*  PB1   --> sortie led
//  GND  *|___|*  PB0   --> entrée surveillée
//

volatile int count = 0; //compte des interrupts


int main(void)
{
	//configuration de la pin de sortie
	//configuration des interruptions
		//Positionnement du Global Interrupt MaSK register
		//Positionnement du Pin Change Mask Register
		//mise en place des interrupts (set global interrupts)

	for(;;)
	{
		
		
		//Si on a eu 200 impulsions ou plus
		{
			//on alume la led
		}
		//sinon
		{
			//On éteind la led
		}
	}
}


ISR (PCINT0_vect) { //vecteur d'interruption
	// detection de front montant
	{  
		//on ajoute 1 au décompte
	}
	//detection de front descendant
	{
		//ici on ne fais rien, juste pour l'exemple...
	}
} 

Voilà, déjà, on peut remarquer en fin de code la façon dont est définie le vecteur d’interruption. Un autre détail à remarquer est la déclaration de ma variable count. Cette variable étant utilisée à la fois par mon programme principal et par ma fonction, il est impératif de la déclarer en volatil, sous peine de ne jamais la voir s’incrémenter.
Voyons voir maintenant comment déclarer nos interruptions (encore une fois, les valeurs des bits sont tirés de la datasheet, cf page 53, chapitre 9.3.2) :

#include <avr/io.h>
#include <avr/interrupt.h>

//	   ___
//  PB5  *|+  |*  VCC
//  PB3  *|   |*  PB2
//  PB4  *|   |*  PB1   --> sortie led
//  GND  *|___|*  PB0   --> entrée surveillée
//

volatile int count = 0; //compte des interrupts


int main(void)
{
	//configuration de la pin de sortie
	//configuration des interruptions
		//Positionnement du Global Interrupt MaSK register
                GIMSK |= (1 << PCIE); 	//Enable pin change interrupt for PORTB 
		    		        //GIMSK = General Interrupt Mask Register
				        //PCIE = Pin Change Interrupt Enable
		//Positionnement du Pin Change Mask Register
                PCMSK = (1 << PB0);  	//Enable pin change interrupt for PB0 (pcint0)
		  		        //PCMSK = Pin Change Mask Register
		//mise en place des interrupts (set global interrupts)
                sei();

	for(;;)
	{
		
		
		//Si on a eu 200 impulsions ou plus
		{
			//on alume la led
		}
		//sinon
		{
			//On éteind la led
		}
	}
}


ISR (PCINT0_vect) { //vecteur d'interruption
	// detection de front montant
	{  
		//on ajoute 1 au décompte
	}
	//detection de front descendant
	{
		//ici on ne fais rien, juste pour l'exemple...
	}
} 

Quelques petites explications complémentaires s’imposent ici. J’ai décidé de dédier une pin de mon Attiny85 à la surveillance du signal d’entrée, j’ai donc utilisé les Pin Change Interrupt. Mais si j’avais voulu utiliser ma broche pour d’autres choses en parallèle, j’aurais du utiliser les External Interrupt Request (donc positionner le bit INT0 au lieu de PCIE). Il faut également noter que par défaut, une interruption ne peut en interrompre une autre (comprendre : les interruptions sont désactivées le temps du traitement du vecteur d’interruption actuel). Il est cependant possible (mais pas franchement recommandé) de les réactiver en réutilisant sei() à l’intérieur de la déclaration du vecteur d’interruption. De la même manière, si vous souhaitez qu’une portion de votre code ne soit interrompue sous aucun prétexte, vous pouvez utiliser la fonction cei().
Voici maintenant le code complet :

#include <avr/io.h>
#include <avr/interrupt.h>

//	   ___
//  PB5  *|+  |*  VCC
//  PB3  *|   |*  PB2
//  PB4  *|   |*  PB1 
//  GND  *|___|*  PB0   --> entrée surveillée
//

volatile int count = 0; //compte des interrupts


int main(void)
{
	//configuration de la pin de sortie
	DDRB |= (1 << PORTB1); 	//on configure PB1 en tant que sortie
				//DDRB = Port B Data Direction Register

	//configuration des interruptions
	GIMSK |= (1 << PCIE); 	//Enable pin change interrupt for PORTB 
				//GIMSK = General Interrupt Mask Register
				//PCIE = Pin Change Interrupt Enable

	PCMSK = (1 << PB0);  	//Enable pin change interrupt for PB0 (pcint0)
				//PCMSK = Pin Change Mask Register

	sei(); //mise en place des interrupts (set global interrupts)

	for(;;)
	{
		
		
		if(count>=200) //Si on a eu 200 impulsions ou plus
		{
			PORTB |= (1 << PB1);
		}
		else
		{
			PORTB &= ~(1<<PB1);
		}
	}
}


ISR (PCINT0_vect) { //vecteur d'interruption
	if (PINB & (1<<PB0)) // detection de front montant
	{  
		count = count++;
	}
	else //detection de front descendant
	{
		//ici on ne fais rien, juste pour l'exemple...
	}
} 

Bon, passons sur les modifications d’état de la led, déjà abordées auparavant. Il est ici intéressant de noter comment se fait la détection d’un front montant ou descendant : à la suite d’un changement d’état, on lit l’état de la pin PB0, si elle est à l’état haut c’était un front montant, sinon, un front descendant.
Voilà, c’était le dernier article de cette série, qui sera suivi très bientôt d’une application concrète 🙂

Bon, si je me suis embêté à vous pondre une série d’articles sur la programmation AVR, c’est que j’avais une petite idée derrière la tête. En l’occurrence, j’avais besoin de pouvoir couper une alimentation embarquée dans un avion RC, bien sûr à distance.
L’idée était donc de réaliser un petit périphérique qui se connecte comme un servo-moteur standard de modélisme, mais qui active un relais en fonction de la valeur.

Déjà, il est important de savoir à quoi ressemble les signaux transmis par le récepteur RC aux servos :

Credit : http://nononux.free.fr

Bon, il s’agit d’une sorte de PWM, mais avec un champs très limité car allant de 5 à 10% de la valeur. Dans mon cas, je ne suis intéressé que par 2 cas : On ou Off. Je décide donc de couper à 50%, de manière à pouvoir utiliser le manche des gaz en guise d’interrupteur : de 0 à 50% je suis Off, de 50% à 100% je suis On.
A partir de là, il y a plusieurs façons de voir les choses : il est possible de régler ça en analogique pur, mais j’avais peur que ce soit trop sensible aux éventuelles perturbations. (Et dans mon cas, je préférerais que ce ne soit pas sensible 😉 ). Je suis donc partis sur la solution numérique pour traiter les impulsions.
Comme c’est pour embarquer dans un avion RC, il ne faut pas que ce soit lourd, donc j’ai choisi le micro-controlleur le plus petit possible, à savoir l’AtTiny85, que vous devez bien connaître désormais (voir ici, ici et ici), et qui plus est dans sa version CMS.

Le sch

Comme vous pouvez le constater sur le schéma ci-dessus, la partie électronique est réduite au strict minimum : Un régulateur de tension, pour fournir le 5v nécessaire à tout l’appareillage (servos, récepteur, etc…) dans le cas où l’on ne dispose pas de BEC (par exemple s’il n’y a pas de moteur). L’AtTiny85 dans sa configuration la plus simple (oscillateur interne), un mosfet pour piloter le relais, et le relais en lui même, petit relais 5v, capable de couper 175W quand même.
Le circuit a été réalisé sur un PCB de 0.8mm d’épaisseur, de manière, là encore à gagner du poids.

Vous l’aurez donc compris, dans ce montage, c’est le code qui fait tout le travail :

#include <avr/io.h>
#include <avr/interrupt.h>

//	   ___
//  PB5  *|+  |*  VCC
//  PB3  *|   |*  PB2
//  PB4  *|   |*  PB1   --> declenchement relais
//  GND  *|___|*  PB0   --> entrée PWM
//

volatile int count = 0; //le rapport cyclique
volatile int toff = 0;  //durée du signal à 0
volatile int ton = 0;	//durée du signal haut

int main(void)
{
	//configuration de la pin de sortie
	DDRB |= (1 << PORTB1); 	//on configure PB1 en tant que sortie
				//DDRB = Port B Data Direction Register

	//configuration du timer1 (Ton)
	TCCR0B |= (1 << CS00) | (1<<CS02); 	//Set up timer1 with prescaler 1/1024

	//configuration des interruptions
	GIMSK |= (1 << PCIE); 	//Enable pin change interrupt for PORTB 
				//GIMSK = General Interrupt Mask Register
				//PCIE = Pin Change Interrupt Enable

	PCMSK = (1 << PB0);  	//Enable pin change interrupt for PB0 (pcint0)
				//PCMSK = Pin Change Mask Register

	sei(); //mise en place des interrupts (set global interrupts)

	for(;;)
	{

		if(count>=7.5) //Si le rapport cyclique est > à 7 (Ton ~1.5ms)
		{
			PORTB |= (1 << PB1);
		}
		else
		{
			PORTB &= ~(1<<PB1);
		}
	}
}

ISR (PCINT0_vect) { //vecteur d'interruption
	if (PINB & (1<<PB0)) // detection de front montant
	{  

		toff = TCNT0; 	//Enregistrement de la valeur du timer Toff
		TCNT0 = 0;	//Réinitialisation du timer
	}
	else //detection de front descendant
	{
		ton = TCNT0;	//Enregistrement de la valeur du timer Ton
		TCNT0 = 0;	//Réinitialisation du timer
		if(toff)	//si on a déjà une valeur pour Toff
		{
			count = (ton*100L)/(ton+toff); //Le rapport cyclique = ton/(Ton+toff)
		}		
	}
}
Voilà, il ne reste donc plus qu’à assembler tout ça, et à tester :

Le montage avec une led pour tester.

La chaîne complète, avec le récepteur

Et histoire de vérifier que tout fonctionne, une petite vidéo :

Voilà, un nouvel article prochainement pour vous faire voir l’utilisation réelle du bidule 😉
Ps : c’est un kit que vous retrouverez sur la boutique

Le premier test m’avais bien plu, et j’avais trouvé l’opération amusante. Vu les quantités que j’avais fait la dernière fois, mes savonnettes sont passées assez vite. J’ai donc décidé de remettre ça, mais cette fois “pour de vrai”, avec des ingrédients sympa, en plus grosse quantité, et au passage, en impliquant Madame 😉

Premièrement, la recette

Lors de ma précédente tentative, je n’avais utilisé que de l’huile d’olive, et la première huile essentielle qui me tombais sous la main, à savoir du clou de girofle. Bon, ça marchait bien, ça allais bien pour se laver les mains, mais c’était quand même pas le savon le plus appétissant que je connaisse…
Cette fois-ci, on va s’ouvrir un peu plus l’appétit avec du savon à l’huile de coco et à la vanille.

Les ingrédients

Voici la liste des courses :
200g d’huile de coco (plus du beurre en fait, mais ça s’appelle huile quand même)
400g d’huile d’olive
85g de soude caustique
200ml d’eau

Première étape, peser les ingrédients

La soude

Comme le nom de l’étape le laisse deviner, on pèse chacun des ingrédients dans des récipients séparés. Pour la soude, j’ai préféré bricoler un récipient en papier plutôt que d’utiliser de la vaisselle de cuisine ou un récipient en plastique dont la tenue à la soude n’était pas garantie.

L’huile de coco

Bon, alors l’huile de coco, ça a deux caractéristiques : Ca ressemble à du saindoux, mais ça sent sacrément bon ! (a tel point que ça donne envie de la goûter tel quel)

Seconde étape, mélanger

Alors là, pas question de faire n’importe quoi, la soude est un produit dangereux. Blouse, lunettes (de chimie) obligatoires (gants vivement conseillés au passage).
Il faut tout d’abord dissoudre les cristaux de soude dans l’eau. La réaction est exothermique, et peut dégager des fumées, il est donc recommandé de faire ça sur le balcon. Donc, dans le saladier, verser l’eau, puis, petit à petit (cuillère à cuillère), verser les cristaux de soude dans l’eau, et remuant (avec un autre instrument que celui qui vous sert à prendre les cristaux de soude!). Je le rappelle, pour ceux qui sont pas habitués, pas d’eau dans la soude, toujours la soude dans l’eau, sinon il y a risque de projections.

Une fois cette étape réalisée, il faut mélanger les corps gras à la soude, de la même manière, en procédant petit à petit au début, puis plus franchement.

Vu comme ça c’est pas appétissant, mais ça sent bon !

Après avoir un peu touillé à la cuillère, je vous conseille de passer au mixer, car il va falloir mélanger longtemps…
Il faut donc mélanger jusqu’à la trace, qui prend entre 15 et 30 min en fonction en la température entre autre. Pour pas tuer votre mixer, je vous conseille de faire ça par tranche de 5 min, avec des poses pour le laisser refroidir entre chaque.
Au bout d’un moment, le mélange commence à épaissir, et le filet qui coule du mixer laisse une marque en surface. C’est la “trace”.

La trace

A ce moment là, vous pouvez ajouter les huiles essentielles (de vanille donc, dans mon cas).

Troisième étape, le moulage et le séchage

Bon, là, faites pas comme moi, et prévoyez suffisammentde moules pour vos savons ! J’avais prévu de jolis moules à madeleines, mais une fois ceux-ci remplis, il me restait encore les 3/4 de la préparation ! Résultat des courses, j’ai mis le reste dans un bac de glace, pour les découper en tranches de 2cm d’épaisseur une fois durcis.

Les savons moulés

Là encore, l’odeur est très appétissante, combinée à la forme de madeleine, on en mangerais 🙂
Au bout de 2/3 jours, il faut les démouler. Il est possible de les entourer de film alimentaire pour qu’ils ne blanchissent pas.

Reste maintenant la partie la plus difficile : attendre ! J’avais testé mes précédents savons au bout d’un mois, et ils étaient encore un peu irritants. Au bout de 2 mois en revanche, ils étaient parfaits ! Je pense donc ne pas utiliser ceux-là avant le mois de Juillet, mais promis, je vous ferais un retour à ce moment là !

C’est pas encore l’heure des bonnes résolutions, mais je renoue ici avec une de mes passion premières, un peu délaissée ces derniers temps : la haute tension.
Le montage auquel je me suis attaqué aujourd’hui est assez simple dans son fonctionnement, il s’agit d’un générateur de Marx, qui permet de multiplier une tension. L’avantage de ce montage, est qu’outre sa simplicité, la tension de sortie est directement proportionnelle au nombre d’étages mis en jeux (aux pertes près).

Générateur de Marx

Le concept consiste à charger X condensateurs en parallèles, et les décharger en série. Pour arriver à un pareil résultat, on va utiliser des éclateurs. En effet, tant que la tension  aux bornes du condensateur ne dépasse pas une certaine tension, rien ne se passe, les condensateurs se chargent tranquillement, et la tension à leurs bornes augmente peu à peu.

Charge

Mais lorsque cette tension est atteinte, un arc électrique se produit sur l’éclateur, qui deviens ainsi conducteur.

Décharge. En bleu ciel, les arcs électriques

(oui, désolé pour les couleurs un peu flashy, ça pique les yeux, je sais)
Pour que ça fonctionne (bien), il y a tout de même quelques paramètres importants à respecter. Déjà, il faut que la tension d’alimentation soit suffisamment élevée pour pouvoir produire un arc électrique, sinon les éclateurs ne fonctionneront pas. Ensuite, les résistances de charges. J’ai un peu galéré pour trouver des résistances correctes pour ce montage. Au début, j’étais partis avec des résistances couches carbone (les classiques), mais rapidement les arcs sont passés par le côté de la résistance, détruisant celles-ci. J’ai ensuite voulu utiliser des résistances de puissances en céramique. Mauvaise idée : elles ont été détruites en totalité sur un seul shoot. Je suspecte plus la déflagration d’avoir endommagé l’intérieur que la chauffe proprement dite car elles étaient sensées tenir plus que les couches carbone, ce qui n’aura pas été le cas ici. Finalement, j’ai utilisé des résistances bobinées de puissances, et après quelques dizaines de minutes de fonctionnement, tout à l’air en ordre de marche. Dernier point, la distance entre les éclateurs est relativement importante, c’est d’elle que dépendra la “tension de claquage”

Mon générateur de Marx

Comme on peut le voir sur la photo, mon générateur est constitué de 6 étages, ce qui multiplie donc par 6 la tension d’entrée… En théorie. En effet, mes éclateurs étant difficiles à régler, ma tension de claquage est assez loin du maximum débité par mon alimentation. Au final, je dois arriver à une tension de 10Kv par condensateur, soit 60Kv en sortie.
Les éclateurs sont de simples fils de cuivre recourbés, passés au papier de verre pour enlever l’émail.

Le générateur en action. Le père Noël fait 12cm

Petit détail pour ceux qui serais tentés par l’expérience, et qu’il est difficile de rendre sur une photo : C’est extrêmement bruyant !! (genre mitraillette dans le salon)

Ceux qui me connaissent bien vous le diront, je suis un gourmand :p C’est pourquoi quand Greg a proposé d’organiser un atelier chocolat au sein du log, j’ai saisi l’opportunité 🙂
L’objectif de l’atelier : nous apprendre à travailler le chocolat, et réaliser nos propres oeufs en chocolat, et autres “bonbons” fourrés. De mon côté, j’avais déjà assisté à un premier atelier la semaine précédente, pendant lequel j’en avais profité pour faire des sucettes au chocolat en forme de moustaches, fourrées à la ganache chartreuse. Cette fois-ci, je me suis donc concentré sur la réalisation d’un “gros” oeuf 🙂
Pour les gourmands qui voudraient s’y essayer, voici un petit récapitulatif de ce qu’il faut (si j’ai bien tout retenu ;)), et de la façon de procéder.

Matériel nécessaire :

  • Une grande casserole (pour le bain marie)
  • Un petit bol en inox (cul-de-poule)
  • Un thermomètre (pas “obligatoire”, mais pour débuter, c’est vraiment plus facile )
  • Un pinceau
  • Un moule demi-oeuf.
  • Une spatule métallique
  • Pistoles de chocolat (pour moi, c’était du chocolat au lait origine Ghana 40,5%)
  • Une bombe d’azote (réfrigérant rapide)

Contrairement à ce qu’on pourrais croire, le moule à œuf n’est pas en silicone, ou autre matériau souple, mais en polycarbonate bien rigide. En effet, le chocolat en refroidissant va se rétracter, et le demi-oeuf se démoulera tout seul.

Première étape : faire fondre le chocolat, au bain marie, en surveillant la température : il faut monter à 40-45° pour le chocolat au lait. Avec un thermomètre c’est facile, sans, ça correspond à un peu moins du seuil de douleur (précis comme indication, n’es-ce pas ;)). Attention à ne pas faire tomber d’eau dans le chocolat lors de cette étape (ni des suivantes), ce qui ferais coaguler ce dernier.

Ensuite, il va falloir tempérer le chocolat. Cette étape est nécessaire à la bonne cristallisation de votre chocolat, et vous permettra d’avoir un joli aspect brillant, exempt de traces blanches. Une fois votre chocolat à température (40-45°C), sortez le du bain marie et ajoutez un peu moins d’un tiers de pistoles à température ambiante (20%  de pistoles). Mélangez jusqu’à que tous les pistoles aient fondus. (S”ils disparaissent trop rapidement, rajoutez en un peu)

A l’aide du pinceau, appliquez généreusement du chocolat sur votre moule, en n’hésitant pas à déborder, et en ne laissant aucun trou ou zone transparente. Cette étape assurera un beau fini extérieur et l’absence de bulles en surface. Laissez prendre légèrement, et versez du chocolat fondu (hauteur d’une phalange) dans votre oeuf. Répartissez de manière homogène et laissez reposer un peu. Répétez l’opération encore une fois ou deux, puis videz l’excédent dans le cul-de-poule. Raclez les bords avec une spatule métallique. Mettez au frais.

Au bout de quelques minutes, retirez votre oeuf du frigidaire, et laissez le finir de cristalliser dans un coin assez frais (genre bord de fenêtre à l’ombre). Lorsque vous verrez un petit espace entre le moule et le bord de votre oeuf, il est prêt à être démoulé. Placez vous au dessus d’une surface dure (genre table), mettez la main sur le moule et renversez. S’il tombe tout seul, votre main le retiendra, sinon, tapotez légèrement le moule dur la table (en laissant la main dessous, hein !). S’il ne tombe toujours pas, n’insistez pas, laissez le prendre encore un peu. Attention, ne mettez pas les doigts sur la surface de l’œuf, vous y laisseriez des traces. Prenez le par l’intérieur.

Voilà, c’est démoulé, vous avez un premier demi-oeuf. Vous pouvez répéter l’opération pour avoir l’autre moitié. Faite refondre votre chocolat, et re-tempérez le avant car il a pris pendant ce temps.

Une fois vos deux moitiés d’œuf démoulées, refaite fondre un peu de chocolat et retempérez. Avec le pinceau, appliquez du chocolat sur le bord intérieur de l’œuf, en débordant sur la tranche. Répétez sur l’autre moitié, et assemblez. Vous pouvez maintenant vous servir de la bombe d’azote pour accélérer la prise sur la jonction.

Atelier chocolat

Atelier chocolat

Mon neuneuf à moi :)

Mon neuneuf à moi 🙂

ps : pas beaucoup de photos de “pendant”, désolé, mais le chocolat, ça en met vraiment de partout 😉

Voilà, ça y est, mon tour est posé sur sa dalle, je peux enfin l’utiliser. Pour maximiser sa précision, il est vivement recommandé de le mettre de niveau. Pour se faire, point de niveau de maçonnerie, il faut utiliser un niveau de précision, précision de l’ordre de 0,01mm/m.

Sauf que c’est pas complètement donné, surtout pour un équipement qui servira 2 fois dans l’année maximum. En y réfléchissant un peu, il est possible d’obtenir une bonne précision avec un niveau laser. Alors d’accord, un niveau laser, c’est pas non plus donné (pour un correct j’entends), mais au moins ça peut servir pour tous les autres travaux, et c’est facile d’en trouver en prêt chez les copains.

L’idée, c’est de se servir d’un miroir (ou de plusieurs) comme d’amplificateurs d’erreurs. Un petit schéma étant plus parlant qu’un long discours, voici la procédure que j’ai suivi :

niveau_laser

Mesure de précision avec un niveau laser

Il faut partir du principe que le faisceau est parfaitement vertical (normalement vrai pour un niveau de bonne facture, et dans tous les cas, ça se vérifie facilement). Le faisceau sort du niveau avec un angle, de manière à tracer une ligne verticale sur le mur d’en face. Il se reflète sur le miroir qui est posé sur la surface à régler. Si la surface n’est pas parfaitement de niveau, le faisceau réfléchi aura un décalage, et une seconde ligne apparaîtra sur le mur d’en face. Quand les deux lignes superposées sont confondues, c’est que l’on est de niveau… pour une direction. Reste ensuite à faire de même pour l’autre direction, et revérifier que tout n’a pas bougé.

Et niveau précision du coups ? Et bien, le faisceau fait environ 1mm de large, la précision escomptée est donc de 1mm/2h, h étant la distance entre l’origine du faisceau et l’endroit le plus éloigné où vous pouvez voir les 2 faisceaux. Dans mon cas, j’étais à 3m, ce qui me donne une précision de l’ordre de 0,1mm/m. Pas aussi bon qu’un niveau de mécanicien, mais définitivement meilleur qu’un niveau de maçon. Pour améliorer encore la chose, il aurais suffi de mettre un second miroir, pour augmenter “virtuellement” la distance.

Mais j’ai fait ça en pleine journée, et le faisceau devenais difficile à lire à cause de la luminosité. Je m’en contenterais pour le moment !

Merci à Fred pour le coups de main et le laser !

La dernière fois, je vous ai présenté une méthode essentiellement graphique, permettant une bonne approximation des paramètres PID de votre régulateur. Encore faut-il savoir comment tracer les graphiques, c’est ce que je me propose de vous expliquer dans cet article. Il est bien sûr évident que cette méthode peut être utilisée pour tracer des graphiques à partir de tout type de données, du moment que les données arrivent via un port série.
La première étape va consister à récupérer les données du port série sur un PC. Comme souvent, il existe plusieurs façon de faire, je vous présenterais celle que j’ai utilisé ici : un petit script python.

import serial
import sys

serialport = serial.Serial("/dev/ttyACM0", 9600, timeout=1)
line = []

while True:
    for c in serialport.read():
        line.append(c)
        if c == '\n':
            for s in line:
                f=open('myfile','a')
                sys.stdout.write(s)
                f.write(s)
                f.close
            line = []
            break

serialport.close()

Ce petit script prend les caractères arrivant sur le port série, jusqu’au caractère marquant la fin de ligne, puis écrit la ligne dans un fichier. Ca peut éventuellement suffire pour tracer un graphique… Mais dans ce cas précis, on veut pouvoir également envoyer une consigne au régulateur, la modifier afin de suivre la réaction de ce dernier. Problème : comment envoyer des données sans perturber la lecture des données et ne pas sauter de mesure ?
Là encore, il y a plusieurs méthodes possibles, certaines plus complexes que d’autres. Celle que j’ai choisi permet de garder un programme simple, mais ne fonctionnera que sous linux. Ceux sous un autre système chercheront du côté du multithreading…

import serial
import sys
import select

serialport = serial.Serial("/dev/ttyACM0", 9600, timeout=1)
line = []

while True:
        while sys.stdin in select.select([sys.stdin], [], [], 0)[0]:
          ligne = sys.stdin.readline()
          if ligne:
            print(ligne)
            serialport.write(ligne+'\r\n')
          else: # an empty line means stdin has been closed
            print('eof')
            exit(0)
        else:
            for c in serialport.read():
                    line.append(c)
                    if c == '\n':
                        for s in line:
                                f=open('myfile','a')
                                sys.stdout.write(s)
                                f.write(s)
                                f.close
                                line = []
                    break

serialport.close()




Ah oui, j’ouvre et ferme le fichier a chaque écriture, de manière à pouvoir accéder aux données pendant l’exécution du programme. Ca permet de visualiser l’évolution du graphique en temps réel.
Les données sont envoyées par le microcontrolleur sous la forme . Je n’ai pas de mesure de temps, chaque échantillon étant pris à un intervalle d’une seconde, il suffit de compter les échantillons pour connaître le temps écoulé.

Passons maintenant au graphique lui-même. Pour cela, j’utilise le logiciel Gnuplot, outil très puissant de tracé de courbes dont nous n’utiliseront ici qu’une infime quantité de ses possibilités. Lorsque vous lancez gnuplot en ligne de commande, vous vous retrouvez avec un shell dans lequel vous pourrez lancer vos commandes gnuplot.

plot 'myfile' using 1 smooth bezier title 'temperature', 
'myfile' using 2  with line title 'CO', 
'myfile' using 3 with line title 'Setpoint'
 plot 'myfile' using 1 

Indique que l’on va tracer une courbe correspondant aux données de la première colonne du fichier

smooth bezier

Indique que l’on veut lisser les données. Il n’est pas toujours intéressant de lisser les données, par exemple, ici la colonne 3 correspondant à la consigne n’est pas lissée, ce qui permet de visualiser le moment exact du changement de consigne. Par contre, dans le cas des valeurs mesurées, cela permet de s’affranchir du bruit sur les mesures.

title 'temperature'

Légende de la courbe. Pratique dès qu’il y a plus d’une courbe.

Par défaut, gnuplot va afficher le résultat à l’écran. Pour pouvoir enregistrer le résultat dans un fichier il faut taper les instructions suivantes :

set terminal "png"
set output "monfichier.png"
replot

Nota : avec la commande replot (ou avec le bouton replot de la gui), vous pouvez rafraîchir les données affichées, de manière à visualiser en continu vos données…
Il y a certainement plein d’amélioration possibles à ma méthode, mais je vous la présente car elle a le mérite d’être simple et rapide à mettre en oeuvre, tout en fournissant de bons résultats

Étant donné que je n’ai pas encore tout le matériel nécessaire pour avancer mes autres projets, j’en profite pour approfondir un peu mes connaissances théoriques et vous en fait donc profiter. Mon projet ici est simple : Réguler une température à l’aide d’un microcontrolleur, mais surtout bien comprendre les tenants et aboutissements des différents réglages et algorithmes. L’objectif ici n’est pas tant d’expliquer ce qu’est un PID, ni comment il fonctionne dans le detail, mais plutôt de donner une méthode permettant d’approximer les coefficients du régulateur.

Je profiterais de cette expérience pour faire 2 articles :
– celui-ci concernant le PID a proprement parler
– Un second article concernant ma méthode pour obtenir des courbes “lives” a partir d’un arduino et d’un PC.

Qu’es-ce qu’un PID

Pour simplifier, on parlera dans la suite de régulation de température, vu que ce sera notre application ici. Mais bien entendu, une régulation peut se faire sur tout type de process (vitesse, débit, etc…)

Lorsqu’on veut réguler la température (d’une pièce par exemple), l’idée la plus simple qui nous vient à l’esprit est de faire une régulation tout-ou-rien : on est en dessous d’un premier seuil, on allume la chauffe, on passe au dessus d’un second seuil on coupe la chauffe. Cette façon de procéder, bien que très simple à implémenter n’est pas exempte de défauts. Par exemple, la température ne sera jamais vraiment constante, même si elle l’est “en moyenne”. Selon le process, on peut arriver à fortement dépasser les consignes (dans un sens ou dans l’autre), en fonction de l’inertie du système, ce qui peut s’avérer nuisible selon ce qu’on veut faire. Enfin, la puissance du chauffage oscillera entre 0 et 100%, ce qui engendre une surconsommation non négligeable.

La régulation PID permet de coller “au plus juste” à la consigne, en évitant les dépassement (en fonction du réglage comme on le verra plus loin), et en n’utilisant que la puissance nécessaire à aller à la consigne et à la maintenir. Concrètement, dans PID, on a P pour Proportionnelle, D pour Dérivée, et I pour Intégrale. Si comme moi vous avez tendance à être un peu allergique aux maths, vous commencez déjà à vous sauver, mais en fait c’est moins compliqué que ça n’en a l’air.

Proportionnelle : c’est ni plus ni moins que la différence entre votre consigne et la valeur actuelle, multiplié par un coefficient de proportionnalité (Que vous définissez, cf la suite). Dans la suite, nous l’écrirons Kc.e(t)

Intégrale : A chaque mesure, vous allez avoir une différence avec la consigne (en plus ou en moins). Prenez cette erreur à chaque pas de temps, multipliez la par un (autre) coefficient de proportionnalité, et additionnez le résultat de tous les pas de temps depuis le début, et vous avez la fameuse composante Intégrale. Dans la suite, l’intégrale sera notée de la façon suivante :

I

Dérivée : Son rôle est de “freiner” l’augmentation de puissance calculée en fonction de P et de I, de manière à ne pas dépasser la consigne. C’est une façon de prendre en compte la vitesse d’évolution de notre process. Pour le calculer, vous faites erreur actuelle – erreur précédente, multiplié par un troisième coefficient de proportionnalité

La formule globale de notre régulateur PID peut donc s’écrire de la manière suivante (plus d’explications plus bas):

Formule PID, source ControlGuru

Formule PID, source ControlGuru

Description du materiel

Le montage utilisé pour ces essais est très simple : un arduino, un bouton 2 positions, 1 capteur de température (DS18B20), un mosfet (IRF540), une résistance 10Ω 5W, et une alimentation 12V. Le mosfet est piloté par une sortie PWM de l’arduino, et gère le courant (puissance) transitant dans la résistance. Cette dernière chauffe donc plus ou moins en fonction du ratio PWM (la tension change) envoyé par l’arduino. Le bouton 2 positions sert à passer d’une première consigne (valeur PWM 5 sur 255) à une seconde (valeur PWM 30 sur 255) Un petit programme python enregistre les valeurs dans un fichier texte sur le pc (via connection série), et j’utilise GnuPlot pour avoir une jolie courbe et pouvoir lire les valeurs des graphiques plus facilement que directement sur l’image. Par la suite, j’ai ameliore mon script python, de maniere a pouvoir saisir une consigne via le port serie, tout en continuant de logguer.

Montage régulation de température

Montage régulation de température. Attention, utiliser une résistance 5W mini !

Méthode de réglage

Je vous présente une méthode “graphique” directement tirée du site Control Guru, appliquée à mon montage de test. Ce site étant en anglais et très dense en informations, j’ai essayé de résumer les points essentiels ici. J’invite ceux qui voudraient approfondir le sujet à consulter ce site qui est une véritable mine d’information sur la régulation.

Définitions

  • PV : Process Variable, cela correspond à la grandeur mesurée que l’on veut influencer (la température dans cet exemple)
  • CO : Controller Output , la sortie de notre contrôleur en %. (Correspond à l’ouverture de la vanne par exemple)
  • Kp : Gain du process
  • Kc : Gain du contrôleur

Introduction

Une fois modélisé notre boucle de contrôle, nous allons pouvoir en déterminer les trois paramètres principaux par expérimentation, mesure et calcul (et oui…)

Les paramètres à déterminer sont :

  • Kp : la Gain du process, ou, dit autrement, le sens et la grandeur de la variation de la sortie
  • Tp : Constante de temps du process, ou la vitesse de variation quand le process a commencé à répondre
  • θp : (Prononcer Thêta P) Temps mort du process, ou le délai écoulé avant que le process commence à réagir
Boucle de contrôle

Boucle de contrôle

Afin de pouvoir déterminer nos trois paramètres, il va nous falloir mesurer la façon dont réagis notre système. La première étape consiste donc à fixer une première valeur de sortie à notre contrôleur (CO) et attendre que la variable de sortie (PV, ici la température) soit bien stabilisée.

Une fois cette dernière stable, nous allons modifier la valeur de sortie du contrôleur (CO), et mesurer le changement obtenu sur la variable de sortie (PV). On logue bien sûr tout ça dans un fichier, avec les pas de temps, de manière à pouvoir également déterminer le temps mort du process (θp) et la vitesse de variation (Tp).

On se sert ensuite de ces mesures pour tracer un joli graphique, qui nous servira à déterminer nos différentes valeurs.

La formule permettant de déterminer notre premier paramètre Kc, qui est à la base du calcul de nos 3 paramètres (cf. formule) est la suivante :

source : controlguru

source : controlguru

pour déterminer Kc, nous devons donc commencer par déterminer différentes variables intermédiaires.

Calcul de Kp

Kp décrit le sens et de combien va évoluer PV, en fonction d’un changement de consigne (CO) Le cacul de Kp est assez simple : Kp=ΔPV/ΔCO ou en d’autre termes Kp = (différence en PV initial et PV final)/(différence entre consigne initiale et finale)

Sur mon prototype, j’ai modifié la consigne initiale (1,96%) pour une nouvelle consigne à 11.76%, voici la courbe obtenue:

Graphique test du système.

Graphique test du système.

En appliquant la formule ci-dessus, on a donc Kp = (79-40)/(11,76-1,96) = 39/9.8 = 3.98°C/%

Détermination de la constante de temps Tp

Tp représente le “en combien de temps” le système va réagir. En partant du même graphique, on va déterminer quand l’état de sortie vaut 63% de δPV, à partir du moment où le système à déjà commencé à réagir (et pas à partir du moment où on a modifié la consigne. Pour ceux qui se demandent d’où sort les 63%, vous trouverez toutes les explications (attention, maths !) dans ce pdf Fichier:Derive63rule.pdf On regarde donc le nombre de secondes écoulées lorsqu’on arrive à 63% de δPV (ici 39*0.63 + 40 = 64.6°C), et le nombre de secondes écoulées au moment où le système commence à réagir (très rapide ici)

Détermination de la constante de temps TP

Détermination de la constante de temps Tp

On obtiens donc Tp = 172 secondes.

Le cas de θp

θp représente le temps mort avant réaction. Dans une boucle de contrôle, il n’est jamais très bon d’avoir du temps mort, et il sera nécessaire d’avoir cette valeur la plus faible possible. Du fait de mon montage de test, le temps mort est ici très réduit. Ceci s’explique facilement par la position du capteur (sur la résistance), et par le faible volume à chauffer (uniquement la résistance. De manière assez intuitive, on imagine très bien que plus le volume à chauffer sera important, plus le temps mort sera long, pour une même puissance de chauffe.

Si on reprend le graphique ci-dessus, on vois que la consigne a été modifié à T1=195s, et j’ai décider de compter le début de la réaction à T2=198s. On a donc θp = T2 – T1 = 198 – 195 = 3s.

Il est intéressant de noter ici que mon échantillonnage étant d’une seconde, la valeur de θp peut être sensiblement erronée, du fait de sa faible durée dans ce cas.

Application à un modèle PID

Formule PID, source ControlGuru

Formule PID, source ControlGuru

Avec:

  • CO = Sortie du contrôleur (Controller Output)
  • CObias = biais du contrôleur, ou valeur nulle.
  • e(T) = Erreur actuelle, définie par SP – PV
  • SP = consigne (Set Point)
  • PV = valeur actuelle du process (Process Value)
  • Kc = Gain du controlleur, un paramètre de réglage
  • Ti = reset time, un paramètre de réglage
  • Td = derivative time, un paramètre de réglage

Calcul du gain et du reset time

Il faut tout d’abord se demander si notre process accepte un dépassement de consigne ou pas, ainsi que la vitesse à laquelle ce process doit réagir.

  • Si on accepte un dépassement, on peut choisir un réglage agressif, et donc un faible temps de réponse (petit Tc). Dans ce cas, Tc est le plus grand de 0.1Tp ou 0.8θp
  • Modéré, le contrôleur produira peu ou pas de dépassement. Dans ce cas, Tc est le plus grand de 1.Tp ou 8.θP
  • Conservateur, le contrôleur ne produira pas de dépassement, mais ira doucement, Tc sera le plus grand de 10.Tp ou 80.θP

Une fois décidé du comportement de notre contrôleur, et calculé Tc, on peut calculer le gain du contrôleur et le reset time grâce à la formule suivante :

source : controlguru

source : controlguru

Pour mon application, je vais choisir un comportement modéré. Tc sera donc la plus grande valeur de 1.Tp ou 8.θP (1×172 ou 8*3=24). On retiendra donc Tc = 172.

J’obtiens donc Kc = (1/3.98)*(172/(3+172)) = 0.25%/°C et Ti = 172

Je peux maintenant calculer mes deux principaux paramètres P et I :
P = Kc = 0.25
I=Kc/Ti. = 0.25/172 = 0.00145
Reste à déterminer D. la formule permettant de déterminer D est Kc.Td, avec Td = (Tp.θP)/(2Tp+θP) = (172*3)/(344+3) = 1.487. D vaut donc 0.25*1.487=0.372
D = 0.372

Mise en application

Reste maintenant à confronter les calculs à la réalité. Voici le code Arduino que j’ai utilisé pour effectuer mes tests :

#include <OneWire.h>
#include <DallasTemperature.h>

// Data wire is plugged into port 2 on the Arduino
#define ONE_WIRE_BUS 2
// Setup a oneWire instance to communicate with any OneWire devices (not just Maxim/Dallas temperature ICs)
OneWire oneWire(ONE_WIRE_BUS);

// Pass our oneWire reference to Dallas Temperature. 
DallasTemperature sensors(&oneWire);
DeviceAddress insideThermometer;

unsigned long lastTime;
double Input, Output, Setpoint;
double ITerm, lastInput;
double kp, ki, kd;
int sampleTime = 1000; //1 sec
double outMin, outMax;
String cmd = String("");
double tfirst = 0;

float printTemperature(DeviceAddress deviceAddress)
{
  sensors.getAddress(insideThermometer, 0);
  float tempC = sensors.getTempC(deviceAddress);
  Serial.print(tempC);
  Serial.print(" ");
  Serial.print(Output);
  Serial.print(" ");
  Serial.println(Setpoint);
  return(tempC);
}

void compute()
{
  unsigned long now = millis();
  int timeChange = (now-lastTime);
  if(timeChange>=sampleTime)
    {
      //get the new input value
      Input = getInput();
      //compute all working error variables
      double error = Setpoint - Input;
      ITerm+=(ki * error);
      if(ITerm>outMax) ITerm = outMax;
      else if(ITerm < outMin) ITerm = outMin;
      double dInput = (Input - lastInput);
      
      //compute PID output
      Output = kp * error + ITerm - kd * dInput;
      if(Output > outMax) Output = outMax;
      else if(Output < outMin) Output = outMin;
      
      //remember some variables for next round
      lastInput = Input;
      lastTime = now;
    }
}

void setTunings(double Kp, double Ki, double Kd)
{
  double sampleTimeInSec = ((double)sampleTime)/1000;
  kp = Kp;
  ki = Ki * sampleTimeInSec;
  kd = Kd / sampleTimeInSec;
}

void setSampleTime(int NewSampleTime)
{
  if (NewSampleTime>0)
  {
    double ratio = (double)NewSampleTime / (double)sampleTime;
    
    ki *= ratio;
    kd /= ratio;
    sampleTime = (unsigned long)NewSampleTime;
  }
}

void setOutputLimits(double Min, double Max)
{
  if(Min > Max) return;
  outMin = Min;
  outMax = Max;
  
  if(Output > outMax) Output = outMax;
  else if (Output < outMin) Output = outMin;
  
  if(ITerm>outMax) ITerm = outMax;
  else if(ITerm<outMin) ITerm = outMin;
}

double getInput(void)
{
  sensors.setResolution(insideThermometer, 9);
  sensors.requestTemperatures(); // Send the command to get temperatures
  return printTemperature(insideThermometer);
}

void setup()
{
  Serial.begin(9600);
  pinMode(3, OUTPUT);
  Setpoint = 0; //on fixe 0 de consigne
  double P, I, D;
  P = 0.25;
  I = 0.00145;
  D = 0.372;
  int STime = 1000;
  setTunings(P,I,D);
  setSampleTime(STime);
  setOutputLimits(0, 255);
}

void loop(){
compute();
analogWrite(3,Output);
if (Serial.available() > 0) {
      char SerialInByte;
      SerialInByte = Serial.read();
      
      if(SerialInByte==13){
        Setpoint = cmd.toInt();
        cmd = String("");
      }
      else
      {
        cmd += String(SerialInByte);
      }
  }
}

et voici le résultat :

Test des paramètres PID calculés

Test des paramètres PID calculés

Conclusion :

J’ai utilisé des paramètres “conservateurs” afin d’éviter de dépasser la consigne. On peut voir qu’effectivement la consigne n’est jamais dépassée, au prix d’une montée en charge un peu lente. En partant sur cette bonne base, il est maintenant facile d’affiner si l’on souhaite quelque chose d’un peu plus réactif, il reste de la marge de manœuvre.

Sources :

La régulation étant un vaste domaine, j’ai trouvé beaucoup d’informations pertinentes sur les deux sites suivants. Si vous n’êtes pas anglophobe, et désirez approfondir le sujet, je vous les recommandes chaudement !
ControlGuru
brettbeauregard.com, auteur d’osPID