Vous êtes sur la page 1sur 34

Programmation des PIC en C

Microcontrôleur facile pour électronicien amateur

PARTIE 2

Noxyben
2008

www.noxyben.fr

-1-
Introduction ................................................................................................................................ 3
Le schéma de base...................................................................................................................... 4
Principe de fonctionnement des ports I/O .................................................................................. 7
Utilisation des ports d’entrée/sortie (I/O)................................................................................... 8
Utilisation d’un PORT en sortie : un chenillard 8 leds .......................................................... 8
Le matériel : ....................................................................................................................... 8
Le programme : ................................................................................................................ 10
Analyse du programme : .................................................................................................. 12
Compilation du programme et injection dans le PIC : ..................................................... 16
Tests ................................................................................................................................. 17
Conclusion :...................................................................................................................... 17
Utilisation d’un PORT en entrée : un chenillard 8 leds contrôlé par 2 boutons poussoirs. . 18
Le matériel : ..................................................................................................................... 18
Le programme : ................................................................................................................ 19
Analyse du programme : .................................................................................................. 22
Compilation du programme et injection dans le PIC : ..................................................... 23
Tests ................................................................................................................................. 24
Conclusion :...................................................................................................................... 24
Le PORTB et les interruptions ............................................................................................. 25
Principe de fonctionnement :............................................................................................ 25
Le matériel : ..................................................................................................................... 26
Le programme : ................................................................................................................ 27
Analyse du programme : .................................................................................................. 29
Tests ................................................................................................................................. 34
Conclusion :...................................................................................................................... 34

-2-
Introduction
Après avoir vu dans la première partie comment est structuré le PIC, on va enchaîner
directement sur des montages concrets : rien de mieux pour apprendre et mémoriser
que de faire soi-même un montage « en vrai ». Je vous invite chaleureusement à
faire un tour chez votre marchand de composants électroniques et à faire chauffer
votre fer à souder. Vous allez avoir besoin d’un PIC16F877, un programmateur, une
platine d’essai du genre « pastilles pré-percées » au pas de 2,54 mm, et quelques
composants qui traînent certainement dans vos fonds de tiroirs : Led, régulateur
7805, condensateurs, support 40 broches…
Pour la partie logicielle il vous faudra télécharger la version de démonstration de
SourceBoost, qui comprend le compilateur BoostC que nous allons employer, et
éventuellement un logiciel de transfert comme Icprog pour piloter votre
programmateur de PIC. Si vous avez acheté un programmateur en kit comme le
Velleman K8076, le logiciel « PICPROG » est compris. Pensez à télécharger les
mises à jour.

-3-
Le schéma de base
IC2
D1
9V =
7805
R1

C3 C4
Led1
1 40
2 39 ICSP
3 38
4 37
5 36
6 35
7 34

PIC 16F877
8 33
C6
-4-

9 32
10 31
C5 11 30
12 29
13 28
14 27
15 26

C2 16 25
C1
17 24
Q1
18 23
19 22
20 21

IC1
Le schéma ci-dessus est la base sur laquelle on va pouvoir construire des
applications utiles. Il ne fait rien d’autre que de permettre au PIC de fonctionner, c’est
en quelque sorte le « minimum vital ». Bien entendu, d’autres variantes sont
possibles, mais plutôt que d’en dresser la liste exhaustive, je vous propose de
réaliser cette version et de remettre à plus tard l’étude des différents circuits
d’oscillateur, de reset…

L’alimentation : Pour alimenter le circuit, une source continue d’environ 9V fera


parfaitement l’affaire (une pile 9V par exemple). La régulation et l’adaptation à un
niveau de 5V requis par le PIC incombent au régulateur 7805.
Remarque : pour que celui-ci fonctionne correctement, la tension d’alimentation doit
être supérieure d’au moins 2 ou 3 volts à la tension régulée, donc 5 + 2 = 7 Volts.
La diode D1 protège le circuit contre les inversions accidentelles de polarité (diode
type 1N4001 ou 1N4007 par exemple).
Le condensateur C3 sert de « réservoir » au régulateur. La règle habituelle est de
prendre 1000µF / Ampère. Dans notre cas, un condensateur électrochimique
de 470 avec une tension d’au moins 16V convient parfaitement.
Le condensateur C4 nous permet de découpler le régulateur du reste du
circuit : il absorbe les variations de tension parasite qui pourraient remonter
vers le régulateur. 10 ou 100 nF devraient convenir.
Le couple R1 – Led1 sert de témoin de mise sous tension, et accessoirement
décharge le condensateur C3 lorsque l’alimentation est coupée.
Avec une tension d’alimentation de 9V, on prendra pour R1 une valeur de
390Ω 1/4W
Les condensateurs C5 et C6 quant à eux sont des condensateurs de
découplage du PIC. À monter au plus près du 16F877, ils empêchent que
d’éventuels parasites collectés par les lignes d’alimentation ne pénètrent dans
le circuit intégré. Quelques pF ou nF du genre condensateur « tantale goutte »
feront l’affaire. Surtout pas de gros condensateur électrochimique de plusieurs
µF qui aurait un effet néfaste sur le temps de montée de l’alimentation et
pourrait ainsi perturber le PIC au moment du démarrage.

L’oscillateur : Il est constitué de Q1, un quartz « parallèle » dont la fréquence


d’oscillation nominale peut aller jusqu’à 20MHz, et des condensateurs C1 et
C2 pour lequel on prendra une valeur de 20pF, conformément aux indications
de la documentation de Microchip.

-5-
Le connecteur ICSP : Bien que n’étant pas indispensable, on va équiper
notre platine de ce connecteur « In Circuit Serial Programming », qui nous
permettra de programmer le PIC sans le retirer de son support. A vrai dire, le
brochage n’est pas vraiment normalisé. Je vous propose donc la version
suivante, que vous pourrez adapter à votre guise. Le seul impératif est que les
bonnes lignes du programmateur aboutissent sur les bonnes pattes du PIC

• patte 1 - 6 : Vpp (tension de programmation)


• patte 2 - 7 : VDD (tension d’alimentation 5V)
• patte 3 - 8 : PGD (Program Data = ligne de transfert des données)
• patte 4 – 9 : Vss (tension de masse = 0V)
• patte 5 - 10 : PGC (Program Clock = ligne d’horloge de programmation)

