I2C function failed when called from ISR

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

I2C function failed when called from ISR

Jump to solution
2,908 Views
kwjohn
Contributor I

Have a problem calling an I2C function from an ISR.

The following code executed with simple_LED_toggle() running OK.

However, when I2C_write_function() is executed, it ran into an infinite loop to check I2C bus busy.

This same I2C_write_function() works 100% OK when it is placed in main() loop.

So, problem is there only when I2C_write_function() is placed in PORTD_IRQHandller()

Any hint on this problem?

void I2C_write_function();

PORTD_IRQHandler()

{

  PORT_HAL_ClearPinIntFlag(portBaseAddr, pin);

  simple_LED_toggle();

  I2C_write_function();

}

0 Kudos
1 Solution
2,159 Views
VictorLorenzo
Contributor IV

Hi John,

The function does not work in the ISR because the low level driver implements transactions using interrupts, the write function configures the hardware IP and the driver code and simply waits for transaction conclusion, that is the loop checking the busy state flag.

You should put the I2C access code out of the ISR, it should not run under interrupt context.

It is a bad design idea, but you could configure the I2C driver for generating interrupts with a higher priority and make sure higher priority interrupts are not disabled in your ISR.

Regards,

Victor

View solution in original post

0 Kudos
8 Replies
2,159 Views
VictorLorenzo
Contributor IV

KW John,

You don't give much details about your platform so this is a guess. Most surely in the BSP your I2C driver is implemented using interrupts, in which case the driver interrupt handling functions will never be called unless they run under a higher interrupt priority. The LED toggle functions can operate under interrupt context as they are simply I/O port write calls that run to completion without firing or requiring interrupts.

In case of desperate need of writing to this I2C device from the interrupt context, configure its driver in polling mode so it does not generate or use interrupts.

As a standard rule we should avoid making blocking accesses to slow devices from interrupt handlers. If you are using MQX, you could design your application using semaphores and flags which you signal/set inside the interrupt handling function and handle in a separate task.

Regards, Victor

2,159 Views
kwjohn
Contributor I

Dear Victor

Thank you. More on this problem. The platform is FRDM-K64F. It is a touch sensor interface with PTE24(SCL), PTE25(SDA), reset to sensor (PTB23), IRQ (PTD1).

PTD1 configured as a GPIO with code here:

//IRQ interface as a GPIO with interrupt enabled

const gpio_input_pin_user_config_t irqPin =

{

  .pinName = GPIO_MAKE_PIN(HW_GPIOD, 1),

  .config.isPullEnable = true,

  .config.pullSelect = kPortPullUp,

  .config.isPassiveFilterEnabled = false,

  .config.interrupt = kPortIntFallingEdge,

};

The interrupt function is like:

void PORTD_IRQHandler(void)

{

     PORT_HAL_ClearPinIntFlag(PORTD_BASE, 1);

     debug_func();

     HAL_I2CWriteRegister(reg, val);

}

I2C is implemented with CMSIS with definition:

/* I2C Driver */

#define _I2C_Driver_(n)  Driver_I2C##n

#define  I2C_Driver_(n) _I2C_Driver_(n)

extern ARM_DRIVER_I2C    I2C_Driver_(0);

#define  ptrI2C         (&I2C_Driver_(0))

No I2C interrupt enabled, I think. Instead, this touch sensor will assert the IRQ pin from high to low whenever a finger is detected. That is why kPortIntFallingEdge is there in const gpio_input_pin_user_config_t irqPin.

HAL_I2CWriteRegister(uint8_t reg, uint16_t val) is declared like this:

static bool HAL_I2CWriteRegister(uint8_t reg, uint16_t val)

{

uint8_t data[3] = {reg, (uint8_t)(val>>8), (uint8_t)val};

ptrI2C->MasterTransmit(0x48, data, 3, false);

while (ptr2C->GetStatus().busy);

if(ptrI2C->GetDataCount() !=3)

{

  return -1;

}

return 0;

}

This same I2C write function works 100% OK in the main loop. It also works in a thread when Bare Metal Abstraction Layer is used, but not in the ISR. It always hang up at the infinite while loop while (ptr2C->GetStatus().busy) in ISR. As a debug action I placed another led toggle function debug_func() in the same ISR which works OK.

This puzzles me a lot.

I agree that semaphore can be used but, why the I2C function doesn't work in ISR? I have implemented the same code in Texas Instruments Tiva C and it works OK, even though there is a blocking function in ISR like the I2C...

John

0 Kudos
2,160 Views
VictorLorenzo
Contributor IV

Hi John,

The function does not work in the ISR because the low level driver implements transactions using interrupts, the write function configures the hardware IP and the driver code and simply waits for transaction conclusion, that is the loop checking the busy state flag.

