[SOLVED] How to do MCU utilization measurements

@donstavely, this time code is super useful! I’m porting code from Arduino, and looking for the libdaisy replacement for millis(); nice to have some working code to start from :slight_smile: I did some cut-and-paste just to test, and wound up with this in Main:

   TimerHandle timer;
    timer.Init (TimerHandle::Config {
             TimerHandle::Config::Peripheral::TIM_2,
             TimerHandle::Config::CounterDir::UP});
    timer.Start ();

    for(;;)
    {
        uint32_t    millis = timer.GetMs();
        uint32_t    tick = timer.GetTick();
        seed.PrintLine("millis: %d, tick: %d", millis, tick);
        System::Delay(500);
    }

The output is not what I expected:

millis: 60, tick: 329
millis: 50099951, tick: 100200109
millis: 100199956, tick: 200400115
millis: 150299949, tick: 300600103
millis: 200399952, tick: 400800111

I thought I was going to see time in milliseconds - these numbers are 100K times that. As a result, I’m seeing rollover very quickly; this will be hard to work with. Am I missing something here? Oh, and can someone tell me where the value of a “tick” is defined? Thanks!

1 Like

You should probably use timer.GetFreq() to compute the time in s or ms from the timer.GetTick(). I don’t know why the GetMs() is implemented like this in libDaisy/src/per/tim.cpp (see below), but it doesn’t return what you could expect from its name or description.

uint32_t TimerHandle::Impl::GetMs()
{
    return GetTick() / (GetFreq() / 100000000);
}

I think 1000 would be a more suitable divisor for the clock frequency. Or am I missing something?

1 Like

Thanks for the feedback, @Firesledge! Here’s an updated code segment and output:

for(;;)
    {
        uint32_t    millis = timer.GetMs();
        uint32_t    tick = timer.GetTick();
        uint32_t    freq = timer.GetFreq();
        float       intervalSec = (float)tick / (float)freq;
        seed.PrintLine("millis: %d, tick: %d. freq: %d", 
            millis, tick, freq);
        seed.PrintLine("time in seconds: %f", intervalSec);
        System::Delay(500);
    }
millis: 6816314, tick: 13632831. freq: 200000000
time in seconds: 0.068164
millis: 56916312, tick: 113832825. freq: 200000000
time in seconds: 0.569164
millis: 107016317, tick: 214032837. freq: 200000000
time in seconds: 1.070164
millis: 157116317, tick: 314232837. freq: 200000000
time in seconds: 1.571164

Tick/freq seems to give time in secs (I’m guessing that the interval of 501 ms that I’m seeing between prints is 500ms for the delay, and 1 ms for the timer/print calls) Unfortunately ticks rolls over at about 21 secs. millis looks to be half of ticks. I dunno. Hopefully, one of the Electro-Smith folks will clarify this on Monday. In the meantime, I can cobble together something from this to proceed. Thanks again!

1 Like

The timer ticks are stored as a 32-bit unsigned integer. So with a 200 MHz clock, the tick counter reaches 2^32 after 21 s and overflows. If you only need to measure short time periods, just subtracting two consecutive values is enough, overflow will be compensated automatically.

If you need the absolute time, you should run your own 64-bit accumulator and add the measured periods. This means you should keep checking the time within 21 s intervals. Or use a second, much slower timer to evaluate from how many 2^32 steps you should fix the newly read value.

Just browsing the STM32H750 Reference Manual - perhaps, if one is using an STLINK or similar it may be possible to use the JTAG DAP to access some of the trace infrastructure. Polling the ITM or ETM would give useful information where a program is spending its time. Of course, I haven’t tried using any of it, so I don’t have a firm idea of what’s possible. YMMV etc.

Cheers

Yep! And fortunately, I’m measuring sequencer steps, so a few seconds will be “gracious plenty” as we say here! I had been thinking about using deltas, but my intuition led me astray; it is, indeed, as you say - the math works out and overflow is compensated automatically! For anyone else who might want to just grab code, here’s my final test code with output. Thanks for “talking me through it” @Firesledge

    TimerHandle timer;
    timer.Init(TimerHandle::Config{TimerHandle::Config::Peripheral::TIM_2,
                                   TimerHandle::Config::CounterDir::UP});
    timer.Start();

    uint32_t freq = timer.GetFreq();
    uint32_t lastTime = timer.GetTick();

    for(;;)
    {
        uint32_t newTick = timer.GetTick();
        float intervalMsec = 1000. * ((float)(newTick - lastTime) / (float)freq);
        lastTime          = newTick;
        seed.PrintLine("tick: %d, delta time in ms: %f", newTick, intervalMsec);
        System::Delay(500);
    }
tick: 1903799931, delta time in ms: 501.000000
tick: 2003999931, delta time in ms: 501.000000
tick: 2104199935, delta time in ms: 501.000000
tick: -2090567361, delta time in ms: 501.000000
tick: -1990367365, delta time in ms: 501.000000
tick: -1890167367, delta time in ms: 501.000000
1 Like

I just saw this topic and thought that this PR might be interesting for you:

1 Like

I apologize if this is a stupid question but is there some special cause to get the counters to work? I was using DaisyDuino but it doesn’t yet support the timers so opted to try going with the C++/Code environment.

Trouble is, for this simple code:

#include "daisy_patch_sm.h"
#include "daisysp.h"

using namespace daisy;
using namespace patch_sm;
using namespace daisysp;

DaisyPatchSM hw;
TimerHandle timer;

uint32_t timer_freq = 0;

int main(void)
{
    timer.Init(TimerHandle::Config{TimerHandle::Config::Peripheral::TIM_2,
                                   TimerHandle::Config::CounterDir::UP});
    timer.Start();

	while(1) {}
}

I’m getting this error:

gave.cpp: In function ‘int main()’:
gave.cpp:16:71: error: no matching function for call to ‘daisy::TimerHandle::Config::Config()’
16 | TimerHandle::Config::CounterDir::UP});

Any ideas with what’s going on? Of note, trying to setup an interrupt callback was also giving me similar troubles.

EDIT: I managed to seemingly get it working with this chunk of code:

	TimerHandle::Config config = TimerHandle::Config();
	config.periph = TimerHandle::Config::Peripheral::TIM_5;
	config.dir = TimerHandle::Config::CounterDir::UP;
	config.enable_irq = 1;

    timer.Init (config);
	timer.SetCallback(WaveStepCallback);
    timer.Start ();
1 Like

For anyone looking for ways to measure CPU utilization in 2023, libDaisy now has a CpuLoadMeter class.

https://electro-smith.github.io/libDaisy/classdaisy_1_1_cpu_load_meter.html#aef64ab306bccdb55ad03828583654915

2 Likes