i2c (Uno & ATTiny85) : Quelle approche/protocole adopter ?
Bonjour,
Désolé si mon titre est un peu vague, je ne sais pas trop comment exprimer mon problème.
En gros, j'essaye de faire communiquer un Arduino Uno avec un ATTiny85 via i2c.
L'Uno est maître, le tiny est esclave.
Sur le tiny, j'utilise TinyWire comme bibliothèque.
Sur l'Uno, celle de base.
Ma problématique est la suivante : jusqu'à quel point je dois envisager les échecs de communication entre les deux µC ?
J'ai déjà fait des programmes qui communiquent en TCP via socket pour échanger des trames de longueur arbitraire (entre autre des logs), et pour distinguer quand je devais m'arrêter de lire, nous avions convenue d'un protocole maison.
Grossièrement, les 4 premiers octet servaient à transmettre la longueur du message.
J'ai voulu appliquer le même principe, mais il semblerait que ce ne soit pas possible entièrement.
Quand mon Uno communique avec mon tiny et qu'il attends une réponse de 3 byte, j'utilise "Wire.requestFrom(I2C_ADRESS_SLAVE, 3)".
Le hic, c'est que si je fais un delay dans mon tiny ou si je n'envoie absolument rien sur l'i2c, la fonction me renvoie quand même 3, la fonction "Wire.available()" aussi (comme si j'avais bien écrit ces 3 bytes alors que pas du tout) et les 3 "Wire.read()" me renvoie 255.
Comment suis-je supposer aborder cette problématique ?
Dois-t-on partir du principe que tant que la connexion i2c est active, alors il n'y a aucune erreur possible ?
Que si je suis supposé avoir X bytes à lire, alors ils sont forcement là ?
Si mon code peut aider à voir si j'ai fait une erreur, le voici :
Fichier commun "Configuration.h"
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
#define I2C_ADRESS_SLAVE 0x00 /* Adresse i2c pour la communication avec l'esclave */
/* "header" est une variable de type "Byte" : c'est la première valeur reçu lors d'une transmission i2c
* Les 3 octets de poids faible sont le nombre de byte à lire après le header
* Les 5 octets suivant (de poids fort) sont l'ordre */
#define I2C_CREATE_HEADER(nb_bytes_to_send, order) ((nb_bytes_to_send & 7) + (order & 31) * 8)
#define I2C_HEADER_GET_NB_BYTES_TO_READ(header) (header & 7)
#define I2C_HEADER_GET_ORDER(header) (header / 8)
#define I2C_ORDER_ASK_NUMBER 0 // On demande le nombre
// 0 bytes transmit après
#define I2C_ORDER_GIVE_NUMBER 1 // On transmet le nombre
// 2 bytes transmis après |
Fichier du Uno (maitre) :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
| #include "Configuration.h"
#include "Wire.h"
bool button1State; // Etat du bouton
void setup()
{
// Initialisation i2c
Wire.begin(); // On rejoint i2c en tant que maitre
// Bouton
pinMode(A1, INPUT_PULLUP);
// DEBUG
Serial.begin(9600); //Initialisation de la communication avec la console
button1State = HIGH; // Boutton non pressé
}
void loop()
{
int etat = digitalRead(A1);
if (etat != button1State) {
// Je passe de HIGH (buton non appuyé) à LOW (bouton appuyé)
if (etat == LOW) {
// Transmission a l'esclave
Wire.beginTransmission(I2C_ADRESS_SLAVE);
Wire.write(I2C_CREATE_HEADER(I2C_ORDER_ASK_NUMBER, 0));
Wire.endTransmission();
// Ici, on devrait normalement attendre 500ms avant d'avoir la vrai réponse (voir code du tiny)
Serial.print("Wire.requestFrom = ");
Serial.print(Wire.requestFrom(I2C_ADRESS_SLAVE, 3));
Serial.print("\n");
Serial.print("Wire.available = ");
Serial.print(Wire.available());
Serial.print("\n");
Serial.print("1 Wire.read() = ");
Serial.print(Wire.read());
Serial.print("\n");
Serial.print("2 Wire.read() = ");
Serial.print(Wire.read());
Serial.print("\n");
Serial.print("3 Wire.read() = ");
Serial.print(Wire.read());
Serial.print("\n");
}
button1State = etat;
}
} |
Fichier du tiny (esclave) :
Code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137
| #include "Configuration.h"
#include "TinyWireS.h"
/* ATTiny85 (Clock 20MHz)
+-\/-+
A0 PB5 1| |8 Vcc
A3 PB3 2| |7 PB2 D0 A1
A2 PB4 3| |6 PB1 D1
GND 4| |5 PB0 D2
+----+
*/
#define __AVR_ATtiny85__
// On déclare l'adresse de la LED de debug sur la pin 2
#define LED1_PIN PB3
// Simple compteur de test ...
byte count;
void setup()
{
// On rejoint le bus i2c en tant qu'esclave
TinyWireS.begin(I2C_ADRESS_SLAVE);
// A but de test
pinMode(LED1_PIN, OUTPUT);
count = 0;
}
void loop()
{
// Si on recoit quelque chose sur le bus I2C
if (TinyWireS.available()) {
byte header; // Premier bit : il s'agit du header (composé du nombre de byte à lire (3 bit) + du numero d'ordre (5 bit)
byte order; // Numero d'ordre : il s'agit d'une action que le µC dois faire
byte nbByteToRead;
// On recupere le header
header = TinyWireS.read();
// On recupere l'ordre et le nombre de byte à lire
nbByteToRead = I2C_HEADER_GET_NB_BYTES_TO_READ(header);
order = I2C_HEADER_GET_ORDER(header);
// On execute l'ordre envoyé
switch (order) {
case I2C_ORDER_ASK_NUMBER:
i2cDiscardRead(nbByteToRead, 100); // nbByteToRead devrait être égale à 0
delay(500); // Simulation d'un probleme de transmission : "normalement", le maitre i2c devrait "voir" que je n'ai pas encore fais les TinyWireS.write
TinyWireS.write(I2C_CREATE_HEADER(2, I2C_ORDER_GIVE_NUMBER));
TinyWireS.write(0);
TinyWireS.write(count);
// Limitation de count dans l'intervalle [0:100]
++count;
if (100 < count) {
count = 0;
}
break;
default:
// Ordre non pris en charge : On consumme tout
i2cDiscardRead(nbByteToRead, 100);
break;
}
}
}
#define DELAY_BETWEEN_READ_TENTATIVE 5 /* en milliseconde */
/* +------------------------------------------------------------------------------------+ */
/* | Tente de lire `nbReadToDiscard` byte pendant `timeout` millisecondes | */
/* +------------------------------------------------------------------------------------+ */
/* | [IN] Timeout : nombre de milliseconde avant d'être considéré en timeout | */
/* | [RETURN] true si nbReadToDiscard lu avant timeout, false sinon | */
/* +------------------------------------------------------------------------------------+ */
bool i2cDiscardRead(int nbReadToDiscard, int timeout)
{
for (int i = 0; i < nbReadToDiscard; ++i) {
if (!i2cRead(NULL, timeout)) {
Blink(LED1_PIN, 1000); // DEBUG (en cas d'erreur, led allumé pendant 1s)
return (false);
}
}
Blink(LED1_PIN, 20); // DEBUG
return (true);
}
/* +------------------------------------------------------------------------------------+ */
/* | Tente de lire un byte pendant `timeout` millisecondes | */
/* +------------------------------------------------------------------------------------+ */
/* | [OUT] result : byte lu | */
/* | [IN] Timeout : nombre de milliseconde avant d'être considéré en timeout | */
/* | [RETURN] true si byte lu avant timeout, false sinon | */
/* +------------------------------------------------------------------------------------+ */
bool i2cRead(byte *result, int timeout)
{
/* Attente de timeout milliseconde max */
while (!TinyWireS.available()) {
delay(DELAY_BETWEEN_READ_TENTATIVE);
timeout -= DELAY_BETWEEN_READ_TENTATIVE;
if (timeout <= 0) {
return (false);
}
}
/* Lecture */
if (result) {
result = TinyWireS.read();
} else {
TinyWireS.read();
}
return (true);
}
/* +------------------------------------------------------------------------------------+ */
/* | Blink une led via delay : pour but de test uniquement | */
/* +------------------------------------------------------------------------------------+ */
/* | [IN] led : port de la led | */
/* | [IN] duration : temps d'eclairage en millisecondes | */
/* +------------------------------------------------------------------------------------+ */
void Blink(byte led, int duration)
{
digitalWrite(led, HIGH);
delay(duration);
digitalWrite(led, LOW);
delay(duration);
} |
Le chef c'est le chef, les autres obéissent
Bonjour,
En fait, malgré la déclaration du mode esclave pour le Tiny, il travaille ou essaie de travailler en mode maître.
Il suffit de regarder l'exemple fourni avec TinyWireS pour voir dans le setup les deux déclaration des fonctions appelées par le maître selon qu'il envoie des données ou qu'il en demande :
Code:
1 2
| TinyWireS.onReceive(receiveEvent);
TinyWireS.onRequest(requestEvent); |
Ce sont ces deux fonctions et non le loop() du Tiny qui géreront les réceptions et les demandes de données. Comme elles n'existent pas, il ne se passe rien (en informatique : n'importe quoi).
Je propose de s'inspirer de l'exemple fourni avec TinyWireS.
Salutations