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:
Can you tell what condition i didn’t satisfy, so my STOP is not handled properly.