joan2937 / pigpio

pigpio is a C library for the Raspberry which allows control of the General Purpose Input Outputs (GPIO).
The Unlicense
1.45k stars 407 forks source link

pigpiod_if2: rising edge callback never triggered if pin was high during pigpio_start() #359

Closed mibcat closed 3 years ago

mibcat commented 4 years ago

HI ! I'm running into trouble using a rising edge callback if the pin was high at the time the function pigpio_start() was executed.

In my application an external device is connected to the Raspi with one IRQ output (for which I like to setup a callback on a rising edge) and a RESET input (which is also resetting the IRQ output)

Although I'm always resetting the device (and the pin is definitely low) before the callback() function is executed the callback is never triggered.

Only an explicit pigpio_stop() followed by a pigpio_start() (after the IRQ line was pulled low by the device due to the reset) brings the callback back to work.

Note: this problem seems not to be existent for the Python interface. In Python there is no need to restart the connection to the daemon. The rising edge callback is triggered as expected after the device was reset.

using the latest version:

pigpio version: 76
pigpiod_if version: 17

Bug or did I miss something ?

Best regards, Michael.

guymcswain commented 4 years ago

Although I'm always resetting the device (and the pin is definitely low) before the callback() function is executed the callback is never triggered.

Not sure I fully understand the situation and a code snippet would be very helpful.

mibcat commented 4 years ago

Thanks for your time !

Here is the initial code - if the IRQ line is high a device reset is performed. The functions pinMode(), digitalRead(), digitialWrite() are wrappers using the pigpiod_if2 api ...

  // connect to pigpiod daemon
  int pigpiod_id = pigpio_start(nullptr, nullptr);

  std::cout << "hardware revision number:" << get_hardware_revision(pigpiod_id) << std::endl;
  std::cout << "pigpio version: " << get_pigpio_version(pigpiod_id) << std::endl;
  std::cout << "pigpiod_if version: " << pigpiod_if_version() << std::endl;

  // check current level of IRQ line
  unsigned irq_level = digitalRead(RFM_IRQ_ARDUINO_PIN_NO);
  if (irq_level)
  {
    // initiate RFM chip reset sequence
    printf("RFM IRQ output at high level !\nReset RFM chip to force pin low\n");
    pinMode(RFM_RESET_ARDUINO_PIN_NO, OUTPUT);
    digitalWrite(RFM_RESET_ARDUINO_PIN_NO, false);
    delay(1);
    digitalWrite(RFM_RESET_ARDUINO_PIN_NO, true);
    delay(1);

    // read IRQ line level again
    irq_level = digitalRead(RFM_IRQ_ARDUINO_PIN_NO);
    printf("RFM IRQ output level: %d\n", irq_level);

    // ==> these lines are needed for a proper work of the callback
    pigpio_stop(pigpiod_id);
    printf("connection to daemon stopped\n");
    pigpiod_id = pigpio_start(nullptr, nullptr);
    printf("connection to daemon restarted\n");
    assert("could not connect to pigpiod daemon" && pigpiod_id >= 0);
  }

The output is the same in both cases but as said the callback is only working if the last block (stop & start connection) is used:

hardware revision number:10494082
pigpio version: 76
pigpiod_if version: 17
RFM IRQ output at high level !
Reset RFM chip to force pin low
configure Arduino pin:  7, mode: 0 ... use GPIO pin:  5
RFM IRQ output level: 0

(Note: the output of the callback itself is not shown here.)

Contrary this small Python node is working as expected - there is no need to restart the connection to the daemon:

#!/usr/bin/env python

import pigpio
import time

IRQPIN = 23

def callback(gpio, level, tick):
  print(gpio, level, tick)

pi = pigpio.pi()

pi.callback(IRQPIN, pigpio.RISING_EDGE, callback)

print('set rising edge callback to pin {}\npress ctrl-c to terminate'.format(IRQPIN))

while True: 
    print('pin level: {}'.format(pi.read(IRQPIN)))
    time.sleep(1)

output:

set rising edge callback to pin 23
press ctrl-c to terminate
pin level: 1
pin level: 1
pin level: 1
pin level: 0
pin level: 0
pin level: 0
pin level: 0
pin level: 0
23 1 1966052270
pin level: 1
pin level: 1
pin level: 1
pin level: 1
guymcswain commented 4 years ago

I don't see the callback function getting initialized in the first script. The two scripts ~behave~ are quite ~differently~ different so I'm wondering why you believe the library is at fault.

mibcat commented 4 years ago

yes the C code above shows only the "device reset" you asked for clarification. And the additional Python script was only added to show the different behavior between the C and Python API.

the C callback initialization is done like (interruptPin converted to BCM pin 23)

...
pinMode(interruptPin, INPUT); 
attachInterrupt(interruptPin, isr0, RISING);
...
void pinMode(unsigned pin, WiringPinMode mode)
{
  printf("configure Arduino pin: %2d, mode: %d ... ", pin, mode);

  unsigned pin_number = convertArduinoPin(pin);

  switch (mode)
  {
    case OUTPUT:
      assert(set_mode(pigpiod_id, pin_number, PI_OUTPUT) == 0);
      assert(set_pull_up_down(pigpiod_id, pin_number, PI_PUD_OFF) >= 0);
      break;

    case INPUT:
    case INPUT_FLOATING:
      assert(set_mode(pigpiod_id, pin_number, PI_INPUT) == 0);
      assert(set_pull_up_down(pigpiod_id, pin_number, PI_PUD_OFF) >= 0);
      break;

    case INPUT_PULLDOWN:
      assert(set_mode(pigpiod_id, pin_number, PI_INPUT) == 0);
      assert(set_pull_up_down(pigpiod_id, pin_number, PI_PUD_DOWN) >= 0);
      break;

    case INPUT_PULLUP:
      assert(set_mode(pigpiod_id, pin_number, PI_INPUT) == 0);
      assert(set_pull_up_down(pigpiod_id, pin_number, PI_PUD_UP) >= 0);
      break;

    default:
      // mode not supported
      assert(false);
  }
  printf("use GPIO pin: %2d\n", pin_number);
}
void attachInterrupt(unsigned pinNo, void (*func)(void), int mode)
{
  unsigned pin_number = convertArduinoPin(pinNo);

  unsigned edge {};

  switch (mode)
  {
    case CHANGE:
      edge = EITHER_EDGE;
      break;

    case RISING:
      edge = RISING_EDGE;
      break;

    case FALLING:
      edge = FALLING_EDGE;
      break;

    default:
      // invalid mode
      assert(false);
      break;
  }

  // support only one interrupt setting
  assert(irq_adapter_func == nullptr);

  // assign given RFM function - will be executed by the irq adapter function
  irq_adapter_func = func;
  assert(irq_adapter_func != nullptr);

  // register adapter function as callback
  rfmirq_id = callback(pigpiod_id, pin_number, edge, irq_adapter);
  assert(rfmirq_id >= 0);

  printf("interrupt for Arduino pin %2d with mode %d registered at %d\n", pinNo, mode, rfmirq_id);
}

Due to a different IRQ function signature of the device driver and pigpiod callback an additional wrapper is needed:

void irq_adapter(int UNUSED(pi), unsigned UNUSED(user_gpio), unsigned UNUSED(level), uint32_t UNUSED(tick))
{
  irq_adapter_func();
}

initialization output:

hardware revision number:10494082
pigpio version: 76
pigpiod_if version: 17
configure Arduino pin:  7, mode: 0 ... use GPIO pin:  5
driver for SPI channel 1 is available @ 1000000 baud
configure Arduino pin:  9, mode: 0 ... use GPIO pin: 26
configure Arduino pin:  2, mode: 2 ... use GPIO pin: 23
interrupt for Arduino pin  2 with mode 3 registered at 0

My assumption is that the "callback edge detection" is only initialized once when the connection to the daemon was started and not when the callback function is registered ...

guymcswain commented 4 years ago

You have a lot going on here that is not pertinent to the problem - which is you are not getting the callback. I suggest you debug why this is so and then if you see a problem with a library API misbehaving distill it down to a simple test case. The fact that the python module is working is a strong case suggesting it is not a library issue.

mibcat commented 4 years ago

OK thanks, I'll try to strip it down to a simple demo to see if the problem still persists ...

mibcat commented 4 years ago

Hi ! I was able to condense the code to a minimum (test_callback.cpp). I also removed the hardware part by "simulating" the reset behavior with a simple Python script (simulate_reset.py) which controls the Raspberry gpio directly. For this a wire connection from BCM 26 to BCM 23 is needed.

The following screen shot shows the real gpio pin states during program execution: Selection_047

Then I stimulate the IRQ input with pigs.

As a result the irq function of the test program is not triggered for the 1st edge but is working for the following edges.

Therefore the title of this issue must be adjusted to "pigpiod_if2: rising edge callback not triggered for the 1st edge if pin was high during pigpio_start()"

==> obviously is there no "edge detection initialization" when a callback function is registered

files.zip

guymcswain commented 4 years ago

First of all, I appreciate the time you've given to make a small test case. Before I attempt to use it I have some questions:

  1. The diagram above has nothing to do with the missed rising edge on IRQ, correct? Sometime later, you create a rising edge on IRQ (using pigs w 23 1) and the callback does not fire, meaning it doesn't print "IRQ callback" to stdout. But on subsequent rising edges of IRQ the callback fires as you expect it. Is my understanding of your of script/method correct?

  2. Earlier you stated:

