Blog on embedded programming

The trip to the dark side

Clock Control on Stm32f4discovery

Hello everyone! This post is dedicated to learning clock configuration of our stm32f4. By default MC is driven by the internal oscillator (HSI) which generates 16 MHz signal. The Internal oscillator frequency stability isn’t good enough for real world applications. There is a 8 MHz crystal on board of stm32f4discovery. The crystal is needed for another one oscillator called HSE. This oscillator has better than HSI frequency stability characteristics. So we need to know how to change the source of a system clock-signal of the MC.

Just switching to the HSE oscillator will set the frequency of clock-signal to 8 MHz. This value is low for being the “heartbeat” of a modern device. Here comes the PLL peripheral which’s aim is to multiply signal’s frequency by some factor. Using PLL makes it possible to reach the clock-signal frequency up to 168 MHz.

I’ve played with it for a while… And here is the story. There were no problems while selecting HSE as the source. Configuring and enabling PLL is also simple. But some strange crazy things started happening after I selected PLL as the source for a system clock. The most unpleasant ones were random fails of burning into the flash or burning into the wrong address of flash…

I took one of the example sources from ST library then and there I saw much preparatory stuff before speeding up the device - including some flash configuration. Fairly speaking, dealing with IDE isolates a programmer from such an experience - all the preparation is done behind the scenes before the main routine is called and everything is working fine. But such experience of investigating some problem is a real charm. That’s why avoiding problems should not be our way.

So, in this post we won’t jump so high (or low ?? :) ). The purpose is to switch to the HSE oscillator.

The code

The program is a simple one-led-blinking program, here is the source code:

(changing_clock.S) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
    .thumb
    .syntax     unified
    .cpu        cortex-m4
#define RCC_BASE            0x40023800
#define RCC_CR              RCC_BASE
#define RCC_CFGR            RCC_BASE + 0x08
#define RCC_PLLCFGR         RCC_BASE + 0x04
#define RCC_AHB1ENR         RCC_BASE + 0x30
#define RCC_CIR             RCC_BASE + 0x0C
#define GPIOD_BASE          0x40020C00
#define GPIOD_MODER         GPIOD_BASE
#define GPIOD_ODR           GPIOD_BASE + 0x14

#define GPIODEN             1 << 3
#define MODER12_OUT         1 << 24
#define LED_GREEN           1 << 12
#define PLLON               1 << 24
#define HSION               1
#define HSEBYP              1 << 18
#define HSEON               1 << 16
#define HSERDY              1 << 17
#define CSSON               1 << 19
#define SW0                 1
#define SWS0                1 << 2             

#define DELAY               0x5F
#define HSE_STARTUP_TIMEOUT 0x1

#define RCC_CFGR_SWS        3 << 2

.section        .text
    .type       _reset, %function
_reset:
    bl          .Lstartup
    bl          .Linit_clock
    ldr         r0, =RCC_AHB1ENR        /* this register controls some amount of peripherals' clock */
    ldr         r1, [r0]
    orr         r1, GPIODEN             /* enabling GPIOD clock */
    str         r1, [r0]
    ldr         r0, =GPIOD_MODER        /* GPIOD mode configuration register */
    ldr         r1, =MODER12_OUT        /* set 12 pin of GPIOD as output */
    str         r1, [r0]
    ldr         r0, =GPIOD_ODR          /* port output register */
    eor         r1, r1                  /* elegant way to clean register */
    eor         r2 ,r2
.Lblink:
    mov         r1, LED_GREEN
    str         r1, [r0]                /* setting LED_GREEN bit in GPIOD_ODR */
    bl          .Ldelay                 /* pause */
    eor         r1, r1
    str         r1, [r0]                /* clearing GPIOD_ODR */
    bl          .Ldelay                 /* pause */
    b           .Lblink                 /* and so on */

.Ldelay:
    movt        r2, DELAY
1:
    subs        r2, r2, 1               /* spend time substracting DELAY value by 1 */
    bne         1b                      /* '1b' means `look backward for label '1'` */

    bx          lr

.Lstartup:
    ldr         r0, =RCC_CR             /* resetting clock configuration to it's defaults */
    ldr         r1, [r0]                /* clearing HSEON, CSSON, PLLON bits - directly disabling */
                                        /* HSE oscillator, clock security system, main PLL */
    orr         r1, HSION               /* setting HSION bit - directly selecting HSI oscillator*/
    ldr         r2, =(HSEON) | (CSSON) | (PLLON)
    bic         r1, r2
    str         r1, [r0]
    ldr         r0, =RCC_CFGR           /* clearing RCC_CFGR - clock configuration register */
    movw        r1, 0
    str         r1, [r0]
    ldr         r0, =RCC_CIR            /* disabling all interrupts */
    str         r1, [r0]
    ldr         r0, =RCC_PLLCFGR        /* setting the default value from datasheet */
    ldr         r1, =0x24003010         /* for PLL configuration register */
    str         r1, [r0]
    bx          lr

.Linit_clock:
    ldr         r0, =RCC_CR             /* enabling HSE clock */
    ldr         r1, [r0]
    bic         r1, HSEBYP              /* clearing HSEBYP bit - not bypassing HSE oscillator*/
    orr         r1, HSEON
    str         r1, [r0]
1:
    ldr         r1, [r0]                /* waiting for HSE to be ready */
    ands        r1, HSERDY
    beq         1b
    ldr         r0, =RCC_CFGR           /* selecting HSE as the source */
    movw        r1, SW0
    str         r1, [r0]
    eor         r2, r2
    movt        r2, HSE_STARTUP_TIMEOUT
1:
    ldr         r1, [r0]                /* waiting for source being selected */
    subs        r2, 1                   /* counting some HSE_STARTUP_TIMEOUT value */
    beq         2f                      /* if HSE doesn't start - remain with HSI */
    and         r1, RCC_CFGR_SWS
    cmp         r1, SWS0
    bne         1b
2:
    bx          lr
    .size       _reset, . - _reset


.section        .int_vector_table, "a", %progbits
    .type       basic_vectors, %object
basic_vectors:
    .word       _estack
    .word       _reset

    .size       basic_vectors, . - basic_vectors

Routines called delay and blink are similar to those in assembly hello world code for stm32f4discovery post. This code introduces two new routines : startup and init_clock.

Startup initializes default clock configuration. Actually it’s not neccesary but it is a good practice and should be done. For example: it is said that main PLLON bit reset value is 0 in stm32f4xx user manual, but on my board it is seen with the debugger that the PLLON bit is “1” after reset. So, the conclusion which is not new in a programming world is: not to rely on something not initialized explicitly. The startup just selects HSI as the clock source; disables HSE, PLL, clock security; resets PLL configuration to the datasheet value; disables interrupts.

The clock_init routine enables HSE, selects it as the system clock’s source and waits for it to be ready, but if some error occurs - it will continue with HSI clock after a little delay. Note: this is just an example and it is not neccessary to do exactly the same thing whenever HSE is selected.

To detect the difference you may test the code without the following line:

1
bl      .Linit_clock

HSI clock will be selected if init_clock is not called and the frequency will be twice higher than in the case init_clock is called. So the blinking will be also twice faster.

Compiling the code remains yet the same as in previous post. Also here is the result binary for download

Feel free to write letters for conversations.