Archive

Posts Tagged ‘C++’

BeagleBone, GPIO, & IRQ

April 15, 2012 9 comments

My last BeagleBone post looked at interfacing the the bone with an incremental rotary encoder. I wasn’t satisfied with the polling loop I used to read inputs from the encoder so I began investigating alternate methods. While reviewing the sysfs GPIO documentation I noticed that …

Inputs can often be used as IRQ signals, often edge triggered but sometimes level triggered. Such IRQs may be configurable as system wakeup events, to wake the system from a low power state.

Later in the documentation it states …

/sys/class/gpio/gpioN/ …

“value” … reads as either 0 (low) or 1 (high). … If the pin can be configured as interrupt-generating interrupt and if it has been configured to generate interrupts (see the description of “edge”), you can poll(2) on that file and poll(2) will return whenever the interrupt was triggered. If you use poll(2), set the events POLLPRI and POLLERR. If you use select(2), set the file descriptor in exceptfds. After poll(2) returns, either lseek(2) to the beginning of the sysfs file and read the new value or close the file and re-open it to read the value.

“edge” … reads as either “none”, “rising”, “falling”, or “both”. Write these strings to select the signal edge(s) that will make poll(2) on the “value” file return.

This file exists only if the pin can be configured as an interrupt generating input pin.

If all this is truly working on the bone then it’s a better method than polling in user space. It should eliminate a bunch of context switching between kernel and user space  in my current code. I had to try this out.

Using poll(2), with a properly configured GPIO, my code could now react to changes generated by the encoder rather than continually polling it on my own. For simplicity I configured the GPIO’s using some shell commands.

echo 70 > /sys/class/gpio/export
echo 71 > /sys/class/gpio/export

echo in > /sys/class/gpio/gpio70/direction
echo in > /sys/class/gpio/gpio71/direction

echo both > /sys/class/gpio/gpio70/edge
echo both > /sys/class/gpio/gpio71/edge

Using a the following C program I monitored a incremental rotary encoder wired to a BeagleBone.

#include <errno.h>
#include <fcntl.h>
#include <poll.h>
#include <stdio.h>

#define A 0
#define B 1

void dump_value(int fd) {
        char buffer[1024];
        lseek(fd, 0, 0);

        int size = read(fd, buffer, sizeof(buffer));
        buffer[size] = NULL;

        printf("\t\t size: %d  buffer: %s\n", size, buffer);
}

void dump_event(int fd, struct pollfd *pfd) {
        short revents = pfd->revents;

        printf("revents: 0x%04X\n", revents);
        if (revents & POLLERR) {
                printf("\t POLLERR  errno: %d\n", errno);

                if (errno == EAGAIN) {
                        printf("\t\t EAGAIN\n");
                }
                if (errno == EINTR) {
                        printf("\t\t EINTR\n");
                }
                if (errno == EINVAL) {
                        printf("\t\t EINVAL\n");
                }
        }

        if (revents & POLLHUP) {
                printf("\t POLLHUP\n");
        }

        if (revents & POLLNVAL) {
                printf("\t POLLINVAL\n");
        }

        if (revents & POLLIN) {
                printf("\t POLLIN\n");

                dump_value(fd);
        }
        if (revents & POLLPRI) {
                printf("\t POLLPRI\n");

                dump_value(fd);
        }
        if (revents & POLLOUT) {
                printf("\t POLLOUT\n");
        }
        if (revents & POLLRDNORM) {
                printf("\t POLLNORM\n");
        }
        if (revents & POLLRDBAND) {
                printf("\t POLLRDBAND\n");
        }
        if (revents & POLLWRNORM) {
                printf("\t POLLWRNORM\n");
        }
        if (revents & POLLWRBAND) {
                printf("\t POLLWRBAND\n");
        }
}

int get_lead(int fd) {
        int value;
        lseek(fd, 0, 0);

        char buffer[1024];
        int size = read(fd, buffer, sizeof(buffer));
        if (size != -1) {
                buffer[size] = NULL;
                value = atoi(buffer);
        }
        else {
                value = -1;
        }

        return value;
}

