Freitag, 3. Juni 2016

STM32 microcontroller #2: setting the clock speed

Introduction

In my first post about the STM32F103RBT6 device, I was not having a crystal oscillator on the board, and the code did not change any of the clock settings. That means, the controller is running at 8 MHz from an internal oscillator. Some applications need more precise timings, in particular USB communication. My breakout PCB contains a place for a crystal. After putting a 16 MHz quartz in place (along with a USB mini-B), my board looks like this:
The schematic drawing looks like this:

Changing the clock frequency is - again - just a matter of setting/clearing the correct bits in the correct registers in the correct order. I'm used to do this on an ATMEGA8, but with the STM32F103RBT6 there are roughly 4 times more bits to set to achieve something. ST-Microelectronics offers a hardware abstraction layer (HAL) library, that replaces the bit-fiddling with higher level function calls. But I still like to see that things can work from scratch, just to convince myself that there is no hidden magic happening. That's why I will show here how to configure the STM32F103RBT6 to run at 48 MHz from a 16 MHz crystal.

The core frequency can be higher than the primary clock source, because the chip contains a phase-locked-loop (PLL) circuit. I think of a PLL as an oscillator (typically faster than the PLL input) which runs not independently, but has a fixed phase relation with another oscillator (the quartz in this case). In this way, the long term stability of the quartz oscillator is also present on the faster PLL oscillator. So, effectively a PLL multiplies the input frequency.
The registers for the clock and PLL configuration are described on page 133 of the Reference Manual. Here are the steps to get to 48 MHz from a quartz:

  • Configure the quartz as PLL input. The term "high speed external oscillator" (HSE) is used. (It is also possible to use the internal oscillator as PLL input to make the controller run faster and if the high stability of a quartz is no needed.)
  • Activate the HSE oscillator. (it is off by default)
  • Configure the PLL to multiply the input frequency by 6. (severel other multiplication factors are possible)
  • Divide the HSE frequency by 2 before entering the PLL.
  • Activate the PLL circuit.
  • Wait until the PLL is locked, i.e. the phase stability between input and output is established.
  • Set main system clock to PLL output.
With a 16 MHz HSE clock, the resulting frequency should be 16/2 *6 = 48. Alternatively, I could have chosen a PLL multiplication of 3, and not divide the HSE frequency by 2.

Here is the main program that does these steps. After setting the clock, the SystemCoreClock is calculated inside the SystemCoreClockUpdate() function from the system_stm32f1xx.h header file. The system clock is displayed as a binary number on a LED array, which is connected to 10 GPIO pins.

#include "stm32f1xx.h"
#include "system_stm32f1xx.h"
#include <stdint.h>

void wait(uint32_t delta)
{
 volatile uint32_t i;
 for (i = delta; i; --i) ;
}

// function to display up to 10 bit integer value 
//   in binary represenation on LED string
void display(uint16_t n);

// configure GPIO registers 
void configure_GPIO();

int main()
{
 configure_GPIO();
 
 // clock setup 
 // register description on page 133 of reference manual
 
 // we can set the PLL entry clock source because PLL is off 
 //   (this doesn't work if PLL is on)
 // HSE as PLL source if bit is set, otherwise PLL source is HSI
 RCC->CFGR   |= RCC_CFGR_PLLSRC;            
 // activate HSE oscillator
 RCC->CR     |= RCC_CR_HSEON;     
 // PLL multiplies by factor of 6          
 RCC->CFGR   |= RCC_CFGR_PLLMULL6;
 // HSE crystal frequency divided by 2  before entering PLL
 RCC->CFGR   |= RCC_CFGR_PLLXTPRE_HSE_DIV2; 
 
 RCC->CR     |= RCC_CR_PLLON;        // activate PLL
 while (!(RCC_CR_PLLRDY & RCC->CR)); // wait until PLL is locked
 
 
 RCC->CFGR   |= RCC_CFGR_SW_PLL;   // use PLL as system clock
 //RCC->CFGR   |= RCC_CFGR_SW_HSE; // this would use ext. crystal 
                                   // directly (without PLL)
 
 // Use this function from the file system_stm32f1xx.h to
 // compute the system core clock frequency based on 
 // register settings and display the number on the LED string.
 SystemCoreClockUpdate();
 display(SystemCoreClock/1000000);
 
 // blink LED
 for(uint16_t i = 1;;)
 {
  GPIOC->BSRR |= GPIO_BSRR_BS4;  // set pin4 (LED on)
  wait(1000000);
  GPIOC->BRR |= GPIO_BRR_BR4;    // reset pin4 (LED off)
  wait(1000000);
 }
}


The makefile is the same as in post #1, only the CFLAGS have an additional entry, namely

-DHSE_VALUE=16000000UL

This is equivalent of writing #define HSE_VALUE=16000000UL in every source file. It tells the software which quartz frequency is physically present. An 8 MHz or 12 MHz quartz would also work, but the numbers in the clock setup have to be updated accordingly.

