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:
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.
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); }
Thank you so much for the Tutorial and the detailed explaination.
AntwortenLöschen