This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

5 simultaneous PWM outputs

i want to program a CC430 to drive 5 RC Servos simultaneously. so far, i have written a routine that drives 1 servo reliably. i would like to extend this routine so it drives 5 servos  in parallel. 

to drive this 1 servo, i am currently using CCR0 as the PWM period counter and and CCR4 for the pulse width. this all works just as expected. what i am struggling with is how to extend the code to drive 5 servos simultaneously without using up all the CCRs. even if i would use 1 CCR for each servo output, i would not have enough of them, as TA0 has only 5 CCR's, and one of them is used for the period. so i am 1 short. unfortunately, the 3 CCR's of TA1 are already used for some other timing tasks.

does anyone have some suggestions on how to drive 5 servos with 4 CCRs? i imagine there must be a way to do this with port mapping more than one PWM output pin to a single CCR and then do altering periods on either of the pins. at least 2 of my servos are not that time critical, so the could be driven with some sort of interleaved scheme.

 

on a slightly different subject, i would like to use a CCR register just like a variable.  how can i store for example "TA0CCR4" and later use the variable so set the register? i tried the following, which didn't work:

u16 myTA0CCR4 = TA0CCR4;

myTA0CCR4 = <some_ccr_value> //compiles, but doesn't work


u16* myTA0CCR4_ptr = (u16*) TA0CCR4;

*(myTA0CCR4) = <some_ccr_value>; //compiles, but doesn't work

 

 

for reference, here is my current routine to drive 1 servo:

 

 

void driveServo(ServoWorker* servoWorker)

