The full source code can be found here
The interesting part is the Interrupt Service Routine (ISR). In the previous example I wasn't using a prescalar but in this one I am. I'm using a 1:8 prescalar which means the timer is increased at an eighth the rate it used to be. this allows the full movement range of the servo to be expressed in one byte.
The ISR implements a 4 state state machine. The states are
- initialDelay - this state turns on the servo and pauses for 0.25ms. This is a absolute shorted pulse that will ever be needed
- servoMinimumDelay - this state pauses for the minimum position of the servo. The SERVO1MIN variable is set to 13 for the Futaba servo. In my last post I discovered that the position I want to be the 0 position requires a pulse of 0.4ms. We have already delayed by 0.25ms in the previous state so we now need to delay by 0.15 ms to complete a 0.4ms delay. Delaying for 0x13 (19) timer ticks, with the prescalar set to 1:8 means we will delay for 19 * (0.01us * 8) = 0.152ms
- servoPositionDelay - the delay based on the required position of the servo. The position is subtracted from 0xFF because the timer triggers when it overflows (reaches 0xFF+1 or 256) . I could miss this line if I made position 0 the max position but having the subtraction means the minimum position is also the minimum delay. The only gotcha in this state is that if we are in position 0 we want to jump to the next state without waiting for the timer to expire
- operationalDelay - this state turns the pulse to the servo off and then sets the timer to delay for 18ms.
So far I haven't taken into account the time it takes to execute the ISR code. When the ISR is longer I'll probably have to modify the timers by a step or two to avoid jitter on the pulse but we'll see how it goes
...
...
ISR
;clear the interrupt
bcf INTCON, TMR0IF
bcf T0CON, TMR0ON ;disable timer
;which state?
movf CONTROL_STATE, w
addwf PCL, f
nop
nop
;delay for 0.25ms as the minimum position of any servo
goto initialDelay
;delay for the minimum for a selected servo
goto servoMinimumDelay
;delay for the position of the servo
goto servoPositionDelay
;delay for 18ms so we can do other stuff
goto operationalDelay
initialDelay
movlw 0xFF
movwf TMR0H
movlw 0xE1 ;256-(250us/8) = 256-31 - 225 = 0xE1
movwf TMR0L
bsf PORTB, RB1 ;turn on the servo control bit
goto ENDISR
servoMinimumDelay
movlw 0xFF
movwf TMR0H ;load high timer
movf SERVO1MIN , w ;load min position
sublw 0xFF ;prepare low byte for timer
movwf TMR0L ;load min delay into timer
goto ENDISR
servoPositionDelay
movf SERVO1POS,w
bz operationalDelay ;if we're at pos=0 don't delay
movf SERVO1POS,w
sublw 0xFF
movwf TMR0L
goto ENDISR
operationalDelay ;delay of 18ms that lets other things happen
bcf PORTB, RB1 ;turn off the servo control bit
;wait 18
movlw 0xF7 ;F7 is the high byte of the timer offset
movwf TMR0H ;write to the high byte first because the actual write occurs when the low byte is written
movlw 0x2E ;36S is the low byte of the timer offset
movwf TMR0L ;The high and low bytes are now written
movlw 0x00 ;reset state machine (0x00 because it will be incremented at the end
movwf CONTROL_STATE ;
decf SERVO1POS, f ;move the servo one step (for testing only)
ENDISR
incf CONTROL_STATE, f
incf CONTROL_STATE, f
incf CONTROL_STATE, f
incf CONTROL_STATE, f ;move to the next state
bsf T0CON, TMR0ON ;enable timer, we're good to go
RETFIE
No comments:
Post a Comment