2

I have built a tricopter from scratch based on a .NET Micro Framework board from TinyCLR.com. I used the FEZ Mini which runs at 72 MHz. Read more about my project at: http://bit.ly/TriRot.

So after a pre-flight check where I initialise and test each component, like calibrating the IMU and spinning each motor, checking that I get receiver data, etc., it enters a permanent loop which then calls the flight controller method on each loop.

I'm trying to tune my PID controller now using the Ziegler-Nichols method, but I am always getting a progressively larger overshoot. I was eventually able to get a [mostly] stable oscillation using proportional control only (setting Ki and Kd = 0); timing the period K with a stopwatch averaged out to 3.198 seconds.

I came across the answer (by Rex Logan) on a similar question by chris12892.

I was initially using the "Duration" variable in milliseconds which made my copter highly aggressive, obviously because I was multiplying the running integrator error by thousands on each loop. I then divided it by another thousand to bring it to seconds, but I'm still battling...

What I don't understand from Rex's answer is:

  1. Why does he ignore the time variable in the integral and differential parts of the equations? Is that right or is it a typo?
  2. What he means by the remark

    In a normal sampled system the delta term would be one...

    One what? Should this be one second under normal circumstances? What if this value fluctuates?

My flight controller method is below:

private static Single[] FlightController(Single[] imuData, Single[] ReceiverData)
{
    Int64 TicksPerMillisecond = TimeSpan.TicksPerMillisecond;
    Int64 CurrentTicks = DateTime.Now.Ticks;
    Int64 TickCount = CurrentTicks - PreviousTicks;
    PreviousTicks = CurrentTicks;
    Single Duration = (TickCount / TicksPerMillisecond) / 1000F;

    const Single Kp = 0.117F; //Proportional Gain (Instantaneou offset)
    const Single Ki = 0.073170732F; //Integral Gain (Permanent offset)
    const Single Kd = 0.001070122F; //Differential Gain (Change in offset)

    Single RollE = 0;
    Single RollPout = 0;
    Single RollIout = 0;
    Single RollDout = 0;
    Single RollOut = 0;
    Single PitchE = 0;
    Single PitchPout = 0;
    Single PitchIout = 0;
    Single PitchDout = 0;
    Single PitchOut = 0;

    Single rxThrottle = ReceiverData[(int)Channel.Throttle];
    Single rxRoll = ReceiverData[(int)Channel.Roll];
    Single rxPitch = ReceiverData[(int)Channel.Pitch];
    Single rxYaw = ReceiverData[(int)Channel.Yaw];

    Single[] TargetMotorSpeed = new Single[] { rxThrottle, rxThrottle, rxThrottle };
    Single ServoAngle = 0;

    if (!FirstRun)
    {
        Single imuRoll = imuData[1] + 7;
        Single imuPitch = imuData[0];

        //Roll ----- Start
        RollE = rxRoll - imuRoll;

        //Proportional
        RollPout = Kp * RollE;

        //Integral
        Single InstanceRollIntegrator = RollE * Duration;
        RollIntegrator += InstanceRollIntegrator;
        RollIout = RollIntegrator * Ki;

        //Differential
        RollDout = ((RollE - PreviousRollE) / Duration) * Kd;

        //Sum
        RollOut = RollPout + RollIout + RollDout;
        //Roll ----- End

        //Pitch ---- Start
        PitchE = rxPitch - imuPitch;
        //Proportional
        PitchPout = Kp * PitchE;

        //Integral
        Single InstancePitchIntegrator = PitchE * Duration;
        PitchIntegrator += InstancePitchIntegrator;
        PitchIout = PitchIntegrator * Ki;

        //Differential
        PitchDout = ((PitchE - PreviousPitchE) / Duration) * Kd;

        //Sum
        PitchOut = PitchPout + PitchIout + PitchDout;
        //Pitch ---- End

        TargetMotorSpeed[(int)Motors.Motor.Left] += RollOut;
        TargetMotorSpeed[(int)Motors.Motor.Right] -= RollOut;

        TargetMotorSpeed[(int)Motors.Motor.Left] += PitchOut;// / 2;
        TargetMotorSpeed[(int)Motors.Motor.Right] += PitchOut;// / 2;
        TargetMotorSpeed[(int)Motors.Motor.Rear] -= PitchOut;

        ServoAngle = rxYaw + 15;

        PreviousRollE = imuRoll;
        PreviousPitchE = imuPitch;
    }
    FirstRun = false;

    return new Single[] { 
    (Single)TargetMotorSpeed[(int)TriRot.LeftMotor], 
    (Single)TargetMotorSpeed[(int)TriRot.RightMotor], 
    (Single)TargetMotorSpeed[(int)TriRot.RearMotor],
    (Single)ServoAngle
    };
}

Edit: I found that I had two bugs in my code above (fixed now). I was integrating and differentiating with the last IMU values as opposed to the last error values. That got rid of the runaway sitation completely. The only problem now is that it seems to be a bit slow. When I perturb the system, it responds very quickly and stop it from continuing, but it takes a long time to get back to the setpoint (0), about 10 seconds or more. Is this now just down to tuning the PID? I'll give the suggestions below a go, and let you know if any of them make a difference.

One question I have is: being a .NET board, I don't want to bank on any kind of accurate timing, so instead of trying to work out at what frequency I am executing that method, surely if I calculate the actual time and factor that into the equations, it should be better, or am I misunderstanding something?

Community
  • 1
  • 1
Gineer
  • 2,358
  • 4
  • 26
  • 40
  • 1. He is using the deltaT in the ID parts. 2. I think he means normalised then deltaT would be 1. Question: do you have to run at 72MHz in your control loop or does the natural response of the system require a high sample/update rate? Not knowing your system dynamics, updating the control loop say 100 times a second would mean that you could use a deltaT of 0.01 secs which in a 72MHz system would be a constant 0.01. – Dave Feb 05 '12 at 15:59
  • Hi, 8 years now that this thread was created, how is it goint with the algorithm? And your tricopter? Would you want to share your Ziegler-Nichols method on github? I m sure that it could interest some C# dev (like me) to get a nugget package to easily use it :). – Cedric Arnould Dec 01 '20 at 23:47
  • 1
    Hi @CedricArnould, To be quite honest, I've redone the tri-copter in C since C# is not time constant, due to garbage collection etc. I may still have the code lying around somewhere if you want it, but I think it's pointless. feel free to contact me directly from gineer.co.za if you really want it. – Gineer Dec 03 '20 at 08:09

0 Answers0