0

I have a program that receives 4 commands on 4 channels to change the color of an LED using mqtt communication. Lets just focus on two of the channels, the time and the red.

I am sending a length of time that I would like to complete the color change by, and a new color value.

I have ran my test runs to calculate the amount of time it seems to take to update a single value. I calculate this by setting the intervalRed value to 0.01 with a duty cycle of 1024, commanding the led to go from its starting value to its ending value of fully on, by the interval value would be 10,240 ticks. when I send that command, take the resulting execution time ( 287ms ) and calculate the time to update a single tick I get a value of 0.028027 ms to complete a single increment. When I set the hardcoded tickTime to that value: 0.028027 and I send a color change from off to on/vice-versa with a requested completion time of 1000ms, the actual execution time varies.

the larger the requested execution time varies, causes a greater difference between the actual and requested time.

I ran many test trials with a set interval to gain a value for the length of time for a single tick but no matter when value I use, there are still discrepancies. I dont believe using a hardcoded value for the length of time for a single tick is the correct way to assure the color change will occur over a given time.

I am happy to provide the terminal output for any attempts to show the output of values used in my calculations if needed (But I believe I need a difference approach all together)

What is the best way to assure the color change completes in the requested amount of time? Perhaps some calculation based on the amount of time passed rather then calculating a hardcoded value for the time of a single tick?

    #include <ESP8266WiFi.h>
#include <PubSubClient.h>


#define CLIENT_NAME "ESP8266_1" // just a name to identify this client
WiFiClient wifiClient;
PubSubClient mqttClient("10.0.0.131", 1883, wifiClient);

//set rgb led pins
int pinGreen = 4;
int pinBlue = 12;
int pinRed = 14;

float currentRed = 1024;
float currentGreen = 1024;
float currentBlue = 1024;

int nextRed = -1;
int nextGreen = -1;
int nextBlue = -1;

float intervalRed = -1;
float intervalGreen = -1;
float intervalBlue = -1;

//the float time was calculated by
//IntervalValue(set to 0.01)/Value Change (1024 when going from on to off) = 10,240 ticks
//ExecutionTime (beginning time - end time) /# of ticks = 0.0347643
// I seem to be getting varying execution times so I have tried values between 0.028 and 0.04
float tickTime = 0.028027;
int executionTime = 1;
int showTime = 1;
int beginningReceivedData;

void setup() 
{
  Serial.begin(9600);
  //set rgb led pins to output

  pinMode(pinRed, OUTPUT);
  pinMode(pinGreen, OUTPUT);
  pinMode(pinBlue, OUTPUT);
  digitalWrite(pinRed, HIGH);
  digitalWrite(pinGreen, HIGH);
  digitalWrite(pinBlue, HIGH);

  mqttClient.setCallback(callback);
  WiFi.begin("partyHouse", "whatsthepassword");
  connectToWiFiAndBroker();
}

void connectToWiFiAndBroker() 
{
  Serial.print("\nConnecting to WIFI");
  while (WiFi.status() != WL_CONNECTED) 
  {
    Serial.print(".");
    delay(500);
  }
  Serial.println("Connected to WIFI!");

  Serial.println("Connecting to broker");
  while (!mqttClient.connect(CLIENT_NAME)) 
  {
    Serial.print(".");
    delay(500);
  }
  Serial.println("Connected to broker!");

  mqttClient.subscribe("InterestingTopics/#");
}

void loop() 
{
  if (!mqttClient.connected()) {
    connectToWiFiAndBroker();
  }
  mqttClient.loop();

  updateColors();    
}
//------------------------------------------------------------------------------------------------------------------------
//The CallBack for processing incoming data
//------------------------------------------------------------------------------------------------------------------------
//the topic is the path based on the subscription
void callback(char* topic, byte* payload, unsigned int length) 
{
  //for tracking the time it takes to process the data, as well as the time it takes to complete updating the colors over the desired time
  beginningReceivedData = millis();
  char commandValue[length+1];
  for (int i = 0; i < length; i++) 
  {
    commandValue[i] = (char)payload[i];
  }
  commandValue[length+1] = '\0';
  if (strcmp(topic, "InterestingTopics/time") == 0) {
    executionTime = atoi(commandValue);
  }
  if (strcmp(topic, "InterestingTopics/red") == 0) {
    showTime = 1;
    nextRed = atoi(commandValue);
    intervalRed = (nextRed - currentRed)/(executionTime/tickTime);
    //manual Interval value used for calculating the tickTime
    if (nextRed < currentRed) {
      intervalRed = -0.1;
    } else {
      intervalRed = 0.1;
    }

    Serial.print("next: ");
    Serial.print(nextRed);
    Serial.print(" Current: ");
    Serial.print(currentRed);
    Serial.print(" Interval: ");
    Serial.println(intervalRed, 10);

    Serial.print("time to process incoming single value (red): ");
    Serial.println(millis() - beginningReceivedData);
  }
  if (strcmp(topic, "InterestingTopics/green") == 0) {
    nextGreen = atoi(commandValue);
    intervalGreen = (nextGreen - currentGreen)/(executionTime/tickTime);
  }
  if (strcmp(topic, "InterestingTopics/blue") == 0) {
    nextBlue = atoi(commandValue);
    intervalBlue = (nextBlue - currentBlue)/(executionTime/tickTime);
  }
}



