Aller au contenu


quadmean

Inscrit(e) (le) 27 déc. 2021
Déconnecté Dernière activité févr. 23 2022 01:56
-----

#115463 Lidar rotatif

Posté par quadmean - 21 février 2022 - 01:06

Bonjour,

pour ma part je possède celui-ci qui coute moins de 15€

https://fr.aliexpres....27426c37SRdg8D

 

Pour peser les pour et les contre de ces bebetes:

 

---:

- beaucoup de modèles donc faire attention d'avoir la librairie adaptée ou de pouvoir la bidouiller car les protocoles / vitesses sont sensiblement différentes d'un modèle à l'autre

- le protocole utilise la transmission UART avec des vitesses non conventionnelles => C'est lent, et il faut être certain que la carte supporte des vitesses non conventionnelles

- Distance minimum en dessous de laquelle il n'y a pas de lecture

- en raison du point ci-dessus un indicateur qualité est donné pour chaque lecture de distance

 

+++:

+ Une fois qu'on a réglé les problèmes listés ci-dessus, c'est plutôt simple d'utilisation, il suffit de lire les valeurs au fil de l'eau. Il y a toutefois un petit calcul pour récupérer les distances

 

En conclusion c'est sur qu'un LIDAR peut être assez simple d'utilisation (si la carte tolère des UART non conventionnelles) mais ca bouffe quand même un peu de CPU car il faut lire toutes les valeurs sur 360 degrés en série, et pour chaque valeur effectuer des opérations (vérifier la qualité de la donnée, calculer la distance en fonction de la donnée lue)

Je n'ai pas trouvé de LIDAR dont le fonctionnement est différent, mais peut être que cela existe.

 

Il y a des exemples d'utilisation sur le net soit en python soit pour des cartes, ainsi qu'un channel discord pour avoir de l'aide. si jamais tu ne trouves pas dis moi et je posterais les liens ici

 

voilà voilà,




#115420 Je suis "déboussolé"

Posté par quadmean - 14 février 2022 - 10:29

Bonjour,

tout à fait d'accord avec les précédents posts. Le résultat de mes précédentes expériences:

Je ne pense pas qu'il soit possible d'utiliser "tel quel" les valeurs d'un IMU ou les valeurs de son compas.

Il y a au moins deux raisons à cela:

1/ Du fait de la sensibilité des capteurs, nécessite de la fusion de données (cf AHRS sur google).

2/ Cela implique la calibration des instruments le nécessitant.

 

Je sais que j'avais promis de faire un tuto la dessus, mais j'avoue avoir pas mal de trucs en cours.

 

En tout cas, une chose est certaine, il est nécessaire d'avoir à minima un CPU type STM32 (ou équivalent) qui permet des opérations sur des nombres flottants 32 bits en natif, et dédié à l'IMU (donc plusieurs choses à la fois= 1 CPU min 32 bits + 1 autre CPU).

