TRIPLE AXIS ACCELEROMETER BREAKOUT - KX134 (QWIIC)

Hey guys

First I apologize for my English, I am not fluent yet. I had bought a “Triple Axis Accelerometer Breakout - KX134 (Qwiic)” and I want to read it via Arduino UNO. If I use I2C , all that I need to do is to support 3.3v power and connect Arduino’s A4、A5 pin to SDA、SCL right? There are some jumpers on the KX134 sensor, but I think it doesn’t need to adjust.

If my thoughts are incorrect, please don’t hesitate to tell me, thank you.

Jay Lee

No need to adjust any jumpers and your connections look good to me.

You should only need to install the KX13x library and run the I2C example sketch to see the sensor working! :slight_smile:

Thank you very much YellowDog :smiley:

These days I use “example1_basic_readings” to read kx134 sensor but it doesn’t work successfully. The kx library is the latest version(2.0.4). I modified some parts of the program to fit kx134 sensor. Serial monitor always shows “could not communicate with the KX13X. Freezing.”

Both of my UNO boards have the same result ,is there anything wrong?

Please correct me if I’m wrong, thank you!

here is my code:

#include <Wire.h>
#include <SparkFun_KX13X.h> // Click here to get the library: http://librarymanager/All#SparkFun_KX13X

//SparkFun_KX132 kxAccel;
SparkFun_KX134 kxAccel; // For the KX134, uncomment this and comment line above

outputData myData; // Struct for the accelerometer's data

void setup()
{

  Wire.begin();

  Serial.begin(115200);
  Serial.println("Welcome.");

  // Wait for the Serial monitor to be opened.
  while (!Serial)
    delay(50);

  if (!kxAccel.begin())
  {
    Serial.println("Could not communicate with the the KX13X. Freezing.");
    while (1)
      ;
  }

  Serial.println("Ready.");

  if (kxAccel.softwareReset())
    Serial.println("Reset.");

  // Give some time for the accelerometer to reset.
  // It needs two, but give it five for good measure.
  delay(5);

  // Many settings for KX13X can only be
  // applied when the accelerometer is powered down.
  // However there are many that can be changed "on-the-fly"
  // check datasheet for more info, or the comments in the
  // "...regs.h" file which specify which can be changed when.
  kxAccel.enableAccel(false);

  //kxAccel.setRange(SFE_KX132_RANGE16G); // 16g Range
  kxAccel.setRange(SFE_KX134_RANGE16G);         // 16g for the KX134

  kxAccel.enableDataEngine(); // Enables the bit that indicates data is ready.
  // kxAccel.setOutputDataRate(); // Default is 50Hz
  kxAccel.enableAccel();
}

void loop()
{
  // Check if data is ready.
  if (kxAccel.dataReady())
  {
    kxAccel.getAccelData(&myData);
    Serial.print("X: ");
    Serial.print(myData.xData, 4);
    Serial.print(" Y: ");
    Serial.print(myData.yData, 4);
    Serial.print(" Z: ");
    Serial.print(myData.zData, 4);
    Serial.println();
  }
  delay(20); // Delay should be 1/ODR (Output Data Rate), default is 1/50ODR
}

pic2.png

What does the back of both boards look like?

What results do you get when you run the I2C scanner sketch?

#include <Wire.h>
 
 
void setup()
{
  Wire.begin();
 
  Serial.begin(9600);
  while (!Serial);             // Leonardo: wait for serial monitor
  Serial.println("\nI2C Scanner");
}
 
 
void loop()
{
  byte error, address;
  int nDevices;
 
  Serial.println("Scanning...");
 
  nDevices = 0;
  for(address = 1; address < 127; address++ )
  {
    // The i2c_scanner uses the return value of
    // the Write.endTransmisstion to see if
    // a device did acknowledge to the address.
    Wire.beginTransmission(address);
    error = Wire.endTransmission();
 
    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.print(address,HEX);
      Serial.println("  !");
 
      nDevices++;
    }
    else if (error==4)
    {
      Serial.print("Unknown error at address 0x");
      if (address<16)
        Serial.print("0");
      Serial.println(address,HEX);
    }    
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");
 
  delay(5000);           // wait 5 seconds for next scan
}

