1
votes

I am using KEIL. Here is my code for I2C setup. I am trying to set it up using interrupts. Actually, everything works fine like this. But only problem I have is that I did one strange thing in order for it to work.

// Variables passed to I2C1_EV_IRQHandler 
static volatile uint8_t counter=0;
static uint8_t slave_address=0, *data_buff,buff_num_bytes=0;

//I2c event and error interrupts setup
void I2C_NVIC_setup(void){
    NVIC->ISER[0] |=NVIC_ISER_SETENA_31; //I2C_IRQ_EV is at 31 position
    NVIC->ISER[1] |=NVIC_ISER_SETENA_0; //I2C_IRQ_ER is at 32 position
    NVIC->IP[31] &= ~0xF0;
    NVIC->IP[31]= (uint8_t)(0x5<<4);    //Event has lower priority
    NVIC->IP[32] &= ~0xF0;
    NVIC->IP[32]= (uint8_t)(0x4<<4);    //Error has higher priority
    __enable_irq();         //ENABLE interrupts
}

//GPIO settings for PB6 and PB7
void I2C_GPIO_init(void){
    RCC->APB2ENR |= RCC_APB2ENR_IOPBEN; //PORTB clock enable

    GPIOB->CRL |= GPIO_CRL_CNF6 |GPIO_CRL_CNF7;         //PB6 and PB7- open drain
    uint32_t buff=GPIOB->CRL;
    buff &= ~GPIO_CRL_MODE6 & ~GPIO_CRL_MODE7;  
    buff |= GPIO_CRL_MODE6_0 | GPIO_CRL_MODE7_0 ;   //2MHz maximum output speed
    GPIOB->CRL=buff; 
    //Dont care about ODR register
}

// Initialization of I2C
bool I2C_init(void){
I2C_GPIO_init();
    I2C_NVIC_setup();
    RCC->APB1ENR |= RCC_APB1ENR_I2C1EN;

    I2C1->CR2 &= ~I2C_CR2_FREQ;
    I2C1->CR2 |= 0x14; //APB1 frequency is 20MHz

    I2C1->CCR |=  I2C_CCR_FS; //Fast mode
    I2C1->CCR |= I2C_CCR_DUTY;  //Duty cycle 16/9(for fast mode)

    I2C1->CCR &= ~I2C_CCR_CCR;
    I2C1->CCR |=    0x04;           //Thigh=1800ns Tlow=3200ns -200KHz frequency

    I2C1->TRISE &= ~I2C_TRISE_TRISE;
    I2C1->TRISE |= 0x07;    //300ns(max Trise)/50ns(Tclck) +1

    I2C1->CR1 |=    I2C_CR1_PE; //Enable peripheral

    I2C1->CR2 |= I2C_CR2_ITBUFEN; //Enable interupts of TxE and RxNE
    I2C1->CR2 |= I2C_CR2_ITEVTEN; //Enable event interupts
    I2C1->CR2 |= I2C_CR2_ITERREN; //Enable error interupts
}
//Master transmitter
bool I2C_transmit(uint8_t b7_address, uint8_t* data, uint8_t num_bytes){
    counter=num_bytes;
    buff_num_bytes=num_bytes;
    data_buff=data;
    slave_address=b7_address<<1 ;//To enter transmiter mode LSB=0

    I2C1->CR1 |= I2C_CR1_START; //Start condition
    while(counter || (I2C1->SR2 & I2C_SR2_MSL) || (I2C1->CR1 & I2C_CR1_STOP)){ //Stop bit is checked because of the problem I have
    }
}   
//IRQ I2C1 event handler
void I2C1_EV_IRQHandler(void){ //Handle the interrupt of I2C1
    uint8_t buff_sr1=I2C1->SR1;
    if(buff_sr1 & I2C_SR1_SB){ //SB bit is set(unsetting by read SR1 and writing adress to DR) 
        I2C1->DR=slave_address; 
    }else if(buff_sr1 & I2C_SR1_ADDR){ //ADDR bit is set(unsetting by read SR1 and read SR2)            
        (void)I2C1->SR2; 
    }
    if ((buff_sr1 & I2C_SR1_TXE)&& !(counter==0)){  //Checking TxE( clearing by writting to DR)
        I2C1->DR=data_buff[buff_num_bytes-counter];
        counter--;
    } else if((buff_sr1 & I2C_SR1_TXE)&&(buff_sr1 & I2C_SR1_BTF)){ //Checking stop 
      //condition(TxE=1,BTF=1,counter=0)
        (void)I2C1->SR1; //Dont know why, but it works(just read some I2C1 register)
        I2C1->CR1 |= I2C_CR1_STOP;  //Generate stop condition
    }

}

