Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
71 changes: 71 additions & 0 deletions MMC5983MA_I2C/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# STM32 C++ Driver for MMC5983MA (I2C)

### Description
This is a high-performance C++ driver for the MEMSIC MMC5983MA 3-axis magnetometer, designed for STM32 microcontrollers using the HAL library.

**Key Feature:** This driver uses a **Stateless I2C Wrapper**, making it thread-safe.

---

### Features
* **Stateless Architecture:** Wrapper stores no state, preventing race conditions in preemptive environments.
* **Standard I2C Support:** Communicates via the standard 2-wire I2C interface.
* **Core Functionality:**
* Read Product ID (Validation).
* Trigger Magnetic Field Measurements.
* Perform SET and RESET operations (De-gaussing).
* Read and scale 18-bit X, Y, Z magnetic data (in Gauss).

---

### Project Structure
* `mmc5983ma_i2c.hpp` / `.cpp`: The main driver class. Handles register logic and data conversion.
* `i2c_wrapper.hpp` / `.cpp`: A lightweight abstraction layer for STM32 HAL I2C functions.
* `mmc5983ma_regs.hpp`: Register map and bit definitions.
* `main_read_test.cpp`: Example implementation.

---

### Dependencies
* **STM32 HAL Library:** Requires `stm32f4xx_hal.h` (or equivalent for your F1/H7/L4 series).
* **I2C Handle:** You must initialize an `I2C_HandleTypeDef` (e.g., `hi2c1`) in your `main.c`.

---

### How to Use

1. **Include Headers:**
```cpp
#include "i2c_wrapper.hpp"
#include "mmc5983ma_i2c.hpp"
```

2. **Instantiate Objects:**
Since the driver is stateless, you create the wrapper and pass it by pointer to the driver, along with the device address.
```cpp
// 1. Create Wrapper (Pass the HAL Handle)
I2C_Wrapper i2cBus(&hi2c1);

// 2. Create Driver (Pass Wrapper Pointer + I2C Address)
// Standard Address: 0x30 (0110000 shifted left by 1 is 0x60, handled internally)
MMC5983MA mag(&i2cBus, 0x30);
```

3. **Initialization & Reading:**
```cpp
if (mag.begin()) {
// Init success
}

// In your loop:
mag.triggerMeasurement();
HAL_Delay(10); // Wait for conversion (~8ms)

MagData data;
if (mag.readData(data)) {
// Use data.scaledX, data.scaledY, data.scaledZ
}
```

### Address Note
The MMC5983MA 7-bit address is `0x30`. The driver automatically handles the left-shift required by the HAL library.
22 changes: 22 additions & 0 deletions MMC5983MA_I2C/i2c_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* i2c_wrapper.cpp
*/
#include "i2c_wrapper.hpp"

I2C_Wrapper::I2C_Wrapper(I2C_HandleTypeDef* hi2c) : _hi2c(hi2c) {
}

bool I2C_Wrapper::writeByte(std::uint8_t devAddr, std::uint8_t regAddr, std::uint8_t data) {
// HAL_I2C_Mem_Write handles Reg Addr + Data Sequence
return HAL_I2C_Mem_Write(_hi2c, devAddr, regAddr, I2C_MEMADD_SIZE_8BIT, &data, 1, 100) == HAL_OK;
}

std::uint8_t I2C_Wrapper::readByte(std::uint8_t devAddr, std::uint8_t regAddr) {
std::uint8_t data = 0;
// HAL_I2C_Mem_Read handles Reg Addr + Data Sequence
return HAL_I2C_Mem_Read(_hi2c, devAddr, regAddr, I2C_MEMADD_SIZE_8BIT, &data, 1, 100);
}

bool I2C_Wrapper::readBytes(std::uint8_t devAddr, std::uint8_t regAddr, std::uint8_t *data, std::uint8_t len) {
return HAL_I2C_Mem_Read(_hi2c, devAddr, regAddr, I2C_MEMADD_SIZE_8BIT, data, len, 100) == HAL_OK;
}
53 changes: 53 additions & 0 deletions MMC5983MA_I2C/i2c_wrapper.hpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/*
* i2c_wrapper.hpp
*/

#ifndef I2C_WRAPPER_HPP
#define I2C_WRAPPER_HPP

#include <cstdint>

extern "C" {
#include "stm32f4xx_hal.h"
}

class I2C_Wrapper {
public:
/**
* @brief Constructor.
* @param hi2c Pointer to the HAL I2C handle
*/

I2C_Wrapper(I2C_HandleTypeDef* hi2c);

/**
* @brief Writes a single byte to a specific register.
* @param devAddr The 8-bit device address (left-shifted)
* @param regAddr The register address to write to.
* @param data The byte to write.
* @return True if successful (HAL_OK), false otherwise.
*/
bool writeByte(std::uint8_t devAddr, std::uint8_t regAddr, std::uint8_t data);

/**
* @brief Reads a single byte from a specific register.
* @param devAddr The 8-bit device address (left-shifted)
* @param regAddr The register address to read from.
* @return 1 if successful, 0 otherwise.
*/
std::uint8_t readByte(std::uint8_t devAddr, std::uint8_t regAddr);

/**
* @brief Reads Multiple bytes from a specific register.
* @param devAddr The 8-bit device address (left-shifted)
* @param regAddr The register address to read from.
* @param data Pointer to the buffer to store read data.
* @param len Number of bytes to read.
* @return True if successful (HAL_OK), false otherwise.
*/
bool readBytes(std::uint8_t devAddr, std::uint8_t regAddr, std::uint8_t data[], std::uint8_t len);
private:
I2C_HandleTypeDef* _hi2c;
};

