2.8 Baud Rate and Clocks

“The time has come,” the walrus said, “to talk of many things: Of baud rates – and clocks – and quartz.”
-- Les huit scaroles --
One thing to consider in any kind of transmission is the speed, how fast or how slowly you can transmit data. I have configured USART1 at 9600 baud, keeping the other settings at default value (8N1), so how fast is that?

A bit of theory

Let’s interpret asynchronous serial transmission, 9600 baud, 8 bits, no parity, 1 stop bit. In my case, 8N1 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.

Baud rate accuracy

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?

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.

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.

Clocks

Looking at the clock tree in the datasheet can be intimidating, it’s definitively about several clocks.

Clock Tree

The default configuration I have been using so far goes like this.

From the peripherals point of view. 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.

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.

Quartz

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.

To set a 24 MHz clock with a 8 MHz High Speed External Oscillator (HSE):

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.

Implementation

I make a copy of uplow.2.c into clocks.c to make the changes.

I expand the board description part by adding HSE, PLL and BAUD macro definitions. Based on those I can handle four clock configurations: HSI, HSE, PLL HSI and PLL HSE.

/* 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
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.
#if CLOCK > 48000000
# error clock frequency exceeds 48MHz
#endif

#if CLOCK % BAUD
# warning baud rate not accurate at that clock frequency
#endif
I expand the definition of the Reset and Clock Control (RCC) peripheral to add the necessary bit fields.
#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 */
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.
/* 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
Systick reload value is calculated based on CLOCK constant value.
    SYSTICK_RVR = CLOCK / 8 - 1 ;   /* HBA / 8 */
Similarly, USART1 baud rate register is calculated based on CLOCK and BAUD constant value.
    USART1[ BRR] = CLOCK / BAUD ;       /* PCLK is default source */
I add a debug print at the end of init() to display which clock configuration has been set.
    kputs(
#ifdef PLL
        "PLL"
#endif
#ifdef HSE
        "HSE"
#else
        "HSI"
#endif
        "\n") ;

Build and test

To build, I first update the composition in Makefile.
SRCS = startup.c clocks.c uptime.c
Build complete successfully, this is for PLL HSE board configuration.
$ 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
I use a board with a 8 MHz quartz soldered on and test the four clock configuration.

Checkpoint

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.

Next, I will implement interrupt driven transmission.


© 2020-2024 Renaud Fivet