Logiciels > Programmation PIC > Bases > MP > Lecture / écriture sur bus I2C > PCF8591

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.

pic_tuto_base_i2c_pcf8591_001a

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 iConfig
iConfig 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 PCF85911001 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 + 000d1 = 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 + 002d1 = 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 + 014d1 = 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.