Skip to main content

Changing the Clock Frequency STM32F1 via Registers

For the most part a lot of us tend to run the microcontroller at it's max clock frequency because what is the point of having a 100 MHz microcontroller only to run it at 1 MHz? Well the most obvious answer is POWER! But there is also the less often considered concept of stray capacitance and inductance which increases in high speed circuits. But then again the high speed signals are all inside your microcontroller, right? Well yes but your microcontroller is not immune to the above phenomena. However that could be a post all of its own and I might get around to doing that.
But enough chatter if you are here it is because you want to know how to change the System Clock of your precious blue pill like a real champ and not by clicking buttons in CUBEMX. I will however use CUBEMX for visual representation of what is going on because it helps visualize the clock tree a bit less daunting than the RM. 

First I want to list the steps required to accomplish the task at hand as well as show the registers and bits that are relevant to each task. 

  • Turn on the HSE (High Speed External)
  • Activate prefetch buffer
  • Change wait states/Latency
  • Configure RCC and PLL settings while the PLL is off
  • Turn on PLL
  • Change clock source
Some of these steps require that we wait for the specific setting to take effect. It is also a good idea to implement something to handle an error on any of these steps. I will only implement during the first task of enabling the HSE.

Enabling the HSE

The high speed external clock source can be anything from a crystal to a MEMs oscillator and even the MCO (main clock output) of another microcontroller, which is a technique used in Discovery and Nucleo boards. 
To enable the HSE which we assume is  properly routed to the correct pins etc.. we simply enable the corresponding bit in the RCC register found in the reference manual of the F1. However it is very important that we wait for the HSE to stabilize, we do this by polling the bit next to it HSERDY (HSE Ready) We can sit in a while loop until it is ready or use a timeout counter. The point is that if it does not become ready we cannot move on and must handle that situation accordingly in our code.

The code for this looks like so: 
 //turn on external crystal

 //wait for HSE crystal be stable
 while(!(RCC->CR & RCC_CR_HSERDY))

Activate Prefetch Buffer and set Latency

This next step (prefetch) is not entirely necessary but if you are running that bad-boy at full speed this will definitely make a difference in instruction execution.
According to the Programming manual for the STM32F1: 
"The implementation of this prefetch buffer makes a faster CPU execution possible as the
CPU fetches one word at a time with the next word readily available in the prefetch buffer.
This implies that the acceleration ratio will be of the order of 2 assuming that the code is
aligned at a 64-bit boundary for the jumps. "

As stated above we will need to dig into the programming manual to find the necessary bits for the next 2 steps.

A 1 in the bit 4 position will activate the prefetch buffer which ideally will be set anyways since the reset value of this register is 0x30 ( 0011-0000). The prefetch also has a status bit next to it that you can check to see if it is enabled, I did not do so in my code but that does not mean that you should not.
The other highlighted bits set the latency required for proper flash operations, because remember that flash or any external memory can only be read and written so fast.. and our microcontroller is usually faster than that. The available settings for those bits can bee seen below. In my example I am going to set the clock to 28 MHz so I will use one wait state.
The code for this looks like so:
 //activate prefetch buffer but it should already be on

 // Flash 1 wait state 
 FLASH->ACR &= ~FLASH_ACR_LATENCY); //reset just to be sure
 FLASH->ACR |= 0x1;    

Configure RCC and PLL Settings

This section is now the heart of changing our clock frequency. I will use the image below to help visualize what we are doing. Below is a screenshot of CUBEMX clock tree,  I have configured it to be 28MHz and highlighted some areas that will become relevant when we edit the specific bit in the registers, so I will reference this image the the numbered highlights.
Number 1 in the image above was what we did in the first step by enabling the HSE. The second thing we did with the Latency and Prefetch is not acknowledged in the clock tree. Lets move on to configuring the PLL.
Here is the CFGR register which helps us accomplish all the settings we need.