void updateColors() {
  //------------------------------------------------------------------------------------------------------------------------
  //Updating the Red
  //------------------------------------------------------------------------------------------------------------------------
  if (currentRed != nextRed && nextRed > -1) {
    currentRed = currentRed + intervalRed;
    //if value decreasing and it passes the next value, set values equal to prevent passing
    if (intervalRed < 0 && currentRed < nextRed) {
      currentRed = nextRed;
    }
    if (intervalRed > 0 && currentRed > nextRed) {
      currentRed = nextRed;
    }
    analogWrite(pinRed, (int)currentRed);
    //this else exists just to print the different in time from receiving the value to the time it took to update
  } else if (currentRed == nextRed && nextRed > -1 && showTime == 1) {
    //showTime set to zero to only print the time once
    showTime = 0;
    Serial.print("full color change has completed taking this much time: ");
    Serial.println(millis() - beginningReceivedData);
  }
  //------------------------------------------------------------------------------------------------------------------------
  if (currentGreen != nextGreen && nextGreen > -1) {
    currentGreen = currentGreen + intervalGreen;
    //if value decreasing and it passes the next value, set values equal to prevent passing
    if (intervalGreen < 0 && currentGreen < nextGreen) {
      currentGreen = nextGreen;
    }
    if (intervalGreen > 0 && currentGreen > nextGreen) {
      currentGreen = nextGreen;
    }
    analogWrite(pinGreen, (int)currentGreen);
  }
  if (currentBlue != nextBlue  && nextBlue > -1) {
    currentBlue = currentBlue + intervalBlue;
    //if value decreasing and it passes the next value, set values equal to prevent passing
    if (intervalBlue < 0 && currentBlue < nextBlue) {
      currentBlue = nextBlue;
    }
    if (intervalBlue > 0 && currentBlue > nextBlue) {
      currentBlue = nextBlue;
    }
    analogWrite(pinBlue, (int)currentBlue);
  }
}

1 Answer 1

1

Don't assume an update takes a fixed time and blast constant changes. Write code that calculates change/millisecond. Then record the starting millis() value when you begin a sequence, and each time through your loop, calculate float(millis()-startTime)/sequenceTime. That will give you the % of color change to apply now. Then use that percentage to calculate the interpolated color value.

That will give you an adaptive system that will set the color value to the approximate desired color for the current stage in your animation, regardless of how long each iteration the loop takes. If the loop lags for some reason, you will get more coarse changes between color values, but it will still track your desired pacing.

4
  • Note that you'll get more responsive code by adapting the above to use fixed-point math, since most Arduinos don't have hardware floating point support and their floating point is quite slow as a result. Mapping arbitrary floating point to fixed point is a little bit tricky, however. I'd suggest just writing it using floating point to start, and then optimizing it to fixed point math if it's too jumpy.
    – Duncan C
    Nov 23, 2019 at 0:43
  • Thank you for the suggestion of fixed point math, I did see a significant difference in execution time when I went from double to float in my math. Not sure I understand how to convert to fixed but, time to do more research Nov 23, 2019 at 1:10
  • Fixed point math uses a fixed "binary decimal place" and treats all values as between 0 and 1 (or 0 and 2, or 0 and 4, depending on where you put the assumed binary decimal place. If you scale all of your values to the range of your fixed-point, you can do integer math on the values, and then interpret the results as fixed-point values, and get much, MUCH faster performance. Figuring out the scaling and ranging takes some thought though.
    – Duncan C
    Nov 23, 2019 at 1:26
  • Note that you could also try using short floating point values rather than float. That will be faster still, and easier to program than fixed point math. It makes your calculations pretty low precision however.
    – Duncan C
    Nov 23, 2019 at 1:29

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.