1 6
2 7
3 8
4 9
5 10

En fonctionnement normal, la pin 1 est pontée sur la pin 6, la pin 2 sur la 7, la


3 sur la 8, la 4 sur la 9 et la 5 sur la 10.
Pour programmer le PIC on déconnecte l’alimentation du circuit, on ôte tous
les ponts du connecteur ICSP et on branche à la place le cordon ICSP du
programmateur, en veillant à ce que les signaux correspondent bien. Si ce
n’est pas le cas, il vous faudra confectionner un cordon ad’ hoc en croisant les
bonnes lignes.

Le circuit de RESET : Rien de plus simple pour l’instant : la ligne de reset


MCLR (la pin 1 du PIC) est simplement reliée à la tension d’alimentation.
Ainsi, pour redémarrer le PIC, on coupe l’alimentation et on réalimente.

-6-
Principe de fonctionnement des ports I/O
Les ports d’entrée / sortie numériques peuvent être considérés comme les
périphériques les plus simples du microcontrôleur. Pour le PIC, on contrôle leur
fonctionnement à l’aide de registres spéciaux (deux registres par port). Par exemple,
pour le port A, on a le registre PORTA et le registre TRISA.
Les registres « TRISx » contrôlent le mode de fonctionnement des entrées / sorties :
selon la valeur de chaque bit, 0 ou 1, la pin correspondante du port fonctionnera soit
en sortie, soit en entrée. Les registres « PORTx » contiennent les données, lues, ou
à écrire sur le port. Certains ports possèdent en plus des fonctionnalités spécifiques :
le PORTB est équipé de résistances de tirage (pull-up) internes qui peuvent être
activées ou non. La pin RB0 du PORTB sert d’entrée d’interruption externe, et les
pins RB4 à RB7 peuvent générer une interruption lorsqu’un changement d’état est
détecté à leurs bornes. Toutes ces particularités sont bien entendu décrites dans les
documentations Microchip spécifiques à chaque composant.

-7-
Utilisation des ports d’entrée/sortie (I/O)

Utilisation d’un PORT en sortie : un chenillard 8 leds

Le matériel :

1 40
2 39
3 38
4 37 VCC
5 36
6 35
R9 R8 R7 R6 R5 R4 R3 R2
7 34
PIC 16F877

8 33
9 32
10 31
11 30
Rd8
12 29
13 28
Rd8
14 27
15 26
Rd6
16 25
17 24
Rd5
18 23
19 22
Rd4
20 21
Rd3
Rd2
Rd1

On va rajouter les quelques composants du schéma ci-dessus à notre montage de


base. Le but est de pouvoir commander 8 leds à travers les 8 lignes du PORTD.
On pourrait raccorder les leds directement aux pins du PIC à travers des résistances
appropriées, (ça marche aussi) mais au cas où l’idée vous viendrait d’allumer les 8
leds en même temps, le courant débité par le PORTD pourrait dépasser la valeur
limite. En effet, un coup d’œil à la rubrique caractéristiques électriques du datasheet
nous informe que le courant maximal pouvant être débité par un PORT est de 200
mA. Avec des résistances légèrement sous-dimensionnées et 8 leds allumées on
pourrait s’en approcher dangereusement. Bon, c’est vrai, j’exagère un peu, même
avec 8 x 20 mA on n’est encore qu’à 160mA de consommation sur le port. Mais
autant adopter de bons réflexes dès le départ. On choisit donc de passer par des
transistors. On pourra prendre des transistors petit signaux standard. Si vous trouvez
dans vos tiroirs des BC537 ou 2N2222 ou quoi que ce soit d’équivalent, ça
conviendra parfaitement.
Pour les résistances R2 à R9, si on considère que le point de fonctionnement des
leds est à environ 1,8V / 10 mA, et que VCC = 5 V : VRx = 5-1,8 = 3,2V ; donc
R = VRx / I = 3,2 / 0.01 = 320.

-8-
La valeur normalisée la plus proche étant 330, on prendra donc pour les résistances
R2 à R9 des 330 Ohms ¼ de Watt.
Pour les résistances Rd1 à Rd8, afin d’assurer une bonne saturation des transistors,
on prendra des résistances de 4,7 kOhms ¼ de Watt.

-9-
Le programme :

On rentre enfin dans le vif du sujet. Afin de piloter notre montage avec un joli effet
« K2000 », je vous propose le programme ci-dessous :

#include <system.h>

//Cible PIC16F877, bits de configuration


#pragma DATA _CONFIG, _PWRTE_OFF & _BODEN_OFF & _WDT_OFF &
_LVP_OFF & _CPD_OFF & _DEBUG_OFF & _HS_OSC & _CP_OFF

//Configuration de la fréquence d'horloge, ici 20Mhz


#pragma CLOCK_FREQ 20000000

void main( void )


{

//Initialisation port A
porta = 0x00;
//Initialisation port B
portb = 0x00;
//Initialisation port C
portc = 0x00;
//Initialisation port D
portd = 0x00;
//Initialisation port E
porte = 0x00;

//Configuration port A
trisa = 0x00;
//Configuration port B
trisb = 0x00;
//Configuration port C
trisc = 0x00;
//Configuration port D
trisd = 0x00;
//Configuration port E
trise = 0x00;

//Configuration A/D pins


adcon1 = 0x06;

//désactivation du mécanisme de gestion des interruptions


clear_bit( intcon, GIE );

- 10 -
//Validation des résistances de pull-ups du port B
clear_bit( option_reg, NOT_RBPU );

//Boucle sans fin


while( 1 )
{
portd = 0b00000001;
delay_ms(100);
portd = 0b00000011;
delay_ms(10);
portd = 0b00000010;
delay_ms(100);
portd = 0b00000110;
delay_ms(10);
portd = 0b00000100;
delay_ms(100);
portd = 0b00001100;
delay_ms(10);
portd = 0b00001000;
delay_ms(100);
portd = 0b00011000;
delay_ms(10);
portd = 0b00010000;
delay_ms(100);
portd = 0b00110000;
delay_ms(10);
portd = 0b00100000;
delay_ms(100);
portd = 0b01100000;
delay_ms(10);
portd = 0b01000000;
delay_ms(100);
portd = 0b11000000;
delay_ms(10);
portd = 0b10000000;
delay_ms(100);
portd = 0b11000000;
delay_ms(10);
portd = 0b01000000;
delay_ms(100);
portd = 0b01100000;
delay_ms(10);
portd = 0b00100000;
delay_ms(100);
portd = 0b00110000;
delay_ms(10);
portd = 0b00010000;
delay_ms(100);
portd = 0b00011000;
delay_ms(10);
portd = 0b00001000;

- 11 -
delay_ms(100);
portd = 0b00001100;
delay_ms(10);
portd = 0b00000100;
delay_ms(100);
portd = 0b00000110;
delay_ms(10);
portd = 0b00000010;
delay_ms(100);
portd = 0b00000011;
delay_ms(10);

}
}

