![]() |
![]() |
![]() |
|
|
|||||||
| 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) |
|
Mike,
You don't have to apologize. Your evaluations are always worthy of note. I didn't get involved with PIC's until a couple years ago, so some of my code can certainly use some refinement. I would now write the reload of the Timer by adding the 20ms number... a trick I learned a few months ago and before I wrote the PIC RTC examples for SF. By the way, some of your ideas have turned out to be an inspiration for some new stuff I'm working on. |
|
|
|
|
|
|
(permalink) |
|
Ok guys, I'm impressed.
Between the two of you you've shown me that a jitter-free lo-rez 'soft' solution with more than 8 channels (which has eluded me) is feasable and probably practical (just not in the current form of Warren's demo') Now, could you build interface code that would allow single channel serial updates instead of that 30 byte serial update? Are there any pins left over to allow using one pin for hardware handshaking to the host to signal when we're sitting in that 17.5 msec 'window' (where we can use the serial port) instead of sending that flag character? Warren, I learn a lot by disecting code and your code is no exception. In this case, decrementing the pwm array values 256 times is better because it would work with 16F' devices too. |
|
|
|
|
|
|
(permalink) | |
|
Quote:
You can use the CCP module PWM mode but you need to break up the 20 msec servo period into smaller faster PWM frames in order to maintain usable hi-rez pulse width resolution. You can multiplex the PWM output signal by using it to drive one of the enable lines on a 74HC238 IC. Change the address lines on the 74HC4017 to direct a 1000 to 2000 usec PWM pulse to one of the eight 74HC238 outputs every 2500 usec interval. While this provides a wonderful hi-rez no-jitter solution, it will only provide you with 16 servo outputs using two 74HC238 ICs and 5 pins (CCP1, CCP2, and 3 pins for decoder address lines). The following example is for 4 MHz (prescaler 1:1) or 16 MHz (prescaler 4:1) clocks. Code:
/* *
* array1 and array2 elements hold Servo pulse width values *
* ranging from 3000..9000 in 250-nsec 'ticks' representing an *
* extended pulse width range of 750-usecs to 2250-usecs *
* */
static unsigned int array1 [] = { 6000, 6000, 6000, 6000,
6000, 6000, 6000, 6000 };
static unsigned int array2 [] = { 6000, 6000, 6000, 6000,
6000, 6000, 6000, 6000 };
/*****************************************************************
* K8LH 16-Channel 74HC238 Hi-Rez 'PWM' Servo Algorithm Driver *
*****************************************************************/
#pragma interrupt isr_hi
void isr_hi ()
{ if(PIR1bits.TMR2IF)
{ PIR1bits.TMR2IF = 0; // clear TMR2 interrupt flag
/**************************************************************
* *
* setup 74HC238 address lines and our work variables for *
* the next servo channel interval during the final (10th) *
* frame of the current servo channel interval (the final *
* frame in each interval will always have a 0% duty cycle *
* and all 74HC238 outputs are 'off' so it will be safe to *
* change the address lines) *
* */
if(frame == 10) // 10 PWM frames/Servo interval
{ frame = 0; // reset PWM frame counter
select %= 8; // 8 Servo intervals/20-msec cycle
pulse1 = array1[select];
pulse2 = array2[select];
PORTB = (PORTB & 0b11111000) | select;
select++; // bump for next interval
}
frame++; // bump for next interrupt
/**************************************************************
* setup CCP1 PWM duty cycle for next 250-usec PWM 'frame' *
* */
if(pulse1 > 1000) // if pulse1 > 250-usecs
{ CCPR1L = 250; // do a 100% duty cycle frame
pulse1 -= 1000; // subtract 250-usecs
}
else // do a variable or 0% frame
{ CCP1CONbits.CCP1Y = (pulse1 & 1);
CCP1CONbits.CCP1X = (pulse1 & 2);
CCPR1L = (pulse1 >>= 2);
pulse1 = 0; // remaining frames are %0
}
/**************************************************************
* setup CCP2 PWM duty cycle for next 250-usec PWM 'frame' *
* */
if(pulse2 > 1000) // if pulse2 > 250-usecs
{ CCPR2L = 250; // do a 100% duty cycle frame
pulse2 -= 1000; // subtract 250-usecs
}
else // do a variable or 0% frame
{ CCP2CONbits.CCP2Y = (pulse1 & 1);
CCP2CONbits.CCP2X = (pulse1 & 2);
CCPR2L = (pulse2 >>= 2);
pulse2 = 0; // remaining frames are %0
}
}
}
Last edited by Mike, K8LH; 5th December 2007 at 06:05 PM. |
||
|
|
|
|
|
(permalink) |
|
An interesting proposition for the communication. The TX pin could be used as a hardware CTS I suppose and then turn on USART/UART for RX only on the RX pin. I don't know if this is what you're inferring. Or, are you thinking of a more compact method of transmitting the data?
I'm referring to your previous posting above.... not the last post. Last edited by wschroeder; 5th December 2007 at 05:13 PM. |
|
|
|
|
|
|
(permalink) |
|
You've got it. Could hardware handshaking eliminate the requirement for the Host to watch for that flag character. Just to simplify host requirements...
|
|
|
|
|
|
|
(permalink) |
|
Brilliant piece of code that 30 servo, I'll put it in one of the Junebug / Firefly tutorials.
The more I work with 18F PICs (in assembly) the less I want to use the 14bit core PICs. |
|
|
|
|
|
|
(permalink) |
|
So it might just work then, for 10 channel max with one pin, using soft PWM. From what I have seen, the PW is 10% max required for full travel. So practically, I can get 9 channels max on one pin using a mux. Does that sound like it will work?
Also, it looks like I can do it all with one small PIC, with an 18F and C code running the sensors and "brain". Or would it still be best to use the PWM pins and use one PIC per leg (some 16F's have pins to use in PWM mode)? Thanks for the help guys. Last edited by Ambient; 5th December 2007 at 08:46 PM. |
|
|
|
|
|
|
(permalink) |
|
It would appear to be possible with almost any PIC.
|
|
|
|
|
|
|
(permalink) | |
|
Quote:
There is no need to worry about the serial communication. At 19200 baud a byte can arrive each 5mS, this fact combined with the 2 byte hardware buffer means that overflow cannot occur and, consequently, no handshake is required. You could simply send something like 128+servo number, high nibble/16, low nibble. Edit, I'm out by a factor of 10 as pointed out by Warren below. Mike. Last edited by Pommie; 6th December 2007 at 01:35 AM. |
||
|
|
|
|
|
(permalink) | |
|
Quote:
|
||
|
|
|
|
|
(permalink) |
|
Whoops, you are correct, I must remember to keep track of that decimal point in future.
Mike. |
|
|
|
|
|
|
(permalink) |
|
You could use the indirect register as a fifo.
Replace Code:
iorlw 4 andwf LATE, 1,0 nop nop nop nop nop incfsz _pos, 1,0 bra $-76 Code:
iorlw 4 andwf LATE, 1,0 btfss PIR1,RCIF ;byte recieved goto DoneRS ;no, so go do a delay movfw RXREG ;yes, get it and movwf POSTINC0 ;store it in buffer bcf PIR1,RCIF ;clr IF CarryOn incfsz _pos, 1,0 bra $-76 DoneRS goto CarryOn ;just delay and jump back I'm not sure how usable the FSR register is with Basic but it should be fine as long as it is saved and restored in the asm part. Mike. Last edited by Pommie; 6th December 2007 at 03:38 AM. |
|
|
|
|
|
|
(permalink) |
|
Tying up the processor for 2500 usecs without being able to cache serial data in a circular buffer as it's coming in is a potentially big problem, in my opinion.
Mike, Just spotted your idea. That would actually work quite well and Main could easily determine the number of characters in the buffer by the value of FSR0L. BTW, you don't need the bcf PIR1,RCIF instruction as it's cleared automatically when you pull a character from RCREG... Last edited by Mike, K8LH; 6th December 2007 at 03:49 AM. |
|
|
|
|
|
|
(permalink) | |
|
Quote:
Here is my attempt at caching the serial data. I have guessed the basic syntax and how variables are accessed. Code:
program SERVO30_18F452
'**********************************************************************
'****** ******
'****** 30 Servo Driver @ 256 Positions using P18 @ 40MHz ******
'****** by W. Schroeder on July 22, 2006 ******
'****** REVISED on Sept 19, 2006..added USART & code tweaks ******
'****** Compiled with mikroBASIC 5.0.0.1 & Tested on 18F452 ******
'****** ******
'****** Servo routines use only ~2.5ms of 20ms cycle ******
'****** This ensures enough time for changing servo values ******
'****** ******
'****** USART is used for communicating new servo values to ******
'****** the PIC. It is important that the PIC initiate this ******
'****** communication with the PC. Therefore, PC software ******
'****** must regularly scan the Comm Port for receipt of a ******
'****** an initiate-communication signal. The PC will send ******
'****** exactly 30 bytes which represent the servo values. ******
'****** ******
'**********************************************************************
' 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.5 <-- note: skips RA4 open-drain output
' servo[5]- PORTB.0
' servo[6]- PORTB.1
' servo[7]- PORTB.2
' servo[8]- PORTB.3
' servo[9]- PORTB.4
' servo[10]-PORTB.5
' servo[11]-PORTB.6
' servo[12]-PORTB.7
' servo[13]-PORTC.0
' servo[14]-PORTC.1
' servo[15]-PORTC.2
' servo[16]-PORTC.3
' servo[17]-PORTC.4
' servo[18]-PORTC.5
' servo[19]-PORTD.0
' servo[20]-PORTD.1
' servo[21]-PORTD.2
' servo[22]-PORTD.3
' servo[23]-PORTD.4
' servo[24]-PORTD.5
' servo[25]-PORTD.6
' servo[26]-PORTD.7
' servo[27]-PORTE.0
' servo[28]-PORTE.1
' servo[29]-PORTE.2
'********************************************************************
dim aa, pos, tmp as byte
dim tmr as word
dim servo as byte[30]
dim saveFSR as byte
dim buffer as byte[10]
dim bufferlen as byte
dim bufferpoint as byte
dim position as byte
sub procedure interrupt
LATA = 63 ' turn on all servos on PortA
LATB = 255 ' turn on all servos on PortB
LATC = 63 ' turn on all servos on PORTC
LATD = 255 ' turn on all servos on PortD
LATE = 7 ' turn on all servos on PORTE
pos = 0 ' manage 8 outputs with 256 positions each
delay_us(500)
ASM ' VERY efficient PWM masking routine
movfw FSR0
movwf saveFSR
movlw _buffer 'maybe this should be &buffer
movwf FSR0 'so FSR points to buffer
'not sure how banking works here
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 32
andwf LATA, 1,0
movlw 0
decfsz _servo+5, 1,0
iorlw 1
decfsz _servo+6, 1,0
iorlw 2
decfsz _servo+7, 1,0
iorlw 4
decfsz _servo+8, 1,0
iorlw 8
decfsz _servo+9, 1,0
iorlw 16
decfsz _servo+10, 1,0
iorlw 32
decfsz _servo+11, 1,0
iorlw 64
decfsz _servo+12, 1,0
iorlw 128
andwf LATB, 1,0
movlw 0
decfsz _servo+13, 1,0
iorlw 1
decfsz _servo+14, 1,0
iorlw 2
decfsz _servo+15, 1,0
iorlw 4
decfsz _servo+16, 1,0
iorlw 8
decfsz _servo+17, 1,0
iorlw 16
decfsz _servo+18, 1,0
iorlw 32
andwf LATC, 1,0
movlw 0
decfsz _servo+19, 1,0
iorlw 1
decfsz _servo+20, 1,0
iorlw 2
decfsz _servo+21, 1,0
iorlw 4
decfsz _servo+22, 1,0
iorlw 8
decfsz _servo+23, 1,0
iorlw 16
decfsz _servo+24, 1,0
iorlw 32
decfsz _servo+25, 1,0
iorlw 64
decfsz _servo+26, 1,0
iorlw 128
andwf LATD, 1,0
movlw 0
decfsz _servo+27, 1,0
iorlw 1
decfsz _servo+28, 1,0
iorlw 2
decfsz _servo+29, 1,0
iorlw 4
andwf LATE, 1,0
btfsz PIR1,RCIF
goto DoneRS
movfw RXREG
movwf POSTINC0
bcf PIR1,RCIF
CarryOn incfsz _pos, 1,0
bra $-76
movlw _buffer
subfw FSR0,W ;W=FSR0-&buffer = len received
movwf _bufferlen
movfw saveFSR ;restore FSR
movwf FSR0
END ASM ' these routines have used ~2500us to this point
TMR0H= Hi(-5438) ' balance of time for 20ms cycle (=17.5ms)
TMR0L = -5438
INTCON.TMR0IF = 0 ' clear interrupt flag
aa = 1
end sub
sub procedure _init
LATA = 0
LATB = 0
LATC = 0
LATD = 0
LATE = 0
TRISA = 0
TRISB = 0
TRISC = 0
TRISD = 0
TRISE = 0
ADCON1 = 7
T0CON = %00000100 ' prescaler = 32 or 3.2us per increment
TMR0H = -2 ' load high byte first
TMR0L = 0 ' load low byte second
INTCON = %10100000 ' Timer0 Interrupt Enabled and Flag Cleared
T0CON.7 = 1 ' Timer0 On
aa = 0
end sub
sub function getRS232 as byte
'this relies on the buffer being flushed before another interrupt comes along
dim temp as int
temp=-1
While temp=-1
if bufferlen<>0 then 'any bytes in buffer
temp=buffer[bufferpoint]
bufferpoint=bufferpoint+1
if bufferpoint=bufferlen then 'read all bytes in buffer?
bufferpoint=0
bufferlen=0
endif
else
aa=0 'clear the interrupt flag
ASM
btfss PIR1,RCIF 'byte available
goto NoRS232 'no carry on
bcf INTCON,7 'dissable interrupts - can't see a way around this
movfw aa 'has an interrupt occured and stolen our data
btfss STATUS,Z 'if so,
goto NoRS232 'go around the outer loop and get it from the buffer
movfw RXREG 'get byte
bsf INTCON,7 'reenable interrupts
movwf _temp 'byte to return
clrf _temp+1 'ensure temp <>-1
NoRS232 bsf INTCON,7 'needed for when goto (above) executed
END ASM
endif
Wend
end sub
main:
_init
USART_Init(19200) ' 19200 should be minimum speed...faster is better
' communicate with PC every 20ms
While true
do
tmp=getRS232
loop until tmp>127 'wait for servo number
tmp=tmp & 127
position = getRS232*16 'get servo position
position = position + getRS232
servo[tmp] = position ' store servo value
Wend
end.
Edit, Mike, I couldn't find a way around disabling interrupts. Have you any ideas? Edit2, changed code. Last edited by Pommie; 6th December 2007 at 04:12 AM. |
||
|
|
|
|
|
(permalink) |
|
This is untested right now, but it should allow simple USART RX source at any baudrate (maybe 115K should be the upper limit). The only stipulation is that the PC starts communication after the PIC has been started.
It's written presently in mikroBasic and will compile with the free version: Code:
program SERVO30_USART
'**********************************************************************
'****** ******
'****** 30 Servo Driver @ 256 Positions using P18 @ 40MHz ******
'****** by W. Schroeder on July 22, 2006 ******
'****** ******
'****** REVISED on Sept 19, 2006..added USART & code tweaks ******
'****** Compiled with mikroBASIC 5.0.0.1 & Tested on 18F452 ******
'****** ******
'****** REVISED on Dec 5, 2007.. added USART for any Baud ******
'****** Compiled with mikroBASIC 6.0 & Tested on 18F452 ******
'****** ******
'****** Servo routines use only ~2.5ms of 20ms cycle ******
'****** USART at any baudrate to change servo values ******
'****** ******
'**********************************************************************
' 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.5
' servo[5]- PORTB.0
' servo[6]- PORTB.1
' servo[7]- PORTB.2
' servo[8]- PORTB.3
' servo[9]- PORTB.4
' servo[10]-PORTB.5
' servo[11]-PORTB.6
' servo[12]-PORTB.7
' servo[13]-PORTC.0
' servo[14]-PORTC.1
' servo[15]-PORTC.2
' servo[16]-PORTC.3
' servo[17]-PORTC.4
' servo[18]-PORTC.5
' servo[19]-PORTD.0
' servo[20]-PORTD.1
' servo[21]-PORTD.2
' servo[22]-PORTD.3
' servo[23]-PORTD.4
' servo[24]-PORTD.5
' servo[25]-PORTD.6
' servo[26]-PORTD.7
' servo[27]-PORTE.0
' servo[28]-PORTE.1
' servo[29]-PORTE.2
'********************************************************************
const T1_20ms as word = 65536-50000+6 ' 20ms + stop timer1 stop compensation
dim pos as byte
dim servo as byte[30]
dim servobuffer as byte[30]
dim lastservobufaddr as word
dim T1 As word absolute $FCE
sub procedure interrupt
LATA = 63 ' turn on all servos on PortA
LATB = 255 ' turn on all servos on PortB
LATC = 63 ' turn on all servos on PORTC
LATD = 255 ' turn on all servos on PortD
LATE = 7 ' turn on all servos on PORTE
pos = 0 ' 256 counts.. rollover to original value
delay_us(500) ' 0 position delay; adjust to suit needs
ASM ' 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 32
andwf LATA, 1,0
movlw 0
decfsz _servo+5, 1,0
iorlw 1
decfsz _servo+6, 1,0
iorlw 2
decfsz _servo+7, 1,0
iorlw 4
decfsz _servo+8, 1,0
iorlw 8
decfsz _servo+9, 1,0
iorlw 16
decfsz _servo+10, 1,0
iorlw 32
decfsz _servo+11, 1,0
iorlw 64
decfsz _servo+12, 1,0
iorlw 128
andwf LATB, 1,0
movlw 0
decfsz _servo+13, 1,0
iorlw 1
decfsz _servo+14, 1,0
iorlw 2
decfsz _servo+15, 1,0
iorlw 4
decfsz _servo+16, 1,0
iorlw 8
decfsz _servo+17, 1,0
iorlw 16
decfsz _servo+18, 1,0
iorlw 32
andwf LATC, 1,0
movlw 0
decfsz _servo+19, 1,0
iorlw 1
decfsz _servo+20, 1,0
iorlw 2
decfsz _servo+21, 1,0
iorlw 4
decfsz _servo+22, 1,0
iorlw 8
decfsz _servo+23, 1,0
iorlw 16
decfsz _servo+24, 1,0
iorlw 32
decfsz _servo+25, 1,0
iorlw 64
decfsz _servo+26, 1,0
iorlw 128
andwf LATD, 1,0
movlw 0
decfsz _servo+27, 1,0
iorlw 1
decfsz _servo+28, 1,0
iorlw 2
decfsz _servo+29, 1,0
iorlw 4
andwf LATE, 1,0
nop
btfsc PIR1, 5,0 ' check USART RX buffer
movff RCREG, POSTINC2 ' move RX byte into servobuffer
nop
incfsz _pos, 1,0
bra $-76 ' loop total of 20224 cycles = 2.02ms
END ASM
T1CON.TMR1ON = 0 ' stop timer1
T1 = T1 + T1_20ms ' reload timer1
T1CON.TMR1ON = 1 ' restart timer1
PIR1.TMR1IF = 0 ' clear interrupt flag
end sub
sub procedure _init
LATA = 0
LATB = 0
LATC = 0
LATD = 0
LATE = 0
TRISA = 0
TRISB = 0
TRISC = 0
TRISD = 0
TRISE = 0
ADCON1 = 7 ' disable ADC's
lastservobufaddr = @servobuffer + 30
INTCON = 192 ' enable GIE & PEIE
T1CON = 32 ' prescaler=4, timer off
T1 = 0 ' load Timer1
PIE1.TMR1IE = 1 ' enable Timer1 Interrupt
PIR1.TMR1IF = 0 ' clear Timer1 Interrupt Flag
T1CON.TMR1ON = 1 ' start Timer1
end sub
main:
_init
USART_Init(115200) ' any baudrate is permissible
FSR0ptr = @servo
While FSR0ptr < (@servo + 30) ' initialize all servo values to 0 position
POSTINC0 = 1
Wend
FSR2ptr = @servobuffer
While true
While FSR2ptr < lastservobufaddr ' FSR2 is RX servobuffer
Do
Loop Until PIR1.RCIF = 1 ' wait for new RX byte
POSTINC2 = RCREG ' load RX byte.. when 30 bytes move on
Wend
FSR2ptr = @servobuffer ' reset RX buffer
FSR1ptr = @servobuffer ' setup tranfer of RX array to
FSR0ptr = @servo ' servo work array
While FSR1ptr < lastservobufaddr
POSTINC0 = inc(POSTINC1) ' transfer RX buffer to work values.. add 1
Wend
Wend
end.
Last edited by wschroeder; 6th December 2007 at 04:05 AM. |
|
|
|
|
| Bookmarks |
| Thread Tools | |
| Display Modes | |
|
|
|
|
||||
| Thread | Thread Starter | Forum | Replies | Latest |
| 555 servo controller? can get them working | lompa | General Electronics Chat | 9 | 31st May 2007 06:24 AM |
| Help required with servo motor controller | mayhem | Robotics Chat | 3 | 26th May 2006 04:21 PM |
| Auto servo controller, need help with the 555 | linuxglobal | General Electronics Chat | 2 | 20th April 2006 03:52 PM |
| looking for servo controller circuit diagram using switch ? | calico | Micro Controllers | 49 | 27th February 2006 01:35 AM |
| Help me start, making a servo controller for motorcycle use. | motoracer | General Electronics Chat | 4 | 11th November 2003 10:29 PM |