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
67 changes: 67 additions & 0 deletions MMC5983MA_SPI/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# STM32 C++ Driver for MMC5983MA

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

This driver communicates with the sensor over the SPI interface and is built to be easily integrated into STM32 projects.

---

### Features
* Read the device Product ID.
* Trigger single-shot magnetic field measurements.
* Perform SET and RESET operations.
* Read 18-bit raw data for X, Y, and Z axes.
* Calculate scaled magnetic field data in Gauss.

---

### Project Structure
* `mmc5983ma.hpp`: The main driver class header. It defines the `MMC5983MA` class, public functions, and private helpers.
* `mmc5983ma.cpp`: The driver implementation file. Contains the logic for all class functions.
* `mmc5983ma_regs.hpp`: A helper header that defines all register addresses and bitmasks for the sensor.
* `spi_wrapper.hpp` / `spi_wrapper.cpp`: An abstraction layer that wraps the STM32 HAL SPI functions (`HAL_SPI_TransmitReceive`, etc.) into a simple C++ class. The `MMC5983MA` driver uses this wrapper for all SPI communication.
* `MMC5983MA_RevA_4-3-19.pdf`: The official sensor datasheet.

---

### Dependencies
* **STM32 HAL Library:** The driver depends on HAL types (`SPI_HandleTypeDef`, `GPIO_TypeDef*`, etc.).
* **`spi_wrapper`:** The `MMC5983MA` class requires a pointer to an initialized `SPI_Wrapper` object.

---

### How to Use
Here is the high-level workflow for integrating the driver:

1. **Include Files:**
In your main application file, include the necessary headers:
```cpp
#include "spi_wrapper.hpp"
#include "mmc5983ma.hpp"
```

2. **Ensure Hardware is Configured:**
Before using the driver, make sure your STM32's `SPI_HandleTypeDef` (e.g., `hspi1`) and the Chip Select (CS) GPIO pin are configured and initialized by the HAL (e.g., in `main.c` via STM32CubeMX).

3. **Create Driver Instances:**
* Create an instance of `SPI_Wrapper`, passing it a pointer to your initialized `SPI_HandleTypeDef`.
* Create an instance of `MMC5983MA`, passing it a pointer to your `SPI_Wrapper` instance, along with the `GPIO_TypeDef*` and `uint16_t` pin number for your CS pin.

4. **Initialize the Sensor:**
* Call the `.begin()` method on your `MMC5983MA` object.
* Check the return value (`bool`) to confirm that communication was successful and the sensor's Product ID was correctly read.

5. **Read Data in Your Main Loop:**
* Call `.triggerMeasurement()` to request a new reading from the sensor.
* Wait for the measurement to complete (refer to the datasheet for measurement time, e.g., `HAL_Delay(10)` for 100Hz bandwidth).
* Create a `MagData` struct variable.
* Call `.readData(your_mag_data_struct)`. If it returns `true`, your struct is now populated with the latest X, Y, and Z data.

---

### Complete Example
Check `main_read_test.cpp` for a complete example on this.

### Datasheet
Initial commit, MMC5983MA magnetometer project Structure.
117 changes: 117 additions & 0 deletions MMC5983MA_SPI/main_read_test.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
/*
* main_read_test.cpp
*
* A complete example of how to initialize and read data from
* the MMC5983MA magnetometer using the C++ driver.
*
* Assumes HAL initialization for SPI and GPIOs is done in main.c.
*/

// Include the HAL library for SPI_HandleTypeDef, GPIO_TypeDef, etc.
// The exact path might vary based on your project structure.
extern "C" {
#include "main.h" // Or "stm32f4xx_hal.h" directly
}

// Include the driver files
#include "spi_wrapper.hpp"
#include "mmc5983ma.hpp"

// For printf debugging (e.g., via SWV/ITM)
#include <stdio.h>

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

// --- IMPORTANT ---
// You must update these definitions to match your hardware configuration
// set up in your .ioc (STM32CubeMX) file.

// 1. Define your SPI Handle (e.g., hspi1, hspi2)
// This handle must be initialized in main.c
extern SPI_HandleTypeDef hspi1;
#define MMC_SPI_HANDLE &hspi1

// 2. Define your Chip Select (CS) Pin Port and Pin
// This pin must be configured as a GPIO_Output in main.c
#define MMC_CS_PORT GPIOA
#define MMC_CS_PIN GPIO_PIN_4

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

// 1. Create an instance of the SPI_Wrapper
// Pass it the pointer to your initialized HAL SPI handle.
SPI_Wrapper spiBus(MMC_SPI_HANDLE);

// 2. Create an instance of the MMC5983MA driver
// Pass it the SPI wrapper, CS port, and CS pin.
MMC5983MA magnetometer(&spiBus, MMC_CS_PORT, MMC_CS_PIN);

// 3. Create a struct to hold the magnetometer data
MagData magData;

