Arduino Manager Development for All

So is there an integer I would need to write to determine what is “ideal”. Im assuming this would be a new way to write the If statement code and not in the case or would this be added to the case code.

Ok I think i got ya on the case instances. Keep it in numbers so it dont have to think as hard. 0-31 would probably better as this will have more resolution. The “happy window” in the case if it were 0-15 would be too wide I think. But I may try and write it both ways and see what happens.

It replaces the case code… It is about functionally equivalent to what you have for the case code because you are stepping by 1’s. it removes the delays and adds one consistent one at the bottom (the delay time was more or less arbitrary)

I forgot to remove “ideal” from the psuedo code. It should be the O2 sensor reading for where the pot is. -10 +10 were arbitrary, but it creates a range where you don’t need to adjust anything.

it is actually the map() function that has the issue, but for the case instances yes.
0-15, or 0-31, either would work.

FWIW I like the dead band (±10) method. In the end that will be much more succinct than coding out so many cases. Also, adjusting the size of the dead band should be an effective tuning point for improving overshoot.

Some additions to the pseudo-code to avoid all delay:

currentMillis = millis();

if(sensorReading> (ideal +10))
{
if (currentMillis - previousRichMillis > interval)
{
previousRichMillis = currentMillis;
lastpos-- ;
myservo.write (lastpos);
}
}
else if (sensorReading < (ideal -10) )
{
if (currentMillis - previousLeanMillis > interval)
{
previousLeanMillis = currentMillis;
lastpos++;
myservo.write (lastpos);
}
}

The method is demonstrated in examples under “Digital” ==> “BlinkWithoutDelay”. Check that out to understand the method and the necessary variable types. Adjusting the value of “interval” is like adding delay except it’s non-blocking.
Liken it to putting a pot on the stove, setting a timer then going about other tasks until the timer goes off. Delay is like doing absolutely nothing but standing, watching intently until the food is cooked.
This BWD method will allow you to get the freshest sensor readings in this application. Also it sets you up for future expansion where actions may need to happen while we are waiting for the food to cook :grinning:

In 8 bit micro land power == speed.

Oh… I didn’t know about the millis(), I was going to ask someone how to do that. I knew delay() blocked, but I knew there was a way around it.

However, why did you do the previousRichMillis and previousLeanMillis?
it seems like it should be more like:

currentMillis = millis();
if (currentMillis - previousMillis >= interval)
{
previousMillis = currentMillis;

if(sensorReading> (ideal +10))
{
lastpos-- ;
myservo.write (lastpos);
}else if ( sensorReading<(ideal -10))
{
lastpos++ ;
myservo.write (lastpos);
}
}

Yeah, I agree with that.

Hi Matt,
Which Lister model are you talking about?

Its a huge 3 cylinder diesel. Not enough cylinders to do what we want, need a min of a 6 to 8 cyl.for 20 kW and above. This was specified by our customer, Im not a big fan of these industrial engines. You can not get parts for em, and suppliers are very limited. In the end they all break no matter if its industrial or not your gonna have to fix it. :fire:

Got a question, what if I put a resistor in the 5v supply to the servo to slow it down? Then we could completely eliminate all delays. If this would work would be the appropriate resistor?

What problem are you trying to solve?

The delays, without them it will be way to fast. With them the code stops as mentioned before. The code you guys have been posting is a bit over my head. lol

Matt,

Doesn’t work that way. The servo is looking for a timed pulse that it then equates to the desired position. Keep the delays.

Stephen

Hi Stephen I understand the pwm signal. :slight_smile: What I am considering is the supply voltage, 1 volt decrease will dramatically slow the speed of the servo to much It wont work at all. These typically have 7v volts running them at least on my nitro RCs they did. When the voltage would drop to around 4v they get real slow and then you know its time for a new battery. So on my set up I have a 12v to 5v regulator. I dont think a resistor will work this will only reduce current and not the voltage correct?

As for now I am pretty happy with the performance, but always want to make it better :slight_smile:

I wouldn’t power limit the motor(servo). If it keeps trying to achieve a desired position and can’t get there it will keep pounding current to the rotor and things can fry due to a lack of back EMF. I only have 5V going to my ultra plain jane low torque servos on the automixer and it is more than enough.

Ok good to know thank you. Well at least for now it works for stationary systems, so we will continue on. :smile:

The function you trying to tune by slowing down response is the derivative part of the PID equation, this is the speed the controller tries to adjust for an error

The Arduino can do this math and you need never know exactly how it does this to make it work ( involves things like Laplace and Fourier transforms… Stuff that makes your brain hurt ).
But Computers are good at this, they can do the math and find the stability, all you need do is play with 3 pots to adjust the Ki KP Kd values of the equation…

