Search code examples
clinux-device-drivergpio

Control a GPIO pin within 150ns of tolerance


My problem is that turning on and off my GPIO pin takes way too long, despite using good timekeeping functionality, including both ndelay from linux/delay.h and my own accurate_ndelay which (shown below) uses ktime_get_ns() from linux/ktime.h.

My kernel version is 4.19.38 with Armbian, running on an OrangePi Zero.

static inline void accurate_ndelay(uint16_t ns){
  uint64_t s = ktime_get_ns();
  uint64_t e = s + ns;
  while(ktime_get_ns() < e);
}
static inline void unsafe_bit2812(struct WS2812* ws2812, uint8_t b){
  if(b){
    gpio_set_value(ws2812->pin, 1);
    accurate_ndelay(ws2812->t0h);
    gpio_set_value(ws2812->pin, 0);
    accurate_ndelay(ws2812->t0l);
  } else {
    gpio_set_value(ws2812->pin, 1);
    accurate_ndelay(ws2812->t1h);
    gpio_set_value(ws2812->pin, 0);
    accurate_ndelay(ws2812->t1l);
  }
}

When I measure the real-world delay (as shown by my oscilloscope, not bad software). The delay is not the expected 350ns, but 920ns. Which for the WS2812 is 770ns too much!


Solution

  • That's some pretty tight timing. The OrangePi Zero run at 1.2 GHz, so 150 ns is 180 clock cycles. That doesn't give you time to do much.

    The first thing to do is use ktime_get_ns() to just measure how long the gpio_set_value() call takes. Or remove the delay and measure it with the scope. You might know the answer already, if you delayed for 350ns and measured 920ns, then it takes about 600ns.

    You're calling gpio_set_value(), which pulls in the safe, portable Linux gpio library. The maximum possible performance would be to write your own driver for the GPIO that goes right to the HW registers and sets the two states, with the delay, as a single action.

    Even with a custom driver, you'll have delays introduced by the clock driving the gpio and the rise and fall times of the device.