[SOLVED] Passing argument in ISR

Thread Starter

MTech1

Joined Feb 15, 2023
181
Hello,

I have a question regarding the handling Interrupt Service Routines (ISRs). Specifically, I'm wondering about the advisability of passing arguments to ISRs or returning values from them.

I understand that ISRs are meant to be fast, efficient, and non-blocking, and I've heard conflicting opinions on whether it's a good idea to pass arguments or return values from ISRs. Some say it can introduce issues like increased execution time, stack usage, and synchronization problems.

However, I'd like to know if there are situations where this is a valid approach or if there are recommended alternatives for communicating between ISRs and the main program.

I'd greatly appreciate your advice.
 

MrChips

Joined Oct 2, 2009
34,628
That is a good question. I have never done it, i.e. pass arguments. Instead, I always use global variables.
Presumably, arguments can be efficiently passed via CPU registers. I don't see how the interrupt mechanism is going to pass arguments either by value or by reference.

Unless someone else can correct me, you cannot pass arguments in the ISR call.
 

dl324

Joined Mar 30, 2015
18,220
I'd like to know if there are situations where this is a valid approach or if there are recommended alternatives for communicating between ISRs and the main program.
Use global variables and be sure to declare them volatile.
 

BobTPH

Joined Jun 5, 2013
11,463
How is the hardware interrupt mechanism going to know what arguments to pass and what to do with the return value? Arguments and return values make no sense for interrupt handlers. In fact, if either is passed in registers, it breaks every program. An interrupt handler cannot change any registers.
 

Papabravo

Joined Feb 24, 2006
22,058
Neither arguments nor return values make any sense in an interrupt context. You have no control over when the interrupt happens, and you have no idea what will happen when the interrupt returns. It is usually the case that you should have the registers be unchanged from the completion of one instruction to the beginning of the next instruction, regardless of how many instructions might be executed in an ISR between those two instructions. This is particularly true for architectures that set condition codes in one instruction and branch on them in the next instruction.
 

nsaspook

Joined Aug 27, 2009
16,251
I often pass a device handler context pointer variable to an ISR on 32-bit systems controller system (designed for efficient multi-tasking interrupt processing) as a structured alternative to global variables. This allows for several different types of the same general device (several types of IMU chips in this example) to use the same routines as they are detected and setup for use during the controller boot. Return values don't make any sense but argument passing is standard on systems like the Linux kernel as it can be used to abstract hardware to a standard API in a structured way dynamically without messy global variables unless they are absolutely needed.
Some PIC32MK code for this device:
https://forum.allaboutcircuits.com/...tter-for-this-application.193257/post-1818575

C:
   /*
     * IMU data structure for driver
     */
    typedef struct _imu_cmd_t {
        const uint16_t id;
        uint32_t board_serial_id;
        enum device_type device;
        const uint8_t cs, spi_bytes, acc_range_scl;
        uint8_t acc_range;
        uint32_t log_timeout, rs, ss;
        volatile bool online, run, update, features, crc_error, angles;
        uint64_t host_serial_id;
        bool locked, warn, down;
#ifdef BNO086
        uint8_t rbuf[512], tbuf[512];
#else
        uint8_t rbuf[512], tbuf[512];
#endif
        uint32_t rbuf32[2], tbuf32[2];
        uint16_t serial1, serial2;
        op_t op;
        volatile bool init_good;
    } imu_cmd_t;



#include "imupic32mcj.h"

static uint32_t delay_freq = 0;
volatile uint32_t bno08x_int_count = 0;

#ifdef __32MK0512MCJ048__
//void qei_index_cb(QEI_STATUS, uintptr_t);

/*
* on QEI index trigger function
*/
//void qei_index_cb(QEI_STATUS status, uintptr_t context)
//{

//}
#endif