Think of your unstable control system like the suspension of a car.
What effects the ride and smooth out the bounce.
Spring rate, shock damping and weight of the car.
Tuning a PID loop is a lot like changing the shocks ( or adjusting ), using stiffer or softer springs ( change the reaction of the body to the road ) or getting a couple of fat ladies to sit in the back seat to increase the mass.

I don’t know how windows works, I don’t have too it just does and and tell it to bring me here.
Same kind of thing in closed loop computer controls just play with it and see if it can solve your trouble.

You should have said something earlier. thought you were following along. :slight_smile:

Try this. I’m not sure if this will quite work. The 3 #define statements at the beginning pretty much are the place to adjust things though unless I have a logic error.

#include <Servo.h>
//GAP is the space both +- around the "ideal" 
#define GAP 10 
//RUNNINGAVG is the number of values stored in the average function. 
#define RUNNINGAVG 10
//INTERVAL is the delay time between iterations of the loop. 
#define O2INTERVAL 100
// These constants won't change:
const int O2SensePin = A1;    // pin that the O2 sensor is attached to
const int O2SwtPin = A2;   //Pin Auto Mode Switch is attached to
const int O2ManPin = A0; // Reads the O2 Manual Adjust Potentiometer on pin A1

Servo myservo;  // create servo object to control a servo 

 class RunningAverage
{
public:
    RunningAverage(void);
    RunningAverage(uint8_t);
    ~RunningAverage();

    void clear();
    void addValue(double);
    void fillValue(double, uint8_t);

    double getAverage();
    // returns lowest value added to the data-set since last clear
    double getMin() { return _min; };
    // returns highest value added to the data-set since last clear
    double getMax() { return _max; };

    double getElement(uint8_t idx);
    uint8_t getSize() { return _size; }
    uint8_t getCount() { return _cnt; }

protected:
    uint8_t _size;
    uint8_t _cnt;
    uint8_t _idx;
    double _sum;
    double * _ar;
    double _min;
    double _max;
};
void O2Sensor(void) ;
//define a rolling average of the last 10.. can be changed. 
RunningAverage sensoravg(RUNNINGAVG);

 //VG 02 Mixture Controller v0.2
//Craeted by Matt Ryder, Vulcan Gasifier LLC May 5, 2015
  //Last edit; May 12, 2015 




int O2SensorIdeal;

int pos = 0;    // variable to store the servo position
int val;    // variable to read the value from the analog pin 
int lastpos = val;     // variable to store the servo last position
int O2SwitchState = 0;         // variable for reading the Fuel Mixer Mode status
int O2Switch =0;            //variable for determining the whether this is the first time this has been run without the switch.
unsigned long currentMillis=0, O2previousMillis=0; //define variables for the millis function.

void setup() 
{ 
  myservo.attach(9);  // attaches the servo on pin 9 to the servo object 



      // Need to create a set point for servo start position after manual tuning
  
  val = analogRead(O2ManPin);  // reads the value of the potentiometer (value between 0 and 1023) 
  val = map(val, 0, 1023, 20, 180);     // scale it to use it with the servo (value between 0 and 180) 
  myservo.write(val);                  // sets the servo position according to the scaled value 
  lastpos = val;                       // sets lastpos to equal the position of the manually set position
  delay(10);                           // waits for the servo to get there 
  
} 
   
void loop() 
{
  currentMillis=millis(); //sets to current time. 
  if (currentMillis - O2previousMillis >= O2INTERVAL)  //check to see whether it is time to check everything. 
      { 
        O2previousMillis = currentMillis;  //keeps the old value of currentMillis. 
        O2Sensor(); 
      }
}//end loop()

