Clock configuration

The configuration of the clocks in Cortex-M microcontrollers happens through the Reset and Clock Control (RCC) registers, located at a specific address within the internal peripheral region. The RCC configuration is vendor-specific, as it depends on the logic of the PLL implemented in the microcontroller. The registers are described in the documentation of the microcontroller, and often, example source code is provided by the chip manufacturer to demonstrate how to properly configure the clocks on the microcontroller. On our reference target, STM32F407, assuming that an external 8 MHz oscillator is used as a source, the following procedure configures a 168 MHz system clock, and ensures that the clock is also distributed to each peripheral bus. The following code ensures that the PLL is initialized with the required value, and that the CPU clock is ticking at the desired frequency. This procedure is common among many STM Cortex-M microcontrollers, and the values for the PLL configurations can be obtained from the chip documentation, or calculated using software tools provided by ST.

The software examples provided after this point will make use of a system-specific module, exporting the functions needed to configure the clock and set the flash memory latency. We now analyze two possible implementations for the PLL configuration, on two different Cortex-M microcontrollers.

To access the configuration of the PLL in the STM32F407-Discovery, first we define some shortcut macros to the addresses of the registers provided by the RCC:

#define RCC_BASE (0x40023800)
#define RCC_CR (*(volatile uint32_t *)(RCC_BASE + 0x00))
#define RCC_PLLCFGR (*(volatile uint32_t *)(RCC_BASE + 0x04))
#define RCC_CFGR (*(volatile uint32_t *)(RCC_BASE + 0x08))
#define RCC_CR (*(volatile uint32_t *)(RCC_BASE + 0x00))

For the sake of readability, and to ensure that the code is maintainable in the future, we also define the mnemonics associated to the single-bit values in the corresponding registers:

#define RCC_CR_PLLRDY (1 << 25)
#define RCC_CR_PLLON (1 << 24)
#define RCC_CR_HSERDY (1 << 17)
#define RCC_CR_HSEON (1 << 16)
#define RCC_CR_HSIRDY (1 << 1)
#define RCC_CR_HSION (1 << 0)

#define RCC_CFGR_SW_HSI 0x0
#define RCC_CFGR_SW_HSE 0x1
#define RCC_CFGR_SW_PLL 0x2
#define RCC_PLLCFGR_PLLSRC (1 << 22)

#define RCC_PRESCALER_DIV_NONE 0
#define RCC_PRESCALER_DIV_2 8
#define RCC_PRESCALER_DIV_4 9

Finally, we define the platform-specific constant values used to configure the PLL:

#define CPU_FREQ (168000000)
#define PLL_FULL_MASK (0x7F037FFF)
#define PLLM 8
#define PLLN 336
#define PLLP 2
#define PLLQ 7
#define PLLR 0

One additional macro invoking the DMB assembly instruction is defined, for brevity, as it will be used in the code to ensure that any pending memory transfer towards the configuration registers is completed before the execution of the next statement:

#define DMB() asm volatile ("dmb");

The next function will then ensure that the PLL initialization sequence is performed, in order to set the correct CPU frequency. First, it will enable the internal high-speed oscillator, and will wait until it is ready by polling the CR:

