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?
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?
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
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.
Hi,
Hi,
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