18 servo controller

Status
Not open for further replies.
Thank you. It's getting late here and I play too late and too much. If you think of a solution please post something... I'll pick it up in the morn. Amazing how a simple interrupt can throw a wrench into a good thing....

Edit:
Before I go... how about deducting a couple TMR1L cycles right before doing that instruction. You would get an error of 30 - 60 or more cycles at 100ns each out of the whole 20ms. That wouldn't hurt anybody. But it could prevent an interrupt at the wrong time. It would require a little change in how the RCIF test is done and acted upon. Just a tired thought and maybe the same problem.
 
Last edited:
So it looks like by using the decade counter we can get away with 10 servos per pin? Damn!

Servos seem to be getting a lot posts around here lately, it's nice to have all these ideas being discussed, I was having a hard time locating a comprehensive tutorial.
 
Last edited:

That almost works. If instead of deducting, you only read the UART if the timer has more than 20 cycles remaining. That is, you can tell if a interrupt is imminent and wait for it.

Mike.
 
Actually, now that I think about it a little more, I can control the amount of time each servo receives a pulse from the 4017, but after the decade counter resets and I start the 1st servos pulse again, I can end up changing the carrier frequency of each servo. But can I fix this by simply using the tenth decade pin and hold it to keep the total refresh rate the same? The problem is, if the first 5 servos with the lowest PWM values, then the sixth servo will have a higher carrier freq. So will a changing carrier frequency screw up the servos?

I could fix this by just using every other channel, so 5 per 4017. That way I could control the time between each servo even being pulsed. Or I could just use two demux's might be less work that way.
 
Last edited:
Ambient,

There is a more fundamental problem with the 4017 solution and that is that the total time frame will be minimum 18*1mS and maximum 18*2mS. You could use just 5 outputs per 4017 but then your frame time will be roughly 18*2.5mS.

Unless you need better than 8 bit accuracy then I think the software solution may be more suited.

Alternatively, 2 16F88 could be configured to do 9 servos each with much higher resolution. Using the internal clock, you could get a resolution of 500nS. I could help with the code if you choose this route.

Mike.
 
Each '4017 is driven directly by a seperate CCP module CCPx pin and so each '4017 maintains its own 20 msec period. Each '4017 will get 10 "compare" interrupts per 20 msec period. And servicing the interrupt is not critical with these hardware solutions compared to the 'soft' solutions. You could disable interrupts for a hundred microseconds or more at a time without affecting performance.

I modified the code to show one way that you might implement dual 74HC4017's. Please note the interrupt code outputs a pulse on the 74HC4017 Q1 through Q9 outputs that corresponds to the ChanA[0] through ChanA[8] pulse width array elements. The interrupt code uses the value in the ChanA[9] element for the Servo period "off time" (20 msecs minus the cumulative pulse on times).

Code:
static unsigned char Qa = 0;
static unsigned char Qb = 0;
static unsigned int ChanA [] = { 1500, 1500, 1500, 1500,
                                 1500, 1500, 1500, 1500,
                                 1500, 20000 };
static unsigned int ChanB [] = { 1500, 1500, 1500, 1500,
                                 1500, 1500, 1500, 1500,
                                 1500, 20000 };

void isr_hi ()
{
 /****************************************************************
  *  K8LH Crazy-18 Hi-Rez 18-chan Dual 74HC4017 Servo Algorithm  *
  ****************************************************************/
  if (PIR1bits.CCP1IF == 1)       // if CCP1 "compare" interrupt
  {
    CCPR1H++;                     // avoid false update "match"
    CCPR1 += ChanA[Qa];           // update "compare" int value
    CCPR1H--;                     // fix CCPR1H
    LATCbits.LATC2 = 0;           // clear CCP1 "CLK" line
    PIR1bits.CCP1IF = 0;          // clear CCP1 interrupt flag

    ChanA[9] -= ChanA[Qa++];      // adjust end-of-period off time

    if (Qa = 10)                  // if end-of-period
    { Qa = 0;                     // reset Qa array index
      ChanA[9] = 20000;           // reset 20.0-msec period and
      LATAbits.LATA0 = 1;         // toggle 74HC4017 "CLR" line
      LATAbits.LATA0 = 0;         // to force Q0 output sync'
    }
  }
  if (PIR2bits.CCP2IF == 1)       // if CCP2 "compare" interrupt
  {
    CCPR2H++;                     // avoid false update "match"
    CCPR2 += ChanB[Qb];           // update "compare" int value
    CCPR2H--;                     // fix CCPR2H
    LATCbits.LATC1 = 0;           // clear CCP2 "CLK" line
    PIR2bits.CCP2IF = 0;          // clear CCP2 interrupt flag

    ChanB[9] -= ChanB[Qb++];      // adjust end-of-period off time

    if (Qb = 10)                  // if end-of-period
    { Qb = 0;                     // reset Qb array index
      ChanB[9] = 20000;           // reset 20.0-msec period and
      LATAbits.LATA1 = 1;         // toggle 74HC4017 "CLR" line
      LATAbits.LATA1 = 0;         // to force Q0 output sync'
    }
  }
}
 
Last edited:
So will a changing carrier frequency screw up the servos?
The '4017 Servo outputs do experience minor period "jitter" for one 20 msec cycle immediately after one of the Servo pulse width values has been changed. I too thought this might be a problem but Nigel and others have said that the Servos can tolerate a wide period margin. Maintaining a jitter free pulse is more important.

