Created
January 22, 2024 21:25
-
-
Save max-dark/8c8678c65b884c62aca328a5bf57f4f5 to your computer and use it in GitHub Desktop.
Play with Linux GPIO API v2
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Play with Linux GPIO API v2 | |
// based on https://github.com/torvalds/linux/tree/v6.1/tools/gpio | |
// compile: g++ -std=c++17 gpio_v2.cpp -o btn-watch-v2 | |
#include <iostream> | |
#include <iomanip> | |
#include <string> | |
#include <string_view> | |
#include <chrono> | |
#include <sys/types.h> | |
#include <sys/stat.h> | |
#include <sys/ioctl.h> | |
#include <sys/poll.h> | |
#include <unistd.h> | |
#include <cstring> | |
#include <fcntl.h> | |
#include <linux/gpio.h> | |
#include <cerrno> | |
namespace gpio::v2 | |
{ | |
using chip_info_t = ::gpiochip_info; | |
using line_info_t = ::gpio_v2_line_info; | |
using line_config_t = ::gpio_v2_line_config; | |
using line_request_t = ::gpio_v2_line_request; | |
using line_values_t = ::gpio_v2_line_values; | |
using line_event_t = ::gpio_v2_line_event; | |
struct chip | |
{ | |
private: | |
std::string dev_name; | |
chip_info_t dev_info{}; | |
int dev_fd = -1; | |
int last_error = 0; | |
int last_result = 0; | |
public: | |
template<class ParamType> | |
bool dev_ctl(unsigned long cmd, ParamType *param) | |
{ | |
last_result = ::ioctl(dev_fd, cmd, param); | |
last_error = errno; | |
return last_result != -1; | |
} | |
[[nodiscard]] | |
int error_code() const | |
{ | |
return last_error; | |
} | |
[[nodiscard]] | |
int result() const | |
{ | |
return last_result; | |
} | |
[[nodiscard]] | |
const char *error_message() const | |
{ | |
return strerror(error_code()); | |
} | |
bool open_dev(const char *dev) | |
{ | |
dev_name = dev; | |
dev_fd = ::open(dev_name.c_str(), O_RDONLY); | |
last_error = errno; | |
return is_open(); | |
} | |
[[nodiscard]] | |
bool is_open() const | |
{ | |
return dev_fd >= 0; | |
} | |
bool close_dev() | |
{ | |
last_result = ::close(dev_fd); | |
last_error = errno; | |
dev_fd = -1; | |
return last_result == 0; | |
} | |
bool fill_info() | |
{ | |
if (is_open()) | |
{ | |
return dev_ctl(GPIO_GET_CHIPINFO_IOCTL, &dev_info); | |
} | |
return false; | |
} | |
[[nodiscard]] std::string_view name() const | |
{ | |
return dev_info.name; | |
} | |
[[nodiscard]] | |
std::string_view label() const | |
{ | |
return dev_info.label; | |
} | |
[[nodiscard]] | |
uint32_t num_lines() const | |
{ | |
return dev_info.lines; | |
} | |
[[nodiscard]] | |
std::string_view file_name() const | |
{ | |
return dev_name; | |
} | |
bool get_line_info(uint32_t line_no, line_info_t *info) | |
{ | |
memset(info, 0, sizeof(*info)); | |
info->offset = line_no; | |
return dev_ctl(GPIO_V2_GET_LINEINFO_IOCTL, info); | |
} | |
}; | |
struct line_watch | |
{ | |
line_request_t request{}; | |
chip* dev = nullptr; | |
int event_fd = 0; | |
}; | |
} // namespace gpio::v2 | |
int main() | |
{ | |
using namespace std::literals; | |
constexpr bool rz_mode = false; | |
constexpr bool enable_debounce = true; | |
constexpr auto debounce_delay = 10ms; | |
constexpr auto consumer_id = "btn-watch-v2"; | |
constexpr uint32_t btn_line = rz_mode ? 4 : 17; // GPIOAO_4 / GPIO17 | |
constexpr auto dev_chip_name = rz_mode ? "/dev/gpiochip1" : "/dev/gpiochip0"; | |
gpio::v2::chip dev; | |
dev.open_dev(dev_chip_name); | |
if (dev.fill_info()) | |
{ | |
gpio::v2::line_info_t info; | |
std::cout | |
<< std::setw(5) << dev.num_lines() | |
<< std::setw(15) << dev.name() << std::setw(15) << dev.label() | |
<< std::endl; | |
if (dev.get_line_info(btn_line, &info)) | |
{ | |
std::cout | |
<< std::setw(5) << info.offset | |
<< std::setw(15) << info.name << std::setw(15) << info.consumer | |
<< std::setw(10) << std::hex << info.flags << std::dec | |
<< std::setw(5) << info.num_attrs | |
<< std::endl; | |
} | |
gpio::v2::line_watch watch; | |
watch.dev = &dev; | |
gpio::v2::line_request_t &req = watch.request; | |
gpio::v2::line_config_t &cfg = req.config; | |
memset(&req, 0, sizeof(req)); | |
req.offsets[req.num_lines] = btn_line; | |
req.num_lines += 1; | |
cfg.flags |= GPIO_V2_LINE_FLAG_INPUT; | |
if constexpr (rz_mode) | |
{ | |
// inverted logic - 0 -> ON, 1 -> OFF | |
cfg.flags |= GPIO_V2_LINE_FLAG_ACTIVE_LOW; | |
cfg.flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_UP; | |
} | |
else | |
{ | |
cfg.flags |= GPIO_V2_LINE_FLAG_BIAS_PULL_DOWN; | |
} | |
// we wait for both edges | |
cfg.flags |= GPIO_V2_LINE_FLAG_EDGE_RISING; | |
cfg.flags |= GPIO_V2_LINE_FLAG_EDGE_FALLING; | |
strncpy(req.consumer, consumer_id, sizeof(req.consumer)); | |
if constexpr (enable_debounce) | |
{ | |
auto &debounce = cfg.attrs[cfg.num_attrs]; | |
debounce.attr.id = GPIO_V2_LINE_ATTR_ID_DEBOUNCE; | |
debounce.attr.debounce_period_us = std::chrono::duration_cast<std::chrono::microseconds>( | |
debounce_delay).count(); | |
debounce.mask |= 1 << 0; | |
cfg.num_attrs += 1; | |
} | |
if (dev.dev_ctl(GPIO_V2_GET_LINE_IOCTL, &req)) | |
{ | |
watch.event_fd = req.fd; // file descriptor for requested events | |
std::cout << "Start watching ... (" << watch.event_fd << ")" << std::endl; | |
// initial value | |
{ | |
gpio::v2::line_values_t values; | |
values.mask = 1; // see gpio::v2::line_request_t::offsets | |
values.bits = 0; | |
std::cout << "Get initial ... "; | |
int ret = ::ioctl(watch.event_fd, GPIO_V2_LINE_GET_VALUES_IOCTL, &values); | |
int last_error = errno; | |
if (ret >= 0) | |
{ | |
bool on = values.bits & 1; | |
std::cout << values.bits << ' ' << (on ? "ON" : "OFF") << std::endl; | |
} | |
else | |
{ | |
std::cout << "Error: (" << last_error << ") " << strerror(last_error) << std::endl; | |
} | |
} | |
auto deadline = std::chrono::steady_clock::now() + 20s; | |
pollfd pfd[1]; | |
pfd[0].fd = watch.event_fd; | |
pfd[0].events = POLLIN | POLLPRI; | |
while (std::chrono::steady_clock::now() < deadline) | |
{ | |
int ret = poll(pfd, 1, 1000); | |
int last_error = errno; | |
if (ret < 0) | |
{ | |
std::cout << "Error: (" << last_error << ") " << strerror(last_error) << std::endl; | |
break; | |
} | |
if (ret > 0) | |
{ | |
gpio::v2::line_event_t event; | |
int read_ret = ::read(watch.event_fd, &event, sizeof(event)); | |
last_error = errno; | |
bool ok = read_ret == sizeof(event); | |
if (ok) | |
{ | |
const char * event_name = "unknown"; | |
switch (event.id) | |
{ | |
case GPIO_V2_LINE_EVENT_RISING_EDGE: | |
event_name = "RISING"; | |
break; | |
case GPIO_V2_LINE_EVENT_FALLING_EDGE: | |
event_name = "FALLING"; | |
break; | |
} | |
std::cout << event.offset << " = " << event_name << std::endl; | |
} | |
else | |
{ | |
std::cout << "Error: (" << last_error << ") " << strerror(last_error) << std::endl; | |
break; | |
} | |
} | |
} | |
std::cout << "...Stop watching" << std::endl; | |
::close(watch.event_fd); | |
} | |
else | |
{ | |
std::cout << "Error: request failure - (" << dev.error_code() << ") " << dev.error_message() << std::endl; | |
} | |
} | |
dev.close_dev(); | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment