DT-Ints latency and other interrupt conciderations
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:
Code:
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:
Code:
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:
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:
Code:
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.
Any possibilities of checking INTs within INTs
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.
one more question for Mr Henrik;
once again, thanks for the wonderful pid filter. im still troubleshooting, and my main problem is this;
Code:
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,
Code:
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.