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

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

- 4 -

9V =

9V = IC2 D1 7805 R1 C3 Led1 C4 1 40 ICSP 2 39 3 38
IC2 D1 7805 R1 C3
IC2
D1
7805
R1
C3

Led1

9V = IC2 D1 7805 R1 C3 Led1 C4 1 40 ICSP 2 39 3 38
C4 1 40 ICSP 2 39 3 38 4 37 5 36 6 35 7
C4
1
40
ICSP
2
39
3
38
4
37
5
36
6
35
7
34
8
33
C6
9
32
10
31
C5
11
30
12
29
13
28
14
27
15
26
16
25
C1
C2
17
24
Q1
18
23
19
22
20
21
PIC 16F877

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

2

3

4

5

Clock = ligne d’horloge de programmation) 1 2 3 4 5 6 7 8 9 10

6

7

8

9

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
1
40
2
39
3
38
4
37
VCC
5
36
6
35
R9
R8
R7
R6
R5
R4
R3
R2
7
34
8
33
9
32
10
31
Rd8
11
30
12
29
Rd8
13
28
14
27
Rd6
15
26
16
25
Rd5
17
24
18
23
Rd4
19
22
20
21
Rd3
Rd2
Rd1
PIC 16F877

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 : V Rx = 5-1,8 = 3,2V ; donc R = V Rx / 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 RB1 7 34
1
40
2
39
3
38
4
37
5
36
6
35
RB1
7
34
RB0/INT
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
PIC 16F877

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;

volatile bit switchoff@0x06.1;

bool go =0;

// 0 pour RB0 // 1 pour RB1

// 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; volatile bit switchoff@0x06.1;

// .0 pour RB0 // .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 8
1
40
2
39
3
38
4
37
5
36
6
35
7
34
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
PIC 16F877

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 -