![]() |
![]() |
![]() |
|
|
|||||||
| Micro Controllers Discuss all aspects of micro controllers - building them, coding them, etc. All controllers are welcome - PIC, BASIC, Z8 Encore!, etc. |
|
|
Thread Tools | Display Modes |
|
|
(permalink) |
|
Yes, you can drive 33 servos with 256 steps of resolution with a single PIC18 (and PIC16 is possible too). Also, this program supports USART with standard baudrates up to 115K. The program presently uses RX only and you must send 33 bytes per frame. Also, there must be more than 20ms between the start of each servo update frame.
To achieve 33 outputs you must the use of the internal oscillator, which is 32MHz, 8MHz x 4PLL. There are several PIC18's that will work easily with this code, and some very inexpensive (PIC18F4510). The program is about 750 words compiled using mikroBasic 6.0, which is well under the 2K limitation of the free demo version of the compiler. I will be adding the AutoBaud feature of the EUSART at a later time to this code. This is a great feature considering the possible variances of frequency using the internal oscillator. I almost forgot to say what I consider a most important feature. You can use up to 5 different types of servos, such as those with different start delays. This makes this program extremely versatile for a complex servo project that requires servos from different manufacturers. At the beginning of the program you can set each port to drive a specified time frame, albeit with the assumption that 1500us is a standard for center. Enjoy! Code:
program SERVO33_USART
'***********************************************************************************
'***********************************************************************************
' SERVO_33 SINGLE PIC SERVO DRIVER WITH USART TO 115Kbaud *
' Warren Schroeder December 6, 2007 *
' Program tested using PIC18F4525 @ 32MHz (Internal Osc + PLL) *
' Source Code is mikroBasic 6.0 (Demo Version will work) *
' *
' This code will manage 33 50Hz Servos @ 8-bit resolution. Servo positions *
' can be updated with USART communication up to 115KBaud. 33 outputs are *
' available by using the internal oscillator feature of this PIC and the x4 *
' PLL feature. Only USART RX is required to use this driver program, but *
' TX is available to the user if needed. Only a single PIC is needed! *
'***********************************************************************************
'***********************************************************************************
'
' These are the port pin assignments for the servo array:
' servo[0] - PORTA.0
' servo[1] - PORTA.1
' servo[2] - PORTA.2
' servo[3] - PORTA.3
' servo[4] - PORTA.4
' servo[5] - PORTA.5
' servo[8] - PORTA.6
' servo[7] - PORTA.7
' servo[8] - PORTB.0
' servo[9] - PORTB.1
' servo[10]- PORTB.2
' servo[11]- PORTB.3
' servo[12]- PORTB.4
' servo[13]- PORTB.5
' servo[14]- PORTB.6
' servo[15]- PORTB.7
' servo[16]- PORTC.0
' servo[17]- PORTC.1
' servo[18]- PORTC.2
' servo[19]- PORTC.3
' servo[20]- PORTC.4
' servo[21]- PORTC.5
' PORTC.6 = USART TX
' PORTC.7 = USART RX
' servo[22]- PORTD.0
' servo[23]- PORTD.1
' servo[24]- PORTD.2
' servo[25]- PORTD.3
' servo[26]- PORTD.4
' servo[27]- PORTD.5
' servo[28]- PORTD.6
' servo[29]- PORTD.7
' servo[30]- PORTE.0
' servo[31]- PORTE.1
' servo[32]- PORTE.2
' PORTE.3 = MCLR or Input Only
'***********************************************************************************
symbol RXFlag = PIR1.RCIF
symbol T0Flag = INTCON.TMR0IF
symbol T1Flag = PIR1.TMR1IF
symbol T1Run = T1CON.TMR1ON
'***********************************************************************************
' Each port can be customized for initial start delay and 1/256 timing
'***********************************************************************************
const
T1_20 as word = 65536-40000+6 ' 20ms + timer1 stop compensation
portAdelay as word = 600 * 8 ' in microseconds * 8
portBdelay as word = 700 * 8
portCdelay as word = 800 * 8
portDdelay as word = 900 * 8
portEdelay as word = 1000 * 8
portAcycle as byte = (12000-portAdelay) >> 7 ' in microseconds * 8
portBcycle as byte = (12000-portBdelay) >> 7
portCcycle as byte = (12000-portCdelay) >> 7
portDcycle as byte = (12000-portDdelay) >> 7
portEcycle as byte = (12000-portEdelay) >> 7
'***********************************************************************************
' Variable Declarations
'***********************************************************************************
dim
T0 as word absolute $FD6 ' address of TMR0L
T1 as word absolute $FCE ' address of TMR1L
FSR_0 as word absolute $FE9 ' address of FSR0L
FSR_1 as word absolute $FE1 ' address of FSR1L
FSR_2 as word absolute $FD9 ' address of FSR2L
servo as byte[33] absolute $15 ' access ram & past system vars
rxbuffer as byte[33] ' usart rx array
endofbuffer as word
loops as byte
'***********************************************************************************
' Interrupt Service Routine
'***********************************************************************************
sub procedure interrupt
'**************
' PORTA
'**************
LATA = Not(LATA) ' turn on all servos on PortA (8)
loops = 0 ' 256 counts.. rollover to original value
TMR0H = Not(Hi(portAdelay))
TMR0L = Not(portAdelay) ' initial delay
T0FLag = 0 ' clear overflow flag
ASM
delayAloop:
btfsc PIR1, RCIF,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
btfss INTCON, TMR0IF,0 ' check for overflow
bra delayAloop
loopAtimeout:
btfss INTCON, TMR0IF ' wait for 1/256 cycle to finsih
bra loopAtimeout
End ASM
TMR0H = Not(Hi(portAcycle))
TMR0L = Not(portAcycle) ' 1/256 cycle time
T0Flag = 0
ASM
portAmask: ' VERY efficient PWM masking routine
movlw 0
decfsz _servo+0, 1,0
iorlw 1
decfsz _servo+1, 1,0
iorlw 2
decfsz _servo+2, 1,0
iorlw 4
decfsz _servo+3, 1,0
iorlw 8
decfsz _servo+4, 1,0
iorlw 16
decfsz _servo+5, 1,0
iorlw 32
decfsz _servo+6, 1,0
iorlw 64
decfsz _servo+7, 1,0
iorlw 128
andwf LATA, 1,0
btfsc PIR1, RCIF,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
decfsz _loops, 1,0 ' 256 loops yet?
bra loopAtimeout
End ASM
'**************
' PORTB
'**************
LATB = Not(LATB) ' turn on all servos on PortB (8)
loops = 0 ' 256 counts.. rollover to original value
TMR0H = Not(Hi(portBdelay))
TMR0L = Not(portBdelay) ' initial delay
T0Flag = 0 ' clear overflow flag
ASM
delayBloop:
btfsc PIR1, RCIF,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
btfss INTCON, TMR0IF,0 ' check for overflow
bra delayBloop
loopBtimeout:
btfss INTCON, TMR0IF,0 ' wait for 1/256 cycle to finsih
bra loopBtimeout
End ASM
TMR0H = Not(Hi(portBcycle))
TMR0L = Not(portBcycle) ' 1/256 cycle time
T0Flag = 0
ASM
portBmask: ' VERY efficient PWM masking routine
movlw 0
decfsz _servo+8, 1,0
iorlw 1
decfsz _servo+9, 1,0
iorlw 2
decfsz _servo+10, 1,0
iorlw 4
decfsz _servo+11, 1,0
iorlw 8
decfsz _servo+12, 1,0
iorlw 16
decfsz _servo+13, 1,0
iorlw 32
decfsz _servo+14, 1,0
iorlw 64
decfsz _servo+15, 1,0
iorlw 128
andwf LATB, 1,0
btfsc PIR1, RCIF,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
decfsz _loops, 1,0 ' 256 loops yet?
bra loopBtimeout
End ASM
'**************
' PORTC
'**************
LATC = Not(LATC) ' turn on all servos on PORTC (6)
loops = 0 ' 256 counts.. rollover to original value
TMR0H = Not(Hi(portCdelay))
TMR0L = Not(portCdelay) ' initial delay
T0Flag = 0 ' clear overflow flag
ASM
delayCloop:
btfsc PIR1, RCIF,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
btfss INTCON, TMR0IF,0 ' check for overflow
bra delayCloop
loopCtimeout:
btfss INTCON, TMR0IF,0 ' wait for 1/256 cycle to finsih
bra loopCtimeout
End ASM
TMR0H = Not(Hi(portCcycle))
TMR0L = Not(portCcycle) ' 1/256 cycle time
T0Flag = 0
ASM
portCmask: ' VERY efficient PWM masking routine
movlw 192 ' mask out RX and TX
decfsz _servo+16, 1,0
iorlw 1
decfsz _servo+17, 1,0
iorlw 2
decfsz _servo+18, 1,0
iorlw 4
decfsz _servo+19, 1,0
iorlw 8
decfsz _servo+20, 1,0
iorlw 16
decfsz _servo+21, 1,0
iorlw 32
andwf LATC, 1,0
btfsc PIR1, RCIF,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
decfsz _loops, 1,0 ' 256 loops yet?
bra loopCtimeout
End ASM
'**************
' PORTD
'**************
LATD = Not(LATD) ' turn on all servos on PortD (8)
loops = 0 ' 256 counts.. rollover to original value
TMR0H = Not(Hi(portDdelay))
TMR0L = Not(portDdelay) ' initial delay
T0Flag = 0 ' clear overflow flag
ASM
delayDloop:
btfsc PIR1, RCIF,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
btfss INTCON, TMR0IF,0 ' check for overflow
bra delayDloop
loopDtimeout:
btfss INTCON, TMR0IF,0 ' wait for 1/256 cycle to finsih
bra loopDtimeout
End ASM
TMR0H = Not(Hi(portDcycle))
TMR0L = Not(portDcycle) ' 1/256 cycle time
T0Flag = 0
ASM
portDmask: ' VERY efficient PWM masking routine
movlw 0
decfsz _servo+22, 1,0
iorlw 1
decfsz _servo+23, 1,0
iorlw 2
decfsz _servo+24, 1,0
iorlw 4
decfsz _servo+25, 1,0
iorlw 8
decfsz _servo+26, 1,0
iorlw 16
decfsz _servo+27, 1,0
iorlw 32
decfsz _servo+28, 1,0
iorlw 64
decfsz _servo+29, 1,0
iorlw 128
andwf LATD, 1,0
btfsc PIR1, RCIF,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
decfsz _loops, 1,0 ' 256 loops yet?
bra loopDtimeout
End ASM
'**************
' PORTE
'**************
LATE = Not(LATE) ' turn on all servos on PORTE (3)
loops = 0 ' 256 counts.. rollover to original value
TMR0H = Not(Hi(portEdelay))
TMR0L = Not(portEdelay) ' initial delay
T0Flag = 0 ' clear overflow flag
ASM
delayEloop:
btfsc PIR1, RCIF,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
btfss INTCON, TMR0IF,0 ' check for overflow
bra delayEloop
loopEtimeout:
btfss INTCON, TMR0IF,0 ' wait for 1/256 cycle to finsih
bra loopEtimeout
End ASM
TMR0H = Not(Hi(portEcycle))
TMR0L = Not(portEcycle) ' 1/256 cycle time
T0Flag = 0
ASM
portEmask: ' VERY efficient PWM masking routine
movlw 0
decfsz _servo+30, 1,0
iorlw 1
decfsz _servo+31, 1,0
iorlw 2
decfsz _servo+32, 1,0
iorlw 4
andwf LATE, 1,0
btfsc PIR1, RCIF,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
decfsz _loops, 1,0 ' 256 loops yet?
bra loopEtimeout
End ASM
T1Run = 0 ' stop timer1
T1 = T1 + T1_20 ' reload timer1
T1Run = 1 ' restart timer1
T1Flag = 0 ' clear interrupt flag
end sub
'***********************************************************************************
' Initialize Ports, Timers, and Other Registers
'***********************************************************************************
sub procedure general_setup
LATA = 0
LATB = 0
LATC = 0
LATD = 0
LATE = 0
TRISA = 0
TRISB = 0
TRISC = 0
TRISD = 0
TRISE = 0
ADCON1 = 15 ' disable ADC's
CMCON = 7 ' disable Comparators
endofbuffer = @rxbuffer + 33
FSR0ptr = @servo
While FSR0ptr < (@servo + 33)
POSTINC0 = 128 ' initialize all servo values to 0 position
Wend
end sub
sub procedure timers_setup
INTCON = 192 ' enable GIE & PEIE
T1CON = 32 ' prescaler=4, timer off
T1 = 0 ' clear Timer1
PIE1.TMR1IE = 1 ' enable Timer1 Interrupt
PIR1.TMR1IF = 0 ' clear Timer1 Interrupt Flag
T1CON.TMR1ON = 1 ' start Timer1
T0CON = 8 ' no prescaler
T0CON.TMR0ON = 1 ' start Timer0
end sub
'***********************************************************************************
' Main Program
'***********************************************************************************
main:
While OSCCON.OSTS = 0 ' wait for oscillator startup flag
Wend
OSCCON = $70 ' internal primary oscillator @ 8MHz
OSCTUNE.6 = 1 ' enable x4 PLL for internal oscillator
general_setup
timers_setup
USART_Init(115200) ' any baudrate up to 115K is allowed
FSR_2 = @rxbuffer
While true
While FSR_2 < endofbuffer ' FSR2 is RX servobuffer
While RXFlag = 0 ' wait for new RX byte
Wend
While TMR1H = 255 ' avoid trasfer if close to interrupt
Wend ' max possible delay is 256*.5us= 128us
If RXFlag = 1 Then
POSTINC2 = RCREG ' save RX byte in array
End If
Wend ' when 30 RX bytes move on
FSR_2 = @rxbuffer ' reset RX buffer
FSR_1 = @rxbuffer ' setup tranfer of RX array to
FSR_0 = @servo ' servo work array
While FSR_1 < endofbuffer
POSTINC0 = inc(POSTINC1) ' transfer RX buffer to work values.. add 1
Wend
Wend
end.
|
|
|
|
|
|
|
(permalink) |
|
Very nice Warren. An elegant solution.
Would you care to review or critique a 'method' I came up with yesterday? The whole ISR 'engine' is something like 35 instructions. Here's the feature list; Code:
;****************************************************************** ; * ; K8LH Hi-Performance 33 Channel Soft Servo Controller Method * ; =========================================================== * ; (C) Copyright 2007, Micro Applications Consultants * ; * ; <> up to 115.2 kbaud serial input, 256 byte circular buffer * ; <> single or multi channel serial update command structure * ; <> 33 pwm "Servo" and or digital "on/off" channel outputs * ; <> Servo pulse widths of 1 to 2559 usecs in 1 usec steps * ; <> a channel width value of 0 is a digital '0' output * ; <> a channel width value of 2560 is a digital '1' output * ; <> only 1 cycle period jitter (125 ns, interrupt latency) * ; <> zero pulse width jitter (no pulse timing variations) * ; * ; 4.0 msec Timer 2 interrupts provide five 8-bit output group * ; intervals per 20 msec servo period (32 MHz clock). Not all * ; output bits are used in all output groups. * ; * ; ch group 0 out ch group 1 out ch group 2 out * ; ------------------ ------------------ ------------------ * ; 00 Servo[0,0] RA0 08 Servo[1,0] RB0 16 Servo[2,0] RC0 * ; 01 Servo[0,1] RA1 09 Servo[1,1] RB1 17 Servo[2,1] RC1 * ; 02 Servo[0,2] RA2 10 Servo[1,2] RB2 18 Servo[2,2] RC2 * ; 03 Servo[0,3] RA3 11 Servo[1,3] RB3 19 Servo[2,3] RC3 * ; 04 Servo[0,4] RA4 12 Servo[1,4] RB4 20 Servo[2,4] RC4 * ; 05 Servo[0,5] RA5 13 Servo[1,5] RB5 21 Servo[2,5] RC5 * ; 06 Servo[0,6] RA6 14 Servo[1,6] RB6 -- Servo[2,6] --- * ; 07 Servo[0,7] RA7 15 Servo[1,7] RB7 -- Servo[2,7] --- * ; * ; ch group 3 out ch group 4 out * ; ------------------ ------------------ * ; 22 Servo[3,0] RD0 30 Servo[4,0] RE0 * ; 23 Servo[3,1] RD1 31 Servo[4,1] RE1 * ; 24 Servo[3,2] RD2 32 Servo[4,2] RE2 * ; 25 Servo[3,3] RD3 -- Servo[4,3] --- * ; 26 Servo[3,4] RD4 -- Servo[4,4] --- * ; 27 Servo[3,5] RD5 -- Servo[4,5] --- * ; 28 Servo[3,6] RD6 -- Servo[4,6] --- * ; 29 Servo[3,7] RD7 -- Servo[4,7] --- * ; * ; * ; ISR 41 words, 20569 cycles, 2572 usecs, 64.3% mcu 'overhead' * ; * Last edited by Mike, K8LH; 23rd December 2007 at 12:11 PM. |
|
|
|
|
|
|
(permalink) |
|
Mike,
I would be interested to see your version. Mike. |
|
|
|
|
|
|
(permalink) |
|
Mike,
Your other code certainly demonstrates your mastery of the time-slice. So I have little doubt that the code behind those specs will fall short of your capability. From your description I am not clear on something. Does each servo have a 2 byte value, or are you updating with just a single byte? |
|
|
|
|
|
|
(permalink) |
|
The Servo[] array is type INT (word) and each element holds a 16 bit number in the range of 0 through 2560, inclusive...
The "output group update" code uses byte size data to update the output port at 1 usec intervals from interval 0 through interval 2559... Last edited by Mike, K8LH; 23rd December 2007 at 12:08 PM. |
|
|
|
|
|
|
(permalink) |
|
I'm intrigued. Maybe using vertical counters? Would be great to see the code.
|
|
|
|
|
|
|
(permalink) |
|
Nope, I'm not using vertical counters.
Basically I update one 'group' output port every 4.0 msec interrupt cycle at 1 usec intervals from a 2560 byte RAM array. Since the array element index is directly proportional to the 1 usec timing interval, it's super fast and eazy to place 8 servo "off" bits into the array using each servo pulse width value as the array index. I've attached the assembly language ISR "engine" (only 35 instructions) and a pseudo BASIC code example of the algorithm. Please tell me if it makes sense? Mike Code:
; ; pseudo BASIC algorithm example (C) 2007, K8LH ; ; Dim n As Byte ' index ; Dim group As Byte ' port group, 0..4 ; Dim Servo(5,8) As Word ' five 8 bit port groups ; ' element values of 0..2560 ; Dim WBuff(2561) As Byte ' work buffer ; ; Interrupt example ' 4.0 msec interrupts ; PIR1.TMR2IF = 0 ' clear Timer 2 interrupt flag ; ; Mask = %00000001 ' servo channel bit mask ; For n = 0 To 7 ' add 8 servo "off" bits to buffer ; WBuff(Servo(group,n)) = WBuff(Servo(group,n)) Or Mask ; Mask = Mask << 1 ' shift bit for next channel ; Next ; ; INDF1 = @LATA + group ' indirect access to group port ; group = (group + 1) Mod 5 ' bump group for next interrupt ; ; WBuff(0) = WBuff(0) Xor (Not INDF1) ' prep 1st XOR value ; ; For n = 0 to 2559 ' exactly 1 usec iterrations ; INDF1 = INDF1 ^ WBuff(n) ' 1 usec output update step ; WBuff(n) = 0 ' zero buffer after using it ; RxCheck ' cache incoming Rx character ; Next ' ; End Interrupt ; Code:
;--< variables >---------------------------------------------------
Group equ 0x000 ; u08, values of 0..4
GetPtr equ 0x001 ; u16, circular buf 'get' pointer
WBuff equ 0x100 ; u08, WBuff[0..2559], 100..AFF
OnBits equ 0xB00 ; u08, WBuff[2560] 1 byte
Servo equ 0xB80 ; u16, Servo[5,8], 80 bytes...BCF
RBuff equ 0xC00 ; Rx buffer, C00..CFF
WSize equ WBuff+d'2560' ; work buffer end address
; *
; ISR 41 words, 20569 cycles, 2572 usecs, 64.3% mcu 'overhead' *
; *
org 0x0000
Vect_IntHi
;
; prep 2560 byte work buffer for current 8 bit output group
;
; each channel pulse width value of 0-2559 is used as a buffer
; array index (or time interval index?) with the channel "off"
; or "toggle" time represented as a single '1' bit within that
; buffer location. note the special case where a digital "on"
; channel pulse width value is 2560. in this case the '1' bit
; is placed one byte beyond the work buffer which prevents the
; bit from being processed (the output isn't toggled "off").
;
; example buffer setup for group '0' (LATA)
; 76543210
; chan 00 RA0 Servo[0,0] = 1500 --> WBuff[1500] = '00000001'
; chan 01 RA1 Servo[0,1] = 2200 --> WBuff[2200] = '00000010'
; chan 02 RA2 Servo[0,2] = 1750 --> WBuff[1750] = '00000100'
; chan 03 RA3 Servo[0,3] = 1500 --> WBuff[1500] = '00001001'
; chan 04 RA4 Servo[0,4] = 1550 --> WBuff[1550] = '00010000'
; chan 05 RA5 Servo[0,5] = 1500 --> WBuff[1500] = '00101001'
; chan 06 RA6 Servo[0,6] = 2559 --> WBuff[2559] = '01000000'
; chan 07 RA7 Servo[0,7] = 0 --> WBuff[0] = '10000000'
;
; Mask = %00000001 ' servo channel bit mask
; For n = 0 to 7 ' add servo "off" bits to buffer
; WBuff(Servo(Group,n)) = WBuff(Servo(Group,n)) Or Mask
; Mask = Mask << 1 ' shift mask for next channel
; Next
;
; 20 words, 83 cycles, 10.375 usecs (isochronous)
;
lfsr 0,Servo ; FSR0 = @Servo[0,0]
swapf Group,W ; W = Group*16 (0,16,32,48,64)
addwf FSR0L,F ; FSR0 = @Servo[Group,0]
movlw b'00000001' ; servo channel bit mask
lpa movff POSTINC0,FSR1H ; servo value as WBuff index
incf FSR1H,F ; add the 100h buffer offset
movff POSTINC0,FSR1L ; FSR1 = @WBuff[pulse_width]
iorwf INDF1,F ; set the servo "off time" bit
rlcf WREG,W ; all 8 servos done?
bnz lpa ; no, branch (do next), else
lfsr 1,LATA ; FSR1 = @LATA
movf Group,W ; add Group number, 0..4
addwf FSR1L,F ; FSR1 = @LATx 'group' latch
btfsc Group,2 ; increment Group
setf Group ;
incf Group,F ; Group = 0..4
;
; 2560 step 2560 usec 8 channel group output update routine
;
; update the output group port from the 2560 byte work buffer
; at 1 usec intervals. outputs are toggled from 'on' to 'off'
; at the correct interval as we encounter each '1' bit in the
; work buffer array. each byte in the work buffer is cleared
; during the process in preperation for the next output group
; cycle.
;
; example output group update sequence excerpt
;
; WBuff[1499] = '00000000' --> LATA '11111111'
; WBuff[1500] = '00001001' --> LATA '11110110' RA0,RA3 off
; WBuff[1501] = '00000000' --> LATA '11110110'
; WBuff[1502] = '00000010' --> LATA '11110100' RA1 now off
;
; 21 words, 20486 cycles, 2560.75 usecs (isochronous)
;
comf INDF1,W ; invert output latch bits to
xorwf WBuff,F ; prep initial port XOR value
lfsr 0,WBuff ; FSR0 = @WBuff[0]
lpb movf INDF0,W ; WREG = WBuff[time_interval]
xorwf INDF1,F ; 1 usec output update 'step'
clrf POSTINC0 ; clear the buffer location
btfsc PIR1,RCIF ; UART Rx char? no, skip, else
movff RCREG,POSTINC2 ; add char to circular buffer
movlw high(RBuff) ; crude but safe %256
movwf FSR2H ; keep it circular (C00..CFF)
movf INDF0,W ; WREG = WBuff[time_interval]
xorwf INDF1,F ; 1 usec output update 'step'
clrf POSTINC0 ; clear the buffer location
nop ; burn off 1 cycle
movf FSR0H,W ; time_interval index hi byte
xorlw high(WSize) ; all 2560 output steps?
bnz lpb ; no, branch, else
bcf PIR1,TMR2IF ; clear timer 2 interrupt flag
retfie FAST ; exit interrupt service routine
;
Last edited by Mike, K8LH; 29th December 2007 at 07:10 PM. |
|
|
|
|
|
|
(permalink) |
|
Mike,
Very nice and it makes perfect sense. I had thought about using a buffer and writing it straight to the port but hadn't thought of doing the xor like that. It also looks like you've used all the available resources. It actually looks as though the hardware was almost designed for that code. I assume this will only work with the newer chips that can use the pll with the internal oscillator to get the 32Meg clock or an older one with an external crystal. Mike. |
|
|
|
|
|
|
(permalink) | |
|
Quote:
The beauty of the concept, in my opinion, is that you only need to place eight '1' bits in that buffer no matter how big it is. Have fun. Mike Last edited by Mike, K8LH; 11th December 2007 at 12:58 PM. |
||
|
|
|
|
|
(permalink) |
|
Mike,
That is a unique, and may I say, very optimized approach to the project. As with all your ideas I like it very much. Warren |
|
|
|
|
|
|
(permalink) |
|
Mike,
I don't think it is possible with a 16F series. The biggest chips only have 368 bytes of ram. I guess I should really make the switch to the 18 series. Mike. |
|
|
|
|
|
|
(permalink) |
|
Mike's great piece of code encouraged me to at least try to optimize my original code in mikroBasic. Still I could not meet quite the ISR size of Mike's code for my ISR methods.
I want to add here that I consider Mike's code a real masterpiece of ingenuity and smart coding. Here is an ASM version of my original ISR code: Code:
servo equ 0x000
sbuff equ 0x028
cyccounter equ 0x042
portcounter equ 0x043
initdelay equ 0x044 ; preloaded in setup
cycdelay equ 0x045 ; preloaded in setup
portcount equ 0x046 ; preloaded in setup
;******************************************************************************
;***** 33 Servos @ 8-bit Resolution
;***** Timer0 maintains initial delay (8-bit mode, prescaler=32, 4us)
;***** Timer1 maintains 20mS Interrupt Cycle (no prescaler, 20000us)
;***** This ISR example assumes 600us intial delay + 7us per step
HighInterrupt:
lfsr 0,servo
lfsr 1,LATA
movff portcount,portcounter ; load portcounter with # of ports
servoport_on
comf INDF1,0 ; turn on whole port
init_delay
bsf T0CON,3,0 ; prescaler=32
movff initdelay,TMR0L ; preload timer0 with init delay
bcf INTCON,TMR0IF
bra timeout
servo_update
movlw .0 ; servo masking to port
decfsz INDF0+0,f,0
iorlw .1
decfsz INDF0+1,f,0
iorlw .2
decfsz INDF0+2,f,0
iorlw .4
decfsz INDF0+3,f,0
iorlw .8
decfsz INDF0+4,f,0
iorlw .16
decfsz INDF0+5,f,0
iorlw .32
decfsz INDF0+6,f,0
iorlw .64
decfsz INDF0+7,f,0
iorlw .128
andwf INDF1,f,0 ; any zeros clear pin
timeout ; and remain cleared
btfss INTCON,TMR0IF,0 ; timer overflow?
bra reload_timer
btfsc PIR1,RCIF,0 ; test for rx byte
movff RCREG,POSTINC2
bra timeout
reload_timer
bcf T0CON,3,0 ; no prescaler
movf cycdelay,w,0
addwf TMR0L,f,0 ; reload timer0
bcf INTCON,TMR0IF,0
decfsz cyccounter,f,0 ; 256 yet?
bra servo_update
next_port
movlw .8
addwf FSR0L,f,0 ; index to next 8 servo values
incf FSR1L,f,0 ; index next port
decfsz portcounter,f,0 ; finished?
bra servoport_on ; do it again
retfie FAST
Last edited by wschroeder; 12th December 2007 at 02:51 AM. |
|
|
|
|
|
|
(permalink) |
|
Very nice Warren. Much cleaner. I know it doesn't seem very intuitive using 40 array elements when you're only using 33 of them but it sure makes the code simpler, doesn't it?
|
|
|
|
|
|
|
(permalink) | |
|
Quote:
Here's my "method" in a lo-rez 254 step 8 channel BASIC example for those who couldn't quite work through the assembly language example. The 'blue' section of code is time critical and so is a good candidate for replacement with assembler code (or much more carefully written BASIC instructions compared to what I have shown). Do you think this could be coded on a 16F' with 4 usec update loop timing? Note that receive buffering isn't shown but is implied. Code:
; ; ' 8 pwm 'servo' or digital 'on/off' channel output example ; ' 254 servo steps, 1..254, 992 to 2008 usecs, 4 usec steps ; ' step value 0 is a digital 'off' output ; ' step value 255 is a digital 'on' output ; ; Dim n As Byte ' ; Dim Servo(8) As Byte ' 8 channel values of 0..255 ; Dim WBuff(256) As Byte ' work (toggle/interval) buffer ; ; Interrupt example ' once exactly every 20 msecs ; PIR1.TMR2IF = 0 ' clear Timer 2 interrupt flag ; ; Mask = %00000001 ' servo channel bit mask ; For n = 0 To 7 ' add servo "off" bits to buffer ; WBuff(Servo(n)) = WBuff(Servo(n)) Or Mask ; Mask = Mask << 1 ' shift bit for next channel ; Next ; ; WBuff(0) = WBuff(0) Xor (Not PORTB) ' prep 1st XOR value ; ; PORTB = PORTB Xor WBuff(0) ' step 0 ; ; DelayUs(992) ' delay 992 usecs ; ; For n = 1 to 254 ' steps 992 thru 2008 usecs ; PORTB = PORTB Xor WBuff(n) ' 4 usec output update 'step' ; WBuff(n) = 0 ' zero buffer after using it ; Next ' ; ; End Interrupt Last edited by Mike, K8LH; 14th December 2007 at 01:18 PM. |
||
|
|
|
|
|
(permalink) | |
|
Quote:
I also thought about the PIC16 with the slower clock. Your code has possibilities, at least in theory at this point. 256 bytes is available, but the broken up addressing would be a interesting hurdle. Maybe some FSR magic could be done. |
||
|
|
|
| Bookmarks |
| Thread Tools | |
| Display Modes | |
|
|
|
|
||||
| Thread | Thread Starter | Forum | Replies | Latest |
| Problems switchin relay with PIC | Andy1845c | General Electronics Chat | 5 | 17th November 2007 06:13 PM |
| RF receive using PIC USART | col_implant | Electronic Projects Design/Ideas/Reviews | 11 | 15th November 2007 04:05 PM |
| High ADC sampling rate PIC, 18F needed? | bananasiong | Micro Controllers | 24 | 28th October 2007 12:13 PM |
| Controlling servos with PIC16F84A | Ostekjeks | Robotics Chat | 3 | 25th February 2005 10:06 AM |
| electric car drive | franklin2 | Electronic Projects Design/Ideas/Reviews | 8 | 18th October 2004 02:30 PM |