Raspberry Pi CM5 IoT In C - - PWM Using GPIO5 |
Written by Harry Fairhead | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Monday, 12 May 2025 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Page 3 of 5
The PWM RegistersThe 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.
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:
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:
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:
There are also some registers at higher locations concerned with PWM interrupts. PWM Clock RegistersAs 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 ) |