Help with PID for hydraulic tilt control

Hi all!

I'm trying to implement a PID controller for a hydraulic tilt compensation. I've got it running allready but without PID so far, cause my coding skills are rather weak.

The following situation is given: I use an IMU to sense the tilt, using y axis quaternion, so I get angle values between +/-15°, the hydraulic proportional valve accepts signals from -10 to +10 volts. I'm using a 12 bit DAC to generate the control voltage with an op amp to scale it to the required +/-10 volts. Now a problem occured, wwithout a proper PID controller I don't get a strong enough response to the tilt error and thus the compensation does only minor corrections of the tilt.

Now my question is, how can I get an PID output with a "center point" of 2047 and an input between -15° and +15°?
My problem is that I don't fully understand the PID libraries, I looked into PID_v1, PID_v2, and QuickPID so far.

I almost forgot to mentionion that I would need some sort of deadband, like +/-1°.

thanks in advance for your efforts

cheers gueee78

use </> and post your code

I haven't implemented the PID yet, but here's how I did it without using PID library:

#include <Wire.h>
#include <Adafruit_Sensor.h>
#include <Adafruit_BNO055.h>
#include <utility/imumaths.h>
#include <EEPROM.h>
#include <SPI-DAC7611.h>
#include <SerialTransfer.h>

int eeAddress = 0;

uint8_t cs = 8;
uint8_t ld = 2;
uint8_t clr = 3;

DAC7611 dac(cs, ld, clr);

SerialTransfer parameters;
int8_t center = 0;
int8_t g_dynPara_neut = 0;
uint8_t g_dynPara_tot = 0;
uint8_t g_dynPara_gain = 50;
float totband = 1;
float totbandL = -1;
float totbandH = 1;
int8_t neutral = 0;

//bool send_values = false;
bool umkehr = true;
struct STRUCT {
  int8_t neut;
  uint8_t tot;
  
  uint8_t gain;
  bool umkehr;
} recStruct;

int dacVal = 2047;
int dacbuffer = 0;
float comp = 0;
  
Adafruit_BNO055 bno = Adafruit_BNO055(55);

void setup(void) 
{
  pinMode(9, OUTPUT);
  Serial.begin(9600);
  parameters.begin(Serial);
  dac.begin();  //initialise DAC
  Serial.println("Orientation Sensor Test"); Serial.println("");
  
  /* Initialise the sensor */
  if(!bno.begin())
  {
    /* There was a problem detecting the BNO055 ... check your connections */
    Serial.print("Ooops, no BNO055 detected ... Check your wiring or I2C ADDR!");
    while(1);
  }
  
  delay(1000);
    
  bno.setExtCrystalUse(true);
}

void loop() 
{
    digitalWrite(9, LOW); 
    if(parameters.available())
    {
        parameters.rxObj(recStruct);
        g_dynPara_gain = recStruct.gain;
        g_dynPara_neut = recStruct.neut;
        g_dynPara_tot = recStruct.tot;
        umkehr = recStruct.umkehr;
        eeAddress = 0;
        EEPROM.update(eeAddress, g_dynPara_gain);
        eeAddress += sizeof(uint8_t);
        EEPROM.update(eeAddress, g_dynPara_neut);
        eeAddress += sizeof(int8_t);
        EEPROM.update (eeAddress, g_dynPara_tot);
        eeAddress += sizeof(uint8_t);
        EEPROM.update (eeAddress, umkehr);

    }
    /* Get a new sensor event */
    sensors_event_t event;
    bno.getEvent(&event);
    neutral = g_dynPara_neut;
    comp = (event.orientation.y - neutral) * g_dynPara_gain;
    totband = g_dynPara_tot / 2;
    totbandL = neutral - totband;
    totbandH = neutral + totband;
    //center = event.orientation.y + neutral; //+ g_dynPara_neut;
    if (umkehr == false)
    {
        if (event.orientation.y <= totbandL || event.orientation.y >= totbandH )
        {
            dacbuffer = 2047 + (comp);
        }
        else
        {
            dacbuffer = 2047;
        }
    }
    else if (umkehr == true)
    {
        if (event.orientation.y <= totbandL || event.orientation.y >= totbandH )
        {
            dacbuffer = 2047 - (comp);
        }
        else
        {
            dacbuffer = 2047;
        }
    }
    
    if (dacbuffer >= 4095) {dacbuffer = 4095;}
    if (dacbuffer <= 0) {dacbuffer = 0;}
    dacVal = dacbuffer;
    dac.analogWrite(dacVal);  //send values to DAC

    /* Display the floating point data */
    /*Serial.print("Y: ");
    Serial.println(event.orientation.y, 4);
    Serial.print("   Neutral:    ");
    Serial.println(neutral);
    Serial.print("   comp:    ");
    Serial.println(comp);
    Serial.print("DAC: ");
    Serial.print(dacVal);*/

   
}

