Jump to content
AGlass0fMilk

Analog Input Smooth - Comparisons Don't Work

Recommended Posts

Hey there guys. First post in a long time.

 

I find myself developing once again on an MSP430G2553 chip on a Rev 1.4 Launchpad and I am trying to take samples from the on-chip ADCs using Energia.

 

It's not secret that the on-chip ADCs are quite unstable (very accurate perhaps?), I don't get anywhere near the kind of jitter I see when using the MCP3008 (10-bit, 8-channel ADC converter IC). Regardless, I am very frustrated with the section of my code that deals with smoothing the signal from analog controls.

 

I am sampling 4 ADC values and averaging it, no problem.

 

What really frustrates me is that when I try to check if the new value is within a certain sensitivity (range) of the last recorded value, I have to do a strange work-around or else I get constant resends as if I'm not even checking if the value is in the certain range.

 

In other words, I cannot use the compare operators greater than, less than, greater than/equal to, and less than/equal to, in order to check if the new ADC value is within a certain range of the old recorded value.

 

To illustrate this, I will include the code I use to do this and show you what I would like to do (which doesn't work) and what I do currently (which still results in some glitches)

 

Just for reference, this is what the Control class looks like:

class Control
{
 public:
  //Identification information
  int pin;      //Sets the pin to be used by the control
  int ID;       //Identification number (0-31)
  
  //Data processing variables
  int value; //Stores the current value
  int oldValue; //Stores the old value (for preventing constant resends)
  int counter; //Counter used for averaging
  int average; //Value use for calculating average
  
  int sensitivity; //Value used for setting sensitivity (a variable in smoothing algorithm) @todo
  
  //Constructor
  Control(int nID, int nPin, int nSensitivity);
  
  //Methods
  int      Read(void);    //Reads the value of the control
  boolean  isNew(void);   //Returns true if the value has updated (within a reasonable range, the sensitivity)
  void     Send(void);    //Transmits the control change to the computer for processing
  
};

And this is what the Control.Read() function does (it includes the average code)

int Control::Read(void)
{
    int pinVal = analogRead(pin);
    if(pinVal == 1023 || pinVal == 0) //it is max/min
    {
      return pinVal; //I do this so that mins/maxs can be sent anyway. It's important that absolute mins/maxs are sent.
    }
    
    if(counter != 3) //Averaging counter that's stored on the class
    {
      average += analogRead(pin);
      counter++; //Increment Counter
      return 1025; //Not ready value (outside ADC range)
    }
    else
    {
      average /= 4; //Divide by 4 to average
      counter = 0; //Reset counter
      return average;
    }
  }
}

Code that I would like to use:

boolean Control::isNew(void) 
{
  value = Read(); //Set value of control

    if(value == 1025) //Not ready
       return false;
       
    //First check for maxs/mins (to prevent resending)
    if(value == 1023 || value == 0) //it is max/min
    {
      if(value != oldValue) //max/min was not sent already
      {
        oldValue = value;
        return true;
      }
    }
    //Check if its within sensitivity setting
    if(value >= (oldValue-sensitivity) || value <= (oldValue+sensitivity)) //This does nothing to smooth input (constantly returns true, even when it shouldn't)
      return false; //Still within sensitivity, report false
    else
    {
      oldValue = value; //It's changed more than the sensitivity, update values and report true
      return true;
    }
  }
  return false; //If it hasn't returned true by now, return false
}

Code that I use right now...

boolean Control::isNew(void) 
{
  value = Read(); //Set value of control
    if(value == 1025) //Not ready
       return false;
       
    //First check for maxs/mins (to prevent resending)
    if(value == 1023 || value == 0) //it is max/min
    {
      if(value != oldValue) //max/min was not sent already
      {
        oldValue = value;
        return true;
      }
    }
    boolean within5 = false; //Please ignore the irrelevant name of this variable
    int checkVal = 0;
    //Then check if it's within 15 of previous value
    for(int i=0;i<15;i++)
    {
      checkVal = value + i;
      if(checkVal == oldValue)
      {
        within5 = true;
        break;
      }

      checkVal = value - i;
      if(checkVal == oldValue)
      {
        within5 = true;
        break;
      }
    }
    if(!within5) //Not within 15
    {
      oldValue = value;
      return true;
    }
  return false; //If it hasn't returned true by now, return false
}

