Title

Autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et dolore feugait

Utilisez le MPU6050 sur Raspberry-Pi

MPU-6050 connecté à un Raspberry-Pi sur le bus i2c-1.

Voici comment utiliser le MPU6050 sur Raspberry-Pi ou sur tout système Linux embarqué. Les MPU-6050 & MPU-6000 sont des composants intéressants proposés par Invensense. Ils disposent d’un accéléromètre, d’un gyroscope et d’une sonde de température. Il est possible d’y connecter des composants i2c supplémentaires et surtout de leur fournir une fréquence d’échantillonnage et de lire les données utiles depuis une FIFO à intervalles réguliers. C’est un bon choix pour faire du traitement du signal.

Il existe des librairies pour Arduino, un pilote dans les sources officielles de Linux. J’ai besoin de bien comprendre son fonctionnement, j’ai donc décidé de l’étudier. Pour cela j’ai obtenu sa Register map, je l’ai connecté sur le bus i2c-1 de mon Raspberry-pi et j’ai joué avec i2cget & i2cset.

MPU6050 sur Raspberry-Pi avec i2cget/i2cset

Configuration du bus i2c sur le Raspberry-pi

Pour connecter l’accéléromètre au bus i2c du Raspberry-Pi. Effectuez les connexions suivantes:

  • Reliez la PIN 3V3 (Pin 1) du Raspberry-Pi au VCC du MPU6050
  • Reliez la pin GND (Pin 39) au GND du MPU6050
  • Reliez la Pin I2C1 SDA (Pin 3 ) au SDA du MPU6050
  • Reliez la Pin SCL (Pin 5) au SCL du MPU6050

Assurez-vous que /dev/i2c-1 existe bien sur votre Raspberrry-Pi. Sinon, consultez notre article sur le bus i2c du Raspberry-Pi.

L’adresse par défaut du MPU6050 est 0x68, vous pouvez tester qu’il répond correctement en lui demandant son adresse (registre WHO_AM_I).

i2cget -y 1 0x68 0x75
0x68 # Cela confirme ce qu'on savait déjà !

Lecture de donnés d’accélération sur du MPU6050 sur Raspberry-Pi

Registre d’alimentation PWR_MGMT_1

Pour pouvoir lire les registres de l’accéléromètre, il faut commencer par gérer la partie alimentation du MPU-60X0 (registre PWR_MGMT_1, 0x6B ). On fait en sorte que le chip ne soit pas en sommeil et que la mesure de température soit activée. Il est déconseillé d’utiliser l’oscillateur interne, donc on va utiliser la PLL de l’axe X du gyroscope. Donc on écrit 0x01 dans ce registre:

i2cset -y 1 0x68 0x6B 0x01
mpu-60X0 accelerometer range
copyright: Invensense (MPU6050 register map)

Registre de configuration de l’accéléromètre ACCEL_CONFIG

Ici nous souhaitons simplement donner la plage de mesure de l’accéléromètre, voici les options possibles :

Dans le cadre de cette démonstration, nous allons utiliser la plage la plus petite de 2g.

i2cset -y 1 0x68 0x1C 0x00

Lecture de l’axe Z de l’accéléromètre ACCEL_ZOUT_x

À présent, nous allons lire l’axe Z de l’accéléromètre, il est perpendiculaire au composant, c’est le plus facile à manipuler. Les données sont lisibles sur 2 registres 0x3F (bits de poids fort) et 0x40 (bits de poids faible). En tenant la composant à l’horizontale, composant vers le haut pour mesurer l’accélération de la gravité, je fais une mesure en lisant les 2 octets en une seule lecture :

i2cget -y 1 0x68 0x3F w
0x6845

L’affichage de i2cget inverse les bits de poids fort et faible, le vrai résultat est donc 0x4568.

Dans la datasheet, il est indiqué que les donnés sont sur 16 bits en complément à 2, le résultat en décimal est de 17768. Soit 1,08g ( il y a 16384 LSB par g).

Configuration du Sample rate avec les registres SMPRT_DIV & CONFIG

Les données sont copiées vers les registres publics à une certaine fréquence. Il n’y à pas de signal extérieur utilisable pour la synchronisation. De même on force le filtrage à 0 :

i2cset -y 1 0x68 0x1A 0x00 # CONFIG

Maintenant, on peut configurer le sample rate divider (SMPRT_DIV). Nous souhaitons un sample rate de 1kHz. D’après la datasheet cela revient à faire 8kHz / (1+SMPLRT_DIV). Nous allons donc écrire 7 dans SMPLRT_DIV :

i2cset -y 1 0x68 0x19 0x07 # Pour 1 kHz - 8kHz /(1 + 7)
i2cset -y 1 0x68 0x19 0x4F # Pour 100Hz - 8kHz /(1 + 79)

