NRF_GPIOTE EVENTS_PORT woes (again)

I have my own code for handling pin interrupts using the NRF_GPIOTE EVENTS_PORT interrupt. I had some issues with this years ago when I was using the nRF52832 chip, which only has one port. The nRF52833 has two ports and I have interrupts on pins in both ports. I've discovered a new problem that seems kind of an inherent flaw with the hardware design.

I've always thought it was a strange system. All input pins are routed through one DETECT signal that, on the rising edge, triggers the EVENTS_PORTS signal that generates the interrupt. The problem is that the DETECT signal can only be brought low by scanning each input pin state inside the interrupt and reconfiguring the SENSE of each pin that would cause it to appear triggered in the LATCH register. After each pass through, you have to check the LATCH register to see if all of the input pins are now in the correct SENSE. If not then you have to keep scanning the pins until you can get all bits in the LATCH register cleared. Only then can the DETECT signal be brought low so that it can transition from low to high to generate another EVENTS_PORT signal. Because of this scheme, you have to always interrupt on both edges of every input and only handle the state that you want.

It's very important to understand that the DETECT signal cannot be queried directly. It can only be queried indirectly via the LATCH register!

As cumbersome as this is, it was at least possible to implement it with the nRF52832. With the nRF52833, having two ports, it appears that this scheme is impossible to implement because there are now two LATCH registers feeding into the one DETECT signal. That means there is no atomic instruction for determining if all latch bits have been cleared. And with no access to the DETECT signal, there is no way to know if both LATCH registers are clear simultaneously.

In fact, it looks like there is a flaw in the documentation. Figure 1 of the GPIO documentation for the nRF52833 shows the same block diagram for the nRF52832, but it isn't really the same. There should be two LATCH registers and two DETECTMODE registers; one per port. I assume the two LATCH registers go through an OR gate to the DETECT signal. In that case, there is no way to determine if the DETECT signal has actually been brought low, so as to re-enable the interrupt generation.

My conclusion is that one cannot safely use both ports for interrupts; only one or the other. Please, someone convince me I'm wrong. This is a significant problem for my application.

  • Hi,

    jsheaney said:
    At the start of your reply, there is an implication that there are two DETECT signals; on per port. At the end, you quote the GPIOTE text, which sounds like there is only one DETECT signal.

    I can understand this is not straight forward, but in short you may say that the two DETECT signals that can be generated from Port0 and Port1 is OR'ed together before input to the GPIOTE module, the GPIOTE module will generate the PORT EVENT when this OR'ed DETECT signal go from low to high. It is also worth mentioning that when configuring to use LDTECT to generate the DETECT signal(s), then there is also some additional circuitry between the OR'gate and the GPIOTE module that is used to trigger a DETECT signal in the following corner case condition: "If one or more bits in the LATCH register are 1 after the CPU has performed a clear operation on the LATCH register, a rising edge will be generated on the LDETECT signal."

    jsheaney said:
    So if P0->LATCH has a bit set and P1->LATCH has a bit set then both have to be cleared in the one ISR. In this scenario, if I clear the bit in P0->LATCH will a new event be generated, even though I haven't cleared P1->LATCH, yet?

    No.

    jsheaney said:
    If so, that means my ISR is going to be re-entered before I service the P1 bits.

    This will never happen, an interrupt will never be interrupted by itself. What can happen is that the PORT event is triggered again during  execution of the GPIOTE handler, in which case the interrupt will be pending and thereby run again afterwards (presuming of course that you cleared the port event BEFORE you started clearing the LATCH registers, and not the other way around).

    jsheaney said:
    If not, then there's a race condition getting both LATCH registers clear.

    There is a possible race condition here, but it can be solved. You just need to ensure that both LATCH registers are 0 the last time you read them by the MCU in the GPIOTE handler. Yes it is possible that they are set after you have read them, but that is not a problem (let me try to explain why). By doing it this way you know that the DETECT signal must have been low at some point (since both LATCH registers were 0), even if it could have changed since you read them. The fact that they both have been 0 at the same time means that any changes afterwards have triggered the DETECT signal and thereby the PORT event (allowing a second GPIOTE handler to be pending).

    What you should _not_ do: "Enter GPIOTE handler. Read and clear Port event if set. Read P0->LATCH, and possible clear it if it's set. Then you read the P1->LATCH, you find it's set and you clear it. Then exit the GPIOTE handler."

    In the above case the problem is the red text, because there is a chance that you will miss any change to P0->LATCH this way. Instead you need to change it to something like this:

    What you should do: "Enter GPIOTE handler. Read and clear Port event if set. (*)Read P0->LATCH, and clear it if it's set. Then you read the P1->LATCH, you find it's set and you clear it. Read both P0->LATCH and P1->LATCH and check they are both 0, if not repeat from (*). Then exit the GPIOTE handler."

    Best regards,
    Kenneth

  • After thrashing this over and over, it seems like the worst case scenario (looking at the actual driver and its order of operations) is that a P0 bit gets set after checking it, but before checking P1. That's the only race condition where it looks like both LATCH registers are clear, but they really are not. In that case, you will exit the ISR, but then the ISR will be immediately re-entered with the P0 bit still set because EVENTS_PORT has already been cleared and set again and the interrupt bit will be pending.

    OK, I believe it! Thanks for your patience!

Related