The main loop does something like this:

  if(control_1.isNew())
  {
    control_1.Send(); //Simply prints the value to the serial line
  }

Similarly, I have tried to see if the new ADC value is above a certain threshhold as to count it as a maximum value. Above a certain value (like 1015) the sensitivity check becomes erratic, it sends new values even if they're within (for example) 2 of the last value.

 

The code I used was:

if(value > 1015) //If it's above a certain threshhold
  value = 1023;  //Count it as a maximum

The kind of output I would get is still like:

1020
1022
1018
1023
1023
1020

The sensitivity is normally set to within 10-20 for the on-board ADC. For controls connected to the MCP3008, the sensitivity is about 5, it is as stable as a rock.

 

What is going on? Why can't I compare these values? Is it related to the type of variable I store the value in? I have tried changing the related variables to unsigned integers and it has no effect.

 

It has had me pulling my hair out. I hate inefficient code on MCUs and using for loops simply to check if a value lies within some range seems pretty inefficient to me.

 

I'll post the finished project when I'm done.

 

Thanks guys!

Share this post


Link to post
Share on other sites

I don't think you want an "OR" in the comparison. No matter what "value" is, it will return true.

I.e. if 'value' is 512, 'oldValue' is 508, and the sensitivity is 10, 508-10 is 498, so the >= is true; and 508+10 is 518, so the <= is true as well. But, if 'value' is 19, 508+10 is 518, so the <= comparison is true and therefore the IF statement is true when using "||" / OR. Likewise, if 'value' is 988, 508-10 is 498, so the >= comparison is similarly true.

 

So maybe:

if((value >= (oldValue-sensitivity)) && (value <= (oldValue+sensitivity)))
?

 

Also, you should constrain / bound "oldValue" with the sensitivity calcs so that it doesn't over / under flow the 0-1023 limits, no?

Share this post


Link to post
Share on other sites

 

if(value >= (oldValue-sensitivity) || value <= (oldValue+sensitivity))

This will always return true.

This method will do what you want if you switch things around a little bit. You want to check to see if the new number is less than X - sensitivity or more than X + sensitivity, and if it is then you run your code.

If it isn't then you return false as the number is still within the sensitivity range.

 

[code]if(value <= (oldValue-sensitivity) || value >= (oldValue+sensitivity)){
   doStuff();
   return true;
}
else{
  return false;
}

Just as an example. Please do note that the above has not actually been compiled/run.

 

 

Or do what abec. suggested, the end result is the same.

 

I'm not sure you need to check for 0 and 1023 specifically. If the last read was 1023 and the next read is also 1023 that will fall within the sensitivity range and be ignored.

Maybe I'm missing something there.

 

 

 

If you have time, do a few more samples with a bit of time between them.

If I can I like to sample decently often over a ~17ms time period to get rid of the 60Hz background EMI.

A running average can be a nice tool for this sort of thing too. Take value and add the last-read value then divide by 2. Very low memory usage, with decent long term averaging.

A small cap between the ADC input pin and GND or VCC can go a long way too, especially if it's a high resistance slow changing (relatively) input.

Share this post


Link to post
Share on other sites

Wow guys... I've been working with this code for so long and never even realized it was the OR operator in the if statement... I feel so embarrassed. I've been programming for a few years now, even on MSP430.

 

Hahaha, I guess it really takes a few fresh eyes to pick out the little mistakes like that. I will recompile it with the && operator when I have the change and see how much it improves.

 

I may try the running average idea. I could also try upping the sample count and balance the latency with the stableness of the readings.

 

Also, the purpose of the check for absolute mins/maxs (0/1023) is so that if I have a previous sent value, for example 1018, it's in the sensitivity range so 1023 will never be sent. I will never register totally 0 or totally 1023, which is critical for my project. Therefore, I have to explicitly check to see if it's a max/min value, and then send it if it hasn't been sent before.

 

I'll check back with you guys again if I have anymore problems. Thanks again for catching my dumb mistakes!

Share this post


Link to post
Share on other sites

That makes sense. Probably worth testing to make sure you can get 0 and 1023. It can be trickier than you'd think, the difference between 0 and 1 at the 3.6v the launchpads typically run at is only 0.0035 volts.

 

I noticed the OR largely because I've been in that trap in almost exactly that situation and driven myself nuts trying to work out what was wrong.

It can be very helpful to take out a calculator and notepad and manually test a program. Takes a bit, but it turns things like that up.

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×