PDA

View Full Version : DT-Ints latency and other interrupt conciderations



HenrikOlsson
- 28th December 2009, 16:04
Hi everybody,
(I appologise in advance for this lenghty post but I'll try to give as much info as I can up front).

I'm working on a DC servomotor drive using my incPID routine and DT-Ints on an 18F2431 running at 20Mhz. The input to the drive is step and direction signals, the step-pulse is connected to INT2 and trips a high priority interrupt, the direction signal to PortB.1. Originally I had the following code in the INT2 ISR:


DoStep:
If PortB.1 = 1 THEN 'Direction signal connected to PortB.1
Setpoint = Setpoint + 1 'Setpoint is a 32bit variable
ELSE
Setpoint = Setpoint - 1
ENDIF

This worked fine and I measured the execution time of ISR to 2-2.4uS depending on if the add or subtract code is executed - not to shabby. The problem was that it took 29uS from the rising edge of the step-pulse to the point where the ISR starts to execute. During testing I found that the code tracked the step-input up to 15kHz but above that it started to miss pulses. In practice 10kHz proved to be the limit, above that too much time was taken away from the PID-routine (which is triggered as a low priority interrupt by TMR2 at 1220Hz) resulting in an unstable servo loop.

So, with my VERY limited ASM skills I set out to change the DoStep ISR from PBP to ASM. I decided to not do the 32bit operations in the ISR and instead went to an 8bit "buffer variable". This not only made it possible for me to code the ISR but it'll also allow me to add features like electronic gearing etc in the future. The ASM ISR now looks like this:


ASM
DoStep
BSF PORTB,6 ;For timing purposes
BTFSS PORTB,1 ;Check direction signal
INCF _StepBuffer ;Increase
BTFSC PORTB,1 ;Check direction signal
DECF _StepBuffer ;Decrease
BCF PORTB,6
INT_RETURN
ENDASM

This alone dropped the latency from 29uS to 8.4uS and the ISR execution time to 1uS.

Is there anything more that can be done to...
A) Lower the latency further and...
B) improve my pathetic attempt at ASM coding above?


In the servo-loop ISR I then have the following code:


INTCON3.4 = 0 'Temporarily disable INT2 interrupt
TempStepBuffer = StepBuffer 'How many steps has arrived?
StepBuffer= 0 'Reset buffer
INTCON3.4 = 1 'Re-enable INT2 interrupt

The new target position is then calculated by adding or subtracting the ABS value of TempStepBuffer from the current target position.

Now I have reliable operation up to ~20kHz step frequency but above that it occasionally misses pulses. It can run at 25-30kHz for several minutes without missing a pulse but all of a sudden it does. The pulsetrain is generated by software on a PC so depending on the actual frequency the jitter can be more or less severe which may explain the occasional lost pulse. Never the less, I'd like it to perform better than this.... (100kHz preferably)

Apart from the DoStep ISR there's one more high priority interrupt (IC2QEIF_INT) which is fired when the 16 bit QEI counter rolls over. That ISR is still in PBP and looks like this:


RollOver:
IF QEICON.5 = 1 then
Position.Word1 = Position.Word1 + 1 'Position is a LONG
ELSE
Position.Word1 = Position.Word1 - 1
ENDIF
@ INT_RETURN


Can some kind person more knowlagable about ASM than me please show me how the RollOver ISR above could look in ASM?

How many cycles does it take for DT-Ints to "get out of" an ASM ISR?

Any other ideas what I can do to increase the performance? (Except going to 40MHz which will be done).

Again, sorry for the long post and many questions.

Thanks!
/Henrik.

Darrel Taylor
- 29th December 2009, 00:55
Hi Henrik,

It does take a lot of time to save/restore all of PBP's System variables.
So, PBP type interrupt handlers are not too good at high frequencies.

Fortunately, both of your interrupt routines aren't doing anything that requires System variables. And you can just change their "Type" to ASM, without actually writing them in ASM.

And your ASM routine would only count 8-bits, so I think it might be a bit limiting.

IF statements with "=", and simple addition/subtraction doesn't use PBP's system vars, so most interrupts that only need to count something can usually be written as PBP, then used as ASM.

HTH,

HenrikOlsson
- 29th December 2009, 08:37
Hi Darrel,
Many thanks, it does help a lot!

So, can I assume that it takes roughly the same time to "get out of" an ASM interupt as it takes to enter it, in this case 8.4uS? Then, theoretically, the max frequency would be 1/(2*8.4+1uS) = 56kHz - if all it had to do was servicing the INT2 interrupt - which unfortunately still isn't good enough :-( Even going to 40Mhz just barely gets me over the 100kHz target with not much time left to do anything.

I know the 8bit buffer variable may "look" limiting but because it is "read" by the servo-loop, which is currently run at 1220Hz, it means the actual limit would be ~155kHz - if I can get that I'd be happy. The only concern I have with this approach is that I need to disable the INT2 interrupt while reading and resetting the buffer. But if I'm not mistaken if an interrupt occurs during this time it will get "latched" and serviced as soon as I re-enable it again.

Thanks again Darrel! I'll give it a go during the day and see what I can get.

/Henrik.

PS. I have a comercially available drive that I bought which also uses the 18F2431, it has a claimed step-frequency of 400kHz - I wonder how they do it....

Darrel Taylor
- 29th December 2009, 10:05
Well let's see what we can do here ...

Since it's an 18F, with "qualified" ASM handlers ...
You can remove the remaining overhead by removing DT_INTS all together, and using an ASM ISR, written in PBP of course.

Something like this ...
DEFINE INTHAND _ISR
DEFINE NO_CLRWDT 1

;---------------------------------------------------------------------------
ISR:
IF INTCON3.1 THEN ' IF INT2 triggered
DoStep:
If PortB.1 = 1 THEN 'Direction signal connected to PortB.1
Setpoint = Setpoint + 1 'Setpoint is a 32bit variable
ELSE
Setpoint = Setpoint - 1
ENDIF
INTCON3.1 = 0 ' clear the int flag
ENDIF

RollOver:
IF PIR3.2 = 1 THEN ' if IC2QEIF triggered
IF QEICON.5 = 1 then
Position.Word1 = Position.Word1 + 1 'Position is a LONG
ELSE
Position.Word1 = Position.Word1 - 1
ENDIF
PIR3.2 = 0 ' clear the int flag
ENDIF
@ RETFIE FAST ; return from interrupt with shadow regs
Setpoint is handled as a LONG, but if you can use a BYTE sized result, you can change it to Setpoint.byte0 to reduce it by a few instructions.

The NO_CLRWDT drops a couple more instructions, but make sure the WDT is OFF.

Compiled, it looks like this, including both handlers ...
_ISR
btfss INTCON3, 001h
goto _RollOver
_DoStep
btfss PORTB, 001h
goto L00003
incf _Setpoint, F
clrf WREG
addwfc (_Setpoint) + 1, F
addwfc (_Setpoint) + 2, F
addwfc (_Setpoint) + 3, F
goto L00004
L00003
decf _Setpoint, F
clrf WREG
subwfb (_Setpoint) + 1, F
subwfb (_Setpoint) + 2, F
subwfb (_Setpoint) + 3, F
L00004
bcf INTCON3, 001h
_RollOver
btfss PIR3, 002h
goto L00005
btfss QEICON, 005h
goto L00007
incf _Position??WORD1, F
movlw 0
addwfc (_Position??WORD1) + 1, F
goto L00008
L00007
decf _Position??WORD1, F
movlw 0
subwfb (_Position??WORD1) + 1, F
L00008
bcf PIR3, 002h
L00005
RETFIE FAST ; return from interrupt with shadow regs


Hope that gets it closer.

HenrikOlsson
- 29th December 2009, 11:48
Thanks Darrel,
Now I'm a bit confused.... Will this allow me to still use DT-Ints for the low-priority interrupts? The actual servo-loop code is, as I said earlier, triggered as a low priority interrupt by TMR2 and I also have a second low priority interrupt for handling USART comms (which isn't used during the tests I've made so far).

Does the RETFIE FAST automatically restore the STATUS, W etc registers or is that not needed due to the "shadow regs"? As you can see I really suck at the low level stuff...

Before seeing your message I tried changing the RollOver ISR to type ASM but it didn't make much difference. Up to 20kHz it works fine, above that it starts to fall apart and I can't really see why.

Your assistance is much apprecited, thanks!

/Henrik.

sougata
- 29th December 2009, 14:12
Hi,

Darrel correct me if I am wrong, I am a bit rusty here.

The shadow registers are not memory mapped but are actually hardware registers, a single level deep hardware stack that can save your context (W,Status, BSR). Optional during <b>call, something s , </b> or a <b>return, something s , </b> and automatic when entering interrupt. You decide whether you want to restore them by Retfie Fast . So when a low priority interrupt (0x0018h) is interrupted by a high priority one, the context saved while entering the low priority is lost. Thus it works best if you are using only HP Ints or using it for only HP Ints.

HenrikOlsson
- 29th December 2009, 16:19
Hi again,
I'll start by quoting myself:

Before seeing your message I tried changing the RollOver ISR to type ASM but it didn't make much difference. Up to 20kHz it works fine, above that it starts to fall apart and I can't really see why.
Correction/clarification somewhere between 17.5-18kHz something happens which makes the TMR2 LP interrupt frequency start to drop. I guess the HP priority interrupts are stealing so much time that the TMR2 interrupts "runs over itself".

At 32kHz the high priority DoStep ISR still tracks the step pulses correctly but now the TMR2 ISR is only run at ~550Hz instead of 1220Hz which can be both heard and felt from the motor. Going higher than this (I tried 33kHz) completely unstabilizes the servo-loop so I don't know how high the INT2 interrupt will actually track the pulses.

/Henrik.

Darrel Taylor
- 29th December 2009, 19:09
I think that both INT2 and TMR2 interrupts should be HIGH priority.
The USART can be LOW priority, since servicing it is not really time critical.
But no, you won't be able to use DT_INTS for just the Low Priority.

Sougata is correct about the shadow registers...
They are only good for High Priority interrupts.
For Low priority ints you have to save W, STATUS and BSR in the ISR.

What are you doing with the USART handler?
Stuffing the bytes in a buffer?
<br>

HenrikOlsson
- 29th December 2009, 23:20
Hi,
At the moment I'm not doing anything with the USART interrupt - it's never been fired during these tests. But I'll need to use it for the "front end" later on - if I get there - and the is to stuff data into a buffer and signal the main program when a LF or CR arrives.

Just so we're all on the same page, here are the current interupts:
INT2_INT - High priority ISR in ASM
IC2QEIF_INT - High priority ISR in PBP but "declared" as ASM in the INT_LIST macro
TMR2_INT - Low priroity ISR in PBP
RX_INT - Low priotity ISR in PBP


I originally had the TMR2 interrupt as high priority but then I didn't get more than a few kHz before it started to miss pulses. The DoServo routine that is run as the TMR2 ISR is quite "heavy" since it does all the math including the PID routine:

DoServo: 'Interrupt handler starts here.
PortB.5 = 1 'Used for timing purposes
GetPosition:
'Since the position counter isn't double buffered we can get to situations
'where the lowbyte overflows between readings of the high and low byte. This
'is prevented by reading the high byte two times and compare the two readings.
'If they are the same then we're fine, if not re-read the registers.

PosHigh = POSCNTH 'Get high byte of counter
PosLow = POSCNTL 'Get low byte of counter
PosTemp = POSCNTH 'Get high byte again

If PosHigh - PosTemp = 0 then Goto Done 'Compare, if equal we're done.

