/**************************************************************************/ /*! @file MSA300.cpp @mainpage MSA300 14-bit digital accelometer library @section author Author Joel Lavikainen @section license License BSD @section intro_sec Introduction Based on Adafruit Accelerometer library code */ /**************************************************************************/ #include "Arduino.h" #include #include #include "MSA300.h" /**************************************************************************/ /*! @brief Writes 8-bits to the specified destination register. @param reg Register address @param value Byte value to be written */ /**************************************************************************/ void MSA300::writeRegister(uint8_t reg, uint8_t value) { Wire.beginTransmission(MSA300_I2C_ADDRESS); Wire.write((uint8_t)reg); Wire.write((uint8_t)(value)); Wire.endTransmission(); } /**************************************************************************/ /*! @brief Reads 8-bits from the specified register @param reg Address of register @return Reply byte */ /**************************************************************************/ uint8_t MSA300::readRegister(uint8_t reg) { Wire.beginTransmission(MSA300_I2C_ADDRESS); Wire.write(reg); Wire.endTransmission(); Wire.requestFrom(MSA300_I2C_ADDRESS, 1); uint8_t data =Wire.read(); return data; } /**************************************************************************/ /*! @brief Reads 16-bits from the specified register @param reg Register address @return 16-bits of data */ /**************************************************************************/ int16_t MSA300::read16(uint8_t reg) { Wire.beginTransmission(MSA300_I2C_ADDRESS); Wire.write(reg); Wire.endTransmission(); Wire.requestFrom(MSA300_I2C_ADDRESS, 2); return (uint16_t)(((Wire.read()&0xff) | (uint16_t)(Wire.read()&0xff) << 8)); } /**************************************************************************/ /*! @brief Read the part ID (can be used to check connection) @return Part ID */ /**************************************************************************/ uint8_t MSA300::getPartID(void) { // Check device ID register return readRegister(MSA300_REG_PARTID); } /**************************************************************************/ /*! @brief Gets the most recent X axis value @return X-axis acceleration data */ /**************************************************************************/ int16_t MSA300::getX(void) { int16_t temp = (readRegister(MSA300_REG_ACC_X_LSB) + (int8_t)(readRegister(MSA300_REG_ACC_X_MSB))*256)/4; return temp; } /**************************************************************************/ /*! @brief Gets the most recent Y axis value @return Y-axis acceleration data */ /**************************************************************************/ int16_t MSA300::getY(void) { int16_t temp = (readRegister(MSA300_REG_ACC_Y_LSB) + (int8_t)(readRegister(MSA300_REG_ACC_Y_MSB))*256)/4; return temp; } /**************************************************************************/ /*! @brief Gets the most recent Z axis value @return Z-axis acceleration data */ /**************************************************************************/ int16_t MSA300::getZ(void) { int16_t temp = (readRegister(MSA300_REG_ACC_Z_LSB) + (int8_t)(readRegister(MSA300_REG_ACC_Z_MSB))*256)/4; return temp; } /**************************************************************************/ /*! @brief Instantiates a new MSA300 class @param sensorID ID for identifying different sensors */ /**************************************************************************/ MSA300::MSA300(int32_t sensorID) { _sensorID = sensorID; _range = MSA300_RANGE_2_G; } /**************************************************************************/ /*! @brief Setups the HW (reads coefficients values, etc.) @return True if connection was established, False if no MSA300 was detected */ /**************************************************************************/ bool MSA300::begin() { Wire.begin(); /* Check connection */ uint8_t partid = getPartID(); if (partid != 0x13) { /* No MSA300 detected ... return false */ Serial.println(partid, HEX); return false; } _multiplier = MSA300_MG2G_MULTIPLIER_4_G; // Enable measurements writeRegister(MSA300_REG_PWR_MODE_BW, 0x10); // Normal mode & 500 Hz Bandwidth writeRegister(MSA300_REG_ODR, MSA300_DATARATE_1000_HZ); // Set Output Data Rate to 1000 Hz writeRegister(MSA300_REG_RES_RANGE, 0x01); writeRegister(MSA300_REG_INT_SET_0,0x00); writeRegister(MSA300_REG_INT_SET_1,0x00); writeRegister(MSA300_REG_INT_LATCH,0x81); return true; } /**************************************************************************/ /*! @brief Sets the g range for the accelerometer @param range Measurement range */ /**************************************************************************/ void MSA300::setRange(range_t range) { /* Read the data format register to preserve bits */ uint8_t format = readRegister(MSA300_REG_RES_RANGE); /* Update the range */ format &= ~0x3; // clear range bits format |= range; /* Write the register back to the IC */ writeRegister(MSA300_REG_RES_RANGE, format); /* Keep track of the current range (to avoid readbacks) */ _range = range; /* Map correct conversion multiplier */ switch(range) { case MSA300_RANGE_16_G: _multiplier = MSA300_MG2G_MULTIPLIER_16_G; break; case MSA300_RANGE_8_G: _multiplier = MSA300_MG2G_MULTIPLIER_8_G; break; case MSA300_RANGE_4_G: _multiplier = MSA300_MG2G_MULTIPLIER_4_G; break; case MSA300_RANGE_2_G: _multiplier = MSA300_MG2G_MULTIPLIER_2_G; } } /**************************************************************************/ /*! @brief Get the g range for the accelerometer @return Measurement range */ /**************************************************************************/ range_t MSA300::getRange(void) { return (range_t)(readRegister(MSA300_REG_RES_RANGE) & 0x3); } /**************************************************************************/ /*! @brief Sets the resolution for the accelerometer @param resolution Measurement resolution */ /**************************************************************************/ void MSA300::setResolution(res_t resolution) { /* Read the data format register to preserve bits */ uint8_t format = readRegister(MSA300_REG_RES_RANGE); /* Update the resolution */ format &= ~0xC; // clear resolution bits format |= resolution; /* Write the register back to the IC */ writeRegister(MSA300_REG_RES_RANGE, format); /* Keep track of the current resolution (to avoid readbacks) */ _res = resolution; } /**************************************************************************/ /*! @brief Get resolution for the accelerometer @return Measurement resolution */ /**************************************************************************/ res_t MSA300::getResolution(void) { return (res_t)(readRegister(MSA300_REG_RES_RANGE) & 0xC); } /**************************************************************************/ /*! @brief Sets the data rate for the MSA300 (controls power consumption) @param dataRate Output data rate */ /**************************************************************************/ void MSA300::setDataRate(dataRate_t dataRate) { /* Note: The LOW_POWER bits are currently ignored and we always keep the device in 'normal' mode */ writeRegister(MSA300_REG_ODR, dataRate); } /**************************************************************************/ /*! @brief Get the data rate for the MSA300 @return Output data rate */ /**************************************************************************/ dataRate_t MSA300::getDataRate(void) { return (dataRate_t)(readRegister(MSA300_REG_ODR) & 0x0F); } /**************************************************************************/ /*! @brief Sets the operating mode for MSA300 @param mode Power mode */ /**************************************************************************/ void MSA300::setMode(pwrMode_t mode) { /* Read the data format register to preserve bits */ uint8_t format = readRegister(MSA300_REG_PWR_MODE_BW); /* Update the mode */ format &= ~0xC0; // clear the mode bits format |= mode; /* Write the register back to the IC */ writeRegister(MSA300_REG_PWR_MODE_BW, format); /* Keep track of the current mode (to avoid readbacks) */ _mode = mode; } /**************************************************************************/ /*! @brief Gets the data rate for the MSA300 @return Power mode */ /**************************************************************************/ pwrMode_t MSA300::getMode(void) { return (pwrMode_t)(readRegister(MSA300_REG_PWR_MODE_BW) & 0xC0); } /**************************************************************************/ /*! @brief Reset all latched interrupts */ /**************************************************************************/ void MSA300::resetInterrupt(void) { /* Read register to preserve bits */ uint8_t reg = readRegister(MSA300_REG_INT_LATCH); /* Turn RESET_INT bit on */ reg |= (1 << 7); /* Write the register back to the IC */ writeRegister(MSA300_REG_INT_LATCH, reg); } /**************************************************************************/ /*! @brief Clear all interrupt registers by setting them to default state. */ /**************************************************************************/ void MSA300::clearInterrupts(void) { writeRegister(MSA300_REG_INT_SET_0, 0x00); writeRegister(MSA300_REG_INT_SET_1, 0x00); writeRegister(MSA300_REG_INT_MAP_0, 0x00); writeRegister(MSA300_REG_INT_MAP_2_1, 0x00); writeRegister(MSA300_REG_INT_MAP_2_2, 0x00); } /**************************************************************************/ /*! @brief Check interrupt registers for all occured interrupts. Return struct with booleans of all triggered interrupts. @return Struct containing boolean status of interrupts. */ /**************************************************************************/ interrupt_t MSA300::checkInterrupts(void) { interrupt_t interrupts; uint8_t motionReg = readRegister(MSA300_REG_MOTION_INT); uint8_t dataReg = readRegister(MSA300_REG_DATA_INT); uint8_t tapReg = readRegister(MSA300_REG_TAP_ACTIVE_STATUS); interrupts.orientInt = (motionReg >> 6) & 1; interrupts.sTapInt = (motionReg >> 5) & 1; interrupts.dTapInt = (motionReg >> 4) & 1; interrupts.activeInt = (motionReg >> 2) & 1; interrupts.freefallInt = (motionReg >> 0) & 1; interrupts.newDataInt = (dataReg >> 0) & 1; /* If there was active or tap interrupts, populate intStatus struct */ if(interrupts.activeInt || interrupts.sTapInt || interrupts.dTapInt) { interrupts.intStatus.tapSign = (tapReg >> 7) & 1; interrupts.intStatus.tapFirstX = (tapReg >> 6) & 1; interrupts.intStatus.tapFirstY = (tapReg >> 5) & 1; interrupts.intStatus.tapFirstZ = (tapReg >> 4) & 1; interrupts.intStatus.activeSign = (tapReg >> 3) & 1; interrupts.intStatus.activeFirstX = (tapReg >> 2) & 1; interrupts.intStatus.activeFirstY = (tapReg >> 1) & 1; interrupts.intStatus.activeFirstZ = (tapReg >> 0) & 1; } return interrupts; } /**************************************************************************/ /*! @brief Set interrupt latching mode @param mode Interrupt mode */ /**************************************************************************/ void MSA300::setInterruptLatch(intMode_t mode) { /* Read register to preserve bits */ uint8_t reg = readRegister(MSA300_REG_INT_LATCH); /* Update latching mode */ reg &= ~0xF0; reg |= mode; /* Write the register back to the IC */ writeRegister(MSA300_REG_INT_LATCH, reg); } /**************************************************************************/ /*! @brief Turn on active interrupt. Interrupt parameter corresponds to interrupt pins. @param axis Axis to set active interrupt on @param interrupt Index of interrupt (1 or 2) */ /**************************************************************************/ void MSA300::enableActiveInterrupt(axis_t axis, uint8_t interrupt) { uint8_t reg; switch(interrupt) { case 1: reg = readRegister(MSA300_REG_INT_MAP_0); reg |= (1 << 2); writeRegister(MSA300_REG_INT_MAP_0, reg); break; case 2: reg = readRegister(MSA300_REG_INT_MAP_2_1); reg |= (1 << 2); writeRegister(MSA300_REG_INT_MAP_2_1, reg); break; } reg = readRegister(MSA300_REG_INT_SET_0); switch(axis) { case MSA300_AXIS_X: reg |= (1 << 0); break; case MSA300_AXIS_Y: reg |= (1 << 1); break; case MSA300_AXIS_Z: reg |= (1 << 2); break; } writeRegister(MSA300_REG_INT_SET_0, reg); } /**************************************************************************/ /*! @brief Toggle freefall interrupt. Interrupt parameter corresponds to interrupt pins. @param interrupt Index of interrupt (1 or 2) */ /**************************************************************************/ void MSA300::enableFreefallInterrupt(uint8_t interrupt) { uint8_t reg; switch(interrupt) { case 1: reg = readRegister(MSA300_REG_INT_MAP_0); reg |= (1 << 0); writeRegister(MSA300_REG_INT_MAP_0, reg); break; case 2: reg = readRegister(MSA300_REG_INT_MAP_2_1); reg |= (1 << 0); writeRegister(MSA300_REG_INT_MAP_2_1, reg); break; } reg = readRegister(MSA300_REG_INT_SET_1); reg |= (1 << 3); writeRegister(MSA300_REG_INT_SET_1, reg); } /**************************************************************************/ /*! @brief Enable freefall interrupt. Interrupt parameter corresponds to interrupt pins. @param interrupt Index of interrupt (1 or 2) */ /**************************************************************************/ void MSA300::enableOrientationInterrupt(uint8_t interrupt) { uint8_t reg; switch(interrupt) { case 1: reg = readRegister(MSA300_REG_INT_MAP_0); reg |= (1 << 6); writeRegister(MSA300_REG_INT_MAP_0, reg); break; case 2: reg = readRegister(MSA300_REG_INT_MAP_2_1); reg |= (1 << 6); writeRegister(MSA300_REG_INT_MAP_2_1, reg); break; } reg = readRegister(MSA300_REG_INT_SET_0); reg |= (1 << 6); writeRegister(MSA300_REG_INT_SET_0, reg); } /**************************************************************************/ /*! @brief Enable single tap interrupt. Interrupt parameter corresponds to interrupt pins. @param interrupt Index of interrupt (1 or 2) */ /**************************************************************************/ void MSA300::enableSingleTapInterrupt(uint8_t interrupt) { uint8_t reg; switch(interrupt) { case 1: reg = readRegister(MSA300_REG_INT_MAP_0); reg |= (1 << 5); writeRegister(MSA300_REG_INT_MAP_0, reg); break; case 2: reg = readRegister(MSA300_REG_INT_MAP_2_1); reg |= (1 << 5); writeRegister(MSA300_REG_INT_MAP_2_1, reg); break; } reg = readRegister(MSA300_REG_INT_SET_0); reg |= (1 << 5); writeRegister(MSA300_REG_INT_SET_0, reg); } /**************************************************************************/ /*! @brief Enable double tap interrupt. Interrupt parameter corresponds to interrupt pins. @param interrupt Index of interrupt (1 or 2) */ /**************************************************************************/ void MSA300::enableDoubleTapInterrupt(uint8_t interrupt) { uint8_t reg; switch(interrupt) { case 1: reg = readRegister(MSA300_REG_INT_MAP_0); reg |= (1 << 4); writeRegister(MSA300_REG_INT_MAP_0, reg); break; case 2: reg = readRegister(MSA300_REG_INT_MAP_2_1); reg |= (1 << 4); writeRegister(MSA300_REG_INT_MAP_2_1, reg); break; } reg = readRegister(MSA300_REG_INT_SET_0); reg |= (1 << 4); writeRegister(MSA300_REG_INT_SET_0, reg); } /**************************************************************************/ /*! @brief Enable new data interrupt. Interrupt parameter corresponds to interrupt pins. @param interrupt Index of interrupt (1 or 2) */ /**************************************************************************/ void MSA300::enableNewDataInterrupt(uint8_t interrupt) { uint8_t reg; switch(interrupt) { case 1: reg = readRegister(MSA300_REG_INT_MAP_1); reg |= (1 << 0); writeRegister(MSA300_REG_INT_MAP_1, reg); break; case 2: reg = readRegister(MSA300_REG_INT_MAP_1); reg |= (1 << 7); writeRegister(MSA300_REG_INT_MAP_1, reg); break; } reg = readRegister(MSA300_REG_INT_SET_1); reg |= (1 << 4); writeRegister(MSA300_REG_INT_SET_1, reg); } /**************************************************************************/ /*! @brief Check orientation of the chip. @return Orientation struct containing z and xy-orientations */ /**************************************************************************/ orient_t MSA300::checkOrientation(void) { orient_t orientation; uint8_t reg = readRegister(MSA300_REG_ORIENT_STATUS); orientation.z = (zOrient_t)((reg >> 6) & 1); orientation.xy = (xyOrient_t)((reg >> 4) & 0x3); return orientation; } /**************************************************************************/ /*! @brief Set offset compensation value for specific axis. Value can vary from 0 to 998.4 mg. Values outside the range will be clamped. @param axis Axis to set offset on @param value Offset value (0 to 998.4 mg) */ /**************************************************************************/ void MSA300::setOffset(axis_t axis, float value) { uint8_t offset = value / 3.9f; uint8_t reg; offset = clamp(offset, 0, 998.4f); switch(axis) { case MSA300_AXIS_X: reg = readRegister(MSA300_REG_OFFSET_COMP_X); reg &= ~0xFF; reg |= offset; writeRegister(MSA300_REG_OFFSET_COMP_X, reg); break; case MSA300_AXIS_Y: reg = readRegister(MSA300_REG_OFFSET_COMP_Y); reg &= ~0xFF; reg |= offset; writeRegister(MSA300_REG_OFFSET_COMP_Y, reg); break; case MSA300_AXIS_Z: reg = readRegister(MSA300_REG_OFFSET_COMP_Z); reg &= ~0xFF; reg |= offset; writeRegister(MSA300_REG_OFFSET_COMP_Z, reg); break; } } /**************************************************************************/ /*! @brief Set threshold of tap interrupt. Value can vary from 0 to full scale of each range. Values outside the range will be clamped. @param value Tap threshold value (0 to full scale) */ /**************************************************************************/ void MSA300::setTapThreshold(float value) { float offset = 0; switch(_range) { case MSA300_RANGE_16_G: offset = clamp(value / MSA300_MG2G_TAP_TH_16_G, 0, 16000.0); break; case MSA300_RANGE_8_G: offset = clamp(value / MSA300_MG2G_TAP_TH_8_G, 0, 8000.0); break; case MSA300_RANGE_4_G: offset = clamp(value / MSA300_MG2G_TAP_TH_4_G, 0, 4000.0); break; case MSA300_RANGE_2_G: offset = clamp(value / MSA300_MG2G_TAP_TH_2_G, 0, 2000.0); break; } writeRegister(MSA300_REG_TAP_TH, offset); } /**************************************************************************/ /*! @brief Set duration of tap interrupt. @param duration Second shock duration: According to tapDuration_t enum. @param quiet Quiet duration: 0 -> 30 ms 1 -> 20 ms @param shock Shock duration: 0 -> 50 ms 1 -> 70 ms */ /**************************************************************************/ void MSA300::setTapDuration(tapDuration_t duration, uint8_t quiet, uint8_t shock) { uint8_t reg = 0; reg |= (quiet << 7); reg |= (shock << 6); reg |= duration; writeRegister(MSA300_REG_TAP_DUR, reg); } /**************************************************************************/ /*! @brief Set threshold of active interrupt. Value can vary from 0 to full scale of each range. Values outside the range will be clamped. @param value Active threshold value (0 to full scale) */ /**************************************************************************/ void MSA300::setActiveThreshold(float value) { float offset = 0; switch(_range) { case MSA300_RANGE_16_G: offset = clamp(value / MSA300_MG2G_ACTIVE_TH_16_G, 0, 16000.0); break; case MSA300_RANGE_8_G: offset = clamp(value / MSA300_MG2G_ACTIVE_TH_8_G, 0, 8000.0); break; case MSA300_RANGE_4_G: offset = clamp(value / MSA300_MG2G_ACTIVE_TH_4_G, 0, 4000.0); break; case MSA300_RANGE_2_G: offset = clamp(value / MSA300_MG2G_ACTIVE_TH_2_G, 0, 2000.0); break; } writeRegister(MSA300_REG_ACTIVE_TH, offset); } /**************************************************************************/ /*! @brief Set duration of active interrupt. Value can vary from 1 ms to 5 ms. @param duration Active interrupt duration (1 to 5 ms) */ /**************************************************************************/ void MSA300::setActiveDuration(uint8_t duration) { uint8_t reg = 0; uint8_t value = clamp(duration - 1, 0, 4); reg |= value; writeRegister(MSA300_REG_ACTIVE_DUR, reg); } /**************************************************************************/ /*! @brief Set duration of freefall interrupt. Value can vary from 2 ms to 512 ms. @param duration Freefall interrupt duration (2 to 512 ms) */ /**************************************************************************/ void MSA300::setFreefallDuration(uint16_t duration) { uint8_t reg = 0; float dur_f; duration = clamp(duration, 2, 512); dur_f = clamp((float)(duration)/2.0f - 1, 0, 256); // avoid rounding the result in between reg |= (uint8_t)dur_f; writeRegister(MSA300_REG_FREEFALL_DUR, reg); } /**************************************************************************/ /*! @brief Set threshold of freefall interrupt. Value can vary from 0 to full scale of each range. Values outside the range will be clamped. @param value Freefall interrupt threshold (0 to full scale) */ /**************************************************************************/ void MSA300::setFreefallThreshold(float value) { float threshold = value / 7.81f; switch(_range) { case MSA300_RANGE_16_G: threshold = clamp(threshold, 0, 16000.0); break; case MSA300_RANGE_8_G: threshold = clamp(threshold, 0, 8000.0); break; case MSA300_RANGE_4_G: threshold = clamp(threshold, 0, 4000.0); break; case MSA300_RANGE_2_G: threshold = clamp(threshold, 0, 2000.0); break; } writeRegister(MSA300_REG_FREEFALL_TH, threshold); } /**************************************************************************/ /*! @brief Set hysteresis value and mode of freefall interrupt. Value can vary from 0 to 500 mg in integers of 125mg. Values outside the range will be clamped. @param mode Mode: 1 -> sum mode |acc_x| + |acc_y| + |acc_z| 0 -> single mode @param value Freefall hysteresis value (0 to 500 mg in steps of 125 mg) */ /**************************************************************************/ void MSA300::setFreefallHysteresis(uint8_t mode, uint8_t value) { uint8_t reg = 0; uint8_t hysteresis = (uint8_t)clamp(value / 125, 0, 500); reg |= (mode << 3); reg &= ~0x3; reg |= hysteresis; writeRegister(MSA300_REG_FREEFALL_HY, reg); } /**************************************************************************/ /*! @brief Swap polarity. @param polarity Polarity to be changed */ /**************************************************************************/ void MSA300::swapPolarity(pol_t polarity) { uint8_t reg = readRegister(MSA300_REG_SWAP_POLARITY); reg ^= (1 << polarity); writeRegister(MSA300_REG_SWAP_POLARITY, reg); } /**************************************************************************/ /*! @brief Set orientation mode @param mode Orientation mode */ /**************************************************************************/ void MSA300::setOrientMode(orientMode_t mode) { uint8_t reg = readRegister(MSA300_REG_ORIENT_HY); reg &= ~0x3; reg |= mode; writeRegister(MSA300_REG_ORIENT_HY, reg); } /**************************************************************************/ /*! @brief Set orientation hysteresis. Value can vary from 0 to 500 mg. @param value Orientation hysteresis value (0 to 500 mg) */ /**************************************************************************/ void MSA300::setOrientHysteresis(float value) { uint8_t reg = readRegister(MSA300_REG_ORIENT_HY); uint8_t hysteresis = (uint8_t)clamp(value / 62.5f, 0, 8); reg &= ~0x70; reg |= hysteresis; writeRegister(MSA300_REG_ORIENT_HY, reg); } /**************************************************************************/ /*! @brief Set z blocking. @param mode Orientation blocking mode @param zBlockValue Limit value of z-blocking */ /**************************************************************************/ void MSA300::setBlocking(orientBlockMode_t mode, float zBlockValue) { uint8_t reg = readRegister(MSA300_REG_ORIENT_HY); reg &= ~0xC; reg |= mode; writeRegister(MSA300_REG_ORIENT_HY, reg); uint8_t value = (uint8_t)clamp(zBlockValue / 62.5f, 0, 15); writeRegister(MSA300_REG_Z_BLOCK, value); } /**************************************************************************/ /*! @brief Get the acceleration. @return Acceleration struct containing accelarations of each axis in m/s^2 */ /**************************************************************************/ acc_t MSA300::getAcceleration(void) { acc_t acceleration; acceleration.x = getX() * _multiplier * GRAVITY; acceleration.y = getY() * _multiplier * GRAVITY; acceleration.z = getZ() * _multiplier * GRAVITY; return acceleration; }