Skip to content

RFC: PulseTime #2210

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
dmopalmer opened this issue Oct 11, 2019 · 9 comments
Open

RFC: PulseTime #2210

dmopalmer opened this issue Oct 11, 2019 · 9 comments

Comments

@dmopalmer
Copy link

This is a Request For Comment for a suggested extension to pulseio for doing high precision timing on multiple inputs on the SAM port of circuitpython.

I would like to do time capture on pin inputs so that I can do things like:

  1. Get the exact time of an event (on one pin), compared to GPS time (referenced to the Pulse Per Second on another pin).
  2. Get the time between a start pulse and a stop pulse on two different pins
  3. Get the frequency, duty cycle, etc. of a signal by measuring the times of all the rising edges, falling edges, or both.
  4. Do the timing at the maximum feasible resolution (e.g. 12.5 ns for an 80 MHz CPU clock) and without jitter (i.e. using the hardware timers).

The current pulseio.PulseIn:

  1. Only gives the duration of a positive pulse, or of a negative pulse, and doesn't give the time between pulses.
  2. Is limited to microsecond resolution, with latency jitter, and to 0.065536 seconds maximum duration.
  3. Can't do multi-pin comparison

Is there already a library that can do what I want? (or a sufficient subset of it)? No sense re-inventing the wheel. (rfc: 'Why don't you just use the fooTime module on Arduino instead?' would be a useful comment.)

If not, I am thinking of adding pulseio.PulseTime (rfc: make it a separate module?) to the samdx1 library. The SAM has TCC (Timer Counter for Control) modules that can run a 24-bit counter from the CPU clock (or other source) and capture the times of up to 4 event sources. My library would set up a TCC with the user's choice of event inputs, and queue up event times as they occur.

Some details:

  1. 24 bits rolls over 5 times per second at 80 MHz, so the library would keep track of the rollover count. Easy code to get wrong, so I will be careful.
  2. The 31 bit integers of circuit Python roll over in 13 seconds (faster for faster clocks), so times would have to be in a format like (seconds,subseconds), with a convenience library to make them useful.
  3. A .now() method is needed to read the current timer state so you know how long ago an event happened.
  4. The minimum time delta between two events on the same pin is limited to how fast the ISR can reset the capture register. The time delta between events on two pins is unrestricted, so simultaneous or either-order single-cycle timing is possible.

rfc: Any other capabilities that should be included?

@tannewt
Copy link
Member

tannewt commented Oct 11, 2019

Sounds like an interesting project! I don't know of anything that currently allows for it (though I'm not much of an Arduino user).

I'd suggest adding it as a separate module so that it can be supported separately from PulseIO.

Other than that, let us know if you have CircuitPython questions. Thanks!

@tannewt tannewt added this to the Long term milestone Oct 11, 2019
@sommersoft
Copy link

sommersoft commented Oct 11, 2019

  1. Get the frequency, duty cycle, etc. of a signal by measuring the times of all the rising edges, falling edges, or both.

frequencyio.FrequencyIn() can get you the frequency. It uses 2+ TCs [reference and source(s)] with 16-bit counters. The capture period can be set in a range between 1ms-500ms. However, it is currently hard-coded to use rising edge. Duty cycle could possibly be coerced out somehow; I've never looked into the math/process.

notro had previously brought up a similar request: #1423. There's discussion in that PR to fold it in with frequencyio. This proposal could probably fit in that module as well. Like @tannewt mentioned, having it separate from pulseio allows modular support (adding to pulseio affects almost every board; the M0 boards are tight with regards to space).

  1. Do the timing at the maximum feasible resolution (e.g. 12.5 ns for an 80 MHz CPU clock) and without jitter (i.e. using the hardware timers).

The aforementioned FrequencyIn uses a DPLL clock to reduce jitter and increase measurements versus DFLL on the SAMD51. The clock is set to 98.3MHz (TC4+ has a max of 100MHz).

@sommersoft
Copy link

I meant to mention that frequencyio is only enabled on SAMD51 based boards. Its too large to include on SAMD21 based boards.

@jepler
Copy link

jepler commented Oct 11, 2019

