diff --git a/MMC5983MA_SPI/README.md b/MMC5983MA_SPI/README.md new file mode 100644 index 0000000..ace55da --- /dev/null +++ b/MMC5983MA_SPI/README.md @@ -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. \ No newline at end of file diff --git a/MMC5983MA_SPI/main_read_test.cpp b/MMC5983MA_SPI/main_read_test.cpp new file mode 100644 index 0000000..c3a031c --- /dev/null +++ b/MMC5983MA_SPI/main_read_test.cpp @@ -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 + +// ################################################################## +// ## 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 + } +} \ No newline at end of file diff --git a/MMC5983MA_SPI/mmc5983ma.cpp b/MMC5983MA_SPI/mmc5983ma.cpp new file mode 100644 index 0000000..36d1367 --- /dev/null +++ b/MMC5983MA_SPI/mmc5983ma.cpp @@ -0,0 +1,156 @@ +/* + * mmc5983ma.cpp + * + * Implementation of the MMC5983MA driver. + */ + +#include "mmc5983ma.hpp" +#include "mmc5983ma_regs.hpp" +#include "spi_wrapper.hpp" +#include + +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 */ \ No newline at end of file diff --git a/MMC5983MA_SPI/mmc5983ma.hpp b/MMC5983MA_SPI/mmc5983ma.hpp new file mode 100644 index 0000000..1a45978 --- /dev/null +++ b/MMC5983MA_SPI/mmc5983ma.hpp @@ -0,0 +1,112 @@ +/* + * mmc5983ma.hpp + * + * C++ driver for the MMC5983MA magnetometer. + */ + +#ifndef MMC5983MA_HPP +#define MMC5983MA_HPP + +#include "mmc5983ma_regs.hpp" +#include "spi_wrapper.hpp" +#include +// if needed: fw declaration for the SPIClass from HAL/SPI wrapper, +// class SPIClass; +extern "C" { + #include "stm32f4xx_hal.h" +} + +struct MagData { + std::uint32_t rawX; + std::uint32_t rawY; + std::uint32_t rawZ; + float scaledX; + float scaledY; + float scaledZ; +}; + +class MMC5983MA { +public: + /** + * @brief Constructor + * @param spiBus Pointer to an initialized SPI wrapper instance. + * @param csPin The GPIO pin for chip select. + */ + MMC5983MA(SPI_Wrapper* spiBus, GPIO_TypeDef* csPort, std::uint16_t csPin); + + /** + * @brief Initializes the sensor. + * @return True on success (e.g., product ID matches), false otherwise. + */ + bool begin(); + + /** + * @brief Triggers a new magnetic field measurement. + */ + void triggerMeasurement(); + + /** + * @brief Reads the latest magnetic field data from the sensor. + * @return True if data is ready and read, false otherwise. + */ + bool readData(MagData& data); + + /** + * @brief Performs a SET operation. + */ + void performSet(); + + /** + * @brief Performs a RESET operation. + */ + void performReset(); + + /** + * @brief Reads the product ID register. + * @return The 8-bit product ID, or 0 on failure. + */ + std::uint8_t getProductID(); + + // --- More functions; later --- + // bool isDataReady(); + // void setBandwidth(std::uint8_t bw); + // float getTemperature(); + // void startContinuousMode(std::uint8_t freq); + // void stopContinuousMode(); + + +private: + /** + * @brief Writes a single byte to a sensor register. + * @param reg The register address. + * @param value The 8-bit value to write. + */ + void writeRegister(std::uint8_t reg, std::uint8_t value); + + /** + * @brief Reads a single byte from a sensor register. + * @param reg The register address. + * @return The 8-bit value read. + */ + std::uint8_t readRegister(std::uint8_t reg); + + /** + * @brief Reads multiple bytes from the sensor starting at a register. + * @param reg The starting register address. + * @param buffer Pointer to the buffer to store read data. + * @param len Number of bytes to read. + */ + void readRegisters(std::uint8_t reg, std::uint8_t* buffer, std::uint8_t len); + + // Member variables + SPI_Wrapper* _spi; + std::uint16_t _csPin; + GPIO_TypeDef* _csPort; + + + // Constants for scaling data + const float _countsPerGauss = 16384.0f; + const float _nullFieldOffset = 131072.0f; +}; + +#endif // MMC5983MA_HPP \ No newline at end of file diff --git a/MMC5983MA_SPI/mmc5983ma_regs.hpp b/MMC5983MA_SPI/mmc5983ma_regs.hpp new file mode 100644 index 0000000..e3e7aa8 --- /dev/null +++ b/MMC5983MA_SPI/mmc5983ma_regs.hpp @@ -0,0 +1,86 @@ +/* + * mmc5983ma_regs.hpp + * WIP | Feat mmc5983ma_regs - Part 1 + * Register definitions and constants for the MMC5983MA magnetometer. + */ +#ifndef MMC5983MA_REGS_HPP +#define MMC5983MA_REGS_HPP + +// Register Map +#define MMC5983MA_XOUT0 0x00 // Xout0 Register addresses +#define MMC5983MA_XOUT1 0x01 // Xout1 Register addresses +#define MMC5983MA_YOUT0 0x02 // Yout0 Register addresses +#define MMC5983MA_YOUT1 0x03 // Yout1 Register addresses +#define MMC5983MA_ZOUT0 0x04 // Zout0 Register addresses +#define MMC5983MA_ZOUT1 0x05 // Zout1 Register addresses +#define MMC5983MA_XYZOUT 0x06 // XYZout Register addresses +#define MMC5983MA_TOUT 0x07 // Temperature Out Unsigned [ (-75~125C); LSB == -75 ]Register addresses +#define MMC5983MA_STATUS 0x08 // Status Register addresses +#define MMC5983MA_IT_CONTROL0 0x09 // Register addresses +#define MMC5983MA_IT_CONTROL1 0x0A // Register addresses +#define MMC5983MA_IT_CONTROL2 0x0B // Register addresses +#define MMC5983MA_IT_CONTROL3 0x0C // Register addresses +#define MMC5983MA_P_ID 0x2F // Register addresses + +// Bit Masks for Status Register (0x08) +#define MMC5983MA_MEAS_M_DONE (1 << 0) // Magnetic Field Data Ready Bit Mask +#define MMC5983MA_MEAS_T_DONE (1 << 1) // Temperature Data Ready Bit Mask +#define MMC5983MA_OTP_RD_DONE (1 << 4) // Memory Read Successful Bit Mask + +// Bit Masks for IT_Control0 Register (0x09) +#define MMC5983MA_TM_M (1 << 0) // MF Measurement Bit Mask +#define MMC5983MA_TM_T (1 << 1) // T Measurement Bit Mask +#define MMC5983MA_INT_MEAS_DONE_EN (1 << 2) // Interrupt Measurement Done Enable Bit Mask +#define MMC5983MA_SET (1 << 3) // Setting Operation Bit Mask +#define MMC5983MA_RESET (1 << 4) // Soft Reset Bit Mask +#define MMC5983MA_AUTO_SR_EN (1 << 5) // Auto Self-Reset Enable Bit Mask +#define MMC5983MA_OTP_READ (1 << 6) // One-Time Programmable Read Bit Mask + +// Bit Masks for IT_Control1 Register (0x0A) +// Bandwidth (BW[1:0]) settings: measurement time and data output rate +#define MMC5983MA_BW_100HZ (0x00) // 00: 8ms, 100Hz +#define MMC5983MA_BW_200HZ (0x01) // 01: 4ms, 200Hz +#define MMC5983MA_BW_400HZ (0x02) // 10: 2ms, 400Hz +#define MMC5983MA_BW_800HZ (0x03) // 11: 0.5ms, 800Hz +// Channel inhibits +#define MMC5983MA_X_INHIBIT (1 << 3) // Disable X channel +#define MMC5983MA_Y_INHIBIT ((1 << 4) | (1 << 5)) // Disable Y and Z channels +// Software Reset +#define MMC5983MA_SW_RST (1 << 7) // Software Reset + + +// Bit Masks for IT_Control2 Register (0x0B) +// Continuous Measurement Frequency (CM_Freq[2:0]) +#define MMC5983MA_CM_FREQ_OFF (0x00) // 000: Continuous Mode Off +#define MMC5983MA_CM_FREQ_1Z (0x01) // 001: 1Hz +#define MMC5983MA_CM_FREQ_10Z (0x02) // 010: 10Hz +#define MMC5983MA_CM_FREQ_20Z (0x03) // 011: 20Hz +#define MMC5983MA_CM_FREQ_50Z (0x04) // 100: 50Hz +#define MMC5983MA_CM_FREQ_100Z (0x05) // 101: 100Hz +#define MMC5983MA_CM_FREQ_200Z (0x06) // 110: 200Hz (BW=01) +#define MMC5983MA_CM_FREQ_1000Z (0x07) // 111: 1000Hz (BW=11) +// Continuous Measurement Enable +#define MMC5983MA_CMM_EN (1 << 3) // Enable Continuous Mode +// Periodic Set (Prd_set[2:0]) - shifted to bits [6:4] +#define MMC5983MA_PRD_SET_1 (0x00 << 4) // 000: Every 1 measurement +#define MMC5983MA_PRD_SET_25 (0x01 << 4) // 001: Every 25 measurements +#define MMC5983MA_PRD_SET_75 (0x02 << 4) // 010: Every 75 measurements +#define MMC5983MA_PRD_SET_100 (0x03 << 4) // 011: Every 100 measurements +#define MMC5983MA_PRD_SET_250 (0x04 << 4) // 100: Every 250 measurements +#define MMC5983MA_PRD_SET_500 (0x05 << 4) // 101: Every 500 measurements +#define MMC5983MA_PRD_SET_1000 (0x06 << 4) // 110: Every 1000 measurements +#define MMC5983MA_PRD_SET_2000 (0x07 << 4) // 111: Every 2000 measurements +// Periodic Set Enable +#define MMC5983MA_EN_PRD_SET (1 << 7) // Enable Periodic Set + + +// Bit Masks for IT_Control3 Register (0x0C) +#define MMC5983MA_ST_ENP (1 << 2) // Self-test positive current +#define MMC5983MA_ST_ENM (1 << 3) // Self-test negative current +#define MMC5983MA_SPI_3W (1 << 6) // Enable 3-wire SPI mode + + +// P_ID Register (0x2F) +#define MMC5983MA_PRODUCT_ID_VALUE 0x28 // Default value of Product ID reg + +#endif // MMC5983MA_REGS_HPP \ No newline at end of file diff --git a/MMC5983MA_SPI/spi_wrapper.cpp b/MMC5983MA_SPI/spi_wrapper.cpp new file mode 100644 index 0000000..a8d89cb --- /dev/null +++ b/MMC5983MA_SPI/spi_wrapper.cpp @@ -0,0 +1,21 @@ +/* + * spi_wrapper.cpp + */ +#include "spi_wrapper.hpp" + +SPI_Wrapper::SPI_Wrapper(SPI_HandleTypeDef* hspi) : _hspi(hspi) { +} + +std::uint8_t SPI_Wrapper::transfer(std::uint8_t txByte) { + std::uint8_t rxByte = 0; + + // HAL to send one byte and receive one byte (txByte). + HAL_SPI_TransmitReceive(_hspi, &txByte, &rxByte, 1, 100); // 100ms timeout + + return rxByte; +} + +void SPI_Wrapper::transmit(std::uint8_t* data, std::uint16_t size) { + // HAL to transmit a block of data. + HAL_SPI_Transmit(_hspi, data, size, 100); // 100ms timeout +} \ No newline at end of file diff --git a/MMC5983MA_SPI/spi_wrapper.hpp b/MMC5983MA_SPI/spi_wrapper.hpp new file mode 100644 index 0000000..f4adc82 --- /dev/null +++ b/MMC5983MA_SPI/spi_wrapper.hpp @@ -0,0 +1,44 @@ +/* + * spi_wrapper.hpp + */ + +#ifndef SPI_WRAPPER_HPP +#define SPI_WRAPPER_HPP + +#include +//#include + +extern "C" { + #include "stm32f4xx_hal.h" +} + +#define COMM_ERROR 5 + +class SPI_Wrapper { +public: + /** + * @brief Constructor. + * @param hspi Pointer to the HAL SPI handle (e.g., &hspi1) + */ + + SPI_Wrapper(SPI_HandleTypeDef* hspi); + + /** + * @brief Transmits and receives a single byte. + * @param txByte The byte to send. + * @return The byte received. + */ + std::uint8_t transfer(std::uint8_t txByte); + + /** + * @brief Transmits a block of data. + * @param data Pointer to the data to send. + * @param size Number of bytes to send. + */ + void transmit(std::uint8_t* data, std::uint16_t size); + +private: + SPI_HandleTypeDef* _hspi; +}; + +#endif // SPI_WRAPPER_HPP \ No newline at end of file diff --git a/NAU7802/ExampleUsage.cpp b/NAU7802/ExampleUsage.cpp new file mode 100644 index 0000000..498bd55 --- /dev/null +++ b/NAU7802/ExampleUsage.cpp @@ -0,0 +1,56 @@ +/* Includes ------------------------------------------------------------------*/ +#include "main.h" +#include "I2C_Wrapper.hpp" +#include "NAU7802.hpp" + +/* Private variables ---------------------------------------------------------*/ +extern I2C_HandleTypeDef hi2c1; + +/* Function Prototypes -------------------------------------------------------*/ +void SystemClock_Config(void); +static void MX_GPIO_Init(void); +static void MX_I2C1_Init(void); + +int main(void) +{ + /* MCU Configuration--------------------------------------------------------*/ + HAL_Init(); + SystemClock_Config(); + MX_GPIO_Init(); + MX_I2C1_Init(); + + /* --- SETUP STAGE --- */ + // 1. Create wrapper and config structs + I2C_Wrapper i2c_bus_1(&hi2c1); + NAU7802_PARAMS adc_config; + NAU7802_OUT adc_data; + + // 3. Configure parameters + adc_config.initialGain = NAU7802_GAIN_128X; + + // 4. Create the driver object + NAU7802 adc(adc_config, i2c_bus_1); + // 5. Check if initialization was successful + if (!adc.get_isInitialized()) + { + // ADC not found or failed to configure + Error_Handler(); + } + + /* --- MAIN LOOP --- */ + while (1) + { + // 7. Check if data is ready + if (adc.isReady()) + { + // 8. Read data + if (adc.readSensor(&adc_data) == HAL_OK) + { + // Successfully read data + //Example Debug Print: + // printf("Raw ADC Value: %ld\r\n", adc_data.raw_reading); + } + } + HAL_Delay(100); + } +} \ No newline at end of file diff --git a/NAU7802/I2C_Wrapper.cpp b/NAU7802/I2C_Wrapper.cpp new file mode 100644 index 0000000..a9715f9 --- /dev/null +++ b/NAU7802/I2C_Wrapper.cpp @@ -0,0 +1,49 @@ +/* + * i2c_wrapper.cpp + */ + +#include "I2C_Wrapper.hpp" + +/** + * @brief Writes data to a register on the I2C device. + * @note Uses HAL_I2C_Mem_Write + */ +uint8_t I2C_Wrapper::writeReg() { + if (numBytes != 1) { + return HAL_ERROR; + } + + HAL_StatusTypeDef status = HAL_I2C_Mem_Write( + hi2c, + deviceAddress, + registerAddress, + I2C_MEMADD_SIZE_8BIT, + sendData.data(), + numBytes, + 100 // timeout + ); + + return (uint8_t)status; +} + +/** + * @brief Reads data from a register on the I2C device. + * @note Uses HAL_I2C_Mem_Read + */ +uint8_t I2C_Wrapper::readReg() { + HAL_StatusTypeDef status = HAL_I2C_Mem_Read( + hi2c, + deviceAddress, + registerAddress, + I2C_MEMADD_SIZE_8BIT, + receiveData.data(), + numBytes, + 100 // timeout + ); + + return (uint8_t)status; +} + +void I2C_Wrapper::updDeviceAddr(uint8_t newAddress) { + deviceAddress = newAddress; +} \ No newline at end of file diff --git a/NAU7802/I2C_Wrapper.hpp b/NAU7802/I2C_Wrapper.hpp new file mode 100644 index 0000000..27316a2 --- /dev/null +++ b/NAU7802/I2C_Wrapper.hpp @@ -0,0 +1,38 @@ +/* + * i2c_wrapper.hpp + */ + +#ifndef I2C_WRAPPER_HPP_ +#define I2C_WRAPPER_HPP_ + +#include + +extern "C" { + #include "stm32f4xx_hal.h" +} + +#define COMM_ERROR 5 + +class I2C_Wrapper { +public: + // Constructor + I2C_Wrapper(I2C_HandleTypeDef* i2c) + : hi2c(i2c), deviceAddress(0) {} + + // Variables + uint8_t registerAddress; + uint8_t numBytes; + std::array sendData; + std::array receiveData; + + // Functions + uint8_t writeReg(); + uint8_t readReg(); + void updDeviceAddr(uint8_t newAddress); + +private: + I2C_HandleTypeDef* hi2c; + uint8_t deviceAddress; +}; + +#endif /* I2C_WRAPPER_HPP_ */ \ No newline at end of file diff --git a/NAU7802/NAU7802.cpp b/NAU7802/NAU7802.cpp new file mode 100644 index 0000000..bd78f5c --- /dev/null +++ b/NAU7802/NAU7802.cpp @@ -0,0 +1,131 @@ +/* + * nau7802.cpp + * + * Implementation of the NAU7802 driver. + */ + +#include "NAU7802.hpp" +#include "stm32f4xx_hal.h" // Only needed for HAL_Delay and HAL_GetTick + +// Begin NAU7802 Startup Sequence +NAU7802::NAU7802(NAU7802_PARAMS configs, I2C_Wrapper& i2c_pointer) + : i2c(i2c_pointer), parameters(configs) +{ + isInitialized = false; + i2c.updDeviceAddr(NAU7802_I2C_ADDRESS); + + // 1. Send a reset command + if (!reset()) { + return; // Failed to reset + } + HAL_Delay(10); + + // 2. Power up the analog and digital sections + i2c.registerAddress = NAU7802_REG_PU_CTRL; + i2c.sendData[0] = NAU7802_PU_CTRL_PUD | NAU7802_PU_CTRL_PUA; + i2c.numBytes = 1; + if (i2c.writeReg() != HAL_OK) { + return; // Failed to write + } + + // 3. Wait for the Power Up Ready bit + uint32_t startTime = HAL_GetTick(); + while (HAL_GetTick() - startTime < 100) { // 100ms timeout + i2c.registerAddress = NAU7802_REG_PU_CTRL; + i2c.numBytes = 1; + if (i2c.readReg() != HAL_OK) { + return; // Failed to read + } + + if ((i2c.receiveData[0] & NAU7802_PU_CTRL_PUR) != 0) { + // Ready! Now set the initial gain. + if (!setGain(parameters.initialGain)) { + return; // Failed to set gain + } + + isInitialized = true; // Success! + return; + } + HAL_Delay(1); + } + + // Timeout occurred +} + +bool NAU7802::isReady() { + i2c.registerAddress = NAU7802_REG_PU_CTRL; + i2c.numBytes = 1; + if (i2c.readReg() != HAL_OK) { + return false; + } + return (i2c.receiveData[0] & NAU7802_PU_CTRL_CR) != 0; +} + +uint8_t NAU7802::readSensor(NAU7802_OUT *dest) { + i2c.registerAddress = NAU7802_REG_ADC_B2; // Start reading from MSB + i2c.numBytes = 3; // Read 3 bytes + uint8_t status = i2c.readReg(); + + if (status != HAL_OK) { + dest->raw_reading = 0; + return status; + } + + // Combine the three bytes from the wrapper's receiveData + int32_t value = ((int32_t)i2c.receiveData[0] << 16) | \ + ((int32_t)i2c.receiveData[1] << 8) | \ + (i2c.receiveData[2]); + + // Sign-extend the 24-bit value to a 32-bit integer + if (value & 0x00800000) { + value |= 0xFF000000; + } + + dest->raw_reading = value; + return status; +} + +bool NAU7802::reset() { + i2c.registerAddress = NAU7802_REG_PU_CTRL; + i2c.sendData[0] = NAU7802_PU_CTRL_RR; + i2c.numBytes = 1; + if (i2c.writeReg() != HAL_OK) return false; + + HAL_Delay(1); + + i2c.sendData[0] = 0x00; // Clear reset bit + if (i2c.writeReg() != HAL_OK) return false; + + return true; +} + +bool NAU7802::setGain(uint8_t gain) { + if (gain > NAU7802_GAIN_128X) { + return false; // Invalid gain setting + } + + // Read the current value of CTRL1 + i2c.registerAddress = NAU7802_REG_CTRL1; + i2c.numBytes = 1; + if (i2c.readReg() != HAL_OK) { + return false; + } + + uint8_t ctrl1_value = i2c.receiveData[0]; + ctrl1_value &= 0b11111000; // Clear the gain bits (bits 0, 1, 2) + ctrl1_value |= gain; // Set the new gain bits + + // Write the modified value back + i2c.registerAddress = NAU7802_REG_CTRL1; + i2c.sendData[0] = ctrl1_value; + i2c.numBytes = 1; + if (i2c.writeReg() != HAL_OK) { + return false; + } + + return true; +} + +bool NAU7802::get_isInitialized(void) { + return isInitialized; +} \ No newline at end of file diff --git a/NAU7802/NAU7802.hpp b/NAU7802/NAU7802.hpp new file mode 100644 index 0000000..2d4e9f0 --- /dev/null +++ b/NAU7802/NAU7802.hpp @@ -0,0 +1,58 @@ +/* + * nau7802.hpp + * + * Driver for the NAU7802 24-bit ADC. + */ +#ifndef NAU7802_HPP +#define NAU7802_HPP + +#include +#include "I2C_Wrapper.hpp" +#include "NAU7802_regs.hpp" + +/** + * @brief Configuration parameters for the NAU7802 driver. + */ +typedef struct NAU7802_DRIVER_PARAMETER { + uint8_t initialGain; // Use one of the NAU7802_GAIN_xxx macros +} NAU7802_PARAMS; + +/** + * @brief Output data structure for the NAU7802. + */ +typedef struct NAU7802_OUTPUT { + int32_t raw_reading; +} NAU7802_OUT; + + +class NAU7802 { +public: + /** + * @brief Constructs the NAU7802 Driver. + * @param configs Configuration settings for the sensor. + * @param i2c_pointer I2C Wrapper for communication. + */ + NAU7802(NAU7802_PARAMS configs, I2C_Wrapper& i2c_pointer); + + // Check if conversion is ready + bool isReady(); + + // Read 24-bit signed ADC value + uint8_t readSensor(NAU7802_OUT *dest); + + // Software reset + bool reset(); + + // Set PGA gain + bool setGain(uint8_t gain); + + // Check if the driver is initialized + bool get_isInitialized(void); + +private: + I2C_Wrapper& i2c; // Reference to the I2C wrapper + bool isInitialized; + NAU7802_PARAMS parameters; // Store the configs +}; + +#endif // NAU7802_HPP \ No newline at end of file diff --git a/NAU7802/NAU7802_regs.hpp b/NAU7802/NAU7802_regs.hpp new file mode 100644 index 0000000..d392847 --- /dev/null +++ b/NAU7802/NAU7802_regs.hpp @@ -0,0 +1,40 @@ +/* + * nau7802_regs.hpp + * + * Register definitions and constants for the NAU7802 ADC. + */ +#ifndef NAU7802_REGS_HPP +#define NAU7802_REGS_HPP + +// Fixed 7-bit I2C address of the NAU7802. [left-shifted for HAL functions] +#define NAU7802_I2C_ADDRESS (0x2A << 1) + +// Register Map +#define NAU7802_REG_PU_CTRL 0x00 +#define NAU7802_REG_CTRL1 0x01 +#define NAU7802_REG_CTRL2 0x02 +#define NAU7802_REG_ADC_B2 0x12 // ADC Result MSB +#define NAU7802_REG_ADC_B1 0x13 // ADC Result Mid-byte +#define NAU7802_REG_ADC_B0 0x14 // ADC Result LSB +#define NAU7802_REG_REVISION_ID 0x1F + +// Gain [in] settings for register +#define NAU7802_GAIN_1X 0b000 +#define NAU7802_GAIN_2X 0b001 +#define NAU7802_GAIN_4X 0b010 +#define NAU7802_GAIN_8X 0b011 +#define NAU7802_GAIN_16X 0b100 +#define NAU7802_GAIN_32X 0b101 +#define NAU7802_GAIN_64X 0b110 +#define NAU7802_GAIN_128X 0b111 + +// PU_CTRL Register Bits +#define NAU7802_PU_CTRL_RR (1 << 0) // Register Reset +#define NAU7802_PU_CTRL_PUD (1 << 1) // Power Up Digital +#define NAU7802_PU_CTRL_PUA (1 << 2) // Power Up Analog +#define NAU7802_PU_CTRL_PUR (1 << 3) // Power Up Ready (Read Only) +#define NAU7802_PU_CTRL_CR (1 << 5) // Cycle Ready (Read Only) +#define NAU7802_PU_CTRL_OSCS (1 << 6) // Select clock source +#define NAU7802_PU_CTRL_AVDDS (1 << 7) // Select internal LDO + +#endif // NAU7802_REGS_HPP \ No newline at end of file diff --git a/NAU7802/README.md b/NAU7802/README.md new file mode 100644 index 0000000..25ab6a4 --- /dev/null +++ b/NAU7802/README.md @@ -0,0 +1,90 @@ + +# STM32 C++ Driver for NAU7802 + +This is a simple, lightweight C++ driver for the Nuvoton NAU7802 24-bit ADC, designed to be used with the STM32 HAL library in a C++ environment (for example, STM32CubeIDE). + +### Features + +- Handles sensor initialization and power-up sequence +- Software reset +- Sets PGA gain (from 1x to 128x) +- Checks if new conversion data is ready +- Reads the 24-bit signed ADC result + +### Project Structure + +The driver is split into logical components to make it easy to understand and port: + +- `I2C_Wrapper.hpp` / `.cpp` + - A small C++ class that wraps the C-style STM32 HAL I2C functions (`HAL_I2C_Mem_Write` / `HAL_I2C_Mem_Read`). + - Acts as the low-level communication handler for register reads/writes. +- `NAU7802_regs.hpp` + - Register addresses, bit masks, and gain values for the NAU7802 (datasheet-based definitions). +- `NAU7802.hpp` / `.cpp` + - The main driver with logic for communicating with the sensor. + - Defines `NAU7802_PARAMS` for configuration and `NAU7802_OUT` for output data. + - Uses `I2C_Wrapper` to perform all register operations. +- `main_read_test.cpp` / `ExampleUsage.cpp` + - Example `main()` files demonstrating driver initialization and reading. + +### How to use + +Follow these steps in your project (assumes HAL and `hi2c1` are available): + +1. Include the headers in your `main.cpp` or equivalent: + +```c +#include "main.h" +#include "I2C_Wrapper.hpp" +#include "NAU7802.hpp" + +// Ensure hi2c1 is declared (defined in main.c) +extern I2C_HandleTypeDef hi2c1; +``` + +2. Initialize the driver (after `MX_I2C1_Init()`): + +```c +// Create the I2C wrapper and config/data structs +I2C_Wrapper i2c_bus_1(&hi2c1); +NAU7802_PARAMS adc_config; +NAU7802_OUT adc_data; + +// Configure parameters +adc_config.initialGain = NAU7802_GAIN_1X; // choose desired gain + +// Create the driver object (constructor will initialize the sensor) +NAU7802 adc(adc_config, i2c_bus_1); + +// Check initialization +if (!adc.get_isInitialized()) { + Error_Handler(); +} +``` + +3. Read data in your main loop: + +```c +while (1) { + if (adc.isReady()) { + if (adc.readSensor(&adc_data) == HAL_OK) { + // Use adc_data.raw_reading (32-bit signed) + // e.g. printf("Raw ADC: %ld\r\n", adc_data.raw_reading); + } + } + HAL_Delay(100); // 100 ms is reasonable for default sample rate +} +``` + +### Complete example + +See `main_read_test.cpp` or `ExampleUsage.cpp` in this driver folder for a complete example that initializes the sensor at 1x gain and continuously reads raw ADC values. + +### Dependencies + +- STM32 HAL (driver uses HAL I2C functions) +- C++11 or newer (used for small utilities like `std::array` in `I2C_Wrapper`) + +### Datasheet + +For full register details and calibration notes, consult the NAU7802 datasheet (e.g. en-us--DS_NAU7802_DataSheet_EN_Rev2.6.pdf). \ No newline at end of file diff --git a/NAU7802/en-us--DS_NAU7802_DataSheet_EN_Rev2.6.pdf b/NAU7802/en-us--DS_NAU7802_DataSheet_EN_Rev2.6.pdf new file mode 100644 index 0000000..8a8a37a Binary files /dev/null and b/NAU7802/en-us--DS_NAU7802_DataSheet_EN_Rev2.6.pdf differ diff --git a/NAU7802/main_read_test.cpp b/NAU7802/main_read_test.cpp new file mode 100644 index 0000000..a6d9ac8 --- /dev/null +++ b/NAU7802/main_read_test.cpp @@ -0,0 +1,73 @@ +/* + * main_nau7802_test.cpp + * + * Example usage file for the refactored NAU7802 driver. + * This example initializes the sensor with 1x gain + * and continuously reads the raw ADC value. + */ + +/* Includes ------------------------------------------------------------------*/ +#include "main.h" +#include "I2C_Wrapper.hpp" +#include "NAU7802.hpp" +#include // For printf debugging + +/* Private variables ---------------------------------------------------------*/ +// Assume hi2c1 is generated by STM32CubeIDE and initialized in main() +extern I2C_HandleTypeDef hi2c1; // Declaration of hi2c1 + +/* Function Prototypes -------------------------------------------------------*/ +// These are assumed to be in main.h or defined in main.c +void SystemClock_Config(void); +static void MX_GPIO_Init(void); +static void MX_I2C1_Init(void); +extern void Error_Handler(void); // Assuming this is defined in main.c + +/** + * @brief The application entry point. + */ +int main(void) +{ + /* MCU Configuration--------------------------------------------------------*/ + HAL_Init(); + SystemClock_Config(); + MX_GPIO_Init(); + MX_I2C1_Init(); // This initializes hi2c1 + + /* --- SETUP STAGE --- */ + // 1. create wrapper and config structs + I2C_Wrapper i2c_bus_1(&hi2c1); + NAU7802_PARAMS adc_config; + NAU7802_OUT adc_data; + // 3. Configure parameters + adc_config.initialGain = NAU7802_GAIN_1X; // Set to 1x gain as requested + + // 4. Create the driver object + NAU7802 adc(adc_config, i2c_bus_1); + + // 5. Check if initialization was successful + if (!adc.get_isInitialized()) + { + // ADC not found or failed to configure + // printf("NAU7802 Initialization Failed!\r\n"); + Error_Handler(); + } + + // printf("NAU7802 Initialized. Gain set to 1x.\r\n"); + + /* --- MAIN LOOP --- */ + while (1) + { + // 7. Check if data is ready (Step 6 is creating the output struct, done globally) + if (adc.isReady()) + { + // 8. Read data + if (adc.readSensor(&adc_data) == HAL_OK) + { + // You UART debug print here: + // printf("Raw ADC Value (Gain 1x): %ld\r\n", adc_data.raw_reading); + } + } + HAL_Delay(100); // Wait 100ms before checking again + } +} \ No newline at end of file