Go Down

Topic: Preset RGB LED colour transitions from accelerometer output (Read 616 times) previous topic - next topic

Lagom

Aug 18, 2018, 02:35 pm Last Edit: Aug 19, 2018, 08:52 pm by Lagom Reason: Added latest code, schematic
I have an Adafruit Metro Mini computing xRoll and yPitch values reading the output of an ADXL345. Apart from a calibration problem, this code and setup, to be later used as an inclinometer for sheet metal work, works as intended.

To pass the time until a replacement breakout board with that sensor arrives, I want to try out an Adafruit Pixie, a 3W RGB LED, to transition between two defined RGB colours according to roll (colours A and B) and pitch (colours C and D). To begin with, I am using a low-cost common cathode RGB LED. The response shall be limited to movements in the upper hemisphere, with z never pointing below the horizon.



Later, I would like to transition between four defined RGB colours for roll and pitch (hence the idea to use arrays), maybe attempting to do it with the HSV colour model; but first I want to get this to work with two RGB colours each.

Code: [Select]
/******* xyz tilt indicator *******/

/******* Adafruit Metro Mini, ANALOG DEVICES ADXL345, common cathode RGB LED *******/

/******* LIBRARIES *******/

#include <Wire.h>

/******* VARIABLES *******/

#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 =  247; // Averaged readings from 2-point calibration with 6-point-tumble method (values particular to each IC)
const int xMin = -266;
const int yMax =  254;
const int yMin = -261;
const int zMax =  233;
const int zMin = -258;

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.3; // Smoothing factor 0 < α < 1 (smaller = smoother = less responsive)
float xEMA = 0;  // Initialise with an arbitrary reading (0 = oriented in the horizontal plane at start)
float yEMA = 0;
float zEMA = 0;

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, 127, 0},
  {0, 127, 255},
};

const byte pinLEDR = 9; // Pins for common cathode RGB LED, see https://dronebotworkshop.com/rgb-leds/
const byte pinLEDG = 10;
const byte pinLEDB = 11;

/******* FUNCTIONS *******/

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);

} // End of setup

void loop()

{

  readSensor();

  float xRot = atan(xEMA / sqrt((pow(yEMA, 2)) + pow(zEMA, 2))) * 57.2957 + 90; // See p. 8 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.002) {  // 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];

    setLED (xR, xG, xB);

    // THIS IS WHERE MY QUESTION COMES IN - HOW CAN I COMBINE BOTH COLOUR TRANSITIONS?

  }

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

} // End of loop

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;

  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;

} // End of readSensor

void registerWrite(byte address, byte val) {

  Wire.beginTransmission(DEVICE);
  Wire.write(address);
  Wire.write(val);
  Wire.endTransmission();

  return;

} // End of registerWrite

void registerRead(byte address, int num, byte _buff[]) { // Reads num bytes into _buff array

  Wire.beginTransmission(DEVICE);
  Wire.write(address);
  Wire.endTransmission();
  Wire.beginTransmission(DEVICE);
  Wire.requestFrom(DEVICE, num);

  int i = 0;

  while (Wire.available())
  {

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

  Wire.endTransmission();

  return;

} // End of registerRead

void setLED (byte r, byte g, byte b) {

  analogWrite(pinLEDR, r);
  analogWrite(pinLEDG, g);
  analogWrite(pinLEDB, b);

  return;

} // End of setLED

First, I make the x and y rotation values positive, from 0° to 180°, and constrain values to remain in the upper hemisphere by checking when z becomes negative. Then, I convert the x and y rotation values to fractions, in order to be able to transition between two RGB colours fetched from the array. This works well for either x or y rotations, but not both concurrently.

The question thus is how I would have to bring the x and y rotation colour transitions together.

In a way, it reminds me of videogame joysticks. The last game I played was PONG in the 70s ; )

Any hints much appreciated!

Lagom

Any of the resident LED wizards have an idea? Should I supply more information or should the matter be explained differently?

Thanks for any kind of help!

Grumpy_Mike

Quote
Should I supply more information or should the matter be explained differently?
I for one have no idea what you are talking about. I saw this when you first posted it and assumed it was me not understanding, but it looks like no one here knows what you are doing.

Lagom

I can either transition from M to G or from YR to CB. But how do I combine the two?

Grumpy_Mike

Still don't understand what you are asking. Sorry.

Lagom

#5
Aug 25, 2018, 11:35 am Last Edit: Aug 25, 2018, 11:58 am by Lagom Reason: Added image
Ok, I made a drawing in Adobe Illustrator that hopefully makes it clear what I mean.



