How to implement a `nop()` loop for sub-µs delays? (Raspberry Pi, GPIO)

Hello Julia people,

I’ve enjoyed using Julia for signal processing and numeric work and I’ve also tried it for some scripting and automation tasks (with mixed results, sometimes the start-up delay is a killer).

I am currently experimenting with Julia on Raspberry Pi Zero W (single-core 1GHz ARM).

To handle bit-banged IO over the Pi’s GPIO port I need a way to implement sub-µs delays. e.g. a hardware protocol may call for waiting 100ns between asserting a clock line and reading a data line. In C I would implement a nop() loop with a volatile variable. In Julia I can’t find a simple way to tell the compiler not to optimise-away a loop that does nothing.

(I am aware that running Julia on a non-realtime OS I’ll never achieve perfect timing and that my process can be preempted for 100s of ms at a time. The goal is to implement a 100ns delay that will never take less than 100ns even if it occasionally takes far longer.)

My questions are:

  • Is there a language feature that equivalent to C’s volatile?
  • Or is there another way to cleanly implement sub-µs delays?

time_ns() is not useful because just calling the function twice takes ~5µs on the Pi: (f() = - time_ns() + time_ns()

So far what I have come up with is this combination of @noinline and @asmcall:

This seems to work. However, relying on LLVM and @asmcall seems like a nasty hack. I’m also concerned that some future compiler or LLVM “improvement” might cause it to be optimised away.

The following is some more detail on what I’m working on for those who are interested…

My aim is to build a collection of Pi boards with a variety of sensors and outputs to be used as a general purpose measurement and control system. I’ll use this as a development and test automation tool for work on larger mechatronic systems. I imagine typing pkjg> test to deploy test harness code to a cluster of Julia Pi boards; deploy control software to the machine being developed; then run through a series of tests controlling the machine and measuring its movements and outputs.

Some pieces that are working so-far:

  • PiGPIOC.jl – Wrapper for the pigpio C Interface, lots of features, but has overhead due to ccall.
  • PiGPIOMEM.jl - /dev/gpiomem interface. Inspired by BaremetalPi.jl, but attempting to achieve lower overhead (GPIO set/clear compiles to 3 ARM instructions, GPIO read is 5).
  • BBSPI.jl – bit-banged SPI protocol, working at ~1MHz (for devices that do not require a synchronous clock).
  • PiADXL345.jl – SPI Accelerometer.
  • PiADS111x.jl – i2c ADC.

I’ve written this type of code many times before in C. But with Julia I’ve had couple of nice Wow! moments:

  • I wanted to try reading from different 4 SPI accelerometers in parallel. In C this would require duplicating or changing a bunch of code and types. In Julia I just passed Vector{GPIOPin} to the accelerometer constructor as the data input line instead of a single GPIOPin. Then I added @. to a few places, and Boom!, the output is a 2D array of (x, y, z) samples where before it was just a 1D vector.
  • I’ve been trying out a few different GPIO libraries and also needed a dummy GPIO interface for unit tests. In C I would have all my modules depending on my_gpio_interface.h, then have a variety of implementation of that interface. In Julia I can just use an informal protocol (duck typing) by saying "a GPIO pin can be anything that implements getindex and setindex!. My unit tests can use Ref{Bool} as GPIO pin stand-ins. Any GPIO library can be used by defining a couple of glue methods without changing any other code.