Re: GPIO inputs on Pis?

From: Dr. Rolf Jansen <freebsd-rj_at_cyclaero.com>
Date: Thu, 26 Jan 2023 04:04:51 UTC
> Am 25.01.2023 um 13:45 schrieb Karl Denninger <karl@denninger.net>:
> 
> On 1/24/2023 14:15, Dr. Rolf Jansen wrote:
>> Yes, and for this reason, this GPIO event code which was developed by Christian Krämer in the course of the GSoC-2018 and has been submitted in 2020 by Ian Lepore to the freebsd tree is perfect.
>> 
>> Ian tested it with a 10 MHz sqaure wave on an imx6 (ARMv7, 1GHz) and got an event every 10 µs. That means the max. speed without event losses would be 100 kHz. I did not do exact measurements, however, my impression is that my RPi4B can do it a tad faster than my BeagleBone Black. With the RPi4, I needed to improve the debouncing of the encoder and the buttons, because it saw hundreds of bounces which the BBB didn’t. However, it may also be, that the internal GPIO circuits of the BBB have a different damping characteristic.
>> 
>> Anyway for my applications, 100 kHz way faster than what I need.
>> 
>> On my GitHub repository I placed another example on using the GPIO events for the RPi:
>> 
>> https://github.com/cyclaero/shutdd
>> 
>> See also the respective thread on this mailing list:
>> 
>> https://lists.freebsd.org/archives/freebsd-arm/2022-July/001576.html
> So..... just to see  if I'm understanding this correctly (pretty sure I am having read through the test code).
> 
> Presume I have "X" pins configured as inputs and "Y" configured as outputs.  I use the ioctl calls to set the outputs (which works just fine) and can read current input state (which also works.)
> 
> If I set on the same descriptor (not on the specific pins; it applies to all input pins on that descriptor as it appears there's no pin-specific setting in the configuration flags to enable this on a pin-by-pin basis) the event report config type I want I can then select() on the descriptor with the usual timeouts (or zero for a poll) and get a "ready" (much as one would for any other sort of I/O) and, if I do get a "ready" return on the descriptor a read() on that descriptor will return zero or more structures of the type I said I configured, each of which describes either an individual state change on one of configured input pins that is set up for individual event reporting OR a structure of the summary of changes for a given pin.
> 
My understanding of what you wrote above sounds correct. However, I don’t use select(), since I am very comfortable with calling a blocking read() from a loop in a pthread. The advantage with this is, that there is almost no time gap in the user land between reading of the events. Below comes an example

You open the respective GPIO bank (the RPI4 got only 1 while the BBB got 4) for reading.

   gpio_handle_t gpio0;

   if ((gpio0 = gpio_open(0)) != GPIO_INVALID_HANDLE)
   {

You configure the pins, here for a mechanical rotary encoder having a push button facility on the axis. The clock and the button changes shall cause interrupts, while the direction shall be polled. 

      gpio_config_t gcfg = {0, {}, 0, GPIO_PIN_INPUT|GPIO_INTR_EDGE_FALLING};

      // Encoder CLOCK
      gcfg.g_pin = 20;
      gpio_pin_set_flags(gpio0, &gcfg);

      // Encoder BUTTON
      gcfg.g_pin = 26;
      gpio_pin_set_flags(gpio0, &gcfg);

      // Encoder DIRECTION
      gcfg.g_pin = 19;
      gcfg.g_flags = GPIO_PIN_INPUT|GPIO_INTR_NONE;
      gpio_pin_set_flags(gpio0, &gcfg);

Now the event loop:

      ssize_t n, rc, rs = sizeof(struct gpio_event_detail);
      double  t;
      int     c = 0;
      struct  gpio_event_detail buffer[64];

      do
      {
         if ((rc = read(gpio0, buffer, sizeof(buffer))) < 0)
            err(EXIT_FAILURE, "Cannot read from GPIO0");

         if (rc%rs != 0)
            err(EXIT_FAILURE, "%s: read() the odd count of %zd bytes from GPIO0\n", getprogname(), rc);

         else
         {
            n = rc/rs - 1;
            t = nanostamp(buffer[n].gp_time);

            switch (buffer[n].gp_pin)
            {
               case 20:
                  c += (gpio_pin_get(gpio0, 19)) ? +1 : -1;
                  break;

               case 19:
                  break;

               case 26:
               default:
                  break;
            }

            printf("%5d %12.9f\tGPIO0.%u \t%u\t%zd\n", c, t, buffer[n].gp_pin, buffer[n].gp_pinstate, n);
         }
      } while (buffer[n].gp_pin != 26);
   }

Basically, that’s it.

 
> I assume given the 16-bit "count" field on the summary return that counts are "since last returned" and not "since boot" or "since descriptor was opened and configuration set.“
> 
> This also presumes that there is some buffer depth on this that, at some point, may be exceeded so if I "go away" for too long I may miss things -- particularly if I need detail-level reporting.

Yes, and for this reason I like to call blocking reads from within a relatively tight event loop.

> Thus it looks like that its possible for most cases (assuming resolution of the time stamps is high enough and the driver timing is accurate enough, plus the code is sparse enough that actually does the reading) to not just read an optical encoder with this but also (if you use detail reporting) to read a bi-phase encoder so you can determine which direction it is moving.

The computational resolution is nanoseconds, but of course you won’t see interrupts in GHz speed - on a RPI4 expect some hundred kHz. I use the following function for conversion to a double value:

static inline double nanostamp(int64_t stamp)
{
   uint64_t ns = (1000000000*(stamp & 0xFFFFFFFFu) >> 32);
   return (int32_t)(stamp >> 32) + ns*1e-9;
}

> On to check it out; if I missed something here a "heh idiot, no it actually works like this!" would be appreciated :-)

I do poll the direction pin when the clock pin of the encoder cause interrupts. At least for mechanical encoders this is less cumbersome because depending on the debouncing circuit you might see pulse trains for each tick and tack. With respect to the direction it is then hard to figure out whether it is actually 0 or 1.