PDA

View Full Version : incPID Routine - Help Needed



Aussie Barry
- 2nd June 2012, 14:21
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:



' 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
incPID_16F684

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

Aussie Barry
- 2nd June 2012, 14:29
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

HenrikOlsson
- 2nd June 2012, 15:47
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.

Aussie Barry
- 3rd June 2012, 06:54
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

HenrikOlsson
- 3rd June 2012, 11:17
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

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

Ioannis
- 6th June 2012, 11:14
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

Aussie Barry
- 12th June 2012, 01:39
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:

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

Ioannis
- 12th June 2012, 07:55
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

Aussie Barry
- 12th June 2012, 09:51
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.


6524

Cheers
Barry

Ioannis
- 12th June 2012, 13:03
Looks nice. An a big motor!

Wish you good luck Barry.

Ioannis

Aussie Barry
- 17th June 2012, 04:42
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 6530

My current program is as follows:


' 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

HenrikOlsson
- 17th June 2012, 09:24
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.

Aussie Barry
- 17th June 2012, 11:31
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

HenrikOlsson
- 17th June 2012, 14:35
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).


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:

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.

Aussie Barry
- 17th June 2012, 15:15
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

Aussie Barry
- 18th June 2012, 11:32
Hi Henrik,

I loaded the following code into my'684 and wired it up on my breadboard with a 2 x 16 LCD.


' 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

HenrikOlsson
- 18th June 2012, 12:31
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:

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.

Aussie Barry
- 18th June 2012, 13:20
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

HenrikOlsson
- 18th June 2012, 16:28
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 (http://darreltaylor.com/DT_INTS-14/intro.html). 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:

' 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.

Aussie Barry
- 18th June 2012, 17:28
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

HenrikOlsson
- 18th June 2012, 17:30
Definitely so. Somehow that fell out of there.....
Why do you need me for this ? ;-)

Ioannis
- 19th June 2012, 07:59
Nice job Henrik.

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

Ioannis

Aussie Barry
- 19th June 2012, 08:20
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:



' 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

HenrikOlsson
- 19th June 2012, 08:55
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:

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:

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.

Ioannis
- 19th June 2012, 09:29
Ooops, yes Henrik. Sorry about that. Thought it was 0.5 second and not Hz as you stated.

Ioannis

Aussie Barry
- 19th June 2012, 09:40
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

HenrikOlsson
- 19th June 2012, 10:14
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.

Aussie Barry
- 19th June 2012, 10:39
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

Aussie Barry
- 20th June 2012, 02:44
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 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

HenrikOlsson
- 20th June 2012, 06:23
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:

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.

Aussie Barry
- 20th June 2012, 07:41
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

Ioannis
- 20th June 2012, 07:50
Why not 16F1827?

I used it and think is a little monster.

Ioannis

HenrikOlsson
- 20th June 2012, 09:40
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.

Aussie Barry
- 20th June 2012, 10:06
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

Ioannis
- 20th June 2012, 10:30
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:



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

Aussie Barry
- 20th June 2012, 10:57
"#*&@"!!!!!

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

Aussie Barry
- 21st June 2012, 12:09
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

HenrikOlsson
- 21st June 2012, 12:47
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 (http://www.henriksplace.se/servo/index.html) (my website)). Here's a Youtube video (http://www.youtube.com/watch?v=U-jrxK7u_34) 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.

Aussie Barry
- 21st June 2012, 13:20
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

Aussie Barry
- 17th September 2013, 13:11
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

HenrikOlsson
- 17th September 2013, 20:26
Hi Barry,
Either is fine - whatever works....
Since you're only looking for velocity and not position (right?) you could either use the motion feedback module in Velocity Mode (something I've never done myself) or use a standard timer/counter as outlined earlier.

As for the timer interrupt... A neat feature of the PCPWM module is that you can use IT (or rather its timer) to generate an interrupt every Nth PWM cycle. That's what the PostScale setting is for. But the available post scale ratios are limited so if your PWM frequency is high and you're looking for a low(ish) interrupt frequency it might not be the best to use.

Again, either works.

What's the lowest speed you expect to run the motor at?

/Henrik.

Aussie Barry
- 17th September 2013, 22:36
Thanks for the feedback Henrik.

The PCPWM frequency is 8kHz and I would like to be able to slow the motor down to 60rpm (1 revolution per second) if possible. This would allow me to perform threading operations on my lathe at a safe speed (the ultimate use for this project is to re-power my 9x20 lathe).

I also need a way of determining and displaying the rotational speed. I know this is possible by manipulating the data from the counter/timer but would it be simpler to use the period measurement function (input capture mode) of the Motion Feedback Module on the 18F2431? Have you, or any of the other forum members, had any experience using the period measurement function?

Cheers
Barry
VK2XBP

mark_s
- 18th September 2013, 00:46
Hello Barry,

I am working on a similar project and following this thread.

