Folks, Any help would be appreciated. I've Googled, studied, and stared for many hours. Problem: Using poll to watch a GPIO pin for change, poll returns "immediately" and persistently, with no value change, with POLLPRI set. Background: This is a prep stage for implementing a software encoder counter where 2 GPIOs are connected to an external rotary encoder with leads A and B. I am working on a user space quadrature encoder routine based on the Linux driver rotary_encoder.c logic. It didn't work and I traced it to failing to respond to the GPIO edge events using poll. I made a single GPIO interrupt response demonstrator (shown excerpted below) and it doesn't work either. Further, it doesn't work on either Beaglebone Blue or Raspberry Pi 3. Hardware: The Blue shows ' uname -a Linux blue1 4.19.94-ti-r42 #1buster SMP PREEMPT Tue Mar 31 19:38:29 UTC 2020 armv7l GNU/Linux' The RPi shows ' uname -a Linux raspberrypi 4.19.93-v7+ #1290 SMP Fri Jan 10 16:39:50 GMT 2020 armv7l GNU/Linux' Both have the same wheel encoders (Pololu Romi encoders item #3542 or its 2.7v version). Both bots will correctly read the encoders. The Blue using the hardware encoder and the RPi via the software driver rotary-encoder. For this demonstrator, the Blue uses other GPIOs, specifically GP0.3 and GP0.4. Signal changes on these GPIOs are read correctly in the gpioN/value register via manual testing. The RPi uses the same GPIOs (2 or 3) as the encoder driver, but with the driver disabled to eliminate the competition. Furthermore: This has been a long standing software challenge. Googling shows many things one needs to know. GPIOs in sysFS are deprecated, but they still work. Setting an edge is what enables polling on the value to pick up the transition. Initial reads are required to clear initial spurious values. sysFS requires POLLPRI events, since POLLIN always returns for sysFS. sysFS always returns POLLERR which can be ignored. One must LSEEK on the value fd before reading the value. One must read the value, wanted or not. Notes on the demonstrator code: I have been working on a common hardware interface module to support bots built on Blue and RPi. I have long been following StrawsonDesign's librobotcontrol. This code uses a few routines from Strawson. One is a function to tell whether the hardware is Beaglebone or RPI and another to launch a thread. I don't show them here. I have elided all the error handling snippets, replacing them with "gripe" comments. The program follows the StrawsonDesign librobotcontrol rc_test examples. In this case, the main checks inputs, sets up hardware, launches a worker thread, then waits for the user to quit via Ctrl-C. Here the worker thread sets up a repeated poll loop. In the eventual quadrature application, the GPIO events feed the quadrature state machine of rotary-encoder.c. A short timeout is set for my use so the thread can be readily killed by the main application, independent of GPIO arrivals. The version here leaves the GPIOs set up upon completion so we can inspect the state of things. The Blue has pinmux helpers for the user GPIOs on GP0 and GP1 which are used to set them to GPIO mode. The RPi doesn't do that. Apparently user GPIOS are pre-set up. The code produces the same result on both machines. The read after poll always fails with return -1. The event is 0x0a which is POLLPRI | POLLERR. While it does stream fast, I can never see a response to manually fiddling with the physical inputs. It never reports time out. OBTW, if you can see into what is going on here, think about how this works when I switch to SCHED_FIFO. All my bot threads run in this 'realtime' mode. For a software encoder that responds to GPIO interrupts, I think this doesn't present any problem that isn't already presented by all the other threads. On RPi, tho, the kernel catches the GPIO interrupts and runs the quadrature logic, only letting the user's poll get control after a complete turn of +1 or -1 occurs. I presume the rotary-encoder driver runs at kernel priority level, which is below the worker thread. At least, if my app consumes too much CPU, the kernel can't keep with its many functions, one of which is this driver. I have done that in the past. Let's get this code to work, then I'd like help thinking through this and its impact on CPUs like Blue and RPi. Because I'm getting lots of input about the CPU load of a software rotary encoder. So, needless to say, I don't get it. I hope someone can see what I'm missing. I need to get past this and get back to making bots go. /** * @excerpts from my_test_gpio_interrupts.c * as of 19 June 2020 * * Demonstrates use sysFS to respond to * gpio interrupts. Instructions are printed to the screen when called. */
#include <stdio.h> #include <signal.h> #include <fcntl.h> #include <stdlib.h> // for atoi #include <getopt.h> #include <unistd.h> //usleep, nanosleep #include <pthread.h> // read event thread #include <poll.h> // interrupt events in read event thread int running = (1==0); int vehicleId =-1; //set based on model category in init_hw int gpio_value_fd; int gpio_num; int rc_pthread_create(pthread_t *thread, void*(*func)(void*), void* arg, int policy, int priority); float tspec2float(struct timespec tIn); char *GP1_3_pinmux_path = "/sys/devices/platform/ocp/ocp:J15_pinmux/state"; char *GP1_4_pinmux_path = "/sys/devices/platform/ocp/ocp:H17_pinmux/state"; int GP1_3_num = 98; //GPIO3_2 is 3*32+2 int GP1_4_num = 97; //GPIO3_1 is 3*32+1 /** * This is a list of general categories of boards. */ typedef enum rc_model_category_t{ CATEGORY_UNKNOWN, CATEGORY_BEAGLEBONE, CATEGORY_RPI, CATEGORY_RPI3, CATEGORY_PC } rc_model_category_t; rc_model_category_t rc_model_category(void); char *model_category2string(void); // printed if some invalid argument was given static void __print_usage(void) { printf("\n"); switch (rc_model_category()){ case CATEGORY_BEAGLEBONE: printf("-g GP1_pin Required. GP1.3 and GP1.4 are available. Enter 3 or 4.\n"); break; case CATEGORY_RPI: case CATEGORY_RPI3: printf("-g GP1_pin Required. Enter 2 or 3.\n"); break; default: printf("-g gpio pin number Required. Varies by board type. Use caution.\n"); } printf("-h print this help message\n"); printf("\n"); } // interrupt handler to catch ctrl-c void cleanup(int signo){ if (signo == SIGINT){ printf("received SIGINT Ctrl-C\n"); running = (1==0); } } int main(int argc, char *argv[]) { int fd; int c, no_g=(1==1), len, ret, gpio; char buf[80]; void* read_gpio_events(void* ptr); pthread_t event_thread; // parse arguments opterr = 0; while ((c = getopt(argc, argv, "g:h")) != -1){ switch (c){ case 'g': // gpio_pin gpio = atoi(optarg); switch(rc_model_category()){ case CATEGORY_BEAGLEBONE: if (gpio == 3 || gpio == 4){ gpio_num = (gpio == 3?GP1_3_num:GP1_4_num); no_g = (1==0); } else{ no_g = (1==1); printf("GPIO pin on socket GP1 must be 3 or 4. Got %d\n",gpio_num); } break; case CATEGORY_RPI: case CATEGORY_RPI3: if (gpio == 2 || gpio == 3){ gpio_num = gpio; no_g = (1==0); } else{ no_g = (1==1); printf("GPIO pin must be xx or 4xx. Got %d\n",gpio); } break; default: ;//what to do??? }//category switch break; case 'h': __print_usage(); return -1; break; default: __print_usage(); return -1; break; } } // if the user didn't give enough arguments, print usage if(no_g == (1==1)){ __print_usage(); return -1; } // set signal handler so the loop can exit cleanly signal(SIGINT, cleanup); running = (1==1); // initialize hardware first switch (rc_model_category()){ case CATEGORY_BEAGLEBONE: //set up pinmux for gpio mode fd = open((gpio == 3?GP1_3_pinmux_path:GP1_4_pinmux_path), O_WRONLY); if (fd == -1) { printf("Error: open for path %s failed\n",(gpio == 3?GP1_3_pinmux_path:GP1_4_pinmux_path)); return -1; } ret = write(fd, "gpio_pu", 8); if(ret<0){ printf("Error: write pinmux for path %s failed\n",(gpio == 3?GP1_3_pinmux_path:GP1_4_pinmux_path)); close(fd); return -1; } close(fd); //clean up left over open gpios by unexporting them fd = open("/sys/class/gpio/unexport", O_WRONLY); if (fd < 0) { printf("Error: gpio/unexport close out failed\n"); return -1; } snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d", gpio_num); if(access(buf, F_OK)==0){ len = snprintf(buf, sizeof(buf), "%d", gpio_num ); write(fd, buf, len); } close(fd); break; case CATEGORY_RPI: case CATEGORY_RPI3: //clean up left over open gpios by unexporting them fd = open("/sys/class/gpio/unexport", O_WRONLY); if (fd < 0) { printf("Error: gpio/unexport close out failed\n"); return -1; } snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d", gpio_num); if(access(buf, F_OK)==0){ len = snprintf(buf, sizeof(buf), "%d", gpio_num ); write(fd, buf, len); } close(fd); break; default: ;//no action proposed } //press on with exporting fd = open("/sys/class/gpio/export", O_WRONLY); if (fd < 0) { printf("Error: gpio/export initialization failed\n"); return -1; } len = snprintf(buf, sizeof(buf), "%d", gpio_num); ret = write(fd, buf, len); if (ret == -1) { //gripe } close(fd); //set direction to be in snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%i/direction", gpio_num); fd = open(buf, O_WRONLY); if (fd < 0) { //gripe and cleanup via unexport } write(fd, "in", 3); close(fd); //set active_low to be false -> active high snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%i/active_low", gpio_num); fd = open(buf, O_WRONLY); if (fd < 0) { //gripe and cleanup via unexport } write(fd, "0", 2); close(fd); //set edge interrupts for rising snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%i/edge", gpio_num); fd = open(buf, O_WRONLY); if (fd < 0) { //gripe and cleanup via unexport } write(fd, "rising", 7); close(fd); //capture gpio value fd snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%i/value", gpio_num); fd = open(buf, O_WRONLY); if (fd < 0) { //gripe and cleanup via unexport } gpio_value_fd=fd; //do a first read to clear it lseek(gpio_value_fd, 0, SEEK_SET); read(gpio_value_fd, buf, sizeof(buf)/sizeof(char)); printf("initial clearing read of gpio value gave %s\n",buf); //launch the thread to monitor the gpio events //support routine elsewhere, "normal" scheduler and 0 priority //ultimately will use SCHED_FIFO and a realtime priority something like 1--that also requires elevated privaledges ret=rc_pthread_create(&event_thread, read_gpio_events, (void*) NULL,SCHED_OTHER,0); if (ret != 0){ printf("Error %d (22 is EINVAL, 1 is EPERM): pthread_create for Blue read_encoder_events\n",ret); } else { printf("hwLib: read_gpio_events thread created\n"); } // wait until the user exits via ctrl-C while(running == (1==1)){ usleep(500000); } // final cleanup--leave GPIOs set up if False if(1==0){ printf("test_gpio_interrupts: closing gpios\n"); switch (rc_model_category()){ case CATEGORY_BEAGLEBONE: //put pinmux back to default mode fd = open((gpio == 3?GP1_3_pinmux_path:GP1_4_pinmux_path), O_WRONLY); if (fd == -1) { //gripe } else { ret = write(fd, "default", 8); if(ret<0){ //gripe } close(fd); } break; case CATEGORY_RPI: case CATEGORY_RPI3: //no action break; default: ;//no action } //clean up open gpios by unexporting them fd = open("/sys/class/gpio/unexport", O_WRONLY); if (fd < 0) { //gripe } else { snprintf(buf, sizeof(buf), "/sys/class/gpio/gpio%d", gpio_num); if(access(buf, F_OK)==0){ len = snprintf(buf, sizeof(buf), "%d", gpio_num); write(fd, buf, len); } close(fd); } } else { printf("test_gpio_interrupts: leaving gpios open\n"); } printf("\ntest_gpio_interrupts: bye bye\n"); return 0; } #define POLL_TIMEOUT (1*1000) /* 1 seconds */ void* read_gpio_events(void *ptr){ struct pollfd fdset[1]; int ret, len; char buf[80]; fdset[0].fd = gpio_value_fd; fdset[0].events = (POLLPRI|POLLERR); fdset[0].revents = 0; //clearing read done by parent before thread launch printf("gpio event thread is up\n"); while (running == (1==1)) { //printf("calling poll( )\n"); fdset[0].revents = 0; ret = poll(fdset, 1, POLL_TIMEOUT); //printf("returning from poll( ) with return %d and event 0x%x\n",ret,fdset[0].revents); if (ret == -1){ //error printf("poll returned error\n"); return NULL; } if (ret == 0){ //timeout - go around again printf("poll returned time out\n"); } else { if ((fdset[0].revents & POLLPRI) == POLLPRI) { lseek(fdset[0].fd, 0, SEEK_SET); len = read(gpio_value_fd, buf, sizeof(buf)/sizeof(char)); if (len < 1){ printf("gpio event = 0x%x, len = %d\n",fdset[0].revents,len); } else { printf("gpio event = 0x%x, len = %d, value = %s\n",fdset[0].revents,len, buf); } } } } printf("gpio event thread closing\n"); return NULL; } -- For more options, visit http://beagleboard.org/discuss --- You received this message because you are subscribed to the Google Groups "BeagleBoard" group. To unsubscribe from this group and stop receiving emails from it, send an email to beagleboard+unsubscr...@googlegroups.com. To view this discussion on the web visit https://groups.google.com/d/msgid/beagleboard/ee803702-d006-4ca7-93b0-bbe8263eafe9o%40googlegroups.com.