After compiling and flashing, the LEDs indicate that the SystemCoreClock/1000000 is 0b0000110000 = 48.


Outlook

For some reason, I could not go to the specified 72 MHz frequency with that method. Probably, I'm missing something. In the next post, I will use the STM32 hardware abstraction layer (HAL) library to do the same thing.

Appendix

For completeness, here are the functions for the GPIO configuration and the LED output:
// function to display up to 10 bit integer value 
//   in binary represenation on LED string
void display(uint16_t n)
{
 if (n & (((uint16_t)1)<<0))  GPIOB->BSRR |= GPIO_BSRR_BS12;
 else                         GPIOB->BRR  |= GPIO_BRR_BR12;

 if (n & (((uint16_t)1)<<1))  GPIOB->BSRR |= GPIO_BSRR_BS13;
 else                         GPIOB->BRR  |= GPIO_BRR_BR13;

 if (n & (((uint16_t)1)<<2))  GPIOB->BSRR |= GPIO_BSRR_BS14;
 else                         GPIOB->BRR  |= GPIO_BRR_BR14;

 if (n & (((uint16_t)1)<<3))  GPIOB->BSRR |= GPIO_BSRR_BS15;
 else                         GPIOB->BRR  |= GPIO_BRR_BR15;

 if (n & (((uint16_t)1)<<4))  GPIOC->BSRR |= GPIO_BSRR_BS6;
 else                         GPIOC->BRR  |= GPIO_BRR_BR6;
                                    
 if (n & (((uint16_t)1)<<5))  GPIOC->BSRR |= GPIO_BSRR_BS7;
 else                         GPIOC->BRR  |= GPIO_BRR_BR7;
                                    
 if (n & (((uint16_t)1)<<6))  GPIOC->BSRR |= GPIO_BSRR_BS8;
 else                         GPIOC->BRR  |= GPIO_BRR_BR8;
                                    
 if (n & (((uint16_t)1)<<7))  GPIOC->BSRR |= GPIO_BSRR_BS9;
 else                         GPIOC->BRR  |= GPIO_BRR_BR9;
                                    
 if (n & (((uint16_t)1)<<8))  GPIOA->BSRR |= GPIO_BSRR_BS8;
 else                         GPIOA->BRR  |= GPIO_BRR_BR8;
                                    
 if (n & (((uint16_t)1)<<9))  GPIOA->BSRR |= GPIO_BSRR_BS9;
 else                         GPIOA->BRR  |= GPIO_BRR_BR9;
}

void configure_GPIO()
{
 // Enbale GPIOC (A,B, and C)
 RCC->APB2ENR   |= RCC_APB2ENR_IOPAEN;
 RCC->APB2ENR   |= RCC_APB2ENR_IOPBEN;
 RCC->APB2ENR   |= RCC_APB2ENR_IOPCEN;
 
 // Configure GPIOC pin 4 by setting bits in the lower half 
 // of the port configuration register CRL
 // (reset value of GPIOC->CRL = 0x44444444)
 GPIOC->CRL |=   GPIO_CRL_MODE4; // MODE4 = 11 (50 MHz = fast mode)
 GPIOC->CRL &= ~(GPIO_CRL_CNF4); // CNF4  = 00 (Push-Pull mode)
 
 GPIOC->CRL |=   GPIO_CRL_MODE6; // same for other GPIOs
 GPIOC->CRL &= ~(GPIO_CRL_CNF6); 
 GPIOC->CRL |=   GPIO_CRL_MODE7; 
 GPIOC->CRL &= ~(GPIO_CRL_CNF7); 
 GPIOC->CRH |=   GPIO_CRH_MODE8; 
 GPIOC->CRH &= ~(GPIO_CRH_CNF8); 
 GPIOC->CRH |=   GPIO_CRH_MODE9; 
 GPIOC->CRH &= ~(GPIO_CRH_CNF9); 
 GPIOB->CRH |=   GPIO_CRH_MODE12; 
 GPIOB->CRH &= ~(GPIO_CRH_CNF12); 
 GPIOB->CRH |=   GPIO_CRH_MODE13; 
 GPIOB->CRH &= ~(GPIO_CRH_CNF13); 
 GPIOB->CRH |=   GPIO_CRH_MODE14; 
 GPIOB->CRH &= ~(GPIO_CRH_CNF14); 
 GPIOB->CRH |=   GPIO_CRH_MODE15; 
 GPIOB->CRH &= ~(GPIO_CRH_CNF15); 
 GPIOA->CRH |=   GPIO_CRH_MODE8; 
 GPIOA->CRH &= ~(GPIO_CRH_CNF8); 
 GPIOA->CRH |=   GPIO_CRH_MODE9; 
 GPIOA->CRH &= ~(GPIO_CRH_CNF9); 
}

Next Part

Next post will show how to use the Hardware abstraction layer from ST-Microelectronics to set the clock speed.

1 Kommentar: