0

Question: Is anyone able to advise on how to interleave operation of the RPi I2C hardware with general GPIO using libgpiod?

Summary: I'm experiencing some unexpected behaviour with RPi4, gpiod and /dev/i2c-1. Calls to write() on /dev/i2c-1 are successful as long as I do not use gpiod library before I open() the /dev/i2c device. I used strace. After using gpiod any calls to open() and ioctl() on /dev/i2c-1 succeed but a write() on /dev/i2c-1 will block for about 1 second then fail with ETIMEDOUT. Also the scope shows no activity on the SDA SCL lines. These symptoms are reproduced even if the gpiod usage took place in a process (program) that has already terminated.

Hardware: Raspberry Pi 4 Model B
debian_version: 11.3
os-release: 11 (bullseye)

Minimum Working Example:

Here is a minimal I2C application

#include <errno.h>
#include <error.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <unistd.h>

int main(void)
{
  int retval;
  int fd;
  const unsigned int i2c_addr = 5;
  const unsigned char payload[] = { 0xAA, 0x55, 0xAA };

  errno = 0;
  fd = open("/dev/i2c-1", O_RDWR);
  if(fd == -1){
    error(1,errno,"Call to open(\"/dev/i2c-1\") returned -1 and set errno = %d", errno);
  }
  else{
    fprintf(stdout,"i2c_app: Successful call of open(\"/dev/i2c-1\")\n");
  }

  errno = 0;
  retval = ioctl(fd, I2C_SLAVE, i2c_addr);
  if(retval == -1){
    error(1,errno,"Call to ioctl(fd) returned -1 and set errno = %d", errno);
  }
  else{
    fprintf(stdout,"i2c_app: Successful call of ioctl(fd)\n");
  }

  errno = 0;
  retval = write(fd, &(payload[0]), sizeof(payload));
  if(retval == -1){
    error(1,errno,"Call to write(fd) returned -1 and set errno = %d", errno);
  }
  else{
    if(retval != sizeof(payload)){
      error(1,0,"Call to write(fd) failed. Expected = %zd Observed = %d", sizeof(payload), retval);
    }
    else{
      fprintf(stdout,"i2c_app: Successful call of write(fd)\n");
    }
  }

  errno = 0;
  retval = close(fd);
  if(retval == -1){
    error(1,errno,"Call to close(fd) returned -1 and set errno = %d", errno);
  }
  else{
    fprintf(stdout,"i2c_app: Successful call of close(fd)\n");
  }

  return 0;
}

Here is a minimal libgpiod application

int main(void)
{
  int retval;
  struct gpiod_chip * chip = NULL;
  struct gpiod_line * gpio2 = NULL;
  struct gpiod_line * gpio3 = NULL;

  errno = 0;
  chip = gpiod_chip_open("/dev/gpiochip0");
  if(chip == NULL){
    error(1,errno,"Call to gpiod_chip_open() returned NULL and set errno = %d", errno);
  }
  else{
    fprintf(stdout,"gpio_app: Successful call of gpiod_chip_open(\"/dev/gpiochip0\")\n");
  }

  errno = 0;
  gpio2 = gpiod_chip_get_line( chip, 2);
  if(gpio2 == NULL){
    error(1,errno,"Call to gpiod_chip_get_line(chip,2) returned NULL and set errno = %d", errno);
  }
  else{
    fprintf(stdout,"gpio_app: Successful call of gpiod_chip_get_line(chip,2)\n");
  }

  errno = 0;
  gpio3 = gpiod_chip_get_line( chip, 3);
  if(gpio3 == NULL){
    error(1,errno,"Call to gpiod_chip_get_line(chip,3) returned NULL and set errno = %d", errno);
  }
  else{
    fprintf(stdout,"gpio_app: Successful call of gpiod_chip_get_line(chip,3)\n");
  }

  errno = 0;
  retval = gpiod_line_request_output(gpio2, "gpio_app", 1);
  if(retval == -1){
    error(1,errno,"Call to gpiod_line_request_output(gpio3) returned -1 and set errno = %d", errno);
  }
  else{
    fprintf(stdout,"gpio_app: Successful call of gpiod_line_request_output(gpio2)\n");
  }

  errno = 0;
  retval = gpiod_line_request_output(gpio3, "gpio_app", 1);
  if(retval == -1){
    error(1,errno,"Call to gpiod_line_request_output(gpio3) returned -1 and set errno = %d", errno);
  }
  else{
    fprintf(stdout,"gpio_app: Successful call of gpiod_line_request_output(gpio3)\n");
  }

  errno = 0;
  retval = gpiod_line_set_value(gpio2, 1);
  if(retval == -1){
    error(1,errno,"Call to gpiod_line_set_value(gpio2) returned -1 and set errno = %d", errno);
  }
  else{
    fprintf(stdout,"gpio_app: Successful call of gpiod_line_set_value(gpio2)\n");
  }

  errno = 0;
  retval = gpiod_line_set_value(gpio3, 1);
  if(retval == -1){
    error(1,errno,"Call to gpiod_line_set_value(gpio3) returned -1 and set errno = %d", errno);
  }
  else{
    fprintf(stdout,"gpio_app: Successful call of gpiod_line_set_value(gpio3)\n");
  }

  if(gpio2 != NULL){
    gpiod_line_release(gpio2);
    fprintf(stdout,"gpio_app: Called gpiod_line_release(gpio2)\n");
  }

  if(gpio3 != NULL){
    gpiod_line_release(gpio3);
    fprintf(stdout,"gpio_app: Called gpiod_line_release(gpio3)\n");
  }

  if(chip != NULL){
    gpiod_chip_close(chip);
    fprintf(stdout,"gpio_app: Called gpiod_chip_close(chip)\n");
  }

  return 0;
}

