Continue to Site

Welcome to our site!

Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

  • Welcome to our site! Electro Tech is an online community (with over 170,000 members) who enjoy talking about and building electronic circuits, projects and gadgets. To participate you need to register. Registration is free. Click here to register now.

Concurrent threads on a pic?

Status
Not open for further replies.
I'm kinda confused as to why this stuff is being implemented as busy-waiting loops when the chip (A PIC18F4550, right?) has 4 separate timer/counter sections? It would seem much more reasonable to use a timer interrupt to schedule the transitions accordingly, plus you'd end up with some pretty insane resolution.

- i.e. set TMR1 to -1000, and you'd get an interrupt in 1000 timer ticks.
 
hjames,

Because I'm not really sure how to do that. :D Delays are pretty straightforward. Do you have any example code that I could look at that uses the method you describe?
 
Last edited:
Well, think of it as a learning experience - learning how to use all the modules in there is a definite bonus.

Page 133 of the PIC data sheet has some sample code and a sample ISR for a RTC timer where they force TMR1 to 0x8000 every time the interrupt is called - which causes an interrupt in another 0x8000 ticks

But in general, just set TMR1 (all 16 bits) 65536 - "# timer ticks" (and since 65536=0, it's just -#ticks), and you'll get an interrupt at that time. Or you can read up on the "capture/compare" stuff and use some of the PWM logic. I can't give you more details without sinking a lot my time (I usually work with the Atmel chips), but I'd be real surprised if it isn't pretty simple.

As for structuring the code, I'd probably break it down into a state-machine like representation to keep the ISR simple. At the start of every frame, caclulate all the delays you need, and then enable the interrupts. The ISR would just do three things : 1) load in a new TMR1 value 2) update the ports with some precalculated values 3) decide whether it needs to execute again, or can shut down and disable it's interrupt.
 
Mike said:
No, he can't use 1-msec interrupts. He using 120 steps/msec which allows him to swing the servo 60 steps left (anti-clockwise) and 60 steps right (clockwise) of the center 1.5-msec pulse width servo position. You'd need 8.3-usec interrupts to provide the same resolution and then you might not have enough time in the ISR to tickle and toggle a counter and output.

The 1ms timer ISR (I mentioned before) can be set to any resoultion (I jumped in late and just picked 1ms for the ISR for the idea). But still load a counter byte for each servo to move them (in usec for this case).

I think a timer ISR (based on the resolution he needs) and byte counter for each pin to move the servos will work. Just he needs to wait (block the pin) for each servo to settle before he updates the bytes counters.
 
I admit I've become very curious about Servos during all the recent discussions. I wouldn't mind 'playing' with one to see how much resolution between 1.0-msecs and 2.0-msecs (or 0.5-msecs and 2.5-msecs) is useful or perceivable.

I too would prefer an assembly language solution using timers and interrupts but I simply don't have time for a new challenge right now (grin).

Have fun guys. Mike
 
With current mid-priced transmitters and servos, such as Multiplex EVO and Hitec servos, the smallest change you can see empirically (i.e., 1 click on the trim) is about 25 us +/- 5us.

I just completed my previous servo project based on the 628A. For my next project, I would like more (at least 2; 3 preferred) PWM channels. Do you have any experience with the 16F886?
Thanks.
John
 
jpanhalt said:
With current mid-priced transmitters and servos, such as Multiplex EVO and Hitec servos, the smallest change you can see empirically (i.e., 1 click on the trim) is about 25 us +/- 5us.

That's interesting! - that's only 40 steps for the 1mS maximum change.

Using a 20MHz PIC, and 25uS timer interrupts, it should easily be possible to do this for 16 servos

I just completed my previous servo project based on the 628A. For my next project, I would like more (at least 2; 3 preferred) PWM channels. Do you have any experience with the 16F886?
Thanks.
John

The hardware PWM isn't really great for driving servos, it's more common to di it in software - obviously a LOT depends what else is going on though?.
 
Last edited:
That's exactly why I used PWM for the servo. The processor is handling several inputs and outputs. It holds servo position very steady while I do other things in the program. The next project will use the second PWM for a more typical, half-bridge motor control at 20KHz with soft start. Hence, my interest in a chip like the 16F886.
Regards, John
 
Xor, at the Mikrobasic forum wrote a multiple servo controller program for 8 or 16 servos( dont recall which) in Mikrobasic. Dont know anything about it personally but there was a lot of interest it it.
zkt
 
jpanhalt said:
Do you have any experience with the 16F886?
Thanks.
John
I can't seem to find info' for the 16F886.

Mike

<added>

I found it listed as Future Product. How can anyone have any experience with it if it's not available yet (grin)?
 
Last edited:
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
 

Attachments

  • Mike's Crazy Hi-Rez Servo Scheme.JPG
    Mike's Crazy Hi-Rez Servo Scheme.JPG
    57.5 KB · Views: 161
Last edited:
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.
 
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
 

Attachments

  • bleh.png
    bleh.png
    12.2 KB · Views: 140
hjames said:
Mike, you've just redesigned the '138 3-to-8 address selector with enable lines...
Good observation but not exactly.

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
 
Mike said:
Good observation but not exactly.

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

Plug the PWM signal into the enable line.

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.
 
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:
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:
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
 
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:
Status
Not open for further replies.

New Articles From Microcontroller Tips

Back
Top