MMC5983MA Frequency

Hi, I am trying to operate the MMC5983MA at 200 Hz with I2C configuration. I have a wire between the INT pin on the magnetometer and pin 2 on my Arduino, the Qwiic Micro. Additionally, I modified Example3-I2C_Continuous_measurement.ino from the component’s GitHub to print out the interval between 200 measurements in milliseconds.

Although I expected this interval to be 1000 (based on 200Hz setting), it is actually around 3716.

I have attached my code below. Is there something I am doing incorrectly? I would appreciate any insight.

Thank you!

/*
  Fast, interrupt driven heading calculation using the MMC5983MA magnetometer.
  By: Nathan Seidle and Ricardo Ramos
  SparkFun Electronics
  Date: April 14th, 2022
  License: SparkFun code, firmware, and software is released under the MIT License(http://opensource.org/licenses/MIT).

  Feel like supporting our work? Buy a board from SparkFun!
  https://www.sparkfun.com/products/19034

  This example demonstrates how to use interrupts to quickly read the sensor instead of polling.

  Hardware Connections:
  Solder a wire from the INT pin and wire it to pin 2 on a RedBoard
  Plug a Qwiic cable into the sensor and a RedBoard
  If you don't have a platform with a Qwiic connection use the SparkFun Qwiic Breadboard Jumper
  (https://www.sparkfun.com/products/17912) Open the serial monitor at 115200 baud to see the output
*/

#include <Wire.h>

#include <SparkFun_MMC5983MA_Arduino_Library.h> //Click here to get the library: http://librarymanager/All#SparkFun_MMC5983MA

SFE_MMC5983MA myMag;

int interruptPin = 2;

volatile bool newDataAvailable = true;
uint32_t rawValueX = 0;
uint32_t rawValueY = 0;
uint32_t rawValueZ = 0;
double scaledX = 0;
double scaledY = 0;
double scaledZ = 0;
double heading = 0;

int count = 0;
unsigned long lastPrint = millis();

void setup()
{
    SerialUSB.begin(115200);
    SerialUSB.println("MMC5983MA Example");

    Wire.begin();

    // Configure the interrupt pin for the "Measurement Done" interrupt
    pinMode(interruptPin, INPUT);
    attachInterrupt(digitalPinToInterrupt(interruptPin), interruptRoutine, RISING);

    if (myMag.begin() == false)
    {
        SerialUSB.println("MMC5983MA did not respond - check your wiring. Freezing.");
        while (true)
            ;
    }

    myMag.softReset();

    SerialUSB.println("MMC5983MA connected");

    SerialUSB.println("Setting filter bandwith to 100 Hz for continuous operation...");
    myMag.setFilterBandwidth(100);
    SerialUSB.print("Reading back filter bandwith: ");
    SerialUSB.println(myMag.getFilterBandwith());

    SerialUSB.println("Setting continuous measurement frequency to 10 Hz...");
    myMag.setContinuousModeFrequency(200);
    SerialUSB.print("Reading back continuous measurement frequency: ");
    SerialUSB.println(myMag.getContinuousModeFrequency());

    SerialUSB.println("Enabling auto set/reset...");
    myMag.enableAutomaticSetReset();
    SerialUSB.print("Reading back automatic set/reset: ");
    SerialUSB.println(myMag.isAutomaticSetResetEnabled() ? "enabled" : "disabled");

    SerialUSB.println("Enabling continuous mode...");
    myMag.enableContinuousMode();
    SerialUSB.print("Reading back continuous mode: ");
    SerialUSB.println(myMag.isContinuousModeEnabled() ? "enabled" : "disabled");

    SerialUSB.println("Enabling interrupt...");
    myMag.enableInterrupt();
    SerialUSB.print("Reading back interrupt status: ");
    SerialUSB.println(myMag.isInterruptEnabled() ? "enabled" : "disabled");

    // Set our interrupt flag, just in case we missed the rising edge
    newDataAvailable = true;
}

void loop()
{
    if (newDataAvailable == true)
    {
        newDataAvailable = false; // Clear our interrupt flag
        myMag.clearMeasDoneInterrupt(); // Clear the MMC5983 interrupt

        // Read all three channels simultaneously
        // Note: we are calling readFieldsXYZ to read the fields, not getMeasurementXYZ
        // The measurement is already complete, we do not need to start a new one
        myMag.readFieldsXYZ(&rawValueX, &rawValueY, &rawValueZ);
    
        // The magnetic field values are 18-bit unsigned. The _approximate_ zero (mid) point is 2^17 (131072).
        // Here we scale each field to +/- 1.0 to make it easier to calculate an approximate heading.
        //
        // Please note: to properly correct and calibrate the X, Y and Z channels, you need to determine true
        // offsets (zero points) and scale factors (gains) for all three channels. Futher details can be found at:
        // https://thecavepearlproject.org/2015/05/22/calibrating-any-compass-or-accelerometer-for-arduino/
        scaledX = (double)rawValueX - 131072.0;
        scaledX /= 131072.0;
    
        scaledY = (double)rawValueY - 131072.0;
        scaledY /= 131072.0;
    
        scaledZ = (double)rawValueZ - 131072.0;
        scaledZ /= 131072.0;
    
        // Magnetic north is oriented with the Y axis
        // Convert the X and Y fields into heading using atan2 (Arc Tangent 2)
        heading = atan2(scaledX, 0 - scaledY);
    
        // atan2 returns a value between +PI and -PI
        // Convert to degrees
        heading /= PI;
        heading *= 180;
        heading += 180;

        count++;

        if(count == 200){
            SerialUSB.print("Interval: ");
            SerialUSB.println(millis() - lastPrint);
            lastPrint = millis();

            count = 0;
        }
    }
}

void interruptRoutine()
{
    newDataAvailable = true;
}

Hi @cactusjuice ,

You could try measuring how long - in millis - it takes to clear the interrupt and read the XYZ fields. Perhaps the I2C bus transaction is slowing everything down? Perhaps try adding Wire.setClock(400000); after the Wire.begin();?

I hope this helps,
Paul

