diff --git a/CMakeLists.txt b/CMakeLists.txt index 6bbb5ebc..399c98b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -136,6 +136,13 @@ if(DEFINED RF24_SPI_SPEED) RF24_SPI_SPEED=${RF24_SPI_SPEED} ) endif() +# allow user customization of default GPIO chip used with the SPIDEV driver +if(DEFINED RF24_SPIDEV_GPIO_CHIP) + message(STATUS "RF24_SPIDEV_GPIO_CHIP set to ${RF24_SPIDEV_GPIO_CHIP}") + target_compile_definitions(${LibTargetName} PUBLIC + RF24_SPIDEV_GPIO_CHIP="${RF24_SPIDEV_GPIO_CHIP}" + ) +endif() # conditionally disable interruot support (a pigpio specific feature) if("${LibPIGPIO}" STREQUAL "LibPIGPIO-NOTFOUND" OR DEFINED RF24_NO_INTERRUPT) message(STATUS "Disabling IRQ pin support") diff --git a/utility/SPIDEV/RF24_arch_config.h b/utility/SPIDEV/RF24_arch_config.h index fa457fde..219bc051 100644 --- a/utility/SPIDEV/RF24_arch_config.h +++ b/utility/SPIDEV/RF24_arch_config.h @@ -44,8 +44,6 @@ #endif typedef uint16_t prog_uint16_t; -typedef uint16_t rf24_gpio_pin_t; -#define RF24_PIN_INVALID 0xFFFF #define PSTR(x) (x) #define printf_P printf diff --git a/utility/SPIDEV/gpio.cpp b/utility/SPIDEV/gpio.cpp index 9f24a74e..f27e2c9b 100644 --- a/utility/SPIDEV/gpio.cpp +++ b/utility/SPIDEV/gpio.cpp @@ -6,97 +6,174 @@ * */ #include -#include "gpio.h" #include // close() #include // open() #include // ioctl() +#include // errno, strerror() +#include // std::string, strcpy() +#include +#include "gpio.h" -const char* dev_name = "/dev/gpiochip4"; +// instantiate some global structs to setup cache +// doing this globally ensures the data struct is zero-ed out +typedef int gpio_fd; // for readability +std::map cachedPins; +struct gpio_v2_line_request request; +struct gpio_v2_line_values data; +struct gpiochip_info chipMeta; -GPIO::GPIO() +void GPIOChipCache::openDevice() { + if (fd < 0) { + fd = open(chip, O_RDONLY); + if (fd < 0) { + std::string msg = "Can't open device "; + msg += chip; + msg += "; "; + msg += strerror(errno); + throw GPIOException(msg); + } + } } -GPIO::~GPIO() +void GPIOChipCache::closeDevice() { + if (fd >= 0) { + close(fd); + fd = -1; + } } -void GPIO::open(int port, int DDR) +GPIOChipCache::GPIOChipCache() { - int fd; - fd = ::open(dev_name, O_RDONLY); - if (fd >= 0) { - ::close(fd); + try { + openDevice(); } - else { - dev_name = "/dev/gpiochip0"; - fd = ::open(dev_name, O_RDONLY); - if (fd >= 0) { - ::close(fd); - } - else { - throw GPIOException("can't open /dev/gpiochip"); + catch (GPIOException& exc) { + chip = "/dev/gpiochip0"; + openDevice(); + } + request.num_lines = 1; + strcpy(request.consumer, "RF24 lib"); + data.mask = 1ULL; // only change value for specified pin + + // cache chip info + int ret = ioctl(fd, GPIO_GET_CHIPINFO_IOCTL, &chipMeta); + if (ret < 0) { + std::string msg = "Could not gather info about "; + msg += chip; + throw GPIOException(msg); + return; + } + closeDevice(); // in case other apps want to access it +} + +GPIOChipCache::~GPIOChipCache() +{ + closeDevice(); + for (std::map::iterator i = cachedPins.begin(); i != cachedPins.end(); ++i) { + if (i->second > 0) { + close(i->second); } } } -void GPIO::close(int port) +// GPIO chip cache manager +GPIOChipCache gpioCache; + +GPIO::GPIO() { } -int GPIO::read(int port) +GPIO::~GPIO() { +} - struct gpiohandle_request rq; - struct gpiohandle_data data; - int fd, ret; - fd = ::open(dev_name, O_RDONLY); - if (fd >= 0) { - rq.lineoffsets[0] = port; - rq.flags = GPIOHANDLE_REQUEST_INPUT; - rq.lines = 1; - ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &rq); - if (ret == -1) { - throw GPIOException("Can't get line handle from IOCTL"); - return ret; - } - ::close(fd); - ret = ioctl(rq.fd, GPIOHANDLE_GET_LINE_VALUES_IOCTL, &data); - if (ret == -1) { - throw GPIOException("Can't get line value from IOCTL"); - return ret; +void GPIO::open(rf24_gpio_pin_t port, int DDR) +{ + if (port > chipMeta.lines) { + std::string msg = "pin number " + std::to_string(port) + " not available for " + gpioCache.chip; + throw GPIOException(msg); + return; + } + + // check if pin is already in use + std::map::iterator pin = cachedPins.find(port); + if (pin == cachedPins.end()) { // pin not in use; add it to cached request + request.offsets[0] = port; + request.fd = 0; + } + else { + request.fd = pin->second; + } + + gpioCache.openDevice(); + int ret; + if (request.fd <= 0) { + ret = ioctl(gpioCache.fd, GPIO_V2_GET_LINE_IOCTL, &request); + if (ret == -1 || request.fd <= 0) { + std::string msg = "[GPIO::open] Can't get line handle from IOCTL; "; + msg += strerror(errno); + throw GPIOException(msg); + return; } - ::close(rq.fd); - return data.values[0]; } - return -1; + gpioCache.closeDevice(); // in case other apps want to access it + + // set the pin and direction + request.config.flags = DDR ? GPIO_V2_LINE_FLAG_OUTPUT : GPIO_V2_LINE_FLAG_INPUT; + + ret = ioctl(request.fd, GPIO_V2_LINE_SET_CONFIG_IOCTL, &request.config); + if (ret == -1) { + std::string msg = "[gpio::open] Can't set line config; "; + msg += strerror(errno); + throw GPIOException(msg); + return; + } + cachedPins.insert(std::pair(port, request.fd)); } -void GPIO::write(int port, int value) +void GPIO::close(rf24_gpio_pin_t port) { + // This is not really used in RF24 convention (designed for embedded apps). + // Instead rely on gpioCache destructor (see above) +} - struct gpiohandle_request rq; - struct gpiohandle_data data; - int fd, ret; - fd = ::open(dev_name, O_RDONLY); - if (fd < 0) { - throw GPIOException("Can't open dev"); - return; +int GPIO::read(rf24_gpio_pin_t port) +{ + std::map::iterator pin = cachedPins.find(port); + if (pin == cachedPins.end() || pin->second <= 0) { + throw GPIOException("[GPIO::read] pin not initialized! Use GPIO::open() first"); + return -1; } - rq.lineoffsets[0] = port; - rq.flags = GPIOHANDLE_REQUEST_OUTPUT; - rq.lines = 1; - ret = ioctl(fd, GPIO_GET_LINEHANDLE_IOCTL, &rq); + + data.bits = 0ULL; + + int ret = ioctl(pin->second, GPIO_V2_LINE_GET_VALUES_IOCTL, &data); if (ret == -1) { - throw GPIOException("Can't get line handle from IOCTL"); + std::string msg = "[GPIO::read] Can't get line value from IOCTL; "; + msg += strerror(errno); + throw GPIOException(msg); + return ret; + } + return data.bits & 1ULL; +} + +void GPIO::write(rf24_gpio_pin_t port, int value) +{ + std::map::iterator pin = cachedPins.find(port); + if (pin == cachedPins.end() || pin->second <= 0) { + throw GPIOException("[GPIO::write] pin not initialized! Use GPIO::open() first"); return; } - ::close(fd); - data.values[0] = value; - ret = ioctl(rq.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &data); + + data.bits = value; + + int ret = ioctl(pin->second, GPIO_V2_LINE_SET_VALUES_IOCTL, &data); if (ret == -1) { - throw GPIOException("Can't set line value from IOCTL"); + std::string msg = "[GPIO::write] Can't set line value from IOCTL; "; + msg += strerror(errno); + throw GPIOException(msg); return; } - ::close(rq.fd); } diff --git a/utility/SPIDEV/gpio.h b/utility/SPIDEV/gpio.h index b9a607f2..006d5d2c 100644 --- a/utility/SPIDEV/gpio.h +++ b/utility/SPIDEV/gpio.h @@ -16,6 +16,18 @@ #include #include #include +#include + +typedef uint16_t rf24_gpio_pin_t; +#define RF24_PIN_INVALID 0xFFFF + +#ifndef RF24_SPIDEV_GPIO_CHIP + /** + * The default GPIO chip to use. Defaults to `/dev/gpiochip4` (for RPi5). + * Falls back to `/dev/gpiochip0` if this value is somehow incorrect. + */ + #define RF24_SPIDEV_GPIO_CHIP "/dev/gpiochip4" +#endif /** Specific exception for SPI errors */ class GPIOException : public std::runtime_error @@ -27,7 +39,28 @@ class GPIOException : public std::runtime_error } }; -typedef int GPIOfdCache_t; +/// A struct to manage the GPIO chip file descriptor. +/// This struct's destructor should close any cached GPIO pin requests' file descriptors. +struct GPIOChipCache +{ + const char* chip = RF24_SPIDEV_GPIO_CHIP; + int fd = -1; + + /// Open the File Descriptor for the GPIO chip + void openDevice(); + + /// Close the File Descriptor for the GPIO chip + void closeDevice(); + + /// should be called automatically on program start. + /// Here, we do some one-off configuration. + GPIOChipCache(); + + /// Should be called automatically on program exit. + /// What we need here is to make sure that the File Descriptors used to + /// control GPIO pins are properly closed. + ~GPIOChipCache(); +}; class GPIO { @@ -41,13 +74,13 @@ class GPIO GPIO(); - static void open(int port, int DDR); + static void open(rf24_gpio_pin_t port, int DDR); - static void close(int port); + static void close(rf24_gpio_pin_t port); - static int read(int port); + static int read(rf24_gpio_pin_t port); - static void write(int port, int value); + static void write(rf24_gpio_pin_t port, int value); virtual ~GPIO();