Go Down

Topic: ADXL345 Adafruit Pixie RGB LED x and y rotation colour mixing (Read 636 times) previous topic - next topic

Lagom

I want to change a 3W RGB LED's colours depending on x and y rotation (180°, only upper hemisphere) with x rotation from Magenta to Green and y rotation from Yellow Orange to Cyan Blue.

I got it to work well, without jitter, but only EITHER blending from Magenta to Green (x) OR Yellow Orange to Cyan Blue. I guess I would need some conditional checking that toggles between the two, depending on which axix is currently dominant, depending on how the sensor is oriented.

Thanks for some ideas!

Code: [Select]
// Library required to read from this sensor; use the latest version
#include <Wire.h>

// Variables that remain constant
#define DEVICE (0x53) // ADXL345 I2C address (fixed)
byte _buff[6];
char POWER_CTL = 0x2D;
char DATA_FORMAT = 0x31;
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;
const int xMax =  254; // Averaged readings from 2-point calibration with 6-point-tumble method (values particular to each IC)
const int xMin = -264;
const int yMax =  258;
const int yMin = -256;
const int zMax =  256;
const int zMin = -240;
const float xOffset = 0.5 * (xMax + xMin); // p. 8 http://www.analog.com/media/en/technical-documentation/application-notes/AN-1057.pdf
const float xGain = 0.5 * (xMax - xMin);
const float yOffset = 0.5 * (yMax + yMin);
const float yGain = 0.5 * (yMax - yMin);
const float zOffset = 0.5 * (zMax + zMin);
const float zGain = 0.5 * (zMax - zMin);
const float alphaEMA = 0.5; // Smoothing factor 0 < α < 1 (smaller = smoother = less responsive)
const byte xRotColours[][3] = { // xRot -> from M 255, 0, 255 to G 0, 255, 0
  {255, 0, 255},
  {0, 255, 0},
};
const byte yRotColours[][3] = { // yRot -> from YR 255, 127, 0 to CB 0, 127, 255
  {255, 32, 0},
  {0, 127, 255},
};
const byte pinLEDR = 9; // Pins for common cathode RGB LED, see https://dronebotworkshop.com/rgb-leds/, later bright 3W Adafruit Pixie
const byte pinLEDG = 10;
const byte pinLEDB = 11;

// Variables that can change
float xEMA = 0;  // Initialise with an arbitrary reading (0 = oriented in the horizontal plane at start)
float yEMA = 0;
float zEMA = 0;

void setup()

{

  Wire.begin();

  Serial.begin(57600);

  registerWrite(DATA_FORMAT, 0x00); // Set to 2g mode, typical output -256 +256 per axis, see p. 4 http://www.analog.com/media/en/technical-documentation/data-sheets/ADXL345.pdf
  registerWrite(POWER_CTL, 0x08); // Set to measuring mode

  pinMode (pinLEDR, OUTPUT);
  pinMode (pinLEDG, OUTPUT);
  pinMode (pinLEDB, OUTPUT);

}

void loop()

{

  readSensor();

  float xRot = atan(xEMA / sqrt((pow(yEMA, 2)) + pow(zEMA, 2))) * 57.2957 + 90; // See p. 7 http://www.analog.com/media/en/technical-documentation/application-notes/AN-1057.pdf
  float yRot = atan(yEMA / sqrt((pow(xEMA, 2)) + pow(zEMA, 2))) * 57.2957 + 90;
  float zRot = atan(sqrt((pow(xEMA, 2)) + pow(yEMA, 2)) / zEMA) * 57.2957;

  if (zEMA > 0.003)
  { // Instability when x and y are exactly in line with earth's gravity, see p. 12 https://www.nxp.com/files-static/sensors/doc/app_note/AN3461.pdf

    double xFraction = (xRot - 0) / (double) (180 - 0); // Convert 0 -> 180 into 0.0 -> 1.0 (percentage)
    double yFraction = (yRot - 0) / (double) (180 - 0);

    byte xR = (xRotColours [1][0] - xRotColours [0][0]) * xFraction + xRotColours [0][0];
    byte xG = (xRotColours [1][1] - xRotColours [0][1]) * xFraction + xRotColours [0][1];
    byte xB = (xRotColours [1][2] - xRotColours [0][2]) * xFraction + xRotColours [0][2];

    byte yR = (yRotColours [1][0] - yRotColours [0][0]) * yFraction + yRotColours [0][0];
    byte yG = (yRotColours [1][1] - yRotColours [0][1]) * yFraction + yRotColours [0][1];
    byte yB = (yRotColours [1][2] - yRotColours [0][2]) * yFraction + yRotColours [0][2];

    /*Serial.print(xFraction, 2);
      Serial.print(" ");
      Serial.println(yFraction, 2);*/

    setLED (xR, xG, xB); // Or use y-values... but how to use both x and y together?
    //setLED (yR, yG, yB);

  }

  //delay(500); // For CoolTerm/Excel use only

}

void readSensor()
{ // Read from the sensor, calibrate readings, smooth output

  uint8_t bytesToRead = 6; // Burst read (preferential as per Analog Devices)
  registerRead( DATAX0, bytesToRead, _buff); // Read from the 6 registers

  int xRaw = (((int)_buff[1]) << 8) | _buff[0]; // 10 bit (2 bytes), LSB first, convert into integer
  int yRaw = (((int)_buff[3]) << 8) | _buff[2];
  int zRaw = (((int)_buff[5]) << 8) | _buff[4];

  float x = (xRaw - xOffset) / xGain; // See p. 8 http://www.analog.com/media/en/technical-documentation/application-notes/AN-1057.pdf
  float y = (yRaw - yOffset) / yGain;
  float z = (zRaw - zOffset) / zGain;

  //Serial.print(xRaw);
  //Serial.print(", ");
  //Serial.print(yRaw);
  //Serial.print(", ");
  //Serial.println(zRaw);

  xEMA = (alphaEMA * x) + ((1 - alphaEMA) * xEMA); // See https://en.wikipedia.org/wiki/Exponential_smoothing
  yEMA = (alphaEMA * y) + ((1 - alphaEMA) * yEMA);
  zEMA = (alphaEMA * z) + ((1 - alphaEMA) * zEMA);

  return;
}

void registerWrite(byte address, byte val)
{
  Wire.beginTransmission(DEVICE);
  Wire.write(address);
  Wire.write(val);
  Wire.endTransmission();
  return;
}

void registerRead(byte address, byte num, byte _buff[])
{ // Reads num bytes into _buff array
  Wire.beginTransmission(DEVICE);
  Wire.write(address);
  Wire.endTransmission();
  Wire.beginTransmission(DEVICE);
  Wire.requestFrom(DEVICE, num);

  byte i = 0;

  while (Wire.available())
  {

    _buff[i] = Wire.read(); // Read 1 byte
    i++;
  }

  Wire.endTransmission();
  return;
}

void setLED (byte r, byte g, byte b)
{
  analogWrite(pinLEDR, r);
  analogWrite(pinLEDG, g);
  analogWrite(pinLEDB, b);
  return;
}

PaulRB

Quote
x rotation from Magenta to Green and y rotation from Yellow Orange to Cyan Blue.
What do you mean by X rotation and y rotation? Can you draw a diagram? Hand drawn is fine. Your colour scheme sounds like it might even be impossible because all colours are mixed from red, blue and green, and you can't increase and decrease green at the same time, for example.

Lagom

Cheers, here's a diagram of sorts... I had the idea that one could somehow at least switch between

Code: [Select]
setLED (xR, xG, xB);
and
Code: [Select]
setLED (yR, yG, yB);

when the sensor is over a certain angle, in some way, it reminds me of a joystick's functionality.


PaulRB

How about using the HSV colour model?


Yellow-orange is around 40 degrees. Blue-cyan is opposite that at around 220 degrees. Magenta is around 300 degrees and green is opposite that at around 120 degrees.

So... Turn your x & y into an angle using atan2(). Add an offset to that angle to adjust it to the desired colours.

What colour do you want when the sensor is level? White or black would be easy.

Lagom

Thanks, yes, I used the FastLed library before for simple LED-strip colour animations. I had not thought of it being of potential use here - I check out what you suggest.

Ideally, if the sensor is level, the RGB LED should be white indeed.

PaulRB

Sorry, I updated my post while you were replying. It's a bad habit of mine.

I realised you were not using FastLED and removed references to it.

The atan2() function will return a Hue angle between -Pi and +Pi. Use Pythagoras to calculate the Saturation value and set Value to maximum. Then convert HSV to RGB. There are libraries and code samples around the net for that: Google for "Arduino HSV to RGB".

Lagom

No worries, I've used the FastLed library before, so that sounds like a plan.

PaulRB

FastLed ... sounds like a plan.
Not sure. Don't think FastLED can control a led through 3 Arduino pwm pins.

Lagom

I was intending to use an Adafruit Pixie RGB LED in the final version. Seems like FastLED caters for that type of LED.

PaulRB

Your second link is broken.

This page confirms FastLED supports Pixie. You're all set!

Lagom

Indeed. All I have to supposedly figure out is how to map roll/pitch meaning x/y output to HSV and then convert back to RGB. Or maybe I can somehow use FastLED to set HSV colours directly.

PaulRB

Let FastLED worry about the HSV to RGB conversion, leaving you to focus on calculating the HSV from roll, pitch, yaw.

The x & y values from the accelerometer will give you coordinates on the colour wheel I posted earlier, with x=0, y=0 at the centre. But you need to convert the x & y to angle and distance from the centre of the wheel. The atan2() function will give you the angle. But the angle will be between -Pi and +Pi. You need H to be between 0 and 255. Pythagoras will give you the distance from the centre, but it will be between 0 and 1g=9.8m/s2 and you need 0 to 255 for S. You can probably use V=255, unless you want to dim the led.

Go Up