Arduino et le bus I2C

Image non disponible

Comment échanger des données entre une carte Arduino et un périphérique communiquant avec une liaison série I2C (Inter-Integrated Circuit).

http://tronixstuff.com/2010/10/20/tutorial-arduino-and-the-i2c-bus/

Article lu   fois.

Les deux auteur et traducteur

Site personnel

Traducteur : Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. But first of all, what is it? Avant tout, de quoi s'agit-il ?

I2C is an acronym for “Inter-Integrated Circuit”. In the late 1970s, Philips' semiconductor division (now NXP ) saw the need for simplifying and standardising the data lines that travel between various integrated circuits in their products. Their solution was the I2C bus. This reduced the number of wires to two (SDA - data, and SCL - clock). Here is a nice introductory video from NXP:

vidéo

I2C est l'acronyme d'Inter-Integrated Circuit. À la fin des années 1970, la division des semi-conducteurs de chez Philips (maintenant devenue NXP) a vu la nécessité de simplifier et standardiser les échanges de données entre les différents circuits intégrés dans leurs produits. Leur solution fut le bus I2C, elle réduisait le nombre de lignes nécessaires à seulement deux lignes, SDA - Serial DAta, et SCL - Serial CLock.

NDLR : c'est maintenant chez le fabricant de semi-conducteurs NXP que vous trouverez les spécifications de la norme I2C.

À l'origine, la communication était limitée à la vitesse de transfert de 100 kbit/s, et cela suffisait dans la majorité des cas. Pour des débits plus rapides, de nouvelles spécifications sont nées. D'abord un Fast Mode à 400 kbit/s, puis un Fast Mode plus (FM+) à 1 Mbit/s. Depuis 1998, il y a une version High Speed à 3,4 Mbit/s. Le débit maximal possible via un bus I2C est spécifié dans l'Ultra Fast mode à 5 Mbit/s, mais avec un fonctionnement un peu particulier.

Caractéristiques principales du bus I2C :

Image non disponible
Source : nxp.com
  • seulement deux lignes (bidirectionnelles) sont nécessaires, une pour transporter les données - SDA -, une autre pour l'horloge de synchronisation - SCK - (1 bit échangé à chaque « coup » d'horloge) ;
  • transmission synchrone. Pas besoin de spécifier une vitesse de transfert comme pour la liaison RS232. Ici, le périphérique maître (master) génère le signal d'horloge qui synchronise et cadence les échanges ;
  • la relation entre les périphériques du bus est de type maître-esclave (master/slave). Le maître est à l'initiative de la transmission et s'adresse à un esclave (ou tous les esclaves) ;
  • chaque périphérique sur le bus I2C est adressable, avec une adresse unique pour chaque périphérique du bus ;
  • l'I2C gère le fonctionnement multimaître (multi-master), plusieurs périphériques maîtres peuvent prendre simultanément le contrôle du bus (système d'arbitrage des maîtres et gestion des collisions).

II. Why would we want to use I2C devices? Pourquoi utiliserions-nous des périphériques I2C ?

As there are literally thousands of components that use the I2C interface! And our Arduino boards can control them all. There are many applications, such a real-time clocks, digital potentiometers, temperature sensors, digital compasses, memory chips, FM radio circuits, I/O expanders, LCD controllers, amplifiers, and so on. And you can have more than one on the bus at any time, in fact the maximum number of I2C devices used at any one time is 112.

Il y a des milliers de composants qui utilisent une interface I2C, et les cartes de la famille Arduino peuvent toutes les contrôler. Les applications sont multiples : horloges temps réel, potentiomètres numériques, capteurs de température, boussoles numériques, mémoires, circuits radio FM, cartes d'extension d'entrées-sorties, contrôleurs d'afficheur LCD, amplificateurs et bien d'autres encore. Vous pouvez connecter plusieurs composants adressables sur un même bus à tout moment (jusqu'à 112 périphériques I2C adressables sur un même bus en théorie).

From a hardware perspective, the wiring is very easy. Those of you with an Arduino Uno or  100% compatible board , you will be using pins A4 for SDA (data) and A5 for SCL (clock):

En ce qui concerne l'architecture matérielle, le câblage est très simple. Ceux qui disposent de l'Arduino Uno ou d'une carte compatible, utiliseront les connecteurs A4 pour SDA (les données) et A5 pour SCL (l'horloge) :

Image non disponible

If you are using an Arduino Mega, SDA is pin 20 and SCL is 21, so note that shields with I2C need to be specifically for the Mega. If you have another type of board, check your data sheet or try the Arduino team's hardware website.  And finally, if you are using a bare DIP ATmega328-PU microcontroller, you will use pins 27 for SDA and 28 for SCL. The bus wiring is simple:

