4
\$\begingroup\$

The ADC module of the STM32 is used in my application which is very sensitive to power consumption.

In this application, the ADC is required to work only at 20 samples per second. Using the DMA uses more power than I expected. I decided to make it work in single-sample mode by having a task (FreeRTOS) trigger a convert and wait for the convert to be done every 50 ms.

Here is my code:

 u16 i;
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, ENABLE);
 ADC_Cmd(ADC1, ENABLE);
 for(i=0;i<sizeof(ADC_Channel_Table)/sizeof(u8);i++)
 {
     ADC_RegularChannelConfig(ADC1, ADC_Channel_Table[i], 1, ADC_SampleTime_71Cycles5);
     ADC_SoftwareStartConvCmd(ADC1, ENABLE);
     while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC))
     {
     }
     ADCConvertedValue[i] = ADC_GetConversionValue(ADC1);
 }
 ADC_Cmd(ADC1, DISABLE);
 RCC_APB2PeriphClockCmd(RCC_APB2Periph_ADC1, DISABLE);

Where "sizeof(ADC_Channel_Table)" is 5 because 5 channels are sampled.

The sample time is ADC_SampleTime_239Cycles5 (actually 256 cycles where the convert time is included). 5 channels therefore requires about 1500 cycles. ADC clock is 12 MHz and 1500 cycles is about 120 µs.

And look at the code:

while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC))
{
}

This means the CPU keeps busy-waiting for the convert to be done, and the wait time is 120 µs in total.

120 µs is big as the CPU must wait for such a long time and this wasted the power, but this level of time is too small for the RTOS. The RTOS is unable to use up such a small time.

So I want to insert some "power-saving" instructions to the wait loop.

For example:

while(!ADC_GetFlagStatus(ADC1, ADC_FLAG_EOC))
{
    __ASM("NOP");__ASM("NOP");__ASM("NOP");__ASM("NOP");
}

But NOP consumes the same amount of power as any other instruction I have tried.

What instruction can I insert in my while loop that would consume the least amount of power?

\$\endgroup\$
4
  • 4
    \$\begingroup\$ Have you considered dumping the RTOS? \$\endgroup\$ Commented Oct 6, 2016 at 4:52
  • \$\begingroup\$ Hopefully I have interpreted what OP has wanted to ask correctly. \$\endgroup\$
    – efox29
    Commented Oct 6, 2016 at 6:28
  • 2
    \$\begingroup\$ I think @AleksiTorhamo has the right answer. You are polling when you should be sleeping and using interrupts to wake yourself help. This would reduce your dynamic power consumption for your processor. \$\endgroup\$
    – efox29
    Commented Oct 6, 2016 at 6:30
  • \$\begingroup\$ Which part of this code provides 50ms sampling rate? \$\endgroup\$ Commented Oct 7, 2016 at 4:46

6 Answers 6

20
\$\begingroup\$

If you want power-saving, put the MCU to sleep. The relevant instructions are WFI and WFE: wait-for-interrupt and wait-for-event, respectively.

WFI is kind of self-explanatory: it wakes up when you get an interrupt. (The interrupt must be enabled, though!)

WFE might merit a bit more explanation. To use it, it's probably enough to know that if you set SEVONPEND, an interrupt that's not enabled in the NVIC qualifies as an event. So if you enable the ADC interrupt in the peripheral but not in the NVIC, you can then wait for it to complete with WFE. Do still use the loop with the flag check, as there might be other events that wake up the MCU. (Just replace the NOPs with a WFE) As usual, for more detailed information, see the reference manual.

You'll probably also want to select how deep sleep you want to go to, but how that's done might depend on the specific STM32 MCU that you're using. At least on one model, the relevant flags were PDDS, LPDS and SLEEPDEEP. You'll certainly want to read the relevant section of the reference manual in any case.

\$\endgroup\$
7
  • 1
    \$\begingroup\$ Probably still want the loop with WFI as well as with WFE, in case more than one interrupt is enabled. \$\endgroup\$
    – abligh
    Commented Oct 6, 2016 at 14:29
  • \$\begingroup\$ @abligh Very true! \$\endgroup\$ Commented Oct 6, 2016 at 15:04
  • \$\begingroup\$ And when you put CPU to sleep, will ADC continue the conversion? Just curious... \$\endgroup\$ Commented Oct 7, 2016 at 1:45
  • 1
    \$\begingroup\$ @AliChen In general, that will depend on the chip and how deep the sleep mode that was selected is. When in doubt, see the reference manual of the chip you're using. On all the chips I remember looking at, the lightest sleep mode will stop the core, but keep all the peripherals running, unless you tell it otherwise (eg. by clearing the LPEN bit of the relevant peripheral). On deeper sleep modes, the peripherals are generally stopped, too, but it all depends on the specific chip and peripheral; Some peripherals on some chips can be kept operating on deeper sleep modes, too. \$\endgroup\$ Commented Oct 7, 2016 at 2:50
  • \$\begingroup\$ That's the question. Not in general, but in this very particular MCU. The OP does not ask if sleep or not to sleep for 50ms between samples. His question is if he can save some more power during 120us of ADC conversion time by doing something other than polling its end-of-conversion flag. \$\endgroup\$ Commented Oct 7, 2016 at 3:06
