Some of you might have recognized that I omitted one significant detail in the C code in my last post "Use WS2812B on the SAMA5D4 Xplained":
How is the correct PWM clock frequency of 2.4 MHz configured?
Clocks, Clocks and more Clocks
The peripherals of the SAMA5D44 (no, this is not a typo, that's the name of the SOC on the SAMA5D4 Xplainded board) all receive their own clock signal. Relevant for PWM is the peripheral clock as shown in the diagram from the datasheet (page 1448):
Most clocks in the SOC are controlled by the Power Management Controller. Contrary to the name this component does not (only) turn the power of integrated devices on and off, it mainly controls the distribution of clock signals. (By not providing a clock signal to a specific component it is turned off.) The programmer can individually request those peripheral clocks to be turned on or off and can select a division factor to be applied to the boards master clock MCK. The factor can be 1, 2, 4 or 8 and is applied to the master clock.
The frequency of the peripheral clock can be calculated by BOARD_MCK / divider. The divider can be read / set through the PMC_PCR register and BOARD_MCK is defined in board.h. This would be nice and easy to use but would work only in a bare metal environment where the clock can not be influenced by other processes. But since I do not intend to dump the Linux running on the board I have to consider these values as variables. Back to the datasheet. It turns out that the relevant values for master clock are the PMC Master Clock Register:
- CSS: Master/Processor Clock Source Selection
- PRES: Master/Processor Clock Prescaler
- MDIV: Master Clock Division
Those values determine the true value of MCK (master clock).
MDIV is an other divisor for the master clock. It can be 1, 2, 3 or 4
The prescaler supports the division by a power of 2 of the selected clock between 1 and 64. The PRES and MDIV fields in PMC_MCKR programm the prescaler.
CSS can have several values to select one of the following clock sources:
- Slow Clock
- Main Clock
- PLLACK
- UPLL Clock
Now we know which clock source is selected an how it's frequency is divided to yield the peripheral clock. Next step is to find the frequency of the source. All source clocks are provided by the Clock Generator.
Slow clock can be selected to be either the 32 kHz on chip RC oscillator or the external 32768 Hz quartz. Selection is made by the OSCSEL bit in the Slow Clock Controller Configuration Register.
Main clock similarly can be configured to use internal or external 12 MHz sources, therefore is always 12 MHz.
UPLL generates frequencies which are higher than the base frequency (main ***) with a PLL. It is programmed through MULA field of the PMC Clock Generator PLLA Register. The MULA field is the PLLA multiplier factor. This parameter can be programmed between 0 and 127. If MULA is set to 0, PLLA is turned off, otherwise the PLLA output frequency is PLLA input frequency multiplied by (MULA + 1). The output is optionally dived by 2 (selected by PLLADIV2) the to generate PLLACK output.
UPLL Clock is fixed to 480 (40 x 12) MHz for USB High Speed.
The result is divided by the MCK_DIV to yield the frequency of the periphal clock wihch is the input into the PWM clock generation.
In C the above looks like that:
int get_periphal_clock_frequency(uint32_t ID) { int frequency, mck_div, mula; switch(PMC->PMC_MCKR & PMC_MCKR_CSS_Msk) { case 0: // Slow Clock is selected if(SCKC->SCKC_CR & SCKC_CR_OSCSEL) // XTAL selected frequency = 32768; else // internal RC selected frequency = 32000; break; case 1: // Main Clock is selected frequency = 12000000; break; case 2: // PLLACK is selected mula = (PMC->CKGR_PLLAR & CKGR_PLLAR_MULA_Msk) >> CKGR_PLLAR_MULA_Pos; frequency = 12000000 * (mula +1); break; case 3: frequency = 480000000; break; } PMC->PMC_PCR = PMC_PCR_PID(ID); // config PMC register read mode for PWM clock mck_div = (PMC->PMC_PCR & 0x00ff00u) >> 8; // read MCK divider -> PWM clock frequency = frequency / mck_div; return(frequency); } }
Generating the desired frequencies
Back to the original problem, calculate the divisors for the PWM clock:
Now that we know the input to the above diagram we can go ahead and do some basic math. To calculate the parameters PREA and DIVA in order to get the desired frequency we have to calculate and factorize the required divider.
divider = peripheral_clock / desired_frequency
split divider in the form 2^prea×diva
Example:
divider = 260
factors of 260: 2^2×5×13
=> prea = 2 and diva = 5×13 = 65
How is factorization done? The answer is astonishingly simple: do a trial division! Since we only need to know the factor which is a power of two this is further simplified to this:
int calculate_PWM_dividers(int frequency, int *div, int *pre) { uint32_t mck_div, f; int n; frequency = abs(frequency); // no negative frequency f = get_periphal_clock_frequency(ID_PWM); *div = f / frequency; // required divisor // split div into pre and div // facorization of divider // pre -> power of 2 // div = all other facors for(n=1; n <= 10; n++) if(*div % ((int) pow(2, n)) != 0) // if not a multiple of 2^n *pre = n - 1; // then it was a multiple of 2^n-1 *div = *div / ((int) pow(2, *pre)); // new divisor is the rest }
Wrap this code nicely in some error handling code and that's it.
Top Comments