void rcc_config(void)
{
uint32_t reg32;
RCC_CR |= RCC_CR_HSION;
DMB();
while ((RCC_CR & RCC_CR_HSIRDY) == 0)
;

The internal oscillator is then selected as a temporary clock source:

    reg32 = RCC_CFGR;
reg32 &= ~((1 << 1) | (1 << 0));
RCC_CFGR = (reg32 | RCC_CFGR_SW_HSI);
DMB();

The external oscillator is then activated in the same way:

    RCC_CR |= RCC_CR_HSEON;
DMB();
while ((RCC_CR & RCC_CR_HSERDY) == 0)
;

On this device, the clock can be distributed to all the peripherals through three system buses. Using prescalers, the frequency of each bus can be scaled by a factor of two or four. In this case, we set the clock speed for HPRE, PPRE1, and PPRE2 to be 168, 84 and 46 MHz respectively on this target:

    reg32 = RCC_CFGR;
reg32 &= ~0xF0;
RCC_CFGR = (reg32 | (RCC_PRESCALER_DIV_NONE << 4));
DMB();
reg32 = RCC_CFGR;
reg32 &= ~0x1C00;
RCC_CFGR = (reg32 | (RCC_PRESCALER_DIV_2 << 10));
DMB();
reg32 = RCC_CFGR;
reg32 &= ~0x07 << 13;
RCC_CFGR = (reg32 | (RCC_PRESCALER_DIV_4 << 13));
DMB();

The PLL configuration register is set to contain the parameters to correctly scale the external oscillator frequency to the desired value:

    reg32 = RCC_PLLCFGR;
reg32 &= ~PLL_FULL_MASK;
RCC_PLLCFGR = reg32 | RCC_PLLCFGR_PLLSRC | PLLM |
(PLLN << 6) | (((PLLP >> 1) - 1) << 16) |
(PLLQ << 24);
DMB();

The PLL is then activated, and the execution is suspended until the output is stable:

    RCC_CR |= RCC_CR_PLLON;
DMB();
while ((RCC_CR & RCC_CR_PLLRDY) == 0);

The PLL is selected as final source for the system clock:

    reg32 = RCC_CFGR;
reg32 &= ~((1 << 1) | (1 << 0));
RCC_CFGR = (reg32 | RCC_CFGR_SW_PLL);
DMB();
while ((RCC_CFGR & ((1 << 1) | (1 << 0))) != RCC_CFGR_SW_PLL);

The internal oscillator is no longer in use, and can be disabled. The control returns to the caller, and all the clocks are successfully set.

As mentioned earlier, the procedure for the clock initialization is strictly dependent on the PLL configuration in the microcontroller. To properly initialize the system clocks required for the CPU and the peripherals to operate at the desired frequencies, it is always advised to refer to the datasheet of the microcontroller provided by the silicon manufacturer. As a second example, we can verify how QEMU is capable of emulating the behavior of the LM3S6965 microcontroller. The emulator provides a virtual clock, which is configurable using the same initialization procedure as described on the manufacturer datasheet. On this platform, two registers are used for clock configuration, referred to as RCC and RCC2:

#define RCC     (*(volatile uint32_t*))(0x400FE060)
#define RCC2 (*(volatile uint32_t*))(0x400FE070)

To reset the RCC to a known state, the reset value must be written to these registers at boot:

#define RCC_RESET  (0x078E3AD1)
#define RCC2_RESET (0x07802810)

This microcontroller uses a raw interrupt to notify that the PLL is locked to the requested frequency. The interrupt status can be checked by reading the bit 6 in the Raw Interrupt Status Register:

#define RIS (*(volatile uint32_t*))(0x400FE050)
#define PLL_LRIS (1 << 6)

The clock configuration routine in this case starts by resetting the RCC registers, and setting the appropriate values to configure the PLL. The PLL is configured to generate a 400 MHz clock from an 8 MHz oscillator source:

void rcc_config(void)
{
RCC = RCC_RESET;
RCC2 = RCC2_RESET;
DMB();
RCC = RCC_SYSDIV_50MHZ | RCC_PWMDIV_64 |
RCC_XTAL_8MHZ_400MHZ | RCC_USEPWMDIV;

The resultant 50 MHz CPU frequency is derived from this master 400 MHz clock using the system divider. The clock is pre-divided by two, and then a factor of 4 is applied:

    RCC2 = RCC2_SYSDIV2_4;
DMB();

The external oscillators are powered on:

    RCC &= ~RCC_OFF;
RCC2 &= ~RCC2_OFF;

And, the system clock divider as well. As long as the bypass bit is set, the oscillator is used as a source for the system clock, and the PLL is bypassed:

    RCC |= RCC_BYPASS | RCC_USESYSDIV;
DMB();

The execution is held until the PLL is stable and has locked on the desired frequency:

    while ((RIS & PLL_LRIS) == 0);

Disabling the bypass bits in the RCC registers at this point is sufficient to connect the PLL output to the system clock:

    RCC &= ~RCC_BYPASS;
RCC2 &= ~RCC2_BYPASS;
}
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.118.140.108