I2C (5 points)

L'I2C est un bus de communication très utilisé dans l'embarqué. Il permet généralement de connecter des micro-contrôleurs à des périphériques simples (flash, capteurs...).

Physiquement le bus I2C est composé de deux fils : un fil de données (SDA) et un fil d'horloge (SCL). Il s'agit d'un protocole série synchrone, les données étant transmises en série (chaque bit l'un après l'autre) sur le fil de données, au rythme de l'horloge SCL.

Les entités connectés sur le bus I2C peuvent se comporter en maître ou en esclave. Les transferts sont initiés uniquement par un maître, vers une autre entité qui se comportera en esclave. L'horloge est généré par le périphérique maître.

Chaque périphérique esclave a une adresse sur 7 bits (dans la version d'I2C qui nous intéresse).

Au niveau le plus bas, I2C permet à un maître d'envoyer un ou plusieurs octets vers un esclave (écriture) ou de lire un ou plusieurs octets depuis un esclave (lecture).

Écriture

Maître  :  START | ADRESSE (7 bits) / 0 (Write) |     | DATA (8 bits) |     | DATA (8 bits) |     | STOP
Esclave :        |                              | ACK |               | ACK |               | ACK | 

Lecture

Maître  :  START | ADRESSE (7 bits) / 1 (Read) |            | ACK |            | NACK | STOP
Esclave :        |                             | DATA (8 b) |     | DATA (8 b) |      |

START (début d'échange) et STOP (fin d'échange) sont des conditions particulières du signal SDA par rapport à l'horloge. Tout le reste (y compris ACK, acquittement positif et NACK, acquittement négatif) sont des bits échangés au rythme de l'horloge.

Restart

Il est possible, et cela sera nécessaire pour parler à l'accéléromètre, d'enchaîner deux échanges, en particulier une écriture et une lecture en supprimant le STOP à la fin du premier échange, le deuxième START étant appelé dans ce cas dans les documents un RESTART.

Travail à faire

Dans un fichier approprié, vous allez écrire les fonctions de base pour manipuler le bus I2C et faire des échanges simples qui nous permettrons par la suite de communiquer avec l'accéléromètre.

Dans notre cas, le micro-contrôleur sera maître sur le bus I2C et nous utiliserons des adresses I2C de 7 bits.

Identification de l'I2C et des IO nécessaires

❎ Identifiez, à partir des documents fournis, le bus I2C (il y en a plusieurs) et les pins auxquels l'accéléromètre LSM6DSL est relié. Indiquez vos réponses en commentaires en début du fichier dans lequel vous allez mettre les fonctions ci-dessous.

Indice : partez de la documentation de la carte, puis des schémas électroniques.

Initialisation de l'I2C

❎ Écrivez une fonction i2c_init qui initialise le bus I2C auquel est relié l'accéléromètre. Cette fonction doit notamment :

  • Configurer correctement les pins nécessaires (SDA et SCL) pour que le micro-contrôleur et l'accéléromètre puissent dialoguer en I2C
  • Configurer l'horloge du contrôleur I2C (plusieurs sources d'horloge sont possibles, cf. registre RCC_CCIPR, on choisira l'horloge PCLK)
  • Initialiser le contrôleur I2C en suivant la procédure décrite figure 393 (p. 1272 du manuel de référence)
    • Les champs NOSTRETCH, ANFOFF et DNF peuvent rester dans leurs valeurs par défaut après reset
    • Concernant les timings I2C, on calculera les différentes valeurs demandées du registre I2C_TIMINGR à partir des données suivantes :
      • L'horloge du contrôleur I2C I2CCLK est PCLK qui est à 80 MHz (soit une période de 12,5 ns)
      • Choisissez une valeur de PRESC intelligente pour avoir une période t_PRESC facile pour les générer les timings suivants
      • t_SCLL = 5 µs
      • t_SCLH = 5 µs
      • t_SCLDEL = 1 µs
      • t_SDADEL = 500 ns
      • Ces valeurs sont conformes à ce qu'attend l'accéléromètre et donnent une fréquence d'horloge I2C (SCL) de l'ordre de 100 kHz.

Transferts de données

❎ Écrivez une fonction void i2c_write(uint8_t saddr, const uint8_t *data, uint8_t num, uint8_t stop)

Cette fonction prend les arguments suivants :

  • saddr : adresse du périphérique esclave I2C concerné par le transfert (7 bits)
  • data : pointeur vers les données à transmettre au périphérique
  • num : nombre d'octets à transmettre (num <= 255)
  • stop : si 0, ne pas envoyer la condition STOP à la fin du transfert des données

Cette fonction envoie (écriture) num octets (maximum 255) situés à l'adresse data vers le périphérique I2C dont l'adresse est saddr. Si stop vaut 0, la condition STOP n'est pas transmise permettant de générer un RESTART lors d'un transfert suivant immédiatement.

Indice : on pourra s'inspirer du texte page 1288 et de la figure 407 page suivante. La branche de gauche du schéma (NACKF) ne vous intéresse pas.

Note : on regardera attentivement la description du champ SADD.

❎ Écrivez une fonction void i2c_read(uint8_t saddr, uint8_t *data, uint8_t num, uint8_t stop)

Cette fonction prend les arguments suivants :

  • saddr : adresse du périphérique esclave I2C concerné par le transfert (7 bits)
  • data : pointeur vers l'adresse où stocker les données reçues
  • num : nombre d'octets à recevoir (num <= 255)
  • stop : si 0, ne pas envoyer la condition STOP à la fin du transfert des données

Cette fonction reçoit (lecture) num octets (maximum 255) et les place à l'adresse data depuis le périphérique I2C dont l'adresse est saddr. Si stop vaut 0, la condition STOP n'est pas transmise permettant de générer un RESTART lors d'un transfert suivant immédiatement.

Indice : on pourra s'inspirer du texte de la page 1292 et de la figure 410 page suivante.