diff --git a/.github/workflows/compile-sketch.yml b/.github/workflows/compile-sketch.yml new file mode 100644 index 0000000..4c35f3f --- /dev/null +++ b/.github/workflows/compile-sketch.yml @@ -0,0 +1,118 @@ +name: Compile Sketch + +on: + # - push + pull_request: + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +jobs: + compile-sketch: + runs-on: ubuntu-latest + + strategy: + fail-fast: false + + matrix: + board: + # Uno + # https://github.com/arduino/ArduinoCore-avr/blob/master/boards.txt + - fqbn: arduino:avr:uno + platforms: | + - name: arduino:avr + source-url: https://downloads.arduino.cc/packages/package_index.json + + # ESP32 + # https://github.com/espressif/arduino-esp32/blob/master/boards.txt + - fqbn: esp32:esp32:esp32 + platforms: | + - name: esp32:esp32 + source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + + # ESP32-S2 + # https://github.com/espressif/arduino-esp32/blob/master/boards.txt + - fqbn: esp32:esp32:esp32s2 + platforms: | + - name: esp32:esp32 + source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + + # ESP32-C3 + # https://github.com/espressif/arduino-esp32/blob/master/boards.txt + - fqbn: esp32:esp32:esp32c3 + platforms: | + - name: esp32:esp32 + source-url: https://raw.githubusercontent.com/espressif/arduino-esp32/gh-pages/package_esp32_index.json + + # Artemis / Apollo3 + # https://github.com/sparkfun/Arduino_Apollo3/blob/main/boards.txt + - fqbn: SparkFun:apollo3:sfe_artemis_atp + platforms: | + - name: SparkFun:apollo3 + source-url: https://raw.githubusercontent.com/sparkfun/Arduino_Apollo3/master/package_sparkfun_apollo3_index.json + + # ESP8266 + # https://github.com/esp8266/Arduino/blob/master/boards.txt + - fqbn: esp8266:esp8266:thingdev + platforms: | + - name: esp8266:esp8266 + source-url: https://arduino.esp8266.com/stable/package_esp8266com_index.json + + # SAMD + # https://github.com/arduino/ArduinoCore-samd/blob/master/boards.txt + #- fqbn: arduino:samd:mkr1000 + # platforms: | + # - name: arduino:samd + # # source-url: https://downloads.arduino.cc/packages/package_index.json + + # SAMD + # https://github.com/arduino/ArduinoCore-samd/blob/master/boards.txt + - fqbn: arduino:samd:nano_33_iot + platforms: | + - name: arduino:samd + # source-url: https://downloads.arduino.cc/packages/package_index.json + + # Nano BLE 33 / nRF52840 + # https://github.com/arduino/ArduinoCore-mbed/blob/master/boards.txt + - fqbn: arduino:mbed:nano33ble + platforms: | + - name: arduino:mbed + # source-url: https://downloads.arduino.cc/packages/package_index.json + + # RP2040 + # https://github.com/arduino/ArduinoCore-mbed/blob/master/boards.txt + - fqbn: rp2040:rp2040:sparkfun_promicrorp2040 + platforms: | + - name: rp2040:rp2040 + source-url: https://github.com/earlephilhower/arduino-pico/releases/download/global/package_rp2040_index.json + + # STM32 + # https://github.com/arduino/ArduinoCore-mbed/blob/master/boards.txt + - fqbn: STMicroelectronics:stm32:GenF4 + platforms: | + - name: STMicroelectronics:stm32 + source-url: https://github.com/stm32duino/BoardManagerFiles/raw/main/package_stmicroelectronics_index.json + + steps: + - name: Checkout + uses: actions/checkout@v2 + + - name: Branch name + run: echo running on branch ${GITHUB_REF##*/} + + - name: Compile Sketch + uses: arduino/compile-sketches@v1 + with: + platforms: ${{ matrix.board.platforms }} + fqbn: ${{ matrix.board.fqbn }} + libraries: | + - source-path: ./ + sketch-paths: | + - examples/example3_buffer + enable-warnings-report: true + enable-deltas-report: true + # verbose: true + + # outputs: + # report-artifact-name: ${{ steps.report-artifact-name.outputs.report-artifact-name }} + diff --git a/library.properties b/library.properties index c435298..d3f6562 100644 --- a/library.properties +++ b/library.properties @@ -1,5 +1,5 @@ name=SparkFun KX13X Arduino Library -version=2.0.3 +version=2.0.4 author=SparkFun Electronics maintainer=Elias Santistevan @ SparkFun Electronics sentence=Communicates and configures the SparkFun KX132/KX134 Accelerometer. diff --git a/src/SparkFun_Qwiic_KX13X.cpp b/src/SparkFun_Qwiic_KX13X.cpp index 51e92dc..f84bdf7 100644 --- a/src/SparkFun_Qwiic_KX13X.cpp +++ b/src/SparkFun_Qwiic_KX13X.cpp @@ -49,14 +49,29 @@ bool QwDevKX13X::initialize(uint8_t settings) if (!enableAccel(true)) return false; + sfe_kx13x_cntl1_bitfield_t cntl1; + cntl1.all = 0; // Reset Value + if (settings == DEFAULT_SETTINGS) + { retVal = writeRegisterByte(SFE_KX13X_CNTL1, DEFAULT_SETTINGS); + if (retVal == 0) // Check the write was successful + { + cntl1.all = DEFAULT_SETTINGS; + _range = cntl1.bits.gsel; // Record the G-range + } + } if (settings == INT_SETTINGS) { enablePhysInterrupt(); routeHardwareInterrupt(0x10); retVal = writeRegisterByte(SFE_KX13X_CNTL1, INT_SETTINGS); + if (retVal == 0) // Check the write was successful + { + cntl1.all = INT_SETTINGS; + _range = cntl1.bits.gsel; // Record the G-range + } } if (settings == BUFFER_SETTINGS) @@ -66,6 +81,11 @@ bool QwDevKX13X::initialize(uint8_t settings) enableSampleBuffer(); // Enable buffer setBufferOperationMode(0x00); // FIFO retVal = writeRegisterByte(SFE_KX13X_CNTL1, INT_SETTINGS); + if (retVal == 0) // Check the write was successful + { + cntl1.all = INT_SETTINGS; + _range = cntl1.bits.gsel; // Record the G-range + } } if (retVal != 0) @@ -79,30 +99,64 @@ bool QwDevKX13X::initialize(uint8_t settings) // // Resets the accelerometer // +// Kionix Technical Reference Manual says: +// "To change the value of the SRST bit, the PC1 bit in CNTL1 register must first be set to 0." +// +// Kionix TN027 "Power On Procedure" says to: +// Write 0x00 to register 0x7F +// Write 0x00 to CNTL2 +// Write 0x80 (SRST) to CNTL2 +// +// Kionix Technical Reference Manual says: +// "For I2C Communication: Setting SRST = 1 will NOT result in an ACK, since the part immediately +// enters the RAM reboot routine. NACK may be used to confirm this command." +// However, we've not seen the NACK when writing the SRST bit. That write always seems to be ACK'd as normal. +// But, the _next_ I2C transaction _does_ get NACK'd... +// The solution seems to be to keep trying to read CNTL2 and wait for the SRST bit to be cleared. + bool QwDevKX13X::softwareReset() { + enableAccel(false); // Clear the PC1 bit in CNTL1 + + int retVal; + + retVal = writeRegisterByte(0x7F, 0); + + if (retVal != 0) + return false; + + retVal = writeRegisterByte(SFE_KX13X_CNTL2, 0); + + if (retVal != 0) + return false; sfe_kx13x_cntl2_bitfield_t cntl2; cntl2.all = 0; cntl2.bits.srst = 1; // This is a long winded, but definitive way of setting the software reset bit - int retVal; - - retVal = writeRegisterByte(SFE_KX13X_CNTL2, cntl2.all); + writeRegisterByte(SFE_KX13X_CNTL2, cntl2.all); // Do the reset - // Logic is inverted here - if we reset using I2C the - // accelerometer immediately shuts off which results - // in a NACK. - if (retVal != 0) - return true; + uint8_t loopCount = 0; + while (loopCount < 10) // Reset takes about 2ms. Timeout after 10ms + { + retVal = readRegisterRegion(SFE_KX13X_CNTL2, &cntl2.all, 1); // Try to read CNTL2 (the first read gets NACK'd) + + if ((retVal == 0) && (cntl2.bits.srst == 0)) // Check if the software reset bit has been cleared + loopCount = 10; // Exit the loop if it has + else + { + loopCount++; // Increment the count and repeat + delay(1); // Delay for 1ms: important for SPI + } + } - return false; + return ((retVal == 0) && (cntl2.bits.srst == 0)); } ////////////////////////////////////////////////// // enableAccel() // -// Enables acceleromter data. In addition +// Enables accelerometer data. In addition // some settings can only be set when the accelerometer is // powered down // @@ -125,6 +179,7 @@ bool QwDevKX13X::enableAccel(bool enable) sfe_kx13x_cntl1_bitfield_t cntl1; cntl1.all = tempVal; cntl1.bits.pc1 = enable; // This is a long winded but definitive way of setting/clearing the operating mode bit + _range = cntl1.bits.gsel; // Update the G-range tempVal = cntl1.all; retVal = writeRegisterByte(SFE_KX13X_CNTL1, tempVal); @@ -154,6 +209,7 @@ int8_t QwDevKX13X::getOperatingMode() sfe_kx13x_cntl1_bitfield_t cntl1; cntl1.all = tempVal; // This is a long winded but definitive way of getting the operating mode bit + _range = cntl1.bits.gsel; // Update the G-range return (cntl1.bits.pc1); // Return the operating mode bit } @@ -192,6 +248,8 @@ bool QwDevKX13X::setRange(uint8_t range) if (retVal != 0) return false; + _range = range; // Update our local copy + return true; } @@ -216,6 +274,7 @@ bool QwDevKX13X::enableDataEngine(bool enable) sfe_kx13x_cntl1_bitfield_t cntl1; cntl1.all = tempVal; cntl1.bits.drdye = enable; // This is a long winded but definitive way of setting/clearing the data ready engine bit + _range = cntl1.bits.gsel; // Update the G-range tempVal = cntl1.all; retVal = writeRegisterByte(SFE_KX13X_CNTL1, tempVal); @@ -247,6 +306,7 @@ bool QwDevKX13X::enableTapEngine(bool enable) sfe_kx13x_cntl1_bitfield_t cntl1; cntl1.all = tempVal; cntl1.bits.tdte = enable; // This is a long winded but definitive way of setting/clearing the tap engine bit + _range = cntl1.bits.gsel; // Update the G-range tempVal = cntl1.all; retVal = writeRegisterByte(SFE_KX13X_CNTL1, tempVal); @@ -278,6 +338,7 @@ bool QwDevKX13X::enableTiltEngine(bool enable) sfe_kx13x_cntl1_bitfield_t cntl1; cntl1.all = tempVal; cntl1.bits.tpe = enable; // This is a long winded but definitive way of setting/clearing the tilt engine bit + _range = cntl1.bits.gsel; // Update the G-range tempVal = cntl1.all; retVal = writeRegisterByte(SFE_KX13X_CNTL1, tempVal); @@ -543,7 +604,7 @@ bool QwDevKX13X::configureInterruptPin(uint8_t pinVal) // bool QwDevKX13X::enablePhysInterrupt(bool enable, uint8_t pin) { - int retVal; + int retVal = -1; uint8_t tempVal; if (pin > 2) @@ -561,7 +622,7 @@ bool QwDevKX13X::enablePhysInterrupt(bool enable, uint8_t pin) inc1.bits.ien1 = enable; // This is a long winded but definitive way of setting/clearing the enable bit tempVal = inc1.all; - writeRegisterByte(SFE_KX13X_INC1, tempVal); + retVal = writeRegisterByte(SFE_KX13X_INC1, tempVal); } if (pin == 2) @@ -576,10 +637,10 @@ bool QwDevKX13X::enablePhysInterrupt(bool enable, uint8_t pin) inc5.bits.ien2 = enable; // This is a long winded but definitive way of setting/clearing the enable bit tempVal = inc5.all; - writeRegisterByte(SFE_KX13X_INC5, tempVal); + retVal = writeRegisterByte(SFE_KX13X_INC5, tempVal); } - return true; + return (retVal == 0); } ////////////////////////////////////////////////// @@ -593,7 +654,7 @@ bool QwDevKX13X::enablePhysInterrupt(bool enable, uint8_t pin) // bool QwDevKX13X::setPinMode(bool activeHigh, uint8_t pin) { - int retVal; + int retVal = -1; uint8_t tempVal; if (pin > 2) @@ -611,7 +672,7 @@ bool QwDevKX13X::setPinMode(bool activeHigh, uint8_t pin) inc1.bits.iea1 = activeHigh; // This is a long winded but definitive way of setting/clearing the level bit tempVal = inc1.all; - writeRegisterByte(SFE_KX13X_INC1, tempVal); + retVal = writeRegisterByte(SFE_KX13X_INC1, tempVal); } if (pin == 2) @@ -626,10 +687,10 @@ bool QwDevKX13X::setPinMode(bool activeHigh, uint8_t pin) inc5.bits.iea2 = activeHigh; // This is a long winded but definitive way of setting/clearing the level bit tempVal = inc5.all; - writeRegisterByte(SFE_KX13X_INC5, tempVal); + retVal = writeRegisterByte(SFE_KX13X_INC5, tempVal); } - return true; + return (retVal == 0); } ////////////////////////////////////////////////// @@ -644,7 +705,7 @@ bool QwDevKX13X::setPinMode(bool activeHigh, uint8_t pin) // bool QwDevKX13X::setLatchControl(bool pulsed, uint8_t pin) { - int retVal; + int retVal = -1; uint8_t tempVal; if (pin > 2) @@ -662,7 +723,7 @@ bool QwDevKX13X::setLatchControl(bool pulsed, uint8_t pin) inc1.bits.iel1 = pulsed; // This is a long winded but definitive way of setting/clearing the latch bit tempVal = inc1.all; - writeRegisterByte(SFE_KX13X_INC1, tempVal); + retVal = writeRegisterByte(SFE_KX13X_INC1, tempVal); } if (pin == 2) @@ -677,10 +738,10 @@ bool QwDevKX13X::setLatchControl(bool pulsed, uint8_t pin) inc5.bits.iel2 = pulsed; // This is a long winded but definitive way of setting/clearing the latch bit tempVal = inc5.all; - writeRegisterByte(SFE_KX13X_INC5, tempVal); + retVal = writeRegisterByte(SFE_KX13X_INC5, tempVal); } - return true; + return (retVal == 0); } ////////////////////////////////////////////////// @@ -694,7 +755,7 @@ bool QwDevKX13X::setLatchControl(bool pulsed, uint8_t pin) // bool QwDevKX13X::setPulseWidth(uint8_t width, uint8_t pin) { - int retVal; + int retVal = -1; uint8_t tempVal; if ((width > 3) || (pin > 2)) @@ -712,7 +773,7 @@ bool QwDevKX13X::setPulseWidth(uint8_t width, uint8_t pin) inc1.bits.pw1 = width; // This is a long winded but definitive way of setting the pulse width tempVal = inc1.all; - writeRegisterByte(SFE_KX13X_INC1, tempVal); + retVal = writeRegisterByte(SFE_KX13X_INC1, tempVal); } if (pin == 2) @@ -727,10 +788,10 @@ bool QwDevKX13X::setPulseWidth(uint8_t width, uint8_t pin) inc5.bits.pw2 = width; // This is a long winded but definitive way of setting the pulse width tempVal = inc5.all; - writeRegisterByte(SFE_KX13X_INC5, tempVal); + retVal = writeRegisterByte(SFE_KX13X_INC5, tempVal); } - return true; + return (retVal == 0); } ////////////////////////////////////////////////// @@ -1290,23 +1351,34 @@ bool QwDevKX13X::runCommandTest() cntl2.bits.cotc = 1; // This is a long winded, but definitive way of setting the COTC bit tempVal = cntl2.all; - // Going to assume that communication is working at this point. - writeRegisterByte(SFE_KX13X_CNTL2, tempVal); + retVal = writeRegisterByte(SFE_KX13X_CNTL2, tempVal); // Start the test + + if (retVal != 0) + return false; + + retVal = readRegisterRegion(SFE_KX13X_COTR, &tempVal, 1); // Check COTR is 0xAA - readRegisterRegion(SFE_KX13X_COTR, &tempVal, 1); + if (retVal != 0) + return false; if (tempVal != 0xAA) return false; - readRegisterRegion(SFE_KX13X_CNTL2, &tempVal, 1); + retVal = readRegisterRegion(SFE_KX13X_CNTL2, &tempVal, 1); + + if (retVal != 0) + return false; cntl2.all = tempVal; - if (cntl2.bits.cotc != 0) + if (cntl2.bits.cotc != 0) // Check the COTC bit has been cleared return false; - readRegisterRegion(SFE_KX13X_COTR, &tempVal, 1); + retVal = readRegisterRegion(SFE_KX13X_COTR, &tempVal, 1); - if (tempVal != 0x55) + if (retVal != 0) + return false; + + if (tempVal != 0x55) // Check COTR is 0x55 return false; return true; @@ -1530,7 +1602,7 @@ bool QwDevKX13X::forceWake() // int QwDevKX13X::readRegisterRegion(uint8_t reg, uint8_t *data, uint16_t len) { - return (int)_sfeBus->readRegisterRegion(_i2cAddress, reg, data, len); + return (_sfeBus->readRegisterRegion(_i2cAddress, reg, data, len)); } ////////////////////////////////////////////////////////////////////////////////// @@ -1546,7 +1618,7 @@ int QwDevKX13X::readRegisterRegion(uint8_t reg, uint8_t *data, uint16_t len) // int QwDevKX13X::writeRegisterRegion(uint8_t reg, uint8_t *data, uint16_t len) { - return (int)_sfeBus->writeRegisterRegion(_i2cAddress, reg, data, len); + return (_sfeBus->writeRegisterRegion(_i2cAddress, reg, data, len)); } ////////////////////////////////////////////////////////////////////////////////// @@ -1562,7 +1634,7 @@ int QwDevKX13X::writeRegisterRegion(uint8_t reg, uint8_t *data, uint16_t len) // int QwDevKX13X::writeRegisterByte(uint8_t reg, uint8_t data) { - return (int)_sfeBus->writeRegisterByte(_i2cAddress, reg, data); + return (_sfeBus->writeRegisterByte(_i2cAddress, reg, data) ? 0 : -1); } //***************************************** KX132 ********************************************************* @@ -1621,21 +1693,23 @@ bool QwDevKX132::getAccelData(outputData *userData) // bool QwDevKX132::convAccelData(outputData *userAccel, rawOutputData *rawAccelData) { - uint8_t regVal; - uint8_t range; - int retVal; + if (_range < 0) // If the G-range is unknown, read it + { + uint8_t regVal; + int retVal; - retVal = readRegisterRegion(SFE_KX13X_CNTL1, ®Val, 1); + retVal = readRegisterRegion(SFE_KX13X_CNTL1, ®Val, 1); - if (retVal != 0) - return false; + if (retVal != 0) + return false; - sfe_kx13x_cntl1_bitfield_t cntl1; - cntl1.all = regVal; + sfe_kx13x_cntl1_bitfield_t cntl1; + cntl1.all = regVal; - range = cntl1.bits.gsel; + _range = cntl1.bits.gsel; // Record the range + } - switch (range) + switch (_range) { case SFE_KX132_RANGE2G: userAccel->xData = (float)rawAccelData->xData * convRange2G; @@ -1720,21 +1794,23 @@ bool QwDevKX134::getAccelData(outputData *userData) // bool QwDevKX134::convAccelData(outputData *userAccel, rawOutputData *rawAccelData) { - uint8_t regVal; - uint8_t range; - int retVal; + if (_range < 0) // If the G-range is unknown, read it + { + uint8_t regVal; + int retVal; - retVal = readRegisterRegion(SFE_KX13X_CNTL1, ®Val, 1); + retVal = readRegisterRegion(SFE_KX13X_CNTL1, ®Val, 1); - if (retVal != 0) - return false; + if (retVal != 0) + return false; - sfe_kx13x_cntl1_bitfield_t cntl1; - cntl1.all = regVal; + sfe_kx13x_cntl1_bitfield_t cntl1; + cntl1.all = regVal; - range = cntl1.bits.gsel; + _range = cntl1.bits.gsel; // Record the range + } - switch (range) + switch (_range) { case SFE_KX134_RANGE8G: userAccel->xData = (float)rawAccelData->xData * convRange8G; diff --git a/src/SparkFun_Qwiic_KX13X.h b/src/SparkFun_Qwiic_KX13X.h index 848c92e..3bcccc4 100644 --- a/src/SparkFun_Qwiic_KX13X.h +++ b/src/SparkFun_Qwiic_KX13X.h @@ -79,8 +79,8 @@ #define SPI_READ 0x80 // OR'ed at most sig BIT with register address -#define DEFAULT_SETTINGS 0xC0 -#define INT_SETTINGS 0xE0 +#define DEFAULT_SETTINGS 0xC0 // CNTL1: Hi-Performance mode; Data-Ready disabled; minimum G-range; Tap & Tilt disabled +#define INT_SETTINGS 0xE0 // CNTL1: Hi-Performance mode; Data-Ready enabled; minimum G-range; Tap & Tilt disabled #define SOFT_INT_SETTINGS 0xE1 #define BUFFER_SETTINGS 0xE2 #define TILT_SETTINGS 0xE3 @@ -179,6 +179,7 @@ class QwDevKX13X sfe_KX13X::QwIDeviceBus *_sfeBus; uint8_t _i2cAddress; uint8_t _cs; + int _range = -1; // Keep a local copy of the range. Default to "unknown" (-1). }; class QwDevKX132 : public QwDevKX13X diff --git a/src/sfe_bus.cpp b/src/sfe_bus.cpp index 28287b5..667ed54 100644 --- a/src/sfe_bus.cpp +++ b/src/sfe_bus.cpp @@ -6,11 +6,11 @@ // // Do you like this library? Help support SparkFun. Buy a board! // -//SparkFun Triple Axis Accelerometer - KX132/KX134 (Qwiic) +// SparkFun Triple Axis Accelerometer - KX132/KX134 (Qwiic) // * KX132 - https://www.sparkfun.com/products/17871 // * KX134 - https://www.sparkfun.com/products/17589 // -// Written by Kirk Benell @ SparkFun Electronics +// Written by Kirk Benell @ SparkFun Electronics // Modified by Elias Santistevan @ SparkFun Electronics, September 2022 // // Repository: @@ -40,10 +40,9 @@ // ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN // CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - // The following classes specify the behavior for communicating // over the respective data buses: Inter-Integrated Circuit (I2C) -// and Serial Peripheral Interface (SPI). +// and Serial Peripheral Interface (SPI). #include "sfe_bus.h" #include @@ -54,317 +53,313 @@ namespace sfe_KX13X #define kMaxTransferBuffer 32 #define SPI_READ 0x80 -// What we use for transfer chunk size -const static uint16_t kChunkSize = kMaxTransferBuffer; + // What we use for transfer chunk size + const static uint16_t kChunkSize = kMaxTransferBuffer; -////////////////////////////////////////////////////////////////////////////////////////////////// -// Constructor -// + ////////////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + // + QwI2C::QwI2C(void) : _i2cPort{nullptr} + { + } -QwI2C::QwI2C(void) : _i2cPort{nullptr} -{ -} + ////////////////////////////////////////////////////////////////////////////////////////////////// + // I2C init() + // + // Methods to init/setup this device. The caller can provide a Wire Port, or this class + // will use the default -////////////////////////////////////////////////////////////////////////////////////////////////// -// I2C init() -// -// Methods to init/setup this device. The caller can provide a Wire Port, or this class -// will use the default - -bool QwI2C::init(TwoWire &wirePort, bool bInit) -{ + bool QwI2C::init(TwoWire &wirePort, bool bInit) + { // if we don't have a wire port already - if( !_i2cPort ) + if (!_i2cPort) { - _i2cPort = &wirePort; + _i2cPort = &wirePort; - if( bInit ) - _i2cPort->begin(); + if (bInit) + _i2cPort->begin(); } - - return true; -} - -////////////////////////////////////////////////////////////////////////////////////////////////// -// I2C init() -// -// Methods to init/setup this device. The caller can provide a Wire Port, or this class -// will use the default -bool QwI2C::init() -{ - if( !_i2cPort ) - return init(Wire); - else - return false; -} - + return true; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // I2C init() + // + // Methods to init/setup this device. The caller can provide a Wire Port, or this class + // will use the default + bool QwI2C::init() + { + if (!_i2cPort) + return init(Wire); + else + return false; + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // ping() + // + // Is a device connected? + bool QwI2C::ping(uint8_t i2c_address) + { -////////////////////////////////////////////////////////////////////////////////////////////////// -// ping() -// -// Is a device connected? -bool QwI2C::ping(uint8_t i2c_address) -{ - - if( !_i2cPort ) - return false; + if (!_i2cPort) + return false; _i2cPort->beginTransmission(i2c_address); return _i2cPort->endTransmission() == 0; -} + } -////////////////////////////////////////////////////////////////////////////////////////////////// -// writeRegisterByte() -// -// Write a byte to a register + ////////////////////////////////////////////////////////////////////////////////////////////////// + // writeRegisterByte() + // + // Write a byte to a register -bool QwI2C::writeRegisterByte(uint8_t i2c_address, uint8_t offset, uint8_t dataToWrite) -{ + bool QwI2C::writeRegisterByte(uint8_t i2c_address, uint8_t offset, uint8_t dataToWrite) + { if (!_i2cPort) - return false; + return -1; _i2cPort->beginTransmission(i2c_address); _i2cPort->write(offset); _i2cPort->write(dataToWrite); - return _i2cPort->endTransmission() == 0; -} + return (_i2cPort->endTransmission() == 0); // true = success, false = error + } + ////////////////////////////////////////////////////////////////////////////////////////////////// + // writeRegisterRegion() + // + // Write a block of data to a device. + int QwI2C::writeRegisterRegion(uint8_t i2c_address, uint8_t offset, const uint8_t *data, uint16_t length) + { -////////////////////////////////////////////////////////////////////////////////////////////////// -// writeRegisterRegion() -// -// Write a block of data to a device. - -int QwI2C::writeRegisterRegion(uint8_t i2c_address, uint8_t offset, const uint8_t *data, uint16_t length) -{ + if (!_i2cPort) + return -1; _i2cPort->beginTransmission(i2c_address); _i2cPort->write(offset); _i2cPort->write(data, (int)length); - return _i2cPort->endTransmission() ? -1 : 0; // -1 = error, 0 = success -} - -//////////////////////////////////////////////////////////////////////////////////////////////////////////// -// readRegisterRegion() -// -// Reads a block of data from an i2c register on the devices. -// -// For large buffers, the data is chuncked over KMaxI2CBufferLength at a time -// -// -int QwI2C::readRegisterRegion(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t numBytes) -{ + return _i2cPort->endTransmission() == 0 ? 0 : -1; // 0 = success, -1 = error + } + + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + // readRegisterRegion() + // + // Reads a block of data from an i2c register on the devices. + // + // For large buffers, the data is chuncked over KMaxI2CBufferLength at a time + // + // + int QwI2C::readRegisterRegion(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t numBytes) + { uint8_t nChunk; uint16_t nReturned; if (!_i2cPort) - return -1; + return -1; - int i; // counter in loop - bool bFirstInter = true; // Flag for first iteration - used to send register + int i; // counter in loop + int failCount = 0; // Keep track of how many times nReturned is != nChunk - while (numBytes > 0) + while ((numBytes > 0) && (failCount < 2)) // Give up after 2 bad requests { - _i2cPort->beginTransmission(addr); + _i2cPort->beginTransmission(addr); + _i2cPort->write(reg); // Write the register address we want to read from + if (_i2cPort->endTransmission() != 0) + return -1; // Fail immediately if the transmission isn't successful - if (bFirstInter) - { - _i2cPort->write(reg); - bFirstInter = false; - } + // We're chunking in data - keeping the max chunk to kMaxI2CBufferLength + nChunk = numBytes > kChunkSize ? kChunkSize : numBytes; - if (_i2cPort->endTransmission() != 0) - return -1; // error with the end transmission + nReturned = _i2cPort->requestFrom((int)addr, (int)nChunk, (int)true); // Always send a stop - // We're chunking in data - keeping the max chunk to kMaxI2CBufferLength - nChunk = numBytes > kChunkSize ? kChunkSize : numBytes; + // No data returned, no dice + if (nReturned == 0) + return -1; // error - nReturned = _i2cPort->requestFrom((int)addr, (int)nChunk, (int)true); + // Check we got back as much data as was requested. + // (Fringe case. This should never happen... But, you know, it _could_...) + if (nReturned != nChunk) + failCount++; // Increment the failCount - // No data returned, no dice - if (nReturned == 0) - return -1; // error + // Copy the retrieved data chunk to the current index in the data segment + for (i = 0; i < nReturned; i++) + { + *data++ = _i2cPort->read(); + } - // Copy the retrieved data chunk to the current index in the data segment - for (i = 0; i < nReturned; i++){ - *data++ = _i2cPort->read(); - } - - // Decrement the amount of data recieved from the overall data request amount - numBytes = numBytes - nReturned; - - } // end while - - return 0; // Success -} + // Decrement the amount of data recieved from the overall data request amount + numBytes = numBytes - nReturned; + // Increment reg by the same ammount + reg += nReturned; + } // end while -////////////////////////////////////////////////////////////////////////////////////////////////// -// Constructor -// + return (numBytes == 0 ? 0 : -1); // 0 = success (all bytes read), -1 = error + } -SfeSPI::SfeSPI(void) : _spiPort{nullptr} -{ -} + ////////////////////////////////////////////////////////////////////////////////////////////////// + // Constructor + // -//////////////////////////////////////////////////////////////////////////////////////////////// -// SPI init() -// -// Methods to init/setup this device. The caller can provide a SPI Port, or this class -// will use the default + SfeSPI::SfeSPI(void) : _spiPort{nullptr} + { + } + //////////////////////////////////////////////////////////////////////////////////////////////// + // SPI init() + // + // Methods to init/setup this device. The caller can provide a SPI Port, or this class + // will use the default -bool SfeSPI::init(SPIClass &spiPort, SPISettings& kxSettings, uint8_t cs, bool bInit) -{ + bool SfeSPI::init(SPIClass &spiPort, SPISettings &kxSettings, uint8_t cs, bool bInit) + { // if we don't have a SPI port already - if( !_spiPort ) + if (!_spiPort) { - _spiPort = &spiPort; + _spiPort = &spiPort; - if( bInit ) - _spiPort->begin(); + if (bInit) + _spiPort->begin(); } + // SPI settings are needed for every transaction + _sfeSPISettings = kxSettings; - // SPI settings are needed for every transaction - _sfeSPISettings = kxSettings; + // The chip select pin can vary from platform to platform and project to project + // and so it must be given by the user. + if (!cs) + return false; - // The chip select pin can vary from platform to platform and project to project - // and so it must be given by the user. - if( !cs ) - return false; - - _cs = cs; + _cs = cs; return true; -} - -//////////////////////////////////////////////////////////////////////////////////////////////// -// SPI init() -// -// Methods to init/setup this device. The caller can provide a SPI Port, or this class -// will use the default -bool SfeSPI::init(uint8_t cs, bool bInit) -{ - - //If the transaction settings are not provided by the user they are built here. - SPISettings spiSettings = SPISettings(2000000, MSBFIRST, SPI_MODE0); - - //In addition of the port is not provided by the user, it defaults to SPI here. - return init(SPI, spiSettings, cs, bInit); - -} - - -////////////////////////////////////////////////////////////////////////////////////////////////// -// ping() -// -// Is a device connected? The SPI ping is not relevant but is defined here to keep consistency with -// I2C class i.e. provided for the interface. -// - - -bool SfeSPI::ping(uint8_t i2c_address) -{ - return true; -} + } + + //////////////////////////////////////////////////////////////////////////////////////////////// + // SPI init() + // + // Methods to init/setup this device. The caller can provide a SPI Port, or this class + // will use the default + bool SfeSPI::init(uint8_t cs, bool bInit) + { + + // If the transaction settings are not provided by the user they are built here. + SPISettings spiSettings = SPISettings(2000000, MSBFIRST, SPI_MODE0); + + // In addition of the port is not provided by the user, it defaults to SPI here. + return init(SPI, spiSettings, cs, bInit); + } + + ////////////////////////////////////////////////////////////////////////////////////////////////// + // ping() + // + // Is a device connected? The SPI ping is not relevant but is defined here to keep consistency with + // I2C class i.e. provided for the interface. + // + + bool SfeSPI::ping(uint8_t i2c_address) + { + return true; + } -////////////////////////////////////////////////////////////////////////////////////////////////// -// writeRegisterByte() -// -// Write a byte to a register + ////////////////////////////////////////////////////////////////////////////////////////////////// + // writeRegisterByte() + // + // Write a byte to a register -bool SfeSPI::writeRegisterByte(uint8_t i2c_address, uint8_t offset, uint8_t dataToWrite) -{ + bool SfeSPI::writeRegisterByte(uint8_t i2c_address, uint8_t offset, uint8_t dataToWrite) + { - if( !_spiPort ) - return false; + if (!_spiPort) + return false; - // Apply settings + // Apply settings _spiPort->beginTransaction(_sfeSPISettings); - // Signal communication start - digitalWrite(_cs, LOW); + // Signal communication start + digitalWrite(_cs, LOW); _spiPort->transfer(offset); _spiPort->transfer(dataToWrite); - // End communcation - digitalWrite(_cs, HIGH); + // End communcation + digitalWrite(_cs, HIGH); _spiPort->endTransaction(); - return true; -} + return true; + } + ////////////////////////////////////////////////////////////////////////////////////////////////// + // writeRegisterRegion() + // + // Write a block of data to a device. -////////////////////////////////////////////////////////////////////////////////////////////////// -// writeRegisterRegion() -// -// Write a block of data to a device. + int SfeSPI::writeRegisterRegion(uint8_t i2c_address, uint8_t offset, const uint8_t *data, uint16_t length) + { -int SfeSPI::writeRegisterRegion(uint8_t i2c_address, uint8_t offset, const uint8_t *data, uint16_t length) -{ + if (!_spiPort) + return -1; - int i; + int i; - // Apply settings + // Apply settings _spiPort->beginTransaction(_sfeSPISettings); - // Signal communication start - digitalWrite(_cs, LOW); + // Signal communication start + digitalWrite(_cs, LOW); _spiPort->transfer(offset); - for(i = 0; i < length; i++) - { - _spiPort->transfer(*data++); - } + for (i = 0; i < length; i++) + { + _spiPort->transfer(*data++); + } - // End communication - digitalWrite(_cs, HIGH); + // End communication + digitalWrite(_cs, HIGH); _spiPort->endTransaction(); - return 0; -} -//////////////////////////////////////////////////////////////////////////////////////////////////////////// -// readRegisterRegion() -// -// Reads a block of data from the register on the device. -// -// -// + return 0; + } + //////////////////////////////////////////////////////////////////////////////////////////////////////////// + // readRegisterRegion() + // + // Reads a block of data from the register on the device. + // + // + // -int SfeSPI::readRegisterRegion(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t numBytes) -{ + int SfeSPI::readRegisterRegion(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t numBytes) + { if (!_spiPort) - return -1; + return -1; int i; // counter in loop - // Apply settings + // Apply settings _spiPort->beginTransaction(_sfeSPISettings); - // Signal communication start - digitalWrite(_cs, LOW); - // A leading "1" must be added to transfer with register to indicate a "read" - reg = (reg | SPI_READ); + // Signal communication start + digitalWrite(_cs, LOW); + // A leading "1" must be added to transfer with register to indicate a "read" + reg = (reg | SPI_READ); _spiPort->transfer(reg); - for(i = 0; i < numBytes; i++) - { - *data++ = _spiPort->transfer(0x00); - } + for (i = 0; i < numBytes; i++) + { + *data++ = _spiPort->transfer(0x00); + } - // End transaction - digitalWrite(_cs, HIGH); + // End transaction + digitalWrite(_cs, HIGH); _spiPort->endTransaction(); - return 0; -} + return 0; + } } \ No newline at end of file diff --git a/src/sfe_bus.h b/src/sfe_bus.h index 8373eac..6d47ba1 100644 --- a/src/sfe_bus.h +++ b/src/sfe_bus.h @@ -6,16 +6,16 @@ // // Do you like this library? Help support SparkFun. Buy a board! // -//SparkFun Triple Axis Accelerometer - KX132/KX134 (Qwiic) +// SparkFun Triple Axis Accelerometer - KX132/KX134 (Qwiic) // * KX132 - https://www.sparkfun.com/products/17871 // * KX134 - https://www.sparkfun.com/products/17589 // -// Written by Kirk Benell @ SparkFun Electronics +// Written by Kirk Benell @ SparkFun Electronics // Modified by Elias Santistevan @ SparkFun Electronics, September 2022 // // Repository: // https://github.com/sparkfun/SparkFun_KX13X_Arduino_Library -// +// // // // SparkFun code, firmware, and software is released under the MIT @@ -44,85 +44,78 @@ // The following classes specify the behavior for communicating // over the respective data buses: Inter-Integrated Circuit (I2C) // and Serial Peripheral Interface (SPI). For ease of implementation -// an abstract interface (QwIDeviceBus) is used. +// an abstract interface (QwIDeviceBus) is used. #pragma once - #include #include namespace sfe_KX13X { -// The following abstract class is used an interface for upstream implementation. -class QwIDeviceBus -{ - public: - - virtual bool ping(uint8_t address) = 0; - - virtual bool writeRegisterByte(uint8_t address, uint8_t offset, uint8_t data) = 0; + // The following abstract class is used an interface for upstream implementation. + class QwIDeviceBus + { + public: + virtual bool ping(uint8_t address) = 0; - virtual int writeRegisterRegion(uint8_t address, uint8_t offset, const uint8_t* data, uint16_t length) = 0; + virtual bool writeRegisterByte(uint8_t address, uint8_t offset, uint8_t data) = 0; - virtual int readRegisterRegion(uint8_t addr, uint8_t reg, uint8_t* data, uint16_t numBytes) = 0; + virtual int writeRegisterRegion(uint8_t address, uint8_t offset, const uint8_t *data, uint16_t length) = 0; -}; + virtual int readRegisterRegion(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t numBytes) = 0; + }; -// The QwI2C device defines behavior for I2C implementation based around the TwoWire class (Wire). -// This is Arduino specific. -class QwI2C : public QwIDeviceBus -{ - public: + // The QwI2C device defines behavior for I2C implementation based around the TwoWire class (Wire). + // This is Arduino specific. + class QwI2C : public QwIDeviceBus + { + public: + QwI2C(void); - QwI2C(void); + bool init(); - bool init(); + bool init(TwoWire &wirePort, bool bInit = false); - bool init(TwoWire& wirePort, bool bInit=false); + bool ping(uint8_t address); - bool ping(uint8_t address); + bool writeRegisterByte(uint8_t address, uint8_t offset, uint8_t data); - bool writeRegisterByte(uint8_t address, uint8_t offset, uint8_t data); + int writeRegisterRegion(uint8_t address, uint8_t offset, const uint8_t *data, uint16_t length); - int writeRegisterRegion(uint8_t address, uint8_t offset, const uint8_t* data, uint16_t length); - - int readRegisterRegion(uint8_t addr, uint8_t reg, uint8_t* data, uint16_t numBytes); - - private: - - TwoWire* _i2cPort; -}; - -// The SfeSPI class defines behavior for SPI implementation based around the SPIClass class (SPI). -// This is Arduino specific. -// Paramaters like "address" are kept although irrelevant to SPI due to the use of the abstract class -// as interface, QwIDeviceBus. -class SfeSPI : public QwIDeviceBus -{ - public: + int readRegisterRegion(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t numBytes); - SfeSPI(void); + private: + TwoWire *_i2cPort; + }; - bool init(uint8_t cs, bool bInit=false); + // The SfeSPI class defines behavior for SPI implementation based around the SPIClass class (SPI). + // This is Arduino specific. + // Paramaters like "address" are kept although irrelevant to SPI due to the use of the abstract class + // as interface, QwIDeviceBus. + class SfeSPI : public QwIDeviceBus + { + public: + SfeSPI(void); - bool init(SPIClass& spiPort, SPISettings& kxSettings, uint8_t cs, bool bInit=false); + bool init(uint8_t cs, bool bInit = false); - bool ping(uint8_t address); + bool init(SPIClass &spiPort, SPISettings &kxSettings, uint8_t cs, bool bInit = false); - bool writeRegisterByte(uint8_t address, uint8_t offset, uint8_t data); + bool ping(uint8_t address); - int writeRegisterRegion(uint8_t address, uint8_t offset, const uint8_t* data, uint16_t length); + bool writeRegisterByte(uint8_t address, uint8_t offset, uint8_t data); - int readRegisterRegion(uint8_t addr, uint8_t reg, uint8_t* data, uint16_t numBytes); + int writeRegisterRegion(uint8_t address, uint8_t offset, const uint8_t *data, uint16_t length); - private: + int readRegisterRegion(uint8_t addr, uint8_t reg, uint8_t *data, uint16_t numBytes); - SPIClass* _spiPort; - // Settings are used for every transaction. - SPISettings _sfeSPISettings; - uint8_t _cs; -}; + private: + SPIClass *_spiPort; + // Settings are used for every transaction. + SPISettings _sfeSPISettings; + uint8_t _cs; + }; }; \ No newline at end of file