1
0
mirror of https://github.com/rfivet/stm32bringup.git synced 2024-12-20 15:58:44 -05:00
stm32bringup/docs/28_clocks.html

334 lines
10 KiB
HTML
Raw Normal View History

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>2.8 Baud Rate and Clocks</title>
<link type="text/css" rel="stylesheet" href="style.css">
</head>
<body>
<h1>2.8 Baud Rate and Clocks</h1>
<blockquote> “The time has come,” the walrus said, “to talk of many things: Of baud
rates and clocks and quartz.”<br>
-- Les huit scaroles --
</blockquote>
One thing to consider in any kind of transmission is the speed, how fast
or how slowly you can transmit data. I have configured <b>USART1</b> at
<b>9600&nbsp;baud</b>, keeping the other settings at default value (<b>8N1</b>),
so how fast is that?
<h2>A bit of theory</h2>
Lets interpret asynchronous serial transmission, 9600 baud, 8 bits, no
parity, 1 stop bit.
<ul>
<li> Serial transmission means transmission on one wire, with each bit sent
one after the other, usually low bit first.
<li> Asynchronous means there is no extra wire for a clock, so transmitter
and receiver must agree on a bit rate and a transmission pattern.
<li> Transmission pattern, <b>8N1</b> in my case, is composed of a start bit,
a data length (<b>8</b> bits), parity (Even, Odd or <b>N</b>one) and <b>1</b>,
1.5 or 2 stop bits.
<li> Because there is no common transmitted clock, the receiver
resynchronizes based on the start bit/stop bit framing of the data. It
samples the line at 16 times the frequency of the agreed clock to detect
the change in the wire state.
</ul>
In my case, <b>8N1</b> means that, because of the framing pattern, for
every byte of data sent, there is one extra start bit and one extra stop
bit sent, its ten bits per byte of data. At 9600 bauds that means 960
bytes per second, fast enough to transmit every characters of a 80×24
terminal screen in two seconds.
<h2>Baud rate accuracy</h2>
It sounds like a pretty robust transmission scheme, sampling at 16 times
the transmission clock isnt call oversampling for nothing. Am I
overdoing something here or just compensating for something I missed?
<p>
The thing is, I didnt program USART1 to transmit at 9600 baud. As my
default clock is 8MHz, I had to write in USART1 baud rate register a
value close to 8000000/9600 or 2500/3, 833 is close enough but my actual
transmission speed is closer to 9604, slightly faster than 9600 baud.
<p>
The error is small (4/10000) and the transmission works fine. Still
common baud rates are 300, 1200, 2400, 9600, 19200, 38400, 57600, 115200.
It would be better if my clock frequency was 6MHz or 12MHz if I
want to work at higher baud rate.
<h2>Clocks</h2>
Looking at the clock tree in the datasheet can be intimidating, its
definitively about several clocks.
<p>
<img alt="Clock Tree" src="img/28_clocktree.png">
<p>
The default configuration I have been using so far goes like this.
<ul>
<li> HSI is the output of a 8MHz High Speed Internal RC oscillator.
<li> HSI is the default source of SYSCLK.
<li> HCLK, the clock of AHB domain, is SYSCLK divided by HPRE a pre-scaler
which default to 1.
<li> The system tick clock default configuration is HCLK/8.
<li> PCLK, the clock of APB domain, is HCLK divided by PPRE a pre-scaler
which default to 1.
<li> GPIO peripherals are on APB domain bus, they use PCLK.
<li> USART1 is also on APB domain bus, but its input clock can be selected,
PCLK is the default input.
</ul>
From the peripherals point of view.
<ul>
<li> SysTick Clock = HCLK/8 = SYSCLK/1/8 = HSI/1/8 = 8/1/8 MHz
<li> GPIOx Clock = PCLK = HCLK/1 = SYSCLK/1/1 = HSI/1/1 = 8/1/1 MHz
<li> USART1 Clock = PCLK = … = 8 MHz
</ul>
As I want to have a clock frequency different than 8 MHz as input for
USART1, I can configure the Phase-Locked Loop (PLL) and switch SYSCLK to
take its input from the PLL instead of HSI.
<p>
The PLL output frequency must be in the range 16-48 MHz. As I am looking
for a frequency that can be divided by 3 to match most of the baud rate,
I will use 24 MHz.
<ul>
<li> Select PLL input as HSI/2.
<li> Set PLLMUL to 6.
<li> Enable PLL and wait that it stabilizes.
<li> Select SYSCLK input as PLL.
<li> Wait for the switch to complete.
</ul>
<h2>Quartz</h2>
I can also activate the quartz if there is one soldered on the board.
Its usually the case but specially for STM32F030F4 which has only 20
pins, a quartz less design that free up two GPIO pins can be a day
saver. Quartz value from 4 to 32 MHz are supported and most design use 8
MHz.
<p>
To set a 24 MHz clock with a 8 MHz High Speed External Oscillator (HSE):
<ul>
<li> Enable HSE and wait that it stabilizes.
<li> Select HSE as input for the PLL with a pre divider of 2.
<li> Set PLLMUL to 6.
<li> Enable PLL and wait that it stabilizes.
<li> Select SYSCLK input as PLL.
<li> Wait for the switch to complete.
<li> Disable HSI.
</ul>
I can use different values for the pre divider and post multiplier of
the PLL (/4, *12 or /1, *3 instead of /2, *6) but I want here to stay
aligned with the HSI/2 input selection when HSE quartz value is 8MHz.
<h2>Implementation</h2>
I make a copy of <b>uplow.2.c</b> into <b>clocks.c</b> to make the changes.
<p>
I expand the board description part by adding <code>HSE</code>,
<code>PLL</code> and <code>BAUD</code> macro definitions. Based on those I can
handle four clock configurations: HSI, HSE, PLL HSI and PLL HSE.
<pre>
/* user LED ON when PA4 is high */
#define LED_IOP A
#define LED_PIN 4
#define LED_ON 1
/* 8MHz quartz, configure PLL at 24MHz */
#define HSE 8000000
#define PLL 6
#define BAUD 9600
#ifdef PLL
# ifdef HSE
# define CLOCK HSE / 2 * PLL
# else /* HSI */
# define CLOCK 8000000 / 2 * PLL
# endif
# if CLOCK < 16000000
# error PLL output below 16MHz
# endif
#else
# ifdef HSE
# define CLOCK HSE
# else /* HSI */
# define CLOCK 8000000
# endif
#endif
</pre>
At compilation time there will be a check if the clock targeted is in
the supported range of the chipset and a warning given if the baud rate
generation is not accurate.
<pre>
#if CLOCK > 48000000
# error clock frequency exceeds 48MHz
#endif
#if CLOCK % BAUD
# warning baud rate not accurate at that clock frequency
#endif
</pre>
I expand the definition of the Reset and Clock Control (RCC) peripheral
to add the necessary bit fields.
<pre>
#define CAT( a, b) a##b
#define HEXA( a) CAT( 0x, a)
#define RCC ((volatile long *) 0x40021000)
#define RCC_CR RCC[ 0]
#define RCC_CR_HSION 0x00000001 /* 1: Internal High Speed clock enable */
#define RCC_CR_HSEON 0x00010000 /* 16: External High Speed clock enable */
#define RCC_CR_HSERDY 0x00020000 /* 17: External High Speed clock ready flag$
#define RCC_CR_PLLON 0x01000000 /* 24: PLL enable */
#define RCC_CR_PLLRDY 0x02000000 /* 25: PLL clock ready flag */
#define RCC_CFGR RCC[ 1]
#define RCC_CFGR_SW_MSK 0x00000003 /* 1-0: System clock SWitch Mask */
#define RCC_CFGR_SW_HSE 0x00000001 /* 1-0: Switch to HSE as system clock */
#define RCC_CFGR_SW_PLL 0x00000002 /* 1-0: Switch to PLL as system clock */
#define RCC_CFGR_SWS_MSK 0x0000000C /* 3-2: System clock SWitch Status Mask$
#define RCC_CFGR_SWS_HSE 0x00000004 /* 3-2: HSE used as system clock */
#define RCC_CFGR_SWS_PLL 0x00000008 /* 3-2: PLL used as system clock */
#define RCC_CFGR_PLLSRC 0x00010000
#define RCC_CFGR_PLLSRC_HSI 0x00000000 /* HSI / 2 */
#define RCC_CFGR_PLLSRC_HSE 0x00010000 /* HSE */
#define RCC_CFGR_PLLXTPRE 0x00020000
#define RCC_CFGR_PLLXTPRE_DIV1 0x00000000 /* HSE */
#define RCC_CFGR_PLLXTPRE_DIV2 0x00020000 /* HSE / 2 */
#define RCC_CFGR_PLLMUL_MSK (0x00F << 18)
#define RCC_CFGR_PLLMUL( v) ((v - 2) << 18)
#define RCC_AHBENR RCC[ 5]
#define RCC_AHBENR_IOP( h) (1 << (17 + HEXA( h) - 0xA))
#define RCC_APB2ENR RCC[ 6]
#define RCC_APB2ENR_USART1EN 0x00004000 /* 14: USART1 clock enable */
</pre>
The code to configure the clocks follow the steps I have described
before. The conditional compilation allows the generation of the four
possible cases: HSI, HSE, PLL HSI and PLL HSE.
<pre>
/* By default SYSCLK == HSI [8MHZ] */
#ifdef HSE
/* Start HSE clock (8 MHz external oscillator) */
RCC_CR |= RCC_CR_HSEON ;
/* Wait for oscillator to stabilize */
do {} while( (RCC_CR & RCC_CR_HSERDY) == 0) ;
#endif
#ifdef PLL
/* Setup PLL HSx/2 * 6 [24MHz] */
/* Default 0: PLL HSI/2 src, PLL MULL * 2 */
# ifdef HSE
RCC_CFGR = RCC_CFGR_PLLSRC_HSE | RCC_CFGR_PLLXTPRE_DIV2 ;
# endif
RCC_CFGR |= RCC_CFGR_PLLMUL( PLL) ;
RCC_CR |= RCC_CR_PLLON ;
do {} while( (RCC_CR & RCC_CR_PLLRDY) == 0) ; /* Wait for PLL */
/* Switch to PLL as system clock SYSCLK == PLL [24MHz] */
RCC_CFGR = (RCC_CFGR & ~RCC_CFGR_SW_MSK) | RCC_CFGR_SW_PLL ;
do {} while( (RCC_CFGR & RCC_CFGR_SWS_MSK) != RCC_CFGR_SWS_PLL) ;
#else
# ifdef HSE
/* Switch to HSE as system clock SYSCLK == HSE [8MHz] */
RCC_CFGR = (RCC_CFGR & ~RCC_CFGR_SW_MSK) | RCC_CFGR_SW_HSE ;
do {} while( (RCC_CFGR & RCC_CFGR_SWS_MSK) != RCC_CFGR_SWS_HSE) ;
# endif
#endif
#ifdef HSE
/* Switch off HSI */
RCC_CR &= ~RCC_CR_HSION ;
#endif
</pre>
Systick reload value is calculated based on <code>CLOCK</code> constant value.
<pre>
SYSTICK_RVR = CLOCK / 8 - 1 ; /* HBA / 8 */
</pre>
Similarly, USART1 baud rate register is calculated based on <code>CLOCK</code>
and <code>BAUD</code> constant value.
<pre>
USART1[ BRR] = CLOCK / BAUD ; /* PCLK is default source */
</pre>
I add a debug print at the end of <code>init()</code> to display which clock
configuration has been set.
<pre>
kputs(
#ifdef PLL
"PLL"
#endif
#ifdef HSE
"HSE"
#else
"HSI"
#endif
"\n") ;
</pre>
<h2>Build and test</h2>
To build, I first update the composition in Makefile.
<pre>SRCS = startup.c clocks.c uptime.c</pre>
Build complete successfully, this is for PLL HSE board configuration.
<pre>
$ make
f030f4.elf from startup.o clocks.o uptime.o
text data bss dec hex filename
1901 0 12 1913 779 f030f4.elf
f030f4.hex
f030f4.bin
</pre>
I use a board with a 8 MHz quartz soldered on and test the four clock
configuration.
<h2>Checkpoint</h2>
I have tuned the baud rate setting by using a higher frequency for the
system clock.&nbsp; The clock tree is complex and I have only looked at a part
of it.&nbsp; Nevertheless the implementation for the clock configuration give
me some flexibility and ease of setup.
<p>
<a href="29_interrupt.html">Next</a>, I will implement interrupt driven
transmission.
<hr>© 2020-2024 Renaud Fivet
</body>
</html>