Here's a script to build them

#!/usr/bin/env sh

gcc -Wall -Werror -Wextra -Wno-misleading-indentation -o i2c_app i2c_app.c
gcc -Wall -Werror -Wextra -Wno-misleading-indentation -o gpio_app gpio_app.c -lgpiod

To install, create an empty directory, copy and paste the code as i2c_app.c and gpio_app.c, and run the build script. Use raspi-config to enable the I2C interface as documented at Raspberry Pi Documentation

To reproduce the problem (a) start with a freshly rebooted RPi4 (b) run gpio_app and ensure it reports no errors (c) run i2c_app and observe the failed call to write() with errno=110 "Connection timed out". Running the apps under strace shows more detail on the failure.

Research: The only related post I found was Trouble using spidev after RPi.GPIO but I didn't find it helpful.

Hypothesis: My program calls gpiod. It feels like the gpiod library is holding kernel resources open after my program has terminated. I'm hoping that turns out to be misapplication by me as this is my first experience with libgpiod.

Workaround: The workaround is to reboot the RPi4, perform the /dev/i2c-1 operations, then perform the gpiod operations, and to refrain from using /dev/i2c-1 until after the next reboot. A better workaround to somehow reset libgpiod would be great.

Solutions: Use gpiod only and create a bit-bang I2C implementation on top of gpiod. This would be a bad case of reinventing the wheel.

Background: My system comprises an RPi4, a device under test (DUT) and some software I've written in C running on the Rpi4. The RPi4 provides GND, 3.3V and there are 8 GPIO connected to the DUT. The DUT has two operating modes, mode #1 and mode #2. After power up the DUT is in mode #1 and it is an I2C slave on two of the GPIO lines. A command in mode #1 moves the DUT to mode #2. In mode #2 the DUT is controlled by a non-standard protocol on the 8 GPIO lines.

On the RPi4 I use /dev/i2c-1 to talk to the DUT in mode #1. For mode #2 I use gpiod aka libgpiod to implement the 8 GPIO protocol.

RPi4 controlling DUT in mode #1 over I2C works fine.

Sending the command to switch DUT from mode #1 to mode #2 works fine.

RPi4 controlling DUT in mode #2 over 8 GPIO lines works fine.

Cycling the power on DUT (to put it back in mode #1) and attempting to communicate over I2C fails as described in the summary above.

Cheers,
Colin.

4
  • For anyone to help they would likely need a small working program or programs which show the problem. E.g. small C programs with build instructions.
    – joan
    Jul 17, 2022 at 16:09
  • Thanks for pointing out the lack of reprex/mwe, @joan. I've updated the OP.
    – Colin Hall
    Jul 17, 2022 at 19:02
  • You could use pigpio.
    – NomadMaker
    Jul 17, 2022 at 20:06
  • I agree with Joan's comments below. I tried gpiochip as it is supposed to be the replacement for sysfs. Unfortunately this is very limited and you can do I/O but little else. Not sure how pigpio does it but I have my own c routines for I²C & SPI which use the kernel driver (and wrap into more user friendly calls - ioctl is a bit esoteric).
    – Milliways
    Jul 18, 2022 at 0:57

1 Answer 1

1

You are using GPIO2 and GPIO3 with the /dev/gpiochip interface (also known as libgpiod).

The /dev/gpiochip library is incomplete and does not really understand GPIO. It thinks they can only have two modes being INPUT or OUTPUT.

To use GPIO2 and GPIO3 as the I2C bus they need to be configured to be in mode ALT0 (the possible modes for a Pi GPIO are INPUT, OUTPUT, ALT0, ALT1, ALT2, ALT3, ALT4, and ALT5).

Once you have used GPIO2 or GPIO3 with /dev/gpiochip they are no longer in mode ALT0 and will no longer function as part of the I2C bus.

The solution is to use one of the other GPIO libraries to restore the GPIO to mode ALT0.

3
  • Thanks for your insight, @joan.
    – Colin Hall
    Jul 21, 2022 at 20:59
  • I think the sequence I experienced is: - RPi4 boots - Something configures GPIO2 GPIO3 in BCM2711 for ALT0. - I launch i2c_app(). It operates successfully with the BCM2711 configured as it was at boot time. - I launch gpio_app(). It is loaded. Before my main() is executed some libgpiod code is executed to configure the BCM2711. If the "something" above is code in the i2c-dev driver then failure to re-execute at open() is a bug in /dev/i2c-1, isn't it? libgpiod can only be responsible for what it knows it needs to provide service.
    – Colin Hall
    Jul 21, 2022 at 21:15
  • The kernel will be setting the mode of the I2C GPIO to ALT0 pretty much at the time the /dev/i2c-0 device is created (part of the boot processing of /boot/config.txt). The driver will assume the mode has been already set when an open/close/read/write of the device is performed.
    – joan
    Jul 21, 2022 at 21:36

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.