Analyse du programme :

Étudions cela un peu plus en détails :

#include <system.h>

Cette directive indique au précompilateur d’inclure le fichier system.h. Celui-ci


contient à son tour une inclusion pour le fichier spécifique à votre PIC (16F877.h),
dans lequel sont indiquées les associations nom de registre / adresses mémoire.
D’où l’importance de bien indiquer à BoostC la cible (ici 16F877) pour laquelle vous
désirez compiler votre programme.

#pragma DATA _CONFIG, _PWRTE_OFF & _BODEN_OFF & _WDT_OFF &


_LVP_OFF & _CPD_OFF & _DEBUG_OFF & _HS_OSC & _CP_OFF

Une autre directive de précompilation, qui permet de définir les bits de configuration
du PIC. Pour mémoire, ces bits de configuration permettent de configurer un certain
nombre de paramètres de fonctionnement du PIC. Bien heureusement, SourceBoost
reprend à peu de chose près les mêmes désignations que Microchip :
• PWRTE_OFF : désactive le Power-up Timer Enable bit
• BODEN_OFF : désactive le Brown-Out reset Enable bit
• WDT_OFF : désactive le Watch Dog Timer
• LVP_OFF : désactive le Low Voltage Programming
• CPD_OFF : désactive la protection de la mémoire de données EEPROM ?
• DEBUG_OFF : désactive l’In-Circuit Debugger
• HS_OSC : Configure l’oscillateur en mode HS (High Speed)
• CP_OFF : désactive la protection du code
Pour l’instant on désactive tout (OFF), à part bien sûr l’oscillateur qu’on configure en
mode « HS », mode recommandé par Microchip pour l’emploi d’un quartz 20 MHz.
Si vous utilisez un quartz 4 MHz, le mode le plus approprié sera « XT ». La différence
réside entre HS et XT réside dans le gain de l’amplificateur inverseur du circuit
d’oscillateur du PIC. Plus la fréquence de travail est élevée, plus le gain doit être
- 12 -
élevé. Mais dans la pratique, en mode « HS » et avec un quartz à 4 MHz, ça marche
aussi. Pour savoir quel mode choisir, regardez dans la documentation Microchip sur
la gamme des PIC « mid-range », ou mieux sur la documentation du 16F877 dans la
section « oscillator ».

#pragma CLOCK_FREQ 20000000

A nouveau une directive de précompilation. On indique ici quelle est la fréquence du


quartz utilisé. Les temporisations générées par la fonction « delay » utilisée plus loin
dans le programme sont basées sur cette valeur.

void main( void )