A while back I found this code some where maybe here? Not sure who the author is?
It calculates RPM using the 18F4431, but not using the velocity mode. Maybe you can use some ideas



'--------Program Desciption--------

' Program uses Timer0 Interrupt to average motor rpm over one minute.
' Motor power set by potentiometer before program begins.
' LCD display updates time, position count and average rpm every second.

'---Review PicBasic Pro Command----

' The PicBasic Pro Compiler Manual is on line at:
' http://www.microengineeringlabs.com/resources/index.htm#Manuals

'---------PIC Connections----------

' 18F4331 Pin Wiring
' --------- ----------
' RA0(AN0) Potentiometer, controls motor power
' RA3 Signal 1 from Encoder
' RA4 Signal 2 from Encoder
' RB5 In Circuit Serial Programming (ICSP) PGM
' 100K Resistor to GND
' RB6 ICSP PGC (Clock)
' RB7 ICSP PGD (Data)
' RC0 Brake Motor 1 on Xavien XDDCMD-1 (Pin 1)
' RC1 PWM Motor 1 on Xavien XDDCMD-1 (Pin 2)
' RC3 Direction Motor 1 on Xavien XDDCMD-1 (Pin 3)
' RD4 LCD Data Bit 4
' RD5 LCD Data Bit 5
' RD6 LCD Data Bit 6
' RD7 LCD Data Bit 7
' RE0 LCD Register Select
' RE1 LCD Enable
' MCLR 4.7K Resistor to +5V & ICSP Vpp
' VDD +5V
' VSS GND
' OSC1 & OSC2 4 MHz Crystal w/ 2-22 pF Cap. to GND

'----Xavien XDDCMD-1 Connections---

' Xavien 2x5 Header Pin Wiring Pin Layout 2x5 Header
' --------------------- ------ ---------------------
' 2 4 6 8 10
' Pin 1 Motor 1 Brake RC0 o o o o o
' Pin 2 Motor 1 PWM RC1 o o o o o
' Pin 3 Motor 1 Direction RC3 1 3 5 7 9

' See schematic at:
' http://cornerstonerobotics.org/schematics/18f4331_hpwm_motor_encoder.pdf

'--Sample POSCNTH, POSCNTL Values and Corresponding Position Counter--

' position = 256 * POSCNTH + POSCNTL

' POSCNTH POSCNTL Position Counter
' ------- ------- ----------------
' 0 0 0
' 0 1 1
' 1 0 255
' 0 128 128
' 128 0 32768
' 0 255 255
' 255 0 65280
' 255 255 65535

'-------------Defines--------------

DEFINE LCD_DREG PORTD ' Set LCD Data port
DEFINE LCD_DBIT 4 ' Set starting Data bit to 4
DEFINE LCD_BITS 4 ' Set LCD bus size to 4
DEFINE LCD_RSREG PORTE ' Set LCD Register Select port to E
DEFINE LCD_RSBIT 0 ' Set LCD Register Select bit to 0
DEFINE LCD_EREG PORTE ' Set LCD Enable port to E
DEFINE LCD_EBIT 1 ' Set LCD Enable bit to 1
DEFINE LCD_LINES 2 ' Set number of lines on LCD to 2
DEFINE LCD_COMMANDUS 2000 ' Set command delay time to 2000 us
DEFINE LCD_DATAUS 50 ' Set data delay time to 50 us
DEFINE ADC_BITS 8 ' Set number of bits in result to 8
DEFINE ADC_CLOCK 3 ' Set clock source (rc = 3)
DEFINE ADC_SAMPLEUS 50 ' Set sampling time in us
DEFINE CCP2_REG PORTC ' Set HPWM Channel 2 port to C
DEFINE CCP2_BIT 1 ' Set HPWM Channel 2 bit to 1

'------------Variables-------------

mot_pwr var byte ' Declare mot_pwr variable, reserve byte
pot_val var byte ' Declare pot_val, reserve byte
position var word ' Declare position, reserve word
second VAr word ' Declare second, reserve word
ticks var byte ' Declare ticks, reserve byte
update var byte ' Declare update, reserve byte
rpm var word ' Declare rpm, reserve word

'----------Initialization----------