please edit your post and post the code using </>

I did, it wasn't displaying it properly in first place so I edited it.

are you driving a DC motor with a PWM.
will the hydraulics hold the position of the table(?) when the motor is off?

Wouldn't that be "only does MAJOR corrections to the tilt"? Without the 'I' term, you need the error to be fairly big to get enough output signal to overcome inertia.

The Output is in arbitrary units. Typically the Output limits would be the DAC limits. If you want the center point to be 2047, just set your Output limits evenly on either side of that. For example 0 and 4095 or 2000 and 2095.

Wouldn't that defeat the purpose of a PID controller?

i really should wait for an answer to reply #6

but if this is a hydraulically controlled device where a motor controls a hydraulic pump, wouldn't the motor need to be driven both forward and reverse?

wouldn't the error be the difference between some current measured position and a desired position resulting in a signed (+/-) value

wouldn't the be driven in one direction if the error is positive and in the opposite direction if negative?

if the above is correct, is PID needed if the motor can be stopped quickly enough within some tolerance, +/- 1deg (or some greater tolerance that accounts for stopping time)?

first of all I wanna thank you for your replies,

you're right, I expressed myself badly.

thanks, that is the answer to one of the questions!

I wouldn't think so, the PID should only ignore a minor tilt but if the error exceeds the deadband it should go almost full blast with correction.

that tilt compensation is part of a way bigger system, the pumps are driven by a combustion engine, the tilting system consists of a proportional directional valve, two locking valves in series with the proportional valve, two hydraulik cylinders and some other safety stuff that isn't relevant for the control.
A -10 volt signal causes maximum roll rate to one side a +10 volt signal maximum roll rate to the other side. I scaled the 0 - 4.095 volts output of the DAC to -10 to +10 volts using an op Amp circuit. that part already works as expected, but since my coding skills aren't en par with my electronic skills I struggle with understanding the pid library code and therefore don't get the "output kick" I'd need to properly drive the hydraulics.

PID is not that complicated. how do you measure your error?

Maybe write your controller to do that rather than delving into the mysteries of PID.

sorry, I forgot to mention that. I'm using an absolute IMU (BNO055) that measures the angle of the platform, so the error is the deviation from horizontal.

I think or rather know that a PID controller does exactly what I want/need, so I don't see why I should reinvent the wheel. It's not that I don't understand PID controllers per se it's rather weak understanding of C++ language.

i think for many of us, figuring out how to use someone else's solution is more difficult than writing and understanding our own code.

with your approach, you'll need help from people familiar with that library.

I was hoping to find someone familiar with one or more of the many PID libraries.

I was basically asking for help cause the circuit is already in duty and I don't have a second set of hardware to test and debug a newly developed code. I thought it would be easier to use a known good code that people used in many applications than write my own that could introduce some unwanted behavior.
as you might have noticed reading my code I can pass parameters to the arduino using a second arduino with a hooked up LCD and an encoder, sending these parameters via RS485.
so I could easily tune the K values on the fly, so that's not my problem.

essentially all I wanted to know is, how a error of maximum +/-15 would result in an output of 0-2047 or 2048-4095, in other words how to setup the PID controller.
Regarding the deadband I can wrap my head around that, so it doesn't necessarily have to be part of the PID controller.

const long  Max = 4096;
const float Kp  = 136.5;

int
pid (
    int  err)
{

    long out = Kp * err;

    out +=  Max / 2;
    out  =  Max <= out ?  Max-1 : out;
    out  =  0   >= out ?  0     : out;

    return out;
}

you may also want to consider testing you code in simulation. in your case, it may be dangerous if not cause damage if the tilt compensation is part of a bigger system

you may want to verify the range and sign of measurements on the target (i.e. system) as well as the range and sign of the control signals.

the simulation would exercise the code using a model of the system (make sure the signs are correct) to at the very least make sure any changes are in the correct direction and are not excessive.

the code can be executed on the target once you're confident about the simulated code. hopefully it is slow rather than too fast. being able to then change parameters (Kp, Kd) on the target (presumably thru the serial monitor) allows fine tuning.

what library is that code using?

i wrote it
see Wiki PID

I mean what library do I have to include?

I read that article before btw

it's not a library ... i wrote it to answer your question