6.0.0-rc4Source

Using Nt GPIO

Naming a GPIO line, reading a push-to-talk switch, driving an LED, and getting permission on Linux.

Link Nt::Gpio (or Nt6::Gpio from an installed package) and include <QGpioLine>. On Zephyr, also set CONFIG_NT_GPIO in prj.conf.

find_package(Nt6 6 REQUIRED)
add_executable(app main.cpp)
target_link_libraries(app PRIVATE Nt6::Gpio)

Nt GPIO is not part of Qt. A program that must also build with Qt keeps its use of QGpioLine behind an interface of its own.

Naming a Line

A QGpioLine is made from the name of one line:

Name

Platform

Meaning

PTT

Linux

The line with this name, from gpio-line-names in the devicetree. Every chip is searched.

gpiochip0:17

Linux

Line 17 of /dev/gpiochip0. A full path such as /dev/gpiochip0:17 also works.

gpio@401b8000:17

Zephyr

Pin 17 of the GPIO device with this name, the name that device_get_binding() takes: the devicetree node name, or its label.

gpioinfo, from the libgpiod tools, lists the chips and lines of a Linux machine.

Reading a Switch

A new line is an input. Set whether it is active low, its bias and its debounce interval before open(). For a push-to-talk switch that pulls the line to ground:

QGpioLine ptt(parser.positionalArguments().constFirst());
ptt.setActiveLow(true);         // pressed pulls the line low
ptt.setBias(QGpioLine::PullUp); // released floats high
ptt.setDebounceInterval(20);    // ignore contact bounce
if (!ptt.open()) {
    std::fprintf(stderr, "ptt: %s\n", qPrintable(ptt.errorString()));
    return 1;
}

value() is then the logical value, true while the switch is held, and valueChanged() is emitted each time it changes:

QObject::connect(&ptt, &QGpioLine::valueChanged, [&](bool pressed) {
    std::printf("TX %s\n", pressed ? "on" : "off");
    std::fflush(stdout);
    if (led.isOpen())
        led.setValue(pressed);
});

The line is watched for edges where the hardware can, and polled every pollInterval() milliseconds where it cannot; isEventDriven() tells which. With a debounce interval, a change is reported once the line has been steady for that long, so a switch reports one press however much its contacts bounce.

Driving a Line

An output line is opened with the value set by setValue() beforehand, false unless changed, and changed with setValue():

QGpioLine led(parser.value(ledOption));
led.setDirection(QGpioLine::Output);
if (parser.isSet(ledOption) && !led.open()) {
    std::fprintf(stderr, "ptt: %s\n", qPrintable(led.errorString()));
    return 1;
}

Handling Errors

open() returns false and sets error() and errorString() when the line cannot be had. BusyError means that another program, or a kernel driver, holds the line. A ResourceError while the line is open means it went away, for example because a USB GPIO adapter was unplugged; the line is closed, and must be opened again:

QObject::connect(&ptt, &QGpioLine::errorOccurred, [&](QGpioLine::GpioError error) {
    if (error == QGpioLine::ResourceError) { // the chip went away
        std::fprintf(stderr, "ptt: %s\n", qPrintable(ptt.errorString()));
        app.exit(1);
    }
});

Permission on Linux

/dev/gpiochip* belongs to root, or to the group gpio on some distributions. To let a user in the group plugdev open them, add a udev rule such as this as /etc/udev/rules.d/60-gpio.rules:

SUBSYSTEM=="gpio", KERNEL=="gpiochip*", GROUP="plugdev", MODE="0660"

Zephyr

The line is configured with gpio_pin_configure() and watched with an interrupt on both edges. The interrupt only wakes the thread that owns the QGpioLine, so its slots run in that thread as usual. A pin whose controller cannot interrupt is polled. Names of lines from the devicetree are not looked up; use the device name and pin number.