3
\$\begingroup\$

I see that once slave address is written in the interrupt handler, I get an ACK Failure right away, but when I do the same thing with polling approach, I do get an ACK and goes on to read off the values from the device.

With interrupts, I see ISR gets triggered only once (in which it writes slave address) and never gets called again. My i2c configurations are fine, but there's something with interrupts that I seem to be missing out on.

Polling approach

HAL_StatusTypeDef HAL_I2C_Master_Transmit (I2C *hi2c, uint8_t *data, uint8_t size)
{
            GenerateStartCondition();

            // validate the completion of start condition   
            while (!GetFlagStatus(hi2c->Instance, I2C_SR1_SB) && HAL_Timeout(5));

            // write slave address along with write bit
            I2C_WriteSlaveAddress(hi2c, WRITE);

            // wait for address to be sent
            while (!GetFlagStatus(hi2c->Instance, I2C_SR1_ADDR) && HAL_Timeout(5));

            // clear address flag
            I2C_ClearADDRFlag(hi2c->Instance);

            // write data to DR ...
}

With interrupts:

void HAL_StartI2CInterrupts() {
     GenerateStartCondition();

    // setting control bits
    hi2c->Instance->CR2 |= I2C_CR2_ITBUFEN;
    hi2c->Instance->CR2 |= I2C_CR2_ITEVTEN;
    hi2c->Instance->CR2 |= I2C_CR2_ITERREN;

void I2C1_EV_IRQHandler () 
{

    uint8_t event_interrupt = (hi2c->Instance->CR2 & I2C_CR2_ITEVTEN) >> I2C_CR2_ITEVTEN_Pos;

    uint8_t buffer_interrupt = (hi2c->Instance->CR2 & I2C_CR2_ITBUFEN) >> I2C_CR2_ITBUFEN_Pos;

    uint8_t var;

    if (event_interrupt)
    {
        //  validate the completion of START condition
        var =  (hi2c->Instance->SR1 & I2C_SR1_SB) >> I2C_SR1_SB_Pos;
        if (var)
        {
            if (hi2c->I2C_State == I2C_TX_BUSY)
            {               
                I2C_WriteSlaveAddress(hi2c, WRITE);     
            }
        }
         // check ADDR bit ...
     }

}

\$\endgroup\$
7
  • \$\begingroup\$ I'm not sure if it's your problem, but as a general rule, avoid doing too much in an interrupt handler. It is a bit suspicious that you are calling I2C_WriteSlaveAddress inside the handler, unless that is how it is intended to be used. \$\endgroup\$
    – Justin
    Mar 31 '20 at 15:09
  • \$\begingroup\$ I need to service the i2c interrupt though. After generating the start condition, I have to send the slave address for me to talk to it, no? @Justin \$\endgroup\$
    – xyf
    Mar 31 '20 at 18:28
  • \$\begingroup\$ Yes, looking at other examples online, it looks like it is fine. Can you look at SDA/SCL on a scope and see if the slave device is actually not sending an ACK? \$\endgroup\$
    – Justin
    Mar 31 '20 at 18:34
  • \$\begingroup\$ Yes, it does not. Do you know if there's any particular way of using interrupts? the only thing I can think of is using incorrect slave address which clearly isn't the case cause it does work with polling method. @Justin \$\endgroup\$
    – xyf
    Mar 31 '20 at 19:15
  • \$\begingroup\$ also, does it make sense to call HAL_StartI2CInterrupts() repeatedly till TXing is done/stop-condition is generated? to me it doesn't make sense but I saw an example online that does that. @Justin \$\endgroup\$
    – xyf
    Mar 31 '20 at 19:31
0
\$\begingroup\$

ST's HAL could be generating a racing condition when called inside an ISR. As mentioned by @Justin, you don't want to occupy the CPU for too long when in an ISR. For your NACK when writing inside an ISR I see two solutions:

  1. Use a flag. Set it on the ISR, on your main loop check for the raised flag, if so send the data through I2C;

  2. Use DMA. HAL DMA calls are not blocking and the driver will deal with writing on the peripheral themselves.

Solution 1 is simpler but less deterministic. 1 would look something like:

static volatile uint8_t i2cSendFlag = 0;

void I2C1_EV_IRQHandler(void){
    i2cSendFlag = 1;
    I2C_ClearFlag(<the active ISR on your code>);
}

static I2CDoWork(void){
    if(i2cSendFlag){
        // Send your data

        // Clear the send flag
        i2cSendFlag = 0;
    }
}

int main(void){
    I2CConfig(I2C1);
    for(;;){
        I2CDoWork();
        // Rest of your code
    }
}

The ISR being called only once seems like peripheral's flags are not been cleared. From 1 each active ISR event is cleared by a differently method. On your HAL configuration step witch event flags are active? Check the below table to see how to clear the corresponding event on the ISR function.

I2C IRQ requests

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.