#endif // I2C_WRAPPER_HPP
85 changes: 85 additions & 0 deletions MMC5983MA_I2C/main_read_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/*
* main_read_test.cpp
*
* Test application for MMC5983MA I2C Driver.
*/

extern "C" {
#include "main.h" // Contains I2C handle declaration (hi2c1)
}

#include "i2c_wrapper.hpp"
#include "mmc5983ma_i2c.hpp" // Ensure this matches your actual header filename

#include <stdio.h>

// ##################################################################
// ## Hardware Configuration ##
// ##################################################################

// 1. Define your I2C Handle (e.g., hi2c1, hi2c2)
extern I2C_HandleTypeDef hi2c1;
#define MMC_I2C_HANDLE &hi2c1

// 2. Define Sensor Address
// Datasheet (Pg 9) specifies 7-bit address: 0110000 (0x30)
#define MMC_I2C_ADDR 0x30

// ##################################################################

// 1. Create the I2C Wrapper instance
I2C_Wrapper i2cBus(MMC_I2C_HANDLE);

// 2. Create the MMC5983MA Driver instance
// Pass pointer to wrapper and the I2C address
MMC5983MA magnetometer(&i2cBus, MMC_I2C_ADDR);

// 3. Data container
MagData magData;

/**
* @brief The application entry point.
*/
int main(void)
{
// --- HAL Init (Usually done in main.c) ---
// HAL_Init();
// SystemClock_Config();
// MX_I2C1_Init();

printf("--- MMC5983MA I2C Test ---\r\n");

// 4. Initialize the sensor
if (magnetometer.begin()) {
printf("Sensor Initialized. Product ID Verified.\r\n");
} else {
printf("Sensor Init Failed! Check wiring and pull-ups.\r\n");
while (1) {
HAL_Delay(1000); // Trap on failure
}
}

// --- Main Loop ---
while (1)
{
// 5. Trigger Measurement
magnetometer.triggerMeasurement();

// 6. Wait for measurement to complete
// Default bandwidth (100Hz) takes ~8ms
HAL_Delay(10);

// 7. Read Data
if (magnetometer.readData(magData)) {
// Print scaled values
printf("X: %.4f G | Y: %.4f G | Z: %.4f G\r\n",
magData.scaledX,
magData.scaledY,
magData.scaledZ);
} else {
printf("Data not ready yet.\r\n");
}

HAL_Delay(500); // Read every 500ms
}
}
100 changes: 100 additions & 0 deletions MMC5983MA_I2C/mmc5983ma_i2c.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
/*
* mmc5983ma.cpp
*
* Implementation of the MMC5983MA driver.
*/

#include "mmc5983ma_i2c.hpp"
#include "mmc5983ma_regs.hpp"
#include "i2c_wrapper.hpp"
#include <cstdint>

using std::uint8_t;
using std::uint16_t;
using std::uint32_t;


// Constructor
MMC5983MA::MMC5983MA(I2C_Wrapper* i2cBus, uint8_t adress) : _i2c(i2cBus) {
_address = (adress << 1); // left shift for HAL compatibility
}

bool MMC5983MA::begin(){
uint8_t productID = getProductID();

return (productID == MMC5983MA_PRODUCT_ID_VALUE);
}

uint8_t MMC5983MA::getProductID(){
// (P ID at 0x2F)
return (readRegister(MMC5983MA_P_ID));
}


void MMC5983MA::triggerMeasurement(){
writeRegister(MMC5983MA_IT_CONTROL0, MMC5983MA_TM_M);
}

void MMC5983MA::performSet(){
writeRegister(MMC5983MA_IT_CONTROL0, MMC5983MA_SET);
}


void MMC5983MA::performReset(){
writeRegister(MMC5983MA_IT_CONTROL0, MMC5983MA_RESET);
}





bool MMC5983MA::readData(MagData& data) {
// check Status reg
uint8_t status = readRegister(MMC5983MA_STATUS);
if (!(status & MMC5983MA_MEAS_M_DONE)) {
return false; // Data not ready
}

// Read 7 measurement regs at once.
uint8_t buffer[7];
readRegisters(MMC5983MA_XOUT0, buffer, 7);

// Combine bytes into raw 18-bit values
data.rawX = ((uint32_t)buffer[0] << 10) |
((uint32_t)buffer[1] << 2) |
((uint32_t)(buffer[6] & 0xC0) >> 6);

data.rawY = ((uint32_t)buffer[2] << 10) |
((uint32_t)buffer[3] << 2) |
((uint32_t)(buffer[6] & 0x30) >> 4);

data.rawZ = ((uint32_t)buffer[4] << 10) |
((uint32_t)buffer[5] << 2) |
((uint32_t)(buffer[6] & 0x0C) >> 2);


// Apply scaling factors (Gauss)
data.scaledX = ((float)data.rawX - _nullFieldOffset) / _countsPerGauss;
data.scaledY = ((float)data.rawY - _nullFieldOffset) / _countsPerGauss;
data.scaledZ = ((float)data.rawZ - _nullFieldOffset) / _countsPerGauss;

return true;
}



/* ------------- PRIVATE HELPERS ------------- */

void MMC5983MA::writeRegister(uint8_t reg, uint8_t value) {
_i2c->writeByte(_address, reg, value);
}

uint8_t MMC5983MA::readRegister(uint8_t reg) {
return _i2c->readByte(_address, reg);
}

void MMC5983MA::readRegisters(uint8_t reg, uint8_t* buffer, uint8_t len) {
_i2c->readBytes(_address, reg, buffer, len);
}

/* MMC5983MA_CPP */
Loading