News:

Let's find out together what makes a PIC Tick!

Main Menu

PID examples

Started by JonW, Mar 21, 2022, 09:30 AM

Previous topic - Next topic

JonW

Does anyone have a well documented example of a PID routine?

J

top204

#1
Many, many years ago, I created an experimental Integer Only PID routine for the Amicus18 board, which used a PIC18F25K20 device operating at 64MHz. It seemed to work well, and I once used it in a motor controller, so I have wrapped the PID routine into a procedure and listed it below for you. Name the listing below as "PID.inc"

$ifndef __PID__
$define __PID__
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Integer only PID routine
' Written by Les Johnson (14-11-11)
'
' Global PID variables
'
    Dim PID_wCorrectionMax As Word              ' Maximum correction to be applied
    Dim PID_wOutputMax As Word                  ' The maximum output value allowed
    Dim PID_wOutputMin As Word                  ' The minimum output value allowed
    Dim PID_wPgain As Word                      ' Response factor for P
    Dim PID_wIgain As Word                      ' Response factor for I
    Dim PID_wDgain As Word                      ' Response factor for D
    Dim PID_wPreviousInput As Word              ' Previous input of the PID   
    Dim PID_wErrorSum As Word                   ' Sum of errors observed
    Dim PID_wErrorMax As Word                   ' Max error sum allowed

'------------------------------------------------------------------------
' Experimental Integer PID routine
' Input     : pInput holds the measurement from a sensor (-32768 to +32767)
'           : pRequired holds the optimum value to acheive
'           : PID_wCorrectionMax holds the maximum correction to apply
'           : PID_wErrorMax holds the maximum error allowed
'           : PID_wPgain holds the Proportional gain (-32768 to +32767)
'           : PID_wIgain holds the Integral gain (-32768 To +32767)
'           : PID_wDgain holds the Derivative gain (-32768 To +32767)
'           : PID_wOutputMax holds the maximum output allowed
'           : PID_wOutputMin holds the minimum output allowed
' Output    : Returns the result of the PID calculation
' Notes     : None
'
Proc PID_Compute(pInput As Word, pRequired As Word), Word
    Dim tSign As Bit                                                    ' Holds the sign of a value
    Dim wTemp As Word                                                   ' Temporary word variable
    Dim wError As Word                                                  ' Error observed
    Dim wCorrection As Word                                             ' Internal correction to be applied
   
    wError = pInput - pRequired                                         ' Find the error between what's required and what's just been input
    wCorrection = wError * PID_wPgain                                   ' P factor
    PID_wErrorSum = PID_wErrorSum + wError                              ' Sum the error value
    tSign = PID_wErrorSum.15                                            ' Save the sign of PID_wErrorSum
    PID_wErrorSum = Abs(PID_wErrorSum)                                  ' Make it a positive value for testing
    If PID_wErrorSum > PID_wErrorMax Then                               ' Is the accumulative error greater than that allowed?
        PID_wErrorSum = PID_wErrorMax                                   ' Yes. So make it maximum
    EndIf
    If tSign = 1 Then                                                   ' Was PID_wErrorSum originally a negative value?
        PID_wErrorSum = -PID_wErrorSum                                  ' Yes. So make it negative
    EndIf
    wCorrection = wCorrection + (PID_wErrorSum * PID_wIgain)            ' I factor
    wTemp = ((PID_wPreviousInput - pInput) * PID_wDgain)                ' \
    wCorrection = wCorrection + wTemp                                   ' / D factor
    PID_wPreviousInput = pInput                                         ' Save the current measurement
    tSign = wCorrection.15                                              ' Save the sign of wCorrection
    wCorrection = Abs(wCorrection)                                      ' Make it a positive value for testing
    wCorrection = wCorrection / 64                                      ' Scale the value for the PWM
    If wCorrection > PID_wCorrectionMax Then                            ' Is the correction greater than that allowed?
        wCorrection = PID_wCorrectionMax                                ' Yes. So make it maximum
    EndIf
    If tSign = 1 Then                                                   ' Was wCorrection originally a negative value?
        wCorrection = -wCorrection                                      ' Yes. So make it negative
    EndIf
    Result = Result - wCorrection                                       ' Subtract the correction from the output result
    If Result > PID_wOutputMax Then                                     ' Trap maximum output
        Result = PID_wOutputMax  
    ElseIf Result <= PID_wOutputMin Then                                ' Trap minimum output
        Result = PID_wOutputMin 
    EndIf