Hello,

Here is the pictures,

And it says “No I2C devices found”

Hi,

Please be careful. The Uno is a 5V board. The I/O pins output and expect 5V signals. The KX134 is a 3.3V board. There will be errors in the I2C data if the voltages are not correct. You need to use a level shifter to convert between 3.3V and 5V.

I hope this helps,

Paul

I am having the same problem with the example code, was there any resolution to this?

Hi c_feldman,

Are you also using an Arduino Uno? If you are, you will need to use a level shifter to convert between the Uno’s 5V signals and 3.3V for the KX134:

https://www.sparkfun.com/products/15439

I hope this helps,

Paul

Even though I am using the 3.3V header on the Arduino Uno? I have a Due coming in since I am using these for a data collection project and need something a little more powerful so will that solve it?

Hi,

It’s confusing… The Uno can output 3.3V power on that header pin, but the microcontroller and its I/O pins are all 5V. You can get errors in the I2C data because the voltages aren’t correct.

The Due should work nicely for you. It does not work well with I2C devices which clock-stretch. But you should be OK with the KX134. I have a Due and a KX134 here, so please reach out if you have more issues and I’ll take a look.

Best wishes,

Paul

Ok, so I now have 2 separate DUE’s and 4 KX134’s (since I am out of time to figure out how to get 2 separate buses setup on a single DUE like I was originally hoping using “Wire” and “Wire1”). I am able to get it to sample and record to the SD card on my Adafruit Data logging shield ~333 times per second (1000 recordings over 3 seconds). I tried adjusting the code so that it is only reading and saving one axis’ value, but I still only saw ~300 readings per second. I rewrote the PC1 and ODCNTL register to properly adjust the ODR to 3200Hz (since I am going to be doing vibration testing at up to 500Hz so I want 6 values per period). The flush command also shows up to a 50ms delay between batches. I am a complete noob when it comes to this (I’m an ME), so I’m not sure if there is some easier way that involves directly streaming the data or something else. I have vibration testing at NTS in a week and can’t get this figured out.

Hi,

For the fastest sampling, you actually want to be using SPI, instead of I2C. But you probably don’t have time to go up that learning curve…? Maybe save that suggestion for later.

Some things that should help:

The Due Wire (I2C) bus almost certainly defaults to 100kHz. You can increase that to 400kHz by calling Wire.setClock(400000); after Wire.begin(); . There is a small complication in that the kxAccel.begin() may reset the bus back to 100kHz depending on how you call it. So, it will do no harm to (re)call Wire.setClock(400000); after kxAccel.begin() or kxAccel.softwareReset()

kxAccel.getAccelData is slow because it manually checks if the buffer is being used, and if the data resolution is 16-bit or 8-bit, each time it is called. That involves extra I2C bus transactions which slow things down. It is much more efficient to use the KX134’s buffer and to read the data directly from the buffer using kxAccel.getRawAccelBufferData. See Example3 for details:

https://github.com/sparkfun/SparkFun_KX … buffer.ino

If you are not using the buffer, kxAccel.getRawAccelRegisterData reads the data directly from the X, Y and Z registers. It too is more efficient than kxAccel.getAccelData

Those changes should increase the sample rate significantly. If you need to increase it further:

If you are using the buffer (based on Example3), then kxAccel.getSampleLevel() also slows things down a little by checking how much data is waiting in the buffer. Again it involves a bus transaction. You can speed things up by connecting the KX134 INT1 pin to a GPIO pin on the Due. When the pin goes high, data is ready and you can read it without needing to call kxAccel.getSampleLevel() first.