Configuration de la FIFO interne avec FIFO_EN et USER_CTRL

Le MPU-6050 dispose d’une FIFO interne de 1024 octets, elle est utile lorsqu’on veut lire une donnée avec une fréquence d’échantillonnage élevée. Il faut commencer par activer cette FIFO (et y faire un RESET au passage) :

i2cset -y 1 0x68 0x6A 0x44

Nous allons activer la copie des données de l’accéléromètre dans la FIFO:

i2cset -y 1 0x68 0x23 0x08 # Pour lire les donnés de l'accéléromètre
i2cset -y 1 0x68 0x23 0x80 # Pour lire à la place les donnés de température

À présent les données de l’accéléromètre sont copiées dans la FIFO. Il y a 6 bytes pour les 3 axes de l’accéléromètre. Comme la FIFO fait 1024 bytes ca nous donne environ 170 mesures (1024/6) avant de remplir la FIFO. Soit 170 millisecondes avec notre fréquence d’échantillonnage de 1 kHz.

Lecture des donnés de la FIFO du MPU6050

Plusieurs registres peuvent nous aider à lire les données de la FIFO, à commencer par FIFO_COUNT_L et FIFO_COUNT_H (0x72 et 0x 73) :

i2cget -y 1 0x68 0x72 w # on lit en une seule fois les 2 registres
0x0004 # soit 0x0400 (i2cget inverse les bits) soit 1024, la fifo est pleine. Logique nous n'avons encore rien lu à ce stade.

Maintenant il est possible de lire le contenu de la FIFO, octet par octet :

i2cget -y 1 0x68 0x74

À ce stade, il est certain qu’utiliser i2cget n’est plus suffisant et qu’il faut utiliser un langage de programmation tel que C ou C++. Pour augmenter la fréquence échantillonnage il sera également nécessaire d’augmenter la vitesse du bus i2c ou bien de passer à SPI.

Augmenter la vitesse du bus i2c du Raspberry-pi

La vitesse maximale de fonctionnement du Bus i2c du MPU6050 est de 400kHz. Par défaut la fréquence de fonctionnement du bus i2c du Raspberry-pi est de 100kHz, il est donc nécessaire d’augmenter la vitesse de fonctionnement du bus pour améliorer les performances. Pour y parvenir, modifiez le contenu du fichier « /boot/config.txt » du Raspberry-Pi et ajoutez-y :

dtparam=i2c_arm_baudrate=400000

Je suis parvenu à augmenter la vitesse jusqu’à 2MHz, cela fonctionne avec ce composant, mais ce ne sera peut-être pas le cas pour tous. Redémarrez le Raspberry-Pi pour appliquer la modification.

Écrire un programme en C pour communiquer avec le MPU6050

Il existe un driver pour le MPU6050 dans les sources officielles de Linux. Malheureusement je ne peux pas l’utiliser, car il n’est pas optimisé pour mon cas d’utilisation. Je n’ai besoin que des données d’accélération, mais j’en ai besoin avec une fréquence élevée. Contrairement au driver qui lit toutes les informations (gyroscope, température, accélération).

J’ai donc écrit un petit programme un programme en C qui fait exactement ce que nous avons fait avec i2cget/i2cset. Le Noyau Linux fournit une interface de programmation i2c pour l’espace utilisateur. Vous pouvez le récupérer simplement ici sur github.

Ce programme lit les données d’accélération et de température à une fréquence de 1000Hz et nécessite d’augmenter significativement la vitesse de fonctionnement du bus.