EndProc

'------------------------------------------------------------------------
' Initialise the PID routine
' Input     : PID_wOutputMax =  Maximum output value
'           : PID_wOutputMin =  Minimum output value
'           : PID_wCorrectionMax =  Maximum correction to be applied
'           : PID_wErrorMax =  Maximum error sum allowed
'           : PID_wPgain =  Response factor for P
'           : PID_wIgain =  Response factor for I
'           : PID_wDgain =  Response factor for D
' Output    : None
' Notes     : Must be used at least once before the PID_Compute procedure is used
'           : Uses a pre-processor macro for ease of use
'
$define PID_Setup(pKP, pKI, pKD, pMaxCorrection, pMaxError, pMinOutput, pMaxOutput) '
    PID_wOutputMax = pMaxOutput          '
    PID_wOutputMin = pMinOutput          '
    PID_wCorrectionMax = pMaxCorrection  '
    PID_wErrorMax = pMaxError            '
    PID_wPgain = pKP                     '
    PID_wIgain = pKI                     '
    PID_wDgain = pKD                     '
    PID_wPreviousInput = 0               '
    PID_wErrorSum = 0

$endif      ' __PID__

The main code was created before I added signed variables into the compiler, so it can be tweaked to use SWord variables, where required, and the tSign bit variable gotten rid of etc...

Below is a demo program I used to give a crude test of it:

'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Amicus18 integer only PID routine Demonstration
' Written by Les Johnson (14-11-11)
'
' Connect a potentiometer to AN0 to give the required value to reach(0 to 1023)
' Connect the output from CCP1 to AN1 through a low pass filter
'
' Turning the pot connected to AN0, the output of the PWM should match it
' Note that the PID routine will require tuning by adjusting P gain, I gain and D gain
'
    Include "Amicus18.inc"
    Include "Amicus18_ADC.inc"                  ' Load the ADC macros into the program
    Include "Amicus18_HPWM10.inc"               ' Load the 10-bit HPWM macros into the program
    Include "PID.inc"                           ' Load the PID routine into the program
'
' Create some user variables
'
    Dim MyInput As Word                         ' Input to the PID, which is the output from the sensor
    Dim MyRequired As Word                      ' The value that the PID is required to reach
    Dim MyOutput As Word                        ' The output from the PID

'------------------------------------------------------------------------
' The main program loop starts here
'
Main:
'
' Open the ADC for 10-bit reading of AN0 and AN1
'
    OpenADC(ADC_FOSC_RC & ADC_RIGHT_JUST & ADC_2_TAD, ADC_REF_VDD_VSS, ADC_2ANA)
'
' Open the CCP1 channel for 10-bit PWM output
'
    OpenAnalog1()
'
' Setup the PID parameters
' (pKP, pKI, pKD, pMaxCorrection, pMaxError, pMinOutput, pMaxOutput)
'
    PID_Setup(10, 3, 1, 9, 1, 0, 1023)
'
' Create a loop to constantly adjust the PWM (CCP1) output depending on the ADC (AN1) input
'
    Do
        MyRequired = ReadADC(ADC_CH0)               ' Read the value required by the PID from AN0
        MyInput = ReadADC(ADC_CH1)                  ' Read AN1 for the value to place into the PID
        MyOutput = PID_Compute(MyInput, MyRequired) ' Compute PID
        WriteAnalog1(MyOutput)                      ' Place the output of the PID onto the HPWM
        DelayMS 3                                   ' A small delay
    Loop                                            ' Do it forever

