incPID Routine - Help Needed


Closed Thread
Page 1 of 2 12 LastLast
Results 1 to 40 of 64
  1. #1
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166

    Default incPID Routine - Help Needed

    Hi All,

    I am endeavouring to put together a closed loop motor speed controller using Henrick's "incPID" routine. The end use for this project is to re-power my 9x20 lathe using a treadmill motor (180Vdc 2HP). For my trial project I am using a 24Vdc 500W electric scooter motor. My ultimate goal will be to maintain constant motor speed under varying mechanical load conditions.

    I have read everything I could find on this forum related to PIC control and I believe what I am trying to achieve should be "do-able."

    I have successfully put together a program based on the 16F684. I have managed to write code that will read a speed pot setting via ADCIN (10 bit) on AN0 (Pin 13) and successfully drive the 24V motor via HPWM output on CCP1/P1A (Pin 5). The PWM signal is fed to the motor via a low-side MOSFET with a suitable transistor drive circuit. Having achieved this, I then went to the next step and tried to implement the PID routine. My code is as follows:

    Code:
    ' PicBasic Pro program to test incPIDv1.5 routine by
    ' Henrick Olsson using PIC16F684
    ' Name: incPID 16F684.pbp
    ' Date: 2nd June, 2012
    ' Author: Barry Phillips
    ' Connect analog input 1 to channel-0 (RA0) Pin 13
    ' Connect analog input 2 to channel-1 (RA1) Pin 12
    
    CMCON0 = %00000111  ' Disable comparators
    ANSEL = %00000011  ' Set AN0 and AN1 as analogue input, all other digital inputs
    ' Define ADCIN parameters
    Define ADC_BITS 10 ' Set number of bits in result
    Define ADC_CLOCK 3 ' Set clock source (3=rc)
    Define ADC_SAMPLEUS 50 ' Set sampling time in uS
    
    '***************** PID-Filter "external" variables *****************************
    '***** These are the variables that the main application uses. *****************
    '*******************************************************************************
    pid_Status var byte              'Status of the PID-filter can be read here.
    pid_Status_Out_Sat var pid_Status.7 'Flag for saturated output
    pid_Status_I_Sat var pid_status.6   'Flag for saturated Integral drive.
    pid_Kp Var Word                  'Proportional gain.                                     
    pid_Ki Var Word                  'Integral gain.
    pid_Kd Var Word                  'Derivative gain.
    pid_Vel_Kff VAR WORD                'Velocity feedforward gain, set to 0 if not used!
    pid_Acc_Kff VAR WORD                'Acceleration feeforward gain, set to 0 if not used!
    pid_Error Var Word               'PID Filter error input. (setpoint - actual)
    pid_Velocity_cmd VAR WORD           'Input of commanded velocity, set by the main application.
    pid_Out Var Word                 'PID Filter output (P + I + D + Vff + Aff)
    pid_Ti var BYTE                  'Time constant for the integral time
    pid_I_Clamp var word             'Clamp for the integrator windup.
    pid_Out_Clamp var word           'Clamp the final output to value in this var.
    '*******************************************************************************
    '******************* PID-Filter "internal" variables ***************************
    '*******************************************************************************
    pid_P Var Word                   'Proportional drive.
    pid_I VAR Word                   'Integral drive.
    pid_D Var Word                   'Derivative drive.
    pid_Vel_FF VAR WORD                 'Velocity feedforward drive.
    pid_Acc_FF VAR WORD                 'Acceleration feedforward drive.
    pid_LastVelocity_cmd VAR WORD       'Previous commanded velocity, automatically set by the PID routine.
    pid_Sign Var BIT                    'Keeps track of the sign of various values
    pid_LastError var word           'Last error, used for calculating D drive
    pid_Ei Var Word                  'Integral drive accumulator.
    pid_EiT VAR Word                    'Temporary calculation variable
    pid_IntCount Var Byte            'Counter for the integrator, matches against Ti
    '*******************************************************************************
    '************************** Initialise variables *******************************
    '*******************************************************************************
    pid_P = 0                            'Reset P, I, D & feedforward variables.
    pid_I = 0
    pid_D = 0
    pid_Velocity_cmd = 0
    pid_LastVelocity_cmd = 0
    pid_Kp = $0300                     'Set Kp to 3.0 (was $0700)
    pid_Ki = $0000                     'Set Ki to 0.0 (was $0080)
    pid_Kd = $0000                     'Set Kd to 0.0 (was $0225)
    pid_Ti = 8                         'Update I-term every 8th call to PID
    pid_I_Clamp = 100                  'Clamp I-term to max ±100
    pid_Out_Clamp = 511                'Clamp the final output to ±511
    pid_Vel_Kff = 0                    'Feedforward terms not used...
    pid_Acc_Kff = 0                    '... set feedforward parameters to 0
    ADValue VAR Word                    'Output from F-V (LM2907)
    Setpoint    Var Word                'Output from Speed Set Pot
    Direction   VAR PORTA.5             'Direction bit set to POTA.5 (Pin 2)
    
    '*******************************************************************************
    '*********************** ROUTINE STARTS HERE ***********************************
    '*******************************************************************************
    Start:
     ADCIN 0, Setpoint          'Read channel 0 to Setpoint
        ADCIN 1, ADValue                'Read channel 1 to ADValue
       
        pid_Error = Setpoint - ADValue  'Calculate the error
        
    '*******************************************************************************
    '********************* GOTO HENRICK'S PID ROUTINE ******************************
    '*******************************************************************************
        Gosub PID                          'Result returned in pid_Drive
    '*******************************************************************************
    '********************TEST FOR VALID DRIVE CONDITIONS ***************************
    '*******************************************************************************
        IF pid_Out.15 THEN              'If negative drive is called for...
            pid_out=0                   '...set output to 0
            pid_Ei=0                    'And keep I-term from accumulating
        ELSE                            'and if positive drive is called for...    
        pid_Out = ABS pid_Out           '...set it to absolute value of pid_Out
        ENDIF
    '*******************************************************************************
    '******************** SEND HPWM SIGNAL TO Channel 1 (Pin 5) ********************
    '*******************************************************************************
        HPWM 1, pid_Out, 10000          'Set PWM output
        Pause 10                        'Wait....
        Goto Start                      '...and do it again.
    
    PID:                                 'This is the entrypoint from the main app.
    '*******************************************************************************
    '****************************  P I D  F I L T E R  *****************************
    '*******************************************************************************
    'Calculate velocity feedforward
    pid_Vel_ff = (ABS pid_Velocity_cmd) */ pid_Vel_Kff      'Multiply absolute value of commanded velocity by the gain
    if pid_Velocity_cmd.15 then pid_Vel_FF = -pid_Vel_FF    'If commanded velocity is negative so is the feedforward
    
    'Calculate acceleration feedforward
    pid_Acc_FF = pid_Velocity_cmd - pid_LastVelocity_Cmd    'Current commanded speed - previous commanded speed is the acc.
    If pid_Acc_ff.15 then                                   'If the result is negative...
      pid_Acc_FF = (ABS pid_acc_FF) */ pid_Acc_Kff          'we take the ABSolute value and multiply by the gain
      pid_Acc_FF = -pid_Acc_FF                              'Then we restore the sign
    ELSE                                                    'Result is positive
     pid_Acc_ff = pid_Acc_FF */ pid_Acc_Kff                 'Multiply result by the gain
    ENDIF
    pid_LastVelocity_Cmd = pid_Velocity_Cmd                 'And remember current commanded velocity for next loop.
    
    'Calculate the proportional drive
    pid_P = (ABS pid_Error) */ pid_Kp                    'Multiply by the P-gain
    If pid_Error.15 then pid_P = -pid_P                     'Re-apply sign if pid_Error is neg
    
    'Calculate the Integral drive
    pid_Ei = pid_Ei + pid_Error                             'Add error to acumulator.
    pid_IntCount = pid_IntCount + 1                         'Increment the reset-time counter.
    If pid_IntCount >= pid_Ti then                          'Is it time to update the I-term?
        pid_EiT = pid_Ei                                    'Copy accumulator
        pid_Sign = pid_EiT.15                               'Save Sign
        pid_EiT = ABS pid_EiT                               'Work with positive numbers
        pid_EiT = pid_EiT */ pid_Ki                         'Multiply by Ki gain
        pid_EiT = pid_EiT / pid_Ti                          'Divide by the reset time
        If pid_Sign then pid_EiT = -pid_EiT                 'Re-apply sign
        pid_I = pid_I + pid_EiT                             'Update I drive
        pid_Sign = pid_I.15                                 'Save Sign
        pid_I = ABS pid_I                                   'Work with positive numbers
        if pid_I >= pid_I_Clamp then                        'I-term is saturated.... 
            pid_Status_I_Sat = 1                            'set pid_I_Clamp flag....
            pid_I = pid_I_Clamp                             'and clamp I-term to what user have set.
        Endif
        If pid_Sign then pid_I = -pid_I                     'Re-apply sign
        pid_IntCount = 0                                    'Reset the counter.
        If pid_EiT > 0 then  
           pid_Ei = 0                                       'Reset the accumulator.
        ENDIF
    Endif
    'Calculate the derivative drive       
        pid_D = pid_Error - pid_LastError
        pid_Sign = pid_D.15                                 'Save Sign
        pid_D = (ABS pid_D) */ pid_Kd                       'Multiply by Kd gain
        If pid_Sign then pid_D = -pid_D                     'Re-apply sign.
    DerivDone:
            pid_LastError = pid_Error                       'Store error for next D calc.
    '*******************************************************************************
    'Calculate the total drive.
    pid_Out = pid_Acc_FF + pid_Vel_FF + pid_P + pid_I + pid_D
    pid_Sign = pid_Out.15                                   'Save Sign
    pid_Out = ABS pid_Out                                   'Convert from two's comp. to abs.
    If pid_Out > pid_Out_Clamp then                         'If output is saturated...
        pid_Status_Out_Sat = 1                              'set status bit and...
        pid_out = pid_Out_Clamp                             'clamp output.
    Endif
    If pid_Sign then pid_out = -pid_out                     'Re-apply sign.
    RETURN                                                  'And return to sender.
    I have also attached a copy of the circuit diagram
    [attach]incPID_16F684[/attach]

    Obviously, I am not having the success I want - otherwise I would not be telling you all this
    I have tried to simplify my setup by eliminating the feedforward terms and by setting the integral and differential terms to zero. In essence, I am looking purely at the proportional control in the first instance and, once that is under control, I propose to tweak the system using the other available terms.

    As shown in the code, I have set pid_Out for single quadrant control by setting any potential negative drive to zero. I have made various attempts using values of Kp ranging from $0100 to $0900 but in all cases I only get varying rates of "bang-bang" output (ie output switching from zero to pid_Out_Clamp). I thought of looking at some of the variables on a 16x2 LCD but decided this might create its own set of timing problems and decided against it. I am not sure where to go or what to do next. Does anyone have any suggestions?

    One thing that has always confused/concerned me with the HPWM statement in this program; pid_Out is a WORD variable (16 bits) but the PBP manual states that duty cycle in the HPWM command only ranges from 0-255 (8 bits). Am I missing something here?

    For the record, I am using PBP 3.0.1.1, MCS 5.0.0.0 and the Melabs U2 programmer.

    I would greatly appreciate any assistance to help resolve my problems with this code.

    Cheers
    Barry (VK2XBP)
    Sydney, Australia
    Attached Images Attached Images

  2. #2
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Two things I have realised since sending the initial post:

    1/. In the circuit diagram, U3 should be 7805, not 7812
    2/. I forgot to mention that my feedback is gained from a photo-interrupter/encoder wheel configuration mounted to the motor shaft. This feeds an LM2907 - Frequesncy to Voltage chip. The output from the LM2907 is adjusted to give 0-5V (stop - full throttle).

    Cheers
    Barry

  3. #3
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi,
    You seem to be approcahing the project in all the correct ways, what you describe is most certainly doable and I honestly can't see why the output would swing between the extreme ends like you explain, especially with a gain of only $0100.

    Can you replace the feedback voltage with another pot? Then, with a gain of $0100, you should be able to adjust the output with EITHER the setpoint pot or the feedback pot. Dissconnect the motor and just observe the output untill you get that working. I'd also try to get the variables output etiher to a LCD like you say or over a serial line to the PC.

    One thing I do see though is that you have the pid_Out_Clamp set to 511 but as you say the HPWM command only accepts a dutycycle of 8 bits (0-255) so I'd set the pid_Out_Clamp variable to 255. You could always try copying pid_Out to a BYTE-sized variable and use that varaible in the HPWM statement but I don't think that is the real problem.

    Hang in there!

    /Henrik.

  4. #4
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Henrik,

    I have had a high degree of success with this project today.
    I disconnected the motor and replaced the feedback voltage with another pot as you suggested. I was able to get some very nice PWM signals showing on the scope. I think a lot of my problems last night were caused by a dodgy MOSFET. It failed under load this morning and the whole system fired up nicely once it was replaced.

    To tune the system I added two more pots; one for Kp and one for Kd. I also connected the LCD which helped me greatly in seeing what was happening. The motor I am using doesn't have alot of inertia so Kp and Kd terms are relatively small for a tightly tuned system response. So far, so good.

    I am a little confused with your velocity feedforward section. pid_velocity_cmd is initially set to zero and your program comment states that this variable is "set by the main program." From what I can find, this variable is only used in the calculation for pid_Vel_ff and pid_Acc_FF. If this is the case, it will always remain zero (as will pid_Vel_FF, pid_Acc_FF and pid_LastVelocity_Cmd). Am I missing something?

    For my trials I ended up setting pid_Vel_FF manually just so the output could overcome what little insertia the system does have.

    There is still a fair bit left to do on this project but before I do much more I need to transfer this circuit from my breadboard to a PCB. I am sure a lot of the instability I am seeing on my scope can be attributed to the breadboard (switching currents, ground loops etc.)

    Your return comments on the feedforward situation would be greatly appreciated.

    Cheers
    Barry

  5. #5
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Barry,
    Excellent news, I'm glad you're moving forward!

    If you're going to use the feedforward terms you need to set pid_Velocity_cmd in your program before you call the pid filter routine, in the same way that you must set pid_Error. In your particular case you'd probably just do something like
    Code:
    pid_Error = Setpoint - ADValue
    pid_Velocity_Cmd = Setpoint
    GOSUB PID
    pid_Velocity_Cmd is the "input variable" for the feedforward terms. pid_Vel_FF, pid_Acc_FF and pid_LastVelocity_Cmd are "internal" variables and you should not need to worry about those.

    In my own use of the filter the setpoint isn't velocity but position so I needed to have a separate input variable for the feedforward. My program then calculates the velocity based on the difference in commanded position between each concecutive loop.

    I hope that makes sense, otherwise let me know and I'll try again.

    /Henrik

  6. #6
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    3,807


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Barry.
    I was looking your schematic and if I may suggest, replacing Q1, Q2, R16 and R17 with a Mosfet Driver chip, like microchip's, would give you much better drive to the mosfet Q3.

    If you have a scope, look at node of the two emitters. If it shows that the two transistors are both conducting for a short time you may be waisting energy and heat the drivers. Also the edge that drives the Mosfet may not be very steep.

    Ioannis

  7. #7
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Ioannis,

    Thanks for your input, much appreciated.
    As previously stated, this is the first stage of a much larger project - to re-power my lathe with a 180Vdc 2HP treadmill motor. Knowing that the final design will involve isolation between control and motor drive stages, I decided to replace my transistor gate drive circuit with an opto-isolated MOSFET driver (FOD3180). Also, I was experiencing lots of noise on my breadboard layout so keeping powr supplies isolated in my prototype development PCB can only be a benefit.

    Attached is a copy of my new circuit diagram:

    incPID_16F684_V4.pdf

    JP1 & JP2 allow me to select one of two feedback sources; the analog output from the LM2907 (0-5V) or a pulse train from the shaft encoder (0-5.5kHz 50% duty cycle). My preference will be to use the pulse train and merely count pulses in my program to determine rotation speed but it was easy enough to fit both into my PCB design.

    I have used multi-turn pots (VR2-VR6) to adjust the gain for the five different PID terms so, theoretically, I should be able to tune the system "on the fly". I will be able to display variables on the 4x20 LCD which will help me "see" what is happening.

    The PCB (165mm x 115mm) has been exposed and etched and is ready for drilling and populating - hopefullt this will be completed tonight. I will keep you updated with more news soon.

    Cheers
    Barry

  8. #8
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    3,807


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Just an observation. The shaft encoder is ready made or are you using an opto interrupter with a disc etc?

    If the latter is the case be sure that it can detect pulses of 5.5KHz.

    I think it is better to use directly the train pulse and feed a timer, TMR1 for example.

    Better accuracy, less parts.

    Ioannis
    Last edited by Ioannis; - 12th June 2012 at 08:11.

  9. #9
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    The shaft encoder is a photo interrupter with a disc - 100 steps per revolution.
    I made the disc myself so I can adjust the number of steps quite easily.
    I did a sanity check on the shaft encoder but counting the number of steps per second using a simple COUNT routine and displaying the result on the LCD. I then compared the result with a non-contact hand-held digital tacho. The two results were near identical - I was very happy with the accuracy of my system.

    I have just finished assembling the PCB (see attached) and hope to do some testing on it tonight.


    Name:  DSC_0969_small.JPG
