Un lecteur du site m'avait demandé de réaliser un chenillard de 10 LED avec le moins de composants possible. C'est je pense, un très bon exercice pour apprendre à programmer un microcontrôleur. Ses principaux avantages sont la simplicité et surtout un résultat visuel immédiat des effets de son programme.
Exercice:
Réaliser un chenillard de 10 LED commandé par deux bouton- poussoirs (BP_NORM et BP_INV). L'un des BP fait défiler les LED dans un sens et l'autre dans l'autre sens. A la mise sous tension la 1 ère LED (D1) est allumée. La commande du BP_NORM allume alternativement chacune des 9 autres LED puis à nouveau la 1 ère et s'arrête sur la LED D2. Une nouvelle action de BP_NORM réalise le même cycle et se termine sur D3 et ainsi de suite. Ce qui revient à réaliser les cycles suivants (les chiffres en gras indiquent sur quelle LED s'arrête le cycle):
1-2-3-4-5-6-7-8-9-10 - - action sur BP_NORM = allume 2 - 3 - 4 -5 -6 -7 - 8 - 9 - 10 - 1 puis s'arrête sur 2
1-2-3-4-5-6-7-8-9-10 -
1-2-3-4-5-6-7-8-9-10 -
1-2-3-4-5-6-7-8-9-10 -
1-2-3-4-5-6-7-8-9-10 -
1-2-3-4-5-6-7-8-9-10 -
1-2-3-4-5-6-7-8-9-10 -
1-2-3-4-5-6-7-8-9-10 -
1-2-3-4-5-6-7-8-9-10 -
1-2-3-4-5-6-7-8-9-10 -
1-2-3-4-5-6-7-8-9-10 - 11 eme action sur BP_NORM et retour état initial.
Une action sur le BP inverse (BP_INV) provoque un cycle inverse - Etat initial LED D1 allumée puis 10-9-8-7-6-5-4-3-2-1-10 et enfin 9 allumée et ainsi de suite.
Le montage sera alimenté en 5 Volts.
Nous procéderons par étape pour en final réaliser le montage complet et répondre au cahier des charges.
Etape 1:
Réaliser un simple chenillard à LED sans BP. Les LED s'allument successivement dès la mise sous tension suivant l'ordre : 1-2-3-4-5-6-7-8-9-10-1-2-3-4-5-6-7-8-9-10-1-2-... et ainsi de suite.
Etape 2:
Réaliser un simple chenillard à LED avec un BP. A la mise sous tension les LED sont éteintes. L'action sur le BP (appuyé puis relaché) provoque le début de défilement des LED en suivant l'ordre de l'étape 1.
Etape 3:
Réaliser un simple chenillard à LED avec un BP. A la mise sous tension les LED sont éteintes. L'action sur le BP (appuyé puis relaché) provoque le début de défilement des LED en suivant l'ordre de l'étape 1. Donc comme pour l'étape 2. Une autre action sur le BP arrête le chenillard et éteint toutes les LED. Une action sur le BP démarre à nouveau le chenillard et ainsi de suite.
Etape 4:
Réaliser le chenillard fonctionnant suivant le cahier des charge avec un seul BP_NORM. Vous procédez comme pour l'étape 3 mais le cycle des LED est cela du cahier des charges. Soit 1-2-3-4-5-6-7-8-9-10
1-2-3-4-5-6-7-8-9-10 (voir début article pour la suite)
Chaque commande de BP_NORM allume successivement les LED dans l'ordre. Au début LED1 est allumée à la mise sous tension, après commande BP_NORM => défilement 2-3-4-5-6-7-8-9-10-1-2 arrêt sur la LED2 allumée et ainsi de suite.
Etape 5:
Montage final avec 2 BP - BP_NORM => défilement ordre normal - BP_INV => défilement ordre inverse.
![]()
Nous n'utiliserons pas la solution portes logiques. Non pas que ce ne soit pas réalisable, mais le nombre de composants serait trop important sur votre plaque d'essais. C'est donc l'occasion d'utiliser un microcontrôleur et pourquoi pas un PIC (C'est réalisable avec d'autre types bien sur !)
Combien faut-il de sorties ? 10 LED = 10 sorties.
Combien d'entrées ? A priori 2 BP = 2 entrées.
Soit 12 lignes de ports d'entrée/sortie. Le PIC16F84 dispose de 13 lignes et convient donc. De plus, il dispose d'une mémoire de programme de type FLASH donc efface, ce qui est bien pratique quand on débute. Il dispose de mémoire de donnée EEPROM et RAM.
Les PIC16F84 sont pour les plus courants, cadencés par un quartz à 4 MHz (ou moins). Cette fréquence de quartz est une valeur courante et permettra au PIC de fonctionner à une vitesse qui lui permet largement de simuler un chenillard (c'est très rapide pour ce type de réalisation).
Il faudra ajouter un deuxième BP (BP_INV) et donc, le PIC devra déterminer quel BP est actionné. Une ligne de port suffit pour l'aider à déterminer celui qui est commandé. Pour lui signaler que l'un des deux est actionné, à priori un composant supplémentaire sera nécessaire. La première idée qui vient à l'esprit pour signaler au PIC que le BP_NORM OU le BP_INV est actionné, est d'ajouter une porte OU en s'arrangeant pour ne pas ajouter d'entrée supplémentaire au PIC.
Le courant dans les LED doit être limité à moins de 20mA. 20 mA c'est énorme pour une LED aujourd'hui. Aussi pour limiter la consommation nous fixerons ce courant à 5mA. R= U/I donc 5V/5mA = 1K.
Combien de résistances ? Une par LED ? Non puisqu'il n'y aura qu'une seule LED à la fois d'allumée. Une seule résistance donc.
A la masse ou au +5V ? En lisant le datasheet du PIC vous vous rendrez compte que le courant que peut fournir le PIC est plus important quand il est écoulé vers la masse - En règle générale, il vaut mieux commander les LED avec un 0V mais le lecteur souhaitait commander les LED avec un niveau haut (+5V) - Ce n'est pas un problème car ce courant (5mA) reste dans le domaine des courants acceptables pour le PIC. Nous voila prêt à réaliser le schéma.
![]()
Télécharger le montage (chenillard.zip - 12Ko)
Le PIC est branché le plus simplement possible. Le quartz de 4 MHz est connecté entre 15 et 16 ainsi que les 2 condensateurs céramiques de 27pF. La ligne MCLR\ (1 - RESET du PIC) est reliée au +5V à travers une résistance R1 de 10K. Les LED sont reliées chacune à une ligne des ports de sortie du PIC tandis que la résistance R3 de 1K limite le courant qui les traverse - Mais attention au nombre de LED allumées en même temps car il ne faut dépasser le courant maximum que peut accepter le PIC. Si vous craignez de vous tromper en programmant, mettez une résistance de 1K en série sur chacune des 10 LED et supprimez R3. Le microcontrôleur est alimenté en 5V (régulateur positif 5V ou alimentation stabilisée non représenté ) par ses pattes 14 (+5V) et 5 (Masse) - Les lignes RB1-7 du port B et RA0-2 du port A commandent les LED (lignes programmées en sortie). Les lignes RB0 et RA4 sont des entrées utilisées respectivement pour détecter l'action sur un BP et pour déterminer quel BP est actionné.
La détection d'une action sur BP_NORM ou BP_INV est réalisée par la porte OU (IC1). Rappel sur l'état logique de cette porte:
ENTREE 4 - BP_NORM ENTREE 5 - BP_INV SORTIE 6 0 0 0 0 1 1 1 1 1 1 0 1S'il est possible d'appuyer en même temps sur les deux boutons, le programme prendra toujours la même décision, c'est BP_INV qui gagne puisque cette ligne est connectée au PIC. Ainsi si cette ligne = 1 logique, BP_INV n'est pas à la masse donc pas commandé. A l'inverse, si cette ligne = 0 logique, BP_INV est actionné. Les résistances de 47K - R2 et R4 - forcent au niveau haut logique (+5V) les entrées de IC1 et l'entrée RA4 du PIC. Si BP_INV est actionné, l'entrée RA4 du PIC passe au niveau 0 logique (OV), tandis que la sortie 6 de IC1 génère un front descendant (1 > 0) sur l'entrée RB0 du PIC qui est une entrée d'interruption.
II.1 Réalisation:
Le montage sera câblé sur une plaque d'essais ou soudé (moins pratique) sur une plaque epoxy à trous métallisés.
Etape 1:
Câbler les régulateur +5V ou alimentation stabilisée sans mettre sous tension. Placer sur la plaque, le PIC16F84, Q1, C5, C6, D1, D2, D3, D4, D5, D6, D7, D8, D9, D10, R1 , R3.
Etape 2-3-4:
Ajouter R2, R4, IC1 (74HC08), BP_NORM. Inutile pour le moment de câbler la connexion R4-RA4.
Etape 5:
Câbler la connexion R4-RA4 et ajouter BP_INV.
Il est temps maintenant de lire le datasheet (http://www.microchip.com) du PIC16F84 pour en comprendre le fonctionnement . Vous pouvez aussi télécharger la version allégée du datasheet du PIC16F84 et lire particulièrement les paragraphes soulignés en jaune (datasheet_pic16F84_simplifié.zip 238Ko).
![]()
Télécharger les programmes (programme.zip - sources + hex)
Il y a autant de manières d'écrire un programme qu'il y a de programmeurs pour les écrire. Donc, votre programme en C sera aussi valable que le mien ! Vous n'êtes pas obligé de vous inspirer du mien pour faire le votre. Dites vous qu'il sera de toute manière meilleur que le mien puisque vous le comprendrez mieux et qu'ainsi vous pourrez mieux le faire évoluer.
Nous détaillerons tout de même les quelques lignes de codes de langage C qui suivent (compilé avec le compilateur C pour PIC de ht-tech version - démo téléchargeable (PICC) ou version freeware pour PIC16F84 sur http://www.htsoft.com ).
Inutile d'être un virtuose de ce langage pour commencer à programmer votre PIC, mais si vous ne connaissez rien de ce langage, il est essentiel de l'étudier un minimum à partir de documentation ou des nombreux sites web consacrés au langage C. Lisez les parties qui traitent des éléments de base: Format d'un programme (voir exemple) - les constantes, les variables et leur déclaration - Les tableaux - Les opérateurs arihtmétiques (+ - * / % - ++ ) - Les opérateurs logiques (! & ^) - quelques instructions d'affectation (=), de choix (if else), de boucle (while - do while - for) - les autres opérateurs ( ! - ++ - -- - >> - << - == - != - && - || - += - -= - /= ...) Cela devrait suffire pour commencer cet exercice. Le reste sera vu plus tard.
Vous connaissez maintenant la fonction de chacun des registres en rouge dans le programme ?. Non et bien ...
Petit rappel avec le datasheet simplifié:
LES PORTA et PORTB: (pages 10 et 12)
TRISA et TRISB sont les registres de direction des PORTA et PORTB. Un '0' donne une sortie et un '1' donne une entrée. INIT_PORTB et INIT_PORTA vont donc configurer les registres de direction TRISA et TRISB des PORTA et B.
LES INTERRUPTIONS: (page 9 et 21) - si nécessaire explication du rôle de l'interruption plus loin.
GIE=1 : bit 7 du registre de contrôle des interruptions, doit être positionné à '1' pour valide toutes les interruptions (en + des bits qui suivent...)
TIMER0 interruption: (pages 15 et 8 - figure 6.1)
T0IE :Bit 5 du registre INTCON, positionné à '1' l'interruption Timer0 est validée. Le bit (ou drapeau ou flag) T0IF sera positionné à '1' quand le registre compteur TMR0 passera de FFh à 00h - OPTION en C (ou OPTION REGISTER) est le registre qui permet de sélectionner l'origine de l'horloge qui incrémente le registre compteur TMR0 ( par le bit 5 T0CS), d'inclure ou non dans le compteur un diviseur de fréquence (prescaler) par le bit 3 PSA, choix du Timer 0 rate ou valeur du diviseur de fréquence fixée par les bit PS0-PS1 et PS2, et enfin le bit 6 - INTEDG vous permet de choisir si un front montant ou descendant (1>0) sur la ligne RB0 déclenchera une interruption.
RB0 interruption : (page 8 et 9)
INTE : Bit 4 du registre de contrôle des interruptions INTCON, positionné à '1' l'interruption RB0 est validée.
INTF: Bit 1du registre INTCON, ce bit (ou drapeau ou flag) est positionné à '1' quand le un front de tension montant (0>1) ou descendant (1>0) survient sur l'entrée RB0. C'est ce flag qui est testé dans la routine d'interruption du PIC pour déterminer que l'interruption (autorisée par INTE et GIE) est déclenchée par RB0. Remarque, même si vous ne validez pas l'interruption RB0 par INTE, vous pouvez par scrutation de INTF, tester si RB0 à recu un front de tension.Une routine d'interruption est un sous-programme qui va être exécuté et donc interrompre le programme en cours d'exécution lorsqu'une condition est présente sur l'entrée RB0 ou le TIMER 0 dans le cas qui nous intéresse. La règle veut que cette routine (void interrupt traite_int(void)) soit la plus courte possible (en terme de temps) car une fois exécutée le microcontrôleur va revenir au programme initialement exécuté après avoir récupéré le contexte (les registres sauvegardés). Vous imaginez donc aisément que ce programme d'interruption peut perturber le programme en cours d'exécution.
Etape 1: Vous utilisez TRISA et TRISB pour fixer la fonction des lignes d'entrée/sortie. Les LED sont commandées par un '1' sur les lignes de sortie correspondantes. solution possible: const unsigned int cycle_B[10] = {2,4,8,16,32,64,128,0,0,0};
const unsigned int cycle_A[10]= {0,0,0,0,0,0,0,1,2,4}; ces tableaux contiennent les 10 valeurs à mettre sur les PORTB et PORTA pour chacun des cycles. Le premier indice (le premier cycle) d'un tableau est 0. Ainsi cycle_B[0] et cycle_1[0] pointe respectivement sur 2 et 0 ce qui allume la LED D1 (RB1), éteint LED D2-D3-D4-D5-D6-D7 et éteint les LED -D8-D9-D10 quand on attribue ces valeurs à PORTB et PORTA -Il va falloir mettre une temporisation qui détermine la durée d'allumage (et d'extinction) des LED. Pour le moment vous pouvez ne pas utiliser le TIMER 0 qui sait, avec une grande précision, compter un temps. Ecrivez un petit programme de temporisation constitué de boucles d'attente d'une durée que vous fixerez. Par exemple:
/*
* Delay.c
* Delay.h
* delais de 250mS et X fois 250mS
* Attention compiler en "full optimization" pour obtenir l'excécution d'une boucle en 3 cycles d'horloge
*/#define XTAL_FREQ 4 /* frequence en MHz (4MHz)*/
void Delay250Us(void)
{
unsigned char _dcnt;
_dcnt=250/(12/XTAL_FREQ);
while(--_dcnt) continue;
}void DelayMs(unsigned int cnt)
{
unsigned char i;
do {
i = 4;
do {
Delay250Us();
} while(--i);
} while(--cnt);
}Le programme téléchargeable est sans doute plus lisible en raison de l'indentation des lignes entre les accolades.
Ajouter ces fonctions à votre programme. Si vous appelez la fonction DelayMs(4), vous créer une temporisation de 1ms, DelayMs(2000) donne 0,5s, voila pour le principe. Explication: en 'full optimization' (option du compilateur C), il faut 3 cycles d'horloges pour exécuter une fois la boucle while(--_dcnt) continue; un cycle d'horloge avec un quartz à 4 MHz dure 1/(4000000/4) = 1µs ce qui donne pour la fonction Delay250Us: 250/(12/4) x 3 x 1µs = 250 µs - donc 2000 x 250µs = 0.5 s. Pendant cette tempo le µC ne fait rien d'autre que d'attendre.
Etape 2 -3 et 4:
Utiliser l'interruption RB0 pour détecter l'action sur le BP_NORM. Pour cela il faut valider au bon moment INTE et GIE et écrire une fonction dont vous faite précéder le nom par interrupt. Cette fonction, très courte, test le bit INTF et met à '1' un bit indicateur 'bp_on' qui signale au programme principal 'main' que l'utilisateur actionne le bouton poussoir. C'est le programme principal qui gère les interruptions, remet à zéro INTF et le bit indicateur 'bp_on'. Utilisez encore votre boucle de temporisation DelayMs (x) pour créer le temps d'attente entre chaque cycle.
Pour l'étape 3, il faut que la boucle qui fait fonctionner les LED en chenillard test à chaque cycle que BP_NORM est actionné. Pour cela, valider l'interruption RB0 (INTE et GIE). La routine d'interruption positionne le flag 'bp_on'. La boucle test 'bp_on' ( while (bp_on == 0); ) et continue à tourner (à allumer les LED) si 'bp_on' = 0 (pas d'action sur BP_ON). Il faudra signaler au programme que le chenillard est allumé ou éteint, solution parmi mille, utilisez le flag 'bp_on' quand il est à '1' le chenillard est allumé et quand il est à '0' le chenillard est éteint. C'est la routine d'interruption qui se charge d'inverser ce flag à chaque action sur le BP. la fonction XOR (^) sait très bien faire cela puisque 1 XOR 1 = 0 et 0 XOR 1 = 1
Etape 5:
Les choses sérieuses commencent. Supprimer vos routines de temporisation et ajouter à votre routine d'interruption l'interruption TIMER 0 après avoir validé TOIE = 1 quand vous avez besoin de compter un temps. Pour ralentir un peu la tempo sélectionner le diviseur de fréquence et divisez par 256 la fréquence du quartz/4 avec OPTION=0b00000111; (PS0-PS1-PS2 =111 => /256) - Dans votre routine d'interruption incrémentez une variable (nb) dont la valeur fixera le délais d'attente 1µs x 256 x256 x nb = temps d'attente .
Essayez l'étape 4
Ajoutez dans 'main' le test de la ligne RA4 pour déterminer si c'est le BP_INV qui est actionné. Si RA4 == 0 alors inversez le cycle (les indices des tableaux) de défilement des LED.
La fonction 'main' est le programme principal en langage C (obligatoire). Void interrupt traite_int(void) est la routine d'interruption qui traite les interruptions RB0 et TMR0. Void init_pic(void) initialise les registres du PIC PORT et interruptions. La variable 'init_pos' donne la position de la LED allumée - et la variable 'n' le nombre de LED à allumer à chaque cycle (11 LED)
Votre chenillard fonctionne, bravo, vous êtes prêt pour réaliser des montages plus complexes. Essayer d'utiliser un µC équipé d'un UART (liaison série) - famille 8051, PIC16F873 ... et connectez le à travers un MAX232 à la liaison RS232 de votre PC - Communiquer avec votre montage... Si l'étape 1 fonctionne, félicitations également, l'objectif est atteint.
Vous avez raison, beaucoup de blablabla, pour des informations que vous apprendrez facilement au fur et à mesure de l'écriture de votre premier programme.
Exemple de solution programme:
/*********************************************************************
* CHENILLARD V2.00 23/11/01 CH_84_V2
* Commande de LED suivant un cycle determine et commande de BP
* Claude DRESCHEL
*
********************************************************************/#include <pic1684.h>
#define INIT_PORTA TRISA = 0b10000; //PortA = RA4=In
#define INIT_PORTB TRISB = 0b00000001 //PortB RB0=In
static bit BP @ ((unsigned)&PORTB*8+0); // BP_NORM et BP_INV SUR RB0
//**************************************************************
// VARIABLES ET CONSTANTES
//**************************************************************
static bit bp_on,temps,bp_inv;
unsigned char nb;
const unsigned int cycle_B[11]={2,4,8,16,32,64,128,0,0,0};
const unsigned int cycle_A[11]={0,0,0,0,0,0,0,1,2,4};
//***************************************************************
void init_pic(void);
void interrupt traite_int(void);//**************************************************************
// PROGRAMME PRINCIPAL 'MAIN'
//**************************************************************
void main(void)
{
signed char init_pos;
unsigned char n;
init_pos=0; //position 0 dans les tableaux
bp_on=0;
bp_inv=0;
init_pic(); //initialisation du PIC
PORTB=cycle_B[init_pos]; //LED1=ON sur portB
PORTA=cycle_A[init_pos];do
{
while(!bp_on); //attend BP
INTE=0; //plus d'interruption sur RB0
TMR0=0x0; //compteur=0
nb=0; //durée nb=0
n=0; // 11 cycle (10 LED+1)
if(RA4==0) bp_inv=1; //BP_INV appuye
while(!BP); //lache le BP ?
temps=0;
T0IE=1; //TMR0 actif :debut tempo anti-rebond BP
do
{
if(bp_inv==0) //sens normal
{
init_pos++; //commence LED suivante
if(init_pos > 9) init_pos=0;
}else //sens inverse
{
init_pos--;
if(init_pos < 0) init_pos=9;
}
PORTB=cycle_B[init_pos]; //config LEDs portB
PORTA=cycle_A[init_pos]; //config LEDs portA
while(!temps); // attend fin tempo
temps=0;
n++;
}while(n!=11); //tant que 10+1 LED allumees
T0IE=0; //invalide interruption TMR0
bp_inv=0;
bp_on=0;
INTE=1; //valide interruption BP sur RB0
}while(!0); //on recommence
}//**************************************************************
// ROUTINE D'INTERRUPTION
//**************************************************************
void interrupt traite_int(void)
{
if(INTF) // interruption RB0
{
bp_on=1; // signale BP actionne
INTF=0; //efface flag INTF
}
if(T0IF) // interruption TMR0
{
if(++nb==4) // nb determine la duree LED allumee
{
temps=1; // fin tempo
nb=0;
}
T0IF=0; //efface flag T0IF
}
}
//**************************************************************
// INITIALISATION DU PIC
//**************************************************************
void init_pic(void)
{
INIT_PORTA; //config PORTA
INIT_PORTB; //config PORTB
OPTION=0b00000111; //config prediviseur et TMR0
T0IF=0; //Flag TIMER0 interrupt
TMR0=0x0; //registre TIMER0=0
T0IE=0; //ne valide pas les interruptions TMR0
INTE=1; // valide interruption BP sur RB0
GIE=1; //valide toutes les interruptions
}
//**************************************************************
// FIN
//**************************************************************