Si vous utilisez une carte Arduino Mega, SDA est sur le connecteur 20 et SCL sur le connecteur 21. Si vous voulez connecter un shield Arduino avec une interface I2C, vérifiez bien sa compatibilité avec l'Arduino Mega. Pour les autres types de carte, il faudra consulter la fiche technique (data sheet) ou se renseigner sur le site de la communauté Arduino. Finalement, si vous interfacez directement le microcontrôleur ATmega328-PU dans son boîtier DIP, vous utiliserez les pattes 27 pour SDA et 28 pour SCL.

L'architecture du bus est simple, ici avec deux périphériques sur le bus I2C :

Image non disponible
Source : nxp.com

If you are only using one I2C device, the pull-up resistors are (normally) not required, as the ATmega328 microcontroller in our Arduino has them built-in.  However if you are running a string of devices, use two 10 kilo ohm resistors. Like anything, some testing on a breadboard or prototype circuit will determine their necessity. Sometimes you may see in a particular device's data sheet the use of different value pull-up resistors - for example 4.7k ohm. If so, heed that advice. The maximum length of an I2C bus is around one metre, and is a function of the capacitance of the bus. This distance can be extended with the use of a special IC, which we will examine during the next I2C chapter.

Avec un seul équipement I2C connecté avec l'Arduino, les résistances de tirage au plus (pull-up resistors) ne sont pas (normalement) requises, car le microcontrôleur ATmega328 de l'Arduino les intègre déjà. Si plusieurs équipements sont connectés au bus, utilisez deux résistances de 10 kΩ chacune. Comme souvent, ce sont les tests que vous effectuerez en prototypant un circuit sur une plaquette de câblage rapide que vous jugerez de leur nécessité. Parfois, dans les fiches techniques (data sheets), vous trouverez d'autres valeurs de résistance, du 4,7 kΩ par exemple. Dans ce cas, suivez les recommandations du constructeur.

La distance maximale maître-esclave pour un bus I2C est d'environ un mètre et dépend de plusieurs facteurs comme la capacité électrique du bus ou le débit de transmission. Cette distance peut être sensiblement augmentée en passant par des interfaces spécifiques (NDLR : un i2c-bus extender amplifie le courant débité par le broches SDA et SCL, ce qui augmente la portée du signal).

Each device can be connected to the bus in any order, and devices can be masters or slaves. In our Arduino situation, the board is the master and the devices on the I2C bus are the slaves. We can write data to a device, or read data from a device. By now you should be thinking “how do we differentiate each device on the bus?”… Each device has a unique address. We use that address in the functions described later on to direct our read or write requests to the correct device. It is possible to use two devices with identical addresses on an I2C bus, but that will be discussed in a later article.

Chaque équipement peut être connecté au bus dans n'importe quel ordre, et certains d'entre eux peuvent parfois passer du statut de maître à esclave et inversement. Dans notre cas précis, la carte Arduino sera dans la situation du maître et les composants connectés sur le bus I2C seront en situation d'esclave. De l'Arduino, on peut « écrire » sur la ligne pour envoyer des données vers un composant, ou « lire » la ligne pour récupérer les données retournées par celui-ci.

Se pose maintenant la question de savoir comment différentier chaque équipement connecté sur le bus. En fait, chacun d'entre eux possède une adresse unique. Cette adresse sera utilisée pour écrire ou lire les données vers le composant souhaité.

As like most devices, we make use of an Arduino library , in this case < wire.h >. Then use the function  Wire.begin(); inside of void setup() and we're ready to go.

Comme souvent, on inclura une bibliothèque Arduino, ici nommée Wire. Puis on commencera par l'initialiser avec Wire.begin() à l'intérieur du void setup().

III. Envoyer des données

Sending data from our Arduino to the I2C devices requires two things: the unique device address (we need this in hexadecimal) and at least one byte of data to send. For example, the address of the part in example 20.1 (below) is 00101111 (binary) which is 0X2F in hexadecimal. Then we want to set the wiper value, which is a value between 0 and 127, or 0x00 and 0x7F in hexadecimal. So to set the wiper to zero, we would use the following three functions:

« Écrire » sur la ligne pour envoyer des données de l'Arduino (maître) vers un composant I2C (esclave) requiert deux choses : l'adresse du composant qui doit être unique sur le bus, et le ou les octets de données à envoyer. Dans l'exemple 1Exemple 1 : potentiomètre numérique qui suit, l'adresse du composant est 0010 1111 en binaire, soit 0x2F en hexadécimal. On souhaite configurer la valeur du curseur (wiper) qui doit être comprise entre 0 et 127 (entre 0x00 et 0x7F en hexadécimal).

Par exemple, pour fixer la valeur du curseur à zéro, on procédera en trois temps :

 
Sélectionnez
Wire.beginTransmission(0x2F);      // part address is 0x2F or 0101111

This sends the device address down the SDA (data) line of the bus. It travels along the bus, and “notifies” the matching device that it has some data coming…

