Continuous ADC+DMA in low power modes (Snooze) and/or different CPU frequencies

Status
Not open for further replies.

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()
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
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
Code:
/**
 *  idle - puts processor into wait mode until next interrupt typically systick.
 */
void SnoozeClass::idle( void ) {
    enter_wait( );
}
But when I follow up enter_wait() and wait() it actually disables the systick:
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]
    }
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.
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);
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.
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);
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.

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
Dynamically switching (from 96MHz):
  • 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
 
Wow you really tore the library apart.

There seems to be a lot of stuff going on here but it seems you are not getting the current reduction you want, you state that you have a lot peripherals attached, can you shut these peripherals down too? I can really only help with what the Teensy is consuming so maybe it would be best to take these issues one at a time.

Lets start with idle mode - I see the issue with disabling the systick so I need to address that, can you tell me what exactly you are trying to do with this? I see you are getting around 13mA at 2MHz. That is really high and makes me think that most of this is things that are attached? Can you tell me more about your setup?
 
Hi duff

thanks for the fast reply.

Wow you really tore the library apart. ... I see you are getting around 13mA at 2MHz. That is really high and makes me think that most of this is things that are attached? Can you tell me more about your setup?

I should clarify first: this is not about the absolute power values. I'm quite confident that your lib provides the correct savings (I'm looking at the "minus" from normal run mode). My post is more about the capabilities and performances (most specifically ADC sampling rate) that are available in the various modes. Acceptable performance i.e. sampling rate determines acceptable power modes for me.

The reference manual states that ADC+DMA is available in VLPW. But the manual does not say "oh, but that's only at quite a reduced and mode-dependent-thus-unstable-on-transition sampling rate". The manual "lured" me into going that path and I put quite a lot of energy into designing all the circuit around the idea of sampling all signals while sleeping and processing them in short bursts later.

So I thought I should share my [partial] disappointment for others to avoid - or possibly (hopefully?) to uncover a mistake I made.

Of course, I probably should have tried that ADC+DMA+Sleep stuff out earlier but then again there were obstacles that looked much more challenging to me like a precision 1.2kV high voltage generator that runs at ~2mA or a transimpedance pulse integrator. I'm a software engineer after all, electronics is merely a two year hobby.

So yes there is roughly ~7mA worth of other stuff going on. There is some potential to save more there, like shutting down some I2C chips, but that seems trivial, so I don't bother now. Again, I'm looking at the "minus" from normal run mode.

Lets start with idle mode - I see the issue with disabling the systick so I need to address that, can you tell me what exactly you are trying to do with this?

I need reliable and reasonably precise timing throughout. The high voltage generator is pwm controlled. It has his own hardware voltage control (pulse skipping) and pulse-to-pulse over-current protection, but frequency and duty cycle are controlled in software for optimal ripple reduction. For that I need reliable millis(). Same goes for the actual measurement the device is doing (gamma spectroscopy). I need to know the precise "exposure time" in order to calculate dose eventually.

So I think the systick needs to remain active through idle() and sleep() i.e. wait() as opposed to deepSleep() i.e. stop(). But hey I'm only a few evenings' worth into this stuff, so maybe I misunderstood ;-)

Other questions:
  • Is there a reason why only the predefined Teensyduino MHZ combos are available dynamically?
  • Why not i.e. 12MHz? (a power of 2 division of F_BUS would allow fast right shift / left shift adjusting PWM flex timer registers on transition)
  • Say I wanted to keep F_BUS at 16MHz and only switch F_CPU, is that possible? (this would keep the PWM flex timer running smoothly)
  • Is there a reason your CPU() does not switch frequencies above 16MHz but instead goes to compiler define F_CPU etc.?
  • Is it at all possible to switch from PEE to PEE i.e. from 24MHz to 96Mhz dynamically? (I must say I can't imagine how you gleaned this info from the cryptic reference manual! Big nod there!!)

Thanks very much!
_Markk
 
Last edited:
So I think the systick needs to remain active through idle() and sleep() i.e. wait() as opposed to deepSleep() i.e. stop(). But hey I'm only a few evenings' worth into this stuff, so maybe I misunderstood ;-)
I see the point but i'm not sure leaving the systick enabled and running would work with sleep (vlpw), it goes into BLPI clock mode where the cpu is running at 2MHz so that transition is going to include quite a bit of jitter in the systick in itself i would assume. Also I'm not sure what clock the systick would use? Not saying you can't but it's going to be tough to get it right?

As for Idle I think you are right, my inkling is it needs to go into wait mode instead vlpw mode. I just wonder if there will be much power reduction? Wait mode is quirky on this silicon for some reason though I haven't looked at it in a while.

Other questions:

  • Is there a reason why only the predefined Teensyduino MHZ combos are available dynamically?
  • Why not i.e. 12MHz? (a power of 2 division of F_BUS would allow fast right shift / left shift adjusting PWM flex timer registers on transition)
Because it's going down a rabbit hole;) Really because its gets to complicated trying to keep track of all the clock settings and such and since the core functions are tied to F_CPU and F_BUS, I don't want to spend a lot of time debugging all the possible combinations people would want rewriting those at different speeds.

  • Say I wanted to keep F_BUS at 16MHz and only switch F_CPU, is that possible? (this would keep the PWM flex timer running smoothly)
ummm, you can't edit those constants, but yes you can switch the cpu speed independant of the bus and mem clocks with many restrictions.

  • Is there a reason your CPU() does not switch frequencies above 16MHz but instead goes to compiler define F_CPU etc.?
yes, because it for reasons I said in quote 2.

  • Is it at all possible to switch from PEE to PEE i.e. from 24MHz to 96Mhz dynamically? (I must say I can't imagine how you gleaned this info from the cryptic reference manual! Big nod there!!)
yes, look at mcg.c, specifcally at mcg_div function just remeber the core is tied to F_CPU and F_BUS. Also look at mk20dx128.c
 
Systick should remain enabled!

I see the point but i'm not sure leaving the systick enabled and running would work with sleep (vlpw), it goes into BLPI clock mode where the cpu is running at 2MHz so that transition is going to include quite a bit of jitter in the systick in itself i would assume. Also I'm not sure what clock the systick would use? Not saying you can't but it's going to be tough to get it right?

As for Idle I think you are right, my inkling is it needs to go into wait mode instead vlpw mode. I just wonder if there will be much power reduction? Wait mode is quirky on this silicon for some reason though I haven't looked at it in a while.

Hi duff

I think the cpu has built-in support for maintaining a stable systick clock. I studied the reference manual which I don't really understand. There is this LPO output always at 1kHz (except in VLLS0) driven by the PMC clock (see page 144). But I don't see how this is connected to the systick. Page 61 seems to say otherwise...

So I tried it out
Code:
// demo: systick is accurate through idle / wait 
// both in normal run mode and VLPR/VLPW

#include <Snooze.h>

#define DEBUG_SERIAL Serial1

void setup() {
  DEBUG_SERIAL.begin(115200);
  while (!DEBUG_SERIAL);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  
}

// alternative Snooze.idle() without the systick disable
void inline Snooze_idleWithSystickStillEnabled() {
  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
}


#define IDLE_ITERATIONS 100000

void loop() {

  // first just delay to get us a baseline power consumption
  {
    DEBUG_SERIAL.println("delay for a while...");
    DEBUG_SERIAL.flush();
    delay(IDLE_ITERATIONS/2);
    DEBUG_SERIAL.println("done burning time "); 
    DEBUG_SERIAL.println();
  }

  // secondly test idle in normal run mode / wait
  { 
    DEBUG_SERIAL.println("now chill for a while...");
    DEBUG_SERIAL.flush(); 
    
    time_t rt = Teensy3Clock.get();
    uint32_t t0 = millis();
    for (int32_t i = 0; i < IDLE_ITERATIONS; i++) {
      Snooze_idleWithSystickStillEnabled();
    }
    uint32_t dt = millis() - t0;
    uint32_t drt = Teensy3Clock.get() - rt;
    DEBUG_SERIAL.print("idled for "); DEBUG_SERIAL.print(0.001*dt, 3); DEBUG_SERIAL.println("s - acording to millis()");
    DEBUG_SERIAL.print("idled for "); DEBUG_SERIAL.print(drt); DEBUG_SERIAL.println("s - acording to RTC");
    DEBUG_SERIAL.print("idle duration avg: "); DEBUG_SERIAL.print(1000.0*drt / IDLE_ITERATIONS, 3); DEBUG_SERIAL.println("ms");
    DEBUG_SERIAL.println();
  }
  
  // thirdly do the the same in VLPR where idle is VLPW
  {
    DEBUG_SERIAL.println("now chill more for a while...");
    DEBUG_SERIAL.flush(); 
    
    time_t rt = Teensy3Clock.get();
    uint32_t t0 = millis();
    REDUCED_CPU_BLOCK() {
      for (int32_t i = 0; i < IDLE_ITERATIONS; i++) {
        Snooze_idleWithSystickStillEnabled();
      }
    }
    uint32_t dt = millis() - t0;
    uint32_t drt = Teensy3Clock.get() - rt;
    DEBUG_SERIAL.print("very low power idled for "); DEBUG_SERIAL.print(0.001*dt, 3); DEBUG_SERIAL.println("s - acording to millis()");
    DEBUG_SERIAL.print("very low power idled for "); DEBUG_SERIAL.print(drt); DEBUG_SERIAL.println("s - acording to RTC");
    DEBUG_SERIAL.print("very low power idle duration avg: "); DEBUG_SERIAL.print(1000.0*drt / IDLE_ITERATIONS, 3); DEBUG_SERIAL.println("ms");
    DEBUG_SERIAL.println();
  }
  
  // finally do the the same with deepSleep()
  {
    DEBUG_SERIAL.println("now for narcolepsy...");
    DEBUG_SERIAL.flush();

    SnoozeBlock config;
    config.setTimer(1);
    time_t rt = Teensy3Clock.get();
    uint32_t t0 = millis();
    for (int32_t i = 0; i < IDLE_ITERATIONS; i++) {
      Snooze.deepSleep(config);
    }
    uint32_t dt = millis() - t0;
    uint32_t drt = Teensy3Clock.get() - rt;
    DEBUG_SERIAL.print("deep slept for "); DEBUG_SERIAL.print(0.001*dt, 3); DEBUG_SERIAL.println("s - acording to millis()");
    DEBUG_SERIAL.print("deep slept for "); DEBUG_SERIAL.print(drt); DEBUG_SERIAL.println("s - acording to RTC");
    DEBUG_SERIAL.print("deep sleep duration avg: "); DEBUG_SERIAL.print(1000.0*drt / IDLE_ITERATIONS, 3); DEBUG_SERIAL.println("ms");
    DEBUG_SERIAL.println();
  }
}
This code demonstrates, that millis() is always [reasonably] accurate through idle() but not through deepSleep() (as expected).

It outputs the following on a bare Teensy 3.1 with just the RTC crystal/battery and Serial1 attached:
elay for a while...
done burning time

now chill for a while...
idled for 100.000s - acording to millis()
idled for 100s - acording to RTC
idle duration avg: 1.000ms

now chill more for a while...
very low power idled for 100.004s - acording to millis()
very low power idled for 99s - acording to RTC
very low power idle duration avg: 0.990ms

now for narcolepsy...
deep slept for 1.200s - acording to millis()
deep slept for 300s - acording to RTC
deep sleep duration avg: 3.000ms
This is obviously just a rough +/-1% comparison, as the RTC has only 1 second resolution and it can be +/-1s. One could increase the IDLE_ITERATIONS to get more (relative) precision. With deepSleep() you lose millis() as expected. The SnoozeBlock 1 ms timer is not accurate, but that's probably due to some LPTMR limitations(?).

These results will also vary with the USB option. The above is with "No USB". If you enable "Serial", you will get two wake-ups / interrupts per millisecond. I guess that's some USB related polling going on there. Consequently you will get the iterations done in half the time in normal run mode. However in VLPR the USB is off, so no change there.
delay for a while...
done burning time

now chill for a while...
idled for 50.107s - acording to millis()
idled for 51s - acording to RTC
idle duration avg: 0.501ms

now chill more for a while...
very low power idled for 100.003s - acording to millis()
very low power idled for 99s - acording to RTC
very low power idle duration avg: 0.990ms
[...]
The power usage (simply measured with Adafruit USB Power Gauge Mini-Kit) is striking (compiled with 96MHz optimized, No USB):
  • minus 11mA just for idle()
  • minus practically all(!) for REDUCED_CPU_BLOCK + idle()
  • same for deepSleep() (meter not accurate enough)
The first two options with perfectly working systick/millis().

Code:
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  39 mA Watts: 0.2
V: 4.6 I:  40 mA Watts: 0.2
V: 4.6 I:  39 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  40 mA Watts: 0.2
V: 4.6 I:  36 mA Watts: 0.2
V: 4.6 I:  38 mA Watts: 0.2
V: 4.6 I:  40 mA Watts: 0.2
V: 4.6 I:  36 mA Watts: 0.2
V: 4.6 I:  39 mA Watts: 0.2
V: 4.6 I:  39 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  39 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  37 mA Watts: 0.2
V: 4.6 I:  40 mA Watts: 0.2
V: 4.6 I:  40 mA Watts: 0.2
V: 4.6 I:  39 mA Watts: 0.2
V: 4.6 I:  38 mA Watts: 0.2
V: 4.6 I:  38 mA Watts: 0.2
V: 4.6 I:  39 mA Watts: 0.2
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  25 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  25 mA Watts: 0.1
V: 4.6 I:  25 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  26 mA Watts: 0.1
V: 4.6 I:  23 mA Watts: 0.1
V: 4.6 I:  26 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  25 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  23 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  25 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  23 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  26 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  23 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  26 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  23 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  25 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  25 mA Watts: 0.1
V: 4.6 I:  23 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:  25 mA Watts: 0.1
V: 4.6 I:  23 mA Watts: 0.1
V: 4.6 I:  25 mA Watts: 0.1
V: 4.6 I:  26 mA Watts: 0.1
V: 4.6 I:  28 mA Watts: 0.1
V: 4.6 I:  27 mA Watts: 0.1
V: 4.6 I:  24 mA Watts: 0.1
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   3 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   3 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   2 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   3 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   9 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   8 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   4 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   3 mA Watts: 0.0
V: 4.6 I:   3 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.7 I:   0 mA Watts: 0.0
V: 4.6 I:   8 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   8 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   3 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   2 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   4 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   4 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   8 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   6 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   6 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   8 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   3 mA Watts: 0.0
V: 4.6 I:   1 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0
V: 4.6 I:   0 mA Watts: 0.0

BTW: With USB enabled you burn a milliamp or two more in normal run mode + idle.

Thanks also for the other answers. I see...

--Markk
 
Last edited:
I think the cpu has built-in support for maintaining a stable systick clock. I studied the reference manual which I don't really understand. There is this LPO output always at 1kHz (except in VLLS0) driven by the PMC clock (see page 144). But I don't see how this is connected to the systick. Page 61 seems to say otherwise...
The systick is not clocked by the LPO but by the core clock - FCLK. The Snooze library actually leaves the systick enabled but disables its interrupts. This is messing up the Idle + millis and such that relies on the systick interrupt. As far as the sleep (vlpw) mode I will leave the systick interrupt disabled for now. Maybe there is way to use the rtc and its clock source? I think we have access to the systick_millis_count variable.

I miss spoke before, Idle does not go into vlpw just regular wait mode but the systick interrupt is turned off so some other exception like you said the usb is popping it out of wait. I'll see what the power savings is by leaving the systick interrupt enabled compared to disabling it. Hope its the same.

REDUCE_CPU_BLOCK which puts the cpu into vlpr mode running @2MHz, I have to think about how to retain the systick count between those transitions from PEE to BLPI(VLPR) and back. The code now resets the systick current value register.

My initial goal was to reduce the power as much possible but thanks for helping with seamless usage between power saving modes and normal usage;)
 