/*
* configure the SPI port bit size for data transfers
*/
uint8_t set_imu_bits(void)
{
    uint8_t imu_bits = 8;

#ifdef BNO086 // mode 3
#define SPI2_CON_MSTEN                      (1 << _SPI2CON_MSTEN_POSITION)
#define SPI2_CON_CKP                        (1 << _SPI2CON_CKP_POSITION)
#define SPI2_CON_CKE                        (0 << _SPI2CON_CKE_POSITION)
#define SPI2_CON_MODE_32_MODE_16            (0 << _SPI2CON_MODE16_POSITION)
#define SPI2_CON_ENHBUF                     (1 << _SPI2CON_ENHBUF_POSITION)
#define SPI2_CON_MCLKSEL                    (1 << _SPI2CON_MCLKSEL_POSITION)
#define SPI2_CON_MSSEN                      (0 << _SPI2CON_MSSEN_POSITION)
#define SPI2_CON_SMP                        (0 << _SPI2CON_SMP_POSITION)

    /* BAUD Rate register Setup */
    SPI2BRG = 19; // 3MHz
#else // mode 0
#define SPI2_CON_MSTEN                      (1 << _SPI2CON_MSTEN_POSITION)
#define SPI2_CON_CKP                        (0 << _SPI2CON_CKP_POSITION)
#define SPI2_CON_CKE                        (1 << _SPI2CON_CKE_POSITION)
#define SPI2_CON_ENHBUF                     (1 << _SPI2CON_ENHBUF_POSITION)
#define SPI2_CON_MCLKSEL                    (1 << _SPI2CON_MCLKSEL_POSITION)
#define SPI2_CON_MSSEN                      (0 << _SPI2CON_MSSEN_POSITION)
#define SPI2_CON_SMP                        (0 << _SPI2CON_SMP_POSITION)

    SPI2BRG = 7; // 8MHz

#ifdef SPI2_32BIT
    imu_bits = 32;
#define SPI2_CON_MODE_32_MODE_16            (3 << _SPI2CON_MODE16_POSITION)
#else
#define SPI2_CON_MODE_32_MODE_16            (0 << _SPI2CON_MODE16_POSITION)
#endif
#endif

    SPI2CONSET = (SPI2_CON_MSSEN | SPI2_CON_MCLKSEL | SPI2_CON_ENHBUF | SPI2_CON_MODE_32_MODE_16 | SPI2_CON_CKE | SPI2_CON_CKP | SPI2_CON_MSTEN | SPI2_CON_SMP);

    return imu_bits;
}

/*
* setup external interrupt #2 for IMU BMA4x0 data update interrupt trigger output
*/
void init_imu_int(const imu_cmd_t * imu)
{
    if (imu) {
        INTCONCLR = _INTCON_INT2EP_MASK; //External interrupt on falling edge
        IFS0CLR = _IFS0_INT2IF_MASK; // Clear the external interrupt flag
        EVIC_ExternalInterruptCallbackRegister(EXTERNAL_INT_2, update_imu_int1, (uintptr_t) imu);
        EVIC_ExternalInterruptEnable(EXTERNAL_INT_2);
    }
}

/*
* user callback function per BMA4x0 data interrupt
* update pacing flag from IMU ISR
*/
void update_imu_int1(uint32_t a, uintptr_t context)
{
    imu_cmd_t * imu = (imu_cmd_t *) context;
    static int8_t i = 0;
    static uint8_t tog = 0;

    if (imu) {
        if (!i++) {

        }
        if (++tog >= 0) {
            imu->update = true;
            tog = 0;
            LED_GREEN_Toggle();
        }
    }
}

/*
* setup external interrupt #2 for IMU BNO08X data update interrupt trigger output
*/
void init_imu_int_bno(const imu_cmd_t * imu)
{
    if (imu) {
        INTCONCLR = _INTCON_INT2EP_MASK; //External interrupt on falling edge
        IFS0CLR = _IFS0_INT2IF_MASK; // Clear the external interrupt flag
        EVIC_ExternalInterruptCallbackRegister(EXTERNAL_INT_2, update_imu_int_bno, (uintptr_t) imu);
        EVIC_ExternalInterruptEnable(EXTERNAL_INT_2);
    }
}

/*
* user callback function per BNO08X data interrupt
* update pacing flag from IMU ISR
* passes an pointer to the IMU data object
*/
void update_imu_int_bno(uint32_t a, uintptr_t context)
{
    imu_cmd_t * imu = (imu_cmd_t *) context;

    if (imu) {
        imu->update = true;
        bno08x_int_count++;
    }
}