PosHigh = POSCNTH 'If not, get values again.
PosLow = POSCNTL

Done:
'The high word of the Position variable is handled by the RollOver ISR that gets
'tripped by IC2QEIF when POSCNT = MAXCNT.

Position.Word0 = POSHIGH * 256 + PosLow 'Put high and lowbyte together.
Speed = Position - oldPosition 'Calculate current motor speed.

INTCON3.4 = 0 'Temporarily disable INT2 interrupt
TempStepBuffer = StepBuffer
StepBuffer = 0
INTCON3.4 = 1 'Re-enable INT2 interrupt.

If TempStepBuffer.7 = 1 then 'Step buffer is negative
Setpoint = Setpoint - ABS (TempStepBuffer)
Else
SetPoint = Setpoint + TempStepBuffer
Endif
pid_Error = Setpoint - Position 'Calculate error....

'Now we have our error-signal in the variable that the PID-routine expects
'so we call the PID-filter with a GOSUB. The filter will respond with the
'output in variable pid_out.

Gosub PID 'and send to PID filter
' (PID filter execution time measured to ~190uS worst case @20Mhz)

If pid_out.15 = 0 then 'If the output from the pid-filter
Direction = 1 'is positive we want to drive the
Duty = pid_Out 'motor forward.
Else
Direction = 0 'If it's negative we want to drive
Duty = ABS (pid_Out) 'it backwards.
Endif

CCP1CON.5 = Duty.1 'Set the two LSB's of the dutycycle-
CCP1CON.4 = Duty.0 'register. Then shift them out and
CCPR1L = Duty >> 2 'set the remainig 8 bits.

oldPosition = Position 'Store position for next interrupt.

ISRCount = ISRCount + 1
PortB.5 = 0

@ INT_RETURN ;ISR is complete, return from here.
The actual PID code isn't shown here, I've measured the execution time of the complete DoServo ISR and it seems to vary alot, from 160uS all the way up to 400uS.

So, I guess I'll need to stick with DT-Ints since re-writing the DoServo routine including the PID code in ASM would take me years. Question is what else, if anything, can be improved to increase the speed. The thing is that I was hoping to add more code to the DoServo routine but I guess that may not be a good idea.

Thanks alot guys!

/Henrik.

sougata
- 30th December 2009, 00:35
Henrik,

I wonder if there could be any hardware optimizations however I would just like to remind you:

Even if an Interrupt is Enabled (IE bit), the Flag (IF bit) always gets set. So it might just be possible to check within an interrupt that if other sources triggered. This enables you to pre-service that interrupt without a return then re-entry. This can save a lot of cycles with the penalty of a bit more code space.

Darrel Taylor
- 30th December 2009, 05:45
I may be continuing to miss the point, but ...

I still think TMR2 should be a high priority interrupt.
Except, it should be ASM, and ONLY collect the data required to calculate the error.

The PID and motor control should be running in the main loop, using the error info from the interrupt.

If the PID happens to miss an update, it won't matter that much because the error will still be updated the next time. And it's not like the PID loop can cancel the error on each update. It takes time. But at least the actual error measurements will be consistent at much higher frequencies.

Obviously, the "I" time period won't be constant, but I think they'll be close enough that you won't notice a difference. IMHO
Then the PID loop can run at it's maximum rate, while the interrupts run at their maximum rate, without interfering with each other.
<br>

sougata
- 30th December 2009, 06:01
I may be continuing to miss the point, but ...

I still think TMR2 should be a high priority interrupt.
Except, it should be ASM, and ONLY collect the data required to calculate the error.

The PID and motor control should be running in the main loop, using the error info from the interrupt.

<br>
I did not realize that the PID routine was running within the ISR. Henrik is it necessary ?? A better way could be accumulating error in the back (ISR) while processing error in main loop. I did tried your PID loop once but never closely worked on it. So excuse my ignorance if I am talking something analogue (illogical!!)

HenrikOlsson
- 30th December 2009, 09:51
Hi,
Yes the PID is currently run as part of the 1220Hz TMR2 ISR because I've always been taught that digital PID systems needs to be run at constant intervals and I can definetly see why that is neccessary.

You are correct that the important thing is to sample the data at precise intervals - if that's not done the effect will be the same as constantly changing the gains up and down. Or it will "look like" the speed of the motor isn't constant even though it actually is because the number of encoder counts per update is changing.

On the other hand, if the output of the filter isn't updated at constant intervals the motor doesn't have the same time to respond to input which is also the same as changing the gains but it might not be as bad as not sampling regularly. So, this is essentially why the PID routine is run inside the TMR2 ISR.

However, it's definitely worth a try to move it to the main routine and then have the TMR2 ISR signaling the main routine that new data is available, I'll try that! That would mean that the TMR2 ISR wouldn't (hopefully) contain any code that requires the PBP system variables so I'll be able to change its type to ASM although it's coded in PBP.


As a side note, I'm wondering what in the DoServo code it is that takes so much time. The PID code alone was measured to 190uS worsy case and it contains far more math than the rest of the DoServo code. Yet the complete DoServo routine sometimes takes 400uS, without it being interrupted. The 190uS was measured when compiled with PBP though, not PBPL but the PID code doesn't contain ANY operations on LONGS, does it matter?

Sorry, now I'm asking queastion that my scope can answer.... Thank you both!

/Henrik.

Ioannis
- 30th December 2009, 10:36
I believe that motor will not feel the difference ifthe PID routine is interrupted for a small fraction of time (in the order of microseconds).

The inertia is large enough even for small motors.

As Darrel always says, keep the ISR as fast as possible and return to your main routine for the processing. That way your PID will still run fast.

Ioannis

HenrikOlsson
- 30th December 2009, 11:43
Hi,
I agree, generally. BUT the "few uS" quickly add up... The servo code, including the PID, is supposed to run at 1220Hz and currently takes up to 400uS from "input to output". Let's say we're stepping at 50kHz and each interrupt takes 20uS. This means that during the 400uS it takes the servo code to execute there will be 20 interrupts, each taking 18uS for a total of another 360uS - the servo code now takes 760uS from "input to output".

However, the numbers will be the same no matter "where" the servo code is run - it's just a matter of finding which works the best. One benefit of moving it to the main routine is that it makes it at least possible for me to have the ISRs in "real" ASM which will help bring the latency down further. Don't know how I'll handle the USART though.... I love DT-Ints.... :-/

Thanks!
/Henrik.

HenrikOlsson
- 1st January 2010, 13:18
Hi,
Still messing around with this.... I changed the TMR2 interrupt to high priority and its type (as "declared" in the INT-List) to ASM but kept the code in PBP and moved the PID and PWM code to the main routine. The INT2 ISR now executes in 14uS. A semaphore signals the main routine to run the PID etc.

It works but there's no noticable increase in performance. At 10kHz the motor moves smoothly and silently, at 22.5kHz it's a lot more nervous and you can hear it kind of "ticking".

So I stopped playing with that for a while and instead tried to find what it is that eats up the CPU cycles in the servo-code and not surprisingly the PID-code is the biggest hog. What I find strange though is that when it's compiled with PBP 2.6 it runs in 40-125uS but when compiled with PBPL it takes 80-325uS to execute - quite a difference.

Why does it take more than twice the number of cycles to execute when compiled with PBPL? The PID code itself doesn't even use any longs.

Thanks!
/Henrik.

Darrel Taylor
- 1st January 2010, 19:33
With PBPL, all internal System and T? variables are LONGs.

All multiplications and divisions are done as Longs, even if they are byte vars.
Most things that get put in a System var with a CALL to the library are done as a Long.


