Markk
Well-known member
Hi
first a big thank-you to duff for the formidable Snooze lib!
I'm playing around with continuous ADC+DMA in low power modes and/or with lowered CPU frequencies. Here are some insights and issues for comment.
First I tried a ADC setup in normal run mode with different CPU frequencies. The setup is the same as in analog_init()
Surprisingly the ADC is fastest with the 16MHz option which selects a 8MHz clock for the ADC and samples at ~430kS/s. Higher CPU frequencies are slower at ~330kS/s although using a faster 12MHz ADC clock. I guess this is some aliasing effect.
Then I tried Snooze.idle() as a first step and found a problem. The comment says
But when I follow up enter_wait() and wait() it actually disables the systick:
So obviously my timing code got all messed up. When I comment out these two lines, the Snooze.idle() works with a 3mA power saving @24MHz.
Then I tried with Snooze.sleep() a.k.a. VLPW. Because the bus clock is off, ADC has to be switched to its own asynchronous clock in order to still operate in VLPW.
Now I only get ~190kS/s in normal run mode.
in and out of the Snooze.sleep() call I had to compensate for the clock change on a PWM channel:
Of course this will have to be optimized and de-glitched. Comments on how to really do such compensation are welcome.
Now ADC+DMA kind of works through Snooze.sleep() (which is cool of course) but the ADC sampling rate seems to become erratic and still lower. Also millis() etc. get all messed up. Power consumption is all over the place. Of course it is often interrupting out of sleep at least each half DMA buffer (buffer has 512 samples).
Next I tried the new REDUCED_CPU_BLOCK (which is a great addition). Again with compensation (Serial too) but only at the beginning/end.
This solution achieved stability but only at ~120kS/s. That's not acceptable for my purposes.
Next I remembered best results at the 16MHz compiler setting. I can't use it statically because of lost USB (and a lot of computing power of course). So I borrowed the CPU() method from duff's deprecated LowPower_Teensy3 lib and used it to dynamically switch to 16MHz.
Here are some rough measurements (power usage is including high voltage generator and other peripherals). All with Snooze.idle().
Compiler settings (for comparison):
Power conservation is not what I hoped for, of course. Really hoped for Snooze.sleep(). Suggestions very welcome.
--Markk
first a big thank-you to duff for the formidable Snooze lib!
I'm playing around with continuous ADC+DMA in low power modes and/or with lowered CPU frequencies. Here are some insights and issues for comment.
First I tried a ADC setup in normal run mode with different CPU frequencies. The setup is the same as in analog_init()
Code:
ADC1_CFG1 =
(ADC_CFG1_12BIT) // analog.c clock config for 12bit
| ADC_CFG1_MODE(1) // Conversion mode, 0=8 bit, 1=12 bit, 2=10 bit, 3=16 bit (+1 in diff mode)
| ADC_CFG1_ADLSMP; // long sample time (for high frequency conversions)
ADC1_CFG2 = ADC_CFG2_MUXSEL // b-channel (?)
//| ADC_CFG2_ADHSC // high speed
| ADC_CFG2_ADLSTS(2); // Sample time, 0=24 cycles, 1=12 cycles, 2=6 cycles, 3=2 cycles
Then I tried Snooze.idle() as a first step and found a problem. The comment says
Code:
/**
* idle - puts processor into wait mode until next interrupt typically systick.
*/
void SnoozeClass::idle( void ) {
enter_wait( );
}
Code:
static inline
void wait( void ) {
[COLOR="#FF0000"]SYST_CSR &= ~SYST_CSR_TICKINT; // disable systick timer interrupt[/COLOR]
SCB_SCR &= ~SCB_SCR_SLEEPDEEP_MASK; // Clear the SLEEPDEEP bit to make sure we go into WAIT (sleep) mode instead of deep sleep.
ATOMIC_BLOCK(ATOMIC_RESTORESTATE) { asm volatile( "wfi" ); }// WFI instruction will start entry into WAIT mode
[COLOR="#FF0000"]SYST_CSR |= SYST_CSR_TICKINT; // renable systick timer interrupt[/COLOR]
}
Then I tried with Snooze.sleep() a.k.a. VLPW. Because the bus clock is off, ADC has to be switched to its own asynchronous clock in order to still operate in VLPW.
Code:
ADC1_CFG1 =
ADC_CFG1_ADIV(0) // direct clock frequency
| ADC_CFG1_ADICLK(3) // ADC's own asynchhronous clock (supports low power modes)
| ADC_CFG1_MODE(1) // Conversion mode, 0=8 bit, 1=12 bit, 2=10 bit, 3=16 bit (+1 in diff mode)
| 0;//ADC_CFG1_ADLSMP; // long sample time (for high frequency conversions)
ADC1_CFG2 = ADC_CFG2_MUXSEL // b-channel (?)
| ADC_CFG2_ADHSC // high speed
| ADC_CFG2_ADLSTS(3); // Sample time, 0=24 cycles, 1=12 cycles, 2=6 cycles, 3=2 cycles
Now I only get ~190kS/s in normal run mode.
in and out of the Snooze.sleep() call I had to compensate for the clock change on a PWM channel:
Code:
DEBUG_SERIAL.flush();
analogWriteFrequency(PIN_HV_PWM, (uint64_t)frequency * F_CPU / 2000000);
analogWrite(PIN_HV_PWM, duty);
Snooze.sleep(config);
analogWriteFrequency(PIN_HV_PWM, frequency);
analogWrite(PIN_HV_PWM, duty);
Now ADC+DMA kind of works through Snooze.sleep() (which is cool of course) but the ADC sampling rate seems to become erratic and still lower. Also millis() etc. get all messed up. Power consumption is all over the place. Of course it is often interrupting out of sleep at least each half DMA buffer (buffer has 512 samples).
Next I tried the new REDUCED_CPU_BLOCK (which is a great addition). Again with compensation (Serial too) but only at the beginning/end.
Code:
REDUCED_CPU_BLOCK()
{
// compensate for VLPR 2MHz clock
analogWriteFrequency(PIN_HV_PWM, (uint64_t)frequency * F_BUS / 2000000);
DEBUG_SERIAL.begin(57600ull * F_CPU / 2000000);
... actual work comes here ...
DEBUG_SERIAL.flush();
}
DEBUG_SERIAL.begin(57600);
analogWriteFrequency(PIN_HV_PWM, frequency);
Next I remembered best results at the 16MHz compiler setting. I can't use it statically because of lost USB (and a lot of computing power of course). So I borrowed the CPU() method from duff's deprecated LowPower_Teensy3 lib and used it to dynamically switch to 16MHz.
Code:
DEBUG_SERIAL.flush();
setCPUFrequency(SIXTEEN_MHZ);
// compensate for dynamically changed clock
analogWriteFrequency(PIN_HV_PWM, (uint64_t)frequency * F_BUS / _bus);
DEBUG_SERIAL.begin(57600ull * F_CPU / _cpu);
... actual work comes here ...
DEBUG_SERIAL.flush();
// Switch back to full CPU Freq
setCPUFrequency(F_CPU);
// compensate
DEBUG_SERIAL.begin(57600);
analogWriteFrequency(PIN_HV_PWM, frequency);
Here are some rough measurements (power usage is including high voltage generator and other peripherals). All with Snooze.idle().
Compiler settings (for comparison):
- 96MHz, ~330kS/s, 37mA
- 24MHz, ~330kS/s, 24mA
- 16 MHZ, ~430kS/s, 20mA
- 8MHz, ~215kS/s, 17mA
- 4MHz, ~180kS/s (async), 15mA
- 2MHz, ~120kS/s (async), 13mA
Power conservation is not what I hoped for, of course. Really hoped for Snooze.sleep(). Suggestions very welcome.
--Markk