This thread has been locked.

If you have a related question, please click the "Ask a related question" button in the top right corner. The newly created question will be automatically linked to this question.

CC2642R: I2C Slave stops working after break / breakpoint

Part Number: CC2642R
Other Parts Discussed in Thread: SEGGER,

Dear all,

on our custom board we have a C2642R and a STM32F407. Data is exchanged via I2C where the CC is slave and the ST is master.   

The I2C implementation is done according to this example: https://e2e.ti.com/support/wireless-connectivity/bluetooth/f/538/t/557767

The only difference is that data is handled in interrupt context and not in a while loop. 

The master polls the slave every 5ms.

When no debugger is connected everything works fine and as expected. In debug mode, everything is also working but only until the application stops on break or hits a breakpoint (only HW breakpoints are used).

->slave is stopped and master is still running!

When slave is started again the application is still running but no data is receiced on I2C.

After that it is necessary to reset both devices to get a working I2C communication.

I'm using IAR Workbench 8.32.2 and SEGGER j-link. I also tried IAR I-Jet but there is no difference.

I hope someone can help me.

Cheers

Oliver 

  • Are you running a protocol stack that is interrupted when you break the operation?

  • We are running BLE protocol stack based on project zero (SDK 3.10.01.11) and added our own service.

    But that part works after application runs again after a break. Advertising continues and connecting/disconnecting is possible.

  • Hi Oliver,

    Since the project works when the debugger is not conneted but does not work when the debugger is connected, here are some hints:

    - One difference in the two scenarios is timing. Is it possible the issue you are seeing is due to a race condition?

    - You say the fail happens after a break (hitting a breakpoint). What is the I2C master doing in the mean while? Is it taking a break? Is it prepared to handle an unannounced break for the slave?

    - My point is, this may be an expected fail, depending on your application.

  • Hi Marie,

    thanks for the reply. 

    Maybe timing is the reason, but there should be a difference where an when the application breaks. After an breakpoint, no matter where and when,  no more I2C communication until reset.

    During the break the master simply continues polling. There ist no error behavior implemented in debug mode. 

    Without debugger the slave will be reset after a couple of cycles not answering.

    I also noticed that it sometimes comes to a hard fault when only the slave is reset manually.

    So, the scenario:

    1. communication is working fine

    2. hit a breakpoint

    3. run again

    4. no communication, even no i2C interupt (start condition etc.)

    5. reset slave 

    6. sometimes hard fault (see attached file), sometimes same like step 4. (50/50)

    When I reset both master and slave everything is working again. But, the sequence seems to be important.

    Slave needs to be started first and then the master. Due to different start up times this is also the sequence without debugger.

    Maybe, there is a problem when the CC2642 is no fully initialized or when there is already communication during initialization?

    Regards,

    Oliver

    Hard fault exception.pdf

  • Hi Oliver,

    Please don't use breakpoints while the I2C is running. Everything that has to do with timing will be messed up. The data buffer can also be out of sync which can lead to the device trying to read an empty pointer. 

    If you need to debug please use other method such as a logic analyzer.

  • Hi Marie,

    I'm not trying to debug I2C. I want to debug our application but that's hardly possible without getting data in or out. 

    It is OK if there are lost messages or messed up data buffers. Thats's just how it is when hitting a breakpoint in the middle of transfer.

    But it has to be synchronized again when I run the application afterwards. And that's excactly what our host controller from ST does.

    How can that be so difficult for TI?

    Cheers,

    Oliver

  • Hi Oliver,

    The I2C is simply not written to handle breakpoints gracefully. Please don't use breakpoint along with I2C.

    If you tell me more about the bug your are seeing I can try to help you narrow it down.

  • Hi Marie,

    that's too bad. In my opinion, the bug is that I2C is not working after a break-point. 

    Do you see any workaround to get the I2C working again without a reset of the whole chip?

    I already tried to disable/enable and to reinitialize the I2C, but it didn't help.

    My problem is not about a specific bug in our application. It's about debugging and developing in general. 

    Our Main application runs on the host controller and the cc2642 handles the BLE interface and a few additional features. All commands, parameters and data to be sent or received have to be transferred over I2C. Therefore, I2C is essential in our case.

    When I can't use breakpoints when I2C is running, I can't use breakpoints at all.  

    Cheers,

    Oliver

     

  • Hello Oliver,

    I'd like to mention that we don't officially support an I2C Slave driver for the CC2642R.

    You mentioned that the I2C implementation is done according to the code example provided here. This customer provided example is actually incorrect in one regard--it does not register the I2C's hardware interrupt with the kernel. Therefore, the execution context in the kernel's scheduler may become out of sync.

    While this may not resolve your issue, I strongly suggest you make the following changes:

    #include <ti/sysbios/hal/Hwi.h>
    
    static Hwi_Handle i2cSlave_hwiHandle;
    
    void init_i2c()
    {
        I2CSlaveInit(I2C0_BASE, 0x74);
    
        /* Create an RTOS aware hardware interrupt */
        i2cSlave_hwiHandle = Hwi_create(INT_I2C_IRQ, i2cCB, NULL, NULL);
        if (NULL == i2cSlave_hwiHandle) {
            //some error occurred
        }
    
        I2CSlaveIntEnable(I2C0_BASE, I2C_SLAVE_INT_START | I2C_SLAVE_INT_STOP | I2C_SLAVE_INT_DATA);
    }
    
    void i2cCB(UArg arg)
    {
        // I2C Slave Interrupt code
    }

  • Oliver,

    See this FAQ here.

    Derrick

  • Hi Derrick,

    thaks for your reply. I see your point. I tried replacing I2CIntRegister(I2C0_BASE, InterruptServiceRoutine) with your example.

      /* init i2c for slave-mode and enable interrupt */
      I2CSlaveInit(I2C0_BASE, slaveAddress);
      
      /* Create an RTOS aware hardware interrupt */
      i2cSlave_hwiHandle = Hwi_create(INT_I2C_IRQ, InterruptServiceRoutine, NULL, NULL);
      if (NULL == i2cSlave_hwiHandle)
      {
        asm("nop");
        //some error occurred
      }
      //I2CIntRegister(I2C0_BASE, InterruptServiceRoutine);
      I2CSlaveIntEnable(I2C0_BASE, I2C_SLAVE_INT_START | I2C_SLAVE_INT_STOP | I2C_SLAVE_INT_DATA);

    But, unfortunately that does not work as stable as before. Single Bytes are sent and received correctly. Which means from the host point of view I can initialize the CC and can read the status back. But when I'm connected via BLE it's not possible to sent larger amounts of data. It simply hangs up. Thats for a session without debugger. Unfortunately debugging is even harder as with the previous implementation. The I2C interrupt comes once and thats it. No breakpoints this time.

    Do I have to make any other settings? Interrupt priorities? Clear interrupts using RTOS functions?

    This is what happens in the ISR:

    /*********************************************************************
     * @fn      InterruptServiceRoutine
     *
     * @brief   Is called when an I2cSlave-Interrupt occurs.
     *
     *          Clears pending interrupt-flags and calls registered callback functions.
     *          Callback functions can be registered by @ref I2cSlave_IntRegister.
     *
     * @return  None.
     */
    uint32_t isr_count = 0;
    void InterruptServiceRoutine()
    {
      /* check and reset current interrupt conditions */
      uint32_t ActiveI2cInterrupts = I2CSlaveIntStatus(I2C0_BASE, true);
      uint32_t I2cStatus = I2CSlaveStatus(I2C0_BASE);
      I2CSlaveIntClear(I2C0_BASE, ActiveI2cInterrupts);
    
      
      isr_count++;
    
      /* Start condition occurred */
      if(ActiveI2cInterrupts & I2C_SLAVE_INT_START)
      {
        if (isrCallbacks.Start)
          (*isrCallbacks.Start)();
      }
    
      /* Stop condition occurred */
      else if(ActiveI2cInterrupts & I2C_SLAVE_INT_STOP)
      {
        if (isrCallbacks.Stop)
          (*isrCallbacks.Stop)();
      }
    
      /* Data interrupt occurred */
      else if(ActiveI2cInterrupts & I2C_SLAVE_INT_DATA)
      {
        /* Master has sent first byte */
        if(I2cStatus == I2C_SLAVE_ACT_RREQ_FBR)
        {
          if (isrCallbacks.ReceivedFirstByte)
          {
            uint8_t data = I2CSlaveDataGet(I2C0_BASE);
            (*isrCallbacks.ReceivedFirstByte)(data);
          }
        }
    
        /* read (Slave -> Master) */
        else if(I2cStatus == I2C_SLAVE_ACT_TREQ)
        {
          if (isrCallbacks.ReadByte)
          {
            uint8_t data = (*isrCallbacks.ReadByte)();
            I2CSlaveDataPut(I2C0_BASE, data);
          }
          else
          {
            I2CSlaveDataPut(I2C0_BASE, 0);
          }
        }
    
        /* write (Master -> Slave) */
        else if(I2cStatus == I2C_SLAVE_ACT_RREQ)
        {
          if (isrCallbacks.WriteByte)
          {
            uint8_t data = I2CSlaveDataGet(I2C0_BASE);
            (*isrCallbacks.WriteByte)(data);
          }
        }
      }
    }

    And here you can what's implemented in the callbacks:

    #ifdef TESTING_MODE
      volatile uint8_t i2c_running = 0;
    #endif
    
    void IsrCallbackStartCondition()
    {
    #ifdef TESTING_MODE
      i2c_running++;
    #endif
      /* reset r/w-index and refresh the data buffer */
      i2c.ReadCount = 0;
      i2c.WriteCount = 0;
      DtsRegs_GetRegisterData(CurrentRegister, i2c.Buffer, sizeof(i2c.Buffer));
    }
    
    void IsrCallbackStopCondition()
    {
    #ifdef TESTING_MODE
      i2c_running++;
    #endif
      /* apply and postprocess written register data */
      if((i2c.WriteCount > 0) && (Reg.Rights & bmDTSREGS_RIGHTS_WRITE))
        DtsRegs_SetRegisterData(CurrentRegister, i2c.Buffer, i2c.WriteCount);
      i2c_PostprocessRegisterAccess();
    }
    
    void IsrCallbackReceivedFirstByte(uint8_t byte)
    {
    #ifdef TESTING_MODE
      i2c_running++;
    #endif
      /* retain active register address and refresh the data buffer */
      CurrentRegister = (EBleDtsRegister_t)byte;
    #ifdef TAPKEY
      if(CurrentRegister == EBleDtsRegister_TapkeyRxFifoLevel)
      {
        popFromCircularBuffer(&circularBuffcontext, DtsRxFifo, &TkRxFifoLevel);
      }
    #endif
      DtsRegs_GetRegisterInfo(CurrentRegister, &Reg);
      memset(i2c.Buffer, 0x00, sizeof(i2c.Buffer));
      DtsRegs_GetRegisterData(CurrentRegister, i2c.Buffer, sizeof(i2c.Buffer));
    }
    
    uint8_t IsrCallbackReadByte()
    {
    #ifdef TESTING_MODE
      i2c_running++;
    #endif
      /* send next byte of data buffer to master */
      uint8_t byte = i2c.Buffer[i2c.ReadCount++ % sizeof(i2c.Buffer)];
      if(i2c.ReadCount > Reg.Length)
        asm("nop"); // todo error
      return byte;
    }
    
    void IsrCallbackWriteByte(uint8_t byte)
    {
    #ifdef TESTING_MODE
      i2c_running++;
    #endif
      /* receive next byte from master and store to data buffer */
      i2c.Buffer[i2c.WriteCount++ % sizeof(i2c.Buffer)] = byte;
      if(i2c.WriteCount > Reg.Length)
        asm("nop"); // todo error
    }
    

     

  •  Hi Derrick,

    I have a little update. Now I think the ISR is registered to the kernel.

    I used the icall API and put the priority to 0. 

      /* Create an RTOS aware hardware interrupt */
      if(ICall_registerISR_Ext(INT_I2C_IRQ, InterruptServiceRoutine, 0) != ICALL_ERRNO_SUCCESS)
      {
        asm("nop");
      }

    In debug mode it is also working except the breakpoint issue. But, there is another thing I discovered.

    I'm using three interrupt sources. Start condition, stop condition and data interrupt. 

    After hitting a break point the ISR is still called. But, the only sources are start and stop condition. There is no more data interrupt.

      

  • Oliver,

    The ICall_registerISR_Ext() API simply wraps the Hwi_create() API I provided.

    If you wish to set the priority, you will need to populate a Hwi_Params() structure and provide a pointer to the Hwi_create() function as I demonstrate below.

    Hwi_Params i2cSlave_hwiParams;
    Hwi_Params_init(&i2cSlave_hwiParams);
    i2cSlave_hwiParams.priority = INTERRUPT_PRIORITY;
    
    /* Create an RTOS aware hardware interrupt */
    i2cSlave_hwiHandle = Hwi_create(INT_I2C_IRQ, InterruptServiceRoutine, &i2cSlave_hwiParams, NULL);
    if (NULL == i2cSlave_hwiHandle)
    {
        asm("nop");
        //some error occurred
    }

    As far as not receiving a data interrupt, it indicates that the I2C Slave is quite literally not receiving an additional data byte. That's an artifact of the underlying FSM (finite state machine) which runs the I2C peripheral.

    Diving deeper into your ISR code provided, you do NOT account for instances where multiple interrupts occur at the same time (ie START and DATA). Such instances will increase as you have additional interrupts occurring in your application (ie BLE preempts, therefore unable to service I2C interrupt immediately). Additionally, placing a break-point will undoubtedly yield instances where multiple interrupts (ie START, STOP and DATA) are concurrently set

    Your ISR logic is implemented using an "if, else if, else if...." schema. You should be using discrete if cases such as:

    /* Start condition occurred */
    if(ActiveI2cInterrupts & I2C_SLAVE_INT_START)
    {
    }
    
    /* Stop condition occurred */
    if(ActiveI2cInterrupts & I2C_SLAVE_INT_STOP)
    {
    }
    
    /* Data interrupt occurred */
    if(ActiveI2cInterrupts & I2C_SLAVE_INT_DATA)
    {
    }

    For example, if you receive both the START and DATA interrupt, you immediately read and clear the interrupt status. You then service the START condition interrupt but never handle the DATA received. 

    Derrick

  • Hi Derrick,

    thank you very much. That seems to be it. I implemented it as you suggested and every interrupt that occurred is now handled by the ISR.

    The result is that I2C communication mostly works again after a break-point. It takes a little moment to sync and sometimes a reset is still needed. But I can live with that.

    Cheers

    Oliver