IF ByteVar1 >= ByteVar2 THEN ; <-- Long
IF ByteVar1 = ByteVar2 THEN ; <-- Byte
WordVar = WordVar /* Const ; <-- Long
ByteVar1 = ByteVar1 + Const ; <-- Byte
ByteVar1 = ByteVar1 + (ByteVar2 >> 2) ; <-- Long
ByteVar1 = ByteVar1 + (ByteVar2 >> 1) ; <-- Byte

Having 32-bit vars is nice, ... unless you need speed.

Is there a particular reason you need PBPL?
Maybe we can cut that down to word sized math so you can go back to PBPW.
<br>

HenrikOlsson
- 1st January 2010, 23:23
Thanks Darrel,
That explains it for sure - but it kind of sucks.... :-/

Basically, the Position and the Setpoint variables are the only longs and the reason for PBPL.

The setpoint is calculated by adding or subtracting the number of steps that has arrived since the last servoloop. For the position I get the lower 16bits from the hardware QEI counter and the upper 16bits by keeping track of the number of over/underflows with the RollOver ISR.

Man, I'm painting myself into a corner here....

BTW, how come these two...

ByteVar1 = ByteVar1 + (ByteVar2 >> 2) ; <-- Long
ByteVar1 = ByteVar1 + (ByteVar2 >> 1) ; <-- Byte
...are treated different?

Thanks for all the help, I appreciate it!

/Henrik.

Darrel Taylor
- 1st January 2010, 23:50
Basically, the Position and the Setpoint variables are the only longs and the reason for PBPL.

The setpoint is calculated by adding or subtracting the number of steps that has arrived since the last servoloop. For the position I get the lower 16bits from the hardware QEI counter and the upper 16bits by keeping track of the number of over/underflows with the RollOver ISR.
If that's all there is ... it's a piece of cake for PBPW.


Man, I'm painting myself into a corner here....
I think not.


BTW, how come these two...

ByteVar1 = ByteVar1 + (ByteVar2 >> 2) ; <-- Long
ByteVar1 = ByteVar1 + (ByteVar2 >> 1) ; <-- Byte
...are treated different?


A single shift right compiles to a

BCF STATUS,C
RRF _ByteVar2,F
or, RRNCF for an 18F.

But for 2 or more shifts, it puts the value in a system reg, saves the number of bits to shift, then calls a library routine to actually do the shift. Therefore ... LONG.

Let me try to figure out the PBPW way. (should be easy)
I think all the info is already in this thread.
Anything missing that might help?
<br>

HenrikOlsson
- 3rd January 2010, 00:14
Thanks Darrel,

At the Piclist I found some signed 32bit math routines (http://www.piclist.com/techref/microchip/math/32bmath-ph.htm). I copied the add, subtract and the needed support routines (addba and negateb) as add and subtract is the only 32bit ops I need (at the moment at least).

After some trial and error I managed to get these working on the 18F2431 by replacing the skpnc with btfsc, STATUS, C and clrc with bcf STATUS, C. I also made it so that the REGA, REGB and MTEMP variables are declared as PBP variables and then preceeded by an underscore in the actual ASM code. I saved it as a separate file and INCLUDE it in the program together with DT-Ints and inc_PID.

I've replaced my LONG-ops in the DoServo code with calls to these routines, it compiles with PBPW and as far as I can currently see it runs correctly. I haven't tried to apply any step-pulses to see what it does "in real life" but I used TMR1 to time the DoServo routine (including the PID) and worst case I've seen so far is 207uS. ~100% increase in performance compared to when compiled with PBPL.

I've also sent various variables (like the 4 bytes 'building' the 32bit position variable and the pid_Error variable) to the PC and it all looks to be correct. I need to test this much much more but what I'm currently worried about is if there are some bank-switching or RAM issues that I need to take into consideration with these math routines.

I consider it pure luck that I've got it working this far and I'm afraid once I'll start to add more variables and code I'll break it - and I'll have no clue why.

/Henrik.

Darrel Taylor
- 3rd January 2010, 00:26
Great minds think alike ....
I had a similar idea, although started with different code from the net.

I guess it's good you got yours working, because I'm still struggling to convert it to 18F's.

I will continue anyway, because it's really cool.
It will be extremely useful for everyone else too.

And if you have problems with yours, it might fill the gap.

Best regards,

HenrikOlsson
- 3rd January 2010, 01:00
Many thanks Darrel,
Looking forward to see what you come up with as it will most definitely be better than my hacks.

Generally speaking, when using 18F devices and declaring variables in PBP but using them in ASM by preceeding them with an _underscore. Is there ANYTHING I should be doing in the ASM code to make SURE it'll work? When lookinh at ASM code I keep seeing BANKSEL, PAGESEL etc but don't know if it applies to 18F and/or when the variable is declared by PBP. I see in the datasheet for the 18F2431 (that I'm currently using) that the RAM is divided into banks and this is what I'm worried about.

As I said, I consider it pure luck that I've gotten this far - that and the fact that the forum has been unaccessable during the whole day which has kind of forced me to read up and do some trial and error - a good thing! ;-)

Anyway, it's almost 2AM here, time for some sleep I guess.

/Henrik.

boroko
- 3rd January 2010, 01:17
I have been watching with great interest.

This Thread and PID-filter routine (2nd try) (http://www.picbasic.co.uk/forum/showthread.php?p=81336) (page 1) PID-filter routine (2nd try) (http://www.picbasic.co.uk/forum/showthread.php?p=54846) (page 2) are very exciting.
I have a need for a small servo driver and I just can't bring myself to spending $115 for another Gecko 320. They are wonderful, but this app is just a small servo and doesn't justify it. The UHU looks perfect, but I really don't care to jump off of the PIC ship.
I'm afraid that all I can offer is my encouragement.

Thanks
Bo

Darrel Taylor
- 4th January 2010, 01:12
Henrik,

Well, my intent was to solve your problem ...
But I'm afraid I may have just muddied the waters even more.

I do believe this will help a great deal though ... fingers crossed.

N-Bit_Math.pbp
http://www.picbasic.co.uk/forum/showthread.php?t=12433

Best regards,

HenrikOlsson
- 5th January 2010, 13:54
Hi,
Today I've had somewhat of a breakthru :-)

I went back for another try of having the servo code in the main routine but this time I took out DT-Ints all together. Currently there are three interrupts but only one of the ISRs (DoStep) is actually ASM. The other two are PBP but apparently they aren't using any system variables so I seem to get away with it - for now.....

The latency for the DoStep ISR is now down to 2uS compared to 8.5uS before (and 29 from the beginning). This made it possible to get a step-rate of at least 60kHz without noticable problems - and this is still with 20Mhz osciallator. I'm still using the 32bit add & subtract routines I mentioned earlier but I plan to try N-Bit_Math once I get the next hurdle/challenge sorted:


With the servo/PID code now running in the main loop I'm trying to figure out how and where I should add the "front end". The front end needs to be able to send and recieve commands thru the USART as it'll be used to setup the various PID parameters, get current position and status etc.

Previously, when the servo/PID code was run as a low priority interrupt, I had some basic HSEROUT statements in the main routine sending out current position etc at intervals to help me debug the system. But now I had to take those out as they would otherwise hold off the execution of the servo/PID code.

My guess is that HSEROUT/IN is out of the question, or perhaps I could use them by creating my own serial buffers and then HSEROUT (and/or IN) one character per pass thru the main loop or something like that.

Any ideas on a good way to approach this?

Thanks!
/Henrik.

Ioannis
- 5th January 2010, 19:46
Either send or receive characters one by one using buffer and interrupt.

I know you are counting the usecs here but do not see how else can have serial communication. (When will PBP support 24F series??)

If it is not necessary to send or receive while running the PID routine, then maybe a button can branch to the setup routine, do what you want to do, then return to main program.

Ioannis

HenrikOlsson
- 5th January 2010, 20:30
Ioannis,
It'll have to be some kind of interrupt driven buffer routine for both sending and receiving, I think. I'd like to be able to send the current error while the system is running so the following error can be watched in "real time".

I'm currently looking at Microchips AN744 which seems to do pretty much what I need. "All" I have to do is figure out how it works and what I need to do get it working "in" PBP.

/Henrik.

DDDvvv
- 5th January 2010, 21:48
my two cents worth,

im getting 55us on the doservo code. is that possible? im using an 18f4431 @ 20mhz (couldnt get 40mhz going)

the scope is a 2211 tektronix. i thought there was something wrong with it, but its working perfect, and upto calibration.

i have the I_term function every 4th call, hence the long 73us on-time after every three loops on the main int code

here's an image of the scope screen.

all interupts are dt_interupts, in pbc.

thanks for all the ideas.

HenrikOlsson
- 5th January 2010, 22:43
Thats nice, I'm glad you got it working! Are you running the DoServo code as part of an Interrupt service routine? What are you using as "input"?

The long execution time (up to 400uS) I mentioned earlier was when the code was compiled with PBPL, when compiled with PBP it runs a lot faster. With PBP I've timed the PID code alone to as low as 40uS so your 55uS for the complete doServo seems plausible. The fact that it works points in that direction as well ;-)

The problem starts to appear when you:
1) Need 32 bit math for the position and setpoint variables (or anything else). Switching to PBPL makes the execution time of almost all arithemtic operations go WAY up. Without switching to PBPL you have to resort to ASM for the 32bit variables and math.

2) Using step- and direction as the input and DT-Ints with PBP interrupt handler for counting the step pulses. This pretty much kills it above a few kHz due the latency caused by saving and restoring the PBP system variables.

At least that's what I've found this past week. If you have yours running with step and direction input I'd love to hear what kind of steprate you can achieve.

Thanks!
/Henrik.

EDIT: Wait a minute, the "period time" is 55uS (ie. the servo code is run at 18kHz), the execution time is probably closer to 40uS or so. Are you really trying to run the servocode at 18kHz?

DDDvvv
- 5th January 2010, 23:15
thanks for the prompt reply.
right now im fighting with the step/dir int routine, which is not showing any sign of life. so im assuming that step/dir is not working.

whole routine is in the interrupt, (just as you had it)except the lcdout, which is the only command in the main routine.

yes, that is 18khz according to the scope. is that not good?

the only motor connection is the encoder and im using leds to show hpwm output. saves a lot on h-bridges and smoke from explosions.
will eventually move to the power control module once everything is figured out.

HenrikOlsson
- 6th January 2010, 07:49
Running the PID-loop at 18kHz is probably overkill, for motorcontrol somewhere between 1000 and 2500Hz seems to be common depending on inertia of the motor etc but YMMV.

Ioannis
- 6th January 2010, 13:38
Ioannis,
It'll have to be some kind of interrupt driven buffer routine for both sending and receiving, I think. I'd like to be able to send the current error while the system is running so the following error can be watched in "real time".

I'm currently looking at Microchips AN744 which seems to do pretty much what I need. "All" I have to do is figure out how it works and what I need to do get it working "in" PBP.

/Henrik.

Checking the RCIF/TXIF or RC1IF/TX1IF (for the 18Fseries) flags I think is enough to see if the UART is ready to send or has received a byte.

Of course the checks and byte read/write to the UART are not free and need your precious time each.

Maybe you have to move to a higher grade PIC like the 24F or even on the 32bit ones but then you also have to say good bye to PBP.

Ioannis

HenrikOlsson
- 6th January 2010, 14:02
Ioannis,
Nah, it should be doable with the 18F series - I'm almost there now. Like I said earlier I have a comercially available drive that is based on the 18F2431 and it works fine, step-rate specified to 400kHz (not verified) and USB interface thru some FTDI-chip.

I've got the code from AN744 working "inside" a very simple PBP program - 5 lines of PBP code and 200 lines of ASM. Now I only need to figure out HOW it works so I get it to do what I need. I may opt for interrupt driven RX and polled TX though.

Anyway, I think I'll start a separate thread for that...

Thanks!
/Henrik.

Ioannis
- 6th January 2010, 17:08
The AN I think is 774 not 744. The driver you have might be based on pure assembly. What clock does it have?

Ioannis

HenrikOlsson
- 6th January 2010, 18:04
Of course, you're right! I've mentioned it twice now and managed to get it wrong both times.

Obviously I don't have access to the source code for the drive but I was told by the developer that he was using MPLAB+BOOSTC (that probably means ASM for the time critical stuff and C for the rest). I don't know what oscillator speed it uses but I suspect it's 10*4MHz.

/Henrik.

Ioannis
- 6th January 2010, 18:22
It makes sense to be so. Are you clocking at this speed too?

Ioannis

HenrikOlsson
- 6th January 2010, 18:35
The protoype, on which I managed to get 60kHz+ yesterday, is still running on 20Mhz which makes me believe I should be able to reach my target of 100Khz even after adding the "front end" and some other features.

But right now these serial routines is making me go nuts....

Ioannis
- 6th January 2010, 19:04
Well, if you go from 20 to 40 MHz then your nice 60KHz is made with no pain 120KHz. That is more than 20% of your target, leaving you with a margin for the serial communication.

Why not try this first, to ease the pain?

Ioannis

DDDvvv
- 7th January 2010, 16:34
once again, thanks for the wonderful pid filter. im still troubleshooting, and my main problem is this;

MAIN:
IF POSITION < SETPOINT THEN ;decreasing the setpoint
pid_Error = setpoint - position ;calculate error
ENDIF
IF POSITION > SETPOINT THEN ;increasing the setpoint
PID_ERROR = 65535-(POSITION - SETPOINT) ;calculate error
endif
gosub pid ;take error to pid filter
DIRECTION = pid_out.15 ;determine direction
PID_TEMP = ABS PID_OUT
DUTY = PID_TEMP ;load duty cycle from pid_out
select case DIRECTION ;direction bit to control
case 0 ; hpwm 1 for forward or hpwm2
GOSUB FORWARD ; for reverse to corresponding
case 1 ; h-bridge circuit
GOSUB BACKWARD ;
END SELECT
LCDOUT $FE, $80, "POS=" ,DEC5 POSITION, " SP=" ,DEC5 SETPOINT; temporary
LCDOUT $FE, $C0, "DT=" ,DEC5 DUTY, " ERR=" ,DEC5 PID_ERROR ; temporary
goto Main


my calculated pid_error jumps from 65534 to 00001 , skipping two numbers; 65535, and 00000. i have the recommended position calculation,

position = 256*poscnth + poscntl this runs from a dt_interrupt (thanks Darrel Taylor). i tried to change setpoint and position variables to byte, instead of word, but pid filter works on word variables, and all calculations get messed up. any solutions? this has been my biggest headache on this project, for the last three weeks. and any help from anyone will be highly appreciated. thanks in advance.

HenrikOlsson
- 7th January 2010, 19:50
Hi,
It "skips" 0 because when Position=Setpoint (ie. Error should be 0) neither of your < and > evaluations are true so it won't even calculate the error. Why it skips 65535 I haven't figured out though. Can you explain why you have those two comparison statements?

All you really should need to do is pid_Error = Position - Setpoint or Setpoint - Position if you prefer it the other way around, it doesn't matter as far as the PID routine is concerned.

Let's say pid_Error = Position - Setpoint (all three are 16bit variables):
If Position is 10000 and setpoint is 9000 the error will be calculated to 1000
If Position is 9000 and setpoint is 10000 the error will be calculated to 64536 (-1000)
If Position is 1 and setpoint is 0 the error will be calculated to 1
If Position is 0 and setpoint is 1 the error will be calculated to 65535 (-1)

Just pass it to the PID-filter, it figures out the sign of the error.

/Henrik.

DDDvvv
- 7th January 2010, 20:55
THAT FIXED IT!!

man, thanks, and pardon my blindness. i did not know that the pid filter takes care of all the math! every little bit! the more reason of printing these pid filter code, framing it and hanging it on the wall next to the family portraits!!!

i had it like that to fix a problem, when using two hpwm's the duty cycle would shift over to pwm2, from pwm1, and vice versa at position roll-over, no matter where setpoint was. that problem has dissapeared also.

thats one hurdle over, and now over to implementing the power control on the same chip, to drive a h-bridge driver.

this chip has 8 pwm outputs (4 complementary pairs) ill try as much as possible to implement full motor control from this same chip, as opposed to using a separate driver, like the lmd18200.

thanks
david m

HenrikOlsson
- 7th January 2010, 21:21
That's great!
Yeah, the 18Fxx31 has some very cool features indeed. I intend to scale up as well once I get the basics going. My prototype uses the LMD18200 and I've abused it quite a bit, still haven't managed to let the smoke out of it though :-)

Keep us posted on the progress!
/Henrik.

DDDvvv
- 10th January 2010, 19:08
hi guys
spent all weekend, maintaining the marriage, NFL playoffs and designing the board for the servo.
attached is the board im trying to design. its nearly done, needs to be optimised, and copper flooded.
im using eagle pcb pro.
i decided to go the old school way; adjust pid variables using pots, and display them on a 20x2 lcd. later on, lcd will be replaced by serial communication, for serial lcd, or pc serial port.

there are seven ports representing; i_term, output_clamp, integrator_clamp, minimum duty cycle, proportional, integral and derivative.

i went to fire up my cnc pcb router, to make this board, and found out that my x-axis is completely dead. needs a new sla7078 unipolar driver. this is what im trying to replace with this servo project.

ill probably order some 7078's, or use press and peel blue (thats how i was making boards before the router came along)

im using the power control module on the 18f4431. its way much better than ccp pwm. its configured to use channels 0,1,2,3, in independent mode, so 0/1 produce same duty, and turn on at the same time, while 2/3 is off, and vice versa.
the fet drivers are ncp5181, of which i could find at mouser.com, and has a bunch of application notes on the web.
still optimising and trimming down the code, which is still undergoing extensive testing.
have fun everyone....

DDDvvv
- 10th January 2010, 19:16
cant figure out how to get a clearer image; wish we could post in .tiff format.

HenrikOlsson
- 11th January 2010, 06:11
Hi,
Cool! The picture is fine!
Personally I've been fighting with the serial buffer routines all weekend. I've got everything working in a separate program but when I tried to integrate it with the rest it started to act up.

After to much trying this, trying that I decided to start over, deleted the current version and went back to an older one. Turns out it was older than I thought so now I have to redo a lot of stuff I thought I had backed up - stupid....

boroko
- 30th January 2010, 13:07
Hi Henrik,

Discussion on the servo driver has been quiet for a while. I hope you haven't lost too much of your work!

As far as the comm, are you just using it to adjust the PID or is the intent to feed data out frequently?

Bo

HenrikOlsson
- 30th January 2010, 14:54
Hi Bo,
I managed to redo what I had lost fairly easily and have been working on it since then. I moved the serial send rountines to the main routine, sending one char each pass thru the routine - if there's nothing more important to do. The serial comms are mostly for setting the various parameters but also for sending internal variables out to help tuning. Currently I have a mode where it spits ot the current error at 50Hz.

I've added velocity feed-forward to the PID routine and I'm now working on acceleration feed-forward. Code size is my biggest concern at the moment, I'm up to 7400 words so it's starting to get tight - and I have many more features I'd like to add.

Have been adding and changing a lot of stuff withot retesting what kind of step-rates I can get so I may need to take a step back again - we'll see how it goes.

/Henrik.

EDIT: Duh....make that 7400BYTES - not words, plenty of space left in other words :-)

boroko
- 30th January 2010, 15:21
Cool!
UHU, watch out!
I have been trying to piece together a servo driver on a much smaller scale and attempting to glean from you and others the best way to do it. Speed, error, and mass are of minimal concern due to the application. I am going to drive an LMD18200 and 18F2431.
Looking forward to seeing your progress
Bo

HenrikOlsson
- 30th January 2010, 17:22
Still got a long way to go before it can compare with the UHU-chip, it's quite impressive how much Uli has been able to get that little AVR to do - without hardware encoder counter. But yeah, the long term plan is to be able to replace the UHU chip on my HP-UHU boards with this one - but that's still a long way to go.

My "developing platform" is exactly what you have, a 18F2431 and a LMD18200. I can't believe that the LMD18200 still lives after all the abuse I've put it thru.... Still using CCP1 for the PWM here but will change over to PCPWM module after re-designing the board.

So, how's your project going? What type of control, position, velocity? Command source, ananlog, step- and direction, high level serial commands?

/Henrik.

boroko
- 2nd February 2010, 12:07
HI Henrik,

The project is an extension of a prior post regarding electronic gearing. I have been using a Gecko 320 and it is way overkill for this app. The most that it will see is 500 Hz from the encoder at x4. It has very little mass and that makes the error handling easier. Like everyone else, I have way too many projects going on simultaneously, so this one is not getting the concentration that it needs. So far, I have been just following this and 3 or 4 other related threads and trying piece everything together to learn.

I hope to get back on it by the weekend.
Thanks for all your work, it is encouraging.

Bo

DDDvvv
- 10th February 2010, 12:15
hi everyone,
i burned my 18f4431 after reversing the 5v power supply (warning!!!>if using power on the icsp cable on the ql200 dev board, black cable is +5v and red cable is gnd!! thanks to the hongkong designers for defying convention!!!!

now im stuck using the 2431 until my mouser order arrives on thursday. this means that all parameters are to be adjusted through hserin to eeprom. i managed to get this working:


DATA @0, WORD $0140 ;P EEPROM
DATA @2, WORD $0020 ;I EEPROM
DATA @4, WORD $0025 ;D EEPROM
DATA @6, WORD $0000 ;INTEGRATOR CLAMP value in eeprom
DATA @8, WORD $01FF ;OUTPUT CLAMP value in eeprom
DATA @10, 8 ;I TERM value in eeprom
DATA @12, 5 ;MINIMUM DUTY value in eeprom

READ 0, pid_Kp.LOWBYTE
READ 1, PID_KP.HIGHBYTE
READ 2, pid_Ki.LOWBYTE
READ 3, PID_KI.HIGHBYTE
READ 4, pid_Kd.LOWBYTE
READ 5, PID_KD.HIGHBYTE
READ 6, pid_I_Clamp.LOWBYTE
READ 7, PID_I_CLAMP.HIGHBYTE
READ 8, pid_Out_Clamp.LOWBYTE
READ 9, PID_OUT_CLAMP.HIGHBYTE
READ 10, pid_Ti
READ 12, MIN_DUTY

i can display all these variables using a hserout, with no sweat. the problem starts when i try to adjust the values using hserin, from a terminal program on my pc (realterm) i tried using a serial dt-interrupt routine and it shows that pc and pic are communicating, but i cannot figure out how to grab the word variables from the receive buffer and load them onto eeprom. how are you guys doing it? im just playing around with this, until i get my 4431's of which ill be using adcin to change all variables.
here's what im trying:

on serial receive interrupt, read RCREG, TO RX_TEMP1
if rx_temp1 = "P" then read RCREG to rx_temp2, then read rcreg to rx_temp3
write value of rx_temp2 and rx_temp3 onto "p" in eeprom
thats the idea in pseudocode. i know im doing it all wrong, and please be gentle with a beginner. thanks for reading.

HenrikOlsson
- 10th February 2010, 16:15
Hi,
The way I do it is (basically) having an interrupt triggered by the USART when a character is received. The ISR takes the char and puts into an array that acts as a buffer, then it increments a "pointer" so it knows where in the array to put the next byte when it comes in. Finally it checks if the recieved char is a LF or CR - if it is it sets a flag, telling the main rountine that there's data to take care of.

The main routine sees the flag and looks at the first character in the array, if it's a "P" it knows it's supposed to set the proportional gain. Next it uses the "pointer" to determine how many charcters there are in the buffer and finally it uses ArrayRead to "convert" the correct number of ASCII characters in the buffer to a value.

In reallity it does a bit more than that but that's pretty much how I did it.

/Henrik.

DDDvvv
- 10th February 2010, 16:41
thanks for the prompt reply. it will probably take me 2 days to figure out some working code for just that, but i dont mind. i found out this is the best way to learn programming. meanwhile, im trying to design a h-bridge test board for a 2431. ill keep you updated on results.

thanks

DDDvvv
- 8th March 2010, 19:14
long time no see, henrik... hows your project going?
i managed to get my h-bridge working. im having trouble tuning the values. i spent all weekend trying to tune a motor at no load, and i kept getting overshoot, undershoot, oscillations, runaway, e.t.c.
im aware that tuning values are unique for every different model of motor, and i've tried all the tuning methods out there. im spoilt by those you-tube videos of pid motor position control.

how do you do it? ive tried the zieger-nichols, and every other tuning idea online, but to no avail. what kind of values do you use for your motors? ill take any parameters, no matter what motor you use, after spending a whole weekend tuning.

thanks

HenrikOlsson
- 8th March 2010, 20:34
Hi,
I usually (well always) do it "by feel"....

Leave I at 0 to begin with it, add P until you're starting to see severe overshoot or it starts to oscillate. When that happens start adding D to dampen it out. Once you dampened it you should be able to add some more P and then follow with even more D. Finally add I to get rid of any steady state error and increase the "stiffness".

On my "developmemnt system" with a smalish (30W) DC-motor, 500 line encoder (2000cpr) and LMD18200 H-bridge I have P:1500, I:100, D:2000, Ti:1

Remember that with very high gains there is a risk of overflowing the internal calculations (there's are no checks for that in PID routines). That would make the loop highly unstable and might cause the servo to run away. I'm not saying that is what is happening in your case, just that it's a possibility. It's also very imortant to set the output clamp to a proper value so you're not overflowing the PWM duty cylcle register.

What's the resolution of your encoder?

/Henrik.

DDDvvv
- 8th March 2010, 21:13
thanks for the reply. ill try your values. my encoder is only 100cpr on a yamamoto 24v motor, ripped off a photocopier machine. i have a bunch of other motors and encoders, upto 1500 cpr. does the cpr make a difference, other than more precision?

HenrikOlsson
- 8th March 2010, 21:34
Yes, it makes a hugh difference. Think about it...what if you only had four counts/revolution, then the motor could move 90 degrees without the system even knowing it. Same motor same settings but now with a 2000 counts/rev (500 lines) encoder, the 90 degree displacements now results in an error of 500 counts. This give the servo-loop a lot more "information" to work with which helps in stabalising the loop.

If that is a 25 line encoder (100cpr) then that may be a big part of your problem. Trying 'my' values with that motor/encoder probably isn't going tp help you one bit. If possible try with a higher resolution encoder.

/Henrik.

DDDvvv
- 8th March 2010, 22:45
oh no.. the encoder is 100 lines per revolution, giving a quadrature resolution of 400 cpr.

ill try the 1500 lines per revolution, thats 6000cpr! anyway, ill experiment with all the encoded motors that i have. thanks

DDDvvv
- 12th March 2010, 18:27
after much troubleshooting, i finally found my mistake. i forgot that the pwm module has always been running at 20khz.

i changed that to minimum allowed for 40mhz operation, which is 2.5khz, and the servo is running like a dream. thanks for all the ideas and tips.

im starting a ac servo thread, since i got the dc servo going , and i hope ill get the same help and ideas.

thanks guys.