For slower PID operation, it would be easier to use Floating Point variables, because it is then a simple set of equations.

Below is the demo program above, running in a simulator. As the potentiometer is turned, the PWM voltage tracks it. I did add a graph to it once upon a time to see it stabilising, but I cannot find that simulation file.

Integer PID.jpg

JonW

Awesome, Many thanks Les


charliecoutas

Good work Les, very useful in future.
Charlie

keytapper

#4
I made mine with a small expectation that uses only one byte result. But the procedure allows to allocate for two or more caller. Each of them will have their own memory to save temporary data.
My design is prepared for a mere 16F628A.
Dim cntx As Byte                    ' dummy variable use in FOR loop
Dim cnty As Byte                    ' dummy variable use in FOR loop
Dim Kp As Word                      ' P variable
Dim Ki As Word                      ' I variable
Dim Kd As Word                      ' D variable
Dim tempdata[8] As Byte At $b0      ' spare RAM to swap procedure data
$define fractionOf(x) x / 1024      ' Added a missing statement on 20th May '22

Proc TEMPctrl(currtemp As Byte, pos As Byte), Byte
' procedure to implent a PID control
' currtemp is for the measured input
' Setpoint is a global value that tell the limit to achieve
' pos is used to map a position in RAM where the data are stored. So the caller
' will obtains its own results
' no_PID is for skipping PID function and operating just as ON/OFF method

  Dim integral As SByte             ' integral term for PID
  Dim previous_error As SByte       ' memory for last error
  Dim derivative As SByte           ' derivative term for PID
  Dim err As SByte                  ' error calculation
  Dim tmp As SDword                 ' 32bit signed scratch variable

  If currtemp < MINTEMP Then        ' the heater didn't reach above minimum
    Result = 100                    ' result will be maximum
    ExitProc                        ' end return
  End If
  cnty = FSR                        ' preserve the register, just in case
  FSR = AddressOf(tempdata) + pos   ' restore the data, loding the area to use
  cntx = FSR
  integral = INDF                   ' into the necessary variables
  Inc FSR
  previous_error = INDF
  pos = pos * 4
  Kp = ERead pos
  Inc pos,2
  Ki = ERead pos
  Inc pos,2
  Kd = ERead pos
  err = Setpoint - currtemp         ' start the calculation
  tmp = err * fractionOf(Kp)        ' evaluate the proportional term
  integral = integral + err         ' add the integral
  ' keep into a reasonable limit
  If integral < 0 Then integral = 0
  If integral > 100 Then integral = 100
  ' calculate the integral term
  tmp = tmp + integral * fractionOf(Ki)
  derivative = err - previous_error
  ' complete the summation
  tmp = tmp + derivative * fractionOf(Kd)
  If tmp < 0 Then  tmp = 0
  If tmp > 100 Then tmp = 100       ' limiting for the necessary
  Result = tmp                      ' convert into a single byte
  FSR = cntx                        ' Save the data, so it's possible to swap
  INDF = integral
  Inc FSR
  INDF = err                        ' save the error, for the next occurence
  FSR = cnty
EndProc
So according to pos there could be a different result for the caller. I probably make a crude method to allocate different data for each caller, but I tried to avoid bank swapping for the intent to use two controller.

There still something that I didn't catch with Mr. Les procedure, I think he has all the card to play safer than me. I should read more and learn some other tricks of the compiler.
Ignorance comes with a cost

JonW

Les I have started to look at this and in your INC file, should the variable declarations SWORD rather than Word?
 

Jon

top204

QuoteThe main code was created before I added signed variables into the compiler, so it can be tweaked to use SWord variables, where required, and the tSign bit variable gotten rid of etc...

They should be able to be converted to SWord types and the sign bit saving/restoring and Abs removed from the code and replaced with signed comparisons.

JonW

AH Sorry, I didn't notice that.  Will give it a go in the simulator