{

La fonction « main » est obligatoire en langage C : c’est le « point d’entrée » du


programme. Le «void» placé avant «main» indique que la fonction ne retourne
aucune valeur, et le «void» entre parenthèses indique qu’on ne prend pas de valeur
en argument. Tout le reste de notre programme viendra se placer entre les
accolades ouvrante et fermante.

//Initialisation port A
porta = 0x00;
//Initialisation port B
portb = 0x00;
//Initialisation port C
portc = 0x00;
//Initialisation port D
portd = 0x00;
//Initialisation port E
porte = 0x00;

Les variables porta, portb, portc, portd et porte sont affectées aux registres
correspondant à l’état des Entrées/Sortie des ports. (respectivement le PORTA,
PORTB, PORTC, PORTD, PORTE). Ces variables sont déclarées dans le fichier
d’entête spécifique au PIC 16F877 (fichier avec l’extension « .h » ), lui-même inclus
dans le fichier «system.h». Ainsi, vous pouvez accéder à un registre du PIC
simplement en l’appelant par son nom, sans se soucier de son adresse.
Écrire une donnée dans un de ces registres affectera dès-lors les pins
correspondantes du PORT, si celles-ci sont configurées en sortie. Ici, on initialise tout
à 0.

- 13 -
//Configuration port A
trisa = 0x00;
//Configuration port B
trisb = 0x00;
//Configuration port C
trisc = 0x00;
//Configuration port D
trisd = 0x00;
//Configuration port E
trise = 0x00;

Suivant le même principe que pour les registres porta, portb, portc… les variables
trisa, trisb, trisc, trisd, trise sont affectées aux registres de contrôle de direction des
ports (respectivement le PORTA, PORTB, PORTC, PORTD, PORTE). On affecte au
cinq registres «tris» la valeur hexadécimale (d’où le «x» pour heXa) 00. Un coup
d’œil aux fiches détaillées des registres correspondant nous montre que cette valeur
00 passe toutes les lignes d’Entrés/Sortie des ports en mode « sortie ».
Une remarque : Microchip recommande de d’initialiser en premier lieu les registres
« portx », et de configurer la direction avec les registres « trisx » ensuite. Pour éviter
d’avoir un état incertain sur les pins au moment du démarrage.

//Configuration A/D pins


adcon1 = 0x06;

Certaines pins des ports sont susceptibles d’être utilisée par le convertisseur
Analogique / Numérique. Par défaut, (voir fiche détaillée du registre adcon1), les pins
en question sont configurées en mode analogique. La valeur hexadécimale 0x06 les
passe en mode numérique.

//désactivation du mécanisme de gestion des interruptions


clear_bit( intcon, GIE );

Le registre intcon contrôle le mécanisme d’interruption. «intcon» pour INTerrupt


CONtrol. GIE, Global Interrupt Enable bit, est un des bits du registre intcon, il active
ou désactive de manière globale toutes les interruptions. La fonction «clear_bit()» est
une fonction implémentée par BoostC, permettant de mettre un bit à 0. Son pendant
est la fonction «set_bit()» qui permet, elle de mettre un bit à 1. La syntaxe ci-dessus
signifie donc : « mettre à 0 le bit GIE du registre intcon ».

- 14 -
//Validation des résistances de pull-ups du port B
clear_bit( option_reg, NOT_RBPU );

Le registre option_reg contrôle certaines options du PIC, parmi lesquelles figure


l’activation ou non des résistances de pull-up du PORTB. Le bit correspondant à
cette option est NOT_RBPU. Celui-ci fonctionne en logique négative (d’où le NOT
devant RBPU). La syntaxe ci-dessus signifie donc « mettre à 0 le bit NOT_RBPU du
registre option_reg ». Or, étant donné que le bit NOT_RBPU fonctionne en logique
négative, le mettre à 0 signifie ACTIVER les résistances de pull-up du PORTB ! Pour
l’instant on ne se sert pas de ces résistances, donc vous pouvez, si vous préférez,
mettre ce bit à 1 pour les désactiver.

//Boucle sans fin


while( 1 )
{

Comme l’indique le commentaire, il s’agit là d’une boucle sans fin. «While» est une
instruction de contrôle de boucle du langage C. La traduction française donnerait :
« tant que 1… ». Or, «1» n’a pas de raison de changer d’état, donc une fois lancée,
on ne sort pas de cette boucle. Tout le code entre les accolades ouvrante et
fermante se répètera dès lors indéfiniment.

portd = 0b00000001;

La variable «portd» est affectée au registre correspondant à l’état des Entrées/Sortie


du PORTD. Ici, on écrit la donnée binaire (d’où le b de 0bxxxxxxxx) 0b00000001, ce
qui a pour effet de passer la pin RD0 à l’état 1, alors que les pins RD1 à RD7 restent
à l’état 0.

delay_ms(100);

La fonction «delay_ms()» est une fonction implémentée par BoostC, permettant


d’introduire une temporisation. Ici, delay_ms(100) génère une temporisation de 100
millisecondes. Cette temporisation est calculée en fonction de la valeur de la
fréquence d’horloge qu’on a indiquée plus en amont par la directive PRAGMA
CLOCK_FREQ.

- 15 -
portd = 0b00000011;
delay_ms(10);

On « allume » une deuxième pin du PORTD, et on attend ensuite 10 millisecondes,


avant de passer à la suite. Le principe utilisé ici est le suivant : on allume une led, on
attend 100 ms, on allume une deuxième led, on attend 10 ms, on éteint la première
led, on attend 100 ms, etc. L’allumage simultané de deux les n’est là que pour
diminuer l’effet de saccade lors du passage d’une led à l’autre.
Vous l’aurez remarqué : cette manière de faire est un peu « rustique », on aurait pu
remplacer la répétition des instructions par des boucles bien construites. Mais d’un
point de vue didactique ça me semble plus simple à comprendre, et comme on est
loin d’avoir saturé la mémoire du PIC…

Compilation du programme et injection dans le PIC :

Après l’étude théorique, il ne reste plus qu’à compiler notre programme et l’injecter
dans le PIC. Pour cela, on va suivre les étapes suivantes :

COMPILATION :

• Lancez l’IDE de SourceBoost.


• Assurer vous que le compilateur utilisé est bien BoostC (dans le menu
paramètres).
• Créez un nouveau projet (menu Projet : Nouveau).
• Dans ce projet, créez un nouveau fichier (menu Fichier : Nouveau) dans
lequel vous allez copier le code ci-dessus.
• Sélectionnez la « cible », autrement dit le composant pour lequel vous voulez
compiler le projet : PIC 16F877 (menu Paramètres : Cible)
• Compilez le programme (menu Construction : Construire)
• Si tout s’est bien passé, le compilateur indique « success » et a généré dans
le répertoire de votre projet plusieurs fichiers. L’un d’entre eux a une extension
en «.hex».

TRANSFERT (avec un programmateur Velleman K8076) :

• Configurez le programmateur pour le 16F877 (il faut que les « ponts »


aboutissent sur les bonnes pattes du support ZIF, si vous utilisez ce dernier)
• A ce stade, soit vous placez le pic sur le support ZIF du programmateur, soit
vous raccordez le cordon « ICSP » sur la platine sur laquelle est monté le PIC.
• Raccordez le programmateur au PC.
• Alimentez le programmateur.
• Lancez le programme PICPROG (livré avec le programmateur Velleman).
• Sélectionnez la cible, ici un PIC 16F877 (si vous ne le trouvez pas dans la
liste, vérifiez sur le site de Velleman s’il n’y a pas de mise à jour).
• Ouvrez le fichier «.hex » généré par le compilateur pour votre projet
• Cliquez sur le bouton « write all data », et attendez que le transfert s’effectue.

- 16 -
• Si à ce stade tout est ok, déconnectez le programmateur, placez le PIC sur
son circuit ou remettez les ponts à la place du connecteur ICSP de votre
platine prototype.

Tests

Vous pouvez alimenter votre circuit et… He is alive ! Un magnifique effet de balayage
anime vos 8 led. Elle est pas belle la vie ?

Conclusion :

Ca y est, vous avez créé une application à base de microcontrôleur ! Il est vrai que
pour l’instant, l’interaction de notre montage avec le monde réel est très limitée. Il
peut néanmoins servir à créer de jolis effets lumineux. On pourrait également
envisager de s’en servir comme télécommande rustique (à une seule fonction ?) en
montant une led infrarouge et en programmant la séquence d’allumages/extinctions
adéquate.

- 17 -
Utilisation d’un PORT en entrée : un chenillard 8 leds contrôlé par 2
boutons poussoirs.

Étape suivante : on va utiliser des pins d’un autre port en entrée, pour contrôler le
fonctionnement de notre application.

Le matériel :

1 40
2 39
3 38
4 37
5 36
6 35
7 34
RB1
RB0/INT
PIC 16F877

8 33
9 32
10 31
11 30
12 29
13 28 switchon switchoff
14 27
15 26
16 25
17 24
18 23
19 22
20 21

On reprend le schéma précédent, et on y ajoute deux boutons poussoirs directement


raccordés aux pins RB0 et RB1, autrement dit les deux bits de poids faible du
PORTB. Vous allez me dire, « Quand on appuie sur les boutons poussoirs, ok, les
pins sont à la masse, mais quand on relâche le potentiel est flottant, non ? »
justement non, puisque qu’on va utiliser les résistances de pull-up interne du PIC.
Une remarque importante : il n’y a que le PORTB qui soit équipé de telles
résistances. Si vous utilisez un autre port, il faudra câbler vous-même ces
résistances de tirage entre VCC et la pin correspondante.

- 18 -
Le programme :

La structure générale est identique à l’exemple précédent, avec tout de même


quelques modifications :

#include <system.h>

//Cible PIC16F877, bits de configuration


#pragma DATA _CONFIG, _PWRTE_OFF & _BODEN_OFF & _WDT_OFF &
_LVP_OFF & _CPD_OFF & _DEBUG_OFF & _XT_OSC & _CP_OFF

//Configuration de la fréquence d'horloge, ici 4Mhz


#pragma CLOCK_FREQ 4000000

void main( void )


{

//Initialisation port A
porta = 0x00;
//Initialisation port B
portb = 0x00;
//Initialisation port C
portc = 0x00;
//Initialisation port D
portd = 0x00;
//Initialisation port E
porte = 0x00;

//Configuration port A
trisa = 0x00;
//Configuration port B
trisb = 0x03; // On configure les deux pins de poids faible en entrée
//Configuration port C
trisc = 0x00;
//Configuration port D
trisd = 0x00;
//Configuration port E
trise = 0x00;

//Configuration A/D pins


adcon1 = 0x06;

//désactivation du mécanisme de gestion des interruptions


clear_bit( intcon, GIE );

- 19 -
//Validation des résistances de pull-ups du port B
clear_bit( option_reg, NOT_RBPU );

volatile bit switchon@0x06.0; // 0 pour RB0


volatile bit switchoff@0x06.1; // 1 pour RB1
bool go =0; // variable de contrôle

//Boucle sans fin


while( 1 )
{

if (switchon == 0)
{
go = 1;
}

if (switchoff == 0)
{
go = 0;
portd = 0b00000000;
}

if ( go )
{
portd = 0b00000001;
delay_ms(100);
portd = 0b00000011;
delay_ms(10);
portd = 0b00000010;
delay_ms(100);
portd = 0b00000110;
delay_ms(10);
portd = 0b00000100;
delay_ms(100);
portd = 0b00001100;
delay_ms(10);
portd = 0b00001000;
delay_ms(100);
portd = 0b00011000;
delay_ms(10);
portd = 0b00010000;
delay_ms(100);
portd = 0b00110000;
delay_ms(10);
portd = 0b00100000;
delay_ms(100);
portd = 0b01100000;

- 20 -
delay_ms(10);
portd = 0b01000000;
delay_ms(100);
portd = 0b11000000;
delay_ms(10);
portd = 0b10000000;
delay_ms(100);
portd = 0b11000000;
delay_ms(10);
portd = 0b01000000;
delay_ms(100);
portd = 0b01100000;
delay_ms(10);
portd = 0b00100000;
delay_ms(100);
portd = 0b00110000;
delay_ms(10);
portd = 0b00010000;
delay_ms(100);
portd = 0b00011000;
delay_ms(10);
portd = 0b00001000;
delay_ms(100);
portd = 0b00001100;
delay_ms(10);
portd = 0b00000100;
delay_ms(100);
portd = 0b00000110;
delay_ms(10);
portd = 0b00000010;
delay_ms(100);
portd = 0b00000011;
delay_ms(10);
}
}
}

- 21 -
Analyse du programme :

Le programme ressemble, à quelques détails près, à celui de l’exemple précédent.

/Cible PIC16F877, bits de configuration


#pragma DATA _CONFIG, _PWRTE_OFF & _BODEN_OFF & _WDT_OFF &
_LVP_OFF & _CPD_OFF & _DEBUG_OFF & _XT_OSC & _CP_OFF

//Configuration de la fréquence d'horloge, ici 4Mhz


#pragma CLOCK_FREQ 4000000

Comme vous pouvez le constater, on a changé de quartz. De 20 MHz on est passé à


4 MHz. Pourquoi ? Pas de raison particulière, c’est uniquement dans un but
didactique. Si vous voulez conserver votre quartz 20 MHz, conservez les valeurs de
l’exemple précédent pour cette section.

//Configuration port B
trisb = 0x03; // On configure les deux pins de poids faible en entrée

Les deux boutons poussoirs (« switchon » et « switchoff » sur le schéma) sont câblés
sur les pins du port B : RB0 et RB1. Dans l’exemple précédent, ces pins étaient
configurées en sortie. Il nous faut maintenant les configurer en entrée. Pour cela, on
met à 1 les bits correspondants du registre trisb, qui contrôle la direction du PORTB.
Ce qui nous donne, en binaire : 0b00000011, et donc, en hexadécimal : 0x03.

volatile bit switchon@0x06.0; // .0 pour RB0


volatile bit switchoff@0x06.1; // .1 pour RB1

On crée ici deux variables, « switchon » et « switchoff », de type « bit », donc codées
chacune sur 1 bit. Ces variables ne devront pas être placées n’importe où en
mémoire. Ce qu’on veut, c’est les associer aux bits correspondants aux pins RB0 et
RB1 du PORTB. On spécifie donc au compilateur l’adresse de ces variables. Par
exemple, pour switchon, « @0x06.0 », signifie « à l’adresse 0x06, le bit 0 ». 0x06
étant, bien entendu, l’adresse du PORTB. Le « .0 » spécifie le rang du bit, ici le bit de
poids 0, autrement dit RB0. Et pour switchoff, on procède de la même manière mais
en associant le bit « .1 », autrement dit RB1.
L’attribut « volatile » devrait surprendre les familiers du langage C. C’est un ajout de
BoostC au langage C ANSI standard, utilisé pour spécifier au compilateur que la
variable peut être modifiée en dehors du flot normal du programme. Imaginez, vous
venez de lire l’état d’une variable, vous ne l’avez pas modifiée, et voilà qu’elle
change d’état ! Incompréhensible. D’où l’attribut volatile. On avait jusqu’à présent les
variables « const » constantes, les variables standard modifiables par le programme,
et on a maintenant les variables « volatile » dont l’état peut changer en dehors du
programme.

- 22 -
bool go =0; // variable de contrôle

On crée une autre variable, « go », de type « bool » qui ne peut prendre que deux
états : vrai ou faux (1 ou 0). On ne spécifie pas d’adresse, c’est donc le compilateur
qui lui en attribuera une dans l’un des registre généraux de l’espace RAM. On
initialise cette variable à 0. Celle-ci servira de variable de contrôle, nous permettant
de valider ou non l’allumage des 8 leds.

if (switchon == 0)
{
go = 1;
}

On introduit une condition de test. Si switchon est égal à 0, on attribue la valeur 1 à


la variable go. Si vous découvrez le langage C, remarquez ici la différence entre un
« = » qui permet d’attribuer une valeur et un « == » qui permet de tester une égalité.

if (switchoff == 0)
{
go = 0;
portd = 0b00000000;
}

C’est au tour de switchoff d’être testé. S’il est égal à 0 (le bouton poussoir switchoff
est actionné et ramène le potentiel Vss sur RB1), on exécute la portion de code entre
accolades : on attribue la valeur 0 à la variable go, puis on met toutes les pins du
PORTD à 0 -> toutes les leds sont éteintes.

if ( go )
{
portd = 0b00000001;
delay_ms(100);
...
...
...
}

On teste enfin la variable « go ». Si elle est « vraie » (état =1), on exécute le code
entre accolades qui allume successivement les leds via le PORTD. Si elle est
« fausse » (état = 0) on recommence la boucle en testant switchon, puis switchoff,
puis à nouveau go, etc.

Compilation du programme et injection dans le PIC :

Rien de particulier ici. Procédez comme pour l’exemple précédent, et tout devrait
bien se passer.

- 23 -
Tests

Vous pouvez alimenter votre circuit et… Rien !


Normal, vous n’avez pas encore appuyé sur switchon. Réparez cet oubli : un
magnifique effet de balayage anime à nouveau vos 8 leds.
Appuyez maintenant sur switchoff. Le balayage s’éteint, mais, pas tout de suite !
Uniquement à la fin du cycle de balayage des leds (eh oui, on doit attendre que le
programme repasse par le test de switchoff).

Conclusion :

Le contrôle du fonctionnement de notre chenillard par deux boutons poussoirs


marque incontestablement un progrès. Vous savez maintenant utiliser les ports I/O
du pic en entrée et en sortie, utiliser les résistances de pull-up du PORTB.
Cependant, le fonctionnement actuel ne nous satisfait pas pleinement. Ce qu’on
voudrait, c’est que le balayage s’éteigne tout de suite, dès qu’on appuie sur
switchoff. Pour remédier à cela, on pourrait introduire des tests de switchoff avant
chaque allumage de la led suivante, plutôt qu’une seule fois avant de dérouler toute
la séquence. On pourrait également remplacer la séquence par une fonction
exécutée en boucle, le test sur switchoff étant alors réalisé avant chaque itération.
Quoi qu’il en soit, nous allons passer à l’étape suivante : l’utilisation du mécanisme
d’interruption sur changement d’état du PORTB.

- 24 -
Le PORTB et les interruptions

Principe de fonctionnement :

Le port B possède une caractéristique particulièrement intéressante : au même titre


que certains autres modules périphériques du PIC, il a la possibilité de générer des
interruptions.
Pour mémoire, le principe de fonctionnement des interruptions est le suivant : à un
moment donné (aléatoire), un événement particulier déclenche une interruption
(débordement d’un compteur, fin de conversion analogique numérique…).
L’exécution normale et linéaire du programme est interrompue, et le PIC se branche
sur une portion de code particulière : la routine d’interruption. Celle-ci exécute alors
un traitement particulier en réponse à l’événement déclencheur de l’interruption.
Puis, on sort de la routine d’interruption et on retourne à l’exécution normale du
programme.
Pour gérer ce mécanisme, il faut : valider dans le programme la ou les sources
d’interruption, et écrire une routine d’interruption appropriée.
Le contrôle des interruptions se fait grâce aux registres INTCON, PIE1, PIR1, PIE2,
PIR2. Le registre INTCON regroupe les bits de validation (= enable bit) et de
signalisation (= flag ou drapeau) des interruptions « de base ». Les interruptions des
modules périphériques sont gérées par les registres PIE et PIR. Sur le PIC 16F877
on a deux PIE et deux PIR, étant donné le nombre important de sources
d’interruptions possibles.
Avec BoostC, la routine d’interruption prend la forme d’une fonction particulière,
intitulée « interrupt ».
Dans l’exemple suivant, on va utiliser une interruption générée par le PORTB pour
contrôler la vitesse de défilement des 8 leds contrôlées par le PORTD. L’interruption
sur changement d’état du PORTB est générée dès qu’un changement d’état est
détecté sur une des pin RB4, RB5, RB6, ou RB7. Dans notre cas, ce sera RB4.

- 25 -
Le matériel :

1 40
2 39
3 38
4 37
5 36
6 35
7 34
PIC 16F877

8 33
9 32
10 31
11 30
12 29
13 28 switchint
14 27
15 26
16 25
17 24
18 23
19 22
20 21

Pour cet exemple, pas de grand changement : on se contente de câbler un bouton


poussoir supplémentaire sur la pin RB4 (la patte n°37 du PIC 16F877). On appelle ce
poussoir « switchint ».

- 26 -
Le programme :

#include <system.h>

//Cible PIC16F877, bits de configuration


#pragma DATA _CONFIG, _PWRTE_OFF & _BODEN_OFF & _WDT_OFF &
_LVP_OFF & _CPD_OFF & _DEBUG_OFF & _XT_OSC & _CP_OFF

//Configuration de la fréquence d'horloge, ici 4Mhz


#pragma CLOCK_FREQ 4000000

//***********************************************************************************************
volatile bit switchint@0x06.4; // 4 pour RB4
int i; // variable utilisée comme compteur dans une boucle "for"
bool speed=1; // variable "mémoire"

//***********************************************************************************************
void interrupt( void ) //routine de gestion des interruptions
{
if (intcon & 0b00000001)
{
delay_ms(10); // attente de 10 ms, pour éviter les rebonds du contact

if (switchint == 0) // on teste si RB4 est maintenu à l'état bas.


{
speed = !speed; // inverse l'état de la variable speed
}
//portb = portb; //lecture du portb pour supprimer le mismatch.
clear_bit( intcon, RBIF); //On efface le bit drapeau de l'interruption
}
}
//***********************************************************************************************
void leds(void) // fonction allumant successivement les leds
{
portd = 0b00000001;
if (speed == 0)
{
delay_ms(50);
}
delay_ms(50);

for ( i=0; i<8; i++ )


{
portd = portd << 0b00000001;
if (speed == 0)
{
delay_ms(50);
}
delay_ms(50);

- 27 -
}
}

//***********************************************************************************************
void main( void )
{

//Initialisation port A
porta = 0x00;
//Initialisation port B
portb = 0x00;
//Initialisation port C
portc = 0x00;
//Initialisation port D
portd = 0x00;
//Initialisation port E
porte = 0x00;

//Configuration port A
trisa = 0x00;
//Configuration port B
trisb = 0x10; // On configure la pin RB4 en entrée (0x10  0b00010000)
//Configuration port C
trisc = 0x00;
//Configuration port D
trisd = 0x00;
//Configuration port E
trise = 0x00;

//Configuration A/D pins


adcon1 = 0x06;

//Validation des résistances de pull-ups du port B


clear_bit( option_reg, NOT_RBPU );

//désactivation (temporaire) du mécanisme de gestion des interruptions


clear_bit( intcon, GIE );

//mise à 0 flags + désactivation toutes interruptions sauf interruption sur


//changement PORTB
intcon = 0b00001000; // on efface les flags, et on valide le bit RBIE : bit de
validation d'interruption sur changement du PORTB

//validation des interruptions :


set_bit(intcon, GIE );

- 28 -
//Boucle sans fin
while( 1 )
{
leds();
}
}

Analyse du programme :

Notre programme nous permet de faire défiler les 8 leds contrôlées par le PORTD :
Contrairement aux exemples précédents où on effectuait un « va et vient » avec un
effet de fondu, les leds sont allumées successivement l’une après l’autre. Puis on
recommence.
Deux vitesses de défilement sont possibles. On passe de l’une à l’autre en
actionnant le bouton poussoir « switchint ».

#include <system.h>

//Cible PIC16F877, bits de configuration


#pragma DATA _CONFIG, _PWRTE_OFF & _BODEN_OFF & _WDT_OFF &
_LVP_OFF & _CPD_OFF & _DEBUG_OFF & _XT_OSC & _CP_OFF

//Configuration de la fréquence d'horloge, ici 4Mhz


#pragma CLOCK_FREQ 4000000

Même séquence d’initialisation qu’à l’exemple précédent : on inclut le fichier


system.h, on configure les bits de configuration, et on indique la fréquence d’horloge.

volatile bit switchint@0x06.4; // 4 pour RB4


int i; // variable utilisée comme compteur dans une boucle "for"
bool speed=1; // variable "mémoire"

On déclare ensuite les variables utilisées dans le programme :


« switchint » qui est une variable de type « bit », affectée à la pin RB4 du PORTB,
« i » qui est une variable de type « int » qui nous servira à compter les itérations
d’une boucle « for », et « speed », variable booléenne (qui ne peut donc valoir que 0
ou 1) qui nous permettra de contrôler les temporisations entre l’allumage des leds et
donc, la vitesse du défilement.

- 29 -
void interrupt( void ) //routine de gestion des interruptions
{
if (intcon & 0b00000001)
{
delay_ms(10); // attente de 10 ms, pour éviter les rebonds du contact

if (switchint == 0) // on teste si RB4 est maintenu à l'état bas.


{
speed = !speed; // inverse l'état de la variable speed
}
//portb = portb; //lecture du portb pour supprimer le mismatch.
clear_bit( intcon, RBIF); //On efface le bit drapeau de l'interruption
}
}

Voici donc la fameuse routine d’interruption ! Il s’agit d’une fonction spéciale chargée
du traitement des interruptions. Pas besoin de l’appeler dans le corps du programme
(contenu dans la fonction main() ), elle sera exécutée automatiquement lorsqu’une
source d’interruption valide se manifestera. Son nom est figé, donc si vous la
remplacez par une fonction « void interruption( void ) », ça ne marchera pas !
SourceBoost ne reconnaitrait pas qu’il s’agit là de la routine de traitement des
interruptions. Quelle que soit la source d’interruption, la fonction interrupt() sera
appelée. Donc, si vous avez plusieurs sources d’interruptions possibles, il faudra
tester quelle est l’origine, afin d’exécuter le traitement adéquat.
C’est ce qu’on fait ici avec l’instruction « if (intcon & 0b00000001) » : on fait un ET
logique entre le registre INTCON et la valeur binaire 0b00000001. Si le bit de poids 0
du registre INCON est égal à 1, le résultat vaudra 1 et la portion de code suivant le
test « if » sera exécutée. Le bit de poids 0 d’INTCON n’est autre que RBIF, bit
« drapeau » de signalisation de l’interruption sur changement du port PORTB.
On aurait pu ici se passer de ce test, étant donné qu’on a qu’une seule source
d’interruption dans notre exemple.
Vient ensuite une temporisation de 10 millisecondes (delay_ms(10) ) qui nous permet
de nous affranchir des rebonds du contact de switchint. Sans cela, ces rebonds
généreraient une multitude d’interruptions successives.
Puis, on test la valeur de switchint, le traitement effectif n’ayant lieu que s’il est à 0,
autrement dit si le bouton poussoir est appuyé. Je dois vous faire remarquer ici que
contre le PIC, vous n’avez aucune chance : il est beaucoup plus rapide que vous !
Une interruption sera générée lorsque vous appuierez sur switchint, mais aussi
lorsque vous le relâcherez ! Mais dans ce dernier cas, switchint sera égal à 1, et le
traitement sera ignoré : la condition du « if » sera fausse.
Vient ensuite le code utile du traitement de notre interruption : on inverse l’état de la
variable « speed » grâce à l’opérateur « ! » (lire « NOT ») qui complémente
l’opérande située à sa droite. Ainsi, « speed » devient égal à l’inverse de « speed ».
Pour mémoire, j’ai mis en commentaire l’instruction « portb = portb ». Pourquoi ? Il
faut savoir ici que l’interruption est générée lorsque l’état physique d’une pin du
PORTB est différent de l’état mémorisé dans son tampon de réception. On a alors un
« mismatch », autrement dit un décalage. Pour supprimer ce décalage, il faut
effectuer une opération de lecture du PORTB pour « réaligner » l’état théorique et
l’état physique du port. Dans notre cas, une lecture de la variable switchint provoque
la lecture du port, c’est donc déjà fait. Mais dans l’hypothèse ou votre code de

- 30 -
traitement de l’interruption n’accéderait pas au PORTB, il faudrait effectuer cette
lecture. Sans quoi, à peine sorti de la routine d’interruption, on y retournerait, le
« mismatch » étant toujours présent !
On efface enfin le bit de signalisation d’interruption, RBIF (poRt B Interrupt Flag bit)
grâce à l’instruction « clear_bit(intcon, RBIF) ».

void leds(void) // fonction allumant successivement les leds


{
portd = 0b00000001;
if (speed == 0)
{
delay_ms(50);
}
delay_ms(50);

for ( i=0; i<8; i++ )


{
portd = portd << 0b00000001;
if (speed == 0)
{
delay_ms(50);
}
delay_ms(50);

}
}

Contrairement aux exemples précédents, le code chargé d’allumer successivement


les leds est regroupé dans une fonction, appelée « leds ». Le « void » à gauche
signifie qu’on ne retourne aucune valeur, alors que le void entre parenthèses indique
qu’on ne prend aucune valeur en argument.
On allume notre première led sur RD0 grâce à l’instruction « portd = 0b00000001 ;
Puis, on test la variable « speed ». En fonction du résultat, soit on exécute une
première temporisation de 50 millisecondes suivie d’une deuxième temporisation de
50ms, soit on exécute uniquement la deuxième temporisation.
Puis on entre dans la boucle for, qui utilise la variable « i » comme compteur.
Dans cette boucle, exécutée 7 fois, on commence par effectuer un décalage à l’aide
de l’instruction « portd = portd << 0b00000001 ; ». La traduction en français de cette
instruction donne quelque chose du genre « portd est égal à portd décalé d’un bit
vers la gauche ». L’opérateur de décalage étant << .
Puis, on retrouve nos deux temporisations dont la première est conditionnée à la
valeur de « speed ».

- 31 -
void main( void )
{

//Initialisation port A
porta = 0x00;
//Initialisation port B
portb = 0x00;
//Initialisation port C
portc = 0x00;
//Initialisation port D
portd = 0x00;
//Initialisation port E
porte = 0x00;

//Configuration port A
trisa = 0x00;
//Configuration port B
trisb = 0x10; // On configure la pin RB4 en entrée (0x10  0b00010000)
//Configuration port C
trisc = 0x00;
//Configuration port D
trisd = 0x00;
//Configuration port E
trise = 0x00;

//Configuration A/D pins


adcon1 = 0x06;

//Validation des résistances de pull-ups du port B


clear_bit( option_reg, NOT_RBPU );

On entre ensuite dans le corps du programme (la fonction « main »), la seule
différence par rapport à l’exemple précédent étant la valeur affectée au registre
« trisb » : seule la pin RB4 est configurée en entrée.

- 32 -
//désactivation (temporaire) du mécanisme de gestion des interruptions
clear_bit( intcon, GIE );

//mise à 0 flags + désactivation toutes interruptions sauf interruption sur


//changement PORTB
intcon = 0b00001000; // on efface les flags, et on valide le bit RBIE : bit de
validation d'interruption sur changement du PORTB

//validation des interruptions :


set_bit(intcon, GIE );

Viens ensuite la partie configuration du mécanisme d’interruption. J’aurais pu faire


plus simple et remplacer les trois instructions par une seule, mais je préfère procéder
comme suis :
1 – désactivation globale des interruptions avec l’instruction « clear_bit( intcon,
GIE) » qui efface le bit GIE du registre INTCON.
2 – effacement des bits « drapeaux » de signalisation d’interruption éventuellement
positionnés, et configuration de la source d’interruption : pour valider l’interruption sur
changement d’état du PORTB, on met à 1 le bit RBIE.
3 – activation globale des interruptions avec l’instruction « set_bit( intcon, GIE) » qui
met à 1 le bit GIE (Global Interrupt Enable) du registre INTCON. A partir de là, c’est
parti : si vous appuyez sur switchint, le PIC exécutera la fonction « interrupt ».

//Boucle sans fin


while( 1 )
{
leds();
}

Puis vient enfin, comme dans les exemples précédents, une boucle sans fin qui se
contente d’appeler la fonction « leds() » définie précédemment.

- 33 -
Tests

Après avoir compilé et transféré votre programme comme précédemment, vous


pouvez alimenter le montage : les leds se mettent à défiler l’une après l’autre, sans
fin. Une pression sur le bouton poussoir switchint fait passer la vitesse de défilement
de rapide à lente. Une nouvelle pression sur switchint et la vitesse de défilement
repasse de lente à rapide. Autrement dit, ça marche : une pression sur le bouton
poussoir switchint lance bien la routine d’interruption, et celle-ci inverse effectivement
la variable booléenne « speed ».
Trop fort, non ?

Conclusion :
Vous connaissez maintenant le mécanisme de base de gestion des interruptions.
Les bits de validation (RBIE) et de signalisation (RBIF) de l’interruption sur
changement du PORTB étant présents dans le registre INTCON, nous n’avons pas
eu pour l’instant à nous préoccuper des registres PIE1, PIR1, PIE2, PIR2.
Mine de rien, notre dernier exemple nous a fait passer d’une programmation
« séquentielle », où toutes les actions étaient effectuées dans l’ordre, l’une à la suite
de l’autre, à une programmation « événementielle » capable de prendre en compte
un événement à tout moment du déroulement du programme.
Autrement dit : on devient bons !

- 34 -

Vous aimerez peut-être aussi