A Neat Debugging Trick

A Neat Debugging Trick

These days I’m playing with Raspberry Pi Pico using it to send CAN bus data. The platform I’m using is actually the Adafruit CAN Bus Feather that contains an RP2040 processor and a MCP25625 CAN controller communicating over an SPI bus.

For a while work was progressing fine and I wrote my CAN Open protocol modules but at a certain point I wanted to switch from a polling strategy to an interrupt driven one. That’s when the problems started to show up: my controller would exchange a few CAN messages and then would lock up. Not hard lockups but the processor would just sit idle as if no interrupts would come through. To make matters worse, the lockup was not systematic, sometimes it would happily exchange some messages while other times it would just sit there and wait.

After a lot of head-scratching, I decided to hookup a logic analyzer to see what’s going on. When all was good it looked like this:
The yellow (INT0) signal is the controller interrupt signal as detected by the Pico. Unfortunately the Adafruit board is too small to be able to hook any probes on it, so I had to “route” the interrupt signal to one of the GPIO pins through software. In other words the interrupt handler would set the GPIO pin to the state of the interrupt line both when entered and just before leaving:

  void on_interrupt ()
  {
    gpio_put (PIN_SCOPE_INT, gpio_get(CAN_PIN_INT));
    //...
    gpio_acknowledge_irq (CAN_PIN_INT, CAN_GPIO_INT_MASK);
    gpio_put (PIN_SCOPE_INT, gpio_get(CAN_PIN_INT));
  }

When things are working you can see the receive interrupt right after the received CAN packet (interrupt is active low). When the interrupt handler finishes processing, the interrupt line goes high. The processor prepares the response packet and sends it to the CAN controller. When the controller finishes sending the packet, it generates another interrupt that is quickly acknowledged by the processor. The signal marked “idle” is that to show when the processer is in the main idle loop where it just keeps flipping a bit to show it’s still alive.

When things aren’t going well it looks like this:

It is missing the interrupt after the sent packet and all the interrupts after that one.

I was still scratching my head not knowing if maybe I’m not handling right the CAN controller or the interrupt system. As I said, I couldn’t just hook the CAN controller interrupt pin as the board is way too small and if the interrupt was not coming I couldn’t drive the GPIO pin.

At this point I had an idea: the RP2040 has two cores; I could use the second core to monitor the interrupt signal in a tight loop and drive the GPIO pin. In software it looks like this:

void core1 ()
{
  gpio_init(PIN_INT);
  gpio_set_dir(PIN_INT, GPIO_OUT);
  gpio_put(PIN_INT, false);

  while (1)
  {
    gpio_put (PIN_INT, gpio_get(CAN_INTERRUPT));
  }
}

void main ()
{
// ...
  multicore_launch_core1(core1);
//...
}

On the logic analyzer it produces the red trace (INT1) that you see at the bottom. It was clear that the CAN controller was generating the interrupt but the Pico was not responding.

At this point I changed my interrupt configuration from edge-triggered to level-triggered and, just like that, the whole system started to work. I still don’t have a good explanation why the edge-triggering is not working but at least I found a workaround.

I also found a neat trick when working with Pico: you can use one core to debug the other.