On Fri, Jun 19, 2020 at 5:18 PM <[email protected]> wrote:

> 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.
>

Please consider using libgpiod.

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 [email protected].
> To view this discussion on the web visit
> https://groups.google.com/d/msgid/beagleboard/ee803702-d006-4ca7-93b0-bbe8263eafe9o%40googlegroups.com
> <https://groups.google.com/d/msgid/beagleboard/ee803702-d006-4ca7-93b0-bbe8263eafe9o%40googlegroups.com?utm_medium=email&utm_source=footer>
> .
>
-- 
https://beagleboard.org/about - a 501c3 non-profit educating around open
hardware computing

-- 
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 [email protected].
To view this discussion on the web visit 
https://groups.google.com/d/msgid/beagleboard/CA%2BT6QPmKoyA_NyQ%3DtPX5zhfoPR2R1UXzZK9gST7bfVeu7WZMzA%40mail.gmail.com.

Reply via email to