The code in Example3 generates an interrupt on INT1 when the buffer is full. In you case, it might be more efficient to generate the interrupt when data is ready (DRDYI1) instead of when the buffer is full (BFI1). The code in Example2 may work better for you. kxAccel.routeHardwareInterrupt(0x10); tells the KX134 to generate an interrupt when data is ready. (For buffer-full, the value is 0x40 instead of 0x10.)

https://github.com/sparkfun/SparkFun_KX … rrupts.ino

If you do use the code from Example2, don’t forget to use kxAccel.getRawAccelRegisterData instead of kxAccel.getAccelData to speed things up:

    rawOutputData myRawData;
    if (kxAccel.getRawAccelRegisterData(&myRawData) == true)
    {
      kxAccel.convAccelData(&myData, &myRawData); // Manually convert the raw data to floating point
      Serial.println();
      Serial.print("X: ");
      Serial.print(myData.xData, 4);
      Serial.print(" Y: ");
      Serial.print(myData.yData, 4);
      Serial.print(" Z: ");
      Serial.print(myData.zData, 4);
      Serial.println();
    }

The getData functions read all three axes registers in one go. You could change the code so that it only reads a single axis, but you would need to hack the library at quite a low level to do that. If you think you need to do that, I’d suggest using SPI instead.

I hope this helps,

Paul

PaulZC,

Thank you so much for the info! I increased the wire clock and optimized/removed some small delays in my code and have gotten as high as ~750Hz. However, when I changed from using getData to getRawAccelRegisterData my recordings have dropped to ~650Hz. Here is my implementation of it:

if (recording) {
    unsigned long currentMicros = micros();
    if (currentMicros - previousMillis >= interval) {
      previousMillis = currentMicros;


      // Get the current date and time from the computer
      String currentDateTime = getCurrentDateTime();

      // Log the data to the batch array
      rawOutputData myRawData;
      if (kxAccel.dataReady()) {
        kxAccel.getRawAccelRegisterData(&myRawData);
        kxAccel.convAccelData(&myData, &myRawData); //converts raw data to floating point
        // Store the data entry in the batch array
        dataBatch[dataCount] = myData;
        dataCount++;

        // Check if the batch is full
        if (dataCount >= BATCH_SIZE) {
          flushDataBatch();
        }
      }
    }
  }

Do you see any glaring problems? I don’t need the most precise data in the world, so I am also thinking of changing to 8-bit values to see how that improves timing? I am not sure what the command is in order to do so (I am not very good at understanding .h files yet), so I think I am going to just try to write to the register to change it (though you probably know the shortcut for it). I am starting to work on setting up a buffer now, otherwise our SWE are suggesting I try creating a multi-threaded program to execute the flush command at the same time as the read from the buffer.

Hi,

It’s difficult for me to comment without seeing all of your code, but here are some suggestions:

You are checking micros() and only calling kxAccel.dataReady() once “interval” has expired.

What value are you using for “interval”? I hope / expect 250 or less?

How long does flushDataBatch() take to execute? Is that where you write the data to SD card? Is that limiting the overall rate?

Somewhere you need to be calling kxAccel.setOutputDataRate(); to set the output data rate. The default is 50Hz which is a “rate” of 6. To set the rate to 3200Hz you need to call kxAccel.setOutputDataRate(12); . Have a look at Table 13 in the Reference Manual: https://cdn.sparkfun.com/assets/4/7/f/9 … ev-1.0.pdf

Threading is a good idea, but you probably don’t have time to add that. You could have one thread reading the accelerometer (over I2C) and buffering the data; and a separate thread to write the data to SD card (over SPI).

Cheers,

Paul

Check how much RAM the Due has available. You may get better results if you keep reading accelerometer data over I2C and buffering it in RAM for (say) a full second, then spend time writing the whole “block” to SD card. Each second’s worth of data samples will be contiguous. But there will be gaps (in time) between each “block” while it was written to SD card. So long as you perform your spectral analysis on each block in turn, and ignore the gaps, all should be well. You may need to write micros to the SD card file too, to indicate each “start of block” and help with your post-processing.

Another thought…

If all you are doing is reading the XYZ data from the accelerometer registers, it takes:

