Raspberry Pi CM5 IoT In C - - PWM Using GPIO5
Written by Harry Fairhead   
Monday, 12 May 2025
Article Index
Raspberry Pi CM5 IoT In C - - PWM Using GPIO5
Pulse Density Mode
The PWM Registers
Working with PWM
What Else Can You Use PWM For?

The PWM Registers

The PWM registers start at 0x40098000, which gives them an offset of 0x9800. Each PWM channel has two fundamental registers, RANGE and DUTY.

The first register in the set is GLOBAL_CTRL in which the first four bits enable the corresponding PWM channel and the topmost bit triggers the setting of new values in the PWM hardware from the registers.

Bits

Name

Access

Reset

31

SET_UPDATE

SC

0x0

30:4

Reserved

3

CHAN3_EN

RW

0x0

2

CHAN2_EN

RW

0x0

1

CHAN1_EN

RW

0x0

0

CHAN0_EN

RW

0x0

 

That is, if you write 0x1 to the GLOBAL_CTRL register then PWM0 is enabled. You can then set its control registers, but these have no effect until you write 080000000 to the GLOBAL_CTRL register to set bit 31, SET_UPDATE, when all of the new parameters are transferred to the PWM hardware. This ensures that all of the PWM settings are changed on the same clock pulse.

After this come the FIFO control register for modes that use the FIFO:

0x04

FIFO_CTRL

0x08

COMMON_RANGE

0x0c

COMMON_DUTY

0x10

DUTY_FIFO

 

The FIFO_CTRL configures the FIFO register at DUTY_FIFO. The COMMON_RANGE and COMMON_DUTY registers set the range and duty for channels that are set to channel binding.

 

From here we have a control, range, phase and duty register for each of the channels. This can be converted into a C array of structs:

typedef struct
{
    uint32_t Ctrl;
    uint32_t Range;
    uint32_t Phase;
    uint32_t Duty;
} PWMregs;
#define PWM ((PWMregs *)(PWMBase + 0x14 / 4))

The only register that hasn’t been discussed is the phase register. This can be used to initialize the counter so as to cause an offset between the different channels i.e. a phase shift.

The channel control registers have a simple structure:

‍Bits

Name

Access

Reset

31:16

SDM_BIAS

RW

0x0000

15:12

SDM_BITWIDTH

RW

0x0

11:9

Reserved

 

8

FIFO_POP_MASK

RW

0x1

7

SDM_DITHER

RW

0x0

6

SDM

RW

0x0

5

USEFIFO

RW

0x0

4

BIND

RW

0x0

3

INVERT

RW

0x0

2:0

MODE

RW

0x0

The fields starting SDM control a Sigma Delta filter so that the PWM output can be used to create analog signals, something that is beyond the scope of this book. The fields with FIFO in their name control the way that the first-in, first out buffer is used in generating custom bit sequences. For the standard PWM modes of operation, the fields that are useful are in the first five bits. Bit 4 can be set to 1 to force the channel to use the global RANGE and DUTY registers – note that each channel still controls its own phase. Bit 3 can be set to invert the output and the low three bits set the PWM mode as described earlier.

You can see the correspondence between the bit values and the modes along with an enum to make it easier to use in the following table:

Action

enum pwm_mode_rp1

0x0 

Generates 0

Zero

0x1 

Trailing-edge mark-space PWM modulation

TrailingEdge

0x2 

Phase-correct mark-space PWM modulation

PhaseCorrect

0x3 

Pulse-density encoded output

PDE

0x4 

MSB Serializer output

MSBSerial

0x5 

Pulse position modulated output

PPM

0x6 

Leading-edge mark-space PWM modulation

LeadingEdge

0x7 

LSB Serialiser output

LSBSerial

There are also some registers at higher locations concerned with PWM interrupts.

PWM Clock Registers

As well as working with the PWM registers, we also have to set the PWM clock. The problem is that the details of the registers are not well documented so we need to resort to reverse engineering. The PWM clock has four registers:

typedef struct
{
    uint32_t PWM0_CTRL;
    uint32_t PWM0_DIV_INT;
    uint32_t PWM0_DIV_FRAC;
    uint32_t PWM0_SEL;
} pwmclockregs;
#define PWMCLK ((pwmclockregs *)ClockBase)

The CTRL and SEL registers are used to setup the clock and the DIV_INT and DIV_FRAC registers set the integer and fractional part of the clock division factors.

The PWM clock registers are at:

#define PWMCLK ((pwmclockregs *)(ClockBase+0x74/4))

 

To set the PWM clock we need to add two functions to Gpio5:

void pwm_init_clock(void)
{
    PWMCLK->PWM0_CTRL = 0x11000840;
    PWMCLK->PWM0_SEL = 1;
}
and:
void pwm_set_clock(uint32_t div,uint32_t frac)
{
    PWMCLK->PWM0_DIV_INT = div;
    PWMCLK->PWM0_DIV_FRAC = frac;
}

The init_clock function is called to configure the PWM clock and the set_clock function is called to set the frequency divider to get the frequency you actually want.



Last Updated ( Tuesday, 13 May 2025 )