PID Control on PANDAII with RLP

Hello all,
I am intended to manage a PID o PANDA II (or at least a Proportional error correction in a first time) ? The final goal is to display time on fez touch in seconds while controlling the PID (input : the speed of the motor is given by a sensor wich can pulse up to 200hz , output : a PWM wich sets the speed of the motor)

The issue is that in C# managed code, when I install an interrupt for counting time between two pulses, the interrupt seems to be disrupted by other tasks running in main process and so It doesn’t measure the correct interval of time. i.e, every second a little buffer is sent to fez touch screen, and that cause lack of precision on the interval time between two pulses of the motor


        static void motor_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            lSystemTickCurrentUs = Utility.GetMachineTime().Ticks;
            fDelayMotorUs = lSystemTickCurrentUs - lSystemTickPreviousUs;
            lSystemTickPreviousUs = lSystemTickCurrentUs;
            fDutyCycleFiltered = (fDutyCycleFiltered + fDutyCycle * 0.02f) / 1.02f;       //filtering for minimizing the lack of precision 
            if (fDutyCycleFiltered > 100) { fDutyCycleFiltered = 100; }
            pwmMotor.Set(Param.iPWMFreqHz, (byte)fDutyCycleFiltered);
            fDutyCycle = fDelayMotorUs / KP;
        }

Indeed I know that .NET MicroFramewoek is not for reaI time, also decided to manage the PID process in native code for best performances : for the moment, I start with an interrupt wired on my motor sensor ; and I would like to get system tick in order to get time measurement between two pulse. Is there a way to get system tick directly in my interrupt or should I create a timer wich will invrement a value periodically?

Here is how I start at the moment :


// RLP_PID.c
 #include "./RLP_User/RLP.h"

void isr(unsigned int Pin, unsigned int PinState, void* Param )
{
	// change the pin state just for exemple and checking that interrupt works
	state = !state;
	RLPext->GPIO.WritePin(pin, state); 
	// Here I would get an absolute time in order to process a P(ID) control

}

// CSharp Method
// byte[] barray = new byte[];
// PID_c.Invoke(uint [] generalArray,byte[] byteArray, byte filler);
int PID_c(unsigned int *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{

	//SETUP MOTOR INTERRUPT
	pin = *(unsigned char*)args[0];
	state = *(unsigned char*)args[1];
	RLPext->GPIO.EnableOutputMode(pin, state);

	RLP_InterruptInputPinArgs ia;
	ia.GlitchFilterEnable = RLP_FALSE;
	ia.IntEdge = RLP_GPIO_INT_EDGE_LOW;
	ia.ResistorState = RLP_GPIO_RESISTOR_PULLUP;
	RLPext->GPIO.EnableInterruptInputMode(20, &ia, isr, 0); // 20 :Pin D0   motor pulse will fire the interrupt
       return 0 ;

}

How to get tick from there?

I guess next steps for me are :

  1. find a way to get the tick or the period between two interrupts
    2)control PWM with PWM0xxx registers
    3)process a light PI control

Any help/tips/suggestions would be appreciated!

cca,

Maybe setting up a timer in native code would work, but an interrupt running in the managed code may cause you to have the same problems, dont know without trying!.
A failsafe method would be to use http://www.ghielectronics.com/catalog/product/375 DL40, if you could get it to work on a PandaII.

You are assuming that the interrupt is serviced exactly when the ISR is executed. Bad assumption.

static void motor_OnInterrupt(uint data1, uint data2, DateTime time)
        {
            lSystemTickCurrentUs = Utility.GetMachineTime().Ticks;
            fDelayMotorUs = lSystemTickCurrentUs - lSystemTickPreviousUs;
            lSystemTickPreviousUs = lSystemTickCurrentUs;
            fDutyCycleFiltered = (fDutyCycleFiltered + fDutyCycle * 0.02f) / 1.02f;       //filtering for minimizing the lack of precision 
            if (fDutyCycleFiltered > 100) { fDutyCycleFiltered = 100; }
            pwmMotor.Set(Param.iPWMFreqHz, (byte)fDutyCycleFiltered);
            fDutyCycle = fDelayMotorUs / KP;
        }

