all
Chapter 14 of 20

PWM Module — Tiva C

Eslam El Hefny Apr 14, 2025 7 min read
70% done

PWM Module — Tiva C

Overview

The TM4C123GH6PM includes two PWM modules (PWM0 and PWM1), each containing four generators for a total of eight generators and sixteen PWM output signals (M0PWM0–M0PWM7 and M1PWM0–M1PWM7). These hardware PWM outputs generate precise, CPU-independent waveforms used for motor control, LED dimming, and signal generation.


Beginner Level — What & Why

What is PWM?

PWM (Pulse Width Modulation) is a technique for controlling power delivery by rapidly switching a signal between ON and OFF. The ratio of ON time to total period is called the duty cycle (0%–100%). By changing the duty cycle, you control the average power delivered to a load — dimming an LED or controlling a motor’s speed.

Real-World Analogy

Imagine a light switch you flick on and off very rapidly. If it is ON half the time and OFF half the time, the bulb appears half as bright. Doing this 50 times per second (50 Hz) is invisible to the eye — the bulb just looks dimmer. That is PWM.

What Problem Does It Solve?

  • LED brightness control without power-wasting resistors
  • DC motor speed control
  • Servo motor position control (50 Hz, 1–2 ms pulse width = 0°–180°)
  • Tone/frequency generation for buzzers

Key Terms

Term Meaning
Period Total cycle time (1 / frequency)
Duty cycle Fraction of period the output is HIGH
Generator Hardware block producing 2 complementary outputs
Count-down Counter counts from LOAD to 0 (simplest mode)
Count-up/down Counter counts up then down (for complementary/dead-band)
PWMDIV System clock prescaler for PWM clock

Intermediate Level — How It Works

PWM Module Architecture

PWM0 Module:
  Generator 0 → M0PWM0 (PB6) + M0PWM1 (PB7)
  Generator 1 → M0PWM2 (PB4) + M0PWM3 (PB5)
  Generator 2 → M0PWM4 (PE4) + M0PWM5 (PE5)
  Generator 3 → M0PWM6 (PC4) + M0PWM7 (PC5)

PWM1 Module:
  Generator 0 → M1PWM0 (PD0) + M1PWM1 (PD1)
  Generator 1 → M1PWM2 (PA6) + M1PWM3 (PA7)
  Generator 2 → M1PWM4 (PF0) + M1PWM5 (PF1)
  Generator 3 → M1PWM6 (PF2) + M1PWM7 (PF3)

Key Registers (Per Generator)

Register Offset from Gen Description
PWMCTL 0x000 Generator control (enable, mode)
PWMINTEN 0x004 Interrupt enable
PWMRIS 0x008 Raw interrupt status
PWMISC 0x00C Interrupt clear
PWMLOAD 0x010 Load value (sets period)
PWMCOUNT 0x014 Current counter value
PWMCMPA 0x018 Compare A (PWMxA duty)
PWMCMPB 0x01C Compare B (PWMxB duty)
PWMGENA 0x020 Generator A action (what to do at compare events)
PWMGENB 0x024 Generator B action
PWMDBCTL 0x028 Dead-band control

Module-level registers:

Register Offset Description
PWMENABLE 0x008 Enable PWM outputs
PWMCTL 0x000 Global sync

TivaWare DriverLib API

#include "driverlib/pwm.h"
#include "driverlib/pin_map.h"

/* Step 1: Configure PWM clock = system clock / 64 = 1.25 MHz */
SysCtlPWMClockSet(SYSCTL_PWMDIV_64);

/* Step 2: Enable PWM1 and Port F */
SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM1);
SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_PWM1));
while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF));

/* Step 3: Configure PF1 as M1PWM5 output */
GPIOPinConfigure(GPIO_PF1_M1PWM5);
GPIOPinTypePWM(GPIO_PORTF_BASE, GPIO_PIN_1);

/* Step 4: Configure Generator 2 count-down, no sync */
PWMGenConfigure(PWM1_BASE, PWM_GEN_2,
                PWM_GEN_MODE_DOWN | PWM_GEN_MODE_NO_SYNC);

/* Step 5: Set period
 * PWM clock = 80 MHz / 64 = 1.25 MHz
 * For 50 Hz: period = 1.25 MHz / 50 = 25,000 ticks */
PWMGenPeriodSet(PWM1_BASE, PWM_GEN_2, 25000);

/* Step 6: Set duty cycle
 * 1.5 ms pulse at 50 Hz = 1.5 ms × 1.25 MHz = 1875 ticks */
PWMPulseWidthSet(PWM1_BASE, PWM_OUT_5, 1875);

/* Step 7: Enable output */
PWMOutputState(PWM1_BASE, PWM_OUT_5_BIT, true);