CCP1CON = %00111111 ' Set Capture/Compare/PWM Module Control
' Register CCP1CON in PWM mode (bits 0-3),
' bits 4,5 set LSBs of 10-bit duty cycle,
' see 18F4331 datasheet page 151 +/-.
ANSEL0 = %00000001 ' Set AN0 to analog, AN1-AN7 to digital,
' see datasheet page 249 +/-.
ANSEL1 = %00000000 ' Set AN8 to digital, see datasheet
' page 249 +/-.
TRISA = %00011111 ' Set TRISA register, RA7-RA5 as outputs,
' RA4-RA0 as inputs, see datasheet
' page 107 +/-.
LATA = %00000000 ' Set all LATA register bits to 0.
TRISB = %00000000 ' Set RB7-RB0 pins in PORTB as outputs.
TRISC = %00000000 ' Set RC7-RC0 pins in PORTC as outputs.
QEICON = %10001000 ' Set Quadrature Encoder Interface Control
' Register. See page 171 +/- for
' encoder set up.
T0CON = %11010101 ' Set TMR0 configuration and enable PORTB
' pullups. Set Timer0 Prescaler Select bits
' (bits 2-0) to 1:64 prescaler value. Timer0
' interrupt occurs every 256 us for a
' 4 MHz crystal so 256us * 64 = 16.384ms.
' 16.384ms * 61 ticks = 0.9994 seconds.
' See Timer0 Control Register 18F4331
' datasheet page 135 +/-.
INTCON = %10100000 ' Enable Timer0 interrupts.
ON INTERRUPT GoTo tickint ' Jump to interrupt handler "tickint"
' after receiving an interrupt.
PORTC.0 = 1 ' Turn on brake.
PORTC.1 = 0 ' Set PWM bit for Channel 2 of HPWM to LOW.


'-------------Main Code------------

pause 500 ' Pause to start up LCD
PORTC.0 = 0 ' Turn off brake
PORTC.3 = 0 ' Set direction of motor
' If position value on LCD is in the 65,000s
' and counting down, then change the
' motor direction: PORTC.3 = 1.
second = 0 ' Set initial values for second and ticks.
ticks = 0
update = 1 ' Enable first display

' Set counter starting position:

POSCNTH = 0 ' Set counter for encoder, H bit
POSCNTL = 0 ' Set counter for encoder, L bit
' position = 256 * POSCNTH + POSCNTL
' With POSCNTH and POSCNTL = 0,
' position counter will start at 0.
' See table above for more sample values.

' Read motor power setting, set motor power before main loop:

ADCIN 0, pot_val ' Read AN0 and store result in pot_val.
' This potentiometer (connected to AN0)
' sets the motor power.
mot_pwr = 11 * pot_val / 16 + 77
' mot_pwr = 11/16 * pot_val + 77
' (Can't write equation as 11/16 * pot_val
' since interger division truncates: any
' fractional part is discarded. Since 11
' and 16 are integers, 11/16 would be
' truncated to zero.)
' 77 is the minimum power to start motor.
' 11/16 is the slope of the line to give
' mot_pwr values from 77 to about 255.
' See graph & equation in schematic.
HPWM 2, mot_pwr, 20000 ' Send PWM signal from RC1 to Pin 2 on
' the Xavien XDDCMD-1 DC motor driver.
loop:

if update = 1 then
POSITION = 256 * POSCNTH + POSCNTL ' Read position
rpm = 60 * (position/32) / second
' 60, (60 seconds/minute)
' position/32, number of revolutions
' (Our motor has 16 holes which generates
' 32 position counts/revolution)
' second, number of seconds

' rev 60 seconds position counts 1 revolution 1
' rpm = --- = ---------- * * ------------------ * ---------
' min 1 minute 32 position counts # seconds

LCDOUT $FE, $80, "rpm=",dec5 rpm, " s=", dec3 second
' Display rpm and seconds on the first line
LCDOUT $FE, $C0, "position = ",DEC5 POSITION
' Display position on second line
' Position will count to 65535, then
' cycle back to 0 and continue counting.
' in 5 decimal digits.
update = 0 ' Reset update to 0
endif

' After 60 seconds, shut down:

if second = 60 then gosub shut_down
' Stop program at 60 seconds
goto loop:


Disable ' Disable interrupts during interrupt handler

' Interrupt handler:

tickint:
ticks = ticks + 1 ' Count parts of a second
if ticks < 61 then tiexit ' Timer0 interrupt occurs every 256 us for
' a 4 MHz crystal so 256us * 64 = 16.384ms.
' 16.384ms * 61 ticks = 0.9994 seconds.
' (61 ticks per 0.9994 seconds)

' One second elapsed, reset ticks and update time:

ticks = 0 ' Reset ticks to 0
second = second + 1 ' Add one second
update = 1 ' Permits LCD update
tiexit: INTCON.2 = 0 ' Reset timer interrupt flag,
' (Reset Timer0 to 0 - next Timer0
' interrupt is in 16.384ms)
resume

' End of interrupt handler

enable ' Enable interrupts

shut_down:

PORTC.0 = 1 ' Turn on motor controller brake

end

HenrikOlsson
- 18th September 2013, 06:16
Hi Barry,
Oh, only 8kHz PWM frequency....
If using the PCPWM timer with a 1:16 postscale it would interrupt at 8k/16=500Hz which, I'd say would be fairly good for a large inertia system like a lathe spindle. BUT, I think the limited resolution of your spindle encoder will present you with some issue if you're simply going to count pulses for the duration of interrupt period. At 500Hz interrupt rate and 60rpm there will be a single count every ~8th interrupt and none in between - even at 100Hz interrupt rate the resolution will be quite limited at the low end.

