Posts Tagged ‘Programmation’

DIY – Thermomètre à tube Nixie

Vendredi, mai 17th, 2013

Ce projet, qui m’aura occupé quelques temps, est parti de trois points :

- Je n’avais pas de thermomètre chez moi, et ma femme et moi n’avons pas tout à fait la même sensibilité à la température, cela permet de donner une valeur objective et d’ajuster en conséquence (soit on met le chauffage, soit l’autre enfile un pull ;))
- J’avais besoin de tester un circuit de commutation pour tubes Nixie (spoiler : dans le but de réaliser une horloge), mais sur un nombre limité de tube, car en cas d’erreur, c’est très pénible de dessouder le tube et de le ressouder
- J’avais envie de réaliser un montage CMS le plus compact possible, et de tester au passage la mise en oeuvre de CMS taille 0402.

Si si, il y a un composant sur C6. La LED est une 5mm, placée là pour donner l'echelle.

Si si, il y a un composant sur C6. La LED est une 5mm, placée là pour donner l’échelle.

Le montage se divise donc en 4 parties : la mesure de la température, effectuée par un vénérable LM35 (mais le montage permet aussi l’utilisation d’un LM73 plus précis) ; l’élévation de tension pour alimenter les tubes, le contrôle des tubes, et le pilotage de tout ça, réalisé par un Atmega328, version cms évidemment.

Le schéma d'ensemble

Le schéma d’ensemble

La partie mesure de température ne nécessite pas d’explications particulières. A noter simplement que le LM73 fonctionne en I2C, et que dans ce cas, il faut impérativement mettre les résistances de pullup R6 et R7. Dans le cas du lm35, elles ne sont plus nécessaires car ce dernier fonctionne en analogique, la sortie de celui-ci étant à connecter à la broche 4 de l’emplacement du lm73 (A5/SCL sur l’atmega).

La partie élévateur de tension est désormais classique sur mon site, il s’agit de la même que pour mes compteurs geiger, à savoir NE555 + Mosfet + bobine. Un petit condensateur 400v sert à lisser la tension obtenue.

La mise en oeuvre de l’AtMega328 n’a rien de spécifique. Il faut en revanche noter deux connecteurs, un connecteur ISP, et un connecteur permettant de brancher un adaptateur série. Le premier devant servir à charger le bootloader Arduino sur l’Atmega, le second à charger le programme/débugger comme s’il s’agissait d’un simple Arduino. Pour une raison que j’ignore, bien que le bootloader soit correctement chargé, il n’a fonctionné que sur une seule de trois cartes que j’ai assemblé. Après tests, la communication série s’effectue correctement et dans les deux sens, mais impossible de flasher l’atmega par ce biais (si quelqu’un a une idée…). Du coups, la programmation se fait via ISP, et le debug par la connexion série.

La partie la plus intéressante de ce montage est la partie pilotage des tubes nixie. Un des objectifs était de réaliser le montage le plus compact possible, exit donc les drivers type 7441, tout sera fait ici à base de transistors.
Afin de ne pas trop consommer, l’affichage des 2 digits ne se fera pas simultanément, mais l’un après l’autre, de manière très rapide, la persistance rétinienne se chargeant de donner l’impression d’un affichage fixe.
Coté cathode, les transistors sont dans une configuration peu courante : la base est commune à tous les transistors, en permanence à +5v, ce qui permet de n’avoir qu’une seule résistance (mais qui impose de n’utiliser qu’un seul digit à la fois). La commutation se fait en ramenant l’émetteur du transistor voulu à  0v. Dans cette configuration, il faut autant d’entrées/sorties sur le microcontrolleur que de digits, mais en l’occurrence, l’Atmega nous en propose nettement plus que nécessaire dans notre cas.

Le driver coté anode

Le driver coté anode

Coté anode, il aurais été possible également de mettre un simple transistor NPN avec une résistance pour faire le travail. Cependant, la consommation « à vide » aurais été supérieure à la consommation lors de l’affichage sur un tube, ce qui n’est clairement pas le but recherché.
Le montage ci-dessus « coupe » le courant, en limitant les pertes à des valeurs infimes. La résistance R12 et le transistor NPN forment un driver de courant constant, réglé de manière à laisser passer juste le courant nécessaire au déblocage du transistor PNP.

Thermomètre Nixie

Le thermomètre Nixie assemblé

Le circuit complet tiens sur un PCB de 5x5cm double face. J’aurais probablement pu faire encore plus petit, mais ça me semblais déjà un bon début !

Le circuit vu du dessus

Le circuit vu du dessus

Concernant l’assemblage du PCB, rien de spécial à mentionner, celui-ci étant étonnamment plus facile à assembler que ce qu’il pourrais sembler au premier abord, et ce, malgré le fait que j’ai soudé des résistances 0805 sur des emplacements 0603 (donc un peu plus petits que les résistances). Ayant fait plusieurs essais, j’ai testé différentes techniques de soudure, je vous ferais un petit topo là-dessus dans un prochain article. Globalement, si on omet les 2 composants 0402 (taille qui n’était pas impérative du tout, mais pour faire des tests), ce n’est pas vraiment plus compliqué qu’avec du traversant, au contraire même.  Le circuit intégré demande un petit coups de main, mais ça se fais très bien, et très rapidement. Les 0402, pour le coups, sont assez délicat à placer, leur petite taille faisant qu’ils se collent à la pane du fer à souder par capillarité, et leur taille nécessite de bons yeux en plus d’une bonne loupe (idéalement, une bino)

Enfin, pour finir, le code source, qui n’a rien de très spécifique, il se contente de récupérer la valeur du lm35, et décomposer le résultat obtenu en deux digits, les unité et les dizaines.

thermometre

Programmation Avr, dernière partie

Dimanche, mars 17th, 2013

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)
Interrupt vectors

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 :)

Programmation Avr, seconde partie

Lundi, janvier 21st, 2013

Bon, j’espère que mon précédent billet vous aura permis de comprendre un peu comment fonctionnait la programmation avr.Il reste néanmoins encore de nombreux points qui pourraient êtres abordés, je voulais vous en présenter encore au moins 2 : les timers et les interruptions. Une fois ces deux points maîtrisés, vous serez capable déjà pas mal de petites choses avec vos microcontrolleurs.

(suite…)

Programmation AVR, premiers pas, partie 1

Jeudi, janvier 3rd, 2013

Je programme occasionnellement sur avr (comprendre autre qu’arduino quoi), mais comme c’est très espacé dans le temps, à chaque fois j’ai l’impression de reprendre depuis le début. Du coups, je me suis dit que ça pourrais être bien de faire un petit papier là-dessus, en expliquant un peu les points qui m’ont posé problème à un moment ou un autre. Je ne suis pas un très bon programmeur, donc il y aura peut être des erreurs ou imprécisions, n’hésitez pas à m’en faire part dans les commentaires si c’était le cas :)
(suite…)

DIY – Shield multiplexeur pour Arduino

Mercredi, décembre 12th, 2012

Et oui, les entrées/sorties, on n’en a jamais assez. En l’occurrence, pour ce projet, j’avais besoin d’avoir une douzaine de capteurs analogiques (des capteurs de force résistifs), et de pouvoir logger les valeurs sur une carte microSD.

MultiplexShield
Je me suis dit que tant qu’à faire, j’allais essayer de faire ça de manière à être le plus polyvalent possible, histoire que ça puisse servir dans d’autres projets.C’est à partir de ce cahier des charges que je suis parvenu à la réalisation de ce shield, qui offre la possibilité de lire (ou d’écrire) sur 32 entrées analogiques, plus 5 E/S numériques (plus les 4 entrées analogiques encore dispo sur l’arduino)

MultiplexShield, le circuit

MultiplexShield, le circuit

Comme vous pouvez le voir sur le circuit ci-dessus, 16 broches (8 sur le premier circuit, 8 sur le second) sont disponibles pour lire des valeurs analogiques à partir de capteurs résistifs (les petites résistances CMS servent à faire un pont diviseur de tension). Les 16 autres broches, permettent de lire/écrire des informations numériques (possible aussi avec les précédentes, sous réserve d’enlever les résistances CMS). L’idée de cet agencement est de n’avoir que les deux fils du capteur à brancher sur la carte.
Concernant les cartes microSD, j’ai préféré maximiser mes chances : j’ai prévu un connecteur pour les adaptateurs type Adafruit (qui embarquent le système de régulation de voltage nécessaire), et pour les BreackOutBoard Sparkfun, qui eux ne comprennent que le slot microSD.

Multiplex Shield le schéma. (cliquez pour agrandir)

Multiplex Shield le schéma. (cliquez pour agrandir)

L’accès aux cartes microSD se fait en utilisant les fonctions SPI matérielles de l’Arduino, histoire de maximiser les performances.
Comme le montrent les photos, il y a eu quelques ratés au niveau du schéma initial (le schéma publié est corrigé), certaines broches avaient été laissées flottantes (toujours vérifier 2 fois sont schéma avant de faire faire un circuit !). Bon, c’est pas dramatique, quelques bouts de fils et c’est réparé, mais bon, c’est pas super esthétique.

Les raccommodages...

Les raccommodages…

Au niveau de l’utilisation de ce shield, c’est plutôt facile : chaque circuit est rattaché à une broche analogique. Lorsqu’on lit la valeur d’une de ces deux broches, on lit en fait la valeur de l’entrée sélectionnée du multiplexeur. Et comment on sélectionne l’entrée ? Bah tout simplement en envoyant au multiplexeur le code binaire correspondant à la broche. Je m’explique : pour se piloter, le CD4067 utilise 4 broche (10, 11, 14 et 13, pour respectivement A, B, C et D). Vous voulez lire la broche 7 ? Et bien, mettez A à 1, B à 1, C à 1 et D à 0 (0111 en binaire, ce qui nous donne 7), tout simplement.
Difficile de faire plus simple non ?

Un petit exemple de code : Lecture multiplexée, enregistrement sur SD