/*
* microsecond busy wait delay, 90 seconds MAX
* Careful, uses core timer
*/
void delay_us(uint32_t us)
{
    // Convert microseconds us into how many clock ticks it will take
    us *= delay_freq; // Core Timer updates every 2 ticks
    _CP0_SET_COUNT(0); // Set Core Timer count to 0
    while (us > _CP0_GET_COUNT()) {
    }; // Wait until Core Timer count reaches the number we calculated earlier
}

/*
* start core-timer for delay_us
*/
void start_tick(void)
{
    /* Start system tick timer */
    CORETIMER_Start();
    delay_freq = CORETIMER_FrequencyGet() / 1000000;

    /*
     * software timers interrupt setup
     * using tickCount
     */
    TMR6_CallbackRegister(timer_ms_tick, 0);
    TMR6_Start(); // software timers counter

#ifdef __32MK0512MCJ048__
    TMR9_Start(); // IMU time-stamp counter
    //    QEI2_CallbackRegister(qei_index_cb, 0);
    //    QEI2_Start();
#endif
#ifdef __32MZ1025W104132__
    TMR2_Start(); // IMU time-stamp counter
#endif

#ifdef __32MZ1025W104132__
    cpu_serial_id = USERID & 0x1fffffff; // get CPU device 32-bit serial number and convert that to 29 - bit ID for CAN - FD
#else
    cpu_serial_id = DEVSN0 & 0x1fffffff; // get CPU device 32-bit serial number and convert that to 29 - bit ID for CAN - FD
#endif
}

void canfd_set_filter(uint32_t fil0, uint32_t fil1)
{
    /* Place CAN module in configuration mode */
    CFD1CONbits.REQOP = 4;
    while (CFD1CONbits.OPMOD != 4);
    // disable filters for configuration
    CFD1FLTCON0bits.FLTEN0 = 0;
    CFD1FLTCON0bits.FLTEN1 = 0;
    CFD1TDCbits.TDCMOD = 2;
    CFD1FLTCON0bits.F0BP = 2; // message stored in FIFO2
    CFD1FLTCON0bits.F1BP = 2; // message stored in FIFO2
    // extended identifier address
    CFD1FLTOBJ0bits.EXIDE = 1;
    CFD1FLTOBJ1bits.EXIDE = 1;
    // match mask to address type
    CFD1MASK0bits.MIDE = 1;
    CFD1MASK1bits.MIDE = 1;
    CAN1_MessageAcceptanceFilterMaskSet(0, (~fil0) & 0x1fffffff); // generate mask from ID
    CAN1_MessageAcceptanceFilterMaskSet(1, (~fil1) & 0x1fffffff);
    CAN1_MessageAcceptanceFilterSet(0, fil0);
    CAN1_MessageAcceptanceFilterSet(1, fil1);
    // enable filters after configuration
    CFD1FLTCON0bits.FLTEN0 = 1;
    CFD1FLTCON0bits.FLTEN1 = 1;
    /* Place the CAN module in Normal mode */
    CFD1CONbits.REQOP = 0;
    while (CFD1CONbits.OPMOD != 0);
}

void clear_can_errors(void)
{
    /*
     * clear application error bits
     */
    CFD1INTbits.SERRIF = 0;
    CFD1INTbits.CERRIF = 0;
    CFD1INTbits.IVMIF = 0;
    CFD1INTbits.WAKIF = 0;
    CFD1INTbits.MODIF = 0;
    CFD1INTbits.TBCIF = 0;
}

void set_can_retran(void)
{
    CFD1FIFOCON1bits.TXAT = CFD_RETRAN;
    CFD1FIFOCON2bits.TXAT = CFD_RETRAN;
    CFD1FIFOCON3bits.TXAT = CFD_RETRAN;
    CFD1FIFOCON4bits.TXAT = CFD_RETRAN;
    CFD1CONbits.RTXAT = CFD_RETRAN_MODE;
}
 
Last edited:

BobTPH

Joined Jun 5, 2013
11,463
That is not an interrupt handler, it is a callback function you have registered to be called when the actual interrupt handler, which is deep within the OS is activated.
 

nsaspook

Joined Aug 27, 2009
16,251
That is not an interrupt handler, it is a callback function you have registered to be called when the actual interrupt handler, which is deep within the OS is activated.
Yes, it's a interrupt handler directly controlled by a interrupt pin on the controller. An event (interrupt in this case) handler is a procedure called when an event happens. It can be a callback.
https://microchip-mplab-harmony.github.io/csp/GUID-9F9A93D8-EBEE-47E3-BB1F-604608870337.html
It's a interrupt (that runs in interrupt context and uses the called context variable) callback function (the ISR code called and executed) for an interrupt (falling edge on a pin) that has NO OS running. It's bare metal C programming using xc32 PLIB interface routines.

https://microchip-mplab-harmony.github.io/csp/GUID-86FB2F4C-69EF-48E0-983C-092719C6D123.html
 
Last edited:

BobTPH

Joined Jun 5, 2013
11,463
EVIC_ExternalInterruptCallbackRegisterAllows application to register callback for every external interrupt pin
You are using a library that includes the actual interrupt handler. Your callback may well run at interrupt context but I don’t believe it is directly invoked by the hardware.
 

nsaspook

Joined Aug 27, 2009
16,251
You are using a library that includes the actual interrupt handler. Your callback may well run at interrupt context but I don’t believe it is directly invoked by the hardware.
Directly from the registered interrupt vector or from a routine started from that interrupt vector is IMO an irrelevant point to the utility of passing an argument to the ISR execution code. There is much to be gained by passing state information to a ISR this way if you design the device handler structure with the ability to dynamically handle several, similar in function devices, to a common user API simply by passing one void 32-bit pointer as a argument.
 

nsaspook

Joined Aug 27, 2009
16,251
No one said an interrupt handler could not pass an argument to a function it calls.
That's not the point, people have said "Neither arguments nor return values make any sense in an interrupt context". I agree with return values but arguments (by whatever means) are a pretty standard feature of good systems software for hardware drivers not written for static operation of one specific device.

The OP: "However, I'd like to know if there are situations where this is a valid approach or if there are recommended alternatives for communicating between ISRs and the main program"

For a simple instance, if you have several chip selects for several devices (sometimes on several versions of the same basic PCB) using DMA transfer process and complete interrupts, the context argument to the interrupt could decide which pin(s) should be used for CS for a specific device programming and command depending on how the interrupt was started using that context variable. Interrupts are NOT always totally asynchronous events and you do have control on when they start happening. Many times they are used to start tasks outside of the primary thread of execution even on systems without an OS (without abstracted task structures) to utilize powerful CPU independent hardware on a controller. (modern 8-bit controllers have this capability)
 
Last edited:

Sensacell

Joined Jun 19, 2012
3,768
An ISR is useless if it doesn't accomplish anything, so it needs to have some input and output.

Let's consider a really simple ISR that runs on a little MCU that has no OS, it's really simple and does a few simple tasks.
The ISR is triggered by a hardware timer, the ISR functions as a real-time-clock, counting the number of interrupts.

This ISR updates Hours, Minutes, and Seconds registers. it's an 8 bit system, so the time is stored in multiple byte-wide memory locations.
What happens when the mainline code is reading the time registers, and the ISR fires, in the middle of the multi-byte read? The ISR could roll-over a register and the value read by the mainline code could be totally corrupted.

This is the problem to consider.
The solution is to disable interrupts when you read the critical bytes in the mainline code, so the registers are always read clean, with no chance that the ISR could corrupt the values, mid-read.
 

Thread Starter

MTech1

Joined Feb 15, 2023
181
In conclusion, while it's possible to pass arguments or return values from ISRs, using shared variables with synchronization or callback functions is often a more efficient and robust approach for communication while maintaining fast and non-blocking ISRs
 

BobTPH

Joined Jun 5, 2013
11,463
In conclusion, while it's possible to pass arguments or return values from ISRs
No.

One person has said that, and I challenged
him, and he agreed that the function he is talking about not actually the ISR. The actual ISR is getting the argument from a global variable and passing it to callback function. You can do that as well.

The ISR is activated by the hardware and receives no arguments.
 
Top