Auteur Sujet: Bibliothèque Wire I2C  (Lu 2507 fois)

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Bibliothèque Wire I2C
« le: janvier 27, 2016, 10:36:09 pm »
Bonjour

Décidément, le site locoduino est vraiment très vivant, avec des articles publiés ou mis à jour très régulièrement.
J'y jette un oeil avec plaisir presque tous les jours.
Bravo et merci

Aujourd'hui j'ai lu l'article sur la bibliothèque Wire.
Cela m'a fait penser que j'ai du dépiauter cette lib récemment (pour mon module contrôleur d'aiguilles) et que j'ai vu quelques trucs complémentaires qui pourraient être utiles à partager. Alors les voici tels que je les ai compris

Tout d'abord, que l'on soit maître ou esclave, émetteur ou récepteur, cette lib s'appuie sur des buffer alloués de manière statique en RAM : un pour l'émission, un pour la réception. Ceux-ci ont une taille de 32 octets. Cette limite n'est pas imposée par le protocole I2C, mais à moins de modifier la bibliothèque, c'est une limite de taille de message à prendre en compte.

Côté maître émetteur :
- beginTransmission() ne fait rien d'autre qu'enregistrer en RAM l'adresse du destinataire
- write() ne fait rien d'autre qu'alimenter en RAM le buffer d'émission
- tout se passe lors du endTransmission() : c'est uniquement lors de l'exécution de cette fonction qu'a lieu la communication physique sur le bus

Côté maître récepteur :
- tout se passe lors du requestFrom(), qui retourne le nombre d'octets effectivement chargés dans le buffer de réception, une fois la communication physique terminée.
- available(), read() ou peek() ne font que piocher dans le buffer de RAM
Du coup, personnellement je trouve la fonction available() assez inutile, et préfère coder des trucs du style
if (requestFrom(esclave, 5) == 5)
{
  for (uint8_t i=0; i<5; i++) reponse[i] = Wire.read();
  ...traiter la réponse
}
else
{
  ... traiter l'erreur
}

Côté esclave : même gestion via deux buffers en RAM
Le handler onReceive() est appelé une fois la communication physique terminée, pour exploiter les octets du buffer. Là encore, l'usage de available() me semble superflu car le nombre d'octets reçus est passé directement en paramètre d'appel du handler.
(bigre au passage, je réalise que ce terme handler fait bizarre, au sein d'articles aussi attachés au respect de la langue française  :P)

Le handler onRequest() est plus subtil, car c'est le seul qui intervient entre deux étapes de la communication physique.
Le but est alimenter le buffer d'émission via la fonction write().

Côté esclave, je me suis pris la tête sur deux points :

1) d'abord ces handler sont déclenchés par des interruptions, c'est-à-dire au beau milieu de n'importe quel endroit du programme.
Et dès lors que l'information échangée fait plus d'un octet, il y a quelques précautions à prendre.
Par exemple en réception, il ne faudrait pas aller mettre à jour des variables globales pile au moment où une partie du programme était en train de les utiliser. Surtout si ces variables ont des liens de corrélation entre elles. Cela peut générer un comportement inattendu du programme.

Pareil en émission : par exemple, si le message émis contient les valeurs de 2 variables, et que l'interruption se déclenche au moment où le programme vient de mettre à jour la première variable mais pas encore la seconde, cela peut entraîner l'émission d'un message incohérent.

Du coup, je préfère passer systématiquement par des buffers de cloisonnement, entre les routines liées aux interruptions et le programme principal.

2) l'impossibilité par l'esclave de connaître le nombre d'octets attendus par le maître
Là pour le coup, la bibliothèque Wire n'y est pour rien. C'est le protocole I2C qui veut ça.
Le paramètre de quantité passé à requestFrom() reste au niveau du maître, qui interrompt la communication en provenance de l'esclave lorsqu'il a reçu la quantité d'octets attendue.
C'est bien dommage, car lorsqu'on souhaite que l'esclave puisse retourner plusieurs types de message différents, on ne peut pas utiliser le nombre d'octets attendus comme élément discriminant.
Il n'y a que deux options :
a) mettre en place un protocole logiciel. Par exemple le maître commence par envoyer une information discriminante, puis lance un requestFrom(). Les périphériques I2C industriels marchent d'ailleurs souvent comme ça.
b) partir du principe que l'esclave renvoie toutes les informations possibles à chaque sollicitation, en mettant les plus importantes en premier, charge au maître de couper quand bon lui semble.

Pour mon contrôleur d'aiguilles, je suis parti sur cette dernière option, la plus simple à première vue, mais je le regrette.
Cela oblige à faire tous les write dans le handler onRequest (avec la limite de 32 octets), car à ce moment là on ne sait pas encore combien d'octets le maître va réellement lire.
Mais au final, je me suis retrouvé dans l'embarras quand j'ai ajouté un dernier octet de CRC antiparasite.
Parce que là, même si le maître ne s'intéresse qu'aux premiers octets du message, il doit quand même tout lire afin de vérifier le CRC.
Pour bien faire, il faudrait soit segmenter le message avec un CRC à la fin de chaque segment, afin que le maître puisse interrompre la communication par endroits. Soit repartir sur l'option a) avec une petite couche de protocole logiciel.