/* Step 8: Start generator */
PWMGenEnable(PWM1_BASE, PWM_GEN_2);

Advanced Level — Deep Dive

Bare-Metal PWM (Servo on PF1 = M1PWM5)

#include "inc/hw_memmap.h"
#include "inc/hw_types.h"
#include "inc/hw_pwm.h"

/* PWM1 Generator 2 base offset = 0x40029080 */
#define PWM1_GEN2_BASE  (PWM1_BASE + PWM_GEN_2_OFFSET)

void PWM1_Servo_Init(void)
{
    /* PWM clock = SYSCLK / 64 = 80 MHz / 64 = 1.25 MHz */
    HWREG(SYSCTL_RCC) |= SYSCTL_RCC_USEPWMDIV;
    HWREG(SYSCTL_RCC) &= ~SYSCTL_RCC_PWMDIV_M;
    HWREG(SYSCTL_RCC) |= SYSCTL_RCC_PWMDIV_64;

    /* Enable PWM1 and Port F clocks */
    HWREG(SYSCTL_RCGCPWM)  |= (1U << 1);  // PWM1
    HWREG(SYSCTL_RCGCGPIO) |= (1U << 5);  // Port F
    while (!(HWREG(SYSCTL_PRPWM)  & (1U << 1)));
    while (!(HWREG(SYSCTL_PRGPIO) & (1U << 5)));

    /* Configure PF1 for M1PWM5 (alternate function 5) */
    HWREG(GPIO_PORTF_BASE + GPIO_O_AFSEL) |= GPIO_PIN_1;
    HWREG(GPIO_PORTF_BASE + GPIO_O_PCTL)  =
        (HWREG(GPIO_PORTF_BASE + GPIO_O_PCTL) & ~0xF0) | 0x50; // PMC1=5
    HWREG(GPIO_PORTF_BASE + GPIO_O_DEN)   |= GPIO_PIN_1;

    /* Disable generator while configuring */
    HWREG(PWM1_GEN2_BASE + PWM_O_X_CTL) = 0;

    /* Count-down mode */
    HWREG(PWM1_GEN2_BASE + PWM_O_X_GENA) =
        PWM_X_GENA_ACTCMPAD_ONE |   // go HIGH when count == CMPA (descending)
        PWM_X_GENA_ACTLOAD_ZERO;    // go LOW when counter loads (i.e., at LOAD)

    /* Period: 25,000 ticks = 20 ms at 1.25 MHz */
    HWREG(PWM1_GEN2_BASE + PWM_O_X_LOAD) = 25000 - 1;

    /* Duty: 1875 ticks = 1.5 ms (90 degrees centre position) */
    HWREG(PWM1_GEN2_BASE + PWM_O_X_CMPA) = 25000 - 1875;

    /* Enable generator */
    HWREG(PWM1_GEN2_BASE + PWM_O_X_CTL) = PWM_X_CTL_ENABLE;

    /* Enable output 5 */
    HWREG(PWM1_BASE + PWM_O_ENABLE) |= PWM_OUT_5_BIT;
}

/* Set servo angle 0-180 degrees
 * Pulse: 1 ms (0°) to 2 ms (180°) at 1.25 MHz = 1250 to 2500 ticks */
void Servo_SetAngle(uint8_t degrees)
{
    uint32_t ticks = 1250 + ((uint32_t)degrees * 1250 / 180);
    HWREG(PWM1_GEN2_BASE + PWM_O_X_CMPA) = 25000 - ticks;
}

Calculating PWM Parameters

PWM clock = System clock / PWMDIV
         = 80 MHz / 64 = 1.25 MHz

Ticks per period = PWM clock / Desired frequency
For 50 Hz servo: 1,250,000 / 50 = 25,000 ticks

Duty cycle ticks = Period ticks × (duty% / 100)
For 50% duty: 25,000 × 0.5 = 12,500 ticks

Servo pulse width in ticks:
1.0 ms = 1,250 ticks (0 degrees)
1.5 ms = 1,875 ticks (90 degrees)
2.0 ms = 2,500 ticks (180 degrees)

Common PWMDIV Values

Macro Divisor PWM Clock at 80 MHz
SYSCTL_PWMDIV_1 1 80 MHz
SYSCTL_PWMDIV_2 2 40 MHz
SYSCTL_PWMDIV_4 4 20 MHz
SYSCTL_PWMDIV_8 8 10 MHz
SYSCTL_PWMDIV_16 16 5 MHz
SYSCTL_PWMDIV_32 32 2.5 MHz
SYSCTL_PWMDIV_64 64 1.25 MHz