void O2Sensor(void) 
{

  
 
    // read the state of the pushbutton value:
  O2SwitchState = digitalRead(O2SwtPin);
   // read the value of the O2 sensor and add it to the avg buffer
   
 sensoravg.addValue((double) analogRead(A1));
  


  if (O2SwitchState == LOW)
  {
  val = analogRead(O2ManPin);  // reads the value of the potentiometer (value between 0 and 1023) 
  val = map(val, 0, 1023, 20, 180);     // scale it to use it with the servo (value between 0 and 180) 
  
  myservo.write(val);                  // sets the servo position according to the scaled value 
  lastpos = val;                       // sets lastpos to equal the position of the manually set position
  delay(10);                           // waits for the servo to get there 
O2Switch = 1;                          //we set this value to one, so we know we are still reading the pot. 

  }else{
    if(O2Switch == 1)
      { O2Switch =0;
       O2SensorIdeal = (int)sensoravg.getAverage();
      }
  //heart of the program..
     if((int)sensoravg.getAverage()> (O2SensorIdeal + GAP))
{
lastpos-- ;
myservo.write (lastpos);
}else if ( (int)sensoravg.getAverage()<(O2SensorIdeal - GAP))
{
lastpos++ ;
myservo.write (lastpos);
}
      
      
  }//end switch state
}//end milli 
//
//    FILE: RunningAverage.cpp
//  AUTHOR: Rob Tillaart
// VERSION: 0.2.08
//    DATE: 2015-apr-10
// PURPOSE: RunningAverage library for Arduino
//
// The library stores N individual values in a circular buffer,
// to calculate the running average.
//
// HISTORY:
// 0.1.00 - 2011-01-30 initial version
// 0.1.01 - 2011-02-28 fixed missing destructor in .h
// 0.2.00 - 2012-??-?? Yuval Naveh added trimValue (found on web)
//          http://stromputer.googlecode.com/svn-history/r74/trunk/Arduino/Libraries/RunningAverage/RunningAverage.cpp
// 0.2.01 - 2012-11-21 refactored
// 0.2.02 - 2012-12-30 refactored trimValue -> fillValue
// 0.2.03 - 2013-11-31 getElement
// 0.2.04 - 2014-07-03 added memory protection
// 0.2.05 - 2014-12-16 changed float -> double
// 0.2.06 - 2015-03-07 all size uint8_t
// 0.2.07 - 2015-03-16 added getMin() and getMax() functions (Eric Mulder)
// 0.2.08 - 2015-04-10 refactored getMin() and getMax() implementation
//
// Released to the public domain
//


RunningAverage::RunningAverage(uint8_t size)
{
    _size = size;
    _ar = (double*) malloc(_size * sizeof(double));
    if (_ar == NULL) _size = 0;
    clear();
}

RunningAverage::~RunningAverage()
{
    if (_ar != NULL) free(_ar);
}

// resets all counters
void RunningAverage::clear()
{
    _cnt = 0;
    _idx = 0;
    _sum = 0.0;
    _min = NAN;
    _max = NAN;
    for (uint8_t i = 0; i < _size; i++)
    {
        _ar[i] = 0.0; // keeps addValue simple
    }
}

// adds a new value to the data-set
void RunningAverage::addValue(double value)
{
    if (_ar == NULL) return;  // allocation error
    _sum -= _ar[_idx];
    _ar[_idx] = value;
    _sum += _ar[_idx];
    _idx++;
    if (_idx == _size) _idx = 0;  // faster than %
    // handle min max
    if (_cnt == 0) _min = _max = value;
    else if (value < _min) _min = value;
    else if (value > _max) _max = value;
    // update count as last otherwise if( _cnt == 0) above will fail
    if (_cnt < _size) _cnt++;
}

// returns the average of the data-set added sofar
double RunningAverage::getAverage()
{
    if (_cnt == 0) return NAN;
    return _sum / _cnt;
}

// returns the value of an element if exist, NAN otherwise
double RunningAverage::getElement(uint8_t idx)
{
    if (idx >=_cnt ) return NAN;
    return _ar[idx];
}

// fill the average with a value
// the param number determines how often value is added (weight)
// number should preferably be between 1 and size
void RunningAverage::fillValue(double value, uint8_t number)
{
    clear(); // TODO conditional?  if (clr) clear();

    for (uint8_t i = 0; i < number; i++)
    {
        addValue(value);
    }
}
// END OF FILE

I should also say what I did.
it reads the O2 sensor into an average every 100 milliseconds.
when you stop hitting the “startup” button. it uses the average from the O2 sensor to determine where the “ideal” sensor value is.
It then steps the motor 1 step, up or down, if the average isn’t in the right around the “ideal”.

I copied the RunningAverage class in so no one had to actually try and import libraries, and it looks messy, but it is still just a single file. It could be further tuned since AFAIK we are just dealing with ints between 0-1023 as readings from the O2 sensor which is spitting out lamba values.

Tillaart’s class would be great if we needed multiple instances, but we have one. Why not the simple averaged array from post 22?

Namely because I didn’t have much time(nor for the rest of the week.), and I need to know whether the mapping of the pot to the sensor logic is going to work. That code is I assume is tested and works.

It will get rewritten, tillart’s code isn’t ideal for this situation except the time factor.

Hey guys Ive had to put this on the back burner for now. Im getting ready for argos we have a ton to do in short time. I will pick up again on this as soon as I can. The little 2 kW machine we built just for this reason so we have something here to test. When we get back next week, we will have to focus on builds. So it might be bit befor I can tinker with this again, but it is great fun, I do what I can to set weekends aside for this. We will soon launch next years development as well.