/*
 * This file is an MPU6050 demonstration.
 * https://openest.io/en/2020/01/21/mpu6050-accelerometer-on-raspberry-pi/
 * Copyright (c) 2020 Julien Grossholtz - https://openest.io.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <linux/i2c-dev.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <stdint.h>

#define MPU6050_I2C_ADDR 0x68

#define REG_ACCEL_ZOUT_H 0x3F
#define REG_ACCEL_ZOUT_L 0x40
#define REG_PWR_MGMT_1 0x6B
#define REG_ACCEL_CONFIG 0x1C
#define REG_SMPRT_DIV 0x19
#define REG_CONFIG 0x1A
#define REG_FIFO_EN 0x23
#define REG_USER_CTRL 0x6A
#define REG_FIFO_COUNT_L 0x72
#define REG_FIFO_COUNT_H 0x73
#define REG_FIFO 0x74
#define REG_WHO_AM_I 0x75

int file = -1;

// Please note, this is not the recommanded way to write data
// to i2c devices from user space.
void i2c_write(__u8 reg_address, __u8 val) {
	char buf[2];
	if(file < 0) {
		printf("Error, i2c bus is not available\n");
		exit(1);
	}

	buf[0] = reg_address;
	buf[1] = val;

	if (write(file, buf, 2) != 2) {
		printf("Error, unable to write to i2c device\n");
		exit(1);
	}

}

// Please note, this is not thre recommanded way to read data
// from i2c devices from user space.
char i2c_read(uint8_t reg_address) {
	char buf[1];
	if(file < 0) {
		printf("Error, i2c bus is not available\n");
		exit(1);
	}

	buf[0] = reg_address;

	if (write(file, buf, 1) != 1) {
		printf("Error, unable to write to i2c device\n");
		exit(1);
	}


	if (read(file, buf, 1) != 1) {
		printf("Error, unable to read from i2c device\n");
		exit(1);
	}

	return buf[0];

}

uint16_t merge_bytes( uint8_t LSB, uint8_t MSB) {
	return  (uint16_t) ((( LSB & 0xFF) << 8) | MSB);
}

// 16 bits data on the MPU6050 are in two registers,
// encoded in two complement. So we convert those to int16_t
int16_t two_complement_to_int( uint8_t LSB, uint8_t MSB) {
	int16_t signed_int = 0;
	uint16_t word;

	word = merge_bytes(LSB, MSB);

	if((word & 0x8000) == 0x8000) { // negative number
		signed_int = (int16_t) -(~word);
	} else {
		signed_int = (int16_t) (word & 0x7fff);
	}

	return signed_int;
}

int main(int argc, char *argv[]) {
	int adapter_nr = 1; /* probably dynamically determined */
	char bus_filename[250];
	char accel_x_h,accel_x_l,accel_y_h,accel_y_l,accel_z_h,accel_z_l,temp_h,temp_l;
	uint16_t fifo_len = 0;
	int16_t x_accel = 0;
	int16_t y_accel = 0;
	int16_t z_accel = 0;
	int16_t temp = 0;
	float x_accel_g, y_accel_g, z_accel_g, temp_f;

	snprintf(bus_filename, 250, "/dev/i2c-1", adapter_nr);
	file = open(bus_filename, O_RDWR);
	if (file < 0) {
		/* ERROR HANDLING; you can check errno to see what went wrong */
		exit(1);
	}


	if (ioctl(file, I2C_SLAVE, MPU6050_I2C_ADDR) < 0) {
		/* ERROR HANDLING; you can check errno to see what went wrong */
		exit(1);
	}

	i2c_write(REG_PWR_MGMT_1, 0x01);
	i2c_write(REG_ACCEL_CONFIG, 0x00);
	i2c_write(REG_SMPRT_DIV, 0x07);
	i2c_write(REG_CONFIG, 0x00);
	i2c_write(REG_FIFO_EN, 0x88);
	i2c_write(REG_USER_CTRL, 0x44);

	while(1) {
		accel_x_h = i2c_read(REG_FIFO_COUNT_L);
		accel_x_l = i2c_read(REG_FIFO_COUNT_H);
		fifo_len = merge_bytes(accel_x_h,accel_x_l);

		if(fifo_len == 1024) {
			printf("fifo overflow !\n");
			i2c_write(REG_USER_CTRL, 0x44);
			continue;
		}

		if(fifo_len >= 8) {
			accel_x_h = i2c_read(REG_FIFO);
			accel_x_l = i2c_read(REG_FIFO);
			accel_y_h = i2c_read(REG_FIFO);
			accel_y_l = i2c_read(REG_FIFO);
			accel_z_h = i2c_read(REG_FIFO);
			accel_z_l = i2c_read(REG_FIFO);
			temp_h = i2c_read(REG_FIFO);
			temp_l= i2c_read(REG_FIFO);

			x_accel= two_complement_to_int(accel_x_h,accel_x_l);
			x_accel_g = ((float) x_accel)/16384;

			y_accel= two_complement_to_int(accel_y_h,accel_y_l);
			y_accel_g = ((float) y_accel)/16384;

			z_accel= two_complement_to_int(accel_z_h,accel_z_l);
			z_accel_g = ((float) z_accel)/16384;

			temp = two_complement_to_int(temp_h, temp_l);
			temp_f = (float)temp/340 + 36.53; // calculated as described in the MPU60%) register map document

			printf("x_accel %.3fg	y_accel %.3fg	z_accel %.3fg	temp=%.1fc         \r", x_accel_g, y_accel_g, z_accel_g, temp_f);
		} else {
			usleep(10000);
		}

	}

	return 0;
}

Julien Grossholtz

Expert en logiciel embarqué, j'accompagne les entreprises dans la création de leurs objets intelligents et leurs IoT.

Leave a Reply