Many thanks

Jon

JonW

#8
Works very, very nicely Les.

Here is the transient response to a file based step function and FW limiting

Red = File based target
Green = PID Tracking Loop Response (Filtered)

Second Trace is the loop time stretched to show the Max Correction limit working to help avoid instability.

Loop time 10us =  Fast loop time and optimised overshoot

Finally Limits on Pmin (input and Output) proving windowed tracking





top204

Glad you liked it Jon.

I've used it a few times with motor control and it was very stable. I also used it for a copper plating bath so that the bath kept a constant current while it was moving in the copper sulphate to allow flow through the holes, and it worked quite well, although the stability time was not as fast as I wanted at the time, so that the copper stayed nice and shiny.

I was going to use a PIC24 for faster response with it, but life changed for me before I had the chance. :-(

JonW

For my applications where it will be used in a triple or quad AGC loop based on Pin Diodes DC to 6GHz+,  a slower more averaged response time is preferred so not to react to any unwanted RF bursts to maintain a constant average aggregate RF power over slow moving changes in the receiver.
I will give it a whirl in the lab today and see how it reacts to fast RF Bursts of modulated data.  Am using RMS detectors, so the aggregate RF detected power will not be impacted by the modulation due to the peak to average ratios of the inputs having different modulation schemes.

Just love its simplicity..

Yasin

If the value of C1 in the diagram is decreased, the capture rate will increase. But it is inconvenient to reduce it too much.

joesaliba

@top204

Les,

Will it work if I replace the 1023 with a variable in the PID_Setup(10, 3, 1, 9, 1, 0, 1023)?

And the pKP is how much of a value is added or decreased for correction?

Regards

Joe

JonW

Quote from: joesaliba on May 22, 2022, 09:40 AM@top204


Will it work if I replace the 1023 with a variable in the PID_Setup(10, 3, 1, 9, 1, 0, 1023)?



Yes it will, as will any of the parameters as they are simply variables in the .INC file

joesaliba

Quote from: JONW on May 22, 2022, 04:37 PM
Quote from: joesaliba on May 22, 2022, 09:40 AM@top204


Will it work if I replace the 1023 with a variable in the PID_Setup(10, 3, 1, 9, 1, 0, 1023)?



Yes it will, as will any of the parameters as they are simply variables in the .INC file

Thank you JonW,

I did try it and works great. I am controlling a small heater using DC and a P-Channel MOSFET so I can get analog signal as a feedback for MyInput.

At first start up after a programming of the PIC, I begin with MyRequired as 0, and alter the value with two push buttons and I can see the output change to my requirement, and that is how it should be.

However, I cannot understand what is going on when I restart the PIC.

The output goes high first, although at first MyRequired is 0, and then switch off as the PID sees that MyInput is high, but My Required is 0. I spent a lot of time trying to find a solution without success.

I did also tried the Static Dim and set all variables to 0 in the PID include but that did not help.

Regards

Joe 




top204

#15
Try making the return variable for the PID_Compute procedure an alias to a global variable, and add the global variable to the PID_Setup meta-macro and clear it to 0 when the PID is first setup.

It may be that it is already holding a large value and the extra values are pushing it over the limit, because RAM is not cleared when a microcontroller is reset. So whenever the microcontroller is reset and the PID is setup, it will contain 0.

To test the theory of the above, add the Clear command to the very beginning of the program and it will clear all the RAM before the program first starts up. If that works, do the code changes from the above statement.

Also remember, the PID is using signed 16-bit variables, so the min is -32768 and the max is 32768.

You will need to do some research on the tuning of the PID with the values for P, I and D in the PID_Setup macro, because values too high will trip the PID into making too large a jump in the corrections. Try the P value as 1 and the I and D values as 0 first, then move them up. PID tuning can be quite time consuming, and if not correct the PID will actually cause more noise than if not there. :-)

