Skip to main content
Visitor II
December 10, 2025
Solved

STM32L0 cannot change PWR_CR_ VOS

  • December 10, 2025
  • 6 replies
  • 171 views

Hi, I'm stuck on a problem with setting PWR_CR_VOS on NUCLEO-32 STM32L031K6
- I want to select HSI16-PLL as clock source
- VDD is 3.3V
- FLASH latency is set to 1WS
But I can only set PWR_CR_VOS to RANGE 2 and RANGE 3.
RANGE 1 cannot be selected, so I can't set HSI-PLL to 32MHz
where am I going wrong?

Example code:

// Set the Flash ACR to use 1 wait-state
// and enable the prefetch buffer and pre-read.
FLASH->ACR |= (FLASH_ACR_LATENCY | FLASH_ACR_PRFTEN | FLASH_ACR_PRE_READ);

//----------------------------------------------------
// 1. POWER ENABLE CLOCK and VOLTAGE REGULATOR
//----------------------------------------------------
// APB2ENR->SYSCFGEN
RCC->APB2ENR |= RCC_APB2ENR_SYSCFGEN;

// APB1ENR->PWREN: Power interface clock enable bit for PWR
RCC->APB1ENR |= RCC_APB1ENR_PWREN;

// PWR: VOS[11:2] bits (Voltage scaling range selection)
// !!! 0x0UL: FORBIDDEN
// 0x1UL: 0b01 RANGE 1 1.8V 32MHz
// 0x2UL: 0b10 RANGE 2 1.5V 16MHz
// 0x3UL: 0b11 RANGE 3 1.2V 4.2MHz
// Clear VOS bits
PWR->CR &= ~PWR_CR_VOS;
// SET RANGE 1
PWR->CR |= (0x1UL << 11U);
// Wait until ready
while (PWR->CSR & PWR_CSR_VOSF);

The value in the register is still 0x2UL, when trying to change to 0x1UL the register is 0x3UL
Where am I making a mistake that RANGE1 cannot be set to 0x1UL?

    This topic has been closed for replies.
    Best answer by waclawek.jan

    The reset value of the VOS field is 0b01, if you OR it with 0b10 the result is 0b11.

    You may want to do something like:

    uint32_t tmp;
    
    tmp = PWR->CR & (~PWR_CR_VOS);
    tmp = tmp |= (0x1UL << 11U);
    PWR->CR = tmp;

    JW

    6 replies

    RinGOAuthor
    Visitor II
    December 10, 2025

    Interestingly, if I generate the CubeMX code with the LL library, it works. PWR_CR_VOS set to 0x1UL

    I've taken the code step by step, but up to the PWR_CR_VOS setting, everything is the same except for the NVIC setting for SysTick, which shouldn't affect the registry settings.

    Super User
    December 10, 2025

    It's in the comments

    // PWR: VOS[11:2] bits (Voltage scaling range selection)
    // !!! 0x0UL: FORBIDDEN

    yet you still do it

    PWR->CR &= ~PWR_CR_VOS;

    NEVER split a read-clear_bits-set_bits-write operation into two (i.e. read-clear_bits-write, read-set_bits-write).

    JW

    RinGOAuthor
    Visitor II
    December 10, 2025

    Okay, I canceled the PWR_CR_VOS register reset. Still the same result. I want to write 0x1UL to the VOS register, but it changes from 0x2UL to 0x3UL
    Why?

    RinGOAuthor
    Visitor II
    December 10, 2025

    An incomprehensible phenomenon for me.
    If I try to change the VOS bit value in the PWR_CR register,
    // SET RANGE 1
    PWR->CR |= (0x1UL << 11U);
    It doesn't work. The value changes from 0x2UL to 0x3UL

     

    If I change the value of the entire PWR_CR register in hard mode,
    // SET RANGE 1
    PWR-> CR = 0x00000800;
    it works. The value changes from 0x2UL to 0x1UL

    Why doesn't it work the first way? Overwriting the entire register is not a solution.

    Super User
    December 10, 2025

    The reset value of the VOS field is 0b01, if you OR it with 0b10 the result is 0b11.

    You may want to do something like:

    uint32_t tmp;
    
    tmp = PWR->CR & (~PWR_CR_VOS);
    tmp = tmp |= (0x1UL << 11U);
    PWR->CR = tmp;

    JW

    RinGOAuthor
    Visitor II
    December 10, 2025

    Yes, but it's basically the same as writing to the register

    PWR-> CR = 0x00000800;

    This method is not about bit manipulation, but writing a 32bit value to the register.

    Graduate II
    December 11, 2025

    The method proposed by @waclawek.jan is not the same as writing directly 0x00000800 to PWR->CR.
    On line 3 tmp has the content of PWR->CR with the bits related to VOS set to 0. The next line is setting VOS as suited for your application. This way you don't care about the content of the whole register, you only change the bits you need or care about.

    Super User
    December 11, 2025

    And what?

    PWR->CR |= (0x1UL << 11U) *is* writing a 32-bit value to the register, too.

    There's no way to change individual bits in the peripheral registers, except in cases where those registers do have a special hardware arrangement to facilitate such individual bit change (e.g. you can inidividually change bits in GPIO_ODR through the special arrangement which that register has in hardware with the GPIO_BSRR register). The PWR_CR register has no such arrangement, so you can write only all 32 bits of it at once.

    JW

    RinGOAuthor
    Visitor II
    December 12, 2025

    I think I understand now.
    I had to stupidly phase it out.
    1. load the value of the entire register into "tmp"
    2. clear the bits for PWR_CR_VOS
    3. set the desired value of PWR_CR_VOS
    4. insert the entire modified 32-bit value back into the register
    ...

    So I will assume that this is not possible with this register because:
    - STM32L031k has PWR_CR_VOS resetting hardware disabled:

    // HW disabled:
    PWR->CR &= ~(0x3UL << 11U); // 0b11

    and it is not possible to set/reset to values ​​less than 0x2 with a regular command:

    // not possible to set
    PWR->CR |= (0x1UL << 11U); // 0b01

    The default value of PWR_CR_VOS is 0x2  (0b10)
    So it can only be modified to values ​​0x2 and 0x3

    To set the value:
    PWR->CR |= (0x1UL << 11U);

    It is probably only possible to do this in the way JW states


    If I understood correctly,
    thank you very much.