A two byte write to set up the read, starting at XOUT_L

A seven byte read to read the six X/Y/ZOUT registers.

At 400kHz, that is roughly 10*9 = 90 bits which takes approx. 225us. So you are limited to approx. 4kHz, when running the bus flat-out. With no extra time for dataReady bus transactions… I.e. you are using the interrupt pin to time your read transactions, so you don’t slow things down with dataReady.

But do you really need 3200Hz? So long as you are sampling at 1600Hz, then - from the Nyquist perspective - I think you’re OK?

Or maybe you really do need SPI - which is ~10 times faster than I2C…?

OK. I’m on UK time and I’m off to bed…!

Thank you for all the help today. I’ll go through your suggestions and see what I can figure out. Is there any function that would allow me to read just 8-bit values or do I need to use register reads for “XOUT_H?” For the morning, here is my entire current working code, which now is able to record at almost 800Hz (Due, Adafruit Data Logging Shield, and SparkFun KX134):

#include <Wire.h>
#include <SPI.h>
#include <SD.h>
#include <SparkFun_KX13X.h>

#define SLAVE_ADDRESS 0x1F
#define REGISTER_ADDRESS 0x1B
#define REGISTER_ADDRESS2 0x21
#define LED_PIN 13

#define Wire Wire1  // Define Wire as Wire1 for the second I2C interface

SparkFun_KX134 kxAccel;

File dataFile;
bool recording = false; // Flag to indicate if recording is active
unsigned long previousMillis = 0;
const unsigned long interval = 312; // Interval in microseconds for recording at 3200Hz (1 second / 3200)
float startMillis = 0;

const int BATCH_SIZE = 100;  // Number of data entries to accumulate before flushing
struct timedOutputData {
  String time;
  outputData data;
};
timedOutputData dataBatch[BATCH_SIZE];  // Array to store data entries
int dataCount = 0;  // Counter for the number of data entries accumulated

void setup() {
  // Remainder of setup as before...
    pinMode(LED_PIN, OUTPUT);

  Wire.begin(REGISTER_ADDRESS); // Initialize I2C communication
  Wire.setClock(400000);
  //Wire.begin(REGISTER_ADDRESS2); // Initialize second I2C communication
  Serial.begin(115200); // Initialize serial communication with baud rate 115200

  //Set up SD card
  if (!SD.begin()) {
    Serial.println("SD card initialization failed!");
    while (true) {
      // Infinite loop to halt the program if SD card initialization fails
    }
  }

  while (!Serial)
    delay(50);

  if (!kxAccel.begin(Wire))
  {
    Serial.println("Could not communicate with the the KX13X. Freezing.");
    while (1)
      ;
  }

  // Prompt for creating a new file
  bool createNewFile = promptCreateNewFile();

  if (createNewFile) {
    createUniqueFile();
  } else {
    openExistingFile();
  }

    // Write header for acceleration data in CSV file
  if (recording && dataFile) {
    dataFile.println("Time,X,Y,Z");
    dataFile.flush();
  }

  // Set the initial value of the CNTL1 register to 00000000
  Serial.println("CNTL1 Register Reset Value:"); // tells you it is about to flash the CNTL register
  writeRegister(SLAVE_ADDRESS, REGISTER_ADDRESS, 0x00);

  // Read and print the initial value to confirm it is in its default state
  byte initialValue = readRegister(SLAVE_ADDRESS, REGISTER_ADDRESS);
  printBinary(initialValue);

  Serial.println("ODCNTL Register Reset Value:"); // tells you it is about to flash the ODCNTL register
  // Set the initial value to 00000110
  writeRegister(SLAVE_ADDRESS, REGISTER_ADDRESS2, 0x06);

  // Read and print the initial ODCNTL value
  byte initialValue2 = readRegister(SLAVE_ADDRESS, REGISTER_ADDRESS2);
  printBinary(initialValue2);

  // Change the value to 00001100
  writeRegister(SLAVE_ADDRESS, REGISTER_ADDRESS2, 0x0C);

  // Read and print the updated value
  byte updatedValue2 = readRegister(SLAVE_ADDRESS, REGISTER_ADDRESS2);
  printBinary(updatedValue2);

  Serial.println("CNTL1 Register");
  // Change the value to 11000000
  writeRegister(SLAVE_ADDRESS, REGISTER_ADDRESS, 0xC0);

  // Read and print the updated value
  byte updatedValue = readRegister(SLAVE_ADDRESS, REGISTER_ADDRESS);
  printBinary(updatedValue);

  Serial.println("Registers have been updated to High performance mode");

  //ACCELEROMETER STARTUP PROCEDURE
  kxAccel.enableAccel(false);
  kxAccel.setRange(SFE_KX134_RANGE64G);
  kxAccel.enableDataEngine();
  //kxAccel.setOutputDataRate(0b0000110); // Set output data rate to 3200Hz
  kxAccel.enableAccel();
  Wire.setClock(400000);
}

