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.

CCS/MSP432P401R: How to calculate timer period and duty cycle for a 50hz PWM servo?

Part Number: MSP432P401R

Tool/software: Code Composer Studio

Timer_A_PWMConfig pwmConfig =
{
        //uint_fast16_t clockSource;
        TIMER_A_CLOCKSOURCE_SMCLK,
        //uint_fast16_t clockSourceDivider;
        TIMER_A_CLOCKSOURCE_DIVIDER_1,
        //uint_fast16_t timerPeriod;
        1280,
        //uint_fast16_t compareRegister;
        TIMER_A_CAPTURECOMPARE_REGISTER_2,
        //uint_fast16_t compareOutputMode;
        TIMER_A_OUTPUTMODE_RESET_SET,
        //uint_fast16_t dutyCycle;
        80
};

int main(void)
{
    /* Halting the watchdog */
    MAP_WDT_A_holdTimer();

    /* Setting MCLK to REFO at 128Khz for LF mode
     * Setting SMCLK to 64Khz */
    MAP_CS_setReferenceOscillatorFrequency(CS_REFO_128KHZ);
    MAP_CS_initClockSignal(CS_MCLK, CS_REFOCLK_SELECT, CS_CLOCK_DIVIDER_1);
    MAP_CS_initClockSignal(CS_SMCLK, CS_REFOCLK_SELECT, CS_CLOCK_DIVIDER_2);
    MAP_PCM_setPowerState(PCM_AM_LF_VCORE0);

    /* Configuring GPIO2.5 as peripheral output for PWM  and P1.1 for button
     * interrupt */

    MAP_GPIO_setAsPeripheralModuleFunctionOutputPin(GPIO_PORT_P2, GPIO_PIN5, GPIO_PRIMARY_MODULE_FUNCTION);
    MAP_GPIO_setOutputHighOnPin(GPIO_PORT_P2, GPIO_PIN5);

    MAP_GPIO_setAsInputPinWithPullUpResistor(GPIO_PORT_P1, GPIO_PIN1);
    MAP_GPIO_clearInterruptFlag(GPIO_PORT_P1, GPIO_PIN1);
    MAP_GPIO_enableInterrupt(GPIO_PORT_P1, GPIO_PIN1);


    /* Configuring Timer_A to have a period of approximately 500ms and
     * an initial duty cycle of 10% of that (3200 ticks)  */
    MAP_Timer_A_generatePWM(TIMER_A0_BASE, &pwmConfig);

    /* Enabling interrupts and starting the watchdog timer */
    MAP_Interrupt_enableInterrupt(INT_PORT1);
    MAP_Interrupt_enableSleepOnIsrExit();
    MAP_Interrupt_enableMaster();

    /* Sleeping when not in use */
    while (1)
    {
        MAP_PCM_gotoLPM0();
    }
}

/* Port1 ISR - This ISR will progressively step up the duty cycle of the PWM
 * on a button press
 */
void PORT1_IRQHandler(void)
{
    uint32_t status = MAP_GPIO_getEnabledInterruptStatus(GPIO_PORT_P1);
    MAP_GPIO_clearInterruptFlag(GPIO_PORT_P1, status);

    if (status & GPIO_PIN1)
    {
        if(pwmConfig.dutyCycle == 1000)
            pwmConfig.dutyCycle = 280;
        else
            pwmConfig.dutyCycle += 280;

        MAP_Timer_A_generatePWM(TIMER_A0_BASE, &pwmConfig);
    }
}

So I want to make it where if i press switch 1 (p1.1) it will rotate the motor all the way to 180 degrees, then if i press it again it will interrupt and go the other way. But right now I can't figure out what the timerperiod and duty cycle should be. I understand that it the period is 20ms, and the duty cycle is 1-2ms. But what I don't get is what does it mean at 1ms the servo will be 0°. Does that mean that if i set the duty cycle to 10% it will aim to go to that position?

Also how do I know what the timer period should be? or can i use any number as long as the duty cycle is set to 5-10% of the period. Thanks a lot in advance!

  • You've set SMCLK=128kHz/2=64kHz, (aka 64000 ticks/sec) so

    1ms is 64000/1000=64 ticks.

    2ms is 2*64=128 ticks.

    20ms is 20*64=1280 ticks. 

    I recall 90-degree servos, where 1ms gives you 0 degress and 2ms gives you 90 degrees. 2ms might give your servo 90 degrees or 180 degrees. It might be quicker to experiment than to try to figure it out.

    Unsolicited:

    >        if(pwmConfig.dutyCycle == 1000)

     This will get you in trouble eventually, since 1000 isn't divisible by 280. I suggest:

    >        if(pwmConfig.dutyCycle >= 1000)

     

  • Thanks so much for your response! I get that now. However, I still don't quite get the logic behind turning the servo. The code as it stands, when i press the switch it moves a little. Then if I press the switch again and again nothing happens. To be honest I don't get what is going on with this code either.

    if(pwmConfig.dutyCycle == 1000)
                pwmConfig.dutyCycle = 280;
            else
                pwmConfig.dutyCycle += 280;

  • A PWM servo positions itself based on the width of the pulse you send. Canonically, a 1ms pulse causes it to position at "min" (usually 0deg), 2ms causes it to position at "max" (usually 90deg), and 1.5ms goes to "middle" (45deg). This positioning needs to be refreshed regularly -- typically every 20ms, though they're usually not very picky about this. [Prof. Pedant says: Strictly speaking, this is Pulse Width Coding (PWC) not Pulse Width Modulation (PWM), since the result depends only on the absolute width of the pulse.]

    There are variants, and I'm not clear where all the numbers in your program came from. What does the datasheet say?

    I expect the code you quoted is intended to increment the pulse width by some amount with each button push until it reaches some maximum then it starts the cycle over -- moving the servo by steps through its movement range. As I mentioned, I don't know where the numbers came from, but I can tell it will overshoot the max and just keep increasing.

  • I got it from this other forum question.

    https://e2e.ti.com/support/microcontrollers/msp430/f/166/t/560568

    But now based on what you said, I changed my code.

    void PORT1_IRQHandler(void)
    {
        uint32_t status;
        do{
            status = MAP_GPIO_getEnabledInterruptStatus(GPIO_PORT_P1);
            if (status & GPIO_PIN1)
            {
                if(pwmConfig.dutyCycle = 64)
                    pwmConfig.dutyCycle = 128;
                else
                    pwmConfig.dutyCycle = 64;
    
                MAP_Timer_A_generatePWM(TIMER_A0_BASE, &pwmConfig);
    
                MAP_GPIO_clearInterruptFlag(GPIO_PORT_P1, status);
            }
        }while (status);
    
    }
    


    So my logic is, if I press the button it will go to the 1ms position (64 tick). Then if i press it again and it is in the 1ms position, it will go to the 2ms position (128 tick). But somehow my code doesn't work. I press the button and it goes to the correct position but then i press it again and nothing happens.

  • Typo alert:

    >            if(pwmConfig.dutyCycle = 64)
    should probably be:
    >            if(pwmConfig.dutyCycle == 64)
     
    (I'm supposing you set dutyCycle=64 initially.)

**Attention** This is a public forum