Still, this might work due to the high inertia but from the PID-filters point of view it's going to look like the spindle isn't moving at all, then all of a sudden it is moving, then it's not moving at all and so on. Depending on the tuning of the filter you're going to have a lot of ripple on the output which will result in some large torque variations in the motor - which will then get filtered by the inertia of system.

I'm thinking that perhaps it would be better to use a capture module and measure the width of each input pulse instead of counting them over time.

/Henrik.

Aussie Barry
- 18th September 2013, 10:01
Hi Henrik,

Nothing is set in concrete on this project!
8kHz is the PWM frequency now but it can be changed. I don't know what the down side would be, if any, to increasing the PWM frequency but I am willing to give it a go if it will help things overall.

I chose 60 steps per revolution on the encoder wheel because a) it was easy to achieve and b) it made RPM calculation easy.
I have now played with period measurements using the 18F2431's IC module so there is less reliance on the original step rate.

Given the inertia of the final system, what would be the expectation for the number of steps per revolution, interrupt rate and minimum motor speed to achieve a nice, tight PID operation?

Cheers
Barry
VK2XBP

HenrikOlsson
- 18th September 2013, 17:08
Hi Barry,
The drawback with a switching frequency as low as 8kHz is that it is in the audible range, ie you or people around you can hear it "whine". The ripple current thru the motor will also be larger - which might or might not matter (think current limiting etc). Going to a higher switching frequency helps with both of the above points but introduces more switching losses in the H-bridge. Normal switching frequencies for motor control tends to be in the 20-50kHz range. but if 8kHz works for you I see no reason to change it.

I can't give you a definitive answer to the second question. I could blame that on not knowing the inertia of your final system but honestly, even if I did I couldn't calculate that for you.

If counting pulses, basically you want as much granularity as you can get at the lower end without overflowing the counter at the top end. If timing pulses it's the other way around, then you risk overflowing the timer at the low end.

To illustrate an example: Lets, for easy of calculation, say that your interrupt rate is 60Hz. If you count pulses and the motor is turning 60rpm you'll get a single count each interrupt. But if its turning say 66rpm the PID filter would only notice that as an "extra" count every 10th interrupt. And when it notices that extra count IT thinks the motor is turning 120rpm (since 1 count per interrupt equals 60rpm). Now all of a sudden tit sees an error of 100% (where did THAT come from) when in reallity the error is 10%. Again, the inertia of the spindle will filter this to some degree but it's something to be aware of.

Again, it might work just fine.

/Henrik.

Aussie Barry
- 19th September 2013, 09:30
Thanks for the sanity check Henrik.

One of the reasons I chose 8kHz switching frequency is that I need to drive the H-Bridge via opto-couplers to achieve isolation between mains power and the controller.
I have been able to achieve sub-10uS opto rise and fall times but need to apply 12uS dead-time for the PWM signal to alleviate MOSFET shoot-through. Increasing the switching frequency just isn't possible without losing a heap of top motor speed.

After careful consideration I believe I might have been setting my sights a bit high on the final performance specifications. I have pulled back on the desired minimum rotational speed and will set it somewhere that is workable within the limits of the sample rate and the number of encoder pulses.

I have also decided not to incorporate the RPM display into the main 18F2431 function. If need be I will add the RPM display at a later date using a dedicated setup.

So now it is a matter of getting back to setting up the timer/counter arrangement and implementing the incPID routine. I will keep you updated with my progress.

Cheers
Barry
VK2XBP

HenrikOlsson
- 19th September 2013, 09:45
Hi Barry,
I see no reason that the RPM display can't be integrated in the same system but you're doing the right thing in getting one thing at a time going.
With a PID update frequency in the low hundreds there is PLENTY of processing power left to run a display. It's just a matter of not allowing the display update to block or take priority of the PID.
Is this some kind of serial display or is it the standard HD44780 type?

Anyway, I'm glad you're moving forward and I'm looking forward to see your progress!

/Henrik.

Aussie Barry
- 19th September 2013, 09:59
Hi Henrik,

I can use a serial display connected to the 18F24321's EUSART or a standard HD44780 type. I suspect the serial device with take up much less overhead once the whole system is running but for the time being I will use the standard HD44780.

I will continue with the PID implementation using the smaller 24V 200W scooter motor - I feel much safe playing with lower, less lethal voltages for my prototyping purposes. Once I have that system operating successfully I will then step up to the 180V treadmill motor.

I will send regular progress details as I achieve various project milestones.

Cheers
Barry
VK2XBP

Aussie Barry
- 20th September 2013, 10:01
OK, I now have a PIC18F2431 with acounter set up on Timer1 and a time base set up on Timer0.
The counter was pretty straight forward but i have a few questions regarding the operation of Timer0