{    

    u16 ccr;

    u8 distance;

    

    //calculate the distance we need to travel (if any)

    distance = (servoWorker->targetPosition < servoWorker->currentPosition) ? 

        (servoWorker->currentPosition - servoWorker->targetPosition) :

        (servoWorker->targetPosition - servoWorker->currentPosition);

    

    if(!distance)

        return;

    

    //calculate requested position in context of pulsewidth range

    ccr = (((unsigned long)servoWorker->targetPosition * (servoWorker->servo->end-servoWorker->servo->start)) / 255) + servoWorker->servo->start;

        

    //set period

    TA0CCR0 = servoWorker->servo->speed-1;

    

    //set duty cycle

    TA0CCR4 =  ccr/2;

    

    //turn PWM on

    TA0CTL = TASSEL_1 + MC_3;

    

    //switch on servo

    SERVO_ON(servoWorker->servoID);

    

    //wait aproriate time to let servo finish travel

    Timer1_A2_Delay(distance*30);

    

    //turn off servo

    SERVO_OFF(servoWorker->servoID);

    

    //turn off PWM

   TA0CTL &= ~(TASSEL_1 | MC_3);

    

    //set current position to the new position we just drove to

    servoWorker->currentPosition = servoWorker->targetPosition;

}

 

  • rl said:
    does anyone have some suggestions on how to drive 5 servos with 4 CCRs?


    Not if the servos need 5 different PWM values.

    I can imagine a configuration with superimposing two PWM frequencies  adn using external filters/frequency mixer to separate the two signals, or using two mexternal monoflops, retriggered by the low and the high stage of the PWM signal, but all this requires much external efforts, if it works at all. You can as well add an external, digitally programmed PWM generator.

    The main reason for using a hardware PWM circuit is that you don't need any software interaction except if changing duty cycle.
    Sure, there are some MSPs where you can remap the output pins and you can constantly switch the PWM frequency, but it won't do any good. You can as well set up an ISR which manually toggles a port pin. (and in fact I use it for an LED-'PWM', but with a relatively low, human-noticeable frequency)

    Or you use an MSP which does what you need. Many of the MSPs have e.g. a TimerB7, which has up to 7 PWM CCRs including TBCCR0.

    rl said:
    u16 myTA0CCR4 = TA0CCR4;
    myTA0CCR4 = <some_ccr_value> //compiles, but doesn't work
    u16* myTA0CCR4_ptr = (u16*) TA0CCR4;
    *(myTA0CCR4) = <some_ccr_value>; //compiles, but doesn't work

    I'm 100% sure that it works exactly the way you wrote it. But I'm not sure that you wrote what you meant.

    TA0CCR4 is a volatile unsigned int. Using TA0CCR4 give the value stored at its location.. So
    u16 myTA0CCR4 = TA0CCR4;
    takes the value in TACCR0 and stores it in a variable named myTA0CCR4.
    myTA0CCR4 = <some_ccr_value>
    assigns a different value to the variable myTA0CCR4. Whatever it is used for or what you want to do with it. For the MSP, myTA0CCR4 is just a value stored at an arbitrary memory location without any special meanig.

    u16* myTA0CCR4_ptr = (u16*) TA0CCR4;
    This takes teh current value of TA0CCR4 and assignes it as an U16 pointer to myTA0CCR4_ptr. It doesn't make any sense. Depending on the current value of TA0CCR4, the pointer points to any possible memory location. And *(myTA0CCR4_ptr) will read from or write to this memory location. Which will most likely crash the application sooner or later. Even worse:
    *(myTA0CCR4) = <some_ccr_value>;
    will write a value to the memory location where the pointer myTA0CCR4 is pointing to. Only that TA0CCR4 is no pointer. TA0CCR4_ptr is. The compiler should bang an error on this line or at least a warning.

    If you want to get the address of the TA0CCR4, the code woud be
    u16* myTA0CCR4_ptr = (u16*) &TA0CCR4;
    But then, myTA0CCR4 is just an alias of TA0CCR4, only that it uses two bytes RAM and is slower to access. It also remove the volatile modifier so depending on the compiler optimisation, you won't always use the actual value in TA0CCR4 but rather a buffered one, So it makes no sense. Using pointers is only useful if you need to CHANGE the pointer while the attached code remains identical. e.g. if you provide the pointer to a function which will do something and will do it with TA0CCR0 or TA0CCR1 or whetever, depending on the parameter or a globally set pointer.

    Doing a quick sweep over your code, I wonder why you do the distance calculation. The destination position solely depends on the PWM value and not on the current position. So just set the new PWM value and you're done. If you busy-wait until the destination is reached, you cannot do anything else (like adjusting the other servos or whatever). And if the position changes again before the servo has settled, it won't change anything. Just set the new PWM and be happy. Of course if you do feedback calculations faster than the servor reacts, this will introduce problems, but doing a busy-waiting surely isn't the best way to handle it.

    Of course you know your hardware better than I do (since you didn't mention it at all). But with all this SERVO_ON and SERVO_OFF, I wonder why you need more than one PWM output at all for all servos. (especially if you change TA0CCR0 each time)

  • Jens,

    thanks you for your comments. Answers and follw up below:

    Jens-Michael Gross said:

    Or you use an MSP which does what you need. Many of the MSPs have e.g. a TimerB7, which has up to 7 PWM CCRs including TBCCR0.

    unfortunately, this design is centered around the CC430, with embedded RF. Choosing a different MSP would have other implications which i am not prepared to deal with.

    Jens-Michael Gross said:

    Doing a quick sweep over your code, I wonder why you do the distance calculation. The destination position solely depends on the PWM value and not on the current position. So just set the new PWM value and you're done. If you busy-wait until the destination is reached, you cannot do anything else (like adjusting the other servos or whatever). And if the position changes again before the servo has settled, it won't change anything. Just set the new PWM and be happy. Of course if you do feedback calculations faster than the servor reacts, this will introduce problems, but doing a busy-waiting surely isn't the best way to handle it.

    Of course you know your hardware better than I do (since you didn't mention it at all). But with all this SERVO_ON and SERVO_OFF, I wonder why you need more than one PWM output at all for all servos. (especially if you change TA0CCR0 each time)

    Sorry, i should have probably provided more info on the hardware. the project i am working on is a CC430 based telemetry & controller module for model airplanes. it is supposed to control 5 servos (among many other things). the servo switching you see in the code is trying to achieve 2 things: 1. reduce power as much as possible by only keeping the servo on for the time needed to drive to a new position. 2. some servos, if switched on before a valid PWM signal is present, causes the servos to jump/jitter for a brief moment. by having PWM output present during the servo is switched on, the switch on behavior of the servo is greatly improved, no jumps, no jitters. the same is try for the moment when the servo is switched off. driving 5 PWM outputs in parallel is really not related to the switch on logic.

    regarding the distance, we use the distance to avoid turning on a servo which doesn't need to drive anywhere. as stated above, turning on a servo with a PWM which is unchanged from the previous position, doesn't mean the servo won't move. many servos with jump to a slightly different position (maybe 1 or 2 deg) and then jump back to the where they were. the distance is a way to avoid this and i have tested this in the lab and it definitely makes a difference.

     

    btw, maybe my code was misleading, the PWM period (ccr0) isn't typically changed between servos. all 5 can use the same period. reading your post, maybe i should try to drive the 5th servo by manually switching the PWM pin in a timer interrupt...

  • Okay, that's a bit more to work with. I guess you're using hte CC430F513x then (you don't need an LCD inside the airplane, do you?).

    What is the minimum PWM frequency the servos require? If 1kHz is fast enough (at least for one or two of them), you can set up TimerA1 for 1kHz (using TA1CCR0) and use TA1CCR1 and TA1CCR2 for two additional PWM outputs. Also, TA1 overflow will give you a nice 1ms timer interrupt that can be used for all kinds of things such as delays. This way you have a delay timer and 6 PWM outputs.

    About the servo jumping: Usually, the cheaper servos just do a voltage level integration over the servo PWM signal (buffered by some input logic so the maximum voltage level is maintained). Usually a low-pass filter. The voltage that forms after the low-pass is the reference voltage for the servo position. The higher the PWM frequency, the less ripple this reference voltage has. If you turn on or change PWM and then immediately the servo, the low-pass hasn't settled properly, hence the position jitter.

    Calculating the distance isn't necessary for checking whether the servo has to move or not. Just comparing old and new value is enough. But since you want to wait until the servo has reached its destination and then switch it off after a calculated time, you'll indeed need it. (I guess you're not really switching the servo off but rather switch the loopback circuit off, since most servos I know of won't hold their position if they are really switched off).

    My thought was that since you don' t need the PWM output all the time, you could just hook all servos to the same PWM output, set the PWM as required, then switch the desired servo on and off again, then continue with the next one. Only one PWM output required for all. Of course this will slow down the update cycle. But unless you do the on/off switching interleaved, you're already that slow anyway.

    As for the delay timer: set a global variable to the desired delay time and enter low power mode. THe tiemr ISR will cound the variabel down each interrupt and if it reaches 0, it will exit LPM, so your main loop continues. Two flies, one timer. :)

**Attention** This is a public forum