STM32L4 wake up from stop mode with USART - Page 1
Forum section: electronics / general

author: Rusty Cube
author_rank: Newbie
timestamp: March 16, 2021, 02:51:46 am
content: Hello!I am working with an STM32L452 and looking to use stop mode 1 for idle time, and wake back up with a USART 1 interrupt. The STM datasheet suggests that even though the low power mode will turn off the main clock used for the USART, it should still be able to wake up by using the HSI16 clock temporarily while the main clock wakes back up. Has anyone had any experience with this feature of the STM32L4 parts and might have some advice as to what I might be missing?Following the suggestions from the datasheet I have set the following registers across the relevant peripherals:SLEEPDEEP = 1 in the SCBPWR CR1 LPMS bits = 0b001 (choosing stop mode 1)RCC CR hsion = 1 (enable the hsi16 clock)RCC CR hsiasfs = 1 (Enable hsi16 clock to be used as a wakeup clock)RCC CR hsikeron = 1 (allow the clock to stay on during stop mode)RCC CFGR stopwuck = 1 (designates the hsi16 clock as the system clock for wakeup)USART1 CR1 UESM = 1 (enables wake from stop mode)USART1 CR3 WUFIE = 1 (allows interrupt generation from stop mode)USART1 CR3 WUS = ? (Not sure what trigger mode will be best yet, I have been using character match before investigating this mode, but that's not an option here)I also know that in my interrupt handler I will need to clear the WUCF interrupt flag.With those parameters set, I don't seem to be able to wake the device from the Stop 1 mode once it enters, and it does not set the WUF interrupt flag when I send a serial message to the USART 1 peripheral. I have also tried taking the USART 1 peripheral off of the APB2 clock it has been on and moving it to the HSI16 clock from the beginning, although that seems to have caused even more problems.I am not developing with STMCube, which is why I have only mentioned things in terms of registers instead of HAL functions. (For those interested, I have been developing in Rust using the STM32 HAL for that language, which has been quite good up to this point, where some of the finer controls seem to be lacking. That said, I still have full control and can directly write to the registers if needed.)I enter the sleep mode with a WFI instruction (managed by Rust's RTIC framework, and it's IDLE handler)For reference, the full datasheet for the part can be found here:https://www.st.com/resource/en/reference_manual/dm00151940-stm32l41xxx42xxx43xxx44xxx45xxx46xxx-advanced-armbased-32bit-mcus-stmicroelectronics.pdfAnd there is also a handy guide from STM that points in the right direction here:https://www.st.com/resource/en/application_note/dm00355687-how-to-wake-up-an-stm32xx-series-microcontroller-from-lowpower-mode-with-the-usart-or-the-lpuart-stmicroelectronics.pdfLet me know if you have any insight into this feature, or suggestions of next steps I can take or things I might be missing. Thanks!P.S Sorry for the long post, and thanks for bearing with me, this is my first post in this community, and I am excited to be here!

author: techman-001
author_rank: Frequent Contributor
timestamp: March 16, 2021, 03:50:36 am
content: Quote from: Rusty Cube on March 16, 2021, 02:51:46 amG'Day,It's like a breath of fresh air seeing Bitfield values expressed as in the ST datasheet, i.e.programming language independentand not buried in C structures or in pages of GUI screen dumpsI'm a Forth user who also has full control and can directly write to the registers, which I do all the time.I've found sleep modes very complex and I'm not familiar with the L series (as yet) but I've browsed the registers and it's not trivial. I suspect that anyone able to obtain butterfly fart levels of sleep power usage have spent *months* optimally integrating their code and hardware.I can't help, I just replied to say 'GO RUST!

author: harerod
author_rank: Frequent Contributor
timestamp: March 16, 2021, 08:27:03 am
content: Hi Rusty Cube, while I am still chuckling from techman-001's "butterfly farts", I can't restrain myself from chiming in.Well, I am not familiar with your particular component, but were I tasked to reduce power consumption, I would check if it were permissible to implement a more straightforward, lower consumption approach: putting the whole device in sleep and waking from a GPIO level change interrupt of the RX pin. At the expense of losing the first wake-up signal, this might yield the lowest power consumption possible. Of course this is a lazy and simple approach, which requires not much specialized knowledge of your device of choice.

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 16, 2021, 06:11:25 pm
content: Can you post relevant parts of your code?In particular, and that may be just a matter of terms, but you say you want to wake up from Stop mode 1, and then you say:QuoteThis is confusing. On those MCUs, the "sleep" mode is NOT a "stop" mode. So can you tell us/show us how you put your MCU in stop mode 1?Also, you seem to imply you are using third-party code (RTIC framework, which I know nothing about). Are you sure things are handled properly in there?And, what is the wakeup mode you're trying to use exactly? Address match? Have you checked that the baud rate is compatible with the wake-up functionality?

author: Rusty Cube
author_rank: Newbie
timestamp: March 16, 2021, 08:17:02 pm
content: Hi, thanks for the responses!@Harerod, thanks for the idea, I will test it out, although I think that missing the first bit of the signal might not be allowable in this situation. (the incoming data is COBS encoded, and the first byte is quite important to decoding the entire message)@SiliconWizard, Sure! Yes, I probably should not have said "sleep mode" there, I was using it interchangeably with "Low Power Mode" Here is a list of the low power modes supported by the part, my code currently works great with the default sleep mode after the WFI instruction, and can enter but not wake from the Stop 1 mode once entered:Yes, I am using a method different than the tried-and-true C libraries to handle things, the RTIC framework for the rust language is essentially a basic scheduler, and when no tasks are scheduled, it calls the WFI instruction automatically. I know that this works fine for entering both the sleep and stop 1 modes, and have measured power consumption to confirm the state is being entered fine.(Here's a snippet from deep within the Rust Cortex M library that generates the assembly WFI instruction:The wakeup mode (determined by the WUS register) is a bit trickier, I have been using the character match interrupt to interrupt out of the normal sleep mode (the interrupt flag is CMF in the USART ISR). Ultimately, it would be awesome if I could do the same out of Stop mode, although that doesn't seem to be an option, as the options for the WUS register are:Maybe I can just do a Start bit detection interrupt for RXNE and sort it out from there?As a couple more bits of information, here are some relevant clips from the reference doc I linked to earlier, where ST describes that this can be done (although somehow I get the feeling like they might have hoped no-one would actually do this):Lastly, there is a long bit of the documentation describing baud rate, and how because this uses a lower speed clock for the first wakeup frame, that it may not support higher baud rates. I have to be communicating at 115.2k baud rate, and I think that might be able to be accomplished by the HSI16 clock, but it also might fall outside the area of acceptable error tolerance, they really aren't making that one easy to calculate... (reference pages 9-12 of this document:https://www.st.com/resource/en/application_note/dm00355687-how-to-wake-up-an-stm32xx-series-microcontroller-from-lowpower-mode-with-the-usart-or-the-lpuart-stmicroelectronics.pdf)Thanks again for all the support so far!

author: bugnate
author_rank: Regular Contributor
timestamp: March 16, 2021, 09:41:37 pm
content: If you are using the method which leaves the HSI16 on all the time, and it looks like you are, I think that 115.2k baud cannot be the problem.Quote from: Rusty Cube on March 16, 2021, 02:51:46 amCan you elaborate? This seems like the best route to debug (setting the HSI16 as the source and leaving it that way). How/where were you doing the switch between APB2 and HSI16 before? (You mentioned all the other steps explicitly but only implied that one). Reconfiguring the USART after the clock change?I would scope whatever you can (UART TX and MCO in particular) to verify your clocks are what you think they are.

author: Rusty Cube
author_rank: Newbie
timestamp: March 16, 2021, 10:18:18 pm
content: Thanks for the reply Bugnate!I was hoping to have the USART peripheral running on the APB clock normally, and only use the HSI16 clock when waking from sleep, as described here:What I was saying in that quote is that I have toyed with having the peripheral always running on the HSI16 clock, although I have not yet gotten that to work. I have tried to get the USART peripheral to use the HSI clock by setting the registersRCC APB2ENR = 0 (disabling APB clock)RCC CCIPR USART1SEL = 0x2 (choosing HSI16 clock)I have to configure the peripheral to use the HSI clock manually, as the HAL that I am working with only supports configuring the USART with the ABP clock by default (curse these new, but undeveloped and unsupported technologies! (I still love Rust though)) here's a code snippet where I attempt to reconfigure the peripheral:Code:unsafe{let periph = pac::Peripherals::steal();periph.USART1.cr1.modify(|_r, w|{w.ue().bit(false) //disable usart1});periph.RCC.apb2enr.modify(|_r, w|{w.usart1en().bit(false) // disable apb2 clock for usart1});periph.RCC.ccipr.modify(|_r, w|{w.usart1sel().bits(0x2) //enable HSI16 clock for usart 1});periph.USART1.cr1.modify(|_r, w|{w.ue().bit(true) //enable usart1});... goes on to change other registers from here...I have also seen some things suggesting I may have to wait for the RCC CR HSIRDY bit to be cleared before the clock is ready to be used, perhaps I have to monitor it before I can do the clock switch.It also now occurs to me that I will have to change the BRR (baud rate register) after changing the clock, hmm...If possible it would be cool to have the dual clock system work, but I am investigating the idea of always having the HSI16 clock be the one to use. Please let me know if you have any other thoughts on this!(also, while I'm at it, my embedded images seem to not be working as I thought they would, any tips for that?)

author: bugnate
author_rank: Regular Contributor
timestamp: March 16, 2021, 11:02:37 pm
content: Quote from: Rusty Cube on March 16, 2021, 10:18:18 pmI don't think this does what you think it does. Leave this on. You cannot access USART registers with it disabled.Quote from: Rusty Cube on March 16, 2021, 10:18:18 pmYup and Yup.Quote from: Rusty Cube on March 16, 2021, 10:18:18 pmMy thinking was that if nothing is working, at least start with HSI16 always-on. As you are discovering, to do otherwise involves a lot of gritty details that quickly become counter-productive with respect to low-power (and low-latency) not to mention simplicity. Personally and professionally (low-power embedded is my bread and butter), I would use the HSI16 always-on method at least for the general case. But there certainly are exceptions e.g., if transitions are rare and run mode duty-cycle is extremelylowhigh. But I don't want to discourage you from doing it for fun!

author: Rusty Cube
author_rank: Newbie
timestamp: March 17, 2021, 03:32:09 am
content: Quote from: bugnate on March 16, 2021, 11:02:37 pmThis is exactly what I needed, It didn't click with me that I was killing the peripheral when I did that. Going from there, I was able to configure the USART1 to run on the HSI16 all the time, and from there my previous changes to the wake up from stop stuff just worked! I am now entering the Stop 1 state and successfully waking from it and reading incoming serial data!Here is the code of all the registers that I have changed to get here:Code:unsafe{let periph = pac::Peripherals::steal();periph.RCC.cr.modify(|_r, w|{w.hsiasfs().bit(true) //enable HSI16 to be used as wakeup clock});periph.RCC.cr.modify(|_r, w|{w.hsikeron().bit(true) //enable HSI16 to always be on during stop states});periph.RCC.cr.modify(|_r, w|{w.hsion().bit(true) //enable HSI16 clock});while periph.RCC.cr.read().hsirdy().bit_is_clear() {}periph.RCC.cfgr.modify(|_r, w|{w.stopwuck().bit(true) //sets HSI16 clock as wake-up system clock});periph.USART1.cr1.modify(|_r, w|{w.ue().bit(false) //disable usart1});periph.RCC.ccipr.modify(|_r, w|{w.usart1sel().bits(0x2) //enable HSI16 clock for usart 1});periph.USART1.brr.modify(|_r, w|{w.brr().bits(0x008A) //set baud rate to 115200 with new clock});periph.USART1.cr1.modify(|_r, w|{w.uesm().bit(true) //enable wake from stop mode});periph.USART1.cr3.modify(|_r, w|{w.wufie().bit(true) //allow interrupt from stop mode});periph.USART1.cr3.modify(|_r, w|{w.wus().bits(0x3) //configure what kind of WUF});periph.USART1.cr1.modify(|_r, w|{w.ue().bit(true) //enable usart1});periph.USART1.icr.write(|w|{w.wucf().bit(true) //clear wuf interrupt flag});periph.PWR.cr1.modify(|_r, w|{w.lpms().bits(0b001) // set low power mode to stop 1});}cx.core.SCB.set_sleepdeep();That said, I think there is still some strange behavior going on with the WUF interrupt bit, probably have to play around with the WUS register and clearing the WUF bit properly in my interrupt handler, but that's something I'll tackle next.Thanks so much Bugnate and the other folks who chimed in here. I've gotten pretty close to "butterfly farts" level of power consumption!

author: harerod
author_rank: Frequent Contributor
timestamp: March 17, 2021, 08:44:46 am
content: Rusty Cube, thanks for the feedback. Sounds like you found a workable solution.The size of your average butterfly depends on location. I would be curious where your your MCU supply current went, with your current setup. Looking at the STM32L452-datasheet I would expect something around 1mA? Is this good enough for your application?When I made my suggestion regarding the GPIO interrupt, I really meant discarding the first complete transmission, because the MCU would be in a deeper sleep mode (or stop mode, as ST calls this state). Assuming that your application would allow that, of course.STM32 are not the best for very low power consumption. When low or below single digit µA are required, I prefer to combine them with a dedicated power management device, which might be either logic or even a 8bit microcontroller (e.g. MSP430 or tinyAVR 1-series).

author: SiliconWizard
author_rank: Super Contributor
timestamp: March 17, 2021, 06:01:59 pm
content: Quote from: Rusty Cube on March 17, 2021, 03:32:09 amGood catch. Yes on STM32's, disabling APB clock for a peripheral will prevent access to its registers, thus you can't configure it. Found that out early when I started with STM32 MCUs, with plain GPIOs.