[SOLVED] LED pitch roll indicator astable... flicker & unlit LEDs... bad coding?

I'm building a bow/stern (pitch) port/starboard (roll) indicator using an accelerometer for a sensor and an LED strip for an indicator (part of a larger system). While it works well in principle, I noticed that

  1. when at rest (on land) and angled manually, the LED indicating pitch flickers one position forwards or backwards, as if can't quite decide, so to say, and

Flicker issue

Without any filtering, averaging, smoothing, etc.

With exponential moving average

  1. if the angle changes rapidly, the short LED tail illumination becomes holey the faster one changes the pitch, as if something can't keep up fast enough.

Tail illumination issue

Obviously the exponential moving average I'm using does not solve issues 1. and 2. So, what am I doing wrong regarding both problems?


(sorry, the old method of embedding small images doesn't work at the moment)

#include <Wire.h>
#include "FastLED.h"

// Variables that remain constant
#define DEVICE (0x53) // ADXL345 I2C address (is fixed!)
byte _buffer[6]; // Array to store the raw sensor output bytes
char POWER_CTL = 0x2D; // Power control register address
char DATA_FORMAT = 0x31; // Data format register address
char DATAX0 = 0x32; // x-axis data register byte 0
char DATAX1 = 0x33; // x-axis data register byte 1
char DATAY0 = 0x34;
char DATAY1 = 0x35;
char DATAZ0 = 0x36;
char DATAZ1 = 0x37;
// Averaged output from 2-point calibration with 6-point-tumble
// method. The values are particular to each IC, meaning each
// sensor must undergo that process (no default values exist!)
const int xUp =  252; // Sensor/PCB pointing up
const int xDown = -262; // Sensor/PCB pointing down
const int yUp =  257;
const int yDown = -255;
const int zUp =  255;
const int zDown = -241;
// Sensor resolution in 2g mode is 3.9 mg/LSB, so output must
// be converted to g
const float rawToG = 0.0039;
// Exponential moving average "α" and "S" initialisation
float alphaEMA = 0.1;
float pitchEMA = 0.0;
const byte pinData = 4; // Digital output pin to LED strip
const byte pinClock = 3; // Digital output pin to LED strip
const byte numLeds = 144; // Number of LEDs
struct CRGB leds[numLeds]; // Array that stores each LED's data

// Variables that can change
int xRaw, yRaw, zRaw; // Store the raw sensor output
float xUp1g, xDown1g, yUp1g, yDown1g, zUp1g, zDown1g; // Decimal values for offset and gain calculations
float xOffset, xGain, yOffset, yGain, zOffset, zGain; // Decimal values for offset and gain calculations
int ledPosition, ledHue;

void setup()
  // Initialise the I2C library to read from the sensor

  // Initialise serial output, only for initial calibration and testing
  // Serial.begin(115200);

  // Set sensor to 2g mode, typical output -256 to +256 per axis
  registerWrite(DATA_FORMAT, 0x00);

  // Set sensor to measuring mode
  registerWrite(POWER_CTL, 0x08);

  // Convert averaged calibration output from above to g
  xUp1g = xUp * rawToG; xDown1g = xDown * rawToG;
  yUp1g = yUp * rawToG; yDown1g = yDown * rawToG;
  zUp1g = zUp * rawToG; zDown1g = zDown * rawToG;

  // Calculate offset and gain values as per Analog Devices
  xOffset = 0.5 * (xUp1g + xDown1g); xGain = 0.5 * (xUp1g - xDown1g);
  yOffset = 0.5 * (yUp1g + yDown1g); yGain = 0.5 * (yUp1g - yDown1g);
  zOffset = 0.5 * (zUp1g + zDown1g); zGain = 0.5 * (zUp1g - zDown1g);

  // Initialise the FastLED library with the type of programmable RGB LED
  // used, the digital output pins the LED strip is wired to, the array that
  // holds each LED's data, and the number of LEDs in the strip. Note: BGR
  // (not RGB) is the required colour-order for APA102C LEDs
  FastLED.addLeds<APA102, pinData, pinClock, BGR>(leds, numLeds);

  // Sets the overall LED strip's brightness (values 0 - 255)

void loop()
  // A call to this function reads from the ADXL345 sensor

  // A call to this function applies offset and gain to the raw output
  // and calculates the roll (x) and pitch (y) angle

  // Set the leading LED's hue
  leds[ledPosition] = CHSV(ledHue, 255, 255);
  // Display all LED's data (= illuminate the LED strip)
  // Generate a short trail behind the leading LED
  for (int i = 0; i < numLeds; i++)

void readSensor()
  // The raw x, y and z data output is represented in six bytes
  byte bytesToRead = 6;

  // Read from the sensor's six data registers using the burst read
  // method of all six registers at once as per Analog Devices
  registerRead(DATAX0, bytesToRead, _buffer);

  // Convert output, 10 bit, 2 bytes, with the LSB first
  xRaw = (((int)_buffer[1]) << 8) | _buffer[0];
  yRaw = (((int)_buffer[3]) << 8) | _buffer[2];
  zRaw = (((int)_buffer[5]) << 8) | _buffer[4];

void calculateAngle()
  // Calculate adjusted x, y and z values based on offset and gain
  // Cast ints to floats at execution time as per Analog Devices
  float xCal = (((float)xRaw * rawToG - xOffset) / xGain) * 256;
  float yCal = (((float)yRaw * rawToG - yOffset) / yGain) * 256;
  float zCal = (((float)zRaw * rawToG - zOffset) / zGain) * 256;

  // Calculate roll and pitch angles where pitch = rotation around
  // y-axis and roll = around x-axis
  float pitch = atan2(xCal, sqrt(yCal * yCal + zCal * zCal)) * 180.0 / PI;
  float roll = atan2(yCal, (sqrt(xCal * xCal + zCal * zCal))) * 180.0 / PI;

  // Calculate exponential moving average
  pitchEMA = (alphaEMA * pitch) + ((1 - alphaEMA) * pitchEMA);

  // Calculate the position of the leading LED depending on the
  // sensor's/PCB's y-axis (pitch)
  ledPosition = round(map(pitchEMA, 90, -90, 0, numLeds - 1));

  // Calculate the hue based on the sensor's/PCB's x-axis roll,
  // where port = red, starboard = green
  ledHue = map(roll, 45, -45, 96, 0);

  // Serial.print(pitchEMA, 0); Serial.print(" "); Serial.println(roll, 0);

  // For CoolTerm/Excel raw value averaging and testing only
  // delay(100);

void registerWrite(byte address, byte value)
  // Write to a sensor register

void registerRead(byte address, byte num, byte _buffer[])
  // Read from the sensor registers into the _buffer array
  Wire.requestFrom(DEVICE, num);

  byte i = 0;

  while (Wire.available())
    // Read one byte at a time
    _buffer[i] = Wire.read();


Wrap your image like so ..... [img]your image url[/img]

Yeah, forgot that step; thanks!

It's late and I've only quickly glanced through your code...

For problem 1, when static, the sensor is reporting fluctuating values? Some random things that spring to mind: dampening the values, applying a dead-zone, using a non-linear conversion.

For problem 2, it simply looks like it's possible for your conversion to skip an LED, e.g. one iteration might set ledPosition 10 and the next might set ledPosition 8. LED 9 never gets lit. To fix that you'll either need to sample faster, or modify the routine to cope with missed LEDs; e.g. instead of setting just one LED, always record the last set LED, and light a range between that last one and the new one. Keeping your fade neat will require a bit of extra effort in that case since you'll have two or more LEDs at the full brightness instead of just one.


For 1. yes, when static, the angle values after my calculation fluctuate as shown, so that means using an exponential moving average as I do is not right, or something regarding my variables/calculations; but I can't figure out how to do it better.

For 2. I don't know how to sample faster. I'm not using any delays. But something in my code(?) results in LEDs being skipped, the faster the boat pitches.

Any suggestions welcome!

When I chart pitchEMA (float) with one decimal, the following picture emerges. One can see the fluctuations well while the sensor is static at 10°, so the EMA is of no use here. Sometimes the fluctuations near 11° and then the neighbouring LED briefly lights up.

I assume this is the reason for both problems 1. and 2. Do I need something else than an EMA or is the float variable type the culprit?

Accidentally sort of solved both issues. When I put a delay(10); in the loop, the leading LED remains in place when the sensor is static and the short tail has no more holes when the boat is heavily jerked up and down.

So, actually putting a spanner in the works improved on both issues. I don't quite understand why that is, and I don't like delays in code as they block, but here we are. Odd.

Looking at the AdaFruit docs for that accelerometer, there is a line that reads:

The setDataRate() function sets the rate at which the sensor output is updated. Rates above 100 Hz will exhibit
increased noise. Rates below 6.25 Hz will be more sensitive to temperature variations. See the data
sheet (https://adafru.it/c5e) for details.

Setting a delay of 10ms means you're sampling at ~100Hz; perhaps that's significant?

The sensor allows for much faster sampling though. Apparently sticking a delay in there, slowing everything down a little, apparently helps.

Why that is so or what in my code is wrong but then alleviated by the delay... I don't know.