Interrupts: #, priority, architecture

@ScruffR, Your original idea about the port number seems to be true, regardless of the fact that the priorities and sub priorities are set to the same values in the HAL. I tested simultaneously triggering interrupts on various pins, and the ISR connected to the pin with the lower port number (regardless of whether the port was A, B, or C) always ran first. I never saw any non-deterministic result. This is the code I used to test,

volatile int x = 4;

void setup() {
    pinMode(D0, OUTPUT); // attached equal length wires from D0 to the two interrupt attached pins
    digitalWrite(D0, LOW);
    pinMode(A0, INPUT);
    pinMode(D3, INPUT);
    attachInterrupt(D3, addOne, RISING);
    attachInterrupt(A0, multiplyByTwo, RISING);

void loop() {
   digitalWrite(D0, HIGH);
   digitalWrite(D0, LOW);
   x = 4;

void addOne() {
    x = x + 1;

void multiplyByTwo() {
    x = x * 2;

Could this be based on the order that the processor reads the port pins?

1 Like

I did get this notion originally out an STM32 programming manual and got the impression that this is “hardwired” due to the order the port pins are fed to the EXTI “muxes” and the EXTI lines are fed into the NVIC logic.
But since I can’t find my original source and with reading up on the NVIC capabilities to dynamically assign priorities I started to doubt my memory :stuck_out_tongue:

I said “stay tuned” for the results of the timing experiments to determine interrupt latency.

With wifi ON:
Most of the time the latency is about 230 clock ticks. That translates to 1.9 microseconds and there’s a lot of variability in that number. The experiment was run a dozen times. Once we saw a latency as long as 33 microseconds. Ouch.

With wifi OFF:
Now we get no excursions to 33 us. The range is from 218 to 236 ticks, which translates to 1.82 us to 1.97 us. That’s an uncertainty band of 18 ticks, or 150us.

That probably kills attaching our sensor pins to Photon pins and using interrupts. I don’t see how we run our application turning wifi on and off while things are running. (Reconnecting can take 5+seconds.) We want better than half microsecond accuracy.

Our ISR is incredibly short and simple. It’s 3 instructions long and the ‘return’ is one of them. (Grab tick count. Store it. Return.) Wish we could run at the top of the interrupt priority list.

Keep in mind that interrupts are queued by priority. There’s nothing to stop you setting up your own interrupt handler, using the NVIC directly and using a higher priority.

1 Like

I was not aware that “normal users” were able to do this! I am glad to hear you say it is possible. We had thought that doing something like this was a major violation of the HAL abstraction.

Is there any documentation on how to do this? Or are there any examples?

Since I have you on the phone and you are a knowledgeable person, let me ask a question to which I have not been able to get an answer. In Photon, what is the maximum number of digital pins that can have an interrupt attached at once? Some people say 4 or 5. I need six.

OK, I don’t take it personal, but @jim_hahn why do you not believe what I’ve said several times about the max. number of attachable interrupts at once?

In the above post I also had a link that shows how to do the interrupt setup manually.

Mat might prove me wrong on the one or the other pin (especially about RX/TX), but no way it’s only five.
I even wouldn’t be surprised if you could even attach interrupts to the pins sharing one EXTI line (for sure you won’t be able to have contradicting trigger-modes or seperate ISRs).

Yes, it does violate the HAL attraction, but keep in mind this abstraction is mainly for system firmware so we can be sure it is portable. For application code,you are free to break portability and use STM32 code directly if you need it.

For examples, see the HAL code that is referenced earlier in this thread. The CMSIS library is how the NVIC interrupts are programmed.

Regarding the maximum number of pin-driven interrupts, ScruffR’s advice still stands - there’s definitely more than 5 interrupt gpio sources - internally there are 16 channels. Not all of these are exposed to external pins.

 * GPIO_PinSource0: A7 (WKP), P1S0, P1S2, B2, B4
 * GPIO_PinSource1: D5, RGBR, P1S1, P1S5, B3, B5
 * GPIO_PinSource2: A2, RGBG, C0, PWR_UC
 * GPIO_PinSource3: D4, A1, RGBB
 * GPIO_PinSource4: D3, A6 (DAC/DAC1), P1S3, RESET_UC
 * GPIO_PinSource5: D2, A0, A3 (DAC2)
 * GPIO_PinSource6: D1, A4, B1
 * GPIO_PinSource7: D0, A5, SETUP_BUTTON
 * GPIO_PinSource8: B0, C5, PM_SCL_UC
 * GPIO_PinSource9: TX, C4, PM_SDA_UC
 * GPIO_PinSource10: RX, C3, TXD_UC
 * GPIO_PinSource11: C2, RXD_UC
 * GPIO_PinSource12: C1, RI_UC
 * GPIO_PinSource13: D7, P1S4, CTS_UC, LOW_BAT_UC
 * GPIO_PinSource14: D6, RTS_UC
 * GPIO_PinSource15: D5, LVLOE_UC

This table includes all pins arranged by source for the Photon, P1, and Electron, so not all of them make sense for the Photon.

I count 13 separate pin sources (including RX/TX) on the Photon.


I don’t know if this is helpful or not, but please take note that hardware interrupts “pend”. I get the sense from this thread that there is a concern about missing one hardware interrupt due to another hardware interrupt occurring on a different pin at the same time. Each time the microcontroller tests interrupts (between machine language instructions), it will end up processing the highest priority interrupt that is pending. But lower priority interrupts continue to pend in hardware registers, and when interrupts return back down the tail chain to the level of a pending interrupt, its ISR will then execute. This means that if you are worried about missing a CHANGE interrupt because it will take too much time to get to it, you need not worry because the hardware detects the CHANGE and pends it in a register, If, on the other hand, you must pick up the precise microseconds time on the interrupts, then you are subject to the variabilities of the environment where multiple, higher priority interrupts can occur (often outside of your control).


BobG: I assume interrupts will never get lost. (!)

My concern was totally with getting the time, via the TICK count, of when my external event took place. We measured latency at 1.9us usually but hopped up to 33us undoubtedly because a higher priority interrupt took place.

I open coded a polling loop which we will run with interrupts off. It takes 143ns to go around this loop so our induced error is in that magnitude. Lots better than 1900ns. Our sensor application wants less than 500ns timing error.

We are afraid of changing the priority level of pin changes by modifying underlying RTOS maintained data structures. And there are not enough Input Capture registers to do the job; we need six.

1 Like

Didn’t take it personal! When my partner and I jumped into the swimming pool named Particle, we we hit with a ton of information. Some of it stuck even if it was wrong. Perhaps it was in a discussion of Core chip that I saw something that said FIVE pins at once. Then I saw an underlying table of interrupt information for the Photon which showed there were five EXTI levels. So I equated them and that helped set the bad idea in concrete.

Another example of this was my partner kept telling me not to use D2. He had early on read, in the Core documentation, that D2 was unavailable for something we needed. Turns out that in the Photon that wasn’t true. But my partner kept bringing it up until we figured out where the false impression came from.

So, sorry - yes, I should have listened to you.


@jim_hahn: 500 ns resolution is getting down into dedicated hardware range, albeit not quite there yet. But I would venture to say that you need a dedicated, fast microcontroller for sure. That means no RTOS, no higher priority interrupts from WiFi, USARTs, internal counter-timers used for micros/millis, etc. If you can figure this out on the Photon, the more power to you. Personally, I’d be inclined to use a dedicated fast microcontroller, such as on the Core or Photon, but dedicated to your code that responds to sensor interrupts and stores the timings and then communicate the results over to a Photon for further processing and Internet communication when there is time between sets of critical measurements (assuming that such times exist). Just a thought, anyway. P.S., I think that some for the Freescale microcontrollers that are meant for automotive ECU use might fit this bill.

1 Like

I’ve been following this thread because I’m also working on a real-time project using the Photon.

I’ve measured the interrupt latency and got the same approximately 1.8us overhead. I can live with that if it were consistent, but it’s not, of course, because other higher priority interrupt routines can be running.

So, a solution might be to raise the interrupt priority of the line I need. The standard API doesn’t provide a way to do this, so (with a fair amount of trepidation) I’ve been looking under the hood, starting with the hints @Ric and @ScruffR offered. And I’ve got an approach which seems it might work … IF all the guesses I’ve made are correct.

Before going into the details: is this doable? Will it work? Is there a better way? I’m new to the Photon, so I’ve had to make a lot of guesses and I’m not very confident that this is even close to right. Review and advice from someone who knows this stuff would be appreciated.


I want to make the interrupt priority on pin D0, say, very high (which I assume means 0 (zero)). (My interrupt service routine isonly 4 or 5 lines, so I think this is safe.)

I think the way to do this is to make a call to NVIC_Init() with a filled in NVIC_InitTypeDef structure. To fill in that structure properly, we have to go from the D0 pin to a “pin source” to an IRQ channel. Here’s the bare bones of the code:

int pin = D0;
uint8_t GPIO_PinSource = PIN_MAP[pin].gpio_pin_source;

NVIC_InitStructure.NVIC_IRQChannel = GPIO_IRQn[GPIO_PinSource];

Whoops, PIN_MAP needs to be filled in first, so doing that comes before the above lines. Here’s the code for that (which I found in “hal_src/core/pinmap_hal.c”):

STM32_Pin_Info* PIN_MAP;
PIN_MAP = HAL_Pin_Map();

For the GPIO_IRQn[] array above, I had to cheat because it doesn’t appear to be included in any of the headers that “applications.h” includes and I couldn’t find a way to get a pointer to it the way HAL_Pin_Map() provides a pointer to the PIN_MAP[] array. So I just copied it from “hal/src/core/interrupts_hal.c”. (Yeah, yeah, I know, this is very bad practice, akin to using “proto-matter”, but … Is there something better?)

Then there’s this code from “firmware/bootloader/src/photon/stdperiphdriver/misc.c”.:

if(GPIO_PinSource > 4)
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 14;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 13;

which I would modify with a priority of, say, zero. (FWIW, the definition of NVIC_InitTypeDef can be found in

Then all that’s left is the call to NVIC_Init().


So, again, is there anyone with experience with this level of code who can advise? Thanks!

1 Like

@randyenger, you’ve done some homework there :+1:
I’ve only browsed over your post, but you are on the right track there.

One thing I already noticed though.
Since you are using the distinction if(GPIO_PinSource > 4) ... else ... I’m not sure if you actually looked at the Photon code. I think to remember this was done one the Core (see code block at the end).
Take the above together with this info here, and you should be good

In order to circumvent the time loss due to multiple levels of indirection you might have to provide the ISR in a certain way too - e.g. you need to use a predefined name.

I’ve reformatted your code blocks too.
The correct symbol to mark code blocks would be the “grave accent” ( ` ) rather than the apostrophe ( ’ ).
I’m not sure where to find this on your keyboard but it usually only appears after you typed another letter (e.g. a vowel where it will be placed above - with other letters - or a blank - it will be put in front).

[interrupt_hal.c in firmware/hal/src/stm32f2xx][1]

click to show code ```cpp void HAL_Interrupts_Attach(uint16_t pin, HAL_InterruptHandler handler, void* data, InterruptMode mode, void* reserved) { uint8_t GPIO_PortSource = 0; //variable to hold the port number

//EXTI structure to init EXT
EXTI_InitTypeDef EXTI_InitStructure = {0};
//NVIC structure to set up NVIC controller
NVIC_InitTypeDef NVIC_InitStructure = {0};

//Map the Spark pin to the appropriate port and pin on the STM32
STM32_Pin_Info* PIN_MAP = HAL_Pin_Map();
GPIO_TypeDef *gpio_port = PIN_MAP[pin].gpio_peripheral;
uint16_t gpio_pin = PIN_MAP[pin].gpio_pin;
uint8_t GPIO_PinSource = PIN_MAP[pin].gpio_pin_source;

//Clear pending EXTI interrupt flag for the selected pin

//Select the port source
if (gpio_port == GPIOA)
GPIO_PortSource = 0;
else if (gpio_port == GPIOB)
GPIO_PortSource = 1;
else if (gpio_port == GPIOC)
GPIO_PortSource = 2;
else if (gpio_port == GPIOD)
GPIO_PortSource = 3;

// Register the handler for the user function name
exti_channels[GPIO_PinSource].fn = handler;
exti_channels[GPIO_PinSource].data = data;

//Connect EXTI Line to appropriate Pin
SYSCFG_EXTILineConfig(GPIO_PortSource, GPIO_PinSource);

//Configure GPIO EXTI line
EXTI_InitStructure.EXTI_Line = gpio_pin;//EXTI_Line;

//select the interrupt mode
EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;
switch (mode)
//case LOW:
//There is no LOW mode in STM32, so using falling edge as default
//EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;
case CHANGE:
//generate interrupt on rising or falling edge
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising_Falling;
case RISING:
//generate interrupt on rising edge
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Rising;
//generate interrupt on falling edge
EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;

//enable EXTI line
EXTI_InitStructure.EXTI_LineCmd = ENABLE;
//send values to registers

//configure NVIC
//select NVIC channel to configure
NVIC_InitStructure.NVIC_IRQChannel = GPIO_IRQn[GPIO_PinSource];
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 14;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
//enable IRQ channel
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//update NVIC registers



Surely, if you’re just wanting to capture the time of an event (eg pin transition) then using timer capture is the way to go? The TIMx hardware on the STM32 will do this, where an external pin change will store the timer count and raise an interrupt, meaning you get resolution down to the timer tick. See “input capture mode” in the timer section of the datasheet.

Generally your interrupt will need to fire and reset the hardware before you can capture the next edge, but that might be ok in your application?

(oh sorry, just read your later comment about there not being enough input capture registers… though, maybe some external hardware would help here? Can any of them transition at any time? There are some alternate ways which you could deal with this eg using a high speed SPI and DMA to essentially sample a shift register at a set rate - it’d only be 4MHz to pull in 8 bits at 500ns resolution - then post-process the data to find the transitions. As SPI running on DMA is deterministic timing, you can extract the time from the position of the change in the buffer. Using XORs on the SR input to make the transitions positive-going pulses would mean you’d only see 1’s in the buffer when there was a change which makes buffer processing faster)


Thanks @ScruffR and @hfiennes for your replies.

First about the input capture registers: I haven’t looked at them very closely yet, but I will. Thanks. (Most of my time so far has been spent trying to understand interrupts.)

@ScruffR: yes, much of what I came up with was stolen from or learned from or inspired by the “HAL_Interrupts_Attach()” routine. Because this “attach()” routine (I assume the API routine “attachInterrupt()” goes pretty much right here) sets the preemption priority, I think I’m going to have to do the “NVIC_Init()” call after I make the call to “attach()”. But I’m guessing that I won’t need to set up the “EXTI_InitStructure” or call “EXIT_Init()”. Does that sound right?

About Photon vs. Core: as I was searching for this or that typedef or routine, I’d sometimes end up at a place with “Photon” in the name and sometimes not. Clearly I found the wrong “HAL_Interrupts_Attach()” routine. Is there a way to tell what’s Photon-specific, what’s generic, and what’s specifically not Photon? (And while we’re drifting off topic, a search would sometimes land me in something like:


and sometimes just in


This looks like some version control stuff, but I’m new to ‘git’, too.

(And lastly, sorry about the single quote versus accent grave. Believe it or not, I’ve never used the accent grave and was convinced it wasn’t even on my keyboard – of course it is – so I thought to myself that they must mean single quotes. Thanks for fixing it!)


First, @jim_hahn, since this is your thread, how are you getting on with your own issue?
And I hope you don’t mind this thread being used for answering to other users but in connection with your topic.

In response to


You are right, this has to do with version control.
The way I do search the repo is by cloning them locally (or without git you could download a branch as ZIP) and use the search features provided by the OS rather than trying to find things on GitHub online. This way you’ll also only find files of your currently checkted out branch (avoiding these “funny” paths).
And as for telling if something goes for the Core or Photon (or any of the other devices) there isn’t one way to tell, but you’d need to consider the file path (stm32f1x or core for the Core and stm32f2x or for other devices) or you look at the includes stm32f1x.h vs. stm32f2x.h or with conditional compile directives distinguishing PLATFORM_ID.

No problem about the formatting :wink:

Well, changing the interrupt priority seems to work. I say “seems to” because there could be a higher priority interrupt that hasn’t happened yet. All 50 or so runs of an interrupt timing test got a consistent 1.9us latency. (It’d be nice if the latency were smaller, but it’s easy enough to deal with a consistent value.)

Thanks to @mdma for the suggestion to change the interrupt priority via the NVIC and to @ScruffR for pointing me to the right files.

In case anyone is interested, here’s the code.

First, right after the call to attach the interrupt, we call our routine to bump the priority.

attachInterrupt(D1, ISRtest, FALLING);  // ‘attachInterrupt()’ is part of the Photon API
setInterruptPriority(D1);                        // this is the routine I wrote

Then, this ‘setInterruptPriority()’ routine looks like this:

// This is essentially just ‘HAL_Interrupts_Attach()’ from “spark/firmware/hal/src/stm32f2xx/interrupts_hal.c”
// with the 'exti' code removed.  because we call ‘attachInterrupt()’ first, all the “exti” stuff that’s missing here
// has been done already and we need only to set the priority via the ‘NVIC_Init()’ call.

void setInterruptPriority(int pin)
    STM32_Pin_Info* PIN_MAP = HAL_Pin_Map();

    GPIO_TypeDef* gpio_port = PIN_MAP[pin].gpio_peripheral;
    uint16_t gpio_pin = PIN_MAP[pin].gpio_pin;
    uint8_t GPIO_PinSource = PIN_MAP[pin].gpio_pin_source;

    NVIC_InitTypeDef NVIC_InitStructure = {0};
    NVIC_InitStructure.NVIC_IRQChannel = GPIO_IRQn[GPIO_PinSource];
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 1;
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;

    NVIC_Init(&NVIC_InitStructure);             // update NVIC register. That's it!

A final note: As I mentioned in my earlier post (January 10), I had to cheat to get the GPIO_IRQn[] array because it doesn’t appear to be included in any of the headers that “applications.h” includes and I couldn’t find a way to get a pointer to it the way HAL_Pin_Map() provides a pointer to the PIN_MAP[] array. (In my January 10 post, I said I copied it from “hal/src/core/interrupts_hal.c” but I should have used “hal/src/stm32f2xx/interrupts_hal.c”. It turns out the two tables are identical.)


I’m glad you got something that works for you. :sunglasses: I’ve added configurable interrupt priority to our backlog -


And if you are in search of a way with “much” shorter latency you could go to the first entry point for the interrupt (as pointed out a few posts up.

1 Like