Electronic – Low Jitter and Latency on signal trigger using WFI / WFE on STM32F405

interruptslatencystm32f4

I have a hobby project using a STM32F405 MCU, which is intended to respond to a fast clock signal (~1Mhz) connected to Pin 1 of GPIOA. For every transition of the input (both rising and falling), I need to execute a short program which accesses address and data on the bus, and takes the appropriate action. The MCU is running at full speed (168 MHz), which gives me approximately 84 clock cycles to process each phase of the external clock.

I've looked into several approaches, and determined that using an Interrupt is not feasible. There is at least 24 clock cycles overhead of servicing an Interrupt, which would waste close to 30% of my available cycles.

The next approach is a polling loop, which works fine and only wastes 5-8 clocks. Basically the cycles needed to poll, plus GPIO latency as it runs at half the CPU speed. The main problem with this is jitter, as the change can happen at any point during the 5-8 clock cycles it takes to do a poll. So the result is that the response to the signal will have a jitter of up to 8 cycles (10%). This is doable, but I need to compensate to the varying jitter by burning off some more cycles using NOPs. This is needed in order to insure the response does not happen within the acceptable time frame.

However, reading a guide on interrupt latency, this guide claims that WFI or WFE can respond very quickly to an event or interrupt. The idea is to have the EXTI generate an interrupt or event (what ever works) on each transition of the external clock. Then I simply use __WFI() or __WFE() to wait for this event, and then immediately start processing. The guide I mention claims a wakeup from a WFI takes 4 cycles, but the point is that the number is consistent, giving me a very low number of wasted cycles and as little jitter as possible.

Now my problem is that I cannot make this work correctly at all. As mentioned, I do not want the ISR to be executed, so I disable all global interrupts before I start waiting.

First I tried WFI; Setting up the BASEPRI register to only let through interrupts of priority 1 (set it to value 2), and further set the priority of the EXTI interrupt in NVIC to 1. But if the interrupt is in fact generated by the GPIO when WFI is called, it does not wake up the CPU. I did a short test by setting the Systick IRQ Priority to 1, and this DOES wake it up. So it seems the EXTI interrupt signal is not working when WFI is called.

Finally, I've tried to use WFE instead, by setting up the EXTI line to generate an Event instead of an Interrupt. However, this exits immediately on the first call (apparently due to a unintended pending event?), and then hangs indefinitely on the second call. It does not respond to transitions of the GPIO pin.

So my questions are as follows:

  • Has anyone successfully used WFI or WFE on this (or a similar) MCU for a similar purpose and made it work?
  • The STM32F4 documentation does not provide any information about the time (clock cycles) it takes to wake up from a WFI/WFE (if it was working). So I don't even know if this will actually achieve my goal of a fast response and predictable jitter. Does anyone have any information on this?

Best Answer

It seems the concept of Events is far simpler than what I assumed from reading the documentation. It is really confusing and not very clear. An event turns out to simply be (on an STM32F4xx) a signal which can wake up the CPU from sleep. This signal is completely independent of Interrupts. You can configure any EXTI to generate an event. And that's basically it; when configured, it will wake up the MCU from WFE, and not generate an IRQ request (unless you explicitly enable EXTI IRQ as well).

So, the reason it did not work, was first that I did not fully understand how it was supposed to work. Secondly "libopencm3" enables BOTH IRQ and Events for EXTI lines (there is no way to enable one or the other), further adding to my confusion. What works is as follows:

  • Configure EXTI Line Source
  • Set EXTI Line Trigger type
  • Enable Event for EXTI line (do not enable EXTI IRQ nor EXTU IRQ in NVIC)
  • Disable Global Interrupt (Or else interrupts will also wake up WFE)
  • WFE

When the EXTI then triggers, the code will continue from the next line after the WFE instruction.

When it comes to speed, it was not as impressive as I hoped, but not too bad either. It seems waking up from a WFE takes approximately 12 cycles from when the event happens until I can update an output. There is a 2 cycle delay on GPIO input and output, and it takes 2 cycles to write to it (as the data is pre-loaded before the WFE call), meaning 6 cycles is used outside of the WFE call. This leaves approximately 6 cycles delay, solely due to WFE. (This is approximate, and was measured with a 40 Mhz oscilloscope).

Comparing this to a Polling Loop, the polling loop was in fact not that much faster on average. However, the Polling results in a quite noticeable jitter on the output, meaning for consistent timing the WFE approach is far superior.

Finally, according to the same guide as mentioned in the original post, it should take 12 cycles to service an interrupt. However, this figure is far from what I observe on my scope (closer to 20 cycles). In addition, a comparable number of cycles would be wasted on IRQ exit, meaning it will be both slower to respond and I can do less operations pr. trigger.

Related Topic