I'll create a floating point version and see how much slower it is. It "should" be much more refined in its operation, so the resolution of the error corrections can be much better, but will take a little longer on an 8-bit device for its floating point calculations, and take more RAM and code memory.

top204

Below is the listing for a 32-bit Floating Point PID library:

$ifndef __PID_Float__
$define __PID_Float__
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Floating Point PID routines.
' Written by Les Johnson for the Positron8 BASIC compiler.
' https://sites.google.com/view/rosetta-tech/home
'
' Create some Global PID variables
'
    Dim PID_fCorrectionMax As Float                     ' Maximum correction to be applied
    Dim PID_fOutputMax     As Float                     ' The maximum output value allowed
    Dim PID_fOutputMin     As Float                     ' The minimum output value allowed
    Dim PID_fPgain         As Float                     ' Response factor for P
    Dim PID_fIgain         As Float                     ' Response factor for I
    Dim PID_fDgain         As Float                     ' Response factor for D
    Dim PID_fPreviousInput As Float                     ' Previous input of the PID
    Dim PID_fErrorMax      As Float                     ' Max error sum allowed
    Dim PID_fErrorSum      As Float                     ' Sum of errors observed
    Dim PID_fResult        As Float                     ' Holds the return value of the PID_FloatCompute procedure

'------------------------------------------------------------------------
' Floating Point PID routine
' Input     : pInput holds the measurement from a sensor
'           : pReq holds the optimum value to acheive
'           : PID_fCorrectionMax holds the maximum correction to apply
'           : PID_fErrorMax holds the maximum error allowed
'           : PID_fPgain holds the Proportional gain
'           : PID_fIgain holds the Integral gain
'           : PID_fDgain holds the Derivative gain
'           : PID_fOutputMax holds the maximum output allowed
'           : PID_fOutputMin holds the minimum output allowed
' Output    : Returns the PID calculation
' Notes     : None
'
Proc PID_FloatCompute(pInput As Float, pReq As Float), PID_fResult
    Dim fCorr  As Float                                         ' Internal correction to be applied
    Dim fError As Float                                         ' Error observed

    fError = pInput - pReq                                      ' Find the error between what's required and what's just been input
    fCorr = fError * PID_fPgain                                 ' P factor
    PID_fErrorSum = PID_fErrorSum + fError                      ' Sum the error value
    If PID_fErrorSum > PID_fErrorMax Then                       ' Is the accumulative error greater than that allowed?
        PID_fErrorSum = PID_fErrorMax                           ' Yes. So make it maximum
    EndIf
    fCorr = fCorr + (PID_fErrorSum * PID_fIgain)                ' I factor
    fCorr = fCorr + ((PID_fPreviousInput - pInput) * PID_fDgain)' D factor
    PID_fPreviousInput = pInput                                 ' Save the current measurement
    fCorr = fCorr / 8.0                                         ' Scale the value
    If fCorr > PID_fCorrectionMax Then                          ' Is the correction greater than that allowed?
        fCorr = PID_fCorrectionMax                              ' Yes. So make it maximum
    EndIf
    Result = Result - fCorr                                     ' Subtract the correction from the output result
    If Result > PID_fOutputMax Then                             ' Trap maximum output
        Result = PID_fOutputMax
    ElseIf Result <= PID_fOutputMin Then                        ' Trap minimum output
        Result = PID_fOutputMin
    EndIf
EndProc

'------------------------------------------------------------------------
' Initialise the PID routine
' Input     : PID_fOutputMax =  Maximum output value
'           : PID_fOutputMin =  Minimum output value
'           : PID_fCorrectionMax =  Maximum correction to be applied
'           : PID_fErrorMax =  Maximum error sum allowed
'           : PID_fPgain =  Response factor for P
'           : PID_fIgain =  Response factor for I
'           : PID_fDgain =  Response factor for D
' Output    : None
' Notes     : Must be used at least once before the PID_FloatCompute procedure is used
'           : Uses a pre-processor meta-macro for ease of use
'
$define PID_Setup(pKP, pKI, pKD, pMaxCorrection, pMaxError, pMinOutput, pMaxOutput) '
    PID_fOutputMax = pMaxOutput          '
    PID_fOutputMin = pMinOutput          '
    PID_fCorrectionMax = pMaxCorrection  '
    PID_fErrorMax = pMaxError            '
    PID_fPgain = pKP                     '
    PID_fIgain = pKI                     '
    PID_fDgain = pKD                     '
    PID_fPreviousInput = 0               '
    PID_fErrorSum = 0                    '
    PID_fResult = 0

