Ghetto CPU busy-meter

Even in the embedded world, you really want to know how busy your CPU is.

Many, or even most, embedded programmers have no idea how ‘busy’ their CPU is. By ‘busy’, I mean what proportion of the available CPU cycles are being spent on doing useful work in the name of the target application, and how much time its idle waiting for something to do. On your desktop PC you’ve got some sort of CPU busy meter available through the Task Manager, Activity Monitor or System Monitor depending on which OS religion you follow. Wouldn’t it be useful to have something similar on that embedded device you’re working on? Far too many people think you need an OS for that kind of info, but it turns out it’s trivially easy to do…and it should be table stakes for getting started on a new design.

In most embedded design patterns, you’ve got some sort of loop where the application sits, waiting for something to happen. That might be a timer expiring, an interrupt arriving or some other event. The general pattern looks something like this;

while (1)
{
    flagSet = flag_get();
    if (!flagSet)
    {
        __WFI();
    }
    else
    {
         <Do things>
    }
}

In this specific example the flags are set via Interrupt routines, and the __WFI instruction is basically “Wait for interrupt”. You can see how this works out….we sit waiting for an interrupt in the WFI (which is generally a low power mode), one arrives, sets a flag or two, and then any outstanding flags are processed before returning to the __WFI for whole thing to happen again.

Well, this will be easy … all we need to do is to provide some indication to the outside world of when we’re busy and when we’re not at the right places in the system. Something like;

while (1)
{
    flagSet = flag_get();
    if (!flagSet)
    {
        <Indicate Idle>
        __WFI();
        <Indicate Not Idle>
    }
    else
    {
         <Do things>
    }
}

That will work. Your split between ‘busy’ and ‘idle’ might not be quite so clean, but I don’t think I’ve ever seen a system where it’s not possible to differentiate between the two cases.

There is one issue, which is that the interrupt routine will be called before the <Indicate Not Idle> gets processed, but interrupt routines should be short in comparison to the total amount of work done…and I’ll give you a fix for that later anyway.

The easiest way to <Indicate Idle> and <Indicate Not Idle> is just by setting or clearing the state of a spare pin on your device. That also means you’ve got to set up the pin to be an output first of all…so our code now looks like;

<Set up output pin>
while (1)
{
    flagSet = flag_get();
    if (!flagSet)
    {
        <Indicate Idle>
        __WFI();
        <Indicate Not Idle>
    }
    else
    {
         <Do things>
    }
}

Great, we can now see, approximately, how busy our CPU is just by monitoring the pin. The code for each of these routines will vary according to the particular CPU you’re using, but here’s a typical example for a STM32F103 implemented using macros and CMSIS for port pin B12;

#define SETUP_BUSY GPIOB->CRH=((GPIOB->CRH)&0xFFF0FFFF)|0x30000
#define AM_IDLE    GPIOB->BRR=(1<<12)
#define AM_BUSY    GPIOB->BSRR=(1<<12)

These magic incantations will vary from CPU to CPU, but the principle holds fine. You might be even luckier and on your CPU have a pin that changes state when it’s in WFI mode without any of this AM_IDLE/AM_BUSY mucking about – typically that would be a clock that only runs while the CPU is active for example.

This works great if you’ve got a scope to see the output on, but I like something a bit more low-tech that can hang off the port so we’ve got a permanent indication of just how busy the system is.

A LED is a good first step – the CPU will be switching between busy and idle at quite a speed, faster than the human eye can follow, so the brightness is proportional to how busy the system is. If you’re a proper bodger you don’t even need a series resistor for the LED…the resistor is only there to limit the current through it and since the maximum current out of the CPU pin will generally be considerably less than the max the LED can cope with, you’re golden…..but do check the specs for your chip and your LEDs!

Unfortunately, the human eye is rather non-linear, so it won’t easily spot the difference between a 60% loaded CPU and an 80% loaded one, so something slightly more sophisticated is in order. If we put an low pass RC filter (or integrator, or smoother…they’re all the same thing, it just depends how you want to think about it) on the output pin then you’ll get a DC voltage out which is proportional to how busy the CPU is. Let’s go one step further and put a potential divider on the front to set the maximum input to the filter to be 1V. For a 3v3 system, something like this;Hey, I’ve now got a meter that reads from idle (0.00V) to fully loaded (1.00V) directly on the display. Meters with an analogue bar on the display are particularly suitable for this job. Increase the value of the capacitor if you need to reduce jitter in the reading, at the expense of making the response to load changes slower .

Just how cool is that? Of course, the practical implementation doesn’t look at pretty as the circuit diagram, but it does the job;

Remember how we said that one of the limitations of this technique was that the Interrupt routine got called before the Busy Indicator got set? If that is a problem (because, for example, you’re worried that you’re spending a lot of time in interrupts) just put another AM_BUSY call at the start of your ISR. Problem solved. Go to town, put one at the start of all of your ISRs.

Similarly, there’s no reason you have to restrict this technique to just telling you when you’re busy….if you bracket a specific routine in your code with the AM_BUSY tags you can read directly off your meter what percentage of your CPU time is being spent in it. You can even have multiple pins tagging different bits of code… knock yourself out.

At the other end of this post, you didn’t know how busy your CPU got. Now, you’ve got no excuses.

Join the discussion here.