Hi Paul, I think it is definitely the (myMag.readFieldsXYZ(&rawValueX, &rawValueY, &rawValueZ) that is slowing the code down. I have the 9DoF IMU with the ISM330DHCX and MMC3983MA, and I do a similar thing there where I print out the milliseconds between interrupts. That code is supposed to take measurements at a frequency of 200Hz based on the TCC timer. When I comment out the magnetometer reading line, the time interval between measurements is 1000. However, when I do not comment out the magnetometer reading line, the code slows down to above 3000.

// Select only one to be true for SAMD21. Must must be placed at the beginning before #include "SAMDTimerInterrupt.h"
#define USING_TIMER_TCC         true      // Only TC3 can be used for SAMD51

#include <Wire.h>
#include <SparkFun_ISM330DHCX.h>
#include <SparkFun_MMC5983MA_Arduino_Library.h>
#include <CRC8.h>
#include "SAMDTimerInterrupt.h"

SFE_MMC5983MA myMag;
SparkFun_ISM330DHCX myISM;
// Structs for X,Y,Z data
sfe_ism_data_t accelData;
sfe_ism_data_t gyroData;

volatile bool IMUNew = false;

uint32_t magX = 0;
uint32_t magY = 0;
uint32_t magZ = 0;

unsigned long time;

int interruptPin = 4;

uint8_t imu_data[43];
CRC8 crc(0x1D, 0xFF, 0xFF, false, false);

volatile bool newDataAvailable = true;

void timer() {
  IMUNew = true;
}

int count;
unsigned long lastPrint = millis();


void setup() {
  Wire.begin();
  SerialUSB.begin(115200);

  if (!myISM.begin()) {
    SerialUSB.println("Did not begin.");
    while (true)
      ;
  }
  if (myMag.begin() == false) {
    SerialUSB.println("MMC5983MA did not respond - check your wiring. Freezing.");
    while (true)
      ;
  }
  // Reset the device to default settings. This if helpful is you're doing multiple
  // uploads testing different settings.
  myISM.deviceReset();
  // Wait for it to finish reseting
  while (!myISM.getDeviceReset()) {
    delay(1);
  }
  SerialUSB.println("Reset.");
  SerialUSB.println("Applying settings.");
  delay(100);
  myMag.softReset();

  myISM.setDeviceConfig();
  myISM.setBlockDataUpdate();

  // Set the output data rate and precision of the accelerometer
  myISM.setAccelDataRate(ISM_XL_ODR_833Hz);
  myISM.setAccelFullScale(ISM_4g);

  // Turn on the accelerometer's filter and apply settings.
  myISM.setAccelFilterLP2();
  myISM.setAccelSlopeFilter(ISM_LP_ODR_DIV_100);

  // Set the output data rate and precision of the gyroscope
  myISM.setGyroDataRate(ISM_GY_ODR_833Hz);
  myISM.setGyroFullScale(ISM_500dps);
  
  // Turn on the gyroscope's filter and apply settings.
  myISM.setGyroFilterLP1();
  myISM.setGyroLP1Bandwidth(ISM_MEDIUM);

  myMag.setFilterBandwidth(100);
  myMag.setContinuousModeFrequency(1000);
  myMag.enableContinuousMode();
  
  myMag.enableAutomaticSetReset();

  SAMDTimer Timer(TIMER_TCC);

  Timer.attachInterrupt(200, timer);

  // Configure the interrupt pin for the "Measurement Done" interrupt
  pinMode(interruptPin, INPUT);
  attachInterrupt(digitalPinToInterrupt(interruptPin), interruptRoutine, RISING);

  myMag.enableInterrupt();
}

void loop() {
  if (IMUNew == true){
	IMUNew = false;
		myISM.getAccel(&accelData);
    myISM.getGyro(&gyroData);
    myMag.getMeasurementXYZ(&magX, &magY, &magZ);

    if (newDataAvailable == true){
      newDataAvailable = false; // Clear our interrupt flag
      myMag.clearMeasDoneInterrupt(); // Clear the MMC5983 interrupt
      myMag.getMeasurementXYZ(&magX, &magY, &magZ);
    }

    time = millis();
    count++;
    
    if(count == 200){
      SerialUSB.print("Interval: ");
      SerialUSB.println(millis() - lastPrint);
      lastPrint = millis();

      count = 0;
      }
	}
}

void interruptRoutine()
{
    newDataAvailable = true;
}

Does Wire.setClock(400000); help? Please give it a try.

Hi Paul, thank you for your suggestion, but Wire.setClock(400000); did not work :frowning:

Hi @cactusjuice ,

Please check you included Wire.setClock(400000); after the Wire.begin();. It increases the bus speed from 100kHz to 400kHz, ~quartering the transaction times.

For best results you may need to swap to the combined ISM330DHCX MMC5983MA breakout and read the data over SPI.

I hope this helps,
Paul

@cactusjuice - fwiw, i am using the 9dof iot w/custom firmware and had considerable headaches associated with libraries and performance. i wrote a custom routine that uses this snippet:
// Wait until measurement is completed or times out

uint16_t timeOut = getTimeout();

do

{

    // Wait a little so we won't flood MMC with requests

    //delay(1);

    esp_rom_delay_us(50); // ESP-IDF low-level delay, \~50 µs

    timeOut--;

} while ((!mmc_io.isBitSet(STATUS_REG, MEAS_M_DONE)) && (timeOut > 0));

NOTE: delay(1) is commented out - you can see this in get functions exposed by the library

keep me posted if you make progress, ive gotten to 100hz bandwidth, but 200hz would be of interest…

also fwiw, i have been able to tweak the I2C bus speed