Dernière mise à jour :
09/05/2010
Présentation
Les exemples décrits ici montrent comment utiliser le
composant PCF8591, qui regroupe en un seul boitier 1 convertisseur
numérique / analogique (CNA ou DAC) et 4 convertisseurs analogique /
numérique (CAN ou ADC). La notion d'adressage "interne" (adresse de
base) et local (adresse variable) sera bien sûr abordée puisqu'il
s'agit d'un élement essentiel à connaitre pour un composant dialoguant selon
la norme I2C.
Electronique mise en oeuvre
Les exemples de code logiciel qui suivent montrent comment
lire ou écrire des valeurs depuis les entrées analogiques ou vers la sortie analogique du PCF8591 grâce à une
liaison
bifilaire I2C.
Schéma électronique pour lecture et écriture
Pour la mise en pratique de ces exemples de code logiciel, le montage dont le schéma
suit est mis en oeuvre.
On y voit un microcontrôleur de type 18F2420 (un autre type de PIC
ou même un microcontrôleur d'une autre famille peut aussi
convenir), un PCF8591, un groupe de 8 interrupteurs, quatre
potentiomètres et un écran LCD à quatre lignes (c'est du luxe mais sur
le papier ça ne coute pas cher).
- le PIC 18F2420 assure la totalité
des commandes d'écriture et de lecture sur le bus I2C et gère les
routine d'affichage pour l'écran LCD;
- le PCF8591 est exploité
presque "à fond" : on utilise son unique sortie analogique et ses
quatre entrées analogiques, lesquelles reçoivent une tension que l'on
peut faire varier grâce aux quatre potentiomètres RV1 à RV4. L'entrée
AGND du PCF8593 est reliée à la masse et son entrée VREF est reliée au +5 V, la
plage des tensions sur les entrées analogiques et sur la sortie
analogique s'étend donc de 0 V à +5 V;
- les 8 interrupteurs câblés
sur le port B du PIC (RB0 à RB7) permettent de définir la valeur de la
tension sur la sortie analogique du PCF8591. Le codage se fait sur
8 bits et on dispose donc de 256 niveaux de tension possible : valeur
binaire 0 pour tension analogique de 0 V, valeur binaire de 255 pour
tension de sortie de +5 V, avec toutes les valeurs intérmédiaires par pas de 20 mV environ.
- l'écran LCD permet de
visualiser la valeur représentative des tensions présentes
sur les quatre entrées analogiques du PCF8591. Chaque valeur de tension
sur les entrées analogiques AN0 à AN3 du PCF8591 est échantillonnée
avec une quantification de 8 bits, on dispose donc d'un octet (ni plus,
ni moins) par valeur. Dans la pratique, on effectuera un traitement des
octets reçus, on n'affichera pas leur valeur brute comme c'est
fait ici pour les besoins simples de l'expérimentation.
Principe de fonctionnement général
Un
PCF8591 est raccordé à un microcontrôleur via un bus I2C.
Le PCF8591 dispose de trois entrées
d'adresse complémentaires, qui permettent de décaller
son adresse
d'affectation de base. Ces trois entrées d'adresse complémentaires
permettent huit combinaisons
possibles, ce qui correspond à huit valeurs d'adresses différentes. On
peut ainsi mettre en parallèle 8 circuits PCF8591 sur un même bus I2C -
chacun avec une adresse différente, ce
qui permet de travailler avec 8 (8 x 1) sorties analogiques distinctes et
32 (8 x 4) entrées analogiques distinctes. Pour plus de détails concernant
l'adressage du composant, voir paragraphe "Adresses du PCF8591"
après le descriptif du code logiciel.
Remarque
On n'est pas obligé d'utiliser
un composants de type I2C pour bénéficier de plusieurs entrées ou
sorties analogiques. Il est tout à fait possible d'utiliser d'autres
types de convertisseurs, I2C ou non, de type parallèle ou de type
série, de type simple ou de type multiple. Mais comme l'idée ici de
voir comment mettre en oeuvre le PCF8591, on ne va pas s'étendre sur
les autres cas d'éspèce.
Lecture et écriture de données avec le PCF8591
Le code exemple qui suit comporte deux ensembles de routines :
- routines de lecture depuis les entrées analogiques du PCF8591
- routine d'écriture vers la sortie analogique du PCF8591.
Le logiciel
Le code ci-dessous correspond au programme complet.
program electronique_pic_tuto_base_i2c_pcf8591_001a;
const
cPCF8591_W = $90;
cPCF8591_R = $91;
var
Values: array[0..3] of byte;
i, iConf, iVal: byte;
LCD_RS : sbit at LATA4_bit;
LCD_EN : sbit at LATA5_bit;
LCD_D4 : sbit at LATA0_bit;
LCD_D5 : sbit at LATA1_bit;
LCD_D6 : sbit at LATA2_bit;
LCD_D7 : sbit at LATA3_bit;
LCD_RS_Direction : sbit at TRISA4_bit;
LCD_EN_Direction : sbit at TRISA5_bit;
LCD_D4_Direction : sbit at TRISA0_bit;
LCD_D5_Direction : sbit at TRISA1_bit;
LCD_D6_Direction : sbit at TRISA2_bit;
LCD_D7_Direction : sbit at TRISA3_bit;
procedure Main_Init;
begin
CMCON := $07; // turn off comparators
INTCON2.NOT_RBPU := 0; // activate pullup
ADCON1 := ADCON1 or $0F; // turn off analog inputs
TRISA := $00; // outputs
TRISB := $FF; // inputs
PORTA := $00;
PORTB := $00;
// I2C comm init
I2C1_Init(100000);
// LCD init
Lcd_Init;
Lcd_Cmd(_LCD_CURSOR_OFF);
end;
procedure Display_Refresh;
var
sVal, sIdx: string[3];
s: string[20];
begin
Lcd_Cmd(_LCD_CLEAR);
for i := 0 to 3 do
begin
ByteToStr(Values[i], sVal);
ByteToStr(i, sIdx);
s := sIdx + ' : ' + sVal;
LCD_Out(i + 1, 1, s);
end;
end;
procedure PCF8591_Write;
begin
// config CAN
iConfig := $40;
iVal := PORTB;
I2C1_Start; // signal START
I2C1_Wr(cPCF8591_W); // Addresse PCF8591
I2C1_Wr(iConf); // envoi configuration générale
I2C1_Wr(iVal); // envoi valeur sortie analogique
I2C1_Stop; // signal STOP
end;
procedure PCF8591_Read_All;
begin
// config CAN
iConf := $44;
I2C1_Start; // signal START
I2C1_Wr(cPCF8591_W); // addresse PCF8591
I2C1_Wr(iConf); // envoi configuration générale
I2C1_Stop; // signal STOP
// read analog Values
I2C1_Start; // signal START
I2C1_Wr(cPCF8591_R); // addresse PCF8591
Values[0] := I2C1_Rd(1); // lecture entree analogique "prev"
Values[0] := I2C1_Rd(1); // lecture entree analogique #0
Values[1] := I2C1_Rd(1); // lecture entree analogique #1
Values[2] := I2C1_Rd(1); // lecture entree analogique #2
Values[3] := I2C1_Rd(0); // lecture entree analogique #3
I2C1_Stop; // signal STOP
end;
procedure PCF8591_Read_One(idx: byte);
begin
// config CAN
iConf := $40;
iConf := iConf xor idx; //
I2C1_Start; // signal START
I2C1_Wr(cPCF8591_W); // addresse PCF8591
I2C1_Wr(iConf); // envoi configuration générale
I2C1_Stop; // signal STOP
// read analog value
I2C1_Start; // signal START
I2C1_Wr(cPCF8591_R); // addresse PCF8591
iVal := I2C1_Rd(1); // lecture entree analogique
iVal := I2C1_Rd(0); // lecture entree analogique
I2C1_Stop; // signal STOP
Values[idx] := iVal;
end;
procedure PCF8591_Read;
begin
// sample values on analog inputs
if 1 = 1 then
begin
PCF8591_Read_All;
end
else
begin
PCF8591_Read_One(0);
PCF8591_Read_One(1);
PCF8591_Read_One(2);
PCF8591_Read_One(3);
end;
// show sampled Values on LCD display
Display_Refresh;
end;
begin
Main_Init;
delay_ms(500);
while true do
begin
// read PCF8591 analog inputs
PCF8591_Read;
Delay_ms(250);
//write to PCF8591 analog output
PCF8591_Write;
Delay_ms(250);
end;
end.
Descriptif du logiciel
Procédure Init
Cette
routine d'initialisation générale, qui est appelée une seule fois au
moment de la mise sous tension du PIC, permet de définir l'orientation
des broches des ports A et B, grâce aux lignes TRISA et TRISB. La ligne
CMCON = 7 permet de désactiver le fonctionnement des comparateurs du
PIC. La ligne ADCON1 permet de désactiver les fonctions liées au
convertisseur Analogique / Numérique, toutes les lignes sont en effet
utilisées ici de façon logique. La ligne INTCON2.NOT_RBPU := 0 permet
d'activer la fonction pullup du port B et évite d'ajouter des
résistances de rappel additionnelles sur chaque ligne du port B. La
ligne I2C1_Init permet d'initialiser
le module MSSP utilisé pour les fonctions de lecture / écriture sur bus
I2C.
Lecture de données en
provenance du PCF8591 (entrées analogiques)Les
valeurs analogiques présentes sur les entrées AN0 à AN3 du PCF8591 sont
lues (échantillonnées) puis affichées sur l'écran LCD. Il existe deux méthodes différentes de lecture des entrées analogiques :
-
soit lecture des quatres entrées les unes après les autres, avec une
routine de lecture complète pour chaque entrée individuelle (quatre routines de
lecture en tout);
- soit lecture en mode rafale des quatres entrées
en même temps, avec une seule routine de lecture complète pour les
quatres entrées.
Le
premier mode de lecture est identifié dans le
code logiciel par le nom de procédure PCF8591_Read_One(idx), la valeur
de idx
correspondant à l'entrée à échantillonner (entre 0 et 3). Le second
mode de lecture est identifié dans le code logiciel par le nom de
procédure PCF8591_Read_All, qui ne demande aucun paramètre puisque
toutes les entrées sont lues en bloc. Le choix du type de lecture est
fait dans la routine PCF8591_Read, dans laquelle vous pouvez poser la
condition 1 = 0 (lecture en quatre appels séparés) ou 1 = 1 (lecture en
bloc). J'ai passé une journée entière pour trouver comment lire dans
ces deux modes, je n'arrivais pas à mettre en face les infos
du datasheet du composant et le logiciel MikroPascal. J'ai en
effet commencé mes tests en mettant dans une seule routine de lecture
I2C, la configuration du CAN avant la lecture des données elles-même.
Pas moyen de faire fonctionner mon système en procédant de la sorte.
J'ai ensuite essayé de séparer les fonctions de configuration et de
lecture des données, c'est à dire en ajoutant un STOP / START entre les
deux, et là ça fonctionnait bien. Attention, je ne dis pas que c'est la
seule solution qui fonctionne, je dis que c'est la seule solution qui
m'a permis d'avoir les résultats attendus. J'ai encore pas mal de choses à
apprendre...
Ecriture de données vers
le PCF8591 (sortie analogique)La
valeur spécifiée par l'utilisateur avec les microswitches
(interrupteurs) connectés sur le port B du PIC est transmise sur
la sortie analogique du PCF8591 après conversion numérique / analogique. Aucune résistance de
rappel n'est
ajoutée sur le port B car il est fait usage du pullup interne au PIC,
qui a été activé lors de l'initialisation principale. Quand un
interrupteur est ouvert, l'entrée correspondante se retrouve à l'état
logique haut. La fermeture d'un interrupteur positionne l'entrée
correspondante à l'état logique bas. Le codage s'effectue en binaire,
avec le bit de poids faible en RB0 et le bit de poids fort en RB7. Si
toutes les entrées du port B du PIC sont à zéro, la tension
disponible sur
la sortie analogique du PCF8591 vaut 0 V. Si toutes les entrées du port
B du PIC sont à l'état logique haut, la tension disponible sur
la sortie analogique du PCF8591 vaut +5 V. Avec toutes les valeurs
intermédiaires que cela suppose, par exemple +2,5 V quand seule
l'entrée RB7 est à l'état logique haut.
Remarque concernant la valeur donnée à la variable iConfigiConfig
est une variable de taille 8 bits (octet) qui permet de stocker les
différents bits de configuration des convertisseurs CAN et CNA du PCF8591. La
valeur $40 qui lui est donnée à certains endroits correspond en fait à
la configuration suivante :
iConf.7 := 0; // -
iConf.6 := 1; // activation sortie analogique
iConf.5 := 0; // -| mode entrées analogiques
iConf.4 := 0; // -|
iConf.3 := 0; // -
iConf.2 := 0; // auto-incrementation
iConf.1 := 0; // -| selection canal
iConf.0 := 0; // -|
Et
la valeur $44 qui lui est donnée à d'autres moments (pour la lecture en
mode rafale par exemple) correspond à la configuration suivante :
iConf.7 := 0; // -
iConf.6 := 1; // activation sortie analogique
iConf.5 := 0; // -| mode entrées analogiques
iConf.4 := 0; // -|
iConf.3 := 0; // -
iConf.2 := 1; // auto-incrementation
iConf.1 := 0; // -| selection canal
iConf.0 := 0; // -|
Programme principal
Dans le programme principal se déroule des actions qui se répètent
indéfiniment, grace à l'emploi d'une boucle infinie (qui ne s'arrête
jamais ca aucune condition n'impose son arrêt). Voici précisement ce
qui est fait de façon répétée :
- on lit les valeurs des tensions appliquées sur les entrées analogiques du PCF8591, et on les affiche
- on attend 250 ms sans rien faire
- on envoie la valeur de la tension spécifiée (par les interrupteurs câblés sur le port B du PIC) sur la sortie analogique du
PCF8591
- on attend 250 ms sans rien faire
Adresses du PCF8591
Chaque composant I2C possède une adresse qui lui est propre, et la
portion de code suivante
(extraite du code complet vu ci-avant) précise à quelle numéro
de porte on doit frapper pour dialoguer avec le PCF8591 tel qu'il
est mis en oeuvre dans le schéma utilisé.
const
cPCF8591_W = $90;
cPCF8591_R = $91;
Dans le cas présent, la définition des adresses est faite "en dur" dans
le code
logiciel via des constantes, avec des valeurs qui collent avec le
schéma proposé en début d'article. Mais dans certains cas vous pouvez
être ammené à modifier l'adresse de ces composants, ne serait-ce que si
vous en mettez plusieurs en parallèle, par exemple deux ou huit. Dans ce cas,
les numéros d'adresses utilisées dans le logiciel du PIC doivent être
adaptées en fonction du composant avec lequel dialoguer. Le procédé
d'adressage est
assez simple à comprendre : le PCF8591 dispose d'une
adresse fixe (qu'on
appelle adresse de base) et cette adresse fixe peut être décallée d'une
certaine valeur grâce aux broches d'adresse A0 à A2. L'adresse de
base est celle qui doit être utilisée quand les trois broches A0
à A2 sont à l'état logique bas. Si au moins une de ces broches
d'adresse est portée à l'état logique haut, essayer de communiquer avec
le composant avec l'adresse de base se soldera par un échec. Vous
trouverez dans le tableau suivant les différentes adresses à utiliser
selon l'état logique donné aux broches A0 à A2 du PCF8591.
A2 |
A1 |
A0 |
PCF8591
Ecriture |
PCF8591
Lecture |
0 |
0 |
0 |
$90 (144d) |
$91 (145d) |
0 |
0 |
1 |
$92 (146d) |
$93 (147d) |
0 |
1 |
0 |
$94 (148d) |
$95 (149d) |
0 |
1 |
1 |
$96 (150d) |
$97 (151d) |
1 |
0 |
0 |
$98 (152d) |
$99 (153d) |
1 |
0 |
1 |
$9A (154d) |
$9B (155d) |
1 |
1 |
0 |
$9C (156d) |
$9D (157d) |
1 |
1 |
1 |
$9E (158d) |
$9F (159d) |
Pour
rappel, la valeur effective et complète de l'adresse est codée sur les
sept bits
de poids fort de l'octet d'adresse envoyé sur le bus I2C, le dernier
bit (de poids faible) étant reservé à
l'information de direction des données. C'est ce qui explique cet écart
permanent de 1 entre l'adresse de lecture et l'adresse d'écriture pour
une configuration données des broches A0 à A2. Le texte qui
suit détaille la procédure qui a été suivie pour remplir le
tableau précédent. Les valeurs numériques notées selon la forme
xxxd sont exprimées
en
décimal, les valeurs notées selon la forme
$xx
sont exprimées en héxadécimal.
Adresse de base du PCF8591
:
1001 000x
- bits 7 à 4 = adresse fixe =
1001
($90 ou 144d)
- bits 3 à 1 = adresse variable =
000
($00 ou 000d)
- bit 0 = bit de direction =
0
pour écriture ou
1
pour lecture
Pour
obtenir la valeur de l'adresse complète (part fixe plus part
variable), il suffit d'additionner les valeurs de ces trois
informations :
- pour écriture :
144d
+
000d
+
0 = 144d = $90
- pour lecture :
144d
+
000d
+
1
= 145d = $91
Exemple
avec adresse variable du PCF8591 = 001 (A2 = 0,
A1 = 0 et A0 = 1), alors on a :
- bits 7 à 4 = adresse fixe =
1001
($90 ou 144d)
- bits 3 à 1 = adresse variable =
001
($02 ou 002d)
- bit 0 = bit de direction =
0
pour écriture ou
1
pour lecture
Ce qui donne les valeurs suivantes pour l'adresse complète :
- pour écriture :
144d
+
002d
+
0 = 146d = $92
- pour lecture :
144d
+
002d
+
1
= 147d = $93
Exemple
avec adresse variable du PCF8591 = 111
(A2 = 1, A1 = 1 et A0 = 1), alors on a :
- bits 7 à 4 = adresse fixe =
1001
($90 ou 144d)
- bits 3 à 1 = adresse variable =
111
($0E ou 014d)
- bit 0 = bit de direction =
0
pour écriture ou
1
pour lecture
Ce qui donne les valeurs suivantes pour l'adresse complète :
- pour écriture :
144d
+
014d
+
0 = 158d = $9E
- pour lecture :
144d
+
014d
+
1
= 159d = $9F
Notez
bien le décallage de 1 bit de la valeur d'adresse variable, toujours d'actualité pour ceux qui se lancent !
Un
exemple pratique de plusieurs PCF8591 montés sur un même bus I2C et où
chacun est adressé de façon séquentielle est visible à la page
Testeur alarme incendie 001.