L'adresse est alors écrite sur la ligne de données SDA du bus, ce qui, en quelque sorte, va notifier le composant concerné que des données vont lui être adressées…

 
Sélectionnez
Wire.write(0); // sends 0 down the bus

This sends the byte of data to the device - into the device register (or memory of sorts), which is waiting for it with open arms. Any other devices on the bus will ignore this. Note that you can only perform one I2C operation at a time! Then when we have finished sending data to the device, we “end transmission”. This tells the device that we're finished, and frees up the I2C bus for the next operation:

L'octet de données est ensuite envoyé dans le registre du composant (une case mémoire en quelque sorte) qui l'attend à bras ouverts. Les autres composants sur le bus vont bien sûr ignorer cette donnée. Notez que vous pouvez n'effectuer qu'une seule opération à la fois sur le bus. Quand l'opération d'écriture sur la ligne est terminée, il faut « couper la transmission » afin de libérer la ligne pour l'opération de lecture/écriture suivante :

 
Sélectionnez
Wire.endTransmission();

NDLR : Les codes qui suivent supposent toujours que la transmission s'effectue correctement, mais si on veut aller plus loin, la méthode Wire.endTransmission() peut aussi retourner un code qui indique le statut de la transmission (si égal à 0, la transmission s'est bien déroulée).

NDLR : envoyer des données selon le protocole I2C

Image non disponible Image non disponible

La transmission commence par une condition START (tandis que la ligne SCL est à l'état HIGH, la ligne SDA bascule à l'état LOW). S'ensuivent les 7 bits de l'adresse du composant (Slave Address), puis un bit R/W qui vaut ici 0 pour indiquer une opération d'écriture sur la ligne SDA. Le composant adressé doit envoyer un bit d'acquittement (ACK pour Acknowledgement, selon les spécifications le composant esclave doit abaisser la ligne SDA pour acquitter de la bonne réception). Chaque octet de donnée écrit sur la ligne (ici deux) par le maître doit être acquitté par l'esclave. La transmission s'arrête en finissant par une condition STOP (tandis que la ligne SCL est à l'état HIGH, la ligne SDA bascule à l'état HIGH).

Image non disponible
Chronogramme - Opération d'écriture sur le bus I2C

La bibliothèque Wire, comme souvent avec les bibliothèques Arduino, vous offre une surcouche logicielle qui permet de vous affranchir des méandres bas niveau du protocole I2C, mais il est toujours bien d'avoir un aperçu de ce qui se dissimule sous le capot.

Some devices may have more than one register, and require more bytes of data in each transmission. For example, the DS1307 real-time clock IC has eight registers to store timing data, each requiring eight bits of data (one byte):

Certains composants I2C ont plusieurs registres, et requiert davantage d'octets de données à chaque transmission. Par exemple, l'horloge temps réel DS1307 a huit registres (donc huit octets) pour sauvegarder les données temporelles :

Image non disponible
Registres de données temporelles - Source : Maxim Integrated DS1307 data sheet

However with the DS1307  - the entire lot need to be rewritten every time. So in this case we would use eight wire.send(); functions every time. Each device will interpret the byte of data sent to it, so you need the data sheet for your device to understand how to use it.

Voir l'exemple 3Exemple 3 : horloge temps réel pour l'utilisation d'un DS1307.

IV. Recevoir des données

R e ceiving data from an I2C device into our Arduino requires two things: the unique device address (we need this in hexadecimal) and the number of bytes of data to accept from the device. Receiving data at this point is a two stage process. If you review the table above from the DS1307 data sheet, note that there is eight registers, or bytes of data in there. The first thing we need to do is have the I2C device start reading from the first register, which is done by sending a zero to the device:

Recevoir des données en provenance d'un composant I2C sur l'Arduino requiert deux choses : l'adresse du composant qui doit être unique sur le bus, et le nombre d'octets attendus en réception. La réception de données à ce point est un processus en plusieurs étapes. Si vous reprenez le tableau précédent extrait de la fiche technique (data sheet) du DS1307, vous noterez l'existence de huit registres, soit huit octets de données (NDLR : le DS1307 étant une horloge temps réel, on trouve dans l'ordre ‑ secondes, minutes, heures, jour, date, mois, année et un octet de contrôle). La première chose à faire est de demander au composant I2C d'être prêt à envoyer ses données en pointant vers le premier registre souhaité. Pour pointer vers le premier registre, on envoie un octet de valeur nulle au composant de la façon suivante :

 
Sélectionnez
Wire.beginTransmission(device_address);
Wire.write(0);
Wire.endTransmission();