void main() {
        int fd[2];

        fd[A] = open("/sys/class/gpio/gpio70/value", O_RDONLY);
        fd[B] = open("/sys/class/gpio/gpio71/value", O_RDONLY);

        struct pollfd pfd[2];

        pfd[A].fd = fd[A];
        pfd[A].events = POLLPRI;
        pfd[A].revents = 0;

        pfd[B].fd = fd[B];
        pfd[B].events = POLLPRI;
        pfd[B].revents = 0;

        int lead[2];
        while (1) {
                int ready = poll(pfd, 2, -1);
                printf("ready: %d\n", ready);

                if (pfd[A].revents != 0) {
                        printf("\t Lead A\n");
                        //dump_event(fd[A], &pfd[A]);
                        lead[A] = get_lead(fd[A]);
                }
                if (pfd[B].revents != 0) {
                        printf("\t Lead B\n");
                        //dump_event(fd[B], &pfd[B]);
                        lead[B] = get_lead(fd[B]);
                }

                printf("\t\t A: %d  B: %d\n", lead[A], lead[B]);
        }
}

Using a timeout of -1 the poll will wait indefinitely. The code is simply waiting for an edge event to occur. That would be one of the encoder leads transitioning from low to high or high to low. This produced the following output when I turned the encoder knob.

ready: 2
         Lead A
         Lead B
                 A: 1  B: 1
ready: 1
         Lead A
                 A: 0  B: 1
ready: 1
         Lead B
                 A: 0  B: 0
ready: 1
         Lead A
                 A: 1  B: 0
ready: 1
         Lead B
                 A: 1  B: 1

In the above example, both leads start out on a detent. As I rotate the knob clockwise lead A goes low and then lead B does the same. At this point the knob is half way between detents. As rotation continues A goes high and then B follows. The knob has settled on the next detent.

I noticed two interesting artifacts.

  1. The first poll will always returns immediately with values for all the GPIO’s being polled.
  2. When I turned the knob quickly I did see some polls return changes on both GPIO’s. This means an intermediate state was lost. This isn’t a fatal problem. It just has to be accounted for when monitoring the encoder’s phases and calculating its position.
  3. Once the GPIO value is read it can’t be read again until another edge event occurs. If you try the read function will fail.

I’d like to see how well this new code can keep up with a fast turning encoder. Since nothing is being buffered by the sysfs GPIO kernel driver there’s a very good chance that the user space code still can’t service the edge events quickly enough.

At some point soon I’ll refactor the rotary code in my BeagleBone library to reflect this method.

Advertisements

BeagleBone with a Rotary Encoder

April 11, 2012 8 comments
Rotary Encoder

PEC11 Incremental Encoder

I’ve been looking at a variety of potential input devices for my BeagleBone. adaruit packages a PEC11 Series – 12 mm Incremental Encoder and I decide to give it a try.

This encoder requires ground on the center lead and provides “output” on the two outside leads. As you turn (click) the encoder knob from one detent to another the output leads will signal either high or low. Combining the output from those leads gives you the relative position of the knob. Monitoring the changes in those output tells you if the encoder has been turned clockwise or counter clockwise.

BeagleBone with Rotary Encoder

BeagleBone with Rotary Encoder

I grounded the encoder using a ground pin from the BeagleBones expansion header and wired the two output leads to a GPIO pin of their own. I could now read the value (signal) from each output lead. All this is pretty straight forward stuff when using a BeagleBone. Now for the fun stuff.

I’m using the sysfs GPIO interface to read output from the encoder. This means that I have to set the direction of the GPIO to “in”. Polling the GPIO’s value returns either a “1” or “0”. As you turn the encoder the value of each GPIO will change from 1 to 0 and then back to zero. But they transition from one value to another at different positions as the encoder turns. This is called a quadrature outputs.

Quadrature Output Table

Quadrature Output Table

