2
\$\begingroup\$

STM32F746-Disco, Win 10, Cube 1.8.0; CMSIS-only (no HAL). C.

I've been messing around with STM32F746 and learning to do various stuff directly via registers (interrupts, dma no problem). I left configuring clock for the better days and was using default 16MHz HSI until today.

Better days have come. I've decided to go full 216MHz from 25MHz crystal with PLL. As usual, covered myself head to toe with documentation. Here is my code:

main.c:

#include "main.h"

int main(void)
{
    rcc_setup();
    gpio_setup();
    while(1){
        blink_ld1();
    }
}

part of rcc.c:

void rcc_init(void) {

    //enable HSE
    RCC->CR |= RCC_CR_HSEON;
    while (!(RCC->CR & RCC_CR_HSERDY)); //wait while hardware signals HSE is stable

    //configure AHB/APB clocks
    RCC->CFGR &= ~RCC_CFGR_HPRE_DIV1; //AHB Prescaler 1
    RCC->CFGR |= RCC_CFGR_PPRE1_DIV4; //APB1 Prescaler 4
    RCC->CFGR |= RCC_CFGR_PPRE2_DIV2; //APB2 Prescaler 2

    //configure PLL
    RCC->CR |= RCC_CR_PLLON; //Enable PLL
    while (!(RCC->CR & RCC_CR_PLLRDY)); //wait while hardware signals PLL is OK

    RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; //PLL/PLL2S source = HSE
    RCC->PLLCFGR |= 25U << RCC_PLLCFGR_PLLM_Pos; //PLLM 25
    RCC->PLLCFGR |= 432U << RCC_PLLCFGR_PLLN_Pos; //PLLN 432
    RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLP; //PLLP 2
    
    //Set System Clock to PLL
    RCC->CFGR |= RCC_CFGR_SW_PLL; // System Clock Mux: Select PLL as system clock
    while ((RCC->CFGR & RCC_CFGR_SWS) != (RCC_CFGR_SWS_PLL)); //wait until PLL is established as system clock
}

rcc_init() is literally the first thing rcc_setup() does.

It crashes with "WWDG interrupt infinite loop" if and only if I execute RCC->CFGR |= RCC_CFGR_SW_PLL;. Otherwise, runs correctly.

As I was debugging, I decided to put a breakpoint exactly where my code breaks to look at the PLL registers and make sure the values I set are OK. And they're clearly not.
RCC_PLLCFGR is all wrong. I commented out all the code that modifies it. No change to values. So the register is at reset value. And I don't understand why.

Also, I tried commenting out APB/AHB prescaler modifications. No change (and it still works with new prescalers as long as system clock is from HSI).

I checked all AHB1ENR and APB's in case I forgot to activate the clock to something. Doesn't seem to be the case.

What did I miss?

EDIT1:

I swapped PLL parameter initialization (25 and 432) and PLL activation. Now the data in the register is changed from reset value. PLLM is correct. PLLN is NOT correct - not 432. Tinkering on. Mini-Edit. Wrote HEX value of 432. Still same fault.

EDIT2:

I went full pen and paper and manually calculated every single bit of the register, taking into account its reset value and bits that aren't supposed to be changed.

RCC_PLLCFGR reset value is 0x2400 3010
I did RCC->PLLCFGR = 0x29406C19; This puts correct values into the correct places, triple checked with debugger and on paper. Same issue.

HAL library enables power for PWR peripheral. I read its part in the reference manual and found no connection to clock speeds. Can it be related or has nothing to do with it?

Also, I don't know why it writes that it fires window watchdog interrupt. There has never been a single program that used it on the disco board, absolutely certain. I think it jumps there due to clock error.

EDIT3: I examined how auto-generated code uses HAL and initializes 216MHz. All PLL values are the same, but it also enables PWR peripheral and sets Overdrive and Overdrive switching. So I did functionally exactly the same as HAL, and I see no changes.

In the rcc_init function now after HSE enable I have the following:

//enable PWR peripheral
    volatile uint32_t temp;
    RCC->APB1ENR |= RCC_APB1ENR_PWREN;
    temp = RCC->APB1ENR & RCC_APB1ENR_PWREN;
    (void) temp; //waste cycles until activated
    PWR->CR1 |= PWR_CR1_VOS; //explicit default value for internal voltage regulator
    (void) temp; //waste cycles until activated
    PWR->CR1 |= PWR_CR1_ODEN;
    while (!(PWR->CSR1 & PWR_CSR1_ODRDY)); //wait while overdrive gets ready
    PWR->CR1 |= PWR_CR1_ODSWEN; //enable overdrive switching
    while (!(PWR->CSR1 & PWR_CSR1_ODSWRDY)); //wait while overdrive gets ready

Double checked register values, they're all set correctcly for overdrive. I even went so far as to debugging the CubeMX generated code and literally screenshotting RCC and PWR registers.

RCC:
CR - identical to the bit
PLLCFGR - identical to the bit
CFGR - identical to the bit