void loop() {
  if (Serial.available()) {
    String command = Serial.readStringUntil('\n');
    command.trim();

    if (command == "start") {
      startRecording();
    } else if (command == "stop") {
      stopRecording();
    }
  }

  if (recording) {
    unsigned long currentMicros = micros();
    if (currentMicros - previousMillis >= interval) {
      previousMillis = currentMicros;

      // Get the current date and time from the computer
      String currentDateTime = getCurrentDateTime();

      // Log the data to the batch array
      if (kxAccel.dataReady()) {
        timedOutputData timedData;
        kxAccel.getAccelData(&timedData.data);
        timedData.time = currentDateTime;
        // Store the data entry in the batch array
        dataBatch[dataCount] = timedData;
        dataCount++;

        // Check if the batch is full
        if (dataCount >= BATCH_SIZE) {
          flushDataBatch();
        }
      }
    }
  }
}

//FUNCTIONS CREATED FOR SIMPLIFICATION OF PROGRAM
//
//
//
//
// Function to write a value to a specific register on the slave device
void writeRegister(byte address, byte reg, byte value) {
  Wire.beginTransmission(address);
  Wire.write(reg);
  Wire.write(value);
  Wire.endTransmission();
  delay(10); // Delay to allow for processing time on the slave device (adjust if necessary)
}

// Function to read the value from a specific register on the slave device
byte readRegister(byte address, byte reg) {
  Wire.beginTransmission(address);
  Wire.write(reg);
  Wire.endTransmission();

  Wire.requestFrom(address, (byte)1);
  if (Wire.available()) {
    return Wire.read();
  }

  return 0; // Return 0 if no data is received
}

// Function to print a binary value
void printBinary(byte value) {
  for (int i = 7; i >= 0; i--) {
    Serial.print(bitRead(value, i));
  }
  Serial.println();
}

// Function to start recording
void startRecording() {
  if (!recording) {
    recording = true;
    startMillis = millis();
    Serial.println("Recording started.");

    // Write a note indicating the start of recording
    if (dataFile) {
      dataFile.println("Recording started");
      dataFile.flush(); // Flush the data to the file
    }
  }
}

// Function to stop recording
void stopRecording() {
  if (recording) {
    recording = false;
    Serial.println("Recording stopped.");

    // Write a note indicating the stop of recording
    if (dataFile) {
      dataFile.println("Recording stopped");
      dataFile.flush(); // Flush the data to the file
    }
  }
}

void createUniqueFile() {
  // Check if a file with the same name already exists
  int fileNumber = 0;
  while (SD.exists("data_" + String(fileNumber) + ".csv")) {
    fileNumber++;
  }

  // Create a new file with a unique name
  String fileName = "data_" + String(fileNumber) + ".csv";
  dataFile = SD.open(fileName, FILE_WRITE);
  if (dataFile) {
    Serial.println("New file created: " + fileName);

    // Write column labels
    dataFile.println("Time,X,Y,Z");
    dataFile.flush(); // Flush the data to the file
  } else {
    Serial.println("Error creating new file!");
  }
}