BTW, my 16-channel dual 74HC238 CCP 'PWM' hardware solution is the only one discussed so far with absoluty 0% pulse width jitter and 0% period jitter.

Mike
 
Last edited:
I will have to look over your code some more then. I just need to find a non-inverting 4-16 demux (two for 18 channel, using one for each robot side.), which is looking impossible. Maybe I will just have to get an inverter for it.

I have not programmed a PIC with C yet, so I am having a hard time with the code. I gotta start looking at some tutorials.

Thanks.
 
What are the 4-16 demux' for?

In your opening post you said you'd be programming in C. Whould you like to see that code in Assembler or BASIC instead?
 
Mike,

I should have realised that you would use 2 CCP/PWM modules. I had it in my head that the interrupts would somehow collide no matter what you did, this of course doesn't matter when it's hardware. This would be Ambient's best solution at the moment. I also missed your 238 solution which, as you say, will give 16 jitter free outputs. My apologies, I got carried away with the other solution.

I assume the 2 x 4-16 demultiplexers would be to service 9 servos per PWM output. Wouldn't this give 18 jitter free outputs?

Mike.
 
Pommie said:
That almost works. If instead of deducting, you only read the UART if the timer has more than 20 cycles remaining. That is, you can tell if a interrupt is imminent and wait for it.

Since the USART has a 2.9 buffer simply checking TMR1H is sufficient. If TMR1H is at full count, 255, at most the program is 256 counts till interrupt. That's 103us if full count with prescaler=4. If running at 115Kbaud, a byte can show in the RXBuffer every 87us, leaving us with a possible 1 byte in the RXBuffer and another showing up while waiting for interrupt. By my calculations there is still enough time to jump to the ISR and finish up reading the RXBuffer in the ISR before creating a Buffer Overflow.

Here is my solution to the USART reading while running 30 servos:

Code:
     While FSR2ptr < lastservobufaddr      ' FSR2 is RX servobuffer
        Do
        Loop Until PIR1.RCIF = 1           ' wait for new RX byte
        While TMR1H = 255                  ' avoid trasfer if close to interrupt
        Wend
        If PIR1.RCIF = 1 Then POSTINC0 = RCREG End If ' 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

This is the change I made to the ISR:

Code:
sub procedure interrupt
    LATA = Not(LATA)                       ' turn on all servos on PortA
    LATB = Not(LATB)                       ' turn on all servos on PortB
    LATC = Not(LATC)                       ' turn on all servos on PORTC
    LATD = Not(LATD)                       ' turn on all servos on PortD
    LATE = Not(LATE)                       ' turn on all servos on PORTE
    pos = 0                                ' 256 counts.. rollover to original value
          ASM
    initdelay:                             ' approx 500us delay using timer1 counts
             movlw     5                   ' prescaler=4, 2.5 counts / microsecond
    delayloop:
             btfsc     PIR1, 5,0           ' check USART RX buffer
             movff     RCREG, POSTINC2     ' move RX byte into servobuffer
             cpfseq    TMR1H, 0            ' timer1 = 1280?
             bra       delayloop
    servomask:                             ' VERY efficient PWM masking routine
             movlw     0
             decfsz    _servo+0, 1,0
             iorlw     1
             decfsz    _servo+1, 1,0
 
I was planning on using an 18F and C for the AI and sensors, while using a 16F and assembly for the servo control. The 18F would have to just send the servo control PIC the 2d vector to move in, and pitch...etc data. Unless one PIC can handle all of it?

The two 4-16 demux would just be for sending the PWM signals to each servo, with each controlling 9. Just like with your 3-8 demux for your 16 servos. Could I actually get away with using a single 18F clocked at 20 MHz? I would probably have 10 or so sensors: IR body heat, ultrasonic distance sensor, 2-3 mics, current sensor (for stalled servo detection and touch detection), temp sensors. I want this robot to be able to navigate obstacles on its own using these sensors.
 
Last edited:
One option that I just thought of: Use 18F for "brain", and when it needs to update a servos position, it simply sends it to the servo control PIC. The servo control PIC would simply act as a latch of sorts. It would just keep telling the servos their position until it got an update on a certain servos position from the brain. This would really free up the 18F, as it wouldn't have to refresh the servos positions unless it was moving.
 
As this is for a hexapod (if I remember correctly?) you could have one slave PIC per leg!.
 
Well there is enough room for 1 PIC each leg, that's for sure. The chassis is huge! I just unpacked it, the usable area for electronics is around 8". Very spacious. But I would like to avoid using more PICs than I need to. One 16F690 would be plenty to handle each side, as well as the current sensors on each leg.
 
Can you post a few pics or a links to pics so we can have a gander at it.
 
One other question: If I use a 6V supply for the servos (5-cell NiMH) and a 5V regulator for the PIC, will the PWM servo signal still work properly? Does the PWM voltage need to match the supply voltage of the servo? I can run everything on a 5V buck-boost ad use a huge Lithium cell.
 

It will be fine, 6V to the servo's and 5V to the PIC isn't a problem.
 
Status
Not open for further replies.
Cookies are required to use this site. You must accept them to continue using the site. Learn more…