2

I'm currently auditing an online edX course about embedded systems, and learning about using the SysTick timer to calculate elapsed time.

Picture of logic I'm referring to

Code of logic I'm referring to

However, there is one point that is confusing me. I understand the idea of subtracting "now" from "last" to get the elapsed time. However, what do you do when the "now" rolls over when the SysTick timer hits 0 and gets reloaded, but the "last" is the value from BEFORE the SysTick timer rolled over (so "now" is > than "last", when normally it should be smaller)? The value is being stored in an unsigned long, so does it break the program? Or does this just never happen, and if so why? I would appreciate any help on clearing this up!

I looked at the only other link I could find that was similar to my question here: How to deal with a wrapping counter in embedded C but I didn't find a clear answer to my question from that.

Community
  • 1
  • 1
Yuerno
  • 751
  • 1
  • 8
  • 27
  • Why don't you simply try it out with some values? `unsigned` integer wraparound is very well defined and even if the counter wraps, the *signed integer difference* to the previous value will still be correct (as long as it is small enough, i.e. half your `int` range) – tofro Jan 21 '17 at 13:48
  • [Using modular arithmetic to avoid timing overflow problems](https://blogs.msdn.microsoft.com/oldnewthing/20050531-22/?p=35493) – kkrambo Jan 21 '17 at 14:42
  • @tofro I will give it a shot, thanks! I just wanted to clear up what was happening logically beforehand, since I figured implementing the code without fully understanding the logic would be pointless. – Yuerno Jan 21 '17 at 14:44

3 Answers3

2

Take a 3 bit counter, it all scales up the same to 24 or 32 (I think the systick timers are 24.

So counting up or down doesnt matter, you just need to adjust the operands correctly. So say counting down 7,6,5,4 7 - 4 is 3 counts. but what about 1,0,7,6 in binary 1 - 6 = 001 - 110

     1
   001
+  001
=======

and solve it

   011
   001
+  001
=======
   011     

and clipping to 3 bits that is the right answer.

Should be simple to write a program for a 4 or 5 bit counter try random sized counts. Or use fixed sized counts allowing for roll over using prime numbers

#include <stdio.h>
#define BITMASK 0xF
int main ( void )
{
    unsigned int now;
    unsigned int beg;
    unsigned int end;
    unsigned int ra;
    unsigned int rb;
    now=0;
    for(ra=0;ra<100000;ra++)
    {
        beg=now;
        for(rb=0;rb<13;rb++) now--;
        end=now;
        if(((beg-now)&BITMASK)!=13)
        {
            printf("Error 0x%X 0x%X\n",beg,end);
        }
    }
    printf("Done.\n");
    return(1);
}

This ONLY works if it is a counter that rolls over from all zeros to all ones or all ones to all zeros depending on the direction. I would hope it obvious if you set a hypothetical 3 bit timer to start at 5 and it rolled over from zero back to that 5 set point then three steps could be 1,0,5,4 and 1-4 is not 3

    111
    001
+   011
=========
    101

Another way to think of this is yet another feature of twos complement, near the wraparound point

101  -3
110  -2
111  -1
000  +0
001  +1
010  +2

It is just like the number line we used in grade school. From the bit pattern 110 to 010 is 4 steps +2 - -2 = 4 or 4 units on the number line. Basically using the beauty of twos complement and we know that addition and subtraction in binary thanks to twos complement is not unsigned or signed specific the same bit patterns result in the same bit patterns. It is just how we interpret them. So we are cheating a little, taking signed math and interpreting the result as unsigned.

old_timer
  • 69,149
  • 8
  • 89
  • 168
  • you have to know the size of your counter, so if you have systick which is a 24 bit counter you cannot just use 32 bit math, the result will be wrong, you have to mask the result with a 24 bit mask. You could use a smaller mask a 20 bit or 16 or whatever so long as within those number of bits the counter is rolling over from all zeros to all ones or all ones to all zeros. for counter/timers like these that generally means a down counter you reload to all ones an an up counter to all zeros (on the rollover) – old_timer Jan 21 '17 at 16:49
  • obviously it does not work if the counter rolls twice so you either have to be watching for it to roll, and adding those rollovers into the result or insure that you sample fast enough to never let it pass your start count or even get too close. take the example program above and instead of the loop running to 13 let it run to 17 or 19 and compare the difference with 17 or 19. should be pretty obvious with a mask of 15 you can never get there. – old_timer Jan 21 '17 at 16:51
  • This is very helpful, thanks so much for working it out with examples! Question about the bit-mask: If you're doing subtraction operations, wouldn't the result end up being 24 bits all the time anyways, so the extra bits past 24 would remain 0 no matter what? How does the 32 bit math affect the fact that you're only using 24 bits? – Yuerno Jan 21 '17 at 17:28
  • in logic subtraction uses an adder, we know from programming class that to "take the twos complement" you invert and add one, so you invert the second parameter and to add one you invert or otherwise bring a one in as the carry in so subtracting 110 means invert and add one so 001 + 1 or 1111010 is a minus six. so 1 - 6 is 001 - 110 = 001 + 001 + 1 – old_timer Jan 21 '17 at 20:39
  • Ahhh awesome, thank you! I accidentally deleted my original question regarding this, so in case someone reads this in the future and is confused, my original question was where the 1 + 001 + 001 came from. – Yuerno Jan 21 '17 at 20:44
1

old_timer had a fantastic answer, and after doing some more research of my own, I found another great answer on Stack Overflow which I figured I would link as well: How to subtract two unsigned ints with wrap around or overflow

Community
  • 1
  • 1
Yuerno
  • 751
  • 1
  • 8
  • 27
  • Yes! The answer is very simple, all you have to do is subtract from the rolling counter to get elapsed time, the subtraction is wrap around robust. A wrapped timer contains the value of 0x05, while the past time is 0xF5, if you evaluate 0x05 - 0xF0 the answer is still 0x10, or 16. 16 unit of time elapsed, wrap agnostic. – Felipe Lavratti Jan 24 '17 at 18:19
1

There is a simple solution to calculating the difference between two integers that are not the same size as any standard integer types. You simply shift the integer so that its MSB is in the MSB position of a standard type, perform the arithmetic, then optionally shift the result back so that it is in the same units as the original values.

In this case systick is a 24 bit counter so:

uint32_t start_time = getSystick() ;

// do something

uint32_t end_time = getSystick() ;

uint32_t elapsed_time_in_systicks = ((start_time << 8) - (end_time << 8)) >> 8 ;

Note the order of the operands - systick counts down. This assumes that you are setting the maximum reload value of 0xffffff, and that systick does not wrap more than once.

If the time intervals are such that the counter may wrap more than once, then you might not need such high resolution, and could perhaps use a lower reload value and an interrupt handler that increments a counter, and use that lower resolution counter for time measurement.

Alternatively you might use a uint8_t counter incremented on the reload interrupt. That counter could then be combined with the 24 bits of systick to create a genuine 32 bit counter. Some care is necessary however to ensure consistency - so that you do not combine a systick just before it reloaded with a reload counter from just after (or vice versa). This can be done with as follows :

uint32_t getSystick32()
{
    uint8_t msb ;
    uint32_t systick ;
    do
    {
       msb = getSystickReloadCounter() ;
       systick = getSystick() ;

    } while( msb != systick_reload_counter ) ;

    return msb << 24 | systick ;
}
Clifford
  • 88,407
  • 13
  • 85
  • 165