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