/**
* @brief The application entry point.
* @retval int
*/
int main(void)
{
// --- HAL and System Init ---
// (This is typically called before main() or at the start)
// HAL_Init();
// SystemClock_Config();
// MX_GPIO_Init();
// MX_SPI1_Init();
// ...

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

// 4. Initialize the sensor
if (magnetometer.begin()) {
printf("Sensor initialized successfully. Product ID OK.\r\n");
} else {
printf("Sensor initialization failed! Check wiring or SPI config.\r\n");
while (1) {
// Hang here on failure
HAL_Delay(1000);
}
}

// --- Main Application Loop ---
while (1)
{
// 5. Trigger a new measurement
magnetometer.triggerMeasurement();

// 6. Wait for the measurement to complete.
// The README suggests 10ms for 100Hz bandwidth.
// Refer to the datasheet (spi/MMC5983MA_RevA_4-3-19.pdf) for exact times.
HAL_Delay(10); // 10ms delay

// 7. Attempt to read the data
if (magnetometer.readData(magData)) {
// If readData() returns true, the 'magData' struct is populated.

// Print the scaled data in Gauss
printf("X: %.4f G | Y: %.4f G | Z: %.4f G\r\n",
magData.scaledX,
magData.scaledY,
magData.scaledZ);

// You can also access the raw 18-bit integer data
// printf("Raw X: %ld | Raw Y: %ld | Raw Z: %ld\r\n",
// magData.rawX,
// magData.rawY,
// magData.rawZ);

} else {
// readData() returned false, meaning the "data ready"
// bit was not set. This might mean the HAL_Delay was too short.
printf("Failed to read data. Data not ready.\r\n");
}

// Wait before the next reading
HAL_Delay(1000); // Read once per second
}
}
156 changes: 156 additions & 0 deletions MMC5983MA_SPI/mmc5983ma.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
/*
* mmc5983ma.cpp
*
* Implementation of the MMC5983MA driver.
*/

#include "mmc5983ma.hpp"
#include "mmc5983ma_regs.hpp"
#include "spi_wrapper.hpp"
#include <cstdint>

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

/**
* @brief Constructor
*/
MMC5983MA::MMC5983MA(SPI_Wrapper* spiBus, GPIO_TypeDef* csPort, uint16_t csPin) :
_spi(spiBus),
_csPort(csPort),
_csPin(csPin)
{
// Constructor body.
// Set the chip select pin HIGH (idle) by default.
HAL_GPIO_WritePin(_csPort, _csPin, GPIO_PIN_SET);
}

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) {
// Read status register to check if data is ready
uint8_t status = readRegister(MMC5983MA_STATUS);

// Check if measurement done bit is set
if (!(status & MMC5983MA_MEAS_M_DONE)) {
return false; // Data not ready
}

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

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
data.scaledX = ((float)data.rawX - _nullFieldOffset) / _countsPerGauss;
data.scaledY = ((float)data.rawY - _nullFieldOffset) / _countsPerGauss;
data.scaledZ = ((float)data.rawZ - _nullFieldOffset) / _countsPerGauss;

return true;
}



/* ========================================================================*/
/* ========================PRIVATE HELPER FUNCTIONS========================*/
/* ========================================================================*/

void MMC5983MA::writeRegister(std::uint8_t reg, std::uint8_t value) {
// Write : R/W bit (0) == 0
uint8_t cmd_byte = (reg << 2) & 0xFC;
uint8_t txBuffer[2] = { cmd_byte, value };

// Pull cd Low to select the chip
HAL_GPIO_WritePin(_csPort, _csPin, GPIO_PIN_RESET);

// Use our wrapper to transmit the 2 bytes
_spi->transmit(txBuffer, 2);

// Pull CS High to deselect the chip
HAL_GPIO_WritePin(_csPort, _csPin, GPIO_PIN_SET);
}

uint8_t MMC5983MA::readRegister(uint8_t reg){
// Read : R/W bit (0) == 1
// Shift address left 2 bits, then OR with 0x01 to set the Read bit
uint8_t cmd_byte = ((reg << 2) & 0xFC) | 0x01;

// Pull CS Low
HAL_GPIO_WritePin(_csPort, _csPin, GPIO_PIN_RESET);

// 1. Send the command byte
_spi->transfer(cmd_byte);

// 2. Send dummy byte (0x00) to clock out the data
uint8_t rx_value = _spi->transfer(0x00);

// Pull CS High
HAL_GPIO_WritePin(_csPort, _csPin, GPIO_PIN_SET);

return rx_value;
}

/**
* @brief Reads multiple bytes from the sensor.
*/
void MMC5983MA::readRegisters(std::uint8_t reg, std::uint8_t* buffer, std::uint8_t len) {
// 1. Create the command byte (same as readRegister)
uint8_t cmd_byte = ((reg << 2) & 0xFC) | 0x01;

// Pull CS Low
HAL_GPIO_WritePin(_csPort, _csPin, GPIO_PIN_RESET);

// 2. Send the command/address byte
_spi->transfer(cmd_byte);

// 3. Read 'len' bytes into buffer
for (uint8_t i = 0; i < len; ++i) {
buffer[i] = _spi->transfer(0x00); // Send dummy byte to clock out data
}

// Pull CS High
HAL_GPIO_WritePin(_csPort, _csPin, GPIO_PIN_SET);


}
/* MMC5983MA_CPP */
Loading