The systick is not clocked by the LPO but by the core clock - FCLK. The Snooze library actually leaves the systick enabled but disables its interrupts. This is messing up the Idle + millis and such that relies on the systick interrupt. As far as the sleep (vlpw) mode I will leave the systick interrupt disabled for now. Maybe there is way to use the rtc and its clock source? I think we have access to the systick_millis_count variable.

Hi duff

again thanks for the quick answers. I think you misunderstood my test results. Idle does NOT "mess up the Idle + millis" inside REDUCE_CPU_BLOCK if (and only if) you leave the systick interrupt enabled. It works!

Although the reference manual states that systick is tied to FCLK (and this cannot be changed in that chip's configuration), the core seems to compensate for changed FCLK dividers automagically for systick. This would actually make sense as "keeping track of time" is hardly an exotic use case.

Well at least I have no other explanation why I don't see see big discrepancies in millis() vs. RTC in my demo code through very long stretches of REDUCE_CPU_BLOCK aka VLPR and idle aka VLPW.

REDUCE_CPU_BLOCK which puts the cpu into vlpr mode running @2MHz, I have to think about how to retain the systick count between those transitions from PEE to BLPI(VLPR) and back. The code now resets the systick current value register.

Are you sure? Then why is my demo code working accurately?

NOTE that the only thing I changed vs. your Snooze is the idle function, leaving systick interrupts enabled in the wait() code.


-Markk
 
When you change cpu frequency you have to change the reload value to reflect the new frequency. I also reset the counter which is probably not the correct way to handle it. You really need a scope to check this which I have just need to find some time!
 
When you change cpu frequency you have to change the reload value to reflect the new frequency. I also reset the counter which is probably not the correct way to handle it. You really need a scope to check this which I have just need to find some time!

Oh I see. Nothing "automagical" there. Thanks.

Just for clarification (more to myself): idle() aka wait() during either normal run mode or VLPR does not go through that code. So it's still valid to say: the systick interrupt should be left enabled and millis() remains accurate. :)

Snooze.sleep() however will suffer from that as does REDUCED_CPU_BLOCK on entering and leaving (negligible, depending on how frequent).

One would need something like this (pseudo code):
Code:
// does NOT work!
uint32_t old_RVR = SYST_RVR;
SYST_RVR = syst;
SYST_CVR = SYST_CVR*(syst + 1)/(old_RVR + 1);
But this does not work as the ARM® v7-M Architecture Reference Manual states (p. B3-27)
The register is write-clear. A software write of any value will clear the register to 0.
:-(

I tried to go around this using something like this:
Code:
static uint32_t systick_error_micros = 0;
void mcg_div( uint8_t cpu_div, uint8_t bus_div, uint8_t mem_div, uint32_t syst ) {
    // config divisors: cpu_div, bus_div, mem_div
    SIM_CLKDIV1 = SIM_CLKDIV1_OUTDIV1( cpu_div ) | SIM_CLKDIV1_OUTDIV2( bus_div ) |	 SIM_CLKDIV1_OUTDIV4( mem_div );
    // determine the microseconds we will lose by resetting the counter
    uint32_t fclk_per_micro = (SYST_RVR+1)/1000;
    systick_error_micros +=  (SYST_RVR - SYST_CVR + (fclk_per_micro>>1))/fclk_per_micro;
    // now do the reset
    SYST_RVR = syst;
    SYST_CVR = 0;
    // whenever the microseconds lost accumulate to one millisecond, adjust millis
    if (systick_error_micros >= 1000) {
       systick_millis_count++;
       systick_error_micros -= 1000;
    }
}

But it didn't work. Surprisingly I found that the systick exception actually occurs too many times. I got two surplus ticks on each sleep()!

blpe_pee(), blpi_pee() and mcg_div() change the divider SYST_RVR. Only mcg_div() resets the counter too.

When I add this to all three:
Code:
// resetting the systick generates one exception too many (?)
  SYST_CVR = 0;
  systick_millis_count--;
then I get reasonably accurate millis() (still -10%). Strange indeed.

Even stranger: Snooze.sleep() with an empty SnoozeBlock takes 2ms! This is always with the systick not disabled in wait().

Extended test sketch:
Code:
// demo: systick is accurate through idle / wait 
// both in normal run mode and VLPR/VLPW

#include <Snooze.h>

#define DEBUG_SERIAL Serial1

void setup() {
  DEBUG_SERIAL.begin(115200);
  while (!DEBUG_SERIAL);

  pinMode(LED_BUILTIN, OUTPUT);
  digitalWrite(LED_BUILTIN, HIGH);
  delay(1000);
  digitalWrite(LED_BUILTIN, LOW);
  DEBUG_SERIAL.println();
  DEBUG_SERIAL.print("CPU is at ");
  DEBUG_SERIAL.print(F_CPU/1000000);
  DEBUG_SERIAL.print("MHz");
  DEBUG_SERIAL.println();
}

// alternative Snooze.idle() without the systick disable
void inline Snooze_idleWithSystickStillEnabled() {
  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
}

// increase this to get more accuracy in comparing the 1-second resolution RTC 
#define IDLE_ITERATIONS 10000

volatile uint32_t sum = 654654;
static void doWork() {
  // do some varying amnount of work but surely less than 1ms even in the slowest mode
  for (uint32_t i = 1 + sum % 193; i > 0; i--) {
    sum += i;
  }
}

void loop() {

  // first just delay to get us a baseline power consumption
  {
    DEBUG_SERIAL.println("delay for a while...");
    DEBUG_SERIAL.flush();
    delay(IDLE_ITERATIONS);
    DEBUG_SERIAL.println("done burning time "); 
    DEBUG_SERIAL.println();
  }

  // secondly test idle in normal run mode / wait
  { 
    DEBUG_SERIAL.println("now idle for a while...");
    DEBUG_SERIAL.flush(); 
    
    time_t rt = Teensy3Clock.get();
    uint32_t t0 = millis();
    for (int32_t i = 0; i < IDLE_ITERATIONS; i++) {
      doWork();
      Snooze_idleWithSystickStillEnabled();
    }
    uint32_t dt = millis() - t0;
    uint32_t drt = Teensy3Clock.get() - rt;
    DEBUG_SERIAL.print("idled for "); DEBUG_SERIAL.print(0.001*dt, 3); DEBUG_SERIAL.println("s - acording to millis()");
    DEBUG_SERIAL.print("idled for "); DEBUG_SERIAL.print(drt); DEBUG_SERIAL.println("s - acording to RTC");
    DEBUG_SERIAL.print("idle duration avg: "); DEBUG_SERIAL.print(1000.0*drt / IDLE_ITERATIONS, 3); DEBUG_SERIAL.println("ms");
    DEBUG_SERIAL.println();
  }
  
  // thirdly do the the same in VLPR where idle is VLPW
  {
    DEBUG_SERIAL.println("now chill for a while...");
    DEBUG_SERIAL.flush(); 
    
    time_t rt = Teensy3Clock.get();
    uint32_t t0 = millis();
    REDUCED_CPU_BLOCK() {
      for (int32_t i = 0; i < IDLE_ITERATIONS; i++) {
        doWork();
        Snooze_idleWithSystickStillEnabled();
      }
    }
    uint32_t dt = millis() - t0;
    uint32_t drt = Teensy3Clock.get() - rt;
    DEBUG_SERIAL.print("very low power idled for "); DEBUG_SERIAL.print(0.001*dt, 3); DEBUG_SERIAL.println("s - acording to millis()");
    DEBUG_SERIAL.print("very low power idled for "); DEBUG_SERIAL.print(drt); DEBUG_SERIAL.println("s - acording to RTC");
    DEBUG_SERIAL.print("very low power idle duration avg: "); DEBUG_SERIAL.print(1000.0*drt / IDLE_ITERATIONS, 3); DEBUG_SERIAL.println("ms");
    DEBUG_SERIAL.println();
  }
  
  // then do the the same with sleep()
  {
    DEBUG_SERIAL.println("now for some napping...");
    DEBUG_SERIAL.flush();

    SnoozeBlock config; // empty block - should still be woken by systick
    time_t rt = Teensy3Clock.get();
    uint32_t t0 = millis();
    for (int32_t i = 0; i < IDLE_ITERATIONS; i++) {
      doWork();
      Snooze.sleep(config);
    }
    uint32_t dt = millis() - t0;
    uint32_t drt = Teensy3Clock.get() - rt;
    DEBUG_SERIAL.print("slept for "); DEBUG_SERIAL.print(0.001*dt, 3); DEBUG_SERIAL.println("s - acording to millis()");
    DEBUG_SERIAL.print("slept for "); DEBUG_SERIAL.print(drt); DEBUG_SERIAL.println("s - acording to RTC");
    DEBUG_SERIAL.print("sleep duration avg: "); DEBUG_SERIAL.print(1000.0*drt / IDLE_ITERATIONS, 3); DEBUG_SERIAL.println("ms");
    DEBUG_SERIAL.println();
  }
  // finally do the the same with deepSleep()
  {
    DEBUG_SERIAL.println("now for narcolepsy...");
    DEBUG_SERIAL.flush();

    SnoozeBlock config;
    config.setTimer(1);
    time_t rt = Teensy3Clock.get();
    uint32_t t0 = millis();
    for (int32_t i = 0; i < IDLE_ITERATIONS; i++) {
      doWork();
      Snooze.deepSleep(config);
    }
    uint32_t dt = millis() - t0;
    uint32_t drt = Teensy3Clock.get() - rt;
    DEBUG_SERIAL.print("deep slept for "); DEBUG_SERIAL.print(0.001*dt, 3); DEBUG_SERIAL.println("s - acording to millis()");
    DEBUG_SERIAL.print("deep slept for "); DEBUG_SERIAL.print(drt); DEBUG_SERIAL.println("s - acording to RTC");
    DEBUG_SERIAL.print("deep sleep duration avg: "); DEBUG_SERIAL.print(1000.0*drt / IDLE_ITERATIONS, 3); DEBUG_SERIAL.println("ms");
    DEBUG_SERIAL.println();
  }
}

Ouputs:
CPU is at 96MHz
delay for a while...
done burning time

now idle for a while...
idled for 100.000s - acording to millis()
idled for 100s - acording to RTC
idle duration avg: 1.000ms

now chill for a while...
very low power idled for 100.001s - acording to millis()
very low power idled for 100s - acording to RTC
very low power idle duration avg: 1.000ms

now for some napping...
slept for 200.000s - acording to millis()
slept for 221s - acording to RTC
sleep duration avg: 2.210ms

now for narcolepsy...
deep slept for 2.534s - acording to millis()
deep slept for 300s - acording to RTC
deep sleep duration avg: 3.000ms

-Markk
 
I can report that there is definitely transition issues with the systick when going from F_CPU to 2MHz and back using the CPU_BLOCK. Not that it will freeze your teensy or such but the seamless transition is not smooth. I'll update this thread with some pics from my scope and I'm currently working on a solution. Not sure if that transition will ever be perfect but the way I'm currently dealing with reseting the systick registers is not right! The problem is when changing the cpu clock source so I need to either disable interrupts during the PEE_BLPI - BLPI_PEE transition or what I'm working on now is disabling the systick interrupt during those transitions and waiting for the systick to count to zero and then change the Reload Value.

Any how I'll fix this "i hope :)" and fix Idle, which should be pretty straight forward.
 
I can report that there is definitely transition issues with the systick when going from F_CPU to 2MHz .... what I'm working on now is disabling the systick interrupt during those transitions and waiting for the systick to count to zero and then change the Reload Value.

Hi duff

this is quite fascinating. Looking forward to the scope pics. Many thanks!

Just to be clear, from my perspective it's most important to fix idle() aka wait(). Personally I can live with some transition jitter in REDUCED_CPU_BLOCK and sleep() because I use those with low frequency.

What's more I just found out the following: if you have the RTC, then you can forget millis() altogether:
Code:
// full resolution RTC ticks in 64 bits with 2^-15s = ~30.5us resolution
int64_t inline ticksRTC() {
  return ((int64_t)RTC_TSR << 15) | RTC_TPR;
}

// after measuring time using ticksRTC() you can convert differences etc. to milliseconds
// NOTE this is more accurate than using the millisRTC() replacement below
int64_t convertRTCTicksToMillis(int64_t rt) {
  return (rt * 1000 + (1 << 14)) >> 15;
}

// after measuring time using ticksRTC() you can convert differences etc. to microseconds
int64_t convertRTCTicksToMicros(int64_t rt) {
  return (rt * 1000000 + (1 << 14)) >> 15;
}

// millis() replacement based on the RTC
// NOTE this will only return the 32 LSBs for compatibility with Arduino millis()
// a sketch should always be ready for rollover which can occur any time with the RTC
uint32_t inline millisRTC() {
  return (uint32_t)((ticksRTC() * 1000) >> 15);
}

// micros() replacement based on the RTC in ~33us coarseness
// NOTE this will only return the 32 LSBs for compatibility with Arduino micros()
// a sketch should always be ready for rollover which can occur any time with the RTC
uint32_t inline microsRTC() {
  return (uint32_t)((ticksRTC() * 1000000) >> 15);
}

// floating point seconds since 1970-01-01 00:00 UTC based on the RTC with ~0.0000305s = ~30.5us resolution 
double inline secondsRTC() {
  return ticksRTC()*(1.0L / (1 << 15));
}

For me this solves all the problems with systick + power modes. Attached is the newest version of my testing sketch.
View attachment snoozeAndWait.ino

But for those without the crystal (and battery?) this does not help :-(

Still, the first step should be documentation: it would be a great benefit for users to be warned that millis(), micros(), delay() etc. become inaccurate or defunct with some Snooze functionality. So adding a quick comment to each of the methods would be a great first step.

The same goes for the most common clocked stuff like HardwareSerial, analogWrite() (if a specific frequency is relevant), etc. Users should be advised that these don't work as expected inside REDUCED_CPU_BLOCK and through sleep().

At the same time one could document that other stuff works, albeit slower:
  • analogRead() works although the ADC will make awfully long conversions.
  • I2C is no problem as it sends its clock out. I use it heavily in my code through all kinds of mixed CPU frequencies, no problem.
  • Haven't tried SPI but I think the same goes there.

Once I'm finally getting around setting up and learning the github basics I could perhaps help more directly.

Thanks again, for all your valuable efforts!

--Markk
 

Attachments

  • snoozeAndWait.ino
    6.9 KB · Views: 102
Last edited:
This shows the bug with the systick when using CPU_BLOCK. In the pic below the traces are:

  1. "CPU" - show when the teensy goes into and out of the CPU_BLOCK, | HIGH = 2MHZ, LOW = F_CPU.
  2. "SYST" - monitors the systick isr, i just toggle a pin every interrupt which should happen every millisecond.
  3. "MCG" - time it takes to change clocks. ->PEE_BLPI i.e. F_CPU MHz to 2 MHz in mcg.h.
DS1Z_QuickPrint5.png

So as you can see the systick isr "SYST" firing all willy nilly during the transition.

I have a fix for this now shown below:
DS1Z_QuickPrint7.png

I'm using the LPTMR to sync the systick but now there is a small delay during this transition but it should keep the millis and such that rely on the systick in sync i hope.

Idle is fine when I don't disable systick isr.

Now I just need to see if these fixes have a impact on the power savings than before.
 
Status
Not open for further replies.
Back
Top