Options for Improving Timestamp Precision
After a quick test determined that my Arduino sketch will be dealing data changing at a faster rate than 1kHz, I switched the timestamp query from calling millis()
to micros()
. As per Arduino documentation, this change improved time resolution by 250 from 1 millisecond precision to 4 microsecond precision. Since I had time on my mind anyway, I took a research detour to learn how this might be improved further. After learning how much work it'd take, I weighed it against my project and decided... nah, never mind.
Hardware: ATmega328P
A web search for ATmega328P processor programming found good information on this page Developing in C for the ATmega328: Marking Time and Measuring Time. The highest possible timing resolution is a counter that increments upon every clock cycle of the processor. For an ATmega328P running at 16MHz, that's a resolution of 62.5 nanoseconds from ticks()
. This 16-bit counter overflows very quickly (once every 4.096 milliseconds) so there's another 16 bit counter ticks_ro()
that increments whenever ticks() overflows. Together they become a 32-bit counter that would overflow every 4.47 minutes, after that we're on our own to track overflows.
However, ticks()
and ticks_ro()
are very specific to AVR microcontrollers and not (easily) accessible from Arduino code because that kills its portability. Other microcontrollers have similar concepts but they would not be called the same thing. (Example: ESP32 has cpu_hal_get_cycle_count()
)
Software: Encoder Library
Another factor in timing precision is the fact that I'm not getting the micros()
value when the encoder position is updated. The encoder position counter is updated within the quadrature decoding library, and I call micros()
sometime afterwards.
timestamp,position,count
16,0,448737
6489548,1,1
6490076,2,1
6490688,5,1
6491300,8,1
6491912,12,1
6492540,17,1
6493220,21,1
6493876,25,1
Looking at the final two lines of this excerpt, I see my code recorded encoder update from position 21 to 25 over a period of 6493876-6493220 = 656 microseconds. But 6493876 is only when my code ran, that's not when the encoder clicked over from 24 to 25! There's been a delay on the order of three-digit microseconds, an approximation derived from 656/(25-21) = 164.
One potential way to improve upon this is to add a variable to the Encoder library, tracking the micros()
timestamp of the most recent position update. I can then query that timestamp from my code later, instead of calling micros()
myself which pads an unknown delay. I found the encoder library source code at https://github.com/PaulStoffregen/Encoder. I found an update()
function and saw a switch()
statement that looked at pin states and updated counter as needed. I can add my micros()
update in the cases that updated position. Easy, or so I thought.
Looking at the code more closely, I realized the function I found is actually in a comment. It was labeled the "Simple, easy-to-read "documentation" version :-)" implying the actual code was not as simple or easy to read. I was properly warned as I scrolled down further and found... AVR assembly code. Dang! That's hard core.
On the upside, AVR assembly code means it can access the hardware registers behind ticks()
and ticks_ro()
for the ultimate in timer resolution. On the downside, I don't know AVR assembly and, after some thought, I decided I'm not motivated enough to learn it for this particular project.
This was a fun side detour and I learned things I hadn't known before, but I don't think the cost/benefit ratio makes sense for my Canon MX340 teardown project. I want to try some other easy things before I contemplate the harder stuff.
This teardown ran far longer than I originally thought it would. Click here to rewind back to where this adventure started.
Captured CSV and Excel worksheets are included in the companion GitHub repository.