Background: it started from having to support many Linux versions, many Raspberry Pi versions and some other Pi flavours. I wanted to find "a library to end all libraries" and of course, failed. But I found that "sysfs" is deprecated and Linux has new ways to access GPIO.
Since those took a while to study and are harder to use than "pigpio", I decided to write a sample program to educate myself, measure performance and maybe help someone else. Caution: the program likely has errors, it might burn your Pi down or do other things. It's a sample / prototype, not a usable tool.
So here comes "gpiotest.cpp".
It should compile with GCC, but for compatibility reasons with my other work, I have used G++. Compile with:
I advise to run it as root, because it tries to change Linux scheduling (more important) and elevate its priority (less important) for its two processes - main thread and reader thread. Without FIFO scheduling, I achieved around 10 KHz of frequency without massive sampling loss, with FIFO scheduling - nearly 50 KHz.
Source code:
Sample output on Raspberry Pi 5, with GPIO 17 and 27 bridged together:
Since those took a while to study and are harder to use than "pigpio", I decided to write a sample program to educate myself, measure performance and maybe help someone else. Caution: the program likely has errors, it might burn your Pi down or do other things. It's a sample / prototype, not a usable tool.
So here comes "gpiotest.cpp".
It should compile with GCC, but for compatibility reasons with my other work, I have used G++. Compile with:
Code:
g++ -std=c++17 -fpermissive -g gpiotest.cpp -pthread -o gpiotest
Source code:
Code:
// GPIO sample program: sends data on GPIO 17, receives on GPIO 27.// Public domain.#include <linux/gpio.h> // everything about GPIOs #include <sys/ioctl.h> // ioctl()#include <fcntl.h> // open(), O_RDONLY#include <unistd.h> // close()#include <stdio.h> // printf()#include <poll.h> // poll()#include <pthread.h> // threading// Chip 0 on older Pi models, chip 4 on Pi 5.#define CHIP "/dev/gpiochip4" // GPIO line numbers.#define WRITE_GPIO 17#define READ_GPIO 27// Termination flag.// This is not an example of good coding style.volatile int TERM = 0;// Data structures from Linux GPIO libs.// These here are global because a thread needs them.// This is not an example of good coding style.struct gpioevent_request event_request;struct pollfd poll_file_descriptor;void *reader(void *arg) { // Elevate reader thread priority and use a FIFO scheduler. pthread_t thread = pthread_self(); const struct sched_param sparam = { .sched_priority = 99, }; int res = pthread_setschedparam(thread, SCHED_FIFO, &sparam); if (res != 0) printf("Failed to elevate reader thread priority!\n"); struct gpioevent_data event_data; poll_file_descriptor.fd = event_request.fd; poll_file_descriptor.events = POLLIN; // Some variables for evaluating throughput __u64 first_event_timestamp = 0; __u64 last_event_timestamp = 0; __u64 count_rising = 0; __u64 count_falling = 0; // Receive loop while (TERM == 0) { int poll_result = poll(&poll_file_descriptor, 1, 1); // time out after 1 milliseconds if (poll_result == 0) { // printf("Poll timeout.\n"); continue; } if (poll_result < 0) { // printf("Poll error.\n"); continue; } if (poll_result > 1) { // printf("Multiple events per poll.\n"); } // The "revents" field counts returned events. // The "POLLIN" constant seems to be a bitmask. if (poll_file_descriptor.revents & POLLIN) { int read_result = read(poll_file_descriptor.fd, &event_data, sizeof(event_data)); if (read_result == -1) { // printf("Read error.\n"); continue; } if (event_data.id == GPIOEVENT_EVENT_RISING_EDGE) { count_rising++; // printf("Rising edge at %llu.\n", event_data.timestamp); } else if (event_data.id == GPIOEVENT_EVENT_FALLING_EDGE) { count_falling++; // printf("Falling edge at %llu.\n",event_data.timestamp); } else { // printf("Some other event?\n"); } if (first_event_timestamp == 0) { first_event_timestamp = event_data.timestamp; } else { last_event_timestamp = event_data.timestamp; } } } printf("Received %llu rising and %llu falling edges.\n",count_rising,count_falling); __u64 duration = last_event_timestamp - first_event_timestamp; double seconds = ((double) duration / (double) 1000000000); printf("Total duration %llu ns (%f s).\n",duration,seconds); __u64 nanos_per_edge = duration / (count_rising + count_falling); printf("Average %llu ns (%llu microseconds) per edge.\n",nanos_per_edge,(nanos_per_edge/1000)); __u64 per_second = count_rising / seconds; printf("Rising edge frequency %llu Hz.\n",per_second); close(poll_file_descriptor.fd); return 0;}int main(int argc, char * const *argv) { int res; // various call results // Elevate main thread priority and use a FIFO scheduler. const struct sched_param sparam = { .sched_priority = 99, }; res = sched_setscheduler(0, SCHED_FIFO, &sparam); if (res != 0) printf("Failed to elevate main thread priority!\n"); // Data structures from Linux GPIO libs. struct gpiochip_info chip_info; struct gpiohandle_request handle_request; int file_descriptor = open(CHIP, O_RDONLY); if (file_descriptor < 0) { printf("Failed opening GPIO chip.\n"); return 1; } res = ioctl(file_descriptor, GPIO_GET_CHIPINFO_IOCTL, &chip_info); if (res < 0) { printf("Failed getting chip information.\n"); close(file_descriptor); return 1; } printf("GPIO chip information:\n"); printf("name: %s\n",chip_info.name); printf("label: %s\n",chip_info.label); printf("lines: %i\n", chip_info.lines); for (int i = 0; i < chip_info.lines; i++) { struct gpioline_info line_info; line_info.line_offset = i; if (ioctl(file_descriptor, GPIO_GET_LINEINFO_IOCTL, &line_info) < 0) { printf("Failed getting line %i info.\n", i); } else { printf("%d %s\n",i,line_info.name); } } // Request events on the reading line. event_request.lineoffset = READ_GPIO; event_request.eventflags = GPIOEVENT_REQUEST_BOTH_EDGES; event_request.handleflags = GPIOHANDLE_REQUEST_INPUT | GPIOHANDLE_REQUEST_BIAS_PULL_DOWN; res = ioctl(file_descriptor, GPIO_GET_LINEEVENT_IOCTL, &event_request); if (res < 0) { printf("Failed requesting events.\n"); close(file_descriptor); return 1; } // Request handle on writing line. Many can be requested instead of one. handle_request.lineoffsets[0] = WRITE_GPIO; handle_request.flags = GPIOHANDLE_REQUEST_OUTPUT | GPIOHANDLE_REQUEST_BIAS_PULL_DOWN; handle_request.lines = 1; res = ioctl(file_descriptor, GPIO_GET_LINEHANDLE_IOCTL, &handle_request); if (res < 0) { printf("Failed requesting write handle.\n"); close(file_descriptor); return 1; } // Start a reader thread pthread_t reader_thread; pthread_create(&reader_thread, NULL, &reader, NULL); // Data handle for writing struct gpiohandle_data hdata; // Time variables for sleeping a short interval. struct timespec ts, tr; ts.tv_sec = 0; printf("------------ now writing and reading -------------------\n"); // Write something out ad hope the reader reads it for (int i = 0; i < 1000; i++) { hdata.values[0] = 1; res = ioctl(handle_request.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &hdata); if (res == -1) printf("Failed setting line value.\n"); // Try to sleep for 5000 nanoseconds. // In reality, due to call latencies, you end up sleeping longer. ts.tv_nsec = 5000; nanosleep(&ts, &tr); hdata.values[0] = 0; res = ioctl(handle_request.fd, GPIOHANDLE_SET_LINE_VALUES_IOCTL, &hdata); if (res == -1) printf("Failed setting line value.\n"); ts.tv_nsec = 5000; nanosleep(&ts, &tr); } close(handle_request.fd); // Tell the thread to finish TERM = 1; // Give it time sleep(1); // Close resources close(file_descriptor); // Rejoin main thread with finished thread pthread_join(reader_thread,NULL); return 0;}
Code:
GPIO chip information:name: gpiochip4label: pinctrl-rp1lines: 540 ID_SDA1 ID_SCL2 GPIO23 GPIO34 GPIO45 GPIO56 GPIO67 GPIO78 GPIO89 GPIO910 GPIO1011 GPIO1112 GPIO1213 GPIO1314 GPIO1415 GPIO1516 GPIO1617 GPIO1718 GPIO1819 GPIO1920 GPIO2021 GPIO2122 GPIO2223 GPIO2324 GPIO2425 GPIO2526 GPIO2627 GPIO2728 PCIE_RP1_WAKE29 FAN_TACH30 HOST_SDA31 HOST_SCL32 ETH_RST_N33 -34 CD0_IO0_MICCLK35 CD0_IO0_MICDAT036 RP1_PCIE_CLKREQ_N37 -38 CD0_SDA39 CD0_SCL40 CD1_SDA41 CD1_SCL42 USB_VBUS_EN43 USB_OC_N44 RP1_STAT_LED45 FAN_PWM46 CD1_IO0_MICCLK47 2712_WAKE48 CD1_IO1_MICDAT149 EN_MAX_USB_CUR50 -51 -52 -53 ------------- now writing and reading -------------------Received 1000 rising and 1000 falling edges.Total duration 16653847 ns (0.016654 s).Average 8326 ns (8 microseconds) per edge.Rising edge frequency 60046 Hz.
Statistics: Posted by diastrikos — Sat Apr 13, 2024 2:49 pm