Introduction: 6.25 Nanosecond Resolution Timer for Any Microcontroller!
While designing a solid state replacement for a vacuum-tubes-and-CRT radar display subsystem, I was stuck at getting a suitable system to time the period between the synch pulse and the echo pulse. The synch pulse and echo pulse were being captured on two pins of an Arduino Due. The Arduino Due has a MCLK/2 clock of 42 MHz, which would give me a resolution of 23.81 nanoseconds (ns) which, for the speed of light, would translate to a distance of 6.86 meters. I wanted a better resolution.
After looking into solutions using high speed oscillators and discrete logic ICs (counters, shift registers), FPGAs and off the shelf solutions costing in the range of USD 2,000/-, I decided to examine the ESP8266 for some solution to my timing problem.
To my delight, I discovered two functions in the Arduino ESP8266 API, namely ESP.getCycleCount() and ESP.reset()
ESP.getCycleCount() counts the instruction cycles since start, in an internal unsigned 32-bit variable, liable to overflow every 28 seconds or so. A one microsecond period will give 160 instruction cycles. Now to measure the software overhead required by your software, you need to only count the instruction cycles your routine counts for a 1 microsecond delay and subtract 160 from that count, to give you the software overhead in acquiring the count. For 1 microsecond delay, I got a count of 213. Which worked out to 213-160 = 53 counts (53 x 6.25 = 331.25 nanoseconds) software overhead to acquire the count. Subtracting 53 from every count gives me a count accurate to within a few tens of picoseconds, for periods from 30 microseconds to about 500 microseconds.
Step 1: Setup the ESP8266
Open the Arduino IDE.
Select the appropriate ESP8266
Select the speed, 160 MHz.
Step 2: Write a Program to Trap a START and a STOP Interrupt
Setup two pins to respond to RISING or FALLING interrupts. Within the START ISR, capture the instruction count. Within the STOP interrupt capture the instruction count and raise a transmit flag. Reset the CPU to obviate a counter overflow. For interrupts that may be far in between, an automatic periodic reset or an overflow handler could be written.
Within the loop() function, transmit the count over the I2C bus. The slave should be setup to receive from the I2C slave.
Check out the program. It is quite self-explanatory.
I am now able to time the period between two events, to a resolution of 6.25 ns, which with the speed of light, resolves to a resolution of about 1.8 meters.
Profiling the count acquisition instructions for the software overhead, which will be a constant, is very important.
13 Comments
4 years ago
Thank you for this article and sharing the code!
Would you kindly explain a bit more in detail how you detected the software overhead? I understand that you put a 1-microsec-delay into you routine. But where exactly? In both call funtions ( startCount() and stopCount() )? After you got your Overhead constant (53 ticks) you canceled the 1-microsec-delay? In this way does not change the value for the constant again?
A lot of questions, but that's an interesting topic!
4 years ago on Step 2
A clever solution. Awesome!
Is the Arduino code available? (I didn't find a link to "Check out the program. It is quite self-explanatory")
Reply 4 years ago
I see the 2 pictures showing the code.do u?
Reply 4 years ago
can you help me?
my timing requirement is from 80uS to 2000uS with a resolution of 50nanoS..
I2c is not working because I2c protocol sends 1 byte at a time...
Question 4 years ago on Step 2
Hellow Sir....
sir my timing requirement is from 80uS to 2000uS with a resolution of 50nanoS..
sir I2c is not working because I2c protocol sends 1 byte at a time...
sir please help... I am a student... I am making a magnetostructive level Transmitter...
7 years ago
I use timer based controller like this in a lot of my Arduino projects.
Reply 4 years ago
If you could provide the links to your related post if any?
5 years ago
Hey, is it possible to expand this to 4 input signals and measure the time between them with nanosecond resolution?
Reply 5 years ago
Sorry, I've just seen this query...
Yes, you could potentially time as many events as there are pins, using interrupt service. All of the ESP8266 pins can be configured for interrupt capture.
5 years ago
First of all that's a lovely solution you spread here! Thanks.
Two short question:
1. What are you using to generate the 1 microsencond pulse (train)? Can a function generator be omited?
2. Isn't there a chance for counter overflows when having small duty cycle (short single pulses followed by long silence) ? I propose a reset right in the start Interrupt Routine so you definitly start your conting with a small count.
(Checking if the stop cycle is lower than the start cycle and in this case adding the counter maximum could also solve the problem.)
5 years ago
Hello ! I repeated your code on esp12 - 160 mhz mode, ports 12, 13 are connected to one synchronization source, but i2c counter always sends 116 value. if I understood correctly, then my tick was 116 * 6.5 = 754 nanoseconds.But why, if the start-stop triggers I synchronized with one source?
Reply 5 years ago
Use a variable to store the current count when the first event occurs. Then, to time the period between the first and second events, just subtract the stored count from the current count and multiply by 6.5ns.
5 years ago
Hello ! I repeated your code on esp12 - 160 mhz mode, ports 12, 13 are connected to one synchronization source, but i2c counter always sends 116 value. if I understood correctly, then my tick was 116 * 6.5 = 754 nanoseconds.