InterfaceGPS 0.1.0
Interface embarquée Qt pour navigation, multimédia, caméra et télémétrie
Chargement...
Recherche...
Aucune correspondance
mpu9250source.cpp
Aller à la documentation de ce fichier.
1
10#include "mpu9250source.h"
11#include "telemetrydata.h"
12#include <QDebug>
13#include <cmath>
14
15// Les bibliothèques système Linux sont isolées pour que Windows ne plante pas à la compilation
16#ifdef Q_OS_LINUX
17#include <fcntl.h>
18#include <unistd.h>
19#include <sys/ioctl.h>
20#include <linux/i2c-dev.h>
21#endif
22
24 : QObject(parent), m_data(data), m_fileDescriptor(-1) {
25 m_timer = new QTimer(this);
26 connect(m_timer, &QTimer::timeout, this, &Mpu9250Source::readSensor);
27}
28
32
34#ifdef Q_OS_LINUX
35 qDebug() << "Tentative d'ouverture du bus I2C-1...";
36 m_fileDescriptor = open("/dev/i2c-1", O_RDWR);
37 if (m_fileDescriptor < 0) {
38 qWarning() << "Échec de l'ouverture du bus I2C.";
39 return;
40 }
41
42 // Connexion à l'adresse I2C du MPU9250
43 ioctl(m_fileDescriptor, I2C_SLAVE, 0x68);
44
45 // Sortie du mode veille (Wake up)
46 char config[2] = {0x6B, 0x00};
47 write(m_fileDescriptor, config, 2);
48
49 // Configuration des échelles de mesure
50 config[0] = 0x1C; config[1] = 0x00; write(m_fileDescriptor, config, 2); // Accéléromètre : +/- 2g
51 config[0] = 0x1B; config[1] = 0x00; write(m_fileDescriptor, config, 2); // Gyroscope : 250 dps (degrés par seconde)
52
53 // Activation du filtre passe-bas matériel (Digital Low Pass Filter - DLPF) à 41Hz
54 // Essentiel pour lisser les vibrations mécaniques du véhicule
55 config[0] = 0x1A; config[1] = 0x03; write(m_fileDescriptor, config, 2); // Gyroscope DLPF
56 config[0] = 0x1D; config[1] = 0x03; write(m_fileDescriptor, config, 2); // Accéléromètre DLPF
57
58 // --- Auto-calibration du gyroscope ---
59 // Le gyroscope dérive naturellement. On calcule son erreur initiale au repos.
60 qDebug() << "Calibration du gyroscope en cours (maintenir le capteur immobile)...";
61 float gyroSum[3] = {0, 0, 0};
62 int samples = 400;
63 char reg = 0x43; // Registre de départ des données gyroscopiques
64 char dataG[6];
65
66 for (int i = 0; i < samples; i++) {
67 write(m_fileDescriptor, &reg, 1);
68 if (read(m_fileDescriptor, dataG, 6) == 6) {
69 // Conversion des valeurs brutes en radians par seconde (rad/s)
70 gyroSum[0] += (int16_t)((dataG[0] << 8) | dataG[1]) * (250.0f / 32768.0f) * (M_PI / 180.0f);
71 gyroSum[1] += (int16_t)((dataG[2] << 8) | dataG[3]) * (250.0f / 32768.0f) * (M_PI / 180.0f);
72 gyroSum[2] += (int16_t)((dataG[4] << 8) | dataG[5]) * (250.0f / 32768.0f) * (M_PI / 180.0f);
73 }
74 usleep(5000); // Échantillonnage toutes les 5ms (total 2 seconde)
75 }
76 m_gyroBias[0] = gyroSum[0] / samples;
77 m_gyroBias[1] = gyroSum[1] / samples;
78 m_gyroBias[2] = gyroSum[2] / samples;
79 qDebug() << "Calibration terminée. Biais du gyroscope calculés.";
80
81 // --- Activation du mode Bypass pour le Magnétomètre (AK8963) ---
82 // Le magnétomètre a sa propre adresse I2C (0x0C) accessible via le MPU
83 config[0] = 0x37; config[1] = 0x02; write(m_fileDescriptor, config, 2);
84 ioctl(m_fileDescriptor, I2C_SLAVE, 0x0C);
85
86 // Configuration du magnétomètre : 16 bits de résolution, mode continu (100Hz)
87 config[0] = 0x0A; config[1] = 0x16; write(m_fileDescriptor, config, 2);
88 qDebug() << "Acquisition MPU9250 démarrée avec succès.";
89
90#else
91 qWarning() << "[MODE SIMULATION] - Code compilé sous Windows/Autre. Le matériel I2C est bypassé.";
92#endif
93
94 m_elapsedTimer.start();
95 m_timer->start(100); // Boucle de lecture à 10Hz
96}
97
98void Mpu9250Source::readSensor() {
99 // ------------------------------------------------------------------------
100 // 0. VÉRIFICATION ET TEMPS ÉCOULÉ
101 // ------------------------------------------------------------------------
102
103 // Le filtre de Madgwick a besoin de savoir exactement combien de temps s'est
104 // écoulé depuis le dernier calcul pour déduire de combien on a tourné
105 // (Vitesse angulaire * temps = Angle).
106 // On prend le temps en millisecondes et on divise par 1000 pour l'avoir en secondes (dt).
107 float dt = m_elapsedTimer.restart() / 1000.0f;
108
109#ifdef Q_OS_LINUX
110 // Si le capteur n'est pas bien connecté ou initialisé, on annule tout.
111 if (m_fileDescriptor < 0) return;
112
113 // ------------------------------------------------------------------------
114 // 1. LECTURE DE L'ACCÉLÉROMÈTRE ET DU GYROSCOPE
115 // ------------------------------------------------------------------------
116
117 // On cible le composant principal (Accel + Gyro) à l'adresse I2C 0x68.
118 ioctl(m_fileDescriptor, I2C_SLAVE, 0x68);
119
120 // On demande à lire à partir de la "case mémoire" (registre) 0x3B.
121 // C'est là que commencent les données de l'accéléromètre.
122 char reg = 0x3B;
123
124 // On va lire 14 octets d'un coup pour gagner du temps :
125 // - 6 octets pour l'accéléromètre (X, Y, Z)
126 // - 2 octets pour le capteur de température interne
127 // - 6 octets pour le gyroscope (X, Y, Z)
128 char dataAG[14];
129
130 if (write(m_fileDescriptor, &reg, 1) == 1 && read(m_fileDescriptor, dataAG, 14) == 14) {
131
132 // --- DÉCODAGE DE L'ACCÉLÉROMÈTRE ---
133 // Le capteur envoie les données "découpées" en morceaux de 8 bits (1 octet).
134 // Mais nos mesures font 16 bits ! Il faut recoller les morceaux.
135 // On prend le premier octet (High), on le décale de 8 zéros vers la gauche (<< 8),
136 // puis on "fusionne" avec le deuxième octet (Low) grâce à l'opérateur "OU" logique (|).
137 // Ensuite, on convertit la valeur brute (-32768 à +32767) en 'g' (gravité).
138 // Comme on a configuré le capteur sur +/- 2g max, on divise 2 par 32768.
139 float ax = (int16_t)((dataAG[0] << 8) | dataAG[1]) * (2.0f / 32768.0f);
140 float ay = (int16_t)((dataAG[2] << 8) | dataAG[3]) * (2.0f / 32768.0f);
141 float az = (int16_t)((dataAG[4] << 8) | dataAG[5]) * (2.0f / 32768.0f);
142
143 // --- DÉCODAGE DU GYROSCOPE ---
144 // On fait le même "recollage" de bits (octets 8 à 13, car 6 et 7 c'était la température).
145 // L'échelle est de 250 dps (degrés par seconde) maximum, donc on multiplie par (250 / 32768).
146 // IMPORTANT : Les mathématiques (Madgwick) fonctionnent en Radians, pas en Degrés !
147 // On multiplie donc par (Pi / 180) pour faire la conversion en Radians/seconde.
148 // Enfin, on soustrait l'erreur (le biais) qu'on a mesurée automatiquement au démarrage.
149 float gx = ((int16_t)((dataAG[8] << 8) | dataAG[9])) * (250.0f / 32768.0f) * (M_PI / 180.0f) - m_gyroBias[0];
150 float gy = ((int16_t)((dataAG[10] << 8) | dataAG[11])) * (250.0f / 32768.0f) * (M_PI / 180.0f) - m_gyroBias[1];
151 float gz = ((int16_t)((dataAG[12] << 8) | dataAG[13])) * (250.0f / 32768.0f) * (M_PI / 180.0f) - m_gyroBias[2];
152
153 // ------------------------------------------------------------------------
154 // 2. LECTURE DU MAGNÉTOMÈTRE (LA BOUSSOLE)
155 // ------------------------------------------------------------------------
156
157 // Le magnétomètre est une puce séparée (AK8963) située à l'adresse 0x0C.
158 ioctl(m_fileDescriptor, I2C_SLAVE, 0x0C);
159
160 char st1;
161 char regSt1 = 0x02; // Registre d'état du magnétomètre
162 write(m_fileDescriptor, &regSt1, 1);
163
164 // On lit l'état. Le bit 0 (st1 & 0x01) nous dit "Data Ready".
165 // Le magnétomètre est plus lent (100Hz max), on vérifie donc s'il a une nouvelle info à donner.
166 if (read(m_fileDescriptor, &st1, 1) == 1 && (st1 & 0x01)) {
167
168 char regMag = 0x03; // Adresse du début des données magnétiques
169 char dataM[7]; // 6 octets pour (X,Y,Z) + 1 octet de statut final obligatoire
170 write(m_fileDescriptor, &regMag, 1);
171
172 if (read(m_fileDescriptor, dataM, 7) == 7) {
173 // ATTENTION PIÈGE : Contrairement à l'accéléromètre, le magnétomètre envoie
174 // le "Low byte" (petits bits) AVANT le "High byte" (gros bits).
175 // On inverse donc l'ordre dans le calcul (dataM[1] << 8 | dataM[0]).
176
177 // On applique ensuite la calibration :
178 // - Hard Iron (soustraction du biais) : annule les champs magnétiques du châssis.
179 // - Soft Iron (multiplication par l'échelle) : redonne une forme parfaitement ronde au signal.
180 float mx = ((int16_t)((dataM[1] << 8) | dataM[0]) - m_magBias[0]) * m_magScale[0];
181 float my = ((int16_t)((dataM[3] << 8) | dataM[2]) - m_magBias[1]) * m_magScale[1];
182 float mz = ((int16_t)((dataM[5] << 8) | dataM[4]) - m_magBias[2]) * m_magScale[2];
183
184 // ------------------------------------------------------------------------
185 // 3. FUSION DE DONNÉES (FILTRE DE MADGWICK)
186 // ------------------------------------------------------------------------
187
188 // Les axes physiques de la puce magnétomètre ne sont pas alignés dans le même
189 // sens que ceux du gyroscope/accéléromètre sur la carte électronique.
190 // On doit les réaligner pour utiliser le standard NED (Nord-Est-Bas).
191 // C'est pour ça qu'on croise des axes (my à la place de mx) et qu'on inverse des signes.
192 madgwickUpdate(-ax, ay, az, gx, -gy, -gz, my, -mx, mz, dt);
193
194 // ------------------------------------------------------------------------
195 // 4. CALCUL DU CAP COMPENSÉ EN INCLINAISON (TILT-COMPENSATED YAW)
196 // ------------------------------------------------------------------------
197
198 // Extraction du Roulis (Roll) et Tangage (Pitch) depuis le Quaternion.
199 // Cela nous dit comment la voiture est penchée par rapport à la gravité terrestre.
200 float roll = std::atan2(2.0f * (q[0] * q[1] + q[2] * q[3]), 1.0f - 2.0f * (q[1] * q[1] + q[2] * q[2]));
201 float pitch = std::asin(2.0f * (q[0] * q[2] - q[3] * q[1]));
202
203 // On projette les vecteurs magnétiques 'my' et '-mx' sur le plan horizontal (sol)
204 // en utilisant le roulis et le tangage. Cela garantit que la boussole pointe
205 // toujours le Vrai Nord, même si la voiture monte une pente raide.
206 float mag_x_horiz = my * std::cos(pitch) + (-mx) * std::sin(roll) * std::sin(pitch) - mz * std::cos(roll) * std::sin(pitch);
207 float mag_y_horiz = (-mx) * std::cos(roll) + mz * std::sin(roll);
208
209 // On calcule l'angle par rapport au Nord Magnétique à partir de cette projection
210 float yaw = std::atan2(mag_y_horiz, mag_x_horiz);
211
212 // Conversion des Radians vers les Degrés.
213 // On ajoute un signe MOINS (-) car les maths tournent en sens anti-horaire,
214 // mais la carte Qt tourne dans le sens horaire (comme une vraie boussole).
215 float heading = -yaw * (180.0f / M_PI);
216
217 // Ajout de la Déclinaison Magnétique pour pointer vers le Nord Géographique (Vrai Nord).
218 // (2.0° correspond environ à la France actuelle, ajustez si besoin).
219 heading += 2.0f;
220
221 // On s'assure que l'angle reste un cercle parfait de 0 à 360°.
222 if (heading < 0) heading += 360.0f;
223 if (heading >= 360.0f) heading -= 360.0f;
224
225 // ------------------------------------------------------------------------
226 // 5. FILTRE DE LISSAGE (PASSE-BAS VISUEL)
227 // ------------------------------------------------------------------------
228
229 // On mélange 90% de l'ancien cap avec 10% du nouveau.
230 // Cela empêche la carte de faire des micro-tremblements à l'écran à cause des vibrations.
231 static float smoothedHeading = heading;
232
233 // Gestion du passage difficile entre 359° et 0° pour éviter que la carte ne fasse
234 // un tour complet sur elle-même lors du passage du Nord.
235 float diff = heading - smoothedHeading;
236 if (diff > 180.0f) smoothedHeading += 360.0f;
237 else if (diff < -180.0f) smoothedHeading -= 360.0f;
238
239 smoothedHeading = (smoothedHeading * 0.90f) + (heading * 0.10f);
240
241 // On remet la valeur lissée proprement entre 0 et 360°.
242 if (smoothedHeading >= 360.0f) smoothedHeading -= 360.0f;
243 else if (smoothedHeading < 0.0f) smoothedHeading += 360.0f;
244
245 // Affichage console pour diagnostiquer
246 qDebug() << " Cap :" << smoothedHeading << "° | dt:" << dt;
247
248 // On envoie cet angle tout propre à l'interface graphique (QML) pour faire tourner la carte !
249 if (m_data) m_data->setHeading(static_cast<double>(smoothedHeading));
250 }
251 }
252 } else {
253 qDebug() << "Erreur de lecture I2C sur l'adresse 0x68 (Capteur débranché ou indisponible).";
254 }
255#else
256 // Empêche le compilateur Windows d'afficher un avertissement pour la variable non utilisée
257 Q_UNUSED(dt);
258#endif
259}
260
269void Mpu9250Source::madgwickUpdate(float ax, float ay, float az, float gx, float gy, float gz, float mx, float my, float mz, float dt) {
270 float q1 = q[0], q2 = q[1], q3 = q[2], q4 = q[3];
271 float norm;
272 float hx, hy, _2bx, _2bz;
273 float s1, s2, s3, s4;
274 float qDot1, qDot2, qDot3, qDot4;
275
276 // Variables auxiliaires pour optimiser les performances (éviter les calculs répétitifs)
277 float _2q1mx; float _2q1my; float _2q1mz; float _2q2mx;
278 float _4bx; float _4bz;
279 float _2q1 = 2.0f * q1; float _2q2 = 2.0f * q2; float _2q3 = 2.0f * q3; float _2q4 = 2.0f * q4;
280 float _2q1q3 = 2.0f * q1 * q3; float _2q3q4 = 2.0f * q3 * q4;
281 float q1q1 = q1 * q1; float q1q2 = q1 * q2; float q1q3 = q1 * q3; float q1q4 = q1 * q4;
282 float q2q2 = q2 * q2; float q2q3 = q2 * q3; float q2q4 = q2 * q4;
283 float q3q3 = q3 * q3; float q3q4 = q3 * q4; float q4q4 = q4 * q4;
284
285 // 1. Normalisation du vecteur d'accélération (Gravité)
286 norm = std::sqrt(ax * ax + ay * ay + az * az);
287 if (norm == 0.0f) return; // Sécurité contre la division par zéro
288 norm = 1.0f/norm;
289 ax *= norm; ay *= norm; az *= norm;
290
291 // 2. Normalisation du vecteur magnétique (Nord)
292 norm = std::sqrt(mx * mx + my * my + mz * mz);
293 if (norm == 0.0f) return;
294 norm = 1.0f/norm;
295 mx *= norm; my *= norm; mz *= norm;
296
297 // 3. Calcul de la direction de référence du champ magnétique
298 _2q1mx = 2.0f * q1 * mx;
299 _2q1my = 2.0f * q1 * my;
300 _2q1mz = 2.0f * q1 * mz;
301 _2q2mx = 2.0f * q2 * mx;
302 hx = mx * q1q1 - _2q1my * q4 + _2q1mz * q3 + mx * q2q2 + _2q2 * my * q3 + _2q2 * mz * q4 - mx * q3q3 - mx * q4q4;
303 hy = _2q1mx * q4 + my * q1q1 - _2q1mz * q2 + _2q2mx * q3 - my * q2q2 + my * q3q3 + _2q3 * mz * q4 - my * q4q4;
304 _2bx = std::sqrt(hx * hx + hy * hy);
305 _2bz = -_2q1mx * q3 + _2q1my * q2 + mz * q1q1 + _2q2mx * q4 - mz * q2q2 + _2q3 * my * q4 - mz * q3q3 + mz * q4q4;
306 _4bx = 2.0f * _2bx;
307 _4bz = 2.0f * _2bz;
308
309 // 4. Descente de gradient : Calcul de l'erreur d'orientation
310 s1 = -_2q3 * (2.0f * q2q4 - _2q1q3 - ax) + _2q2 * (2.0f * q1q2 + _2q3q4 - ay) - _2bz * q3 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q4 + _2bz * q2) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q3 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
311 s2 = _2q4 * (2.0f * q2q4 - _2q1q3 - ax) + _2q1 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q2 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + _2bz * q4 * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q3 + _2bz * q1) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q4 - _4bz * q2) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
312 s3 = -_2q1 * (2.0f * q2q4 - _2q1q3 - ax) + _2q4 * (2.0f * q1q2 + _2q3q4 - ay) - 4.0f * q3 * (1.0f - 2.0f * q2q2 - 2.0f * q3q3 - az) + (-_4bx * q3 - _2bz * q1) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (_2bx * q2 + _2bz * q4) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + (_2bx * q1 - _4bz * q3) * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
313 s4 = _2q2 * (2.0f * q2q4 - _2q1q3 - ax) + _2q3 * (2.0f * q1q2 + _2q3q4 - ay) + (-_4bx * q4 + _2bz * q2) * (_2bx * (0.5f - q3q3 - q4q4) + _2bz * (q2q4 - q1q3) - mx) + (-_2bx * q1 + _2bz * q3) * (_2bx * (q2q3 - q1q4) + _2bz * (q1q2 + q3q4) - my) + _2bx * q2 * (_2bx * (q1q3 + q2q4) + _2bz * (0.5f - q2q2 - q3q3) - mz);
314 norm = std::sqrt(s1 * s1 + s2 * s2 + s3 * s3 + s4 * s4); // Normalisation de l'erreur
315 norm = 1.0f/norm;
316 s1 *= norm; s2 *= norm; s3 *= norm; s4 *= norm;
317
318 // 5. Calcul du taux de changement du quaternion (Fusion Gyroscope + Erreur corrigée par beta)
319 qDot1 = 0.5f * (-q2 * gx - q3 * gy - q4 * gz) - beta * s1;
320 qDot2 = 0.5f * (q1 * gx + q3 * gz - q4 * gy) - beta * s2;
321 qDot3 = 0.5f * (q1 * gy - q2 * gz + q4 * gx) - beta * s3;
322 qDot4 = 0.5f * (q1 * gz + q2 * gy - q3 * gx) - beta * s4;
323
324 // 6. Intégration dans le temps pour obtenir le nouveau quaternion d'orientation
325 q1 += qDot1 * dt;
326 q2 += qDot2 * dt;
327 q3 += qDot3 * dt;
328 q4 += qDot4 * dt;
329
330 // 7. Normalisation finale du quaternion
331 norm = std::sqrt(q1 * q1 + q2 * q2 + q3 * q3 + q4 * q4);
332 norm = 1.0f/norm;
333
334 q[0] = q1 * norm;
335 q[1] = q2 * norm;
336 q[2] = q3 * norm;
337 q[3] = q4 * norm;
338}
339
341 m_timer->stop();
342#ifdef Q_OS_LINUX
343 if (m_fileDescriptor >= 0) {
344 close(m_fileDescriptor);
345 m_fileDescriptor = -1;
346 }
347#endif
348}
Mpu9250Source(TelemetryData *data, QObject *parent=nullptr)
Constructeur de la source MPU9250.
void stop()
Arrête l'acquisition et ferme la connexion I2C.
void start()
Démarre l'acquisition matérielle.
~Mpu9250Source()
Destructeur. Assure la fermeture propre du descripteur de fichier I2C.
Classe représentant les données en temps réel du véhicule. Cette classe hérite de QObject et centrali...
Rôle architectural : Source de données inertielles et boussole matérielle (I2C).
Rôle architectural : Modèle central de télémétrie partagé entre les modules C++ et l'interface QML.