The code I have so far only allows transitioning the RGB colour from M to G or from YR to CB. It constrains accelerometer output to upper hemisphere rotations alright. It transitions the RGB LED colour between the colours fetched from the array alright. But only for x or y rotations, not both concurrently.

Here is the relevant section from the complete example above:

Code: [Select]
 float xRot = atan(xEMA / sqrt((pow(yEMA, 2)) + pow(zEMA, 2))) * 57.2957 + 90; // See p. 8 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.002) {  // 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]; // M to G, 0° - 180°
    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]; // YR to CB, 0° - 180°
    byte yG = (yRotColours [1][1] - yRotColours [0][1]) * yFraction + yRotColours [0][1];
    byte yB = (yRotColours [1][2] - yRotColours [0][2]) * yFraction + yRotColours [0][2];

    setLED (xR, xG, xB);

    // THIS IS WHERE MY QUESTION COMES IN - HOW CAN I COMBINE X AND Y COLOUR TRANSITIONS?

PaulRB

I can either transition from M to G or from YR to CB. But how do I combine the two?
You should explain what you mean by those letters. I think you mean Magenta, Green, Yellow-Red and Cyan-Blue. I guess the bracketed numbers in your diagram are the same colours expressed as {red, green, blue} colour coordinates.

By the way, your array idea is a little pointless in my opinion. Your arrays have only 2 elements and you always access them with hard-coded indexes. If you find yourself writing code like that, those are signs that arrays may not have been an appropriate choice.

But back to your transition problem. I'm not sure there is a way to do that with the colour scheme you have chosen. Movements in the roll and pitch directions would have contradictory effects on the red, green and blue values.

Lagom

Thanks for chiming in!

I thought that this being the LED forum section, writing about RGB colours and colour triples {###,###,###}, also shown in the code, made it obvious; sorry for that.

I put the colour values in the arrays, because later I need to transition between more than two colours per axis, so I thought it was practical to put that in already; but, yes, for the moment, I can get rid of them.

Quote
Movements in the roll and pitch directions would have contradictory effects on the red, green and blue values.
Well, that's the point of it all ; )

Just like I mentioned in the initial post, the idea can be equated to one of those joysticks in remotely actuated medical instrumentation, where depending on left/right and forward/backward a bright RGB LED illuminated indicator goes from red/blue and yellow/green, where the colours are mixed, when the joystick is moved diagonally, for example.

In some way, this is just like some of the several full-rotation RGB LED colour transition examples, where, however, the x and y output of the accelerometer is simply mapped to 0-255 to then write to analog pins. With all examples of that kind, one does 1. not constrain the colour change to the upper hemisphere (this works well already) and 2. cannot transition between defined RGB colours (this works only for either x or y at the moment).

If you have some ideas, much appreciated.

Once this works and is understood, in a next step, I will then want go away from RGB to use HSL instead to avoid the "muddy centre" one gets with RGB transitions.

Grumpy_Mike

OK I get what you mean.
Unfortunately it can not be done. Mathematically it is a nonsense.

Forget all the colours for a moment and say you want to map just the amount of red over 0 to 1 while an X  value  goes from -1 to +1. That is not hard to do and gives you an expression red = (X + 1)/2
But now you want to also map red over a Y value that goes from -1 to +1 Again this is not hard and gives you red = (Y + 1)/2

But the problem is that X and Y are independent variables and so for any combination of these variables red has two possible values, but you need to have only one value of red to set your red brightness. Therefore your problem has no soloution.

Grumpy_Mike

Quote
I will then want go away from RGB to use HSL instead to avoid the "muddy centre" one gets with RGB transitions.
You still get a muddy center with HLS colour space.

Lagom

Quote
Unfortunately it can not be done.
That's rather peculiar.

I've worked as a product developer on medical instrumentation where this was implemented (joystick, not accelerometer) and then I looked at various of these simplistic YouTube examples, one of which I linked to, so obviously it can be done. I could cheat and use two RGB LEDs ; ) but cheating is not really allowed for apprentices, only the master is allowed to futz : )

Anyway, not to worry. Thanks for having had a look at it!

Grumpy_Mike

Quote
so obviously it can be done.
I think you misunderstood what was being done or need to do something else. You can do it with one colour component say red for one axis and two colour components for the other, say green and blue but you can't do it for both.

As I said the inputs are independent so what they control must be independent. You could use other parameters like HSV space but again you can not control all three components with both axis.

Go Up