mirror of
https://github.com/rfivet/stm32bringup.git
synced 2024-12-20 15:58:44 -05:00
334 lines
10 KiB
HTML
334 lines
10 KiB
HTML
|
<!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 baud</b>, keeping the other settings at default value (<b>8N1</b>),
|
|||
|
so how fast is that?
|
|||
|
|
|||
|
<h2>A bit of theory</h2>
|
|||
|
|
|||
|
Let’s 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, it’s 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 isn’t call oversampling for nothing. Am I
|
|||
|
overdoing something here or just compensating for something I missed?
|
|||
|
<p>
|
|||
|
The thing is, I didn’t 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, it’s
|
|||
|
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.
|
|||
|
It’s 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. The clock tree is complex and I have only looked at a part
|
|||
|
of it. 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>
|