Skip to content

Instantly share code, notes, and snippets.

@max-dark
Created January 22, 2024 21:25
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save max-dark/8c8678c65b884c62aca328a5bf57f4f5 to your computer and use it in GitHub Desktop.
Save max-dark/8c8678c65b884c62aca328a5bf57f4f5 to your computer and use it in GitHub Desktop.
Play with Linux GPIO API v2
// 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