1/. Timer Pre-Load
Timer0 is a 16 bit timer which gives an output as it rolls over from $FFFF to 0.
An 8MHZ clock speed will provide one instruction every 500ns. For a 100Hz time base (10ms period) I would need 20000 instruction cycles (20000 x 500ns = 10ms).
A free running timer wiith no pre-load would roll over every 65536th cycle so to achieve a 20000 cycle roll over I need to pre-load the timer with 65536 - 20000 = 45536.
All good - nice clean theory however, a 45536 pre-load results in ~50Hz time base!

Here is my code:



#CONFIG
CONFIG OSC = IRCIO
CONFIG FCMEN = ON
CONFIG IESO = OFF
CONFIG PWRTEN = ON
CONFIG BOREN = ON
CONFIG BORV = 42
CONFIG WDTEN = OFF
CONFIG WDPS = 512
CONFIG WINEN = OFF
CONFIG PWMPIN = OFF
CONFIG LPOL = HIGH
CONFIG HPOL = HIGH
CONFIG T1OSCMX = OFF
CONFIG MCLRE = ON
CONFIG STVREN = ON
CONFIG LVP = OFF
CONFIG DEBUG = OFF
CONFIG CP0 = ON
CONFIG CP1 = ON
CONFIG CP2 = ON
CONFIG CP3 = ON
CONFIG CPB = OFF
CONFIG CPD = OFF
CONFIG WRT0 = OFF
CONFIG WRT1 = OFF
CONFIG WRT2 = OFF
CONFIG WRT3 = OFF
CONFIG WRTC = OFF
CONFIG WRTB = OFF
CONFIG WRTD = OFF
CONFIG EBTR0 = OFF
CONFIG EBTR1 = OFF
CONFIG EBTR2 = OFF
CONFIG EBTR3 = OFF
CONFIG EBTRB = OFF
#ENDCONFIG

' Set up interrupt routine
INCLUDE "DT_INTS-18.bas" ; Base Interrupt System
INCLUDE "ReEnterPBP-18.bas" ; Include if using PBP interrupts
ASM
INT_LIST macro ; IntSource, Label, Type, ResetFlag?
INT_Handler TMR0_INT, _ISR, PBP, yes
endm
INT_CREATE ; Creates the interrupt processor
ENDASM

@ INT_ENABLE TMR0_INT ; Enable Timer 0 Interrupts

' Define LCD registers and bits
Define LCD_DREG PORTB
Define LCD_DBIT 4
Define LCD_RSREG PORTA
Define LCD_RSBIT 1
Define LCD_EREG PORTA
Define LCD_EBIT 2
Define LCD_BITS 4
Define LCD_LINES 2

' Set up Internal Oscillator @ 8MHz
DEFINE OSC 8
OSCCON = %01110010 ' Internal oscillator, 8MHz


' Set up Timer0
T0CON = %00001000 ' Timer off
' 16 bit
' Internal clock
' Prescaler not assigned
T0CON.7 = 1 ' Start Timer0

' Set up I/O Ports
ANSEL0 = 0 ' No analogue input, all digital
TRISA = 0 ' ALL PORTA as outputs
PORTA = 0 ' All outputs low
TRISB = 0 ' ALL PORTB as outputs
PORTB = 0 ' All outputs low
TRISC = 0 ' Set PORTC as output
PORTC = 0 ' All outputs low


Pause 100
LCDOUT $FE,1," Baztronics "
Pause 100

TMR0_Reload CON 55750 ' Reload value for ~100Hz interrupt frequency at 8MHz, prescaler 1:1
TMR0_Temp VAR WORD ' Temporary variable use for reloading the timer

LED1 VAR PortC.2

Main:
Pause 100
Goto Main

' This is the interrupt routine for the TMR0 interrupt.
ISR:

T0CON.7 = 0 ' Stop timer

TMR0_Temp.LOWBYTE = TMR0L
TMR0_Temp.HighByte = TMR0H ' Get current "time"
TMR0_Temp.LOWBYTE = TMR0L
' TMR0_Temp.HighByte = TMR0H
TMR0_Temp = TMR0_Temp + TMR0_Reload ' Add reload value to get correct interval

TMR0L = TMR0_Temp.LOWBYTE
TMR0H = TMR0_Temp.HIGHBYTE ' Move result back to timer
TMR0L = TMR0_Temp.LOWBYTE
' TMR0H = TMR0_Temp.HIGHBYTE

T0CON.7 = 1 ' Start timer
Toggle LED1
@ INT_RETURN



I know my internal oscillator is operating at 8MHz because my LCDOUT commands work properly.
Can anyone explain why the time base is not as predicted?

2/. TMR0L and TMR0H
The datasheet states that "TMR0H is not the high byte of the timer/counter in 16bit mode, but is actually a buffered version of the high byte of Timer0" and that it "is not directly readable nor writable."
It goes on to explain that "TMR0H is updated wth the contents of the high byte of TMR0 during a read of TMR0L" The same conditions apply for write operations.
The way I understand this, I must read the TMR0L first, this transfers the buffered data to the high byte of TMR0H and THEN I can read the high byte directly.
Obviously I have mis understood the operation as this does not work.
The only way I can get the timer to provide a stable time base is to read TMR0L twice and TMR0H once OR read TMR0H twice and TMR0L once.

Would someone be able to describe how/why the low and high bytes of TMR0 should be read/written?

Cheers
Barry
VK2XBP

HenrikOlsson
- 20th September 2013, 10:20
Hi Barry,

1) Are you sure you're not getting 100Hz interrupt rate? You are using TOGGLE in the ISR so if the interrupt frequency IS 100Hz it will blink the LED at 50Hz.

2) The way I understand it is that when reading TMR0 you should first read TMR0L, this triggers a transfer (in hardware, nothing you need to worry about) of the content of the actual high byte of the counter into TMR0H which you can then read. When writing you do it the other way around, first load TMR0H then you write TMR0L and that write operation triggers a transfer from TMR0H to the actual high byte of counter.

This hardware buffering prevents the possibillity of the counter rolling over in between reading or writing each individual byte but it's important to do it the correct order.

If you're not using TMR2 for anything it might be easier to use that since it doesn't really require reloading. It counts from 0 to PR2, generates an interrupt and start over at 0. No need to stop the timer, get the value, add reload, write new value and restart the timer. Its only 8 bits but it does have a prescaler so and postscaler so you should be able to get 100Hz. Of course using TMR1 works too!

/Henrik.

Aussie Barry
- 20th September 2013, 10:38
Hi Henrik,

Thanks for the quick reply.

1/. Yes, I wasn't using a LED to check the time base - I measured the frequency on my digital oscilloscope

2/. Hmmm.... OK, I will do a bit more playing around to see what I can come up with.

I will check out TMR2 and see if I can get it working as a 100Hz time base.

Lot's of ways to skin a cat. I will try them all and see what I can learn along the way :)

Cheers
Barry
VK2XBP

HenrikOlsson
- 20th September 2013, 11:22
Hi Barry,
I'm still not convinced....
If the PIC is toggling the output at 100Hz the measured frequency will be 50Hz because it turns the output ON 50 times per second and OFF 50 times per second. Replace the TOGGLE LED1 with LED1 = 1 : PAUSEUS 100 : LED1 = 0 and see if that convinces you it actually IS interrupting at 100Hz.

/Henrik.

Aussie Barry
- 20th September 2013, 11:56
Hi Henrik,

Yes, you were right on both instances.

I now have a nice, stable 100Hz time base and most importantly - I understand why :)

Next step is to incorporate the timer/counter setup with the PWM motor drive to count encoder steps.
From there it is a matter of getting it all to work with the PID routine.

Once more I am indebted to you, Henrik, and the other forum members for the invaluable assistance. Thank you.

Cheers
Barry
VK2XBP

Aussie Barry
- 25th September 2013, 10:10
Hi All,

I am pleased to report that I now have the PID system operating with a timer/counter configuration (100Hz time base) AND a 4 x 20 serial LCD being driven via the 18F2431's EUSART and HSEROUT commands.
My original design only allowed single direction motor drive but the new design has the motor driven by a MOSFET H-Bridge allowing for forward and reverse direction.

Henrik, you suggested to limit the pid_Out result to allow only positive drive outcomes for my original design. Now that I have forward and reverse direction capability, should I re-introduce positive and negative pid_Out drive conditions or do you think switching the motor/H-Bridge between forward and reverse state will cause undue stress on the motor?

Cheers
Barry
VK2XBP

HenrikOlsson
- 25th September 2013, 11:40
Hi Barry,
I'm delighted to hear you've got it going, nice work!
Can we see some photos of the thing or perhaps even a video?

It depends on the motor, the H-bridge and if you have any sort of current limiting built into the circuit or not. When the motor is turning CW and you apply a voltage to make it turn CCW the EMF of the motor will end up in series with the supply voltage from the bridge.

IF the motor is doing full speed in one direction and the operator decides it should go full speed in the other direction there will basically be twice the supply voltage across the motor when it starts to decelerate (if you don't apply a ramp on the control signal), if you do have current limiting this shouldn't be too much of any issue but if you're operating this without a current limit there is a chance that you'll damage the H-bridge and possibly the motor (depending on it's peak current rating etc etc). (It might even throw the chuck of the spindle....)

How do you drive the H-bridge? Are you using the PCPWM module in full bridge mode?

If you are controlling position then I'd say you need 4 quadrants but if you're "only" controlling velocity, though both CW and CCW, then I think you'll be fine limiting the output to only positive (or only negative, depending on direction). The motor will still be able to "brake" and regenerate, you just can't generate torque in one direction while it's turning in the other.

/Henrik.

Aussie Barry
- 25th September 2013, 12:05
Hi Henrik,

The whole system is rather messy at the moment as a result of the changes made during prototype development. I am happy to send some photos once I have it back in some resemblance of order :)
The power drive for the 1500W motor will ultimately be fed from rectified 240V mains and three large filter caps for a supply rail of around 360Vdc. Right now I am merely driving a 200W 24 volt scooter motor of a 3A bench power supply.
I have overcurrent protection built in to the design by sniffing voltage across a low resistance between the bottom of the H-Bridge and ground. This circuit is designed to trip under overcurrent situation (eg motor stall) but that is the extent of it.

The H-Bridge consists of four APT5010LVR MOSFETS fed by a pair of NCP5181P half-bridge drivers. The 18F2431 feeds the MOSFET drivers from the PCPWM module in full bridge mode. For added safety I have the control circuitry optically isolated from the power circuitry.

I am a little confused by the statement in your last sentence - If I limit the output to only positive (or only negative, depending on direction) how is the motor "still able to brake and regenerate? Do you mean that with pid_Out = 0 the two bottom "legs" of the H-Bridge effectively provide a short circuit across the motor terminals thus providing a brake force via back EMF? Please explain.

Cheers
Barry
VK2XBP

HenrikOlsson
- 25th September 2013, 17:52
Hi Barry,
It depends on how you are driving the bridge.
If I understand correctly you're feeding the MOSFETs (thru their drivers) from two of the PWM generators - both in complementary mode, ie four outputs from the PIC. PWM0 & PWM1 are feeding the left leg while PWM2 & PWM3 are feeding the right leg of the bridge (or the other way around). Is that correct so far?

Now, what do you do when you want the motor to turn clockwise (or counter clockwise)?
Do you set one of the PWM generators to 0% (effectively turning on the LOW side switch of that leg) and then apply the output of the PID-filter to the other PWM generator? If you ARE doing this then the motor IS shorted thru the lower two switches during the off-period of the PWM cycle (and, obviously, when the dutycycle of both generators are 0). This will brake the motor but won't regenerate (I think), ie it won't feed energy back to the powersupply. Instead, the energy will be absorbed by the armature resistance of the motor, the rds-on of the MOSFET, wiring, connectors and so on. If this IS what you're doing please note that the braking current this generates does not flow thru your current sense resistor. If the motor is spinning, generating 200V of EMF and you suddenly set both dutycycle registers to 0% the motor will be shorted thru the bottom two switches, how much current will that result in?

The other option is to run the bridge in what's called locked antiphase mode. In this mode you typically use a single PWM generator and drive one diagonal pair of switches with the "true" output and the other diagonal pair with the inverted output.
In this mode the bridge switches between the two diagonal pairs during the PWM cycle. When the dutycycle is 50% each diagonal pairs are "on" for 50% of the PWM cycle resulting in a net current of zero thru the motor. When "forward torque" is to be generated the "balance" is shifted so that one diagonal pair is "on" for a larger amount of time during the PWM cycle resulting in "actual" current thru the motor. In this case the motor will brake AND regenerate when the voltage produced by the bridge is LESS than the motor produced by the EMF of the motor. The motor will never be shorted out.

Phew, I hope I got that right....don't quote me though!

/Henrik.

Aussie Barry
- 26th September 2013, 00:56
Hi Henrik,

Thank you for the detailed response.
Yes, I am using two of the PWM generators - both in complementary mode.

I have configured my PWM drive per your first description; I am NOT running it in locked antiphase mode.
I plan do code my program such that the operator (ie me) cannot reverse the motor direction instantly. If the motor is spinning clockwise and the operator flicks the switch to make it spin anti-clockwise, the program will not reverse the direction until the pulse train frequency from the encoder wheel reaches zero - the motor stops spinning. I could also add a short time delay between motor stop and reverse direction.

When the operator goes from one motor direction to "off" I plan to allow the motor to free spin rather than shorting the motor terminals via the two lower MOSFETS. This should negate any major issues with high current cycles being generated from the back EMF of the motor. If the "spin-down" time is too excessive I will look to ramp down the PWM duty cycle for a more controlled response.

The circuit will also have an "Emergency Stop" function. In this case the PWM signal is set to zero, the bottom two MOSFETS are turned on and maximum braking is applied to the motor. I should be able to arrange the circuit such that this condition does not fry any part of the H-Bridge but I figure if I really need to hit the Emergency Stop button (worse case scenario for personal safety) then I can live with having to re-build electronics rather than losing a limb, or worse.

Cheers
Barry
VK2XBP

HenrikOlsson
- 26th September 2013, 06:17
Hi Barry,
OK, it seems you've got it covered.
Keep us posted!

/Henrik.

Aussie Barry
- 26th September 2013, 09:37
Hi Henrik,

I have been doing some more research on locked antiphase and I see some benefits of this configuration for my application - but some disadvantages too.
Life is full of compromise, why would a motor speed controller design be any different? :)

Before I go off on a dead end:

1/. Can a locked antiphase arrangement be configured on a PIC18F2431 using the PCPWM Module? If not, can it be done via other hardware PWM modules within this PIC?
2/. I have read in other posts on the forum that your incPID routine will work with locked antiphase configuration. Please confirm this is the case.
3/. Will regenerative braking become a major issue to handle for a 180Vdc 1500W treadmill motor with a rectified mains supplied power supply?