6
\$\begingroup\$

Lets try to answer the opposite question to explain why NOP doesn't save power. The Cortex-M3 is a processor designed to be small and relatively simple - it doesn't have many of the circuits designed to perform dedicated tasks (caches, floating-point, branch prediction), or where it does, they are limited implementations to get the biggest hit for lowest cost.

Most instructions progress through the 3-stage pipe doing similar amounts of work. Instruction fetch, decode, and an ALU operation. To increase the power, maybe we could add in a data transfer (which lights up more logic outside of the core, but then most likely might stall the whole pipe since the core is in-order). Maybe we could use the single-cycle divide just after doing a data transfer - there you could possibly get two extra parts of activity happening concurrently. Maybe doing this will reduce the register file activity though, so its not all helping to hit peak power.

Although there is some instruction to instruction variation, (and a good amount of design effort to optimise this), the bulk of the active power doesn't depend too much on the instructions. Sure, filling the pipe with several NOP will stop any toggle activity, but the pipe still advances. Stalling the pipe (for a slow data transfer) will do much more, but the only well-defined state that you can rely on to be optimal will be WFI and the various sleep states.

Bigger cores will have much more peak-to-mean because a larger proportion of their silicon will be inactive in a pre-fetched and predicted idle loop (and likley more other active logic on the chip, which can also often be clock/power gated)

\$\endgroup\$
1
  • \$\begingroup\$ Of course there are a lot of peripherals outside the CPU that consume power on these devices too, each of which has its own clock control. \$\endgroup\$ Commented Oct 7, 2016 at 21:23
4
\$\begingroup\$

If you're happy for samples every 62.5ms or 31.25ms (as opposed to 50ms), you can use the RTC alarm function to wake up the STM32 and standby/stop between conversions. This will massively save power, especially if you configure the ADC to go to low power mode after conversion (this may be STM32 series dependent). If you start a conversion then go into standby/stop, once you wake up the conversion will be complete anyway. This does add a latency of one sample and requires you to process the sample before starting a new sample but dependent on your application, this may be feasible.

Flow:

Start ADC conversion and enable RTC alarm

Enter stop mode/standby mode

Once RTC has woken up the microcontroller, grab ADC conversion value

Process sample (store/filter etc.)

Start process again!

Stop and standby modes use very little power so depending on your application, this may be a suitable method.

\$\endgroup\$
3
\$\begingroup\$

I'd look into using a timer to start the conversion, and an interrupt to collect the result.

The CPU core can then sleep until data is available.

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

As an alternative to sleeping, you might be able to configure the clock generator to only give the core one out of every 2, 4, 8, etc. clocks going into the chip. If the CPU will need to do many operations that take six cycles worth of code, but can only be triggered once every 64 cycles due to external hardware constraints, slowing the CPU to 1/8 speed may be just the ticket. While using an idle mode with interrupt wake-up may be better than leaving the CPU at full speed, the CPU may need to spend many cycles for each event configuring the idle/wake transitions. Some controllers have options to set speeds separately for the main line versus interrupt handlers, so that even if the main-line slows the CPU to 1/8 speed interrupt handlers will run at normal speed. I don't think that's a terribly common feature, though. Otherwise you'll need to pick a speed which is fast enough to work with any interrupt handlers that might be executed while doing the I/O in question.

\$\endgroup\$
0
\$\begingroup\$
  1. Set a timer with a 1/20s interval to start a conversion
  2. Enable the ADC interrupt and handle the result there

Or if a slight clock jitter is not problem.

  1. Setup a 50ms FreeRTOS timer. They are efficient
  2. Enable the ADC interrupt and handle the result there

To put the MCU to sleep otherwise, just put asm("WFI") in the FreeRTOS idle loop.

I have a similar situation, but I've just aligned everything to the FreeRTOS 1ms tick timer.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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