Rpi-2040 Getting Accurate Timers and SYS_TICK

In this quick guide we go over getting the Raspberry pi Pico as accurate as we can by accessing its own SYS_TICK timer..

Rpi-2040 Getting Accurate Timers and SYS_TICK

Music of the Day: Mr. Robot Hacking Mix

It was very interesting to see massive skew on a sleep_us(1) call. No really I will show you shortly..   Why - because it is promoted as an accurate timer on many many websites.  Sure for kids projects it's handy to blink some LEDS - but when we start talking chip-to-chip pin driving clock cycles matter, and it looks to fail poorly. Caveat that this thing for its $$$ is just hitting it out of the park!

Prequel: Getting Your Enviroment to Work.

If you need to understand how to setup a full development environment (with step-debuging and a CMSIS-DAP probe)

CLion Support for Step-Debugging w/ Raspberry Pi-Pico
In this guide we get stepper-debugging to work for the Raspberry Pi Pico with a $25 CMSIS probe!

Timer Basics:

There are are two main timers  in the pico-sdk (C/C++) which supply delays:

  • A micro-second timer
sleep_us(10);
  • And a milli-second timer
sleep_ms(10);

They are handy for human time, but when we are dealing with high speed pulsing - it was found they quickly just don't have the accuracy.

So we will start counting SYS_TICK itself for the highest accuracy we can get.

  • Page 78/79 of the rpi datasheet shows us how to read SYS_TICK by enabling it and then setting its counter.
  • It is the most accurate counter in the system without getting external high-frequency components.
  • We enable the counter by setting bits 2 and 0
  • Bit 2 - Use Processor Clock
  • Bit 0 - Enable the Timer.
systick_hw->csr = 0x05;
  • And then set a count-down timer with:
systick_hw->rvr = 0x00FFFFFF;
  • This 24-bit timer should theoretically overflow every 134 milliseconds if the rpi is clocking at it's standard 125 Mhz.
  • Now lets look at the following code block:
#include "hardware/structs/systick.h"

int main()
{

 stdio_init_all();
 
 while (true)
  {
    systick_hw->csr = 0x05;
    systick_hw->rvr = 0x00FFFFFF;
    u32 mval = systick_hw->cvr;
    sleep_us(1);
    u32 nval = systick_hw->cvr;
    u32 elapsed = mval - nval;
    printf("Elapsed SYS_TICK: %u\n", elapsed);
    sleep_ms(1000);
  }
}
  • It is effectively starting the count-down timer with initial value of 0xFFFFFF - sleeping for 1 micro-second and measuring the clock cycles to do both giving you the differential.
  • Finally it sleeps for 1 second and just repeats.

Shockingly this is how the timer results came out:

Whoa... major inaccuracies (like 30-50%!) Are these chips reliable for anything other than funsies led blinking??

  • Taking out the sleep functions entirely was eye-popping.  We replaced the sleep functions with NOP timers as in:
for (u16 t = 0; t < 500; t++)
{};   // Equivalent to volatile asm('NOP');
  u32 global_counter = 0;

  while (true)
  {
    systick_hw->csr = 0x05;
    systick_hw->rvr = 0x00FFFFFF;
    u32 mval = systick_hw->cvr;
    for (u16 t = 0; t < 500; t++)
    {};
    u32 nval = systick_hw->cvr;
    u32 elapsed = mval - nval;
    printf("Elapsed SYS_TICK: %u\n", elapsed);
    for (u32 t = 0; t < 500000; t++)
    {};
  }

What did we get?

Poof. Accuracy came back. How about a very tight loop. As tight as can be made:

    systick_hw->csr = 0x05;
    systick_hw->rvr = 0x00FFFFFF;
    u32 mval = systick_hw->cvr;
    asm("nop");
    u32 nval = systick_hw->cvr;
    u32 elapsed = mval - nval;
    printf("Elapsed SYS_TICK: %u\n", elapsed);
    for (u32 t = 0; t < 5000000; t++)
    {};

What do we get - a solid 7 clock count.

Summary: After writing this it was explored if anyone really out there in search-engine land was really looking at this and or asking about it - and it was surprising that literally nobody was actually looking at this topic even historically.

Results: It looks like it would be reasonable to read chip pulses in speeds up to 10-25 Mhz, and or about roughly 0.1 to 0.05 microseconds - which is pretty good for a $4 MCU microboard..

Linux Rocks Every Day