TD5: Communication avec un accéléromètre

Dans ce TD, nous allons brancher un accéléromètre à notre carte Arduino Uno et communiquer avec. Nous allons mettre en oeuvre l'ensemble des points abordés dans le cours pour cela.

Capteur

Nous allons travailler sur un accéléromètre.

Que mesure ce capteur, et dans quelle unité ?

Donnez des exemples d'applications de ce capteur.

Branchements

À l'aide des datasheets, déterminer les branchements à réaliser entre l'accéléromètre (pensez à consulter le Pin mapping UNO/ATmega328P)

Demandez à votre enseignant de valider vos branchements avant de passer à la suite.

Communication I2C

Réglage de la bitrate

Dans la datasheet du MMA8451, chercher la valeur maximale de la bitrate pour la communication I2C.

Écrivez le code d'une fonction i2c_init() qui initialise la communication I2C à la bitrate maximale.

Vous utiliserez les registres TWBR et TWSR pour cela. Cf page 180 de la datasheet de l'ATmega328P.

Primitives I2C

Comme vu dans le cours, une transaction I2C est composée de plusieurs étapes:

  1. Envoi de la condition de START
  2. Envoi de l'adresse de l'esclave suivi du bit R/W (0 pour écrire, 1 pour lire)
  3. Envoi des données
  4. Envoi de la condition de STOP ou d'une nouvelle condition de START (ce qu'on appelle un repeated START)

Nous vous fournissons les primitives suivantes:

uint8_t i2c_wait() {
    while (!(TWCR & (1 << TWINT)))
        ;

    return TWSR & 0xF8;
}

uint8_t i2c_start() {
    TWCR = (1 << TWINT) | (1 << TWSTA) | (1 << TWEN);
    return i2c_wait();
}

uint8_t i2c_stop() { 
    TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWSTO);
}

uint8_t i2c_send(uint8_t data) {
    TWDR = data;
    TWCR = (1 << TWINT) | (1 << TWEN);
    return i2c_wait();
}

uint8_t i2c_receive(uint8_t ack) {
    if (ack) {
        TWCR = (1 << TWINT) | (1 << TWEN) | (1 << TWEA);
    } else {
        TWCR = (1 << TWINT) | (1 << TWEN);
    }
    i2c_wait();
    return TWDR;
}

À l'aide de la datasheet de l'ATmega328P, répondez aux questions suivantes:

  • À quoi sert la fonction i2c_wait()?
  • Quelle est la valeur de retour de la fonction i2c_wait()?
  • À quoi sert le masque 0xF8 dans la fonction i2c_wait()?
  • À quoi sert l'argument ack de la fonction i2c_receive()? (dans quel cas faut-il envoyer un ACK?)
  • À quoi sert le bit TWEA dans le registre TWCR?
  • À quel niveau logique un ACK est-il envoyé sur le bus I2C?

Transactions I2C

Nous allons tout d'abord déclarer une structure i2c_message qui représente le contenu d'une transaction I2C:

struct i2c_message {
    uint8_t read; // Lecture ?
    char *data; // Données à transmettre
    uint8_t size; // Taille des données (en octets)
};

Implémentez la fonction suivante:

void i2c_transfer(uint8_t addr, struct i2c_message *messages, uint8_t size)
{
}

Qui transfère l'ensemble des messages contenus dans le tableau messages au périphérique I2C d'adresse addr.

La fonction i2c_transfer() doit donc pour chaque message:

  • Envoyer la condition de START
  • Envoyer l'adresse de l'esclave suivi du bit R/W
  • Envoyer les données à partir du champ data de la structure i2c_message OU recevoir les données dans le champ data de la structure i2c_message
  • Envoyer la condition de STOP si il n'y a pas d'autres messages à envoyer

Communication avec l'accéléromètre

Lecture du registre WHO_AM_I

En lisant la datasheet du MMA8451, déterminer l'adresse I2C de l'accéléromètre.

Toujours à l'aide de la datasheet, déterminer l'adresse et la valeur attendue du registre WHO_AM_I

Écrivez maintenant la fonction:

uint8_t mma8451_read_byte(uint8_t register_addr)
{
}

Qui utilise la fonction i2c_transfer() pour lire un octet à l'adresse register_addr de l'accéléromètre.

Testez votre fonction en lisant le registre WHO_AM_I de l'accéléromètre et en vérifiant que la valeur retournée est bien celle attendue en l'envoyant sur le port série.

Démarrage des mesures

Au démarrage, l'accéléromètre ne prend pas de mesure (cf page 11). Lisez le registre SYSMOD pour vérifier que l'accéléromètre est bien en mode STANDBY.

Implémentez maintenant la fonction:

void mma8451_write_byte(uint8_t register_addr, uint8_t data)
{
    // TODO
}

Qui utilise la fonction i2c_transfer() pour écrire un octet data à l'adresse register_addr

Écrivez la valeur adéquate dans le registre CTRL_REG1 pour démarrer les mesures à
la fréquende de 100Hz.

Lisez le registre SYSMOD pour vérifier que l'accéléromètre est bien en mode ACTIVE.

Lecture des mesures

  • Dans quels registres du MMA8451 sont stockées les mesures ?
  • Comment sont-elles encodées ?
  • Quelle est la conversion entre la mesure numérique et l'unité physique ?

Nous représenterons une mesure par la structure suivante:

struct mma8451_data {
    int16_t x;
    int16_t y;
    int16_t z;
};

Écrivez la fonction:

struct mma8451_data mma8451_read()
{
    // TODO
}

Qui déclenche une lecture I2C afin d'obtenir les mesures de l'accéléromètre, et les retourne sous la forme d'une structure mma8451_data.

Gestion des interruptions

Au lieu de lire les mesures à la demande, nous allons utiliser les interruptions afin d'être notifié lorsque de nouvelles mesures sont disponibles.

Nous allons brancher la broche INT0 de l'ATmega328P sur la broche INT1 de l'accéléromètre.
Déterminez le branchement nécessaire.

À l'aide de la datasheet de l'ATmega328P, notamment la section 12, paramétrez l'interruption INT0 pour qu'elle soit déclenchée sur un front descendant.

À l'aide des registres CTRL_REG4 et CTRL_REG5 de l'accéléromètre, paramétrez l'interruption INT1 pour qu'elle soit déclenchée lorsque de nouvelles mesures sont disponibles.

Attention: les registres CTRL_REG4 et CTRL_REG5 sont vérouillés lorsque le capteur est en mode ACTIVE. Il faut donc le mettre en mode STANDBY avant de pouvoir les modifier.

Dans la fonction d'interruption, déclenchez une lecture I2C afin d'obtenir les mesures.

Mesure du temps de réponse

Paramétrez le Timer1 de l'ATmega328P afin qu'un incrément de 1 correspond à 4µs.

Combien de temps faudrait t-il pour que le timer déborde si on ne le réinitialise pas ?

Combien de temps est supposé durer une mesure ?

À l'aide de TCNT1, mesurez le temps entre deux interruptions et affichez le sur le port série.

Correction