If you take a look at the last several lines:

    } else if((buff_sr1 & I2C_SR1_TXE)&&(buff_sr1 & I2C_SR1_BTF)){ //Checking stop 
      //condition(TxE=1,BTF=1,counter=0)
        (void)I2C1->SR1; //Dont know why, but it works(just read some I2C1 register)
        I2C1->CR1 |= I2C_CR1_STOP;  //Generate stop condition
    }

Here when I was debugging (this code, without line (void)I2C1->SR1;), when I tried to send data, first package would be sent without any problems. At the end it would generate stop condition (MSL bit would be cleared, and line released), but STOP bit would stay set (that’s why I put STOP bit check in I2C_transmit while loop). If I would manually clear the STOP bit in debug windows, it would continue, but in the next transmit cycle it wouldn’t generate proper start (SB would be set, but MSL bit would stay at reset state).

While debugging, if i would put breakpoint before this line:

(keep in mind that at this time I didn’t find solution for this problem, so everything the same just without this line- (void)I2C1->SR1;)

I2C1->CR1 |= I2C_CR1_STOP;  //Generate stop condition

After stopping there, examining the registers, and then continuing, everything worked fine. I found on the stackexchage that:

Especially the part about reading the registers can give you a debugging hell as the read of the debugger will trigger the state machine just like a read of your code, so as soon as you stop and inspect the registers (sometimes the debugger does it all automatically) the read happens and the peripheral will do something again.

It was clear that when I was triggering STOP condition, some requirements were not satisfied. So I decided to check whether this is the case, and seems like it was a solution. Adding read of whatever register of I2C((void)I2C1->SR1; //or any other I2C register), solves the problem. There must be logical explanation, but I can not find it. I studied reference manual really carefully, the result is this code, but I didn’t find anything which would explain me this.

Here is table from Reference manual about Master transmitting, for your reference: enter image description here

Can you tell what condition i didn’t satisfy, so my STOP is not handled properly.

2
1 << 14 magic numbers == no help. Respect our time and use human readable definitions from CMSIS0___________
@P__J__ You are right. I changed all to CMSIS definitionsVaso
FWIW, I do not see any mention of this in the F1 series errata st.com/resource/en/errata_sheet/cd00190234.pdf There is a mention about BERR and misplaced STOP, but I do not think it applies in your case.Richard at ImageCraft
@RichardatImageCraft Thank you for your answer. I also checked errata, yeah, no mention found about this problem there.Vaso
I did not write my code as an interrupt driven state machine, but as straight line polling code. In that case, I do know that my code works without reading the SR before generating the STOP condition, and this is the case with F1xx (your case), L0xx, F2xx, F4xx, and F7xx. Not sure how it could make a difference though.Richard at ImageCraft

2 Answers

1
votes

OK, I just converted my test program to use interrupt driven transmit based on this code. My test is on a STM32F411RE, which has a similar I2C implementation with the F103 (the other series such as F0xx, F3xx, and F7xx use another I2C implementation). Anyway, at least I can confirm that without the "(void)I2C1->SR1;" in the transmit interrupt handler, it does not work. It only works if the line is added in.

The test blinks an 8x8 LED matrix using an I2C expander chip, so there are 64*2+ I2C transmit calls to walk the LED matrix.

Since the F411RE is a current and newer than the F103, my guess is that this is an undocumented requirement!

Now at least in my test, the "(I2C1->CR1 & I2C_CR1_STOP)" is not needed in the transmit function.

0
votes
I2C1->CR1|=0x0200;  // stop (bit 9 set for stop)
while(I2C1->SR2&0x0002);  // Bus busy

These two instructions are used to set the Stop Bit and clear the Bus Busy flag.