PWR:
CR1 - identical to the bit
CSR1 - identical to the bit
CR2 - identical to the bit
CSR2 - identical to the bit

\$\endgroup\$
13
  • \$\begingroup\$ Are you ORing the 432 value to the register, or writing it? \$\endgroup\$
    – Justme
    Feb 21 at 16:01
  • \$\begingroup\$ Obviously ORing since I have other parts of the register to write to. How could that be a problem? \$\endgroup\$
    – Ilya
    Feb 21 at 16:47
  • 1
    \$\begingroup\$ Are you using some bootloader? Because if this is the first code that executes, it will not be possible to get a watchdog interrupt because the watchdog is not enabled. So I suspect that some other code is running first and leaving your chip in an modified configuration which you aren't accounting for. Just one example -- the bootloader might have enabled the PLL, so you should explicitly disable it, and wait for it to stop, before changing its configuration. \$\endgroup\$
    – Ben Voigt
    Feb 21 at 22:25
  • 1
    \$\begingroup\$ @Ilya Hi, It seems you have solved it yourself - great. When this happens, please self-answer the question by writing your own answer. So move the solution out of the question, into its own answer. Then you can "accept" the answer (i.e. click the "green check mark") which solved the problem for you (probably your answer) to effectively close the question. That is the correct method of closing a question which you solved yourself (see the link I gave above). Thanks. \$\endgroup\$
    – SamGibson
    Feb 22 at 16:19
  • 1
    \$\begingroup\$ I can accept my answer in 22 hours, it says. Thanks for the link. \$\endgroup\$
    – Ilya
    Feb 22 at 16:43

3 Answers 3

3
\$\begingroup\$

I think you are assuming the register reset value is 0, but it's 0x24003010.

You can't just go ORing the bits with new N/M values and expect the values to be correct.

\$\endgroup\$
5
  • \$\begingroup\$ alright now this is actually a valid point. I will nullify the bits before ORing the right thing and will get back with it tomorrow. Somehow I saw the non-zero reset value when debugging and it simply didn't even occur to me for whatever reason. \$\endgroup\$
    – Ilya
    Feb 21 at 22:04
  • \$\begingroup\$ I have manually written PLLCFGR value. Literally pen and paper, every bit. RCC->PLLCFGR = 0x29406C19; Checked in the debugger, all values are correct (again pen and paper). Same problem. What am I missing urgh \$\endgroup\$
    – Ilya
    Feb 22 at 13:59
  • \$\begingroup\$ Unfortunately, I have identical register content, and still doesn't work (more in edit) \$\endgroup\$
    – Ilya
    Feb 22 at 15:33
  • \$\begingroup\$ What else have you done except boosted the frequency? Set the internal voltage regulator to high enough voltage to run CPU core at max speed? Set the flash prefetching to run at max speed? Set AHB and APB bus system clock divisors to run at their respective maximum speeds? \$\endgroup\$
    – Justme
    Feb 22 at 15:53
  • \$\begingroup\$ I left internal voltage regulators at default (as it is in CubeMX code). I don't entirely understand the whole purpose of that regulator and why my code ran without it from HSI. Also, I have no idea how prefetching works, I'm not that deep into it yet. And yes, I did set AHB and APB dividers. I've solved it. Edited it in. Compared CubeMX and my code side by side in terms of what they do. Obviously mine is 1/10th long. Thank you for your participation! \$\endgroup\$
    – Ilya
    Feb 22 at 15:57
1
\$\begingroup\$

The reference manual says (section 5.2.3 "PLL")

Since the main-PLL configuration parameters cannot be changed once PLL is enabled, it is recommended to configure PLL before enabling it (selection of the HSI or HSE oscillator as PLL clock source, and configuration of division factors M, N, P, and Q).

Your code is trying to change the parameters while the PLL is running.

After you do

RCC->CR |= RCC_CR_PLLON; //Enable PLL
while (!(RCC->CR & RCC_CR_PLLRDY)); //wait while hardware signals PLL is OK

it is totally forbidden to do

RCC->PLLCFGR |= RCC_PLLCFGR_PLLSRC_HSE; //PLL/PLL2S source = HSE
RCC->PLLCFGR |= 25U << RCC_PLLCFGR_PLLM_Pos; //PLLM 25
RCC->PLLCFGR |= 432U << RCC_PLLCFGR_PLLN_Pos; //PLLN 432
RCC->PLLCFGR &= ~RCC_PLLCFGR_PLLP; //PLLP 2

Swap those two blocks and try again (also be sure to overwrite the whole register as @Justme pointed out and not rely on the previous configuration).

\$\endgroup\$
0
0
\$\begingroup\$

I went into CubeMX generated code again and examined line by line. I did everything right, except that I didn't set Flash latency! One and only line of code! And it worked! Since I have next to no experience with Flash yet, I took CubeMX-generated Flash latency value. Got some reading to do now. But for now, it works.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.