// Function to open the existing file
void openExistingFile() {
  dataFile = SD.open("data.csv", FILE_WRITE);
  if (dataFile) {
    Serial.println("Existing file opened: data.csv");
    // Move the file pointer to the end of the file
    dataFile.seek(dataFile.size());
  } else {
    Serial.println("Error opening file for appending!");
  }
}

// Function to prompt for creating a new file
bool promptCreateNewFile() {
  while (true) {
    Serial.println("Do you want to create a new file? (Y/N)");
    while (!Serial.available()) {
      // Wait for input
    }

    char input = Serial.read();
    if (input == 'Y' || input == 'y') {
      return true;
    } else if (input == 'N' || input == 'n') {
      return false;
    }
  }
}

String getCurrentDateTime() {
  unsigned long currentMillis = millis();
  unsigned long elapsedMillis = currentMillis - startMillis;

  // Format the elapsed milliseconds as a string
  String elapsedMillisStr = String(elapsedMillis);

  return elapsedMillisStr;
}

void flushDataBatch() {
  // Log the data batch to the file
  for (int i = 0; i < dataCount; i++) {
    dataFile.print(dataBatch[i].time);
    dataFile.print(",");
    dataFile.print(dataBatch[i].data.xData, 4);
    dataFile.print(",");
    dataFile.print(dataBatch[i].data.yData, 4);
    dataFile.print(",");
    dataFile.println(dataBatch[i].data.zData, 4);
  }
  dataFile.flush(); // Flush the data to the file

  // Reset the counter for the next batch
  dataCount = 0;
}

Hi,

I’ve spent as much time as I can on this today. I need to get back to the day job!

I took your code and tweaked it a bit:

I removed your register write and read functions. You can do everything through the library - but there was one trick I needed to remember to put the chip into high-performance mode.

I added support for the accelerometer interrupt. That’s the only way to get to 1600Hz reliably.

I removed the String from your timedOutputData and replaced it with a char array. String is dynamic, it grows as you add text to it. Using it in an array can leave your memory in a mess. It’s better to define a char array which is (just) big enough to hold your text.

The ‘timestamp’ is now millis plus three decimal places of micros.

The reworked code is here: https://github.com/sparkfun/SparkFun_KX … ogging.ino

I’ve tested it on ESP32 (Thing Plus C) and it works well at 1600Hz. 3200Hz ~works but the sampling becomes irregular. It is pushing the bus too hard.

The red trace (Channel 2) is the KX134 interrupt pin. You can see how the interrupts become regular quickly at 1600Hz, but at 3200Hz they’re all over the place. The bus can’t cope.

It might be a good idea to discard the first two or three samples in each 100-sample batch, as the timing of the first ~two is off and will throw off your frequency analysis.

I haven’t had time to test this on a Due, but it should work OK.

Let me know if the example needs tweaking. I’ll include it in the next release of the library so others can use it too.

Good luck - and I hope this works well for you!

Paul

Oh, yes. Forgot to mention. Reading 8-bit data won’t help. It would actually be slower to set up and perform three single byte reads, than to read all six bytes of 16-bit data.

PaulZC,

Thank you so much for all your help, I appreciate all the help considering this is my day job. Also thank you for clearing up the information on what functions I should be using rather than writing to the registers as well as 8-bit vs 16-bit recording. I made some tweaks to the program for my setup and it works like a charm.

I still ideally would like to be able to run 2 of these sensors at the same time for data collection, but it looks like maybe I am at the limit of the i2c bus and I should probably just run each kx134 off of it’s own DUE (If so I need to order more today, unless a similar result could be achieved on an uno R3 which we have tons of laying around)? Also, in terms of the analysis of the data I will be collecting, with this interrupt pin method, every time the batch is emptied by the interrupt there is a ~50ms delay (i.e. sample I tested went from 70ms on the last recording to 131ms). From what I understand this means I will need to break up the data into batches before plotting, right?