SCL3300 sensor 3-axis inclinometer with angle output and digital SPI interface

Revision:
0:e8ba98a758d0
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/SCL3300.cpp	Mon Aug 15 19:17:10 2022 +0000
@@ -0,0 +1,469 @@
+/******************************************************************************
+SCL3300.cpp
+SCL3300 Arduino Driver
+David Armstrong
+Version 3.3.0 - September 13, 2021
+https://github.com/DavidArmstrong/SCL3300
+
+Resources:
+Uses SPI.h for SPI operation
+
+Development environment specifics:
+Arduino IDE 1.8.15
+
+This code is released under the [MIT License](http://opensource.org/licenses/MIT).
+Please review the LICENSE.md file included with this example.
+Distributed as-is; no warranty is given.
+
+******************************************************************************/
+
+// include this library's description file
+#include "SCL3300.h"
+
+// Public Methods //////////////////////////////////////////////////////////
+// Set the sensor mode to the number provided as modeNum.
+boolean SCL3300::setMode(int modeNum) {
+  // Set Sensor mode - If not called, the default is mode 4, as set in header file
+  // Only allowed values are: 1,2,3,4
+  if (modeNum > 0 && modeNum < 5) {
+    scl3300_mode = modeNum;
+    if (!setFastRead) beginTransmission(); //Set up this SPI port/bus
+    transfer(modeCMD[scl3300_mode]); //Set mode on hardware
+    if (!setFastRead) endTransmission(); //Let go of SPI port/bus
+    if (crcerr || statuserr) {
+      reset(); //Reset chip to fix the error state
+      return false;  //Let the caller know something went wrong
+    } else return true; // Valid value, and chip was set to that mode
+  } else
+    return false; // Invalid value
+}
+
+// Current Version of begin() to initialize the library and the SCL3300
+boolean SCL3300::begin(void) {
+  //This is the updated Version 3 begin function
+  // Determine if we need to set up to use the default SPI interface, or some other one
+  if (_spiPort == nullptr) _spiPort = &SPI;
+  
+  //Wait the required 1 ms before initializing the SCL3300 inclinomenter
+  unsigned long startmillis = millis();
+  while (millis() - startmillis < 1) ;
+  
+  initSPI();    // Initialize SPI Library
+  if (!setFastRead) beginTransmission(); //Set up this SPI port/bus
+  //Write SW Reset command
+  transfer(SwtchBnk0);
+  transfer(SWreset);
+  startmillis = millis();
+  while (millis() - startmillis < 1) ;
+  //Set measurement mode
+  transfer(modeCMD[scl3300_mode]); //Set mode on hardware
+  //We're good, so Enable angle outputs
+  transfer(EnaAngOut);
+  //The first response after reset is undefined and shall be discarded
+  //wait 5 ms to stablize
+  startmillis = millis();
+  while (millis() - startmillis < 100) ;
+
+  //Read Status to clear the status summary
+  transfer(RdStatSum);
+  transfer(RdStatSum); //Again, due to off-response protocol used
+  transfer(RdStatSum); //And now we can get the real status
+  
+  //Read the WHOAMI register
+  transfer(RdWHOAMI);
+  //And again
+  transfer(RdWHOAMI);
+  if (!setFastRead) endTransmission(); //Let go of SPI port/bus
+  //We now wait until the end of begin() to report if an error occurred
+  if (crcerr || statuserr) return false;
+  // Once everything is initialized, return a known expected value
+  // The WHOAMI command should give an 8 bit value of 0xc1
+  return (SCL3300_DATA == 0xc1); //Let the caller know if this worked
+}
+
+// Set up the SPI communication with the SCL3300 with provided Chip Select pin number, and provided SPI port
+boolean SCL3300::begin(SPIClass &spiPort, uint8_t csPin) {
+  scl3300_csPin = csPin;
+  _spiPort = &spiPort; //Grab the port the user wants us to use
+  return begin();
+} // begin
+
+// Set up the SPI communication with the SCL3300 with provided Chip Select pin number
+boolean SCL3300::begin(uint8_t csPin) {
+  scl3300_csPin = csPin;
+  _spiPort = &SPI; // With this call, we do the default SPI interface
+  return begin();
+} // begin
+
+//Check to validate that the sensor is still reachable and ready to provide data
+boolean SCL3300::isConnected() {
+  if (!setFastRead) beginTransmission(); //Set up this SPI port/bus
+  transfer(SwtchBnk0);
+  //Read the WHOAMI register
+  transfer(RdWHOAMI);
+  //And again
+  transfer(RdWHOAMI);
+  if (!setFastRead) endTransmission(); //Let go of SPI port/bus
+  if (crcerr || statuserr) return false;
+  // Once everything is initialized, return a known expected value
+  // The WHOAMI command should give an 8 bit value of 0xc1
+  return (SCL3300_DATA == 0xc1); //Let the caller know if this worked
+}
+
+//Read all the sensor data together to keep it consistent
+//This is required according to the datasheet
+boolean SCL3300::available(void) {
+  //Version 3 of this function
+  boolean errorflag = false;
+  //Read all Sensor Data, as per Datasheet requirements
+  if (!setFastRead) beginTransmission(); //Set up this SPI port/bus
+  transfer(SwtchBnk0);
+  transfer(RdAccX);
+  if (crcerr || statuserr) errorflag = true;
+  transfer(RdAccY);
+  if (crcerr || statuserr) errorflag = true;
+  sclData.AccX = SCL3300_DATA;
+  transfer(RdAccZ);
+  if (crcerr || statuserr) errorflag = true;
+  sclData.AccY = SCL3300_DATA;
+  transfer(RdSTO);
+  if (crcerr || statuserr) errorflag = true;
+  sclData.AccZ = SCL3300_DATA;
+  transfer(RdTemp);
+  if (crcerr || statuserr) errorflag = true;
+  sclData.STO = SCL3300_DATA;
+  transfer(RdAngX);
+  if (crcerr || statuserr) errorflag = true;
+  sclData.TEMP = SCL3300_DATA;
+  transfer(RdAngY);
+  if (crcerr || statuserr) errorflag = true;
+  sclData.AngX = SCL3300_DATA;
+  transfer(RdAngZ);
+  if (crcerr || statuserr) errorflag = true;
+  sclData.AngY = SCL3300_DATA;
+  transfer(RdStatSum);
+  if (crcerr || statuserr) errorflag = true;
+  sclData.AngZ = SCL3300_DATA;
+  transfer(RdWHOAMI);
+  if (crcerr || statuserr) errorflag = true;
+  sclData.StatusSum = SCL3300_DATA;
+  transfer(RdWHOAMI);
+  if (crcerr || statuserr) errorflag = true;
+  sclData.WHOAMI = SCL3300_DATA;
+  if (!setFastRead) endTransmission(); //Let go of SPI port/bus
+  if (errorflag) return false; //Inform caller that something went wrong
+  // The WHOAMI command should give an 8 bit value of 0xc1
+  return (SCL3300_DATA == 0xc1); //Let the caller know this worked
+}
+
+/* Set SCL3300 library into Fast Read Mode
+ * Warning: Using Fast Read Mode in the library works by keeping the
+ *          SPI connection continuously open.  This may or may not affect
+ *          the behavior of other hardware interactions, depending on the
+ *          sketch design.  Fast Read Mode is considered an advanced use case,
+ *          and not recommended for the beginner.
+*/
+void SCL3300::setFastReadMode() {
+  setFastRead = true;
+  beginTransmission(); //Set up this SPI port/bus
+  begin(); //Re-init chip
+}
+
+/* Stop Fast Read Mode
+ * Warning: Using Fast Read Mode in the library works by keeping the
+ *          SPI connection continuously open.  This may or may not affect
+ *          the behavior of other hardware interactions, depending on the
+ *          sketch design.  Fast Read Mode is considered an advanced use case,
+ *          and not recommended for the beginner.
+*/
+void SCL3300::stopFastReadMode() {
+  setFastRead = false;
+  endTransmission();  //Close connection to SPI port/bus
+  begin(); //Re-init chip
+}
+
+//Return the calculated X axis tilt angle in degrees
+double SCL3300::getCalculatedAngleX() {
+  double tempX = angle(sclData.AngX);
+  if (tempX < 0.) tempX += 360.;
+  return tempX;
+}
+
+//Return the calculated Y axis tilt angle in degrees
+double SCL3300::getCalculatedAngleY() {
+  double tempY = angle(sclData.AngY);
+  if (tempY < 0.) tempY += 360.;
+  return tempY;
+}
+
+//Return the calculated Z axis tilt angle in degrees
+double SCL3300::getCalculatedAngleZ() {
+  double tempZ = angle(sclData.AngZ);
+  if (tempZ < 0.) tempZ += 360.;
+  return tempZ;
+}
+
+//Return the calculated X axis offset tilt angle in degrees
+double SCL3300::getTiltLevelOffsetAngleX() {
+  return angle(sclData.AngX);
+}
+
+//Return the calculated Y axis offset tilt angle in degrees
+double SCL3300::getTiltLevelOffsetAngleY() {
+  return angle(sclData.AngY);
+}
+
+//Return the calculated Z axis offset tilt angle in degrees
+double SCL3300::getTiltLevelOffsetAngleZ() {
+  return angle(sclData.AngZ);
+}
+
+//Return the calculated X axis accelerometer value in units of 'g'
+double SCL3300::getCalculatedAccelerometerX(void) {
+  return acceleration(sclData.AccX);
+}
+
+//Return the calculated Y axis accelerometer value in units of 'g'
+double SCL3300::getCalculatedAccelerometerY(void) {
+  return acceleration(sclData.AccY);
+}
+
+//Return the calculated Z axis accelerometer value in units of 'g'
+double SCL3300::getCalculatedAccelerometerZ(void) {
+  return acceleration(sclData.AccZ);
+}
+
+//Return value of Error Flag 1 register
+uint16_t SCL3300::getErrFlag1(void) {
+  if (!setFastRead) beginTransmission(); //Set up this SPI port/bus
+  transfer(SwtchBnk0);
+  transfer(RdErrFlg1);
+  transfer(RdErrFlg1);
+  if (!setFastRead) endTransmission(); //Let go of SPI port/bus
+  //Since we are fetching the Error Flag 1 value, we want to return what we got
+  //to the caller, regardless of whether or not there was an error
+  //if (crcerr || statuserr) return ((uint16_t)(SCL3300_CMD) & 0xff); //check CRC and RS bits
+  return SCL3300_DATA;
+}
+
+//Return value of Error Flag 2 register
+uint16_t SCL3300::getErrFlag2(void) {
+  if (!setFastRead) beginTransmission(); //Set up this SPI port/bus
+  transfer(SwtchBnk0);
+  transfer(RdErrFlg2);
+  transfer(RdErrFlg2);
+  if (!setFastRead) endTransmission(); //Let go of SPI port/bus
+  //Since we are fetching the Error Flag 2 value, we want to return what we got
+  //to the caller, regardless of whether or not there was an error
+  //if (crcerr || statuserr) return ((uint16_t)(SCL3300_CMD) & 0xff); //check CRC and RS bits
+  return SCL3300_DATA;
+}
+
+// Read the sensor Serial Number as created by the manufacturer
+unsigned long SCL3300::getSerialNumber(void) {
+  //Return Device Serial number
+  boolean errorflag = false;
+  unsigned long serialNum = 0;
+  if (!setFastRead) beginTransmission(); //Set up this SPI port/bus
+  transfer(SwtchBnk1);
+  if (crcerr || statuserr) errorflag = true;
+  transfer(RdSer1);
+  if (crcerr || statuserr) errorflag = true;
+  transfer(RdSer2);
+  serialNum = SCL3300_DATA;
+  if (crcerr || statuserr) errorflag = true;
+  transfer(SwtchBnk0);
+  serialNum = ((unsigned long)SCL3300_DATA << 16) | serialNum;
+  if (!setFastRead) endTransmission(); //Let go of SPI port/bus
+  //We wait until now to return an error code
+  //In this case we send a 0 since a real serial number will never be 0
+  if (crcerr || statuserr || errorflag) return 0;
+  return serialNum;
+}
+
+// Place the sensor in a Powered Down mode to save power
+uint16_t SCL3300::powerDownMode(void) {
+  //Software power down of sensor
+  if (!setFastRead) beginTransmission(); //Set up this SPI port/bus
+  transfer(SwtchBnk0);
+  transfer(SetPwrDwn);
+  endTransmission(); //Let go of SPI port/bus
+  //Since an error is non-zero, we will return 0 if there was no error
+  if (crcerr || statuserr) return (uint16_t)(SCL3300_CMD & 0xff); //check CRC and RS bits
+  return 0;
+}
+
+// Revive the sensor from a power down mode so we can start getting data again
+uint16_t SCL3300::WakeMeUp(void) {
+  //Software Wake Up of sensor
+  beginTransmission(); //Set up this SPI port/bus
+  transfer(WakeUp);
+  if (!setFastRead) endTransmission(); //Let go of SPI port/bus
+  //Since an error is non-zero, we will return 0 if there was no error
+  if (crcerr || statuserr) return (uint16_t)(SCL3300_CMD & 0xff); //check CRC and RS bits
+  return 0;
+}
+
+// Hardware reset of the sensor electronics
+uint16_t SCL3300::reset(void) {
+  //Software reset of sensor
+  //beginTransmission(); //Set up this SPI port/bus
+  //transfer(SwtchBnk0);
+  //transfer(SWreset);
+  //endTransmission(); //Let go of SPI port/bus
+  //we have to call begin() to set up the SCL3300 to the same state as before it was reset
+  begin(); //Re-init chip
+  //Since an error is non-zero, we will return 0 if there was no error
+  if (crcerr || statuserr) return (uint16_t)(SCL3300_CMD & 0xff); //check CRC and RS bits
+  return 0;
+}
+
+// Routine to get temperature in degrees Celsius
+double SCL3300::getCalculatedTemperatureCelsius(void) {
+  // Return calculated temperature in degrees C
+  double Temperature = -273. + (sclData.TEMP / 18.9);
+  return Temperature;
+}
+
+// Routine to get temperature in degrees Farenheit
+double SCL3300::getCalculatedTemperatureFarenheit(void) {
+  // Return calculated temperature in degrees F
+  double Temperature = -273. + (sclData.TEMP / 18.9);
+  Temperature = (Temperature * 9./5.) + 32.;
+  return Temperature;
+}
+
+//Convert raw angle value to degrees tilt
+double SCL3300::angle(int16_t SCL3300_ANG) { //two's complement value expected
+  // Return Angle in degrees
+  double Angle = (SCL3300_ANG / 16384.) * 90.; // 16384 = 2^14
+  return Angle;
+}
+ 
+ //Convert raw accelerometer value to g's of acceleration
+double SCL3300::acceleration(int16_t SCL3300_ACC) { //two's complement value expected
+  // Return acceleration in g
+  if (scl3300_mode == 1) return (double)SCL3300_ACC / 6000.;
+  if (scl3300_mode == 2) return (double)SCL3300_ACC / 3000.;
+  if (scl3300_mode == 3) return (double)SCL3300_ACC / 12000.;
+  if (scl3300_mode == 4) return (double)SCL3300_ACC / 12000.;
+  return (double)SCL3300_ACC / 12000.; //Default should never be reached
+}
+
+//private functions for serial transmission
+// Begin SPI bus transmission to the device
+void SCL3300::beginTransmission() {
+  _spiPort->beginTransaction(spiSettings);
+} //beginTransmission
+
+// End SPI bus transmission to the device
+void SCL3300::endTransmission() {
+  // take the chip/slave select high to de-select:
+  digitalWrite(scl3300_csPin, HIGH);
+  _spiPort->endTransaction();
+  unsigned long startmillis = millis();
+  while (millis() - startmillis < 1) ; //wait a bit
+} //endTransmission
+
+//Initialize the Arduino SPI library for the SCL3300 hardware
+void SCL3300::initSPI() {
+  //Initialize the Arduino SPI library for the SCL3300 hardware
+  _spiPort->begin();
+  // Maximum SPI frequency is 2 MHz - 4 MHz to achieve the best performance
+  // initialize the chip select pin:
+  pinMode(scl3300_csPin, OUTPUT);
+  digitalWrite(scl3300_csPin, HIGH);
+  // Data is read and written MSb first.
+  // Data is captured on rising edge of clock (CPHA = 0)
+  // Data is propagated on the falling edge (MISO line) of the SCK. (CPOL = 0)
+}
+
+// The following is taken directly from the Murata SCL3300 datasheet
+// Calculate CRC for 24 MSB's of the 32 bit dword
+// (8 LSB's are the CRC field and are not included in CRC calculation)
+uint8_t SCL3300::CalculateCRC(uint32_t Data)
+{
+uint8_t BitIndex;
+uint8_t BitValue;
+uint8_t SCL3300_CRC;
+
+SCL3300_CRC = 0xFF;
+for (BitIndex = 31; BitIndex > 7; BitIndex--) {
+  BitValue = (uint8_t)((Data >> BitIndex) & 0x01);
+  SCL3300_CRC = CRC8(BitValue, SCL3300_CRC);
+}
+SCL3300_CRC = (uint8_t)~SCL3300_CRC;
+return SCL3300_CRC;
+}
+uint8_t SCL3300::CRC8(uint8_t BitValue, uint8_t SCL3300_CRC)
+{
+  uint8_t Temp;
+  Temp = (uint8_t)(SCL3300_CRC & 0x80);
+  if (BitValue == 0x01) {
+    Temp ^= 0x80;
+  }
+  SCL3300_CRC <<= 1;
+  if (Temp > 0) {
+    SCL3300_CRC ^= 0x1D;
+  }
+  return SCL3300_CRC;
+}
+
+// Routine to transfer a 32-bit integer to the SCL3300, and return the 32-bit data read
+unsigned long SCL3300::transfer(unsigned long value) {
+  FourByte dataorig;
+  unsigned long startmicros;
+  
+  dataorig.bit32 = value; //Allow 32 bit value to be sent 8 bits at a time
+  #ifdef debug_scl3300
+  Serial_SCL.print(dataorig.bit32, HEX);
+  Serial_SCL.print(" ");
+  for (int j = 3; j >= 0; j--) {
+    Serial_SCL.print(dataorig.bit8[j], HEX);
+    Serial_SCL.print(" ");
+  }
+  #endif
+  //Must allow at least 10 uSec between SPI transfers
+  //The datasheet shows the CS line must be high during this time
+  if (!setFastRead) startmicros = micros();
+  //while ((micros() - startmicros < 10) && (micros() > 10)) ;
+  if (!setFastRead) while ((micros() - startmicros < 10)) ;
+  
+  digitalWrite(scl3300_csPin, LOW); //Now chip select can be enabled for the full 32 bit xfer
+  SCL3300_DATA = 0;
+  for (int i = 3; i >= 0; i--) { //Xfers are done MSB first
+    dataorig.bit8[i] = _spiPort->transfer(dataorig.bit8[i]);
+  }
+  SCL3300_DATA = dataorig.bit8[1] + (dataorig.bit8[2] << 8);
+  SCL3300_CRC = dataorig.bit8[0];
+  SCL3300_CMD = dataorig.bit8[3];
+  digitalWrite(scl3300_csPin, HIGH); //And we are done
+  #ifdef debug_scl3300
+  for (int i = 3; i >= 0; i--) {
+    Serial_SCL.print(" ");
+    Serial_SCL.print(dataorig.bit8[i], HEX);
+  }
+  Serial_SCL.print("  ");
+  #endif
+  if (SCL3300_CRC == CalculateCRC(dataorig.bit32))
+    crcerr = false;
+  else
+    crcerr = true;
+  //check RS bits
+  if ((SCL3300_CMD & 0x03) == 0x01)
+    statuserr = false;
+  else
+    statuserr = true;
+  #ifdef debug_scl3300
+  Serial_SCL.print((SCL3300_CMD & 0x03));
+  Serial_SCL.print(" ");
+  Serial_SCL.print(SCL3300_DATA, HEX);
+  Serial_SCL.print(" ");
+  Serial_SCL.print(SCL3300_CRC, HEX);
+  Serial_SCL.print(" ");
+  Serial_SCL.print(CalculateCRC(dataorig.bit32), HEX);
+  Serial_SCL.print(" ");
+  Serial_SCL.println(crcerr);
+  #endif
+  return dataorig.bit32;
+}