My personal use case is a running count of pulses from a source of around 10 MHz, which I can check "from time to time" but maybe as infrequently as once a second. If it can reset to 0 at a specific value (e.g. 600e6 ~1 minute) even better.

@dmopalmer
Copy link
Author

My personal use case is a running count of pulses from a source of around 10 MHz, which I can check "from time to time" but maybe as infrequently as once a second. If it can reset to 0 at a specific value (e.g. 600e6 ~1 minute) even better.

10 MHz is a bit too fast for a 'A pulse caused an interrupt: take down all the details then return from interrupt'.

What would work is 'The PulseTime clock is driven from this external pin instead of the CPU clock'. Then you just call .now() each time you want to know the count.

Resetting the hardware at a specific count is too specific, but I could expose the number of ticks in a second, for you to set to 6e8, so that when you read a time that is nominally (seconds, subseconds) it would actually give (minutes, ticks).

Is your specific use case a clock based on a GPS-disciplined oscillator (TCXO/OCXO/Rb) with a 10 MHz output? You can use the 10 MHz as an external clock, do capture on the PPS, and have 3 other capture channels timestamped to the precise UTC time with 100 ns precision and accuracy (after subtracting the tick number corresponding to the PPS).

@tannewt
Copy link
Member

tannewt commented Mar 12, 2020

@dmopalmer Could you please elaborate on why you want these things? I'm reworking some clock stuff now to enable sleep and am revisiting both PulseIn and FrequencyIn to do it.

@dmopalmer
Copy link
Author

My specific use-case is rather idiosyncratic. (I want to time the detections of photons from a telescope tracking a satellite to read out an optical identification beacon) but there are many other applications.

e.g. If you want to do period timing, e.g. for a tachometer or frequency counter, then measuring the individual times of each of a second's worth of pulses gives a more accurate frequency than counting how many pulses you get in a second. Because if your signal has a frequency around 2.5 Hz, you sometimes get 3 pulses in a second, and sometimes 2: but if you get a pulse at t = 0.3692 s, and another pulse at 0.7691, and another at 1.1690, then you know that the frequency is 2/(1.1690-0.3692) = 2.5006 Hz, where all those digits are significant.

I am currently distracted by other things. (I didn't get to the point where the timer-counter was actually incrementing with each clock pulse.) If I get back to it, my path might be to implement the receiver functionality of the esp32.RMT class in MicroPython.

@tannewt
Copy link
Member

tannewt commented Mar 14, 2020

@dmopalmer I'd recommend using pulseio.PulseIn in CircuitPython rather than frequencyio. It'll gives you ~1us accuracy of the gap between edges. The one tricky bit might be that it looks like the pulse high is only 2us long. That might confuse PulseIn and we'll need to adjust it.

(Looking back at the top I see you addressed PulseIn issues.)

  1. PulseIn gives you time of both high and low portions of the pulse. "Idle" is just used to wait for the first "active" pulse. After that it alternates between idle and active.
  2. Right now we standardize on microsecond accuracy but we could move the internal count to 32 bits from 16 and use more memory to have higher resolution and longer periods. This wouldn't be hard. Latency could be reduced by using "capture" functionality on the reference counter but will increase implementation complexity because this clock will soon be shared amongst all PulseIns. (I'm changing it now with my lower power work.)
  3. Multiple pins are supported and will soon use the same reference clock. It won't be better than 1us + latency though.

I really appreciate you replying with your use case. It really helps separate the things we need from the things that would be nice to have but maybe never use.

@mrdalgaard
Copy link

Bumping this, as i ran across it for my use case.
Building a remote anemometer/wind vane station, with solar/battery and LoRa.

The anemometer is a simple pulse device, with 1 pulse per rotation. I expect it to be pulsing in the low seconds to milliseconds range.

This rules out using the frequencyio and pulseio modules, and leaves me with countio, which will have the problem @dmopalmer mentions of not using period timing.
This means i will have to do longer samples, wasting more power, and will also be unable to measure things such as short term wind gusts.

Would it be possible to increase the internal count to 32bit of pulseio, or maybe even make it configurable?

Oh, and first post here - i recently got into microcontrollers, thanks to CircuitPython, and its ease of use. Just want to thank everyone involved in making it!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants