From 7a6abc3ff344a16ee8e3958d6d461ae8c004363d Mon Sep 17 00:00:00 2001 From: Limor Fried Date: Fri, 18 Jul 2025 18:43:52 -0400 Subject: [PATCH] Complete QMC5883P library implementation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add complete Control Register 1 & 2 functionality - Add all setter/getter functions for mode, ODR, OSR, DSR, range, set/reset mode - Add status register functions (isDataReady, isOverflow) - Add data reading functions (getRawMagnetic, getGaussField) - Add soft reset and self-test functions - Add comprehensive test example with range cycling - All functions tested and verified working with hardware - Gauss conversion automatically adapts to current range setting - Range cycling test demonstrates consistent Gauss values across all ranges 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- Adafruit_QMC5883P.cpp | 259 +++++++++++++++++++++++ Adafruit_QMC5883P.h | 85 ++++++++ examples/test_QMC5883P/test_QMC5883P.ino | 174 ++++++++++++++- 3 files changed, 517 insertions(+), 1 deletion(-) diff --git a/Adafruit_QMC5883P.cpp b/Adafruit_QMC5883P.cpp index abc162a..a0e109f 100644 --- a/Adafruit_QMC5883P.cpp +++ b/Adafruit_QMC5883P.cpp @@ -67,4 +67,263 @@ bool Adafruit_QMC5883P::begin(uint8_t i2c_addr, TwoWire *wire) { } return true; +} + +/*! + * @brief Reads raw magnetic field data from all three axes + * @param x + * Pointer to store X-axis raw data (2's complement) + * @param y + * Pointer to store Y-axis raw data (2's complement) + * @param z + * Pointer to store Z-axis raw data (2's complement) + * @return True if read was successful, otherwise false. + */ +bool Adafruit_QMC5883P::getRawMagnetic(int16_t *x, int16_t *y, int16_t *z) { + uint8_t buffer[6]; + + // Read all 6 bytes (X,Y,Z LSB+MSB) starting from X LSB register + Adafruit_BusIO_Register data_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_XOUT_LSB, 6); + if (!data_reg.read(buffer, 6)) { + return false; + } + + // Combine LSB and MSB for each axis (2's complement) + *x = (int16_t)((buffer[1] << 8) | buffer[0]); // X = MSB[1] | LSB[0] + *y = (int16_t)((buffer[3] << 8) | buffer[2]); // Y = MSB[3] | LSB[2] + *z = (int16_t)((buffer[5] << 8) | buffer[4]); // Z = MSB[5] | LSB[4] + + return true; +} + +/*! + * @brief Reads magnetic field data and converts to Gauss + * @param x + * Pointer to store X-axis field in Gauss + * @param y + * Pointer to store Y-axis field in Gauss + * @param z + * Pointer to store Z-axis field in Gauss + * @return True if read was successful, otherwise false. + */ +bool Adafruit_QMC5883P::getGaussField(float *x, float *y, float *z) { + int16_t raw_x, raw_y, raw_z; + + // Get raw magnetic data + if (!getRawMagnetic(&raw_x, &raw_y, &raw_z)) { + return false; + } + + // Get current range to determine conversion factor + qmc5883p_range_t range = getRange(); + float lsb_per_gauss; + + switch(range) { + case QMC5883P_RANGE_30G: + lsb_per_gauss = 1000.0; + break; + case QMC5883P_RANGE_12G: + lsb_per_gauss = 2500.0; + break; + case QMC5883P_RANGE_8G: + lsb_per_gauss = 3750.0; + break; + case QMC5883P_RANGE_2G: + lsb_per_gauss = 15000.0; + break; + default: + return false; + } + + // Convert to Gauss + *x = (float)raw_x / lsb_per_gauss; + *y = (float)raw_y / lsb_per_gauss; + *z = (float)raw_z / lsb_per_gauss; + + return true; +} + +/*! + * @brief Checks if new magnetic data is ready + * @return True if new data is ready, false otherwise + */ +bool Adafruit_QMC5883P::isDataReady() { + Adafruit_BusIO_Register status_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_STATUS); + Adafruit_BusIO_RegisterBits drdy_bit = Adafruit_BusIO_RegisterBits(&status_reg, 1, 0); + return drdy_bit.read(); +} + +/*! + * @brief Checks if data overflow has occurred + * @return True if overflow occurred, false otherwise + */ +bool Adafruit_QMC5883P::isOverflow() { + Adafruit_BusIO_Register status_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_STATUS); + Adafruit_BusIO_RegisterBits ovfl_bit = Adafruit_BusIO_RegisterBits(&status_reg, 1, 1); + return ovfl_bit.read(); +} + +/*! + * @brief Sets the operating mode + * @param mode + * The operating mode to set + */ +void Adafruit_QMC5883P::setMode(qmc5883p_mode_t mode) { + Adafruit_BusIO_Register ctrl1_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL1); + Adafruit_BusIO_RegisterBits mode_bits = Adafruit_BusIO_RegisterBits(&ctrl1_reg, 2, 0); + mode_bits.write(mode); +} + +/*! + * @brief Gets the current operating mode + * @return The current operating mode + */ +qmc5883p_mode_t Adafruit_QMC5883P::getMode() { + Adafruit_BusIO_Register ctrl1_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL1); + Adafruit_BusIO_RegisterBits mode_bits = Adafruit_BusIO_RegisterBits(&ctrl1_reg, 2, 0); + return (qmc5883p_mode_t)mode_bits.read(); +} + +/*! + * @brief Sets the output data rate + * @param odr + * The output data rate to set + */ +void Adafruit_QMC5883P::setODR(qmc5883p_odr_t odr) { + Adafruit_BusIO_Register ctrl1_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL1); + Adafruit_BusIO_RegisterBits odr_bits = Adafruit_BusIO_RegisterBits(&ctrl1_reg, 2, 2); + odr_bits.write(odr); +} + +/*! + * @brief Gets the current output data rate + * @return The current output data rate + */ +qmc5883p_odr_t Adafruit_QMC5883P::getODR() { + Adafruit_BusIO_Register ctrl1_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL1); + Adafruit_BusIO_RegisterBits odr_bits = Adafruit_BusIO_RegisterBits(&ctrl1_reg, 2, 2); + return (qmc5883p_odr_t)odr_bits.read(); +} + +/*! + * @brief Sets the over sample ratio + * @param osr + * The over sample ratio to set + */ +void Adafruit_QMC5883P::setOSR(qmc5883p_osr_t osr) { + Adafruit_BusIO_Register ctrl1_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL1); + Adafruit_BusIO_RegisterBits osr_bits = Adafruit_BusIO_RegisterBits(&ctrl1_reg, 2, 4); + osr_bits.write(osr); +} + +/*! + * @brief Gets the current over sample ratio + * @return The current over sample ratio + */ +qmc5883p_osr_t Adafruit_QMC5883P::getOSR() { + Adafruit_BusIO_Register ctrl1_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL1); + Adafruit_BusIO_RegisterBits osr_bits = Adafruit_BusIO_RegisterBits(&ctrl1_reg, 2, 4); + return (qmc5883p_osr_t)osr_bits.read(); +} + +/*! + * @brief Sets the downsample ratio + * @param dsr + * The downsample ratio to set + */ +void Adafruit_QMC5883P::setDSR(qmc5883p_dsr_t dsr) { + Adafruit_BusIO_Register ctrl1_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL1); + Adafruit_BusIO_RegisterBits dsr_bits = Adafruit_BusIO_RegisterBits(&ctrl1_reg, 2, 6); + dsr_bits.write(dsr); +} + +/*! + * @brief Gets the current downsample ratio + * @return The current downsample ratio + */ +qmc5883p_dsr_t Adafruit_QMC5883P::getDSR() { + Adafruit_BusIO_Register ctrl1_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL1); + Adafruit_BusIO_RegisterBits dsr_bits = Adafruit_BusIO_RegisterBits(&ctrl1_reg, 2, 6); + return (qmc5883p_dsr_t)dsr_bits.read(); +} + +/*! + * @brief Performs a soft reset of the chip + * @return True if reset was successful, false otherwise + */ +bool Adafruit_QMC5883P::softReset() { + Adafruit_BusIO_Register ctrl2_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL2); + Adafruit_BusIO_RegisterBits reset_bit = Adafruit_BusIO_RegisterBits(&ctrl2_reg, 1, 7); + + // Set reset bit + reset_bit.write(1); + + // Wait for reset to complete (datasheet doesn't specify time, use 50ms) + delay(50); + + // Check if chip ID is still valid after reset + Adafruit_BusIO_Register chip_id_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CHIPID); + uint8_t chip_id = chip_id_reg.read(); + return (chip_id == 0x80); +} + +/*! + * @brief Performs self-test of the chip + * @return True if self-test passed, false otherwise + */ +bool Adafruit_QMC5883P::selfTest() { + Adafruit_BusIO_Register ctrl2_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL2); + Adafruit_BusIO_RegisterBits test_bit = Adafruit_BusIO_RegisterBits(&ctrl2_reg, 1, 6); + + // Set self-test bit + test_bit.write(1); + + // Wait for self-test to complete (datasheet suggests 5ms) + delay(5); + + // Check if self-test bit auto-cleared (indicates completion) + uint8_t test_status = test_bit.read(); + return (test_status == 0); +} + +/*! + * @brief Sets the magnetic field range + * @param range + * The magnetic field range to set + */ +void Adafruit_QMC5883P::setRange(qmc5883p_range_t range) { + Adafruit_BusIO_Register ctrl2_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL2); + Adafruit_BusIO_RegisterBits range_bits = Adafruit_BusIO_RegisterBits(&ctrl2_reg, 2, 2); + range_bits.write(range); +} + +/*! + * @brief Gets the current magnetic field range + * @return The current magnetic field range + */ +qmc5883p_range_t Adafruit_QMC5883P::getRange() { + Adafruit_BusIO_Register ctrl2_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL2); + Adafruit_BusIO_RegisterBits range_bits = Adafruit_BusIO_RegisterBits(&ctrl2_reg, 2, 2); + return (qmc5883p_range_t)range_bits.read(); +} + +/*! + * @brief Sets the set/reset mode + * @param mode + * The set/reset mode to set + */ +void Adafruit_QMC5883P::setSetResetMode(qmc5883p_setreset_t mode) { + Adafruit_BusIO_Register ctrl2_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL2); + Adafruit_BusIO_RegisterBits setreset_bits = Adafruit_BusIO_RegisterBits(&ctrl2_reg, 2, 0); + setreset_bits.write(mode); +} + +/*! + * @brief Gets the current set/reset mode + * @return The current set/reset mode + */ +qmc5883p_setreset_t Adafruit_QMC5883P::getSetResetMode() { + Adafruit_BusIO_Register ctrl2_reg = Adafruit_BusIO_Register(i2c_dev, QMC5883P_REG_CONTROL2); + Adafruit_BusIO_RegisterBits setreset_bits = Adafruit_BusIO_RegisterBits(&ctrl2_reg, 2, 0); + return (qmc5883p_setreset_t)setreset_bits.read(); } \ No newline at end of file diff --git a/Adafruit_QMC5883P.h b/Adafruit_QMC5883P.h index 59e7f73..6996410 100644 --- a/Adafruit_QMC5883P.h +++ b/Adafruit_QMC5883P.h @@ -44,6 +44,73 @@ #define QMC5883P_REG_CONTROL2 0x0B ///< Control register 2 /*=========================================================================*/ +/*========================================================================= + CONTROL REGISTER 1 ENUMS + -----------------------------------------------------------------------*/ +/*! + * @brief Operating mode options + */ +typedef enum { + QMC5883P_MODE_SUSPEND = 0x00, ///< Suspend mode + QMC5883P_MODE_NORMAL = 0x01, ///< Normal mode + QMC5883P_MODE_SINGLE = 0x02, ///< Single measurement mode + QMC5883P_MODE_CONTINUOUS = 0x03 ///< Continuous mode +} qmc5883p_mode_t; + +/*! + * @brief Output data rate options + */ +typedef enum { + QMC5883P_ODR_10HZ = 0x00, ///< 10 Hz output data rate + QMC5883P_ODR_50HZ = 0x01, ///< 50 Hz output data rate + QMC5883P_ODR_100HZ = 0x02, ///< 100 Hz output data rate + QMC5883P_ODR_200HZ = 0x03 ///< 200 Hz output data rate +} qmc5883p_odr_t; + +/*! + * @brief Over sample ratio options + */ +typedef enum { + QMC5883P_OSR_8 = 0x00, ///< Over sample ratio = 8 + QMC5883P_OSR_4 = 0x01, ///< Over sample ratio = 4 + QMC5883P_OSR_2 = 0x02, ///< Over sample ratio = 2 + QMC5883P_OSR_1 = 0x03 ///< Over sample ratio = 1 +} qmc5883p_osr_t; + +/*! + * @brief Downsample ratio options + */ +typedef enum { + QMC5883P_DSR_1 = 0x00, ///< Downsample ratio = 1 + QMC5883P_DSR_2 = 0x01, ///< Downsample ratio = 2 + QMC5883P_DSR_4 = 0x02, ///< Downsample ratio = 4 + QMC5883P_DSR_8 = 0x03 ///< Downsample ratio = 8 +} qmc5883p_dsr_t; +/*=========================================================================*/ + +/*========================================================================= + CONTROL REGISTER 2 ENUMS + -----------------------------------------------------------------------*/ +/*! + * @brief Field range options + */ +typedef enum { + QMC5883P_RANGE_30G = 0x00, ///< ±30 Gauss range + QMC5883P_RANGE_12G = 0x01, ///< ±12 Gauss range + QMC5883P_RANGE_8G = 0x02, ///< ±8 Gauss range + QMC5883P_RANGE_2G = 0x03 ///< ±2 Gauss range +} qmc5883p_range_t; + +/*! + * @brief Set/Reset mode options + */ +typedef enum { + QMC5883P_SETRESET_ON = 0x00, ///< Set and reset on + QMC5883P_SETRESET_SETONLY = 0x01, ///< Set only on + QMC5883P_SETRESET_OFF = 0x02 ///< Set and reset off +} qmc5883p_setreset_t; +/*=========================================================================*/ + /*! * @brief Class for hardware interfacing with the QMC5883P 3-axis magnetometer */ @@ -53,6 +120,24 @@ public: ~Adafruit_QMC5883P(void); bool begin(uint8_t i2c_addr = QMC5883P_DEFAULT_ADDR, TwoWire *wire = &Wire); + bool getRawMagnetic(int16_t *x, int16_t *y, int16_t *z); + bool getGaussField(float *x, float *y, float *z); + bool isDataReady(); + bool isOverflow(); + void setMode(qmc5883p_mode_t mode); + qmc5883p_mode_t getMode(); + void setODR(qmc5883p_odr_t odr); + qmc5883p_odr_t getODR(); + void setOSR(qmc5883p_osr_t osr); + qmc5883p_osr_t getOSR(); + void setDSR(qmc5883p_dsr_t dsr); + qmc5883p_dsr_t getDSR(); + bool softReset(); + bool selfTest(); + void setRange(qmc5883p_range_t range); + qmc5883p_range_t getRange(); + void setSetResetMode(qmc5883p_setreset_t mode); + qmc5883p_setreset_t getSetResetMode(); private: Adafruit_I2CDevice *i2c_dev; ///< Pointer to I2C bus interface diff --git a/examples/test_QMC5883P/test_QMC5883P.ino b/examples/test_QMC5883P/test_QMC5883P.ino index d4f658a..5d8af5a 100644 --- a/examples/test_QMC5883P/test_QMC5883P.ino +++ b/examples/test_QMC5883P/test_QMC5883P.ino @@ -20,8 +20,180 @@ void setup() { } Serial.println("QMC5883P Found!"); + + // Set to normal mode + qmc.setMode(QMC5883P_MODE_NORMAL); + + qmc5883p_mode_t currentMode = qmc.getMode(); + Serial.print("Mode: "); + switch(currentMode) { + case QMC5883P_MODE_SUSPEND: + Serial.println("Suspend"); + break; + case QMC5883P_MODE_NORMAL: + Serial.println("Normal"); + break; + case QMC5883P_MODE_SINGLE: + Serial.println("Single"); + break; + case QMC5883P_MODE_CONTINUOUS: + Serial.println("Continuous"); + break; + default: + Serial.println("Unknown"); + break; + } + + // Set ODR (Output Data Rate) to 50Hz + qmc.setODR(QMC5883P_ODR_50HZ); + qmc5883p_odr_t currentODR = qmc.getODR(); + Serial.print("ODR (Output Data Rate): "); + switch(currentODR) { + case QMC5883P_ODR_10HZ: + Serial.println("10Hz"); + break; + case QMC5883P_ODR_50HZ: + Serial.println("50Hz"); + break; + case QMC5883P_ODR_100HZ: + Serial.println("100Hz"); + break; + case QMC5883P_ODR_200HZ: + Serial.println("200Hz"); + break; + default: + Serial.println("Unknown"); + break; + } + + // Set OSR (Over Sample Ratio) to 4 + qmc.setOSR(QMC5883P_OSR_4); + qmc5883p_osr_t currentOSR = qmc.getOSR(); + Serial.print("OSR (Over Sample Ratio): "); + switch(currentOSR) { + case QMC5883P_OSR_8: + Serial.println("8"); + break; + case QMC5883P_OSR_4: + Serial.println("4"); + break; + case QMC5883P_OSR_2: + Serial.println("2"); + break; + case QMC5883P_OSR_1: + Serial.println("1"); + break; + default: + Serial.println("Unknown"); + break; + } + + // Set DSR (Downsample Ratio) to 2 + qmc.setDSR(QMC5883P_DSR_2); + qmc5883p_dsr_t currentDSR = qmc.getDSR(); + Serial.print("DSR (Downsample Ratio): "); + switch(currentDSR) { + case QMC5883P_DSR_1: + Serial.println("1"); + break; + case QMC5883P_DSR_2: + Serial.println("2"); + break; + case QMC5883P_DSR_4: + Serial.println("4"); + break; + case QMC5883P_DSR_8: + Serial.println("8"); + break; + default: + Serial.println("Unknown"); + break; + } + + // Set Range to 8G + qmc.setRange(QMC5883P_RANGE_8G); + qmc5883p_range_t currentRange = qmc.getRange(); + Serial.print("Range: "); + switch(currentRange) { + case QMC5883P_RANGE_30G: + Serial.println("±30G"); + break; + case QMC5883P_RANGE_12G: + Serial.println("±12G"); + break; + case QMC5883P_RANGE_8G: + Serial.println("±8G"); + break; + case QMC5883P_RANGE_2G: + Serial.println("±2G"); + break; + default: + Serial.println("Unknown"); + break; + } + + // Set SetReset mode to On + qmc.setSetResetMode(QMC5883P_SETRESET_ON); + qmc5883p_setreset_t currentSetReset = qmc.getSetResetMode(); + Serial.print("Set/Reset Mode: "); + switch(currentSetReset) { + case QMC5883P_SETRESET_ON: + Serial.println("Set and Reset On"); + break; + case QMC5883P_SETRESET_SETONLY: + Serial.println("Set Only On"); + break; + case QMC5883P_SETRESET_OFF: + Serial.println("Set and Reset Off"); + break; + default: + Serial.println("Unknown"); + break; + } } void loop() { - delay(1000); + static int range_counter = 0; + static unsigned long last_range_change = 0; + + // Change range every 3 seconds + if (millis() - last_range_change > 3000) { + qmc5883p_range_t ranges[] = {QMC5883P_RANGE_2G, QMC5883P_RANGE_8G, QMC5883P_RANGE_12G, QMC5883P_RANGE_30G}; + const char* range_names[] = {"±2G", "±8G", "±12G", "±30G"}; + + qmc.setRange(ranges[range_counter]); + Serial.print("*** Changed to range: "); + Serial.println(range_names[range_counter]); + + range_counter = (range_counter + 1) % 4; + last_range_change = millis(); + delay(100); // Let range change settle + } + + if (qmc.isDataReady()) { + int16_t x, y, z; + float gx, gy, gz; + + if (qmc.getRawMagnetic(&x, &y, &z)) { + Serial.print("Raw - X: "); Serial.print(x); + Serial.print(" Y: "); Serial.print(y); + Serial.print(" Z: "); Serial.print(z); + + if (qmc.getGaussField(&gx, &gy, &gz)) { + Serial.print(" | Gauss - X: "); Serial.print(gx, 3); + Serial.print(" Y: "); Serial.print(gy, 3); + Serial.print(" Z: "); Serial.println(gz, 3); + } else { + Serial.println(" | Failed to convert to Gauss"); + } + + if (qmc.isOverflow()) { + Serial.println("WARNING: Data overflow detected!"); + } + } else { + Serial.println("Failed to read magnetic data"); + } + } + + delay(200); } \ No newline at end of file