Pulse Width Modulation, #include <aery32/pwm.h>

Start by initializing the PWM channel which you want to use

pwm_init_channel(2, MCK);

The above initializer sets channel’s two PWM frequency equal to the main clock and omits the duration and period for default values. The default values for the duration and period are 0 and 0xFFFFF, respectively. If you like to start the channel with different values, you could have defined those too like this

pwm_init_channel(2, MCK, 50, 100);

This gives you duty cycle of 50% from start. The maximum value for both the duration and the period is 0xFFFFF. It is also worth noting that when the period is set to its maximum value, the channel’s duty cycle can be set most accurately.

The above initializers set the channel’s frequency equal to the main clock. The other possible frequency selections are

  • MCK_DIVIDED_BY_2
  • MCK_DIVIDED_BY_4
  • MCK_DIVIDED_BY_8
  • MCK_DIVIDED_BY_16
  • MCK_DIVIDED_BY_32
  • MCK_DIVIDED_BY_64
  • MCK_DIVIDED_BY_128
  • MCK_DIVIDED_BY_256
  • MCK_DIVIDED_BY_512
  • MCK_DIVIDED_BY_1024
  • PWM_CLKA
  • PWM_CLKB

PWM_CLKA and PWM_CLKB are two extra PWM clock sources. The difference to other sources is an additional linear divider block that comes after the MCK prescaler. To initialize the divider block for the PWM_CLKA and PWM_CLKB call

pwm_init_divab(MCK, 10, MCK_DIVIDED_BY_2, 10);

Now PWM_CLKA has the frequency of MCK / 10 Hz and PWM_CLKB is MCK / 2 / 10 Hz. If you don’t care about CLKB, you can omit the last two of the parameters like this

pwm_init_divab(MCK, 10);

Note

If the divider of PWM_CLKA or PWM_CLKB has been set zero, then the PWM clock will equal to the MCK, MCK_DIVIDED_BY_2, etc. Whatever was the chosen prescaler. So it does not make sense to set the divider of the extra PWM clock zero, because then you don’t have any extra clock selection.

Setting up PWM mode

Before enabling the initialized PWM channel or channels, you may like to setup the channel mode to set PWM alignment and polarity

pwm_setup_chamode(2, LEFT_ALIGNED, START_HIGH);

The alignment (left or center, LEFT_ALIGNED and CENTER_ALIGNED, respectively) defines the shape of PWM function, see datasheet page 680. The polarity defines the polarity of the duty cycle. With START_HIGH, the duty cycle is 100% when duration / period of the PWM function gives 1. With START_LOW you would get 100% duty cycle when the duration / period is 0.

Enabling and disabling the PWM

PWM is enabled and disabled by channels. Several channels can be enabled at once to get synchronized output. To enable channels two and four call

pwm_enable((1 << 2)|(1 << 4));

Same goes for the disabling the channels. The following call will disable the channel two

pwm_disable(1 << 2);

The parameter of the enable and disable functions is a bitmask of the channels to be enabled or disabled. There is also function to check if the channel has been enabled already. The following snippet will do something if the channel two was already enabled

if (pwm_is_enabled(1 << 2)) {
    /* Do something */
}

Modulating the PWM output waveform

You can modulate the PWM output waveform when it is active by changing its duty cycle like this

pwm_update_dutycl(2, 0.5);

The above function call will update the channel’s two duty cycle to 50%. In case you want to specify completely new values for the period and duration use these two functions

pwm_update_period(2, 0x1000);
pwm_update_duration(2, 0x10);

Furthermore, to keep PWM output at the desired state for the amount of periods, before changing its state again, use the wait function. This also allows you to do updates from the beginning of the next period and thus avoiding to overwrite the value too soon. For example, to wait 100 periods on channel two call

pwm_wait_periods(2, 100);

With the combination of the update functions and the wait function, you can make a smoohtly blinking LED, just like this

uint8_t channel = 2;
uint32_t duration = 0;
uint32_t period = 0x1000;

for (;;) {
    for (; duration < period; duration++) {
        pwm_update_duration(channel, duration);
        pwm_wait_periods(channel, 500);
    }
    for (; duration > 0; duration--) {
        pwm_update_duration(channel, duration);
        pwm_wait_periods(channel, 500);
    }
}

Note

Duration has to be smaller or equal to period.