Doing I/O from an interrupt handler

cancel
Showing results for 
Show  only  | Search instead for 
Did you mean: 

Doing I/O from an interrupt handler

Jump to solution
1,674 Views
durga_choudhury
Contributor V

The relevant parameters for this question are as follows:

1. IDE: S32 DS for ARM v 2.2 (Yes, I know this is old, but so is the product in question; we need to support the existing code)

2. Chip: S32K148

3. OS: Windows 11

4. System OS: Bare metal

 

Attempting to do any kind of SPI Master transfer in an ISR hangs, and usually the API that was interrupted is a blocking I/O (I2C or SPI) call.

 

On a blocking call (to LPSPI_DRV_MasterTransferBlocking() ), the call hangs because sysTick interrupts do not seem to be happening, so the MCU does not get a sense of elapsing time.

 

On a non-blocking call (LPSPI_DRV_MasterTransfer() followed by LPSPI_DRV_MasterGetTransferStatus(SPI1, &byte_remaining) ), byte_remaining stays at the original value. I have attempted to add a call to  LPSPI_DRV_MasterAbortTransfer(SPI1) before calling LPSPI_DRV_MasterTransfer() for the async transfer, but the result is the same.

 

On a semi-related question, what triggers the SysTick_Handler() ? My intuition says it should be running all the time increasing the tick count every 1ms, but putting a break point in the body of it seems to hit only some time. Strangely, it is sometimes not hit even when a blocking SPI transfer is successful, which should be arming the system timer if it was unarmed before.

 

Another semi-related question is that in the S32 Cookbook example code for SPI, there are no use of the SDK provided APIs; the examples do all the register bit banging directly. Are there any examples that are closer to production worthy code, in that they at least call the NXP APIs instead of re-inventing the wheel?

0 Kudos
Reply
1 Solution
1,529 Views
PetrS
NXP TechSupport
NXP TechSupport

Hi,

even though it now “works” you should still discourage doing the SPI transfer inside the ISR. 
If you ever change priorities later, or add another peripheral, you can easily re‑create the deadlock condition you just diagnosed. The safest design would be always to keep ISRs short and defer real work to the main loop.

Use the ISR only as a trigger; perform the SPI transfer synchronously in main() or as a non‑blocking transfer initiated in the ISR but completed by the LPSPI ISR, and main waits for the completion flag.

BR, Petr

View solution in original post

0 Kudos
Reply
5 Replies
1,623 Views
PetrS
NXP TechSupport
NXP TechSupport

Hi,

Blocking I/O from an ISR won’t work with the S32K1 SDK. LPSPI_DRV_MasterTransferBlocking() waits on an OSIF semaphore; in ISR context there’s no place to block and no tick/time base to advance, so it hangs. Use the non‑blocking API (LPSPI_DRV_MasterTransfer) and complete the transfer in the LPSPI ISR/callback, then signal a task/main loop. Also, SysTick fires on timer wrap, but its handler only runs when not masked and with sufficient NVIC priority—so if PRIMASK is set or you’re in a higher‑priority ISR, it will be deferred. 

The cookbook projects are deliberately lightweight, showing peripheral use directly without the higher‑level driver abstractions; they’re intended as learning starting points, not production usage. If you want NXP driver‑style examples, look to SDK, or rather RTD driver/example projects rather than the cookbook.
 
BR, Petr
0 Kudos
Reply
1,607 Views
durga_choudhury
Contributor V

Hello @PetrS 

 

Thank you for your response, but I am afraid I don't really understand it.

 

What you seem to be saying is that doing blocking I/O inside an ISR is a bad idea, and I agree. But why should it not work? The tick counter increments in an interrupt handler that priority -1, that is higher than any I/O interrupt priority. And I am not changing any interrupt mask in my code; does the driver change it under the hood?

 

Our application is bare metal (single threaded), so waiting in an ISR context and application context is pretty much the same thing. The only difference is that in an ISR context, other I/O interrupts will be blocked. But this is exactly what I want anyway. The timer ISR, however, should not be blocked because of the higher priority. I have tried adding a call to ENABLE_INTERRPUTS() on the first line of the ISR, but it made no difference.

 

