-
1 Attachment(s)
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
-
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
-
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.
-
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
-
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
-
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
-
1 Attachment(s)
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:
Attachment 6523
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
-
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
-
1 Attachment(s)
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.
Attachment 6524
Cheers
Barry
-
Re: incPID Routine - Help Needed
Looks nice. An a big motor!
Wish you good luck Barry.
Ioannis
-
1 Attachment(s)
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 Attachment 6530
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
-
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.
-
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
-
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.
-
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
-
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
-
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.
-
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
-
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.
-
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
-
Re: incPID Routine - Help Needed
Definitely so. Somehow that fell out of there.....
Why do you need me for this ? ;-)
-
Re: incPID Routine - Help Needed
Nice job Henrik.
LED1 will toggle LED1 every 1 second not 0.5 (based on correct 8MHz clock).
Ioannis
-
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
-
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.
-
Re: incPID Routine - Help Needed
Ooops, yes Henrik. Sorry about that. Thought it was 0.5 second and not Hz as you stated.
Ioannis
-
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
-
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.
-
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
-
1 Attachment(s)
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 Attachment 6535
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
-
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.
-
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
-
Re: incPID Routine - Help Needed
Why not 16F1827?
I used it and think is a little monster.
Ioannis
-
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.
-
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
-
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
-
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
Quote:
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
-
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
Quote:
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
-
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 here (my website)). Here's a Youtube video 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.
-
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
-
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