Dominique

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 1722
  • 100% Arduino et N
    • Voir le profil
Re : Bibliothèque Wire I2C
« Réponse #1 le: janvier 27, 2016, 11:08:09 pm »
Bonsoir,

Je viens de faire une petite modification de l'article sur la bibliothèque I2C et ta contribution est interessante et bien complémentaire, pour les experts .

Ce serait bien de créer un petit exemple qui met en oeuvre des échanges de messages entre 2 Arduino.

Quand tu parles de buffer de cloisonnement, s'agit-il de buffer cycliques ou circulants (c'est ce que j'avais fait dans mon précédent réseau).

Cordialement

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Re : Bibliothèque Wire I2C
« Réponse #2 le: janvier 27, 2016, 11:35:06 pm »
Autre point : l'utilisation d'écrans LCD I2C présente quelques inconvénients potentiels dont il faut peut-être se méfier ici

D'abord, selon les modèles, ils ont tendance à tirer du courant sur la ligne 5V.
Il vaut mieux avoir une bonne alim, sinon cela peut nuire à la transmission des données sur le bus I2C, quand il est partagé avec d'autres périphériques.

Ensuite, si on regarde le protocole mis en oeuvre, c'est un gaspillage de bits : une vraie usine à gaz.
Le module standard que l'on trouve comme interface I2C ressemble à ceci :

Ne pas hésiter à retirer le cavalier pour mettre une alim 5V dédiée.

D'un coté, il est soudé aux bornes du contrôleur LCD standard hitachi HD44780.
De l'autre, il est relié au bus I2C.
Entre les deux, il y a un bête PCF8574.

Le LCD est commandé en mode 4 bits (demi-octet).
Chaque demi-octet doit être transmis 3 fois au PCF8574 : une première fois pour présenter le demi-octet au contrôleur, les secondes et troisièmes fois pour réaliser l'impulsion sur l'entrée Enable du contrôleur.
Un caractère à afficher => deux demi-octets => 2x3=6 transmissions au PCF8574

Une transmission au PCF8574 = 1 octet d'adresse + 1 octet de donnée = environ 20 bits incluant start/stop/ack

Donc pour un seul caractère à afficher on envoie environ 120 bits sur le bus I2C.
Cela donne un rapport de 1 à 15, entre l'information utile (un octet) et l'information transmise (120 bits).
Pas génial. Je continue :

Le bus I2C tourne en standard à 100 kHz, cela nous fait 1,2 ms de temps de transmission par caractère à afficher.

Ecran 4 lignes x 20 caractères => 100 ms pour afficher 80 caractères.
Et encore je ne compte pas les petits delay inutiles qui se cachent souvent dans les bibliothèques LCD I2C, ni les besoins de positionnement de curseur ou pire : l'utilisation de caractères personnalisés.

Au final, on peut facilement se retrouver avec un truc qui crache des bits en pagaille sur le bus pendant plus d'un dizième de seconde, vers un autre truc qui pompe trop de jus sur l'alim 5V, le tout dans un environnement sensible et parasité lié au modélisme ferroviaire.

Il y a une solution alternative bien plus intéressante : remplacer le PCF8574 par une arduino pro mini fonctionnant en esclave I2C.
L'arduino pro mini peut commander le controleur hitachi en mode 8 bits et gérer l'impulsion sur enable, de manière bien plus rapide et efficace.
La quantité d'information transmise sur le bus I2C est alors divisée par 10.
« Modifié: janvier 27, 2016, 11:37:25 pm par bricoleau »

bricoleau

  • Jr. Member
  • **
  • Messages: 51
    • Voir le profil
Re : Re : Bibliothèque Wire I2C
« Réponse #3 le: janvier 28, 2016, 12:06:19 am »
Quand tu parles de buffer de cloisonnement, s'agit-il de buffer cycliques ou circulants (c'est ce que j'avais fait dans mon précédent réseau).

Arf désolé mon CAF (Coefficient d'Acceptation Féminin) ne m'autorise plus de contribution significative pour aujourd'hui  ;D

Il s'agit plutôt de zones tampon :
dans le sens émission : alimentées systématiquement à chaque loop() à partir de données stables, pour être utilisées par le handler onRequest()
dans le sens réception : alimentées par le handler onReceive(), pour prise en compte dans loop() entre deux traitements principaux.

voir par exemple le code , chercher le handler emettreI2C() puis remonter la pelote.

a+

Thierry

  • Global Moderator
  • Hero Member
  • *****
  • Messages: 502
    • Voir le profil
Re : Bibliothèque Wire I2C
« Réponse #4 le: janvier 28, 2016, 10:33:03 am »
Soit repartir sur l'option a) avec une petite couche de protocole logiciel.

Je me demande quand même si le mieux ne serait pas de faire une nouvelle bibliothèque NewWire intégrant tes idées et simplifiant/sécurisant l'accès à la communication I2C maître ou esclave...