8

In a minimal STM32 application I've written that writes characters to USART1, the USART doesn't seem to work when I try to enable all the clocks I need at once:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA
                       | RCC_APB2Periph_AFIO
                       | RCC_APB2Periph_USART1, ENABLE);

But when I enable the clocks one at a time, it works:

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,  ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,   ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_USART1, ENABLE);

Why is this? Is there a specific order these clocks have to be enabled in? (If so, where is this documented?)

(I've left out all the code following this that initializes the GPIO pins, sets up the USART, and starts sending content, as it's the same in each application. If it's relevant, let me know and I'll include it.)

The device I'm using is the STM32F103VET6.


Since there's some interest in the assembly involved, here it is. For all three clocks at once:

00000000 <main>:
   0:   b590            push    {r4, r7, lr}
   2:   b089            sub     sp, #36 ; 0x24
   4:   af00            add     r7, sp, #0
   6:   f244 0014       movw    r0, #16389      ; 0x4005
   a:   2101            movs    r1, #1
   c:   f7ff fffe       bl      0 <RCC_APB2PeriphClockCmd>

And for one clock at a time:

00000000 <main>:
   0:   b590            push    {r4, r7, lr}
   2:   b089            sub     sp, #36 ; 0x24
   4:   af00            add     r7, sp, #0
   6:   2004            movs    r0, #4
   8:   2101            movs    r1, #1
   a:   f7ff fffe       bl      0 <RCC_APB2PeriphClockCmd>
   e:   2001            movs    r0, #1
  10:   2101            movs    r1, #1
  12:   f7ff fffe       bl      0 <RCC_APB2PeriphClockCmd>
  16:   f44f 4080       mov.w   r0, #16384      ; 0x4000
  1a:   2101            movs    r1, #1
  1c:   f7ff fffe       bl      0 <RCC_APB2PeriphClockCmd>
  ...

And here's RCC_APB2PeriphClockCmd:

00000000 <RCC_APB2PeriphClockCmd>:
   0:   4b04            ldr     r3, [pc, #16]   ; (14 <RCC_APB2PeriphClockCmd+0x14>)
   2:   699a            ldr     r2, [r3, #24]
   4:   b109            cbz     r1, a <RCC_APB2PeriphClockCmd+0xa>
   6:   4310            orrs    r0, r2
   8:   e001            b.n     e <RCC_APB2PeriphClockCmd+0xe>
   a:   ea22 0000       bic.w   r0, r2, r0
   e:   6198            str     r0, [r3, #24]
  10:   4770            bx      lr
  12:   bf00            nop
  14:   40021000        .word   0x40021000

0x40021000 is the base address of the RCC peripheral; the #24 offset points to the RCC_APB2ENR register, which has a bit for each clock that's being enabled. (See page 109 of RM0008 for details.)

  • 1
    what does the disassembly look like for both options? Can you post the disassembly for each? – J-Dizzle Mar 16 '14 at 07:17
  • The assembly is pretty boring — `RCC_APB2PeriphClockCmd()` is a perfectly normal function (which writes to `APB2ENR`); both options result in calls to it with different arguments. –  Mar 16 '14 at 07:22
  • 2
    assembly is never boring; its the whole dang point of embedded programming ;)!! So option A is just one 'mov immediate, (dest)', and option B is 3 'mov immediate, (dest)'? – J-Dizzle Mar 16 '14 at 07:36
  • also to note; APB buses are a pain; perhaps this is a peripheral synchronization 'feauture' ST built-in that couples these USART's together via a common shared-element. thus the disassembly would be useful, as a first step. – J-Dizzle Mar 16 '14 at 07:38
  • @justinmreina: Again, `RCC_APB2PeriphClockCmd` is a function call. So the code ends up as either one `movs movs bx`, or three `movs movs bx` with different immediate values. –  Mar 16 '14 at 16:19
  • 1
    If the clock tree uses a *PLL* (up clocks), then you often need to let it lock before enabling other sibling clocks. I don't know anything about this particular part. However, it is pretty typical to require starting parent clocks first. One module maybe enabled and starting to clock it may result in it trying to access another module which may cause some sort of latch up; ensure the modules are in a disabled state before starting a clock [this maybe impossible as the registers may need a clock to respond]. – artless noise Mar 17 '14 at 14:54

2 Answers2

5

Well, I think I figured it out, and it turned out to not be a hardware problem at all... there were a number of problems with my toolchain configuration:

  1. I was setting -nostdlib. This was causing some global initialization code to not be generated. I'm not sure how important that was, but other issues included:

  2. I was not passing -mthumb and other CPU options to the linker. This was causing some of the generated startup code to be garbage.

  3. My startup file didn't contain a call to __libc_init_array. This was causing some more initialization code to be dropped at link time.

I'm still not sure why splitting up the peripheral clock initializations managed to work around this. Perhaps the change in the amount of code was bumping something to just the right alignment? Anyways, solving the underlying issues seems to have patched things up so far (although I'm still kind of suspicious of some of the remaining startup code).

2

You might want to let us know exactly which device you're using and/or look at the errata for that device. For example, the errata for the STM32L100x6/8/B-A (and other) devices has the following (http://www.st.com/web/en/resource/technical/document/errata_sheet/DM00097022.pdf):

2.6.1 Delay after an RCC peripheral clock enabling

Description

A delay between an RCC peripheral clock enable and the effective peripheral enabling should be taken into account in order to manage the peripheral read/write to registers.

This delay depends on the peripheral's mapping:

  • If the peripheral is mapped on AHB: the delay should be equal to 2 AHB cycles.
  • If the peripheral is mapped on APB: the delay should be equal to 1 + (AHB/APB prescaler) cycles.

Workarounds

  1. Use the DSB instruction to stall the Cortex-M CPU pipeline until the instruction is completed.
  2. Insert "n" NOPs between the RCC enable bit write and the peripheral register writes (n = 2 for AHB peripherals, n = 1 + AHB/APB prescaler in case of APB peripherals).

This doesn't really sound like your problem but it might be related (maybe the one-at-a-time enabling introduces a delay that turns out to be necessary).

Michael Burr
  • 333,147
  • 50
  • 533
  • 760
  • The specific device I'm using is the STM32F103VET6. I don't see that issue listed in its errata (http://www.st.com/web/en/resource/technical/document/errata_sheet/CD00197763.pdf), and I'm not sure how that would apply here anyway, as I'm not using any peripherals yet, just enabling them! –  Mar 16 '14 at 04:13
  • Yeah - it didn't seem to me that this was your problem, but I thought I'd post in case it was related. As an experiment, you might want to add a small delay after a call to a 'combined' `RCC_APB2PeriphClockCmd()` just to see if that makes some sort of difference. – Michael Burr Mar 16 '14 at 21:19
  • I've tried that - unfortunately, it didn't seem to make a difference. :( –  Mar 16 '14 at 21:59