First thing I do is reset the PLLSRC (bit 16) and PLLXTPRE (bit 17) and PLLMULL (bits 18-21) just in case there are some other values there, I want to set it to a know value of 0x00. I will explain what those bits are in the coming sentences.
Looking at our Clock tree diagram you see Number 2 has a value of "/1" this divided by one, in other words no real division, means our HSE of 8MHz will not get divided and the PLL will see all 8 Mhz.
In the STM32F1 this value can be "/1" or "/2" and it is controlled by the PLLXTPRE bit. Below is the explanation of that bit which is a bit confusing in the RM
Basically it says this PLLXTPRE bit is the same as bit 0 in the CFGR2 register, (but in the STM32F1 there is no CFGR2 register) then it says if the bits in CFGR2 are not set (which they are not because we dont have such a register) then this bit controls PREDIV1 (STEP2  in clock Tree)
If this PLLXTPRE bit is set to 1 then the HSE is divided by 2 ("/2"). and if this PLLXTPRE bit is set to 0 then HSE is not divided("/1") . DO NOT CONFUSE BIT SET TO 1 MEANS "/1"  anything divided by 1 means no division so that would be PLLXPRESET to 0

Next we set the PLL source which is Number 3 clock tree image. And it is bit 16 in the register. This tells the PLL multiplexer we are using the HSE passing through PREDIVE1 (which we configured above)  as the source to the PLL

Next we set the PLL multiplier to 7 which is Number 4 in the clock tree and bits 18-21 in the register.

Then we set the AHB prescaler which is Number 6 in the clock tree and bits 0 to  3 in the register, this will be Divide by 2.

Then we set the APB1 prescaler which is Number 7 in the clock tree and bits 8 to 10 in the register. This will be divide by 1 or "no division"

and then we set the APB2 prescaler which is Number 8 in the clock tree and bits 11 to 13 in the register. This will also be divide by one or no division.

The code looks like so:
 //configure RCC and PLL settings while PLL is off
 RCC->CFGR &= ~(RCC_CFGR_PLLXTPRE);  //PLLXTPRE bit set to 0
 RCC->CFGR |= RCC_CFGR_PLLSRC;  //pll source
 RCC->CFGR |= RCC_CFGR_PLLMULL7;  //pll miultiplier 
 RCC->CFGR |= RCC_CFGR_HPRE_DIV2; //AHB prescaler
 RCC->CFGR |= RCC_CFGR_PPRE1_DIV1; //APB1 presacaler 
 RCC->CFGR |= RCC_CFGR_PPRE2_DIV1; //APB2 prescaler

Once that is done we have to turn on the PLL which is done in the CR register of the RCC , which is the first register image posted above. This is done using bit 24, and we MUST wait for the PLL to be ready before using it by polling ther PLLRDY bit (PLL ready)
 //turn on PLL
 while (!(RCC->CR & RCC_CR_PLLRDY)) 

Once the PLL is on we can select it as a source to the System Clock Multiplexer  which is Number 5 in the clock tree image and bits 0 to 1 in the CFGR register. and we also must wait for that to take effect by polling the SWS bits in the register..
 //set pll as clock source
 while (!(RCC->CFGR & RCC_CFGR_SWS_PLL)) 

Now the HCLK is set to 28MHz and SYSCLK is 56 MHz , all of our peripherals run off the HCLK as seen in the clock tree. One last thing we must to is let the CMSIS core functions know that we have changed the clock because some of them depends on an accurate value for the variable called  SystemCoreClock  We can do this by calling a simple functions that looks at all the registers we just configured and it figures out the clock speed.

This function must be called before you call the SysTick_Config function.. In fact if you are changing the clock freq this should be the first thing you do followed by the SystemCoreClockUpdate function. Because all your other things like UART baudrate and SPI I2C clocks all depend on you calculating the baud rate based on a correct number.

Putting it all together for you guys that love to copy and paste:
NOTE: this is just the raw code to get the job done with no error checking, what happens when the HSE is never ready? you'll be stuck in that while loop. So make sure you make your code robust in that respect.