You should put the I2C access code out of the ISR, it should not run under interrupt context.

It is a bad design idea, but you could configure the I2C driver for generating interrupts with a higher priority and make sure higher priority interrupts are not disabled in your ISR.

Regards,

Victor

0 Kudos
2,159 Views
kwjohn
Contributor I

Hi Victor

Instead of using I2C in ISR, I have followed your recommendation to use RTOS with a semaphore. Posting semaphore from ISR with pending semaphore calling I2C is more elegant.

This problem has been solved. Thanks a lot.

John

0 Kudos
2,159 Views
mjbcswitzerland
Specialist V

Hello John

It is not clear how ptrI2C->MasterTransmit() and ptr2C->GetStatus() operate.

Since the I2C HW is different on the Tiva and the Kinetis the content will not be the same and may also have different implementations/configurations.

Is the bus really busy (when you look at the signals with an oscilloscope)?

What happens when you debug the code?  it should be visible what is being read and why it behaves differently when called from main or when called from the ISR.

You don't seem to be sure whether ptrI2C->MasterTransmit() is using interrupts or not. If it is, it could well fail if the port IRQ's priority is higher than the I2C's interrupt priority - why not set the prorities to ensure no potential problem? Or show the code so that it is visible whether it is interrupt driven or not?

Or attach the binary that fails so that it can be run on the FRDM-K64F to check what is is doing in its failing wait loop.

Regards

Mark

Kinetis: µTasker Kinetis support

K64: µTasker Kinetis FRDM-K64F support  / µTasker Kinetis TWR-K64F120M support  / µTasker Kinetis TWR-K65F180M support

For the complete "out-of-the-box" Kinetis experience and faster time to market

2,159 Views
kwjohn
Contributor I

Hi Mark

Thanks a lot. You are right. I2C ISR has been enabled with one of the CMSIS style statement:

I2Cdrv->PowerControl (ARM_POWER_FULL);

with I2C_PowerControl() run like this: (extracted from I2C_MK64F.c released by Freescale Pack for Keil MDK CMSIS)

static int32_t I2C_PowerControl (ARM_POWER_STATE state, I2C_RESOURCES *i2c) {

  if ((i2c->ctrl->flags & I2C_INIT) == 0U) {
    /* Driver not initialized */
    return (ARM_DRIVER_ERROR);
  }

  switch (state) {
    case ARM_POWER_OFF:
      if (i2c->ctrl->status.busy) {
        /* Transfer in progress */
        return ARM_DRIVER_ERROR_BUSY;
      }

      if (i2c->ctrl->flags & I2C_POWER) {
        i2c->ctrl->flags &= ~(I2C_POWER | I2C_SETUP);

        /* Disable I2C interrupt requests in NVIC */
        NVIC_DisableIRQ (i2c->irq_num);

        /* Disable I2C peripheral */
        I2C_HAL_Disable (i2c->base);

        /* Disable I2C peripheral clock */
        SIM_HAL_DisableClock (SIM_BASE, i2c->clock_gate);
      }
      break;

    case ARM_POWER_LOW:
      return ARM_DRIVER_ERROR_UNSUPPORTED;

    case ARM_POWER_FULL:
      if ((i2c->ctrl->flags & I2C_POWER) == 0U) {
        /* Enable I2C peripheral clock */
        SIM_HAL_EnableClock (SIM_BASE, i2c->clock_gate);

        /* Enable I2C interrupt requests in NVIC */
        NVIC_EnableIRQ (i2c->irq_num);

        /* Initialize peripheral to known state */
        I2C_HAL_Init (i2c->base);

        /* Enable START/STOP condition detection interrupt */
        I2C_HAL_SetStartStopIntCmd (i2c->base, true);

        /* Enable I2C interrupts */
        I2C_HAL_SetIntCmd (i2c->base, true);

        /* Enable I2C peripheral */
        I2C_HAL_Enable (i2c->base);

        /* Ready for operation */
        i2c->ctrl->flags |= I2C_POWER;
      }
      break;
  }

  return ARM_DRIVER_OK;
}

A good lesson here: don't blindly follow those examples from documents/app notes.

Thanks a lot.

0 Kudos
2,159 Views
mjbcswitzerland
Specialist V

Hi

Which procesor and which board?

Do you have a binary that can be run on a standard board to check the reason?

Regards

Mark

0 Kudos
2,159 Views
kwjohn
Contributor I

Mark

The platform is FRDM-K64F. It is a touch sensor interface with PTE24(SCL), PTE25(SDA), reset to sensor (PTB23), IRQ (PTD1). Some more details in another reply to Victor below.

John

0 Kudos