![]() |
![]() |
![]() |
|
|
|||||||
| 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) |
|
Speaking of interrupts, rock-solid PWM output, crazy ideas, etc., what do you guys think of this scheme?
Setup a nice hi-rez 1-msec PWM signal on your favorite PIC and feed it into the A0 address line of a 16-byte EPROM. Connect the other three EPROM address lines to the PIC. The EPROM is programmed so that the PWM signal driving A0 will toggle the corresponding data output line (servo line). Invert the PWM output (this option available on some newer chips like the 12F683, see Errata) and setup TMR2 interrupts at the PWM 1-msec period. Your ISR will increment a counter [0..19], drive the EPROM A3:A1 lines with that value, then load the PWM duty cycle register with a value from a 20-byte Servo Duty Cycle array indexed by the 0..19 counter (array values 08 through 19 will contain 00h). I suspect a little 12F683 could easily provide RS232 communications and drive up to 8 servos using 250 4-usec steps, 125 steps each side of center, using this scheme. Yes, no, maybe, huh? I suppose it really comes down to finding a small inexpensive 99 cent EPROM of some sort, doesn't it? Regards, Mike <added> Oops. I did it again. I forgot about the minimum 1-msec on-time for each servo. Gads! Perhaps if I left the Servo 01 bit (d0) set for both parts of the Servo 1 addresses to pick up the extra 1-msec? I'll have to brainstorm this more guys. Sorry Last edited by Mike, K8LH; 19th July 2006 at 07:48 PM. |
|
|
|
|
|
|
(permalink) |
|
I did a few tests last night and I've noticed that the period really does effect the response times and torque greatly. I set it up for 20ms and 36ms and just as you might expect the response time was almost double along with about 1/2 as much holding power. In my opinion a 20ms period or less should always be used.
|
|
|
|
|
|
|
(permalink) |
|
Mike, you've just redesigned the '138 3-to-8 address selector with enable lines...
|
|
|
|
|
|
|
(permalink) |
|
So since I don't do PIC's full time, here's the AVR approach:
The ATMEGA8 has a couple timers - This design would use TMR1, and it's two compare registers - OCR1A, and OCR1B. TMR1 is a 16 bit timer, so if the chip is running at 8MHz, TMR1 would overflow in ~8msec, which is longer than the 2msec pules we need to generate, so we won't need to use a prescaler. OCR1A, and OCR1B are configured to generate a pulse on compare matches, as well as trigger an interrupt. The general idea is that the '164 is a 8 bit shift register. When the timing is operational, we're going to have 1 bit which ends up activating 1 servo at a time. Since this is a hardware timer with 0 clock/interrupt jitter, we don't need to play games scheduling interrupts. C-style psuedo code /* U1Delays, U2Delays hold the pulse widths in CPU clocks - 1ms -> 8000, 2ms -> 16000. */ unsigned short U1Delays[8]; unsigned short U2Delays[8]; unsigned char bitCountU1, bitCountU2; startPulseTrain(){ PB0 &= ~0x01; //set PB0 to '0'; clockShiftRegisters(8); //clear both shift registers PB0 |= 0x01; //set PB0 to '1' clockShiftRegisters(1); //push a single bit into the shift registers PB0 &= ~0x01; //set PB0 to '0'; //Indicate which bit each shift register is currently at bitCountU1=bitCountU2=1; //get the current value of Timer1 unsigned short tmp = TCTR1; //schedule when the compare registers trip OCR1A = U1Delays[0] + tmp; OCR1B = U2Delays[0] + tmp; enableOCR1AInterrupt(); enableOCR1BInterrupt(); } //OCR1A will automatically pulse the clock line, this ISR routine is here //in order to schedule the following clock pulse OCR1A_interrupt(){ if(bitCountU1==8) disableOCR1AInterrupt(); OCR1A += U1Delays[bitCountU1++]; // schedule the next interrupt } // ditto for the OCR1B |
|
|
|
|
|
|
(permalink) | |
|
Quote:
The '138 gives you a 1-of-8 output. The EPROM provides 8 outputs with only one of the 8 being modulated by the PWM signal. Mike |
||
|
|
|
|
|
(permalink) | |
|
Quote:
I should've said '238 since the '138 apparently has idle-high outputs. I don't think I've ever actually used either of these chips in any of my designs, I usually end up using a '164 or '595 shift register for everything. |
||
|
|
|
|
|
(permalink) |
|
Ok, I think I figured out a workable version of Mike's Crazy EPROM 8 Servo Controller (partial schematic pictured in a previous post).
Using a 4-MHz clock, TMR2/PWM is set up for a 2.49-msec period (inverted PWM output). Each of the eight Servos has a programmable 0.4-msec to 2.4-msec "on-time" in 250 8-usec steps and an overall period of 19.9-msecs. 500 4-usec steps is possible with minor modification. The ISR (below) handles everything in the background. You simply stuff a value between 000 and 250 for each Servo in the 8-byte ServoPWM array (SArray) in the Main program (I think my math code may be wrong, needs work, but you should get the idea what can done with 40 or so lines of ISR code). Mike Code:
;******************************************************************
;
; clock 4-MHz, TMR2 prescaler 16, PR2=156, 2496-usec PWM period
;
EADR equ h'60' ; EPROM address / 2 [0..7]
DutyLo equ h'61'
DutyHi equ h'62'
SArray equ h'68' ; 8-bytes [68h..6Fh]
;
; save main program context
;
ISRPROC movwf W_ISR ; save W-reg |B?
swapf STATUS,W ; doesn't change STATUS bits |B?
movwf S_ISR ; save STATUS reg |B?
clrf STATUS ; force bank 0 |B0
movf FSR,W ; |B0
movwf F_ISR ; save FSR
bcf PIR1,TMR2IF ; clear TMR2 interrupt flag |B0
;
; EEPROM address counter / servo counter maintenance
;
incf EADR,f ; increment counter |B0
bcf EADR,3 ; force 0..7 |B0
;
; set eeprom address lines on RB0:RB2 during minimum 96-usec
; window at beginning of interrupt where PWM output is low
;
movf PORTB,W ; |B0
andlw b'11111000' ; |B0
iorwf EADR,W ; |B0
movwf PORTB ; set EPROM address lines |B0
;
; set Servo PWM output. duty cycle set in 8-usec steps
;
movf EADR,W ; [0..7] |B0
addlw SArray ; add index to array address |B0
movwf FSR ; setup indirect address |B0
clrf DutyHi ; |B0
movf INDF,W ; user ServoPWM value [000..250] |B0
addlw d'50' ; add minimum 0.4-msec on-time |B0
skpnc ; |B0
incf DutyHi,f ; |B0
sublw low d'312' ; 2496-usec period /8-usecs |B0
movwf DutyLo ; |B0
movf DutyHi,W ; |B0
skpc ; borrow? |B0
incf DutyHi,W ; |B0
sublw high d'312' ; |B0
movwf DutyHi ; |B0
rrf DutyHi,f ; |B0
rrf DutyLo,W ; |B0
movwf CCPR1L ; set 000..150 (16-usec ticks) |B0
bcf CCP1CON,DC1B1 ; |B0
skpnc ; |B0
bsf CCP1CON,DC1B1 ; |B0
;
; restore main program context
;
ISR_X movf F_ISR,W ; |B0
movwf FSR ; restore FSR |B0
swapf S_ISR,W ; |B0
movwf STATUS ; restore STATUS |B?
swapf W_ISR,f ; don't screw up STATUS |B?
swapf W_ISR,W ; restore W-reg |B?
retfie ; return from interrupt |B?
Last edited by Mike, K8LH; 20th July 2006 at 12:52 PM. |
|
|
|
|
|
|
(permalink) |
|
hjames,
Looking at the 74HCT164 Data Sheet, I'm having trouble figuring out how you clear a bit without performing a shift operation. Actually, I don't understand how you can use the shift register at all. Also not familiar with AVR (yet) or the C language. Mike Last edited by Mike, K8LH; 20th July 2006 at 01:36 AM. |
|
|
|
|
|
|
(permalink) |
|
The "clockShiftRegisters(#)" is supposed to mean create # clock pulses on PB1 and PB2. (okay, so it's really sloppy english-c code...)
Well, the idea is that it loops through all the servo's in order - skipping is okay, but no going back. The only important thing is that the shift register is initialized to all zero's except for the first one. The main idea is that those 1-2msec pulses don't have to be aligned versus anything - this approach just takes them, squishes them all together in time, and runs through them in one pass. The main benefits of the approach are: 1) only uses up 2 pins of the controller (per set of servos) 2) if there is a hardware timer, this thing will have *no* jitter and obscene resolution 3) (probably not too important) - it's easier to isolate or level-convert the outputs since you only have to level-translate or use a TTL threshold part like the HCT series. The fun part is that this is probably doable on one of the 5 or 8 pin PICs / AVR's. How's that for a weekend project? James |
|
|
|
|
|
|
(permalink) |
|
James,
So you're shifting that '1' bit after each servo 'on-time' then delaying 20-msecs minus the cumulative on-times before starting over? What happens if Servo 03 on-time changes? Wouldn't that affect time between pulses for Servo 04 through 08 during one cycle? For example, if Servo 03 on-time changes from 1.1-msecs to 2.0-msecs, wouldn't that effectively increase the period for Servo 04 through Servo 08 for one cycle from 20.0-msecs to 20.9-msecs? If so, is that important? Mike Last edited by Mike, K8LH; 20th July 2006 at 02:48 AM. |
|
|
|
|
|
|
(permalink) | ||
|
Quote:
Quote:
James |
|||
|
|
|
|
|
(permalink) | |
|
Quote:
![]() Your solution and perspective is appreciated. Thank you Sir. Mike Last edited by Mike, K8LH; 20th July 2006 at 01:42 PM. |
||
|
|
|
|
|
(permalink) |
|
No prob. I've gotten an inkling to build the *smallest* 16 port servo driver just for bragging rights. Maybe 1/2" on a side. Unfortunately the pin headers for the servo's would bulk it up by just a bit...
James |
|
|
|
|
|
|
(permalink) | |
|
Quote:
![]() |
||
|
|
|
|
|
(permalink) |
|
Okay, I see what you're getting at.
If we look at the last servo in a set and and we say that all the servo's are sitting at 1msec pulse widths, and on the next frame we suddenly increas all the pulse widths to 2msec, the last (say 8th one) will end up having a 20 + 7 msec gap between pulses. If we then decrease all the pulse widths back to 1msec, then the 8th servo will end up having a 20-7msec gap between pulses. So yeah, there will be a pulse gap variation - but as long as the servo is happy with a worst-case 27msec update period, everything should still be okay. Plus this is definitely worst-case since suddenly railing servo's isn't exactly a useful thing to be doing a first place. |
|
|
|
|