un capteur non paramétré en DMP (le DMP est le code embarqué dans les capteurs devant faire la fusion de données mais aucun code n'est bon.)

Deux autres raisons qui militent pour les STM32:

Il est préférable à mon sens d'utiliser le SPI et non le I2C, car c'est plus rapide, donc moins de risque de drift... Donc qui dit plus rapide, dit besoin d'avoir un processeur plus rapide.

Il est également pertinent d'utiliser le pin d'interruption. Et donc là aussi, il est donc nécessaire d'avoir un CPU plus rapide.

 

=> Si de base, ton processeur est un ATMEGA 8bits, il faut oublier l'utilisation des MPU. Trop peu de ressources. A minima, il faut soit un MPU + CPU sur la même carte (ca existe sur Aliexpress) soit un MPU et 2 CPU, dont l'un au moins est 32 bits.

Voilà, je sais que c'est pas les meilleurs nouvelles, mais les MPU, c'est pas simple.




#115327 Wheelson, robot a chenilles autonome?

Posté par quadmean - 03 février 2022 - 02:18

Question bête,

as-tu fais des tests avec disons l'équivalent de masse d'un dispositif cible?

Le comportement de tes suspensions, de ton couple, ne risque-t-il pas de changer une fois électronique en place?




#115098 IMU : lequel choisir ?

Posté par quadmean - 05 janvier 2022 - 09:16

Hello Dakota,

trouvé le problème sur mon banc de test, je ne pensais pas que c'était possible, mais le magnétomètre à l'intérieur de la puce avait laché. Donc le DMP faisait de la fusion de données sans magnétomètre, c'est balo. Je m'en suis aperçu en tentant une initialisation manuelle du magnétomètre dans la puce... Résultat KO

 

Heureusement, comme j'achète mes composants en chine, vu le prix, je les prends toujours minima en quantité x2 :)

 

J'ai donc pu tester la librairie sparkfun, et au premier abord, je ne suis pas du tout satisfait.

Je me retrouve dans la situation suivante:

D'un côté la librairie sparkfun gère mieux le DMP que ma librairie, c'est incontestable, mais d'un autre côté, mathématiquement, les quaternions sont inexploitables, il en manque ! Donc du coup, l'IMU fonctionne bien, et même très bien je dois avouer, mais uniquement sur 180 degré par axe ... La honte...

 

D'un autre côté, ma librairie, qui utilise mathématiquement toutes les données du DMP, mais je perd en précision, probablement par ce que je gère mal le DMP.

En fait, avec du recul, j'ai l'impression que la perte de précision vient de la chaleur généree par le DMP. ça chauffre trop. Je me suis litéralement brulé le doigt et au sens propre. on peut supposer que les instruments sont déréglés.

 

Donc je vais faire deux tests:

le premier c'est voir si j'arrive à corriger les erreurs de la librairie sparkfun, ce qui me permettrait de voir si effectivement j'ai une meilleure précision et peut être fournir comme ça une solution clé en main.

La seconde, si c'est comme je le crois la chaleur qui est responsable des imprécisions, cela veut dire qu'à ce stade, utiliser le DMP n'est pas efficace, et qu'il faut faire la fusion des données à côté. Donc je testerais ce point là si nécessaire.

 

On avance....

 

J'espère améliorer la précision des capteurs d'Alan et te donner une solution!




#115071 IMU : lequel choisir ?

Posté par quadmean - 03 janvier 2022 - 07:31

Bonjour Dakota,

Hé oui c'est pas simple les IMU...

Il faudrait que tu précises avec quel IMU tu as fais tes tests.

A la lecture de tes commentaires, je suppose que tu n'utilises pas le DMP, ou alors que tu n'utilises pas les quaternions.

les quaternions sont des objets mathématiques indispensables dans les calculs d'angle des IMUs car ils evitent le drift et le gimball lock (je ne sais plus comment ça se dit)

 

Sans DMP, une calibration manuelle est nécessaire, ainsi que la fusion des données car aucun des instruments seul n'est assez fiable pour avoir une information pertinente.

 

C'est pour cela qu'il est vivement conseillé d'utiliser le DMP.  il y a une autocalibration, mais ce n'est pas instantané : l'autocalibration s'apprend avec les mouvements reçus. Egalement le DMP fait la fusion des données et génére les quaternions pour les calculs.

 

Bon... De mon côté pour pouvoir t'aider j'ai refais mon banc de test avec l'affichage 3D de l'IMU et en utilisant la dernière librairie de Sparkfun pour le 20948.

Là je n'ai pas des bonnes nouvelles : le résultat est incohérent, et l'IMU chauffe comme pas possible, ce qui n'était pas le cas avec la version 'Alan'

Comme il y a pas mal de nouveautés sur ce banc de tests, je dois décortiquer un par un les points pour savoir d'ou vient le problème.

 

Ci-dessous le code source que j'ai utilisé, qui est celui de sparkfun modifié.

L'important c'est le début de la boucle loop, tu as le détail du calcul des 3 angles à partir des quaternions.

#include <Arduino.h>
#include "ICM_20948.h" // Click here to get the library: http://librarymanager/All#SparkFun_ICM_20948_IMU

ICM_20948_SPI icm20948;

#define CS_PIN PA4
#define SPI_FREQ 100000

#define SLAVE_SDA PB9
#define SLAVE_SCL PB8
#define SLAVE_ADR 0x21

TwoWire slaveI2C(SLAVE_SDA, SLAVE_SCL);

void receiveEvent(int);
void requestEvent();

void setup()
{
  // put your setup code here, to run once:

  Serial.begin(115200);
  Serial.println("Reseting");

  SPI.begin();

  slaveI2C.begin(SLAVE_ADR);
  // slaveI2C.setClock(SPI_FREQ);
  slaveI2C.onReceive(receiveEvent);
  slaveI2C.onRequest(requestEvent);

  bool initialized = false;
  while (!initialized)
  {

    icm20948.begin(CS_PIN, SPI, SPI_FREQ); // Here we are using the user-defined SPI_FREQ as the clock speed of the SPI bus

    Serial.print(F("Initialization of the sensor returned: "));
    Serial.println(icm20948.statusString());
    if (icm20948.status != ICM_20948_Stat_Ok)
    {
      Serial.println("Trying again...");
      delay(500);
      initialized = true;
    }
    else
    {
      initialized = true;
    }
  }

  // In this advanced example we'll cover how to do a more fine-grained setup of your sensor
  Serial.println("Device connected!");

  // Here we are doing a SW reset to make sure the device starts in a known state
  icm20948.swReset();
  if (icm20948.status != ICM_20948_Stat_Ok)
  {
    Serial.print(F("Software Reset returned: "));
    Serial.println(icm20948.statusString());
  }
  delay(250);

  // Now wake the sensor up
  icm20948.sleep(false);
  icm20948.lowPower(false);

  bool success = true; // Use success to show if the DMP configuration was successful

  // Initialize the DMP. initializeDMP is a weak function. In this example we overwrite it to change the sample rate (see below)
  success &= (icm20948.initializeDMP() == ICM_20948_Stat_Ok);

  // DMP sensor options are defined in ICM_20948_DMP.h
  //    INV_ICM20948_SENSOR_ACCELEROMETER               (16-bit accel)
  //    INV_ICM20948_SENSOR_GYROSCOPE                   (16-bit gyro + 32-bit calibrated gyro)
  //    INV_ICM20948_SENSOR_RAW_ACCELEROMETER           (16-bit accel)
  //    INV_ICM20948_SENSOR_RAW_GYROSCOPE               (16-bit gyro + 32-bit calibrated gyro)
  //    INV_ICM20948_SENSOR_MAGNETIC_FIELD_UNCALIBRATED (16-bit compass)
  //    INV_ICM20948_SENSOR_GYROSCOPE_UNCALIBRATED      (16-bit gyro)
  //    INV_ICM20948_SENSOR_STEP_DETECTOR               (Pedometer Step Detector)
  //    INV_ICM20948_SENSOR_STEP_COUNTER                (Pedometer Step Detector)
  //    INV_ICM20948_SENSOR_GAME_ROTATION_VECTOR        (32-bit 6-axis quaternion)
  //    INV_ICM20948_SENSOR_ROTATION_VECTOR             (32-bit 9-axis quaternion + heading accuracy)
  //    INV_ICM20948_SENSOR_GEOMAGNETIC_ROTATION_VECTOR (32-bit Geomag RV + heading accuracy)
  //    INV_ICM20948_SENSOR_GEOMAGNETIC_FIELD           (32-bit calibrated compass)
  //    INV_ICM20948_SENSOR_GRAVITY                     (32-bit 6-axis quaternion)
  //    INV_ICM20948_SENSOR_LINEAR_ACCELERATION         (16-bit accel + 32-bit 6-axis quaternion)
  //    INV_ICM20948_SENSOR_ORIENTATION                 (32-bit 9-axis quaternion + heading accuracy)

  // Enable the DMP Game Rotation Vector sensor (Quat6)
  success &= (icm20948.enableDMPSensor(INV_ICM20948_SENSOR_GAME_ROTATION_VECTOR) == ICM_20948_Stat_Ok);

  // Enable additional sensors / features
  // success &= (icm20948.enableDMPSensor(INV_ICM20948_SENSOR_RAW_GYROSCOPE) == ICM_20948_Stat_Ok);
  //success &= (icm20948.enableDMPSensor(INV_ICM20948_SENSOR_RAW_ACCELEROMETER) == ICM_20948_Stat_Ok);
  // success &= (icm20948.enableDMPSensor(INV_ICM20948_SENSOR_MAGNETIC_FIELD_UNCALIBRATED) == ICM_20948_Stat_Ok);

  // Configuring DMP to output data at multiple ODRs:
  // DMP is capable of outputting multiple sensor data at different rates to FIFO.
  // Setting value can be calculated as follows:
  // Value = (DMP running rate / ODR ) - 1
  // E.g. For a 225Hz ODR rate when DMP is running at 225Hz, value = (225/225) - 1 = 0.
  success &= (icm20948.setDMPODRrate(DMP_ODR_Reg_Quat6, 0) == ICM_20948_Stat_Ok); // Set to 225Hz
  //success &= (icm20948.setDMPODRrate(DMP_ODR_Reg_Accel, 0) == ICM_20948_Stat_Ok); // Set to 225Hz
  // success &= (icm20948.setDMPODRrate(DMP_ODR_Reg_Gyro, 0) == ICM_20948_Stat_Ok); // Set to 225Hz
  // success &= (icm20948.setDMPODRrate(DMP_ODR_Reg_Cpass, 0) == ICM_20948_Stat_Ok); // Set to 225Hz

  // Enable the FIFO
  success &= (icm20948.enableFIFO() == ICM_20948_Stat_Ok);

  // Enable the DMP
  success &= (icm20948.enableDMP() == ICM_20948_Stat_Ok);

  // Reset DMP
  success &= (icm20948.resetDMP() == ICM_20948_Stat_Ok);

  // Reset FIFO
  success &= (icm20948.resetFIFO() == ICM_20948_Stat_Ok);

  // Check success
  if (success)
  {
    Serial.println(F("DMP enabled!"));
  }
  else
  {
    Serial.println(F("Enable DMP failed!"));
    while (1)
      ; // Do nothing more
  }
}

double roll=0, pitch=0, yaw=0;
uint16_t droll=0,dpitch=0,dyaw=0 ;


void loop()
{
  // put your main code here, to run repeatedly:
  icm_20948_DMP_data_t data;
  icm20948.readDMPdataFromFIFO(&data);
  if ((icm20948.status == ICM_20948_Stat_Ok) || (icm20948.status == ICM_20948_Stat_FIFOMoreDataAvail)) // Was valid data available?
  {
    if ((data.header & DMP_header_bitmap_Quat6) > 0) // Check for GRV data (Quat6)
    {
      double q1 = ((double)data.Quat6.Data.Q1) / 1073741824.0; // Convert to double. Divide by 2^30
      double q2 = ((double)data.Quat6.Data.Q2) / 1073741824.0; // Convert to double. Divide by 2^30
      double q3 = ((double)data.Quat6.Data.Q3) / 1073741824.0; // Convert to double. Divide by 2^30

      double q0 = sqrt(1.0 - ((q1 * q1) + (q2 * q2) + (q3 * q3)));

      double q2sqr = q2 * q2;

      // roll (x-axis rotation)
      double t0 = +2.0 * (q0 * q1 + q2 * q3);
      double t1 = +1.0 - 2.0 * (q1 * q1 + q2sqr);
      double roll = atan2(t0, t1) * 180.0 / PI;

      // pitch (y-axis rotation)
      double t2 = +2.0 * (q0 * q2 - q3 * q1);
      t2 = t2 > 1.0 ? 1.0 : t2;
      t2 = t2 < -1.0 ? -1.0 : t2;
      double pitch = asin(t2) * 180.0 / PI;

      // yaw (z-axis rotation)
      double t3 = +2.0 * (q0 * q3 + q1 * q2);
      double t4 = +1.0 - 2.0 * (q2sqr + q3 * q3);
      double yaw = atan2(t3, t4) * 180.0 / PI;
    
      droll=roll ;
      dpitch=pitch ;
      dyaw=yaw ;
    
    }

    if ((data.header & DMP_header_bitmap_Accel) > 0) // Check for Accel
    {
      float acc_x = (float)data.Raw_Accel.Data.X; // Extract the raw accelerometer data
      float acc_y = (float)data.Raw_Accel.Data.Y;
      float acc_z = (float)data.Raw_Accel.Data.Z;
    }
  }
  if (icm20948.status != ICM_20948_Stat_FIFOMoreDataAvail) // If more data is available then we should read it right away - and not delay
  {
    delay(10);
  }
}

uint16_t command = 0;

void receiveEvent(int howMany)
{
  if (slaveI2C.available() < 1)
    return;
  uint8_t c = slaveI2C.read();
  if (c < 2)
    command = c;
  else
    command = 0;
}

void requestEvent()
{
  switch (command)
  {
  case 0:
    slaveI2C.write(0);
    break;
  case 1:
  {
    slaveI2C.write((uint8_t*)&dyaw,2) ;
    slaveI2C.write((uint8_t*)&dpitch,2) ;
    slaveI2C.write((uint8_t*)&droll,2) ;
  }
  }

}

// initializeDMP is a weak function. Let's overwrite it so we can increase the sample rate
ICM_20948_Status_e ICM_20948::initializeDMP(void)
{
  // The ICM-20948 is awake and ready but hasn't been configured. Let's step through the configuration
  // sequence from InvenSense's _confidential_ Application Note "Programming Sequence for DMP Hardware Functions".

  ICM_20948_Status_e result = ICM_20948_Stat_Ok; // Use result and worstResult to show if the configuration was successful
  ICM_20948_Status_e worstResult = ICM_20948_Stat_Ok;

  // Normally, when the DMP is not enabled, startupMagnetometer (called by startupDefault, which is called by begin) configures the AK09916 magnetometer
  // to run at 100Hz by setting the CNTL2 register (0x31) to 0x08. Then the ICM20948's I2C_SLV0 is configured to read
  // nine bytes from the mag every sample, starting from the STATUS1 register (0x10). ST1 includes the DRDY (Data Ready) bit.
  // Next are the six magnetometer readings (little endian). After a dummy byte, the STATUS2 register (0x18) contains the HOFL (Overflow) bit.
  //
  // But looking very closely at the InvenSense example code, we can see in inv_icm20948_resume_akm (in Icm20948AuxCompassAkm.c) that,
  // when the DMP is running, the magnetometer is set to Single Measurement (SM) mode and that ten bytes are read, starting from the reserved
  // RSV2 register (0x03). The datasheet does not define what registers 0x04 to 0x0C contain. There is definitely some secret sauce in here...
  // The magnetometer data appears to be big endian (not little endian like the HX/Y/Z registers) and starts at register 0x04.
  // We had to examine the I2C traffic between the master and the AK09916 on the AUX_DA and AUX_CL pins to discover this...
  //
  // So, we need to set up I2C_SLV0 to do the ten byte reading. The parameters passed to i2cControllerConfigurePeripheral are:
  // 0: use I2C_SLV0
  // MAG_AK09916_I2C_ADDR: the I2C address of the AK09916 magnetometer (0x0C unshifted)
  // AK09916_REG_RSV2: we start reading here (0x03). Secret sauce...
  // 10: we read 10 bytes each cycle
  // true: set the I2C_SLV0_RNW ReadNotWrite bit so we read the 10 bytes (not write them)
  // true: set the I2C_SLV0_CTRL I2C_SLV0_EN bit to enable reading from the peripheral at the sample rate
  // false: clear the I2C_SLV0_CTRL I2C_SLV0_REG_DIS (we want to write the register value)
  // true: set the I2C_SLV0_CTRL I2C_SLV0_GRP bit to show the register pairing starts at byte 1+2 (copied from inv_icm20948_resume_akm)
  // true: set the I2C_SLV0_CTRL I2C_SLV0_BYTE_SW to byte-swap the data from the mag (copied from inv_icm20948_resume_akm)
  result = i2cControllerConfigurePeripheral(0, MAG_AK09916_I2C_ADDR, AK09916_REG_RSV2, 10, true, true, false, true, true);
  if (result > worstResult)
    worstResult = result;
  //
  // We also need to set up I2C_SLV1 to do the Single Measurement triggering:
  // 1: use I2C_SLV1
  // MAG_AK09916_I2C_ADDR: the I2C address of the AK09916 magnetometer (0x0C unshifted)
  // AK09916_REG_CNTL2: we start writing here (0x31)
  // 1: not sure why, but the write does not happen if this is set to zero
  // false: clear the I2C_SLV0_RNW ReadNotWrite bit so we write the dataOut byte
  // true: set the I2C_SLV0_CTRL I2C_SLV0_EN bit. Not sure why, but the write does not happen if this is clear
  // false: clear the I2C_SLV0_CTRL I2C_SLV0_REG_DIS (we want to write the register value)
  // false: clear the I2C_SLV0_CTRL I2C_SLV0_GRP bit
  // false: clear the I2C_SLV0_CTRL I2C_SLV0_BYTE_SW bit
  // AK09916_mode_single: tell I2C_SLV1 to write the Single Measurement command each sample
  result = i2cControllerConfigurePeripheral(1, MAG_AK09916_I2C_ADDR, AK09916_REG_CNTL2, 1, false, true, false, false, false, AK09916_mode_single);
  if (result > worstResult)
    worstResult = result;

  // Set the I2C Master ODR configuration
  // It is not clear why we need to do this... But it appears to be essential! From the datasheet:
  // "I2C_MST_ODR_CONFIG[3:0]: ODR configuration for external sensor when gyroscope and accelerometer are disabled.
  //  ODR is computed as follows: 1.1 kHz/(2^((odr_config[3:0])) )
  //  When gyroscope is enabled, all sensors (including I2C_MASTER) use the gyroscope ODR.
  //  If gyroscope is disabled, then all sensors (including I2C_MASTER) use the accelerometer ODR."
  // Since both gyro and accel are running, setting this register should have no effect. But it does. Maybe because the Gyro and Accel are placed in Low Power Mode (cycled)?
  // You can see by monitoring the Aux I2C pins that the next three lines reduce the bus traffic (magnetometer reads) from 1125Hz to the chosen rate: 68.75Hz in this case.
  result = setBank(3);
  if (result > worstResult)
    worstResult = result;      // Select Bank 3
  uint8_t mstODRconfig = 0x04; // Set the ODR configuration to 1100/2^4 = 68.75Hz
  result = write(AGB3_REG_I2C_MST_ODR_CONFIG, &mstODRconfig, 1);
  if (result > worstResult)
    worstResult = result; // Write one byte to the I2C_MST_ODR_CONFIG register

  // Configure clock source through PWR_MGMT_1
  // ICM_20948_Clock_Auto selects the best available clock source – PLL if ready, else use the Internal oscillator
  result = setClockSource(ICM_20948_Clock_Auto);
  if (result > worstResult)
    worstResult = result; // This is shorthand: success will be set to false if setClockSource fails

  // Enable accel and gyro sensors through PWR_MGMT_2
  // Enable Accelerometer (all axes) and Gyroscope (all axes) by writing zero to PWR_MGMT_2
  result = setBank(0);
  if (result > worstResult)
    worstResult = result;  // Select Bank 0
  uint8_t pwrMgmt2 = 0x40; // Set the reserved bit 6 (pressure sensor disable?)
  result = write(AGB0_REG_PWR_MGMT_2, &pwrMgmt2, 1);
  if (result > worstResult)
    worstResult = result; // Write one byte to the PWR_MGMT_2 register

  // Place _only_ I2C_Master in Low Power Mode (cycled) via LP_CONFIG
  // The InvenSense Nucleo example initially puts the accel and gyro into low power mode too, but then later updates LP_CONFIG so only the I2C_Master is in Low Power Mode
  result = setSampleMode(ICM_20948_Internal_Mst, ICM_20948_Sample_Mode_Cycled);
  if (result > worstResult)
    worstResult = result;

  // Disable the FIFO
  result = enableFIFO(false);
  if (result > worstResult)
    worstResult = result;

  // Disable the DMP
  result = enableDMP(false);
  if (result > worstResult)
    worstResult = result;

  // Set Gyro FSR (Full scale range) to 2000dps through GYRO_CONFIG_1
  // Set Accel FSR (Full scale range) to 4g through ACCEL_CONFIG
  ICM_20948_fss_t myFSS; // This uses a "Full Scale Settings" structure that can contain values for all configurable sensors
  myFSS.a = gpm4;        // (ICM_20948_ACCEL_CONFIG_FS_SEL_e)
                         // gpm2
                         // gpm4
                         // gpm8
                         // gpm16
  myFSS.g = dps2000;     // (ICM_20948_GYRO_CONFIG_1_FS_SEL_e)
                         // dps250
                         // dps500
                         // dps1000
                         // dps2000
  result = setFullScale((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), myFSS);
  if (result > worstResult)
    worstResult = result;

  // The InvenSense Nucleo code also enables the gyro DLPF (but leaves GYRO_DLPFCFG set to zero = 196.6Hz (3dB))
  // We found this by going through the SPI data generated by ZaneL's Teensy-ICM-20948 library byte by byte...
  // The gyro DLPF is enabled by default (GYRO_CONFIG_1 = 0x01) so the following line should have no effect, but we'll include it anyway
  result = enableDLPF(ICM_20948_Internal_Gyr, true);
  if (result > worstResult)
    worstResult = result;

  // Enable interrupt for FIFO overflow from FIFOs through INT_ENABLE_2
  // If we see this interrupt, we'll need to reset the FIFO
  // result = intEnableOverflowFIFO( 0x1F ); if (result > worstResult) worstResult = result; // Enable the interrupt on all FIFOs

  // Turn off what goes into the FIFO through FIFO_EN_1, FIFO_EN_2
  // Stop the peripheral data from being written to the FIFO by writing zero to FIFO_EN_1
  result = setBank(0);
  if (result > worstResult)
    worstResult = result; // Select Bank 0
  uint8_t zero = 0;
  result = write(AGB0_REG_FIFO_EN_1, &zero, 1);
  if (result > worstResult)
    worstResult = result;
  // Stop the accelerometer, gyro and temperature data from being written to the FIFO by writing zero to FIFO_EN_2
  result = write(AGB0_REG_FIFO_EN_2, &zero, 1);
  if (result > worstResult)
    worstResult = result;

  // Turn off data ready interrupt through INT_ENABLE_1
  result = intEnableRawDataReady(false);
  if (result > worstResult)
    worstResult = result;

  // Reset FIFO through FIFO_RST
  result = resetFIFO();
  if (result > worstResult)
    worstResult = result;

  // Set gyro sample rate divider with GYRO_SMPLRT_DIV
  // Set accel sample rate divider with ACCEL_SMPLRT_DIV_2
  ICM_20948_smplrt_t mySmplrt;
  // mySmplrt.g = 19; // ODR is computed as follows: 1.1 kHz/(1+GYRO_SMPLRT_DIV[7:0]). 19 = 55Hz. InvenSense Nucleo example uses 19 (0x13).
  // mySmplrt.a = 19; // ODR is computed as follows: 1.125 kHz/(1+ACCEL_SMPLRT_DIV[11:0]). 19 = 56.25Hz. InvenSense Nucleo example uses 19 (0x13).
  mySmplrt.g = 4; // 225Hz
  mySmplrt.a = 4; // 225Hz
  // mySmplrt.g = 8; // 112Hz
  // mySmplrt.a = 8; // 112Hz
  result = setSampleRate((ICM_20948_Internal_Acc | ICM_20948_Internal_Gyr), mySmplrt);
  if (result > worstResult)
    worstResult = result;

  // Setup DMP start address through PRGM_STRT_ADDRH/PRGM_STRT_ADDRL
  result = setDMPstartAddress();
  if (result > worstResult)
    worstResult = result; // Defaults to DMP_START_ADDRESS

  // Now load the DMP firmware
  result = loadDMPFirmware();
  if (result > worstResult)
    worstResult = result;

  // Write the 2 byte Firmware Start Value to ICM PRGM_STRT_ADDRH/PRGM_STRT_ADDRL
  result = setDMPstartAddress();
  if (result > worstResult)
    worstResult = result; // Defaults to DMP_START_ADDRESS

  // Set the Hardware Fix Disable register to 0x48
  result = setBank(0);
  if (result > worstResult)
    worstResult = result; // Select Bank 0
  uint8_t fix = 0x48;
  result = write(AGB0_REG_HW_FIX_DISABLE, &fix, 1);
  if (result > worstResult)
    worstResult = result;

  // Set the Single FIFO Priority Select register to 0xE4
  result = setBank(0);
  if (result > worstResult)
    worstResult = result; // Select Bank 0
  uint8_t fifoPrio = 0xE4;
  result = write(AGB0_REG_SINGLE_FIFO_PRIORITY_SEL, &fifoPrio, 1);
  if (result > worstResult)
    worstResult = result;

  // Configure Accel scaling to DMP
  // The DMP scales accel raw data internally to align 1g as 2^25
  // In order to align internal accel raw data 2^25 = 1g write 0x04000000 when FSR is 4g
  const unsigned char accScale[4] = {0x04, 0x00, 0x00, 0x00};
  result = writeDMPmems(ACC_SCALE, 4, &accScale[0]);
  if (result > worstResult)
    worstResult = result; // Write accScale to ACC_SCALE DMP register
  // In order to output hardware unit data as configured FSR write 0x00040000 when FSR is 4g
  const unsigned char accScale2[4] = {0x00, 0x04, 0x00, 0x00};
  result = writeDMPmems(ACC_SCALE2, 4, &accScale2[0]);
  if (result > worstResult)
    worstResult = result; // Write accScale2 to ACC_SCALE2 DMP register

  // Configure Compass mount matrix and scale to DMP
  // The mount matrix write to DMP register is used to align the compass axes with accel/gyro.
  // This mechanism is also used to convert hardware unit to uT. The value is expressed as 1uT = 2^30.
  // Each compass axis will be converted as below:
  // X = raw_x * CPASS_MTX_00 + raw_y * CPASS_MTX_01 + raw_z * CPASS_MTX_02
  // Y = raw_x * CPASS_MTX_10 + raw_y * CPASS_MTX_11 + raw_z * CPASS_MTX_12
  // Z = raw_x * CPASS_MTX_20 + raw_y * CPASS_MTX_21 + raw_z * CPASS_MTX_22
  // The AK09916 produces a 16-bit signed output in the range +/-32752 corresponding to +/-4912uT. 1uT = 6.66 ADU.
  // 2^30 / 6.66666 = 161061273 = 0x9999999
  const unsigned char mountMultiplierZero[4] = {0x00, 0x00, 0x00, 0x00};
  const unsigned char mountMultiplierPlus[4] = {0x09, 0x99, 0x99, 0x99};  // Value taken from InvenSense Nucleo example
  const unsigned char mountMultiplierMinus[4] = {0xF6, 0x66, 0x66, 0x67}; // Value taken from InvenSense Nucleo example
  result = writeDMPmems(CPASS_MTX_00, 4, &mountMultiplierPlus[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(CPASS_MTX_01, 4, &mountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(CPASS_MTX_02, 4, &mountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(CPASS_MTX_10, 4, &mountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(CPASS_MTX_11, 4, &mountMultiplierMinus[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(CPASS_MTX_12, 4, &mountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(CPASS_MTX_20, 4, &mountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(CPASS_MTX_21, 4, &mountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(CPASS_MTX_22, 4, &mountMultiplierMinus[0]);
  if (result > worstResult)
    worstResult = result;

  // Configure the B2S Mounting Matrix
  const unsigned char b2sMountMultiplierZero[4] = {0x00, 0x00, 0x00, 0x00};
  const unsigned char b2sMountMultiplierPlus[4] = {0x40, 0x00, 0x00, 0x00}; // Value taken from InvenSense Nucleo example
  result = writeDMPmems(B2S_MTX_00, 4, &b2sMountMultiplierPlus[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(B2S_MTX_01, 4, &b2sMountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(B2S_MTX_02, 4, &b2sMountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(B2S_MTX_10, 4, &b2sMountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(B2S_MTX_11, 4, &b2sMountMultiplierPlus[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(B2S_MTX_12, 4, &b2sMountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(B2S_MTX_20, 4, &b2sMountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(B2S_MTX_21, 4, &b2sMountMultiplierZero[0]);
  if (result > worstResult)
    worstResult = result;
  result = writeDMPmems(B2S_MTX_22, 4, &b2sMountMultiplierPlus[0]);
  if (result > worstResult)
    worstResult = result;

  // Configure the DMP Gyro Scaling Factor
  // @param[in] gyro_div Value written to GYRO_SMPLRT_DIV register, where
  //            0=1125Hz sample rate, 1=562.5Hz sample rate, ... 4=225Hz sample rate, ...
  //            10=102.2727Hz sample rate, ... etc.
  // @param[in] gyro_level 0=250 dps, 1=500 dps, 2=1000 dps, 3=2000 dps
  result = setGyroSF(4, 3);
  if (result > worstResult)
    worstResult = result; // 4 = 225Hz (see above), 3 = 2000dps (see above)

  // Configure the Gyro full scale
  // 2000dps : 2^28
  // 1000dps : 2^27
  //  500dps : 2^26
  //  250dps : 2^25
  const unsigned char gyroFullScale[4] = {0x10, 0x00, 0x00, 0x00}; // 2000dps : 2^28
  result = writeDMPmems(GYRO_FULLSCALE, 4, &gyroFullScale[0]);
  if (result > worstResult)
    worstResult = result;

  // Configure the Accel Only Gain: 15252014 (225Hz) 30504029 (112Hz) 61117001 (56Hz)
  // const unsigned char accelOnlyGain[4] = {0x03, 0xA4, 0x92, 0x49}; // 56Hz
  const unsigned char accelOnlyGain[4] = {0x00, 0xE8, 0xBA, 0x2E}; // 225Hz
  // const unsigned char accelOnlyGain[4] = {0x01, 0xD1, 0x74, 0x5D}; // 112Hz
  result = writeDMPmems(ACCEL_ONLY_GAIN, 4, &accelOnlyGain[0]);
  if (result > worstResult)
    worstResult = result;

  // Configure the Accel Alpha Var: 1026019965 (225Hz) 977872018 (112Hz) 882002213 (56Hz)
  // const unsigned char accelAlphaVar[4] = {0x34, 0x92, 0x49, 0x25}; // 56Hz
  const unsigned char accelAlphaVar[4] = {0x3D, 0x27, 0xD2, 0x7D}; // 225Hz
  // const unsigned char accelAlphaVar[4] = {0x3A, 0x49, 0x24, 0x92}; // 112Hz
  result = writeDMPmems(ACCEL_ALPHA_VAR, 4, &accelAlphaVar[0]);
  if (result > worstResult)
    worstResult = result;

  // Configure the Accel A Var: 47721859 (225Hz) 95869806 (112Hz) 191739611 (56Hz)
  // const unsigned char accelAVar[4] = {0x0B, 0x6D, 0xB6, 0xDB}; // 56Hz
  const unsigned char accelAVar[4] = {0x02, 0xD8, 0x2D, 0x83}; // 225Hz
  // const unsigned char accelAVar[4] = {0x05, 0xB6, 0xDB, 0x6E}; // 112Hz
  result = writeDMPmems(ACCEL_A_VAR, 4, &accelAVar[0]);
  if (result > worstResult)
    worstResult = result;

  // Configure the Accel Cal Rate
  const unsigned char accelCalRate[4] = {0x00, 0x00}; // Value taken from InvenSense Nucleo example
  result = writeDMPmems(ACCEL_CAL_RATE, 2, &accelCalRate[0]);
  if (result > worstResult)
    worstResult = result;

  // Configure the Compass Time Buffer. The I2C Master ODR Configuration (see above) sets the magnetometer read rate to 68.75Hz.
  // Let's set the Compass Time Buffer to 69 (Hz).
  const unsigned char compassRate[2] = {0x00, 0x45}; // 69Hz
  result = writeDMPmems(CPASS_TIME_BUFFER, 2, &compassRate[0]);
  if (result > worstResult)
    worstResult = result;

  // Enable DMP interrupt
  // This would be the most efficient way of getting the DMP data, instead of polling the FIFO
  // result = intEnableDMP(true); if (result > worstResult) worstResult = result;

  return worstResult;
}



#115051 Alan

Posté par quadmean - 01 janvier 2022 - 01:52

Bonjour et très bonne année 2022 ! Santé pognon !

 

Alors oui dans l'absolu il y a beaucoup de cartes qui ne soient pas des mega et plus puissantes et moins cher aussi, et la plupart se programment également avec de l'Arduino et en général il n'y a aucun code à réécrire (ou marginalement une ou deux lignes).

 

je t'encourage en première étape à passer sur studio code avec platformio, il y a une petite courbe d'apprentissage mais franchement c'est pas trop dur... Il y a des tutos partout.

 

Si tu ne souhaites pas le faire, tu peux toujours rester sur Arduino IDE mais tu te fermeras des possibilités, par exemple de debuggage, et de facilité de programmation.

 

Les cartes STM32 dont j'ai parlé fonctionnent sous les deux environnements ArduinoIDE ou StudioCode + PlateformIO. L'avantage c'est qu'elles sont 32bits, plus rapides, et aussi possèdent des instructions de calcul

 

L'inconvénient de celles que j'utilise c'est qu'elles ont moins de broches mais comme elles sont pas cher, on peut en avoir plusieurs et les associer via le protocole I2C par exemple. Ça fait une sorte de parallélisme de calcul.

 

Mais sinon il existe des cartes STM32 tout plein de broches comme l'ATMEGA pour un prix très raisonnable:

 

https://www.aliexpre...arch-mainSearch

 

Tu vois là ici on est déjà sur du 168Mhz... et en 32bits... contre 16mhz en 8bits pour l'ATMEGA......

 

Sans compter toutes les possibilités que ça offre...




#115028 IMU : lequel choisir ?

Posté par quadmean - 28 décembre 2021 - 07:30

Hello Oracid,

Super vidéo très intéressante. Merci! Je progresse dans ma quête de l'IMU ultime, et ce n'est pas gagné....

 

Ce que j'ai compris en regardant la vidéo et en lisant les commentaires c'est que les deux ont l'air de se valoir, mais dans les commentaires, on parle éventuellement  d'une plus grande sensibilité de l'IMU 20948 aux vibrations. Donc au premier abord, de ce côté avantage au BNO.

Néanmois balle au centre! la vidéo a été faite à partir d'une très vielle librairie de l'IMU 20948. On peut le constater par la date de la vidéo. Hors depuis Avril 2021 et la version 1.2.5 , des évolutions significatives ont été apportées à chaque version sur le paramétrage du DMP pour améliorer la précision (qui était déjà pas mal) et supprimer le drift.

 

Le réel avantage du BNO mais qui explique aussi son prix (l'IMU 20948 est à moins de 5€), c'est qu'il possède une mémoire flash ce qui permet de stocker sa configuration dans le BNO, ce qui n'est pas possible avec l'IMU, mais ça se contourne en paramétrant l'IMU à l'initialisation.  La librairie de l'IMU embarque maintenant une fonction qui fait tout le travail...

 

Je vais ressortir mon banc de tests et recompiler le code avec la dernière version de la librairie pour regarder tout ça...

 

Très intéressant ces évolutions ... Merci mille fois, pour ces éléments Oracid. Je vais reprendre mon code pour voir si maintenant on peut avoir plus de qualité sur la lecture d'une vitesse moyenne.




#115010 Alan

Posté par quadmean - 27 décembre 2021 - 10:55

Merci beaucoup Oracid!

Je viens de regarder vos vidéos, c'est tout à fait impressionnant!




#115008 Alan

Posté par quadmean - 27 décembre 2021 - 06:25

Bonjour à tous,

je suis nouveau ici, je tenais à vous présenter mon robot en cours de construction.

C'est Alan,

 

il y a plusieurs micro controlleurs STM 32 + Jetson Nano

4 roues motrices

2 gyroscopes

3 servos

 

programmé principalement en Python et C++

La tête est articulée dans tous axes + vision 3D

 

Toutes les pièces sont trouvées sur les sites de commerce chinois habituels ou bien imprimées en 3D (à l'exception du Jetson Nano)

 

 

Encore beaucoup de travail à faire...

Image(s) jointe(s)

  • 1640621768252.-v2.jpg