Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for the adxl345 accelerometer for the BTT EBB42 V1.2 toolhead #27635

Open
wants to merge 5 commits into
base: bugfix-2.1.x
Choose a base branch
from

Conversation

rondlh
Copy link
Contributor

@rondlh rondlh commented Jan 11, 2025

Add support for the adxl345 accelerometer for the BTT EBB42 V1.2 toolhead

Requirements

BTT EBB42 V1.2 toolhead

Benefits

The accelerometer can be used for input shaping.

Configurations

Just enable ACCELEROMETER_ADXL345 in Configuration_adv.h
Note: This is new, and currently there is no code to process the accelerometer data.

Related Issues

#27588

Add support for the adxl345 accelerometer
@thinkyhead
Copy link
Member

We will need to explore the methods used for sampling, motion patterns that are analyzed, and the conversion of the collected data into settings to apply to our existing input shaping features. This is a challenging frontier for the casual developer, but I believe we can tackle it!

@thinkyhead thinkyhead requested a review from tombrazier January 12, 2025 00:08
@tombrazier
Copy link
Contributor

This is going to get a lot of people excited I think.

After a very quick quick look at the code, it looks to me like a wrapping of an SPI interface for the adxl345. Have I read that correctly?

Using the accelerometer to measure resonant frequencies can be done with a frequency sweep by printing the same pattern used here. Whilst this is occurring, it will be necessary to poll the appropriate ADXL345::readMeasurement[XYZ]() function at high frequency and record the peak values for each frequency. I am vaguely imagining maintaining a "peak" value which decays by some factor before every sample and then is boosted if the sample value is higher than the decaying peak. i.e. something like peak = MAX(sample, peak * 0.5).

@thinkyhead
Copy link
Member

Here's a sample of arduinoFFT code from the chatbot that should help our cause…

#include <SPI.h>
#include <arduinoFFT.h>

// Constants
#define FFT_SIZE 128          // Size of FFT (power of 2)
#define SAMPLING_FREQUENCY 1000 // Sampling frequency in Hz
#define ADXL_CS_PIN 10         // Chip Select pin for ADXL

// Globals
double vReal[FFT_SIZE];
double vImag[FFT_SIZE];
arduinoFFT FFT = arduinoFFT(vReal, vImag, FFT_SIZE, SAMPLING_FREQUENCY);

void setup() {
  Serial.begin(115200);
  pinMode(ADXL_CS_PIN, OUTPUT);
  SPI.begin();

  // ADXL initialization code here
}

void loop() {
  // Collect data
  for (int i = 0; i < FFT_SIZE; i++) {
    vReal[i] = readADXL_X(); // Replace with function to read ADXL X-axis acceleration
    vImag[i] = 0;            // Imaginary part is always 0
    delayMicroseconds(1000000 / SAMPLING_FREQUENCY);
  }

  // Perform FFT
  FFT.Windowing(FFT_WIN_TYP_HAMMING, FFT_FORWARD); // Apply a window function
  FFT.Compute(FFT_FORWARD);                        // Compute FFT
  FFT.ComplexToMagnitude();                        // Compute magnitude

  // Find dominant frequency
  double peakFrequency = FFT.MajorPeak();
  Serial.print("Peak Frequency: ");
  Serial.println(peakFrequency, 2);

  delay(1000); // Delay for visualization
}

// Example function to read X-axis acceleration
double readADXL_X() {
  digitalWrite(ADXL_CS_PIN, LOW);
  SPI.transfer(0x32 | 0x80); // Send read command for X-axis
  uint8_t lsb = SPI.transfer(0x00);
  uint8_t msb = SPI.transfer(0x00);
  digitalWrite(ADXL_CS_PIN, HIGH);
  int16_t raw = (msb << 8) | lsb;
  return raw * 0.004; // Convert to g's (adjust based on ADXL scale)
}

@tombrazier
Copy link
Contributor

Oh that makes tons of sense. Then you don't even have to know what the input frequency is when measuring the output.

@thinkyhead
Copy link
Member

Yep. We just need enough samples at a regular interval to get some complete waves. There's probably some dynamic methods as well, such as counting up frequency peaks in buckets and then narrowing down on each peak.

@robherc
Copy link
Contributor

robherc commented Jan 15, 2025

So are we trying to FFT the "ringing" from an abrupt step-input, or should we feed in sinusoidal motion of known peak distance/acceleration, in a frequency sweep, and look for the induced over/undershoots of the target points?

I agree that sharp-input ringing could potentially give a "quicker output" through fft, and may give better accuracy for resonance decay-rates, but I'd expect the second method to give better settings for exactly what amount of over/undershoot to expect, and thus how to compensate best for the initial inaccuracy caused by axis resonance.

Ok, so I guess both would give different bits of information that would benefit the calibrated settings we could generate from the test.

@rondlh
Copy link
Contributor Author

rondlh commented Jan 16, 2025

This is going to get a lot of people excited I think.

After a very quick quick look at the code, it looks to me like a wrapping of an SPI interface for the adxl345. Have I read that correctly?

Using the accelerometer to measure resonant frequencies can be done with a frequency sweep by printing the same pattern used here. Whilst this is occurring, it will be necessary to poll the appropriate ADXL345::readMeasurement[XYZ]() function at high frequency and record the peak values for each frequency. I am vaguely imagining maintaining a "peak" value which decays by some factor before every sample and then is boosted if the sample value is higher than the decaying peak. i.e. something like peak = MAX(sample, peak * 0.5).

Thanks for the feedback. I will have a look into this.
The ADXL345 is a SPI/i2c device, but the users don't care about that, they just want the data from the sensor. That's what the code in the PR does, just give you the acceleration data. To make this useful the data needs to be interpreted and the code might need to be updated to meet the requirements. You are talking about "high frequency", what numbers are we talking about?

Yep. We just need enough samples at a regular interval to get some complete waves.
@thinkyhead Any suggestion for which interval to use?

If we don't care about the real time aspect of the data, then the sensor with its 32 level FIFO is quite powerful.
The sensor supports up to a sample rates of 3200Hz, but is quite noisy at that speed. Typically you want to sample at more than double the frequency you are trying to measure, 3-4 times is even better. I expect that we are dealing with quite low frequencies, below 50Hz, so a sample frequency of 100Hz or 200Hz would be suitable.
I will do some experiments on it when I find the time.

@robherc
Copy link
Contributor

robherc commented Jan 16, 2025

If we don't care about the real time aspect of the data, then the sensor with its 32 level FIFO is quite powerful. The sensor supports up to a sample rates of 3200Hz, but is quite noisy at that speed. Typically you want to sample at more than double the frequency you are trying to measure, 3-4 times is even better. I expect that we are dealing with quite low frequencies, below 50Hz, so a sample frequency of 100Hz or 200Hz would be suitable. I will do some experiments on it when I find the time.
FWIW, Nyquist says we need to sample at a minimum of 5x the frequency of the fastest wave you want to capture.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
Status: In Progress
Development

Successfully merging this pull request may close these issues.

[FR] ADXL345 3-axis accelerometer support for input shaping measurements
4 participants