My assumption is that the "callback edge detection" is only initialized once when the connection to the daemon was started and not when the callback function is registered ...

I don't believe that is true. After you register the callback, the daemon begins looking for changes on that pin. The pin changes levels are buffered so processing begins as much as 120 msec earlier in time. I think it would be best to hold reset initially for > 120msec before registering your callback. [Edit: But in this test scenario, if you generate the rising edge >> 120msec after registering callback, it should work as expected.]

mibcat commented 4 years ago

dito: thanks for your time and your fast responding time !! :thumbsup:

The diagram above has nothing to do with the missed rising edge on IRQ, correct? Sometime later, you create a rising edge on IRQ (using pigs w 23 1) and the callback does not fire, meaning it doesn't print "IRQ callback" to stdout. But on subsequent rising edges of IRQ the callback fires as you expect it. Is my understanding of your of script/method correct?

Yes, your understanding is correct:

I don't believe that is true. After you register the callback, the daemon begins looking for changes on that pin. The pin changes levels are buffered so processing begins as much as 120 msec earlier in time. I think it would be best to hold reset initially for > 120msec before registering your callback. [Edit: But in this test scenario, if you generate the rising edge >> 120msec after registering callback, it should work as expected.]

Because I'm generating the IRQ edges "by hand" there is definitely a delay in the range of seconds from registering the callback until my first IRQ edge :smile:

mibcat commented 4 years ago

if you like to run it on your raspi please follow these steps: (in my case the build output of the test_callback.cpp is called pigpio-api-test)

  1. connect BCM26 with BCM23
  2. start Python script simulate_reset.py in 1st console
  3. start binary pigpio-api-test in 2nd console
  4. stimulate BCM26 in 3rd console via pigs
    
    pigs m 26 w # set BMC26 to output

pigs w 26 1 # set BCM26 level to high --> console 2 shows no irq output message (-> edge not detected) pigs w 26 0 # set BCM26 level to low

pigs w 26 1 # set BCM26 level to high --> console 2 shows "IRQ callback" pigs w 26 0 # set BCM26 level to low

guymcswain commented 4 years ago

What is your compile command? I'm getting error compiling test_callback.cpp

mibcat commented 4 years ago

It's a Eclipse CDT project and I do cross compiling for ARM but the followings lines should work using the installed g++ compiler on the Raspi.

arm-linux-gnueabihf-g++ -std=c++1y -O0 -g3 -Wall -Wextra -c -fmessage-length=0 -o test_callback.o test_callback.cpp

arm-linux-gnueabihf-g++ -o "pigpio-api-test"  ./test_callback.o   -lpigpiod_if2 -lrt

Assuming that pigpio is correctly installed on your machine :smile: I removed the paths to my cross compiled lib from the options:

compiler -I <path to pigpio header> linker -L <path to pigpio libs>

Hope this works for you ...

mibcat commented 4 years ago

just to be sure there are no show-stopper: are you able to build the test_callback.cpp ?

guymcswain commented 4 years ago

Have you compiled and tested it on a raspberry pi? If so, provide the complete instructions used to run this test.

mibcat commented 4 years ago

no, it was compiled on my development pc and yes I've tested it on a raspi3b.

The build instruction is pretty the same as written above (except using g++ and the linker option -lrt seems not be needed on the raspi)

Just tested successfully the build on my raspi with the following sequence:

  1. copy the file test_callback.cpp to a target location on the raspi
  2. from this location execute the following two commands in a terminal:
    g++ -std=c++1y -O0 -g3 -Wall -Wextra -c -fmessage-length=0 -o test_callback.o test_callback.cpp
    g++ -o "pigpio-api-test"  ./test_callback.o   -lpigpiod_if2

    Of cources this assumes that pigpio is installed on that raspi.

At the end you should find the binary pigpio-api-test.

pi@raspi3b:~/test $
> ll
total 372
drwxr-xr-x  2 pi pi   4096 Jul 10 18:21 ./
drwxr-xr-x 12 pi pi   4096 Jul 10 18:09 ../
-rwxr-xr-x  1 pi pi 149552 Jul 10 18:21 pigpio-api-test*
-rw-r--r--  1 pi pi   2509 Jul 10 18:08 test_callback.cpp
-rw-r--r--  1 pi pi 215324 Jul 10 18:21 test_callback.o
guymcswain commented 4 years ago

simulate_reset.py set BCM26 pin high so your first pigs command, pigs w 26 1, doesn't generate a rising edge. This seems to be an invalid test case.

mibcat commented 4 years ago

But pin BCM26 is reset by the same Python script (initiated by test_callback.cpp via BCM5) long before pigs w 26 1 command is typed in the console. As shown in the graph above the pin is definitely low at the time pigs is used.

guymcswain commented 4 years ago

I'm marking this issue as invalid and will close if I don't hear back with a definite test case showing that a problem exists.