I have tried the non-blocking version also, and that also hangs, in a different way. On a non-blocking call (LPSPI_DRV_MasterTransfer() followed by LPSPI_DRV_MasterGetTransferStatus(SPI1, &byte_remaining) ), byte_remaining stays at the original value. I have attempted to add a call to  LPSPI_DRV_MasterAbortTransfer(SPI1) before calling LPSPI_DRV_MasterTransfer() for the async transfer, but the result is the same. Both of the above API calls are made from the ISR. The use case is that if the user presses a button, a GPIO interrupt is fired and it wants to read a value over SPI.

 

One thing worth pointing out again is that the API that the ISR is interrupting is a blocking SPI transfer. Adding a call to LPSPI_DRV_MasterAbortTransfer() in the ISR before starting the new transfer also did not help.

 

I am on a very tight deadline on this issue; any help is greatly appreciated.

Tags (1)
0 Kudos
Reply
1,604 Views
PetrS
NXP TechSupport
NXP TechSupport

Hi,

OK, then simply interrupts priorities could cause this behavior. The blocking API hangs simply because the LPSPI ISR cannot run while the GPIO ISR is active if both interrupts have the same priority. The blocking call waits for the ISR to finish the transfer and post the semaphore - but that ISR never gets CPU time until the GPIO ISR exits. The SDK’s driver indeed finishes transfers in the LPSPI interrupt, and only then posts the semaphore to release the blocking call, so equal‑priority ISRs will deadlock the transfer this way. 

The non‑blocking API behaves the same: if called from a GPIO ISR of equal priority, the LPSPI ISR cannot preempt it, so byte_remaining never changes because the driver state machine is never serviced by the LPSPI IRQ. Only after raising LPSPI to a higher priority (numerically lower value) will the LPSPI interrupt fire immediately, complete the transfer, and allow both blocking and non‑blocking APIs to work as expected. Try 

/* Smaller number = higher priority on Cortex-M */
INT_SYS_SetPriority(SysTick_IRQn, 0);     // if you rely on tick timeouts
INT_SYS_SetPriority(LPSPI1_IRQn, 1);      // must be higher than GPIO
INT_SYS_SetPriority(PORTC_IRQn, 2);       // your button’s PORTx IRQ
 
Note: regardless of the priority fix, it’s still advisable not to use the blocking SPI API inside an ISR, and likewise not to wait inside an ISR for a non‑blocking transfer to finish. The S32K LPSPI driver progresses transfers in its interrupt handler, and long ISRs can prevent other interrupts from running.
 
BR, Petr

0 Kudos
Reply
1,591 Views
durga_choudhury
Contributor V

Hello @PetrS 

 

Thank you very much. The interrupt priority indeed solved the problem. However, I do have a question:

 

I agree that blocking inside an ISR is not a good idea, but what is the alternative in a single threaded application? I could spin in the application context until the SPI transfer is done. That will allow other I/O interrupts to happen (my understanding is that negative priority interrupts will fire anyway; they are not disabled when a positive priority ISR is entered). But in my situation doing overlapping I/O is neither possible nor desirable.

 

1. It is not possible because the S32K148 is the bus master for all the buses. So if it is doing a SPI transfer in an ISR, it cannot possibly drive another I/O bus and therefore cannot trigger interrupts.

 

2. It is not desirable because there could be shared globals between the ISRs, so I don't want to start a new I/O until the current one is complete.

 

For a single threaded bare metal application, what would be your recommendation to handle this situation?

0 Kudos
Reply
1,530 Views
PetrS
NXP TechSupport
NXP TechSupport

Hi,

even though it now “works” you should still discourage doing the SPI transfer inside the ISR. 
If you ever change priorities later, or add another peripheral, you can easily re‑create the deadlock condition you just diagnosed. The safest design would be always to keep ISRs short and defer real work to the main loop.

Use the ISR only as a trigger; perform the SPI transfer synchronously in main() or as a non‑blocking transfer initiated in the ISR but completed by the LPSPI ISR, and main waits for the completion flag.

BR, Petr

0 Kudos
Reply