mbed library sources

Dependents:   Encrypted my_mbed lklk CyaSSL_DTLS_Cellular ... more

Issue: Duty cycle is not maintained when period is changed in pwmout_api.c::pwmout_period_us()

I believe I've found a bug in pwmout_api.c. This applies to the LPC1768 and possibly others that use similar code. I searched for other reports of this issue on mbed.org, but didn't find anything.

While using the PwmOut driver, I noticed that if I changed the PWM period, the duty cycle also changed (because the pulse width did not change). According to the PwmOut class documentation, the duty cycle should be maintained at its previous value whenever the period is changed. It's possible that this hasn't been reported due to the likelihood that PWM period is rarely updated dynamically. So, users may not be looking for this type of issue.

I tracked this problem down to the following code in pwmout_api.c:

// Set the PWM period, keeping the duty cycle the same.
void pwmout_period_us(pwmout_t* obj, int us) {
    // calculate number of ticks
    uint32_t ticks = pwm_clock_mhz * us;

    // set reset
    LPC_PWM1->TCR = TCR_RESET;

    // set the global match register
    LPC_PWM1->MR0 = ticks;

    // Scale the pulse width to preserve the duty ratio
    if (LPC_PWM1->MR0 > 0) {
        *obj->MR = (*obj->MR * ticks) / LPC_PWM1->MR0;
    }

    // set the channel latch to update value at next period start
    LPC_PWM1->LER |= 1 << 0;

    // enable counter and pwm, clear reset
    LPC_PWM1->TCR = TCR_CNT_EN | TCR_PWM_EN;
}

Note that on entry, ticks is set to the new period in usec. This is then used to set the new match register value (the new period), LPC_PWM1->MR0. Subsequently, in the if() {...} multiply/divide calculation, ticks is equal to LPC_PWM1->MR0. So, the resulting value of *obj->MR, the pulse width, is unchanged from its previous value, in this calculation:

        *obj->MR = (*obj->MR * ticks) / LPC_PWM1->MR0;

This causes the duty cycle to not be maintained when only period is changed. As a possible solution, I believe the assignment LPC_PWM1->MR0 = ticks should be moved just after the if() {...} statement. This would use the period corresponding to the current duty cycle to update the pulse width.

Could mbed developers look into this issue to confirm or not? I also posted this in the mbed.org Bugs & Suggestions forum.

4 comments:

01 Feb 2014

Hello David G,

are you familiar with github? Because the most of a development is happening there, so you can ask for a pull request with the fix already in place. Based on a review, it will get accepted or declined with comments.

I just glanced at files, looks like more targets has this problem. Not 13XX, which seems to be doing it correctly.

Thanks for reporting the bug.

Regards, 0xc0170

01 Feb 2014

It probably also needs to do it for all MRs, and not just the current one.

That or decide to not keep the duty cycle constant with changing periods, less code :D, and imo there is also something to say for that.

03 Feb 2014

Erik,

You are correct that all the PWM MR's would need to be changed in order to account for a change in PWM period on any of the channels. I was just going to make an edit to my issue above, but noticed that you already pointed out that fact. Thanks. The problem fix is quite a bit more involved for that reason.

05 Feb 2014

It might make sense to maintain an array of duty cycles, one per PWM channel. An element in this array could be updated whenever a PWM channel pulse width or duty cycle is changed. When period is changed, use the duty cycles to update the MR's of all PWM channels based on the new period.