See the DateTime parameter passed to the ISR? Use that not GetMachineTime. That will get you the ACTUAL time the interrupt happened. GetMachineTime is the time it was serviced.

Davef->Thank you for the solution, but I would like (try) to do the job without any extra hardware. Indeed, I keep in mind (as backup solution) the possibility to embed the PID control on a separate chip, and just give orders from pandaII to this chip through serial com. ( Maybe even something really cheap like TI MSP430 http://e2e.ti.com/group/msp430launchpad/w/default.aspx )

Brett->Thank you too, you are right, I had not seen that! I will try it asap. But I think even if it fixes the issue of time measurement beetwen two interupts, I wil go ahead on the “natve code” way because I want some “real-time” reactivity (delay beetween the interrupt and the actual time of ISR execution as short and constant as possible ).
So I will look for a (native) way to get the tick or the period between two interrupts. (maybe the lpc23xx documentation, or I will find some C-style “gettimeofday” equivalent)

FYI,
we managed to realise a complete PID in order to control our two motions motor with a Panda II without RLP

It worked pretty well. You can check the code on our Codeplex : mlrobotic.codeplex.com

It’s in MLRobotic2012 then Asserv Project, there is a PID.cs

We gave few details on our blog but i m afraid it’s in french : http://blog.mlrobotic.com/2012/02/asservissement-net/

Hi Pacman, Yes it seems good, but I guess you use some extra components with serial interface (LS7366) for the input measurement . Then the board has no real time to care about.

What I would do is to implement a native PID (wich will hopefully be real-time) to regulate the speed of a motor, without extra components (even inexpansive)
Regarding the lpc23xx user manual, a good solution is to use native timers, interrupts and hardware PWM (and let the other stuff with the LCD screen / WEB server… on managed side).

PS : no problem for the french, it is my native language :wink:

I made some progress in my attempts: A have now a timer, a PWM controller and an interrupt , all in native side (see below)
So I should have everything to build a PID that match my needs.
But I have a last issue :
-When I launch my program from visual Express 2010, (F5 button or ctrl+F5 to execute without debug), the program works as expected : On pin PWM1 I have a PWM signal (with a fake PID for the moment : the duty cycle inversely proportional to frequency of signal on D0.)
-When I use the reset button, or when I cycle the pandaII power, the PWM signal is no longer present on pin PWM1 (the signal is pull-down). I can see that the program is alive, because the onboard led is blinking…Only the PWM is not working!!!

->What is strange is the different behaviour between lauching from VSEXpress and from Panda itself !? Maybewhen loading from VSExpress there are some extra initialisations that I forgot? or any startup timing considerations?I don’t see what I miss? See my code below.

On managed side :

using System;
using System.Threading;
using Microsoft.SPOT;
using Microsoft.SPOT.Hardware;
using GHIElectronics.NETMF.FEZ;
using GHIElectronics.NETMF.Native; //code natif RLP

namespace HelloNativeCode
{
    public class Program
    {
        static RLP.Procedure TestMethod_c;

        public static void Main()
        {

            //Init RLP
            int ret = 0;
            RLP.Enable();
            RLP.Unlock("C........................................................................................................................................................");
            byte[] elf_file = Resources.GetBytes(Resources.BinaryResources.RLP_PID);
            if (elf_file == null)
            {
                Debug.Print("Echec de chargement du fichier binaire");
                return;
            }
            
            RLP.LoadELF(elf_file);
            RLP.InitializeBSSRegion(elf_file);
            TestMethod_c = RLP.GetProcedure(elf_file, "PID_c");

            // We don't need this anymore
            elf_file = null;

            //Use RLP fonction
            ret = TestMethod_c.Invoke(18, true); //led = 69 //D0 = 20 //D1=18
            Debug.Print("Ret" + ret);

            // Blink board LED for checking program activity
            bool ledState = false;
            OutputPort led = new OutputPort((Cpu.Pin)FEZ_Pin.Digital.LED, ledState);
            while (true)
            {
                // Sleep for 500 milliseconds
                Thread.Sleep(500);
                // toggle LED state
                ledState = !ledState;
                led.Write(ledState);
            }

        }//End Main

    }//End Program

}//End namespace

On native side :

// RLP_PID.c
 #include "./RLP_User/RLP.h"
 #include "LPC23xx.h"
 #include "irq.h"
 #include "RLP_PID.h"
 #include "bitmask.h"


unsigned IsrInstalled = FALSE;
unsigned pin;
unsigned pinTimer;
unsigned stateISR = 0;
unsigned stateTimer = 0;
unsigned short MsSinceLastCall = 0;
unsigned int uiCptSpeed = 0;
unsigned int uiDutyCycle = 0;

union{
	unsigned int	uiVal[2];
	unsigned long	ulVal;
}Capture;

int Deinit(void* generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
    if (IsrInstalled)
    {
        RLPext->Interrupt.Uninstall(TIMER3_INT);
        IsrInstalled = FALSE;
    }
    return 0;
}

void Capture_Interrupt(unsigned int Pin, unsigned int PinState, void* Param )
{
	// change the pin state for testing
	stateISR = !stateISR;
	RLPext->GPIO.WritePin(pin, stateISR); //led = 69 //D0 = 20 //D1=18
	uiCptSpeed++;
}


void Timer_Interrupt( void* arg )
{
	//	toutes les 100 ms
	//Process PID
	uiDutyCycle = (2000/uiCptSpeed);
	if (uiDutyCycle>1000)
	{uiDutyCycle=1000;}
	//Update PWM
	//PWM1MR0		= 1000;			// période en µs : 1000µs = 1ms
	PWM1MR1		= uiDutyCycle;//800			// Temps haut en µs
	PWM1LER		= 0x02;			// Latch du registre PWM1MR1

	//Reset Pulse Counter
	uiCptSpeed = 0;

    T3IR |= 1;

    }

// CSharp Method
// byte[] barray = new byte[];
// PID_c.Invoke(uint [] generalArray,byte[] byteArray, byte filler);
int PID_c(unsigned int *generalArray, void **args, unsigned int argsCount, unsigned int *argSize)
{
	pin = *(unsigned char*)args[0];
	stateISR = *(unsigned char*)args[1];
	RLPext->GPIO.EnableOutputMode(pin, stateISR);
	RLPext->GPIO.WritePin(pin, stateISR);

	pinTimer = 33;//led = 69 //D0 = 20 //D1=18 //D2 = 33
	stateTimer = *(unsigned char*)args[1];
	RLPext->GPIO.EnableOutputMode(pinTimer, stateTimer);
	RLPext->GPIO.WritePin(pinTimer, stateTimer);

	RLPext->Interrupt.GlobalInterruptEnable();
	////////////////////////////////////////////////////////
	//START SETUP MOTOR INTERRUPT
	RLP_InterruptInputPinArgs ia;
	ia.GlitchFilterEnable = RLP_FALSE;
	ia.IntEdge = RLP_GPIO_INT_EDGE_LOW;
	ia.ResistorState = RLP_GPIO_RESISTOR_PULLUP;
	RLPext->GPIO.EnableInterruptInputMode(20, &ia, Capture_Interrupt, 0); // 20 :Pin D0
	//END SETUP MOTOR INTERRUPT
	////////////////////////////////////////////////////////

	////////////////////////////////////////////////////////
	//START SETUP PWM
	//According to lpc23xx_um.pdf p562
	//PWM INIT :
	// power control register :
 	PCONP		|=	BIT6;				//power control bit : pwm1
	PCLKSEL0	&=	~(BIT12 | BIT13);	//Peripheral clock PCLKPWM =CCLK/4 : 72Mhz/4 = 18Mhz
	PINSEL3		|=	BIT5;				//Pin function select register : set GPIO 1.18 for PWM1.1
	PINSEL3		&=	~BIT4;

	PWM1TCR		= 0x02;			// Timer Control Reg :reset du timer
	PWM1PR		= 17;			// Prescaler reg : divise par 18Mhz par (17 +1) pour avoir 1Mhz

	PWM1MCR		= 0x00000002;	// Match Control Reg : reset du compteur sur PWM1MR0
	PWM1PCR		= 0x0200;		// PWM Control Reg : PWM1 output enable
	PWM1TCR		= 0x09;			// Timer Control Reg : Timer enable & PWM enable

	//PWM CONFIGURATION :
	PWM1MR0		= 1000;			// période en µs : 1000µs = 1ms
	PWM1MR1		= 400;			// Temps haut en µs
	PWM1LER		= 0x03;			// Latch du registre PWM1MR0 & PWM1MR1
	//END SETUP PWM
	////////////////////////////////////////////////////////

	////////////////////////////////////////////////////////
	//START SETUP TIMER INTERRUPT
	//Deinit(0, 0, 0, 0);

	// Setup timer 3 According to lpc23xx_um.pdf p551
	PCONP |= 1 << 23;        	// power control bit :  timer 3
	PCLKSEL1 &=	~(BIT14 | BIT15);//Peripheral clock PCLKPWM =CCLK/4 : 72Mhz/4 = 18Mhz
	//PCLKSEL1 |= 0x03 << 14;		// T3 clock = PCLK / 8 = 72 Mhz / 8 = 9 Mhz
	//PINSEL not necessary
	T3MR0 = (18000000 / 1000) ;    //Match reg 0 : 1000 Hz

	T3MCR = 0x03;           	// Match Control Reg : bit0: Interrupt on MR0: an interrupt is generated when MR0 matches the value in the TC
								// Match Control Reg : bit1: Reset on MR0: the TC will be reset if MR0 matches it

	T3PR = 99;//17               	// Set prescaler to 99+1=100
	T3TCR = 0x02;               	// Reset timer
	T3TCR = 0x01;               	// Start timer

	// Hook our interrupt handlers
	if (!RLPext->Interrupt.Install(TIMER3_INT, Timer_Interrupt, 0))
	return -1;

	RLPext->Interrupt.Enable(TIMER3_INT);

	//END SETUP TIMER INTERRUPT
	////////////////////////////////////////////////////////


	return 0;

}


->Fixed : the PWM registers were not set in the good order. In this way it works:


	//PWM INIT :
	// power control register :
 	PCONP		|=	BIT6;				//power control bit : pwm1
	PCLKSEL0	&=	~(BIT12 | BIT13);	//Peripheral clock PCLKPWM =CCLK/4 : 72Mhz/4 = 18Mhz
	PINSEL3		|=	BIT5;				//Pin function select register : set GPIO 1.18 for PWM1.1
	PINSEL3		&=	~BIT4;
 
	PWM1TCR		= 0x02;			// Timer Control Reg :reset du timer
	PWM1PR		= 17;			// Prescaler reg : divise par 18Mhz par (17 +1) pour avoir 1Mhz

	//PWM CONFIGURATION :
	PWM1MR0		= 1000;			// période en µs : 1000µs = 1ms
	PWM1MR1		= 400;			// Temps haut en µs
	PWM1LER		= 0x03;			// Latch du registre PWM1MR0 & PWM1MR1

	PWM1MCR		= 0x00000002;	// Match Control Reg : reset du compteur sur PWM1MR0
	PWM1PCR		= 0x0200;		// PWM Control Reg : PWM1 output enable
	PWM1TCR		= 0x09;			// Timer Control Reg : Timer enable & PWM enable