As usual, I don't expect anyone to do all the work for me. What I am after is some yes/no type answers from people who have dealt with this type of project and maybe some guidance on the best way forward.
I do this type of work as a hobby and for the enjoyment of learning along the way but it is always better if I don't have to "re-invent the wheel" if someone has already "been there, done that!" :)

Cheers
Barry
VK2XBP

HenrikOlsson
- 26th September 2013, 11:23
Hi Barry,
First I want to stress that I'm in no way an expert on motor control, I only know what I've learned from my own experiments. There will certainly be things which I either don't know, is missing or even get wrong.


1/. Can a locked antiphase arrangement be configured on a PIC18F2431 using the PCPWM Module? If not, can it be done via other hardware PWM modules within this PIC?
Yes but as far as I know not the way you have it wired now. You need one PWM generator in complementary mode and wire it so that PWM0 drives the upper right and lower left switch while PWM1 drives the upper left and lower right switch.


2/. I have read in other posts on the forum that your incPID routine will work with locked antiphase configuration. Please confirm this is the case.
If you've read that you've probably also read that the incPID routine does not know, nor does it care, what it is controlling or what you do with the value it puts out - all it is doing is crunching numbers. It's up to you as the user of it to take its output and apply it accordingly. Be it locked antiphase PWM, basic "unipolar" PWM, sign magnitude PWM, a +/-10V DAC or whatever, it doesn't matter.


3/. Will regenerative braking become a major issue to handle for a 180Vdc 1500W treadmill motor with a rectified mains supplied power supply?
I don't think regenerative braking per se will be a problem but I think you need to make sure you don't allow "instant" reversals or stops - like you explained earlier. Never allow the setpoint value to change from one value to another "instantly", always apply ramping and I think you should be fine. Especially so since you don't really have an active current limiting scheme.

If you rectify your 240V mains you'll end up with almost twice the rated voltage of the motor....

I guess the only way to find out is try it. Be careful though!

/Henrik.

Aussie Barry
- 26th September 2013, 11:52
Thanks for the sanity check Henrik.

I appreciate your claim not to be a motor control expert but your experimental knowledge is still quantum leaps ahead of mine. I promise to take your comments on board and apply the concepts with due diligence in my designs :)

1/. OK, and understand. I need to redesign the power control PCB anyway because I want to move from phototransistor opto isolators to logic opto devices to reduce turn-on/turn-off times and thereby reduce dead-time.

2/. I thought that was the case - I just wanted to make sure there was a proven method of adjusting pid_Out around the 50% duty cycle setting.

3/. As described in my earlier post, potential for direction reversal will be eliminated by my program and instant stop available only in emergency situations.

I am aware that the rail voltage of my rectified mains power supply will be twice that of the treadmill motor. My original intention was to limit the PWM to 50% maximum such that the average voltage applied would be around the motor voltage rating. Yes, peak voltages will be higher but from my knowledge of treadmill motor drive configurations, these motors are used this way all the time. Actually, it was the fact that locked antiphase configurations only apply half rail voltage to the motor at any one time that sparked my interest to their use in my application - or have I got that concept all mixed up too?

Lots more reading and thinking to be done on my part but I will keep you updated with my progress milestones.

Cheers
Barry
VK2XBP

HenrikOlsson
- 26th September 2013, 12:31
Hi Barry,

1) OK. You can always run some mod wires on the one you've got to verify before committing to new FR4.

2) Yes. At startup you set your dutycycle register so that you get a 50% dutycycle output. If pid_Out is positive you ADD it to the 50% value, if pid_Out is negative you subtract the ABSolute value of pid_Out from the 50% value.

3) Well, in locked antiphase mode it's not only when YOU reverse direction. If the speed setpoint is changed (even though it is still in the same direction) the PID filter will react to this sudden error and, depending on the tuning of the filter, do more or less violent things with the output to bring the actual speed back to the setpoint. This can in reality mean that the polarity of the average voltage applied to the motor is reversed, ending up in series with the EMF. I'm not saying it's GOING to be an actual problem, just be aware of it. Of course you can always clamp the output to either positive or negative IF it turns out to be an issue in the future.

In locked antiphase mode the full rail voltage is always applied across the motor (except during the dead time period). It's either applied in one polarity or the other.
* If the duty cycle is 50% and the motor is stationary and you measure the average voltage across the motor you'll read 0V.
* If you use a differential probe and look at the voltage with a 'scope you'll see voltage swing of +/- the rail voltage.
* If you measure the average voltage from either motor terminal to ground (still at 50% dutycycle, stationary motor) you'll measure half the rail voltage.
* If the dutycycle is 0% or 100% (don't do that you'll probably fry the high side driver) the voltage across the motor will obviously be + or - the full rail voltage.

/Henrik.