This quadrature output table is taken from the PEC11’s data sheet. For the untrained (like me) this diagram is a bit tough to understand at first. When the encoder is positioned on a detent both the A and B leads will signal low. As the encoder is turned clockwise (CW) the A lead will start to signal high and then B lead will follow with a high signal. As the encoder continues to turn the A signal goes low and the B signal follows as the encoder arrives at the next detent. The opposite occurs if the encoder is turned counter clockwise (CCW). These signal pairs are called Gray code.

Another twist is that low signal from an encoder lead results in the GPIO’s value returning 1 (ON). A high signal returns a GPIO value of 0 (OFF). These tables describes the Gray code sent from the encoder and the associated values from the GPIO’s.

Clockwise
Encoder Gray Code GPIO Values
Phase A B A B
1 0 0 1 1
2 1 0 0 1
3 1 1 0 0
4 0 1 1 0
Counter Clockwise
Encoder Gray Code GPIO Values
Phase A B A B
1 0 0 1 1
2 0 1 1 0
3 1 1 0 0
4 1 0 1 1

An incremental rotary encoder such as this cannot represent it’s absolute position.  It just signals it’s position relative to the last detent it was on. Tracking the signal changes as it transitions from one detent to another will identify when the encoder travels clockwise or counter clockwise. This encoder has 24 detents (segments).

I wrote a program that constantly polls the two GPIO’s connected to the encoder’s A/B leads. With a very simple finite state machine (a switch statement) I was able to track the rotary of the encoder’s knob. Here’s some of the C++ code.

Rotary::Rotary(unsigned a, unsigned b) : _position(0) {
        A = new GPIO(a);
        B = new GPIO(b);

        A->direction("in");
        B->direction("in");
}

unsigned Rotary::phase() {
    unsigned a = A->value();
    unsigned b = B->value();

    a = (a == 0) ? 1 : 0;
    b = (b == 0) ? 1 : 0;

    return (a << 1) | b;
}

#define DETENT   0
#define CW_1     1
#define CW_2     2
#define CW_3     3
#define CCW_1   -1
#define CCW_2   -2
#define CCW_3   -3

void *Rotary::monitor(void *arg) {
        Rotary *rotary = static_cast<Rotary *>(arg);
        int state = DETENT;
        int last = -1;

        while (rotary->run_state) {
                int value = rotary->phase();

                if (value != last) {
                        switch (state) {
                        case DETENT:
                                if (value == 2) {
                                        state = CW_1;
                                }
                                else if (value == 1) {
                                        state = CCW_1;
                                }
                                break;
                        case CW_1:
                                if (value == 0) {
                                        state = DETENT;
                                }
                                else if (value == 3) {
                                        state = CW_2;
                                }
                                break;
                        case CW_2:
                                if (value == 2) {
                                        state = CW_1;
                                }
                                else if (value == 1) {
                                        state = CW_3;
                                }
                                break;
                        case CW_3:
                                if (value == 1) {
                                        state = CW_2;
                                }
                                else if (value == 0) {
                                        state = DETENT;
                                        rotary->_position++;
                                }
                                break;
                        case CCW_1:
                                if (value == 0) {
                                        state = DETENT;
                                }
                                else if (value == 3) {
                                        state = CCW_2;
                                }
                                break;
                        case CCW_2:
                                if (value == 1) {
                                        state = CCW_1;
                                }
                                else if (value == 2) {
                                        state = CCW_3;
                                }
                                break;
                         case CCW_3:
                                if (value == 3) {
                                         state = CCW_2;
                                }
                                else if (value == 0) {
                                         state = DETENT;
                                         rotary->_position--;
                                }
                          }

                          last = value;
                  }
        }
}

This source code is available at github.

When run in a hard loop the monitor code will consistently track the knobs rotation. I spun the knob as quickly as I could and was unable to make the program miscount detents (clicks). Later when I ran the code in a thread I did see some missed detents.

Finally, you can plug this encoder into a breadboard but it doesn’t go in easily and is prone to pop out.

Categories: BeagleBone, C++ Tags: , ,