void makeItGoFast(void)

 //turn on external crystal

 //wait for HSE crystal be stable
        //there are better ways to handle this but ill use simple polling
 while(!(RCC->CR & RCC_CR_HSERDY))
 //activate prefetch buffer but it should already be on

 // Flash 2 wait state 
 FLASH->ACR &= ~(FLASH_ACR_LATENCY);  //reset just to be sure
 FLASH->ACR |= (uint32_t)0x1;    

 //configure RCC and PLL settings while PLL is off
 RCC->CFGR &= ~(RCC_CFGR_PLLXTPRE);   //PLLXTPRE bit set to 0
 RCC->CFGR |= RCC_CFGR_PLLSRC;   //pll source
 RCC->CFGR |= RCC_CFGR_PLLMULL7;  //pll miultiplier 
 RCC->CFGR |= RCC_CFGR_HPRE_DIV2;  //AHB prescaler
 RCC->CFGR |= RCC_CFGR_PPRE1_DIV1;  //APB1 presacaler 
 RCC->CFGR |= RCC_CFGR_PPRE2_DIV1;  //APB2 prescaler
 //turn on PLL
 while (!(RCC->CR & RCC_CR_PLLRDY)) ;
 //set pll as clock source
 RCC->CFGR &= ~(RCC_CFGR_SW);  //reset just in case 
 while (!(RCC->CFGR & RCC_CFGR_SWS_PLL)) ;



Popular posts from this blog

Interface a Rotary Encoder the right way.

So whilst reading my favorite odyssey found here I stumbled onto something I never spotted before. It turns out that the general purpose timers support hardware interfacing with an incremental encoder. This means no more interrupts and no need to increment whatever variable you had. Now all you have to do is get the value from the Count register in the timer. Did I also mention it takes care of figuring out which direction you are turning it? Amazing! Lets talk about it.

Encoder Interface Mode Verbatim from the reference manual of the STM32F103 page 392 or Section 15.3.12  "Encoder interface mode acts simply as an external clock with direction selection. This 
means that the counter just counts continuously between 0 and the auto-reload value in the 
TIMx_ARR register (0 to ARR or ARR down to 0 depending on the direction). So the user 
must configure TIMx_ARR before starting. In the same way, the capture, compare, 
prescaler, trigger output features continue to work as normal."

STM32L0/F0/F3 I2C : Part 3 RX TX via DMA

Most modern microcontroller come with a peripheral called DMA which allows for an even more hands-off approach. The Direct Memory Access controller will get a tutorial of it's own in the future. However, it is so simple to use that I can easily explain the required bits in this tutorial without feeling like I will overwhelm the reader. In this iteration of the I2C series I will cover how to TX and RX date on the peripheral in conjunction with the DMA controller. 

First let me briefly explain in a high level what the DMA controller does. It allows you to transfer data in 3 ways: Peripheral to memoryMemory to peripheralMemory to memory. What does this mean? For example, when you receive data on I2C in your RXDR register, you can have the DMA controller move that data received into an array. At the end of the day your variables are just memory locations, so if you have declared an array it has an address associated with it, this is address is what the DMA considers  "Memory"…

STM32 I2C Does it suck?

Recently I have been annoyed with  bugs in the I2C implementation in ST's F1/L1/F40/F2 series, and  any microcontroller made by ST before 2012. There appears to be, what I can only describe as, a race condition when attempting to receive data in I2C interrupt(low priority)  or polling mode.

Just to be clear a race condition is defined as , verbatim from wiki: "the behavior of an electronics, software, or other system where the system's substantive behavior is dependent on the sequence or timing of other uncontrollable events. It becomes a bug when one or more of the possible behaviors is undesirable."
I was humming along trying to make my YouTube tutorials when I arrived to the I2C protocol implementation. I have used I2C with the F1 series plenty of times, however I have never had the need to receive data from a slave. However for the sake of completeness I decided to implement an RXing routine in I2C for my tutorial. Only to find statements like this in the referen…