Views: 2154
Size:  93.7 KB

    Cheers
    Barry

  10. #10
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    3,807


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Looks nice. An a big motor!

    Wish you good luck Barry.

    Ioannis

  11. #11
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    My experimants with the PID controller have been going extremely well.
    I have removed the "feed forward" terms from the program (adjusting P, I and D terms is more than enough for me to deal with) and I have taken the "display variables" routine out of the PID loop, replacing it with an "If...Then" statement based on the state is a push button connectred to RC1 (Pin 9).

    My current circuit diagram is incPID_16F684_V5.pdf

    My current program is as follows:

    Code:
    ' Name: incPID_16F684_v3.pbp
    ' Date: 14th June, 2012
    ' Author: Barry Phillips
    
    ' AN0 (Pin 13) = Set Point
    ' AN1 (Pin 12) = Kp
    ' AN2 (Pin 11) = Kd
    ' AN4 (Pin 10) - Ki
    ' RA5 (Pin 2) = LCD Out
    ' RA4 (Pin 3) = Feedback Input (Tacho)
    ' CCP1 (Pin 5) = HPWM Ouptut
    ' RC1 (Pin 9) = Display Button
    ' Define DEBUG registers and bits
    DEFINE  DEBUG_REG   PORTA
    DEFINE  DEBUG_BIT   5
    DEFINE  DEBUG_BAUD  9600
    DEFINE  DEBUG_MODE  1
    
    CMCON0 = %00000111  ' Disable comparators
    ANSEL = %01010111  ' Set AN0, AN1, AN2, AN4 and AN6 as analogue inputs
    TRISA = %00010111     ' Set PORTA.0 - PORTA.4 to input
    TRISC = %00000010     ' Set PORTC.1 as input
    ADCON1 = %01010000     ' Set A/D clock Fosc/16
    ADCON0 = %10000001     ' Right justify result
          ' Set Vref = VDD
          ' Select channel AN0
          ' Turn on A/D converter
    OSCCON = %01110101      ' Set clock frequency to 8MHz
    define OSC  8
                            
    ' Define ADCIN parameters
    Define ADC_BITS 10 ' Set number of bits in result
    Define ADC_CLOCK 3 ' Set clock source (3=rc)
    Define ADC_SAMPLEUS 50 ' Set sampling time in uS
    Pause 1000
    '***************** PID-Filter "external" variables *****************************
    '***** These are the variables that the main application uses. *****************
    '*******************************************************************************
    pid_Status var byte              'Status of the PID-filter can be read here.
    pid_Status_Out_Sat var pid_Status.7 'Flag for saturated output
    pid_Status_I_Sat var pid_status.6   'Flag for saturated Integral drive.
    pid_Kp_low  var word                
    pid_Kp_high var word
    SO  con 5
    pid_Kp Var Word                  'Proportional gain.                                     
    pid_Ki Var Word                  'Integral gain.
    pid_Kd Var Word                  'Derivative gain.
    pid_Vel_Kff VAR WORD                'Velocity feedforward gain, set to 0 if not used!
    pid_Acc_Kff VAR WORD                'Acceleration feeforward gain, set to 0 if not used!
    pid_Error Var Word               'PID Filter error input. (setpoint - actual)
    pid_Velocity_cmd VAR WORD           'Input of commanded velocity, set by the main application.
    pid_Out Var Word                 'PID Filter output (P + I + D + Vff + Aff)
    pid_Ti var BYTE                  'Time constant for the integral time
    pid_I_Clamp var word             'Clamp for the integrator windup.
    pid_Out_Clamp var word           'Clamp the final output to value in this var.
    '*******************************************************************************
    '******************* PID-Filter "internal" variables ***************************
    '*******************************************************************************
    pid_P Var Word                   'Proportional drive.
    pid_I VAR Word                   'Integral drive.
    pid_D Var Word                   'Derivative drive.
    pid_Vel_FF VAR WORD                 'Velocity feedforward drive.
    pid_Acc_FF VAR WORD                 'Acceleration feedforward drive.
    pid_LastVelocity_cmd VAR WORD       'Previous commanded velocity, automatically set by the PID routine.
    pid_Sign Var BIT                    'Keeps track of the sign of various values
    pid_LastError var word           'Last error, used for calculating D drive
    pid_Ei Var Word                  'Integral drive accumulator.
    pid_EiT VAR Word                    'Temporary calculation variable
    pid_IntCount Var Byte            'Counter for the integrator, matches against Ti
    '*******************************************************************************
    '************************** Initialise variables *******************************
    '*******************************************************************************
    pid_P = 0                            'Reset P, I, D & feedforward variables.
    pid_I = 0
    pid_D = 0
    pid_Velocity_cmd = 0
    pid_LastVelocity_cmd = 0
    pid_Kp = 128                     'Set Kp (was $0700)
    pid_Ki = 64                      'Set Ki (was $0080)
    pid_Kd = 118                      'Set Kd (was $0225)
    pid_Ti = 1                       'Update I-term every 5th call to PID
    pid_I_Clamp = 200                 'Clamp I-term to max 200
    pid_Out_Clamp = 255              'Clamp the final output to 255
    pid_Vel_Kff = 0                  'Feedforward terms not used...
    pid_Acc_Kff = 0                  '... set feedforward parameters to 0
    ADValue     VAR Word             'Output Tacho
    Setpoint    Var Word             'Output from Speed Set Pot
    
    debug $FE, $80 + 5, "BAZTRONICS"
    DEBUG $FE, $94 + 2, "PID Motor Speed"
    debug $FE, $D4 + 3, "Controller v3"
    '*******************************************************************************
    '*********************** ROUTINE STARTS HERE ***********************************
    '*******************************************************************************
    Start:
     ADCIN 0, Setpoint          'Read channel 0 to Setpoint
    If PORTC.1 = 0 then
        adcin 1, pid_Kp                 'Read channel 1 to pid_Kp
        adcin 2, pid_Kd                 'Read channel 2 to pid_Kp
        adcin 4, pid_Ki                 'Read channel 4 to pid_Kp
        DEBUG $FE, $C0, "Kp: ", dec pid_Kp, "            "        
        DEBUG $FE, $94, "Kd: ", dec pid_Kd, "            "
        DEBUG $FE, $D4, "Ki: ", dec pid_Ki, "   ", dec ADValue*6, " rpm    "
    Endif
        Count PORTA.4,   100, ADValue
        pid_Error = Setpoint - ADValue  'Calculate the error
      
    '*******************************************************************************
    '************************** GOTO PID ROUTINE ***********************************
    '*******************************************************************************
        Gosub PID                          'Result returned in pid_Drive
    '*******************************************************************************
    '********************TEST FOR VALID DRIVE CONDITIONS ***************************
    '*******************************************************************************
        IF pid_Out.15 THEN              'If negative drive is called for...
            pid_out=0                   '...set output to 0
            pid_Ei=0                    'And keep I-term from accumulating
        ELSE                            'and if positive drive is called for...    
            pid_Out = ABS pid_Out       '...set it to absolute value of pid_Out
        ENDIF
    '*******************************************************************************
    '******************** SEND HPWM SIGNAL TO Channel 1 (Pin 5) ********************
    '*******************************************************************************
        HPWM 1, pid_Out, 10000          'Set PWM output
    '    Pause 10                        'Wait....
        Goto Start                      '...and do it again.
    
    PID:                                'This is the entrypoint from the main app.
    '*******************************************************************************
    '****************************  P I D  F I L T E R  *****************************
    '*******************************************************************************
    'Calculate the proportional drive
    pid_P = (ABS pid_Error) */ pid_Kp       'Multiply by the P-gain
    If pid_Error.15 then pid_P = -pid_P     'Re-apply sign if pid_Error is neg
    
    'Calculate the Integral drive
    pid_Ei = pid_Ei + pid_Error             'Add error to acumulator.
    pid_IntCount = pid_IntCount + 1         'Increment the reset-time counter.
    If pid_IntCount >= pid_Ti then          'Is it time to update the I-term?
        pid_EiT = pid_Ei                    'Copy accumulator
        pid_Sign = pid_EiT.15               'Save Sign
        pid_EiT = ABS pid_EiT               'Work with positive numbers
        pid_EiT = pid_EiT */ pid_Ki         'Multiply by Ki gain
        pid_EiT = pid_EiT / pid_Ti          'Divide by the reset time
        If pid_Sign then pid_EiT = -pid_EiT 'Re-apply sign
        pid_I = pid_I + pid_EiT             'Update I drive
        pid_Sign = pid_I.15                 'Save Sign
        pid_I = ABS pid_I                   'Work with positive numbers
        if pid_I >= pid_I_Clamp then        'I-term is saturated.... 
            pid_Status_I_Sat = 1            'set pid_I_Clamp flag....
            pid_I = pid_I_Clamp             'and clamp I-term to what user have set.
        Endif
        If pid_Sign then pid_I = -pid_I     'Re-apply sign
        pid_IntCount = 0                    'Reset the counter.
        If pid_EiT > 0 then  
           pid_Ei = 0                       'Reset the accumulator.
        ENDIF
    Endif
    'Calculate the derivative drive       
        pid_D = pid_Error - pid_LastError
        pid_Sign = pid_D.15                 'Save Sign
        pid_D = (ABS pid_D) */ pid_Kd       'Multiply by Kd gain
        If pid_Sign then pid_D = -pid_D     'Re-apply sign.
    DerivDone:
            pid_LastError = pid_Error       'Store error for next D calc.
    '*******************************************************************************
    'Calculate the total drive.
    pid_Out = pid_P + pid_D + pid_i
    pid_Sign = pid_Out.15                   'Save Sign
    pid_Out = ABS pid_Out                   'Convert from two's comp. to abs.
    If pid_Out > pid_Out_Clamp then         'If output is saturated...
        pid_Status_Out_Sat = 1              'set status bit and...
        pid_out = pid_Out_Clamp             'clamp output.
    Endif
    If pid_Sign then pid_out = -pid_out     'Re-apply sign.
    RETURN                                  'And return to sender.
    One thing I have noticed is that the PWM output jitters, even when the motor is operating with no mechanical load. The variation is not extreme but I would like to understand why it is happening. The jitter is more noticable at lower PWM duty cycles. I suspect it may have something to do with the COUNT statement used to get ADValue (ie the number of pulses from the shaft encoder in 100ms). Is there a better way of doing this? Perhaps Darrel's interrupt routine should be used - if so, what interrupt handler and how should it be implemented? The pulse train from the photo-interrupter is relatively clean but only swings from about 0.6V to 4V. I plan to try and clean up this signal by feeding it through a schmitt trigger.

    My final thought for this project is to include a motor speed display; RPM updated every 100ms using ADValue as the data source. My concern is that the delay associated with sending this information to the display may cause some issues with the response time of the PID routine. Am I asking too much of the 16F684 to handle PID routine and the display routine concurrently?

    All comments and suggestions are gratefully welcomed.

    Cheers
    Barry
    VK2XBP

  12. #12
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Barry,
    Any jitter on the input (ADValue variable in this case) or in the time between sampling the input will cause jitter on the output. If there's jitter on the input variable the motor probably IS changing speed slightly as I think the time that COUNT statement is actually counting pulses is pretty stable. However the time between samples may vary a bit in this case since you rely on the time of execution of the rest of the code.

    Any jitter in the input signal gets "amplified" by the gain of the filter, especially the P and D gain. If you have P gain of 5 and a D gain of 10 then a difference of '1' on the input will cause the output jump '15' from one sample to the other. Now you're running very low gains so it shouldn't be too sensitive.

    If the time between samples varies then a difference of x counts means different things. Lets say the count is 100, next sample it's 110 and the next it's 120. Now, lets say that the time between samples 1 and 2 is longer than between samples 2 and 3. This means that the motor is actually accelerating "harder" between samples 2 and 3 (the increase in number of pulses was the same but during a shorter period of time) but the filter doesn't know this.

    The execution time of the PID routine may not be the same every time thru it and your DEBUG statements, when executed, will take quite a bit of time.

    You're running the PID loop at less than 10Hz, I think that's a bit slow - I'd probably run atleast 10 times faster than that. (For my own motorcontrol project I'm running it at 1220-2800Hz).

    What I'd do is to use DT-Ints to set up a timer interrupt at around 100Hz to begin with. I'd feed the pulses to another timer/counter and set that up to count the pulses in hardware instead of using the COUNT command. Each time the interrupt fires it grabs the count from the timers registers and subtracts the "last" count from it to get the "new" count. Then it feeds that to the PID routine.

    Outputting data to the display or PC should be handled by the main routine. Unfortunatly the '684 doesn't have a UART so you're forced to use the bitbanged routine (SEROUT, DEBUG etc). If an interrupt fires during one of these statements the timing will get messed up and corrupt the data. Because of this I'd try to set up buffer and a system of semaphores (free_to_send, data_to_send, sending_data) and have it send one or two bytes between each interrupt to make sure it's got enough time to spit the bits out before the next interrupt.

    /Henrik.

  13. #13
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Henrik,

    Thank you once again for your very detailed response. Much appreciated.

    I understand that my PID loop is running slow and appreciate that it would be best "to count the pulses in hardware instead of the COUNT command" BUT I need some direction on how to make this happen. I have had zero experience using timer interrupts (though I have worked with Darrel's DT-Interrupt for IOC). Can you walk me through the process or direct me to a previous thread which explains the process? I am really struggling to get my head around timer registers, where and how the pulses are stored and what needs to be done to retrieve them for implementation with the PID routine.

    As far as the data outputting is concerned, I am quite happy to live with my "display on button press" routine just to see the filter gain terms after adjustment. I haven't played with a UART before so that will become a whole new learning curve for me. For the time being, I will be happy just to get the PID routine nice and stable. I can always program another PIC just to handle the RPM output routine...

    Cheers
    Barry
    VK2XBP

  14. #14
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Barry,
    Lets take it one step at the time and see what we get.

    The '684 has three timers, TMR0, TMR1 and TMR2. Since you're using the ECCP module to generate PWM (thru the HPWM command) it occupies TMR2 so we can't use that for anything else. That leaves us TMR0 and TMR1.

    Looking at the datasheet sections for TMR0 and TMR1 we can see that they both can be configured as either a timer or as counter so we just need to decide which to use as our counter. TMR0 is only 8 bits so if we're going to use that as our counter we need to make sure to not overflow it. If you have 100 counts per revolution and 2500rpm we're getting 4167 counts per second. If we were to run the PID loop at 10Hz we'd get 416 counts between updates which clearly overflows the 8bit counter, we'd need to either lower the input frequency or increase the update rate - you get the idea.

    Looking at the datasheet section for TMR0 we can see that it's controlled thru the 6 lower bits in OPTION_REG. All we really need to do to set up as a counter is to set the T0CS bit (OPTION_REG.5 = 1). Now TMR0 will count the pulses comming in on the T0CKI pin (pin 11).
    Code:
    TRISA.2 = 1		' Make TOCKI input
    OPTION_REG.0 = 0	' Prescaler 1:1
    OPTION_REG.1 = 0
    OPTION_REG.2 = 0
    OPTION_REG.3 = 1	' Precaler assigned to WDT
    OPTION_REG.4 = 0	' Increment in low-to-high transition
    OPTION_REG.5 = 1	' Clock on T0CKI-pin
    OPTION_REG.6 = 0
    OPTION_REG.7 = 1
    At this point all you need to do is to read the count out of the TMR0 register:
    Code:
    newCount VAR BYTE
    newCount = 0
    Main:
      newCount = TMR0 - newCount
      DEBUG "TMR0 count: ", #TMR0, 13
      DEBUG "Count since last time: ", #newCount, 13, 13
      Pause 1000
    Goto Main
    Try this and/or a variation of it to see if you can get TMR0 to count the pulses in your application.

    One final note, the idea with the incPID routine was for it be INCLUDEd in a program, like yours. There's nothing wrong with copy/pasting it like you've done but it may distract the view from your actual application.

    /Henrik.

  15. #15
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Thanks Henrik.

    I will need to wade through this tomorrow...oops, it is already tomorrow!

    I will get back to you after I have had some time to digest it and put it into action

    Cheers
    Barry
    VK2XBP

  16. #16
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Henrik,

    I loaded the following code into my'684 and wired it up on my breadboard with a 2 x 16 LCD.
    Code:
    ' Define LCD registers and bits
    
    Define LCD_DREG PORTC
    Define LCD_DBIT 0
    Define LCD_RSREG PORTA
    Define LCD_RSBIT 5
    Define LCD_EREG PORTC
    Define LCD_EBIT 4
    Define LCD_BITS 4
    Define LCD_LINES 2
    
    CMCON0 = %00000111  ' Disable comparators
    ANSEL = %00000000  ' Set PORTA as digital I/O
    TRISC = 0               ' Set PORTC as output
    TRISA.1 = 0             ' Make TOCKI input
    OPTION_REG.0 = 0        ' Prescaler 1:1
    OPTION_REG.1 = 0        ' 
    OPTION_REG.2 = 0        '
    OPTION_REG.3 = 1        ' Prescaler assigned to WDT
    OPTION_REG.4 = 0        ' Increment on low-to-high transition
    OPTION_REG.5 = 1        ' Clock on T0CKI-pin
    OPTION_REG.6 = 0        '
    OPTION_REG.7 = 1        '
    OSCCON = %01110101      ' Set clock frequency to 8MHz
    define OSC  8
    newCount    var byte
    newCount = 0
     
    Main:
        newCount = TMR0 - newCount
        lcdout $FE, 1
        LCDOUT "TMR0 Count: ", #TMR0
        LCDOUT $FE, $C0
        LCDOUT "Count since: ", #newCount
        Pause 1000
    Goto Main
    I adjusted the speed of the motor such that the pulse train frequency from the photo interruptor was under 200Hz.
    Each loop of the main program gives me a valid reading for #TMR0 and #newCount but there is no correlation between the numbers.

    To be honest, I don't really see how there could be. TMR0 doesn't appear to be reset anywhere so it should just roll over from 255 to 0 each time the maximum count is reached.

    When/how does TMR0 start and stop counting?

    You did say in your last post that we would take this "one step at a time."
    First step completed - we are now counting pulses. What next?

    If I understand the process correctly, we need to set up one timer (say TMR0) as an accurate interval timer (say 10ms) and the other timer (TMR1) as a counter for the pulses. As the interval timer ends its cycle, the value of the counter is returned as ADValue for the PID routine. Am I on the right path here?

    I look forward to your "next step" suggestions.

    Cheers
    Barry
    VK2XBP

  17. #17
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Barry,
    TMR0 starts counting as soon as you set to BE a counter (OPTION_REG.5=1) and it will keep counting the incoming pulses. It will overflow att 255->0. The idea was that, because both newCount and TMR0 is a BYTE, that it will/should automatically give you the correct result but I think I may have made a little mistake there.

    Lets try this instead:
    Code:
    newCount VAR BYTE
    oldCount VAR BYTE
    newCount = 0
    oldCount = 0
    
    Main:
      newCount =  TMR0 - oldCount
      oldCount = TMR0
      LCDOUT "TMR0 Count: ", #TMR0
      LCDOUT $FE, $C0
      LCDOUT "Count since: ", #newCount
      Pause 1000
    Goto Main
    When we have TMR0 counting pulses reliably we'll move to the next step which will be set TMR1 up to generate an interrupt periodically (using DT Ints). This interrupt will then "capture" the current count, calculate the "speed" using the above code and run the PID-loop. But lets make sure we have TMR0 and the code above working properly first. Basically it should now display the frequency of the incoming signal but the timebase will be a bit off due to LCDOUT statements taking a bit of time on top of the PAUSE 1000.

    /Henrik.

  18. #18
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    All good Henrik.
    It is nice and stable and giving me a good frequency output.
    I verified the result by inputing a 10Hz and then 100Hz square wave from my signal generator and the result was within 1Hz.

    Let's go to Step 2

    Cheers
    Barry
    VK2XBP

  19. #19
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Great! Now lets put that on hold for a bit and see if we can get a timer interrupt going - then we'll glue it together.
    I am a bit worried about the somewhat limited amount of RAM on '684 but we'll just have to see. I won't try to go into too much detail about DT-Ints, there's been several threads and several examples on the forum and obviosuly on Darrels site. If you don't already have the needed files you get them here. Place them either in the project directory or in the root folder of your PBP installation.

    TMR1 is a 16bit timer and it causes an interrupt (if enabled) at rollover from $FFFF to 0. If we would let the timer free-run we'd get an interrupt every 65536th instruction cycle. At 8Mhz, that would be every 32.768ms or a frequency of ~30.5 Hz. If we want any other frequency we need to make sure that the timer doesn't start at 0 but at some other value. This is done by preloading the timer inside the interrupt service routine.

    At 8Mhz one instruction cycle is 500ns. If we want an interrupt frequency of 100Hz we need the timer to roll over every 10ms, or once every 20000 instruction (20000*500ns=10ms). Because the timer interrupt when it rolls over we use 65536-20000=45536.


    OK, now the code:
    Code:
    ' Included the interrupt system
    INCLUDE "DT_INTS-14.bas"
    INCLUDE "ReEnterPBP.bas"
    
    ' When compiling for the 16F684 DT-Ints tells us to add these variables so we do that
    wsave VAR BYTE $20 SYSTEM
    wsave1 VAR BYTE $A0 SYSTEM
    
    TMR1_Reload CON 45536           ' Reload value for ~100Hz interrupt frequency at 8MHz, prescaler 1:1
    
    TMR1_Temp VAR WORD              ' Temporary variable use for reloading the timer
    IntCount VAR BYTE               ' Keep track of number of interrupts
    oldCount VAR BYTE
    newCount VAR BYTE
    Frequency VAR WORD
    UpdateDisplay VAR BIT
    
    LED1 VAR PortC.4
    LED2 VAR PortC.5
    
    ASM
    INT_LIST  macro    ; IntSource,        Label,  Type, ResetFlag?
            INT_Handler   TMR1_INT,  _ISR,   PBP,  yes
        endm
        INT_CREATE            ; Creates the interrupt processor
    ENDASM
    
    T1CON.0 = 1                       ' Start TMR1
    @   INT_ENABLE  TMR1_INT  ; Enable Timer 1 Interrupts
    
    Main:
        Toggle LED1
        Pause 1000
        Goto Main
    
     
    ' This is the interrupt routine for the TMR1 interrupt.
    ISR:
        T1CON.0 = 0                         ' Stop timer
        TMR1_Temp.HighByte = TMR1H          ' Get current "time"
        TMR1_Temp.LOWBYTE = TMR1L
        TMR1_Temp = TMR1_Temp + TMR1_Reload ' Add reload value to get correct interval
        TMR1H = TMR1_Temp.HIGHBYTE          ' Move result back to timer 
        TMR1L = TMR1_Temp.LOWBYTE
        T1CON.0 = 1                         ' Start timer
    
        If IntCount = 19 THEN
            Toggle LED2
            IntCount = 0
        ENDIF
    @ INT_RETURN
    The main routine in the above code will blink LED1 at 0.5Hz. The timer interrupt should cause LED2 to blink at ~5Hz. The code compiles here but I haven't tested it. Obviosuly you need to add your CONFIG, TRIS, ANSEL, CMCON, whatever at the top too.

    Give it a go, see if you can calculate and set the desired frequency.

    /Henrik.

  20. #20
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Thanks Henrik.

    It looks good but I can't see how LED2 will ever toggle. I think there should be a line
    IntCount = IntCount + 1
    somewhere within the interrupt routine.

    Anyway, I will have a play with it and let you know how I go.

    Cheers
    Barry
    VK2XBP

  21. #21
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Definitely so. Somehow that fell out of there.....
    Why do you need me for this ? ;-)

  22. #22
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    3,807


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Nice job Henrik.

    LED1 will toggle LED1 every 1 second not 0.5 (based on correct 8MHz clock).

    Ioannis

  23. #23
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Henrik,

    All good so far - Step 2 complete.
    I have the leds blinking and have modified the code to display Frequency (averaged over a ten cycle loop) on the LCD as follows:

    Code:
    ' Define LCD registers and bits
    Define	LCD_DREG	PORTC
    Define	LCD_DBIT	0
    Define	LCD_RSREG	PORTA
    Define	LCD_RSBIT	5
    Define	LCD_EREG	PORTC
    Define	LCD_EBIT	4
    Define	LCD_BITS	4
    Define	LCD_LINES	2
    
    
    CMCON0 = %00000111		' Disable comparators
    ANSEL = %00000000		' Set PORTA as digital I/O
    TRISC = 0               ' Set PORTC as output
    TRISA.1 = 0             ' Make TOCKI input
    OPTION_REG.0 = 0        ' Prescaler 1:1
    OPTION_REG.1 = 0        ' 
    OPTION_REG.2 = 0        '
    OPTION_REG.3 = 1        ' Prescaler assigned to WDT
    OPTION_REG.4 = 0        ' Increment on low-to-high transition
    OPTION_REG.5 = 1        ' Clock on T0CKI-pin
    OPTION_REG.6 = 0        '
    OPTION_REG.7 = 1        '
    
    OSCCON = %01110101      ' Set clock frequency to 8MHz
    define OSC  8
    
    
    ' Included the interrupt system
    INCLUDE "DT_INTS-14.bas"
    INCLUDE "ReEnterPBP.bas"
    
    ' When compiling for the 16F684 DT-Ints tells us to add these variables so we do that
    wsave VAR BYTE $20 SYSTEM
    wsave1 VAR BYTE $A0 SYSTEM
    
    TMR1_Reload CON 45536           ' Reload value for ~100Hz interrupt frequency at 8MHz, prescaler 1:1
    
    TMR1_Temp VAR WORD              ' Temporary variable use for reloading the timer
    IntCount VAR BYTE               ' Keep track of number of interrupts
    oldCount VAR BYTE
    newCount VAR BYTE
    Frequency VAR WORD
    UpdateDisplay VAR BIT
    
    LED1 VAR PortA.0
    LED2 VAR PortA.1
    
    ASM
    INT_LIST  macro    ; IntSource,        Label,  Type, ResetFlag?
            INT_Handler   TMR1_INT,  _ISR,   PBP,  yes
        endm
        INT_CREATE            ; Creates the interrupt processor
    ENDASM
    
    T1CON.0 = 1                       ' Start TMR1
    @   INT_ENABLE  TMR1_INT  ; Enable Timer 1 Interrupts
    
    Main:
    '    Toggle LED1
    '    Pause 1000
    '    Goto Main
    
        if intCount = 0 then
            LCDOUT $FE, $02, "NewCount: ", #newCount, "  "
            LCDOUT $FE, $C0
            LCDOUT "Frequency: ", #Frequency, "   "
            Frequency = 0
        endif
    Goto Main
     
    ' This is the interrupt routine for the TMR1 interrupt.
    ISR:
        T1CON.0 = 0                         ' Stop timer
        TMR1_Temp.HighByte = TMR1H          ' Get current "time"
        TMR1_Temp.LOWBYTE = TMR1L
        TMR1_Temp = TMR1_Temp + TMR1_Reload ' Add reload value to get correct interval
        TMR1H = TMR1_Temp.HIGHBYTE          ' Move result back to timer 
        TMR1L = TMR1_Temp.LOWBYTE
        newCount =  TMR0 - oldCount
        oldCount = TMR0
        T1CON.0 = 1                         ' Start timer
        IntCount = INtCount + 1             ' Increment interrupt counter
        Frequency = Frequency + newCount
        If IntCount = 9 THEN
            Frequency = Frequency/10
            IntCount = 0
        ENDIF
    @ INT_RETURN
    OK, I appreciate that the Frequency readout isn't really the true frequency but I just wanted to prove the averaging routine.

    Onwards and upwards... What's next?

    Cheers
    Barry
    VK2XBP

  24. #24
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Ioannis,
    Not sure I understand.... it will toggle LED1 once every second which means it will blink at 0.5Hz - isn't that what I wrote?

    Barry,
    Excellent! I wouldn't check IntCount in the main routine like that. Instead I'd check IntCount in the ISR and set a flag (UpdateDisplay or whatever). The main routine then checks this flag, executes the code and resets the flag. The reason for this is that when the code grows the Main routine may "miss" the when IntCount=0 (depending on what else it has to do and how long it takes). If you instead use the flag/semaphore method the Main routine WILL see that it's time to update display - when it gets around to it.

    OK, now that you have a stable time base and the pulses counted in hardware it's time to add in the PID again. The ISR would look something like:
    Code:
    ISR:
    
        T1CON.0 = 0                         ' Stop timer
        TMR1_Temp.HighByte = TMR1H          ' Get current "time"
        TMR1_Temp.LOWBYTE = TMR1L
        TMR1_Temp = TMR1_Temp + TMR1_Reload ' Add reload value to get correct interval
        TMR1H = TMR1_Temp.HIGHBYTE          ' Move result back to timer 
        TMR1L = TMR1_Temp.LOWBYTE
        T1CON.0 = 1				' Start timer
    
        ' Get the current motor velocity
        newCount =  TMR0 - oldCount
        oldCount = TMR0
    
        ' Calculate error and run PID
        pid_Error = SetPoint - newCount
        GOSUB PID
    
        IntCount = IntCount + 1             ' Increment interrupt counter
        If IntCount = 9 THEN
            UpdateDisplay = 1
            IntCount = 0
        ENDIF
    
    @ INT_RETURN
    Your setpoint in this case is pulses per interrupt period, not actual frequency in Hz.

    And the Main routine perhaps something like:
    Code:
    UpdateDisplay VAR BIT
    Main:
       If UpdateDisplay THEN
         'LCDOUT.....
         'LCDOUT.....
       UpdateDisplay = 0
    Goto Main
    This is untested but I'm sure you'll get the idea.

    /Henrik.

  25. #25
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    3,807


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Ooops, yes Henrik. Sorry about that. Thought it was 0.5 second and not Hz as you stated.

    Ioannis

  26. #26
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    OK, now I am a little confused....
    You said that Setpoint now becomes pulses per interrupt period but how is this referenced to the ADCin result of my speed set trimpot?
    Shouldn't the setpoint always be derived from the setting of the speed set trimpot and the error be calculated from the pulses per interrupt period?

    Cheers
    Barry
    VK2XBP

  27. #27
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Yes, what I mean is that if you previously had your setpoint scaled directly in Hz it now needs to be scaled in pulses/interrupt period.

    /H.

  28. #28
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    OK. Now I understand.
    Setpoint was previously read via ACDin (0-1023).
    ADValue was scaled in pulses per 100ms (approx 0-550)

    In the new setup, variable NewCount (replacing ADValue) can range from 0-255 so I propose to convert the ADCin result from 10-bit to 8-bit
    Setpoint = Setpoint>>2
    so that the error term does not get too large and gain terms can be kept to a manageable size.

    I will let you know how things pan out after tonight's round of experimants.

    Cheers
    Barry
    VK2XBP

  29. #29
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Things have been going too well for too long. A crach and burn situation was iminent!
    Things started falling apart once I pieces everything together.

    For starters, should I be able to use the serial LCD (debug statements) on RA5 with the DT-interrupts and counters on TMR0 and TMR1?
    The LCD screen is completely garbled.

    I have modified the circuit so that the three ADCin pots for Kp, Kd and Ki terms are on AN4, AN5 and AN6 and the encoder wheel pulse train is fed to M0CKI (pin 11). Circuit diagram is attached here incPID_16F684_V6.pdf

    I am in the process of cleaning up the program (making the PID routine as an included file as per Henrik's suggestion). I will post the code soon.

    In the mean time, any comments regarding the use of the serial LCD with DT-interrupts happening in the background would be greatly appreciated.

    Cheers
    Barry
    VK2XBP

  30. #30
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Barry,
    I touched this issue in a previous reply. The command you're using to communicate with the display (DEBUG) is a bit-banged command, it relies on software timing to generate the correct baudrate. When you add interrupts to the mix the interrupt code will "steal" time from the rest of the program. So if a DEBUG statement is executing when the interrupt fires the timing WILL be off.

    The usual cure to this problem is to use HSEROUT instead of DEBUG but that requires a PIC with an USART which the '684 doesn't have :-(
    The easiest thing to try is probably to split the messages to the LCD up in several pieces and send them one by one. Or even write the message to an array and use DEBUG to send one byte at the time from that array, disabling/enabling the interrupt system between each byte.

    Something like this perhaps:
    Code:
    ArrayWrite myString,[$FE,1,"P: ", #pid_Kp, " I: ", #pid_Ki", "D: ", #pid_Kd"
    
    For i = 0 to 16  'Or how many chars to send
      @ INT_DISABLE TMR1_INT
      DEBUG myString[i]
      @ INT_ENABLE TMR1_INT
    NEXT
    Now, I very rarely use DEBUG so I'm not sure it can handle arrays like this but I think it should. The "best" approach would probably be to switch to a PIC with an USART though.

    /Henrik.

  31. #31
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Thanks Henrik.

    I thought this might be the case but thought it would be better to confirm my thoughts before pulling out what's left of my hair...
    I had considered changing PIC's after one of your previous posts - I have been looking at 16F1825. What do you think of this as an alternative?
    I would value your recommendations on other alternates.

    For the time being I will just eliminate all display functions and "fly blind". I should at least be able to get the PID routine working, something yet to be achieved since introducing the interrupts!

    Cheers
    Barry
    VK2XBP

  32. #32
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    3,807


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Why not 16F1827?

    I used it and think is a little monster.

    Ioannis

  33. #33
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Barry,
    The 16F1825 looks very nice indeed. Seems to be pin compatible with the '684, has 4 times the amount of program memory and 8 times the amount of RAM and it has the USART. It's a bit more complicated than the '684 with all the peripheral multiplexed across the pins but don't let that put you off. I'd say it's a winner.

    The 16F1827 looks nice but it's got less flash and less RAM compared to the 16F1825 (not a problem if you don't need it) and it's not pin compatible with the 16F684 - which the 16F1825 seems to be.

    /Henrik.

  34. #34
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Sweet

    Thanks Henrik. I had already placed an order on Element 14 for two pieces as a precautony measure. They were quoting 2-3 days delivery so I hope to have the chips in time for the weekend.

    I will study up on the datasheet and try to sort out what "critical" registers need to be set for my application. This sort of thing is not rely my forte so any hints or recommendations wold be greatly appreciated. It took me ages to work out tht the comparitors needed to be disabled on the '684 to make it work!

    Cheers
    Barry
    VK2XBP

  35. #35
    Join Date
    Nov 2003
    Location
    Greece
    Posts
    3,807


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Barry, as I said I used the 1827 and it was a pain to find all correct settings. I had to enable PWM for two outputs.

    I do not claim that the settings may be complete, and according to your needs you have to change some.

    But is is a good start:

    Code:
    ASM
      __config _CONFIG1, _FOSC_INTOSC & _WDTE_OFF & _PWRTE_ON & _MCLRE_ON & _CP_ON & _CPD_OFF & _BOREN_ON & _CLKOUTEN_OFF & _IESO_OFF & _FCMEN_OFF
      __config _CONFIG2, _WRT_OFF & _PLLEN_ON & _LVP_OFF & _STVREN_OFF & _BORV_25
    ENDASM
    
    DEFINE OSC 32                       'Internal RC Oscillator with PLL
     
    DEFINE HSER_RCSTA 90h
    DEFINE HSER_TXSTA 24h
    DEFINE HSER_BAUD 115200
    DEFINE HSER_SPBRG 68
    DEFINE HSER_CLROERR 1
    
    DEFINE ADC_BITS 10 ' Set number of bits in result (8, 10 or 12)
    DEFINE ADC_CLOCK 3 ' Set clock source (rc = 3)
    DEFINE ADC_SAMPLEUS 50 ' Set sampling time in microseconds
    
    OSCCON= %11110000   'PLL enabled, Internal RC-8MHz and PLL x 4
     
    PORTB = 0:PORTA=0
    TRISB = %10110111
    TRISA = %00111111
    
    ADCON0 = %00000001
    ADCON1 = %10100011
    
    ANSELA = %00000001           'ANS0 analog in, all ohters digital I/O
    ANSELB = %00000110           ' PB1,PB2 Analog inputs
    
    APFCON0 = %00000000
    APFCON1 = %00000001
    
    BAUDCON = %00011000          'Bit 4: 1 Inverted data on Tx, 0 Non inverted for direct serial use
    
    CCP1CON = %00111100          'Control of CCPx modules
    CCP2CON = %00111100
    CCP3CON = %00000000
    CCP4CON = %00000000
    
    CM1CON0.7=0
    CM2CON0.7=0
    
    CPSCON0 = 0
    CPSCON1 = 0 
    
    DACCON0 = 0
    
    FVRCON = %10000011
    
    INTCON = %11000000   ' global & peripheral ints enabled
    
    OPTION_REG = %01000111    'Tmr0 from 256 presc and Int Clock
    
    WPUB=%10110001              ' Weak pullup individual settings. See also on Option_Reg
    
    DEFINE CCP1_REG PORTB
    DEFINE CCP1_BIT 3    
    DEFINE CCP2_REG PORTB
    DEFINE CCP2_BIT 6    
    CCPTMRS=0
    
    PR2=255
    
    CCP1CON=$0C
    CCP2CON=$0C
    T2CON=%00000110        'Prescaller / 16, Timer2 ON
    Ioannis

  36. #36
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    "#*&@"!!!!!

    I don't think I could have worked that out in a month of Sundays! - Thanks Ioannis.

    As Henrik stated, the 16F1825 is pin compatible with the '684 that I am currently using but your "head start" on setups will be invaluable in my future endeavours.

    Henrik also asked the question
    Why do you need me for this ? ;-)
    I think the answer to that is now self explanitory...

    Thanks for all your help guys. I am sure I will be back with more questions very soon

    Cheers
    Barry
    VK2XBP

  37. #37
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi All,

    I have had a bit of time to think about where I am going with this project and what my ultimate goal is.
    As stated in my original message

    The end use for this project is to re-power my 9x20 lathe using a treadmill motor (180Vdc 2HP).
    I could spend time tweaking the current system, change the PIC (along with the extended learing curve associated with the new settings) but is it really worth it? At this stage I have proved the concept using the 24Vdc motor; proved it well enough to move forward to the final platform. I have decided to take the plunge and jump straight to the full blown design. Yeah, scary huh!

    The final system will require forward and reverse motor operation. This will be achieved using four IGBT's configured in an H-Bridge arrangement. The IGBT's will be fired by dedicated high-side and low-side gate drivers. All control circuitry will be isolated from the power circuit by opto-isolators (5kV isolation). Forward, reverse and stop (emergency) functions will be performed by switches on digital I/O lines, speed to be set by ADCin via a pot, feedback via photo-interrupter/shaft encoder wheel with an interrupt driven counter (eg. TMR0 and TMR1). As in the prototype system, gain terms will be set via individual pots with ADCin routines. Settings to be displayed on a serial LCD.

    I have searched the Microchip website for an appropriate device and decided on the 18F2431. Quite a powerful device with lots of in-built functions dedicated to motor drive applications.

    I now have lots of new information to try and get my head around. The 18F2431 datasheet is mammoth! I plan to piece the circuit diagram together, lay out a new pcb and then begin to learn the ways of driving the 18F2431. This won't happen overnight - but it will happen

    Thanks to all those who have helped me get to this stage. I am sure I will be calling on you again in the very near future.

    Any recommendations, suggestions or comments on what I am trying to do, before I embark on this adventure, would be greatly appreciated.

    Cheers
    Barry
    VK2XBP

  38. #38
    Join Date
    Oct 2005
    Location
    Sweden
    Posts
    3,521


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Barry,
    The 18F2431 is a wonderful device (a bit expensive but there's really no alternative in the 8bit line-up of PICs) and it is the chip I personally use with the PID routine (for info please see with a bit of "bench test demo".

    I haven't used the PCPWM module in full bridge mode but I think there's a post somewhere around here, made by Bruce, with a nice example. Apart from that I know the 2431 pretty well so you won't be alone.

    Keep it up!
    /Henrik.

  39. #39
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Henrik,

    Nice to know I won't be flying blind with my choice of PIC. I am really getting in WAY above my head with this beast but what the heck - it only set me back $10
    I will send you a copy of the new circuit diagram once I have finished it. Hopefully the pin-out for my needs should be relaitvely straight forward.
    I will also do some forum searches on the 2431 and see what I can find.

    I was very impressed with your Youtube video. Nice bit of motor control. Once I get my 9x20 lathe re-powered I will be looking to do a CNC conversion on my X2 mill. So much to do, so little spare time available...

    Onwards and upwards

    Cheers
    Barry
    VK2XBP

  40. #40
    Join Date
    Jan 2011
    Location
    Sydney, Australia
    Posts
    166


    Did you find this post helpful? Yes | No

    Default Re: incPID Routine - Help Needed

    Hi Henrik,

    It has been quite some time since I last posted on this subject. A lot of water has flowed under the bridge since then but I am pleased to advise that I have my 1500W H-Bridge power board up and running via PIC18F2431 in PCPWM mode.
    I have forward, reverse and emergency stop functions operating on a test motor (24V 200W scooter motor) and nice clean rotary encoder signals from a home made encoder wheel and IR photo-interrupter - 60 steps per revolution.
    My next stage of the project is to try and implement your incPID routine.
    My question is this: Should I take the approach you outlined above (ie use DT-Ints to set up a timer interrupt at around 100Hz and then feed the pulses to another timer/counter) or would there be a better way of doing it on the 18F2431 using some of the available Motion Feedback modules?

    Your advice and direction would be greatly appreciated.

    Cheers
    Barry
    VK2XBP

Members who have read this thread : 1

You do not have permission to view the list of names.

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts