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.

Reply via email to