Gotchas

  • PWM uses its own clock divider (SYSCTL_RCC PWMDIV) — it is separate from the system clock divider.
  • PWMPulseWidthSet accepts the number of ticks the output is HIGH — do not confuse with the LOAD register value.
  • In count-down mode, CMPA acts on the descending count, so CMPA = LOAD - pulse_ticks for correct behaviour in bare-metal.
  • PWMOutputState must be called to actually enable the output pin — PWMGenEnable only starts the counter.
  • PF0 conflict: M1PWM4 shares PF0 with the NMI function — unlock PF0 as shown in the GPIO chapter before using it for PWM.

Step-by-Step Example

/*
 * pwm_servo.c
 * Sweeps a servo from 0 to 180 degrees and back using M1PWM5 on PF1
 * Board  : TM4C123GXL EK LaunchPad
 * SDK    : TivaWare_C_Series-2.2.x
 * Wiring : PF1 → servo signal wire, 5V to servo power (external supply)
 */

#include <stdint.h>
#include <stdbool.h>
#include "inc/hw_memmap.h"
#include "driverlib/sysctl.h"
#include "driverlib/gpio.h"
#include "driverlib/pwm.h"
#include "driverlib/pin_map.h"

/* PWM clock = 80 MHz / 64 = 1.25 MHz, period = 25000 ticks = 20 ms (50 Hz) */
#define PWM_PERIOD_TICKS   25000
#define SERVO_MIN_TICKS    1250   /* 1.0 ms = 0 degrees */
#define SERVO_MAX_TICKS    2500   /* 2.0 ms = 180 degrees */

void Servo_SetDegrees(uint8_t deg)
{
    /* Map 0-180 degrees to 1250-2500 ticks */
    uint32_t ticks = SERVO_MIN_TICKS +
                     ((uint32_t)deg * (SERVO_MAX_TICKS - SERVO_MIN_TICKS)) / 180;
    PWMPulseWidthSet(PWM1_BASE, PWM_OUT_5, ticks);
}

int main(void)
{
    uint8_t deg;

    /* Step 1: 80 MHz system clock */
    SysCtlClockSet(SYSCTL_SYSDIV_2_5 | SYSCTL_USE_PLL |
                   SYSCTL_OSC_MAIN   | SYSCTL_XTAL_16MHZ);

    /* Step 2: PWM clock = system / 64 = 1.25 MHz */
    SysCtlPWMClockSet(SYSCTL_PWMDIV_64);

    /* Step 3: Enable PWM1 and Port F */
    SysCtlPeripheralEnable(SYSCTL_PERIPH_PWM1);
    SysCtlPeripheralEnable(SYSCTL_PERIPH_GPIOF);
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_PWM1));
    while (!SysCtlPeripheralReady(SYSCTL_PERIPH_GPIOF));

    /* Step 4: PF1 → M1PWM5 */
    GPIOPinConfigure(GPIO_PF1_M1PWM5);
    GPIOPinTypePWM(GPIO_PORTF_BASE, GPIO_PIN_1);

    /* Step 5: Generator 2, count-down mode */
    PWMGenConfigure(PWM1_BASE, PWM_GEN_2,
                    PWM_GEN_MODE_DOWN | PWM_GEN_MODE_NO_SYNC);

    /* Step 6: 50 Hz period */
    PWMGenPeriodSet(PWM1_BASE, PWM_GEN_2, PWM_PERIOD_TICKS);

    /* Step 7: Centre position (90 degrees = 1.5 ms) */
    PWMPulseWidthSet(PWM1_BASE, PWM_OUT_5, 1875);

    /* Step 8: Enable output and start generator */
    PWMOutputState(PWM1_BASE, PWM_OUT_5_BIT, true);
    PWMGenEnable(PWM1_BASE, PWM_GEN_2);

    /* Sweep 0 → 180 → 0 degrees continuously */
    while (1)
    {
        for (deg = 0; deg <= 180; deg++)
        {
            Servo_SetDegrees(deg);
            SysCtlDelay(SysCtlClockGet() / 3 / 50);  // ~20 ms per step
        }
        for (deg = 180; deg > 0; deg--)
        {
            Servo_SetDegrees(deg);
            SysCtlDelay(SysCtlClockGet() / 3 / 50);
        }
    }
}

Summary

Key Point Details
PWM modules PWM0 (4 generators, 8 outputs) + PWM1 (4 generators, 8 outputs)
PWM clock System clock / PWMDIV (64 options)
Servo frequency 50 Hz (PERIOD = 25,000 at 1.25 MHz)
Servo 0° 1.0 ms pulse = 1,250 ticks
Servo 90° 1.5 ms pulse = 1,875 ticks
Servo 180° 2.0 ms pulse = 2,500 ticks
Enable output PWMOutputState + PWMGenEnable
Duty cycle PWMPulseWidthSet(base, output, ticks)

Next Chapter

SSI Interface

Share: