Raspberry Pi CM5 IoT In C - - PWM Using GPIO5 |
Written by Harry Fairhead | ||||||
Monday, 12 May 2025 | ||||||
Page 4 of 5
Working with PWMExtending Gpio5 to work with PWM is fairly easy but there is no point in trying to follow the Pico SDK as its PWM hardware is very different from the CM5. Now we have the functions to set the clock running we can set the GPIO lines we want to use into PWM mode: int pwm_setup(uint32_t gpio, enum pwm_mode_rp1 mode) { int pwm = getPWM(gpio); if (pwm < 0) return -1; pwm_init_clock(); if (gpio == 18 | gpio == 19){ gpio_set_function(gpio, GPIO_FUNC_PWM2); } else{ gpio_set_function(gpio, GPIO_FUNC_PWM1); } PWM[pwm].Ctrl = mode; pwm_disable(gpio); } enum pwm_mode_rp1 { Zero = 0x0, TrailingEdge = 0x1, PhaseCorrect = 0x2, PDE = 0x3, MSBSerial = 0x4, PPM = 0x5, LeadingEdge = 0x6, LSBSerial = 0x7 }; This allows you to setup a GPIO line to use PWM, but of course not all GPIO lines support PWM and the ones that do use particular pwm channels. The getPWM function returns the channel used or -1 if the GPIO line isn’t PWM compatible: int getPWM(uint32_t gpio) { switch (gpio) { case 12: return 0; case 13: return 1; case 14: return 2; case 15: return 3; case 18: return 2; case 19: return 3; } return -1; } Notice that this returns the correct PWM channels for GPIO18 and GPIO19 and the setup function uses the correct GPIO mode for this selection. The PWM line is set to a disabled state so that it can have its range, duty and phase set before being enabled: int pwm_enable(uint32_t gpio) { int pwm = getPWM(gpio); if (pwm < 0) return -1; uint32_t temp = 1 << pwm; *PWMBase = *PWMBase | temp | 0x80000000; } int pwm_disable(uint32_t gpio) { int pwm = getPWM(gpio); if (pwm < 0) return -1; uint32_t temp = 1 << pwm; temp = ~temp & 0xf; *PWMBase = (*PWMBase & temp) | 0x80000000; } Notice that no check is made to see if the PWM line has been configured. A check is made that the GPIO line is a valid PWM line. After this the GLOBAL_CTRL register is used to enable the PWM channel and set the top most bit to commit the change. Notice that this update is done so as not to change any other settings in the register. Now all we need is a function that sets the range, duty and phase: int pwm_set_range_duty_phase(int32_t gpio, Notice that the high bit of the GLOBAL_CTRL register is set to commit the changes. Only the phase register actually needs this as the DUTY and RANGE registers update automatically at the end of the PWM cycle. The set_range_duty_phase function works, but in many cases we would like to set a frequency and a duty cycle as a percentage: int pwm_set_frequency_duty(int32_t gpio, int32_t freq, int dutyPercent) { int32_t div = PWMCLK->PWM0_DIV_INT; int32_t frac = PWMCLK->PWM0_DIV_FRAC; int32_t pwmf = PWMClock / div; int32_t range = pwmf / freq-1; int32_t duty = range * dutyPercent / 1000+1; pwm_set_range_duty_phase(gpio, range, duty, 0); } The duty cycle is specified as a percentage*10 to allow it to specify a decimal part. For example setting duty to 25 sets the duty cycle to 2.5% Notice that this function assumes that you have set the correct clock rate to give a good duty cycle resolution. In chapter but not in extract
|
||||||
Last Updated ( Tuesday, 13 May 2025 ) |