Thursday, 16 March 2017

microcontroller - High resolution system timer in STM32


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 (preemption) 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.



Answer



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.



No comments:

Post a Comment

arduino - Can I use TI&#39;s cc2541 BLE as micro controller to perform operations/ processing instead of ATmega328P AU to save cost?

I am using arduino pro mini (which contains Atmega328p AU ) along with cc2541(HM-10) to process and transfer data over BLE to smartphone. I...