adafruit / circuitpython

CircuitPython - a Python implementation for teaching coding with microcontrollers
https://circuitpython.org
Other
4.02k stars 1.19k forks source link

Peripheral Register to PIO for Timing #7641

Open latkinso42 opened 1 year ago

latkinso42 commented 1 year ago

This feature would enable many amateurs and professionals to leverage the power of processor peripherals for precise microsecond level timing applications, such as measurement, where Circuitpython's inherent time resolution of near 1 millisecond is insufficient.

Use of IRQs would not be needed (to the best of my knowledge). The trigger would merely be a state change of a register or more specifically, particular bits of a register.

Example Need: A current project, a precise ultrasonic range finder, uses the RP2040. As sound travels at 343 meters per second or 343 micrometers per microsecond, microsecond timing is needed "even" to acquire near millimeter precision.

The current timing in Circuitpython is about 1 millisecond. However, with peripherals like the DMA and PIO many timing issues can be improved.

One such timing issue was improved for the Circuitpython 8.0.0 release throught the feature analogbufio. This new library addition to Circuitpython allows for fast ADC read into memory at speeds of up to 2.0 microseconds per ADC conversion.

Another feature of the rp2040 and pioasm has been to generate a finite number of 'perfect' squarewaves at precisely 40KHZ or 25 microsecond cycle width. Using the PIO assembly one can also institute a desired delay before the pulse with extreme precision. Call the function 'Pulse8'.

While either are very precise independently, the exact time between invocation and actual execution is in the implementation details (overhead) of Circuitpython.

Attempts to measure the overhead for both 'analogbufio.readinto()' and the call to 'Pulse8()' have been difficult. By making use of the rp2pio.background_write() one can kick off the pulse then quickly (well ... quicker anyway) call analogbufio.readinto().

Many experiments are made without the transducers by merely connecting the digital pulse out  to the ADC0 input. This is merely an electronic kludge but it works for measuring and one can assume nearly zero delay from the transmit pin to the ADC0 pin.

Because the two actions are inherently independent (from a circuitpython view) the best timing offset estimation is around 800 microseconds plus or minus 100 microseconds (estd).

Solution: Using an RP2PIO State Machine, one might SET (active timing) or GET (passive timing) specified triggers where the PIO timing is extremely precise.

The result of this enhancement to Circuitpython may stretch across various other microcontrollers extending the capability of Circuitpython. This feature will further reduce the need to write complex applications in C using detailed hardware knowledge. This feature will also eliminate the need to electrically cross connect IO pins usually requiring external adder circuits.

gneverov commented 1 year ago

Thanks @latkinso42 for writing up your use case.

FWIW, I've been exploring a new approach for doing I/O. In this approach everything is a "stream" which serves as a common abstraction for connecting independent IO modules, like analogbufio and rp2pio. In this imaginary world, analogbufio would expose a stream of the ADC samples, which you'd connect to a PIO State Machine. When streams are "connected" they automatically transfer data without any interaction from the main program. When your PIO program starts outputting the pulse, it would simultaneously start feeding data from its TX FIFO (containing the ADC samples) to its RX FIFO. It's RX FIFO is also exposed as a stream, which you can then read from your main Python program. This way you know that the first ADC sample that you read (from the State Machine) is from a known time in your pulse generating program.

latkinso42 commented 1 year ago

Excellent idea and very exciting. I have to admit, I'm not the EE or HW guy I need to be to make any of this happen. I barely suffice to understand the genius that has come before me. However, it can be manifested SW-wise, precise timeing for peripherals would make for powerful control with ease of programming. I suppose it would not need to add data 'streams' but merely use the PIO as a trigger. In the case of analogbufio, the DMA starts running, but effectively does nothing until the first conversion. The ADC is configure via the CS register (third bit) to 'start_many' as opposed to one-shots. Your 'stream' idea would then generalize the setups required to 'time-trigger' streams.