Take the 2-minute tour ×
Electrical Engineering Stack Exchange is a question and answer site for electronics and electrical engineering professionals, students, and enthusiasts. It's 100% free, no registration required.

I have some software running on an STM32F103 (I'd also like to be able to use the F4) where I'd like to be able to 'timestamp' events (such as interrupts on external pins) to the nearest microsecond (if not better).

As the software will be running for some time I want to use a 64 bit timer, and to me it made sense to use the built in SysTick timer. I have created SysTickMajor (a 64 bit counter) which increments every time SysTick overflows, and a function getSystemTime which combines this counter and the current value of SysTick to get me an accurate 64 bit time:

#define SYSTICK_RANGE 0x1000000 // 24 bit
volatile long long SysTickMajor = SYSTICK_RANGE;

voif onInit(void) {
  SysTick_CLKSourceConfig(SysTick_CLKSource_HCLK_Div8);
  SysTick_Config(SYSTICK_RANGE-1);
  /* Super priority for SysTick - is done over absolutely everything. */
  NVIC_InitStructure.NVIC_IRQChannel = SysTick_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);
}

void SysTick_Handler(void) {
  SysTickMajor+=SYSTICK_RANGE;
}

long long getSystemTime() {
  long long t1 = SysTickMajor;
  long long time = (long long)SysTick->VAL;
  long long t2 = SysTickMajor;
  // times are different and systick has rolled over while reading
  if (t1!=t2 && time > (SYSTICK_RANGE>>1)) 
    return t2 - time;
  return t1-time;
}

The only problem is this doesn't appear to be 100% accurate - for instance, if I do the following:

bool toggle = false;
while (true) {
  toggle = !toggle;
  LED1.write(toggle);
  long long t = getSystemTime()() + 1000000;
  while (getSystemTime() < t);
}

I won't get a good square wave - occasionally there will be glitches (even though the SysTick interrupt is finished very quickly), because at points the time returned by getSystemTime is completely wrong.


UPDATE: When this happens (I am recording the time value on an interrupt triggered by an external event) I dump the last 3 values to serial. Repeatably I get things like:

>0x278-E2281A 
 0x278-E9999A 
 0x278-0001E5

>0x5BE-F11F51
 0x5BE-F89038
 0x5BE-0000FB

I've stuck dashes in to depict which bits come from SysTick.

Now just to avoid doubt, I've used the code from starblue below. I've also changed it to use a 32 bit value for SysTickMajor, and I've ANDed out SysTick->VAL, just in case.

It appears that the SysTick_Handler is not preempting the other interrupt!

Now I've checked over this and I did have the Preemption priorities wrong previously, but now I set NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4); then set up the interrupts - SysTick preemption priority 0, and the other interrupt is 7.

Is there something else I have to do to get preemption to work?


UPDATE2: I created a tiny test case for this exact issue (SysTick not preempting other interrupts), and have put it here:

STM32 Interrupt Priority Problems


So what's wrong with the code above? Is there a better way to get a good high resolution timer? It seems like something obvious that people would want to do but I'm struggling to find examples.

share|improve this question
 
Maybe you will try DMA? Increase some variable on timer overflow. –  Eddy_Em Jul 16 '13 at 18:50
 
Do you have an example of incrementing a variable with DMA? Just a note: There should really be interrupt disable/enable around each mention of SysTickMajor in getSystemTime - however even this doesn't stop the occasional glitch –  Gordon Williams Jul 16 '13 at 19:23
 
Your main mistake is that you run while (getSystemTime() < t); : this function longs for some time, that's why you won't be able to get time perfectly. Work on interrupts or make atomic operation of time calculation (SysTick interrupt would modify some long long variable by DMA or other mechanism, and last your line may be like while(SystemTime - SystemTime0 < 1000000) –  Eddy_Em Jul 17 '13 at 4:54
 
It's not like it's a little bit off - occasionally the pulse could be anywhere between 0 and the length it was supposed to be. I have now modified the question after I tried some other things. However if you do have a solution which involves DMA (I'm not sure how it can be done?) I'd be very interested. –  Gordon Williams Jul 17 '13 at 18:31
add comment

2 Answers

Your code has two problems:

  • You don't know whether the interrupt that increased SysTickMajor happened before or after you read the timer value into time.

  • Operations on 64-bit values are not atomic on a 32-bit system. So the value for SysTickMajor could be updated by the interrupt between reading the two 32-bit words.

I would do it as follows:

major0 = SysTickMajor;
minor1 = SysTick->VAL;
major1 = SysTickMajor;

minor2 = SysTick->VAL;
major2 = SysTickMajor;

if ( major0 == major1 )
{
    time = major1 + minor1;
}
else
{
    time = major2 + minor2;
}

Here you assume that if SysTickMajor changed during the first block of assignments it won't change again in the second block. (This assumption could be wrong if this code is interrupted during this routine and can't run for a long time.)

See also this question on Stackoverflow.

share|improve this answer
 
Thanks - I've now started using your code (see modified question) but I still have the problem! I've even swapped the 64 bit values down to 32 bit in order to be sure that wasn't an issue. It now looks like the SysTick interrupt is not being called when SysTick overflows, rather that there being a synchronization issue. –  Gordon Williams Jul 17 '13 at 18:29
 
I prefer to read low/high/low and repeat if needed. Among other things, that approach will work correctly even if "logical carry propagation" is slow. Suppose, for example, that when by the time the processor reads the data from SysTick->VAL it has already started executing the instruction to read SysTickMajor into major1. The minor1 value could reflect a timer that has wrapped, even though major1 would not. Using low/high/low, such things won't happen unless the relative timing difference between high- and low-word actions exceeds the time between the two low-word reads. –  supercat Jul 17 '13 at 19:12
add comment
up vote 0 down vote accepted

I just found the answer from a very helpful poster on the STM32 forum:

The following isn't correct. SysTick is a 'System Handler', and as such the priority isn't set in this way at all:

  NVIC_InitStructure.NVIC_IRQChannel = SysTick_IRQn;
  NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; // Highest priority
  NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
  NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
  NVIC_Init(&NVIC_InitStructure);

It's actually set with:

  NVIC_SetPriority(SysTick_IRQn, 0);

Calling that code instead solves the problem!

So my version of getSystemTime could have had a problem, and starblue's method is nicer than mine - however the actual cause of the problems was the above.

Just thought I'd add one other possible improvement to getSystemTime that I tried as well:

do {
 major0 = SysTickMajor;
 minor = SysTick->VAL;
 major1 = SysTickMajor;
while (major0 != major1);
return major0 - minor;

This will effectively negate the problem where you could (somehow) end up with the SysTick being implemented twice in very quick succession.

share|improve this answer
add comment

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.