'------------------------------------------------------------------------
_PID_Main:
$endif       ' __PID_Float__

Name the above listing as "PID_Float.inc" and copy the file to the Includes folder: "C:\Users\User Name\PDS\Includes\", so all BASIC program files can see it. I have implemented the Result from the PID calculator procedure as an alias so it can be cleared before the PID routine is used for the very first time. The PID_Setup meta-macro clears the Result variable.

Below is a demonstration program for the above library that alters the voltage out based upon the voltage in:
'
'   /\\\\\\\\\
'  /\\\///////\\\
'  \/\\\     \/\\\                                                 /\\\          /\\\
'   \/\\\\\\\\\\\/        /\\\\\     /\\\\\\\\\\     /\\\\\\\\   /\\\\\\\\\\\  /\\\\\\\\\\\  /\\\\\\\\\
'    \/\\\//////\\\      /\\\///\\\  \/\\\//////    /\\\/////\\\ \////\\\////  \////\\\////  \////////\\\
'     \/\\\    \//\\\    /\\\  \//\\\ \/\\\\\\\\\\  /\\\\\\\\\\\     \/\\\         \/\\\        /\\\\\\\\\\
'      \/\\\     \//\\\  \//\\\  /\\\  \////////\\\ \//\\///////      \/\\\ /\\     \/\\\ /\\   /\\\/////\\\
'       \/\\\      \//\\\  \///\\\\\/    /\\\\\\\\\\  \//\\\\\\\\\\    \//\\\\\      \//\\\\\   \//\\\\\\\\/\\
'        \///        \///     \/////     \//////////    \//////////      \/////        \/////     \////////\//
'                                  Let's find out together what makes a PIC Tick!
'
' Floating Point PID routine Demonstration.
' Written by Les Johnson for the Positron8 BASIC compiler.
' https://sites.google.com/view/rosetta-tech/home
'
' Connect a potentiometer to AN0 to give the required value to reach(0 to 1023).
' Connect the output from CCP1 to AN1 through a low pass filter.
'
' Turning the pot connected to AN0, the output of the PWM should match it.
' Note that the PID routine will require tuning by adjusting P gain, I gain and D gain.
' As well as the maximum correction value allowed and the maximum error value allowed.
'
    Device = 18F25K20
    Declare Xtal = 64

    Include "Amicus18_ADC.inc"                  ' Load the PIC18F25K20 ADC routines into the program
    Include "Amicus18_HPWM10.inc"               ' Load the PIC18F25K20 10-bit HPWM routines into the program
    Include "PID_Float.inc"                     ' Load the Floating Point PID routines into the program
'
' Create some user variables
'
    Dim MyInput    As Word                      ' Input to the PID, which is the output from the sensor
    Dim MyRequired As Word                      ' The value that the PID is required to reach
    Dim MyOutput   As Word                      ' The output from the PID

'------------------------------------------------------------------------
' The main program loop starts here
'
Main:
'
' Open the ADC for 10-bit reading of AN0 and AN1
'
    OpenADC(ADC_FOSC_RC & ADC_RIGHT_JUST & ADC_2_TAD, ADC_REF_VDD_VSS, ADC_2ANA)
'
' Open the CCP1 channel for 10-bit PWM output
'
    OpenAnalog1()
'
' Setup the PID parameters
' (pKP, pKI, pKD, pMaxCorrection, pMaxError, pMinOutput, pMaxOutput)
'
    PID_Setup(2.0, 0, 0, 2.0, 0.5, 0, 1023)
'
' Create a loop to constantly adjust the PWM (CCP1) output depending on the ADC (AN1) input
'
    Do
        MyRequired = ADIn(0)                            ' Read the value required by the PID from AN0
        MyInput = ADIn(1)                               ' Read AN1 for the value to place into the PID
        MyOutput = PID_FloatCompute(MyInput, MyRequired)' Compute PID
        WriteAnalog1(MyOutput)                          ' Place the output of the PID onto the HPWM
        DelayMS 10                                      ' A small delay
    Loop                                                ' Do it forever

'--------------------------------------------------------------------------
' Configure the device to operate at 64MHz using a 16MHz crystal
'
Config_Start   
    FOSC = HSPLL        ' HS oscillator, PLL enabled and under software control
    STVREN = Off        ' Reset on stack overflow/underflow disabled
    WDTEN = Off         ' WDT disabled (control is placed on SWDTEN bit)
    FCMEN = Off         ' Fail-Safe Clock Monitor disabled   
    IESO = Off          ' Two-Speed Start-up disabled
    WDTPS = 128         ' Watchdog ratio is 1:128
    BOREN = Off         ' Brown-out Reset disabled in hardware and software
    BORV = 18           ' VBOR set to 1.8 V nominal
    MCLRE = On          ' MCLR pin enabled, RE3 input pin disabled
    HFOFST = On         ' The system clock is held off until the HF-INTOSC is stable.
    LPT1OSC = Off       ' Timer1 operates in standard power mode
    PBADEN = Off        ' PORTB<4:0> pins are configured as digital I/O on Reset
    Debug = Off         ' Background debugger disabled: RB6 and RB7 configured as general purpose I/O pins
    XINST = Off         ' Instruction set extension and Indexed Addressing mode disabled (Legacy mode)
    CCP2MX = PORTC      ' CCP2 input/output is multiplexed with RC1
    LVP = Off           ' Single-Supply ICSP disabled
    Cp0 = Off           ' Block 0 (000800-001FFFh) not code-protected
    CP1 = Off           ' Block 1 (002000-003FFFh) not code-protected
    CPB = Off           ' Boot block (000000-0007FFh) not code-protected
    CPD = Off           ' Data EEPROM not code-protected
    WRT0 = Off          ' Block 0 (000800-001FFFh) not write-protected
    WRT1 = Off          ' Block 1 (002000-003FFFh) not write-protected
    WRTB = Off          ' Boot block (000000-0007FFh) not write-protected
    WRTC = Off          ' Configuration registers (300000-3000FFh) not write-protected
    WRTD = Off          ' Data EEPROM not write-protected
    EBTR0 = Off         ' Block 0 (000800-001FFFh) not protected from table reads executed in other blocks
    EBTR1 = Off         ' Block 1 (002000-003FFFh) not protected from table reads executed in other blocks
    EBTRB = Off         ' Boot block (000000-0007FFh) not protected from table reads executed in other blocks
Config_End

The Floating Point PID calculations take approx 110 uS when the device is operating at 64MHz. Not bad at all for an 8-bit device written in BASIC. :-)

Below is a simulation screenshot of the above program working:

Float PID Demo.jpg

joesaliba

Thanks Les,

Will try it out.

Regards

Joe

JonW

Quote from: top204 on May 23, 2022, 08:31 AMTry making the return variable for the PID_Compute procedure an alias to a global variable

I just read the manual again after reading this post where the return parameter can even reference a SFR!  I can see a whole host of uses for this to create new commands that directly influence the SFR on a call to that procedure.  There is so much detail in the manual when you read it carefully. 

You should seriously consider crowd funding the Arm compiler, that way you can earn while you develop and market the product at the same time!  ;)

J

top204

Many thanks Jon.

The procedure mechanism I created does have a lot of potential and offers mechanisms that make coding so much more efficient.

I would not know where to start to crowd fund?