Now the I2C device will send data from the first register when requested. We now need to ask the device for the data, and how many bytes we want. For example, if a device held three bytes of data, we would ask for three, and store each byte in its own variable (for example, we have three variables of type byte: a, b, and c . The first function to execute is:

À partir de là, le composant I2C retournera l'état de ses registres sous forme d'octets de données si on le lui demande. L'Arduino (le maître) doit alors demander combien d'octets il veut récupérer. Par exemple, pour récupérer les trois premiers octets correspondant aux (secondes ; minutes ; heures) du DS1307, l'Arduino devra écrire sur la ligne SDA afin de recquérir ces trois octets, puis lire la ligne SDA afin de stocker chaque octet retourné dans une variable.

On requiert trois octets avec la commande :

 
Sélectionnez
Wire.requestFrom(device_address, 3);

Which tells the device to send three bytes of data back to the Arduino. We then immediately follow this with:

Cette instruction est immédiatement suivie par la lecture consécutive des trois octets retournés par le composant I2C :

 
Sélectionnez
*a = Wire.read();
*b = Wire.read();
*c = Wire.read();

We do not need to use Wire.endTransmission() when reading data. Now that the requested data is in their respective variables, you can treat them like any ordinary byte variable. For a more detailed explanation of the I2C bus, read this explanatory document by NXP. Now let's use our I2C knowledge by controlling a range of devices…

Il n'est pas nécessaire d'utiliser Wire.endTransmission() après la lecture sur la ligne. Une fois que les données demandées sont récupérées dans leur variable respective, il ne reste plus qu'à les traiter.

NDLR : recevoir des données selon le protocole I2C

Image non disponible Image non disponible

La transmission commence par une condition START (tandis que la ligne SCL est à l'état HIGH, la ligne SDA bascule à l'état LOW). S'ensuivent 7 bits de l'adresse du composant (Slave Address), puis un bit R/W qui vaut ici 1 pour indiquer une opération de lecture sur la ligne SDA. Le composant adressé doit envoyer un bit d'acquittement (ACK pour Acknowledgement, selon les spécifications le composant esclave doit abaisser la ligne SDA pour acquitter de la bonne réception). Chaque octet de donnée retourné sur la ligne (ici deux) par l'esclave doit être acquitté par le maître. La transmission s'arrête en finissant par une condition STOP (tandis que la ligne SCL est à l'état HIGH, la ligne SDA bascule à l'état HIGH).

Image non disponible
Chronogramme - Opération de lecture sur le bus I2C

Après la théorie, la pratique avec quelques exemples d'utilisation de composants I2C…

V. Exemple 1 : potentiomètre numérique

The Microchip MCP4018T digital linear potentiometer. The value of this model is 10 kilo ohms. Inside this tiny, tiny SMD part is a resistor array consisting of 127 elements and a wiper that we control by sending a value of between 0 and 127 (in hexadecimal) down the I2C bus. This is a volatile digital potentiometer, it forgets the wiper position when the power is removed. However naturally there is a compromise with using such a small part, it is only rated for 2.5 milliamps - but used in conjunction with op amps and so on. For more information, please consult the data sheet . As this is an SMD part, for breadboard prototyping purposes it needed to be mounted on a breakout board. Here it is in raw form:

Le potentiomètre numérique linéaire MCP4018T de Image non disponibleMicrochip. Ce modèle est un potentiomètre numérique 10 kΩ. À l'intérieur de ce minuscule composant CMS (« Composant Monté en Surface »), un réseau de 127 résistances et un curseur (wiper) que l'on peut configurer en envoyant une valeur entre 0 et 127 grâce à une interface I2C. (NDLR : un rappel sur le principe de fonctionnement du bon vieux potar dans sa version mécanique). Sa mémoire est « volatile », c'est-à-dire que sa configuration est perdue lorsque le composant n'est plus alimenté. Il y a des limitations dans l'utilisation d'un tel composant, notamment la valeur du courant qui traverse les pattes A, B et W : 2,5 mA au maximum, mais vous pouvez compléter le circuit avec des amplis ops et autres composants. Pour davantage d'informations, consultez la fiche technique (data sheet) sur le site de Microchip. Comme le MCP4018T est un composant de type CMS (« Composant Monté en Surface »), pour pouvoir l'utiliser en prototypage sur une plaquette de câblage, il faut le monter sur un petit circuit imprimé adaptateur qui se présente comme suit sur la photo :

Image non disponible

Above the IC is a breakout board. Consider that the graph paper is 5mm square! It is the incorrect size, but all I have. However soldering was bearable. Put a drop of solder on one pad of the breakout board, then hold the IC with tweezers in one hand, and reheat the solder with the other hand - then push the IC into place. A few more tiny blobs of solder over the remaining pins, and remove the excess with solder wick. Well … it worked for me:

Sur la photo ci-dessus, les lignes sur la feuille forment des carreaux de 5 mm de côté. Le circuit adaptateur n'est pas tout à fait à la bonne taille, mais c'est tout ce que j'ai ! Toutefois, c'est acceptable pour faire les soudures. Faites fondre une goutte de métal sur une pastille de l'adaptateur, maintenez le composant à l'aide d'une petite pince d'une main puis de l'autre main réchauffez la soudure et faites glisser le composant au bon emplacement. Déposez quelques gouttes de métal pour souder les autres pattes, puis enlevez l'excédent, cela a bien marché pour moi :

Image non disponible

NDLR : si vous ne souhaitez pas vous lancer dans des opérations périlleuses de soudure de composants, vous trouverez des composants au fonctionnement similaire dans des boîtiers DIP qui peuvent être directement insérés sur une plaquette de câblage, sans soudure. Par exemple, le Maxim DS1803. Attention, ce n'est pas le même prix non plus !

Our example schematic is as follows:

Le schéma de câblage est le suivant :

Image non disponible

As you can see, the part is simple to use, your signal enters pin 6 and the result of the voltage division is found on pin 5. Please note that this is not a replacement for a typical mechanical potentiometer, we can't just hook this up as a volume or motor-speed control! Again, please read the data sheet .

Comme vous pouvez le constater, le composant est simple d'utilisation. La tension d'entrée est appliquée sur la broche 6 et le résultat de la division de tension est récupéré sur la broche 5. Notez que le composant ne remplacera pas tel quel un potentiomètre classique à fonctionnement mécanique pour, par exemple, faire varier la vitesse d'un moteur électrique. Une fois encore, consultez les caractéristiques du composant sur la fiche technique (data sheet).

Control is very simple, we only need to send one byte of data down, the hexadecimal reference point for the wiper, e.g.:

Configurer le composant est très simple, on a juste besoin d'envoyer un octet de donnée correspondant à la position du curseur (wiper) :

 
Sélectionnez
Wire.beginTransmission(0x2F);      // part address is 0x2F or 0101111b
Wire.write(0x3F); //
Wire.endTransmission();

Voici un court programme de démonstration où le curseur (wiper) prend toutes les valeurs possibles entre 0 et 127 :

Here is a quick demonstration that moves the wiper across all points:

 
Sélectionnez
// Example 20.1

int dt = 2000; // used for delay duration
byte rval = 0x00; // used for value sent to potentiometer
#include "Wire.h"
#define pot_address 0x2F // each I2C object has a unique bus address, the MCP4018 is 0x2F or 0101111 in binary

void setup()
{
  Wire.begin();
  Serial.begin(9600); 
}

void potLoop()
// sends values of 0x00 to 0x7F to pot in order to change the resistance
// equates to 0~127
{
  for (rval=0; rval<128; rval++)
  {
    Wire.beginTransmission(pot_address);
    Wire.write(rval); // 
    Wire.endTransmission();
    Serial.print(" sent - ");
    Serial.println(rval, HEX);
    delay(dt);
  }
}

void loop()
{
  potLoop();
}

 and a video demonstration:

Vidéo de démonstration :


Cliquez pour lire la vidéo


VI. Exemple 2 : capteur de température

Now we will read some data from an I2C device. Our test subject is the ST Microelectronics CN75 temperature sensor . Again, we have another SMD component, but the CN75 is the next stage larger than the part from example 20.1. Thankfully this makes the soldering process much easier, however still requiring some delicate handiwork:

Voici maintenant un exemple où nous allons lire les données en provenance d'un composant I2C. Il s'agit du capteur de température CN75 de chez ST Microelectronics. Ce composant de surface CMS est un peu plus grand que celui de l'exemple 1 précédent, ce qui rend le travail de soudure un peu plus facile bien qu'il reste assez délicat à réaliser à la main :

Image non disponible

First, a small blob of solder, then slide the IC into it. Once that has cooled, you can complete the rest and solder the header pins into the breakout board:

Le voici une fois soudé sur le circuit adaptateur :

Image non disponible

NDLR : là aussi vous trouverez des composants I2C aux fonctionnalités similaires en boîtier DIP et à insérer directement sur une plaquette de câblage, sans soudure. Par exemple, le Maxim DS1621.

Our example schematic is as follows:

Le schéma de câblage est le suivant :

Image non disponible

Pins 5, 6 and 7 determine the final three bits of the device address - in this case they are all set to GND, which sets the address to 1001000. This allows you to use multiple sensors on the same bus. Pin 3 is not used for basic temperature use, however it is an output for the thermostat functions, which we will examine in the next chapter.

Les pattes 5, 6 et 7 permettent de fixer les trois premiers bits de poids faible de l'adresse du composant. Comme elles sont reliées à la masse GND, l'adresse du composant est 0100 1000, soit 0x48 en hexadécimal. Ce système permet d'utiliser plusieurs capteurs de température sur le même bus. La patte 3 peut servir à envoyer un signal lors de franchissement de seuils de température (thermostat), mais ne sera pas utilisée dans notre exemple.

As a thermometer it can return temperatures down to the nearest half of a degree Celsius. Although that may not be accurate enough, it was designed for automotive and thermostat use. For more details please read the data sheet . The CN75 stores the temperature data in two bytes, let's call them A and B. So we use

Le capteur retourne la température au demi-degré Celsius le plus proche. Il n'est pas d'une grande précision, mais il a été conçu pour le secteur automobile ou des utilisations comme thermostat. Pour plus de détails, consultez la fiche technique (data sheet) sur le site de ST Microelectronics.

Après acquisition et conversion de la température, le CN75 stocke celle-ci dans un registre 16 bits (codage sur 9 bits), soit deux octets à requérir. On écrira donc :

 
Sélectionnez
Wire.requestFrom(cn75address, 2)

with the second parameter as 2, as we want two bytes of data. Which we then store using the following functions:

Puis on les récupère dans des variables de type byte avec deux opérations de lecture sur la ligne SDA :

 
Sélectionnez
*a = Wire.read(); // first received byte stored here
*b = Wire.read(); // second received byte stored here

where *a and *b are variables of the type byte. And as always, there is a twist to decoding the temperature from these bytes. Here are two example pieces of sample data:

Il faut maintenant jongler avec ces deux octets pour en extraire la valeur de la température en degrés Celsius. Voici deux exemples d'acquisition de données :

 
Sélectionnez
Example bytes one: 00011001 10000000
Example bytes two: 11100111 00000000
Image non disponible
Format du registre de température - Source : ST Microelectronics, STCN75 data sheet

The bits in each byte note particular values… the most significant bit (leftmost) of byte A determines whether it is below or above zero degrees - 1 for below zero. The remaining seven bits are the binary representation of the integer part of the temperature; if it is below zero, we subtract 128 from the value of the whole byte and multiply by -1. The most significant bit of byte B determines the fraction, either zero or half a degree. So as you will see in the following example sketch, there is some decision making done in showCN75data() :

Les huit bits TD1 à TD8 (HS byte) représentent la valeur de la partie entière de la température en complément à deux. Dans cette représentation binaire, TD8 est le bit de signe et vaut 1 si la température est négative. Dans ce dernier cas, il faut soustraire 128 à l'octet complet HS pour extraire la valeur absolue de la température. Le bit de poids fort TD0 de l'autre octet (LS byte) est à 1 s'il y a un demi-degré à prendre en compte dans la partie fractionnaire de la température. Vous retrouvez le détail des calculs dans la fonction showCN75data() du programme suivant :

 
Sélectionnez
// Example 20.2

#include "Wire.h"
#define cn75address 0x48 // with pins 5~7 set to GND, the device address is 0x48

void setup()
{
  Wire.begin(); // wake up I2C bus
  Serial.begin(9600);
}

void getCN75data(byte *a, byte *b)
{
  // move the register pointer back to the first register
  Wire.beginTransmission(cn75address); // "Hey, CN75 @ 0x48! Message for you"
  Wire.write(0); // "move your register pointer back to 00h"
  Wire.endTransmission(); // "Thanks, goodbye..."
  // now get the data from the CN75
  Wire.requestFrom(cn75address, 2); // "Hey, CN75 @ 0x48 - please send me the contents of your first two registers"
  *a = Wire.read(); // first received byte stored here
  *b = Wire.read(); // second received byte stored here
}

void showCN75data()
{
  byte aa,bb;
  float temperature=0;
  getCN75data(&aa,&bb);
  if (aa>127) // check for below zero degrees
  {
    temperature=((aa-128)*-1);
    if (bb==128) // check for 0.5 fraction
    {
      temperature-=0.5;
    }
  } 
  else // it must be above zero degrees
  {
    temperature=aa;
    if (bb==128) // check for 0.5 fraction
    {
      temperature+=0.5;
    }
  }
  Serial.print("Temperature = ");
  Serial.print(temperature,1);
  Serial.println(" degrees C");
  delay(1000);
}

void loop()
{
  showCN75data();
}

And here is the result from the serial monitor:

Et voilà un exemple de résultat obtenu affiché dans le Terminal Série :

Image non disponible

VII. Exemple 3 : horloge temps réel

Now that we know how to read and write data to devices on the I2C bus - here is an example of doing both, with a very popular device - the Maxim DS1307 real-time clock IC. Before moving on, consider reading their good data sheet.

Maintenant que nous avons vu comment lire et écrire des données sur un bus I2C, voici un nouvel exemple avec un composant très célèbre : l'horloge temps réel Maxim DS1307. Commencez par consulter sa fiche technique (data sheet).

Image non disponible

Furthermore, it also has a programmable square-wave generator. Connection and use is quite simple:

En outre, il possède un générateur de signaux carrés (SQW - Square-Wave Generator). Ce composant est simple à câbler et à utiliser.

Image non disponible

However some external components are required: a 32.768 kHz crystal, a 3V battery for time retention when the power is off, and a 10k ohm pullup resistor is required if using as a square-wave generator, and 10k ohm pull-up resistors on the SCL and SDA lines. You can use the SQW and timing simultaneously. If we have a more detailed look at the register map for the DS1307:

Cependant, il a besoin d'autres composants pour fonctionner : un quartz à 32,768 kHz, une pile 3 V pour maintenir les données sauvegardées lorsque l'alimentation est coupée, une résistance de tirage 10 kΩ (pull-up) si vous utilisez le générateur de signaux carrés (SQW) et deux autres résistance de tirage 10 kΩ (pull-up) pour les lignes SDA et SCL du bus I2C.

Une petite recherche Internet et vous trouverez de nombreux shields compatibles Arduino à base de DS1307 que vous n'avez plus qu'à insérer sur votre carte Arduino.

Ci-dessous, un tableau récapitulant les registres du DS1307 :

Image non disponible
Registres de données temporelles - Source : Maxim Integrated DS1307 data sheet

We see that the first seven registers are for timing data, the eighth is the square-wave control, and then another eight RAM registers. In this chapter we will look at the first eight only. Hopefully you have noticed that various time parameters are represented by less than eight bits of data - the DS1307 uses binary-coded decimal . But don't panic, we have some functions to do the conversions for us.

Les sept premiers registres sont réservés aux données temporelles : secondes, minutes, heures, etc. Le huitième permet de configurer le générateur SQW, puis suivent 56 registres de mémoire vive supplémentaire. Dans notre exemple, nous n'utiliserons que les huit premiers registres.

Vous noterez que pour certains registres, le codage ne se fait pas sur la totalité des huit bits. En fait, le DS1307 utilise le système Décimal codé binaire (ou BCD pour Binary Coded Decimal) pour coder les données numériques, mais nous emploierons des fonctions personnalisées pour faire la conversion en nombre décimal.

However, in general  - remember that each bit in each register can only be zero or one - so how do we represent a register's contents in hexadecimal? First, we need to find the binary representation, then convert that to hexadecimal. So, using the third register of the DS1307 as an example, and a time of 12:34 pm - we will read from left to right. Bit 7 is unused, so it is 0. Bit 6 determines whether the time kept is 12- or 24-hour time. So we'll choose 1 for 12-hour time. Bit 5 (when bit 6 is 0) is the AM/PM indicator - choose 1 for PM. Bit 4 represents the left-most digit of the time, that is the 1 in 12:34 pm. So we'll choose 1. Bits 3 to 0 represent the BCD version of 2 which is 0010.

Prenons par exemple le troisième registre à l'adresse 0x02, celui qui sauvegarde les heures, et supposons que l'horloge doit indiquer 12:34 PM. Le bit 7 est inutilisé, et est à zéro par défaut. Le bit 6 indique le format horaire, 12 ou 24 heures. Il doit être à 1 pour indiquer le format 12 heures. Le bit 5, lorsque le bit 6 est à 1 (choix du format 12 heures), indique la période, AM (avant midi) ou PM (après midi) - Ici, le bit 5 doit être à 1 pour indiquer la période PM. 12 = 1x10 + 2, donc dans le système Décimal codé binaire il faut mettre le bit 4 des dizaines à 1, puis coder le chiffre des unités avec les bits 0 à 3 restants, soit 0010 = 2.

So to store 12pm as hours we need to write 00110010 as hexadecimal into the hours register - which is 0x32. Reading data from the DS1307 should be easy for you now, reset the register pointed, then request seven bytes of data and receive them into seven variables. The device address is 0x68.  For example:

Ainsi, pour sauvegarder l'heure 12 PM, on doit stocker la valeur 0011 0010, soit 0x32 en hexadécimal, dans le registre des heures à l'adresse 0x02.

Vous connaissez le principe de récupération des données sur un bus I2C maintenant. Il faut pointer sur le premier registre souhaité, requérir les sept octets utiles et les lire un par un en les stockant dans autant de variables. L'adresse du composant est 0x68. Par exemple :

 
Sélectionnez
Wire.beginTransmission(0x68);
Wire.write(0);
Wire.endTransmission();
Wire.requestFrom(DS1307_I2C_ADDRESS, 7);
*second     = bcdToDec(Wire.read());
*minute     = bcdToDec(Wire.read());
*hour       = bcdToDec(Wire.read());
*dayOfWeek  = bcdToDec(Wire.read());
*dayOfMonth = bcdToDec(Wire.read());
*month      = bcdToDec(Wire.read());
*year       = bcdToDec(Wire.read());

At which point the time data will need to be converted to decimal numbers, which we will take care of in the example sketch later. Setting the time, or controlling the square-wave output is another long operation - you need to write seven variables to set the time or eight to change the square-wave output. For example, the time:

Après lecture des registres, les données BCD (Binary coded Decimal) sont converties en nombres décimaux avec la fonction bcdToDec().

Régler l'heure de l'horloge ou configurer le générateur de signaux carrés nécessite d'écrire dans les registres, par exemple pour régler l'heure :

 
Sélectionnez
Wire.beginTransmission(0x68);
Wire.write(0);
Wire.write(decToBcd(second));
Wire.write(decToBcd(minute));
Wire.write(decToBcd(hour));
Wire.write(decToBcd(dayOfWeek));
Wire.write(decToBcd(dayOfMonth));
Wire.write(decToBcd(month));
Wire.write(decToBcd(year));
Wire.endTransmission();

The decToBcd is a function defined in our example to convert the decimal numbers to BCD suitable for the DS1307.

La fonction decToBcd() convertie une valeur décimale dans le système BCD avant d'être envoyée dans les registres.

NDLR : curieusement, l'auteur ne fournit pas les codes des fonctions personnalisées bcdToDec() et decToBcd() . Google et les forums de Developpez.com sont vos amis…

You can also address each register individually. We will demonstrate doing this with an explanation of how to control the DS1037's in built square-wave generator:

Vous pouvez également vous adresser à chaque registre individuellement. Voici pour preuve un code commenté configurant le registre 0x07 du générateur de signaux carrés du DS1307 :

 
Sélectionnez
/*
DS1307 Square-wave machine
 Used to demonstrate the four different square-wave outputs from Maxim DS1307
 See page nine of data sheet for more information
 John Boxall - tronixstuff.com
 */

#include "Wire.h"
#define DS1307_I2C_ADDRESS 0x68 // each I2C object has a unique bus address, the DS1307 is 0x68

void setup()
{
 Wire.begin();
}

void sqw1() // set to 1Hz
{
 Wire.beginTransmission(DS1307_I2C_ADDRESS);
 Wire.write(0x07); // move pointer to SQW address
 Wire.write(0x10); // sends 0x10 (hex) 00010000 (binary)
 Wire.endTransmission();
}

void sqw2() // set to 4.096 kHz
{
 Wire.beginTransmission(DS1307_I2C_ADDRESS);
 Wire.write(0x07); // move pointer to SQW address 
 Wire.write(0x11); // sends 0x11 (hex) 00010001 (binary)
 Wire.endTransmission();
}

void sqw3() // set to 8.192 kHz
{
 Wire.beginTransmission(DS1307_I2C_ADDRESS);
 Wire.write(0x07); // move pointer to SQW address 
 Wire.write(0x12); // sends 0x12 (hex) 00010010 (binary)
 Wire.endTransmission();
}

void sqw4() // set to 32.768 kHz (the crystal frequency)
{
 Wire.beginTransmission(DS1307_I2C_ADDRESS);
 Wire.write(0x07); // move pointer to SQW address 
 Wire.write(0x13); // sends 0x13 (hex) 00010011 (binary)
 Wire.endTransmission();
}

void sqwOff()
// turns the SQW off
{
 Wire.beginTransmission(DS1307_I2C_ADDRESS);
 Wire.write(0x07); // move pointer to SQW address
 Wire.write(0x00); // turns the SQW pin off
 Wire.endTransmission();
}

void loop()
{
 sqw1();
 delay(5000);
 sqw2();
 delay(5000);
 sqw3();
 delay(5000);
 sqw4();
 delay(5000);
 sqwOff();
 delay(5000);
}

Here is the SQW output in action - we measure the frequency using my very old Tek CFC-250:

Voici la vidéo de démonstration mettant en œuvre le programme précédent. La fréquence sur la sortie SQW (Square-Wave Generator) est mesurée avec un bon vieux compteur de fréquence Tek CFC-250 :


Cliquez pour lire la vidéo


For further DS1307 examples, I will not repeat myself and instead direct you to the list of many tronixstuff articles that make use of the DS1307 .

VIII. Conclusion

So there you have it - hopefully an easy to understand introduction to the world of the I2C bus and how to control the devices within. Part two of the I2C tutorial has now been published , as well as an article about the NXP SAA1064 LED display driver IC  and the Microchip MC23017 16-bit port expander IC .

Nous espérons vous avoir fait découvert le fonctionnement du bus I2C et que vous pourrez désormais interfacer plus facilement les composants utilisant ce bus dans vos projets Arduino.

IX. Sitographie

X. Notes de la Rédaction Developpez.com

Cet article est la traduction du tutoriel écrit par John Boxall et intitulé Arduino and the I2C bus. Retrouvez la version originale de cet article ainsi que les autres chapitres consacrés à Arduino sur le site tronixstuff.

Nous remercions les membres de la Rédaction de Developpez.com pour le travail de traduction et de relecture qu'ils ont effectué, en particulier :

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

  

Licence Creative Commons
Le contenu de cet article est rédigé par John Boxall et est mis à disposition selon les termes de la Licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 3.0 non transposé.
Les logos Developpez.com, en-tête, pied de page, css, et look & feel de l'article sont Copyright © 2013 Developpez.com.