Go Down

Topic: Multiple input/outputs combined (Read 207 times) previous topic - next topic

DiggerBro

Dec 06, 2017, 02:35 am Last Edit: Dec 07, 2017, 10:05 am by DiggerBro
EDIT: problem resolved, replaced by newer, better problem!

Hi, I am trying to integrate two separate P controllers that read different data and translate it into different responses, separately. One function reads the current RPM ad adjusts a throttle servo accordingly (powering a small gas engine), the other reads the temperature at (much longer) intervals and adjusts the fuel/air mixture needed accordingly using a continuous rotation servo. I have gotten each one working independently, but not together. I have considered using two separate arduino boards, but one important function of the temperature monitor is that it can send a signal to close the throttle, if the engine gets too warm. This means one arduino to rule them all.

Note: I am using a hall effect sensor for my RPM, and a spark fun thermocouple for temperature readings. This code has some test parameters set in it right now so that I don't have to run the engine to test its functionality.

At the current time, the serial output shows many rapid readings of the temperature sensor at startup, followed by continuous RPM readings after that with no more temp readings. What I want to see is an rpm reading every .5 seconds, and a temp reading every 2.5 seconds (or in practice, 25 seconds).

I tried using a modulo to reset my temperature count without using delay(25000) which is obviously impossible to keep reading rpm for that time and adjusting throttle.

Any help is much appreciated. I have studies Blink Without Delay and thought this was the best way of doing things. Working alternative ideas would be super great;

Code: [Select]
volatile byte half_revolutions;
 unsigned int rpm;
 unsigned long timeold;

#include <Servo.h>
#include <SparkFunMAX31855k.h> // Using the max31855k driver
#include <SPI.h>  // Included here too due Arduino IDE; Used in above header
#include <Servo.h>
Servo traxxas;  // create traxxas object to control the Throttle
Servo LS; // create LS object to control the HSN
// Define SPI Arduino pin numbers (Arduino Pro Mini)
int Throttle; // variable throttle object

int T = 71; // create variable throttle pos for TRA2055
 
const uint8_t CHIP_SELECT_PIN = 10; // Using standard CS line (SS)
// SCK & MISO are defined by Arduiino
const uint8_t VCC = 14; // Powering board straight from Arduino Pro Mini
const uint8_t GND = 15;
int HSN; //variable High Speed Needle position object
int N = 92; // create neutral position for LS-3006
// Instantiate an instance of the SparkFunMAX31855k class
SparkFunMAX31855k probe(CHIP_SELECT_PIN, VCC, GND);

const int delayTemp = 2500; //Sets the delay for the temp sensing ~Pwal

void setup()
{
  // put your setup code here, to run once:
  Serial.begin(9600);
  attachInterrupt(0, magnet_detect, RISING); //Initialize the intterrupt pin (Arduino digital pin 2)
  half_revolutions = 0;
  rpm = 0;
  timeold = 0;
  traxxas.attach(3);  // attaches the Traxxas TRA2055 servo on pin 9 to the Throttle object
  LS.attach(10); //attaches the LS-2006 servo on pin 10 to the HSN object
  Serial.println("\nBeginning...");
  delay(50);  // Let IC stabilize or first readings will be garbage
}

void loop(){
  // Uses modulo opperator so that the program can see if the time is a multiple of the temp reading delay and runs temp function
  if(delayTemp % millis() == 0) {
    float temperature = probe.readTempF();
    if(!isnan(temperature)) {
        Serial.print("\tTemp[F]=");
        Serial.println(temperature);
        //190
        if (temperature < 70){
          HSN = N;
          LS.write(HSN);
       }
        //190 240
       else if(temperature >= 70 && temperature < 80){
          HSN = 100;
          LS.write(HSN);
          delay(330);
          HSN = N;
          LS.write(HSN);
        }
        //240 260
        else if(temperature >= 80 && temperature <= 90){
          HSN = N;
          LS.write(HSN);
        }
        //260 280
        else if(temperature > 90 && temperature <= 95){
        HSN = 83;
        LS.write(HSN);
        delay(330);
        HSN = N;
        LS.write(HSN);
        }
      //filler command. Actual command: T = 76 to shut throttle due to overheat
      else {
        HSN = N;
        LS.write(HSN);
        T = 76;
        traxxas.write(Throttle);
      }
    }
  }
  if(millis() - timeold == 500){
    rpm = 120 * half_revolutions;
    timeold = millis();
    half_revolutions = 0;
    Serial.println(rpm,DEC);
    Throttle = T;
    traxxas.write(Throttle);
    if(rpm <= 2500){
      T = 71;
    }
    else if(rpm > 2500 && rpm < 7900){
      T = T - .5;
    }
    else if(rpm >= 7900 && rpm <= 8100){
      T = T;
    }
    else if(rpm > 8100 && rpm < 12000){
      T = T + .5;
    }
    else{
      T = 76;
    }

     // ===================
     // this is a loose block that won't compile - these curly braces don't belong to anything
     {
      T = min(T, 76); // Sets the throttle closed servo max position)
      T = max(T, 50); // sers the throotle open servo max position)
     }
    // ===================
     }
}

//This function is called whenever a magnet/interrupt is detected by the arduino
void magnet_detect(){
   half_revolutions++;
   //Serial.println("detect");
}


*hopefully, my code posted properly this time in the code box.
Needs help wrangling pixies

Delta_G

Close.  What you want looks like

[code]  paste your code here. [/code]
If at first you don't succeed, up - home - sudo - enter.

MorganS

Note: I'm answering this before the code tags are fixed, so there may be errors in what I'm quoting due to the incorrect tags.

Quote
Code: [Select]
  if(delayTemp % millis() == 0) {
There's several things wrong with this. For starters, you've got the modulo operator the wrong way around. But this is totally the wrong way to use millis(). You cannot guarantee that this gets checked on every millisecond. There are situations where you can skip a millisecond. If that occurs on the special millisecond you're looking for, then you miss it.

This also doesn't rollover correctly. The rollover is not divisible by 2500 so you will get a glitch every 49 days.

Quote
Code: [Select]
  if(millis() - timeold == 500){
This is closer, but now if you miss that special millisecond then you have to wait 49 days before it works again.

Read http://www.gammon.com.au/millis to learn the right way to use millis().

But overall, this looks like the right approach to combine the two jobs on one Arduino. Another way, if you want the two jobs to occur at a simple multiple (do X 5 times for every 1 Y) you just count the number of times the first thing is done and the second thing checks if it goes over the required number of repetitions.

Quote
Code: [Select]
    if(rpm <= 2500){
      T = 71;
    }
    else if(rpm > 2500 && rpm < 7900){
      T = T - .5;
    }
    else if(rpm >= 7900 && rpm <= 8100){
      T = T;
    }
    else if(rpm > 8100 && rpm < 12000){
      T = T + .5;
    }
    else{
      T = 76;
    }
That's not a P controller, that's an I controller! Every time the RPM is high, T is reduced. So T is reduced MORE when it is high LONGER IN TIME. I would be surprised if that works. It will just keep hunting up and down around the setpoint and never actually stop at the setpoint. Also, you sent the last value of T out to the output device, then you calculate the new value of T? Why not send that shiny new value out? You want it to get old before it's allowed out of school?
"The problem is in the code you didn't post."

DiggerBro

#3
Dec 07, 2017, 09:21 am Last Edit: Dec 07, 2017, 09:30 am by DiggerBro
Close.  What you want looks like


[code]  paste your code here. [/code]

Thanks, glad I got that cleared up.

Note: I'm answering this before the code tags are fixed, so there may be errors in what I'm quoting due to the incorrect tags.
There's several things wrong with this. For starters, you've got the modulo operator the wrong way around. But this is totally the wrong way to use millis(). You cannot guarantee that this gets checked on every millisecond. There are situations where you can skip a millisecond. If that occurs on the special millisecond you're looking for, then you miss it.

This also doesn't rollover correctly. The rollover is not divisible by 2500 so you will get a glitch every 49 days.
This is closer, but now if you miss that special millisecond then you have to wait 49 days before it works again.

Read http://www.gammon.com.au/millis to learn the right way to use millis().

But overall, this looks like the right approach to combine the two jobs on one Arduino. Another way, if you want the two jobs to occur at a simple multiple (do X 5 times for every 1 Y) you just count the number of times the first thing is done and the second thing checks if it goes over the required number of repetitions.
That's not a P controller, that's an I controller! Every time the RPM is high, T is reduced. So T is reduced MORE when it is high LONGER IN TIME. I would be surprised if that works. It will just keep hunting up and down around the setpoint and never actually stop at the setpoint. Also, you sent the last value of T out to the output device, then you calculate the new value of T? Why not send that shiny new value out? You want it to get old before it's allowed out of school?
OK, I messed around a bit more (like, hours more), and came up with this:

https://pastebin.com/757tJqJw

Insofar, this was worked for two tanks of fuel. I changed the servo type, and you are right that it hunts around for the right value without ever finding it completely. However, I have considered this and for this application, that's OK - because the nitro engine is inconsistent, it responds to throttle input differently depending on where there throttle was before and the load and the mixture situation and temperature of the engine, all of which are constantly changing. This engine is hooked up to a large flywheel so that it responds to throttle input more slowly making this possible without really severe oscillations. When I looked at using a PID controller, I found it really difficult to adopt it to this scenario. Most PID skeletons I found look like this:
Code: [Select]

/*working variables*/
unsigned long lastTime;
double Input, Output, Setpoint;
double errSum, lastErr;
double kp, ki, kd;
void Compute()
{
   /*How long since we last calculated*/
   unsigned long now = millis();
   double timeChange = (double)(now - lastTime);
 
   /*Compute all the working error variables*/
   double error = Setpoint - Input;
   errSum += (error * timeChange);
   double dErr = (error - lastErr) / timeChange;
 
   /*Compute PID Output*/
   Output = kp * error + ki * errSum + kd * dErr;
 
   /*Remember some variables for next time*/
   lastErr = error;
   lastTime = now;
}
 
void SetTunings(double Kp, double Ki, double Kd)
{
   kp = Kp;
   ki = Ki;
   kd = Kd;
}


 Partly because of the reasons listed, also in part because of the nature of the throttle controlled by a servo (note that lower servo value = more throttle). Most PID controllers I found were accustomed to on/off applications such as a fan motor that runs off of PWM, not off of servo input. Additionally, I felt that my knowledge of coding was far to basic and my resources too limited to properly make and tune a PID controller to this application. Of course, if you have any ideas on how to manifest that, I would be all ears.

Right now there is only one major shortcoming of this code - the overheat shutoff function. . The arduino normally associates throttle with RPM, and mixture with temperature. However, for ONE condition only (if the temperature is too hot), the program must associate throttle with temperature, and react by closing the throttle to allow the engine to cool and prevent engine damage.


This part of the code is of particular interest:
Code: [Select]

else { //Engine is much too hot! Let's richen and cut throttle.
N = 25;
 HSN = N;
// LS.write(HSN);
RS.write(HSN);
Throttle = T;
T = 76;
traxxas.write(Throttle);
Serial.println(HSN);
Serial.println(Throttle); }


I know there is a lot of stuff commented out. That was there to operate a continuous speed servo which I have since replaced with a standard 180ish degree servo that is less susceptible to feedback and noise interference (after many capacitors, resistors and diodes failed to resolve the interference problems properly). I am keeping it because I may try and revert back to this or a similar servo at a later date so that I can completely automate the engine using a handheld controller.

Thanks for the advice! Very grateful for your help.

P.S. currently, it doesn't matter to me. But, at the moment the system can not run by itself with its own power supply (7.2V 1800mah NiMh battery) properly without being plugged into my computer. the code seems to get all messed up and the servos move at sporadic intervals. Do you think this is a power supply problem? I can power one of the servos using an alternative power supply (6v radio receiver battery pack). Or, is it a Serial function issue? I have read that commenting out all of he serial commands might help this, but I am skeptical of this.
Needs help wrangling pixies

MorganS

Quote
Code: [Select]
Throttle = T;
T = 76;
traxxas.write(Throttle);
Read that part again. What do you think it does? I think it sets the throttle servo to some value other than 76. Is that what you wanted?

It should not be that hard to adapt an existing PID algorithm. Just make the K factors negative, so a higher input gives a lower output. That's what K is there for - it mathematically converts inputs into outputs.

Which Arduino are you using? The Due gets upset if it has serial outputs and no USB to send them to. All the others should be fine. It sounds like a power supply issue.
"The problem is in the code you didn't post."

Go Up